1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * TextTool
4  *
5  * Authors:
6  *   Lauris Kaplinski <lauris@kaplinski.com>
7  *   bulia byak <buliabyak@users.sf.net>
8  *   Jon A. Cruz <jon@joncruz.org>
9  *   Abhishek Sharma
10  *
11  * Copyright (C) 1999-2005 authors
12  * Copyright (C) 2001 Ximian, Inc.
13  *
14  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
15  */
16 
17 #include <gdk/gdkkeysyms.h>
18 #include <gtkmm/clipboard.h>
19 #include <glibmm/i18n.h>
20 #include <glibmm/regex.h>
21 
22 #include "text-tool.h"
23 
24 #include "context-fns.h"
25 #include "desktop-style.h"
26 #include "desktop.h"
27 #include "document-undo.h"
28 #include "document.h"
29 #include "include/macros.h"
30 #include "inkscape.h"
31 #include "message-context.h"
32 #include "message-stack.h"
33 #include "rubberband.h"
34 #include "selection-chemistry.h"
35 #include "selection.h"
36 #include "style.h"
37 #include "text-editing.h"
38 #include "verbs.h"
39 
40 #include "display/control/canvas-item-curve.h"
41 #include "display/control/canvas-item-quad.h"
42 #include "display/control/canvas-item-rect.h"
43 #include "display/control/canvas-item-bpath.h"
44 #include "display/curve.h"
45 
46 #include "object/sp-flowtext.h"
47 #include "object/sp-namedview.h"
48 #include "object/sp-text.h"
49 #include "object/sp-textpath.h"
50 #include "object/sp-rect.h"
51 #include "object/sp-shape.h"
52 #include "object/sp-ellipse.h"
53 
54 #include "ui/knot/knot-holder.h"
55 #include "ui/shape-editor.h"
56 #include "ui/widget/canvas.h"
57 #include "ui/event-debug.h"
58 
59 #include "xml/attribute-record.h"
60 #include "xml/node-event-vector.h"
61 #include "xml/sp-css-attr.h"
62 
63 using Inkscape::DocumentUndo;
64 
65 namespace Inkscape {
66 namespace UI {
67 namespace Tools {
68 
69 static void sp_text_context_validate_cursor_iterators(TextTool *tc);
70 static void sp_text_context_update_cursor(TextTool *tc, bool scroll_to_see = true);
71 static void sp_text_context_update_text_selection(TextTool *tc);
72 static gint sp_text_context_timeout(TextTool *tc);
73 static void sp_text_context_forget_text(TextTool *tc);
74 
75 static gint sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, TextTool *tc);
76 static gint sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, TextTool *tc);
77 static void sptc_commit(GtkIMContext *imc, gchar *string, TextTool *tc);
78 
getPrefsPath()79 const std::string& TextTool::getPrefsPath() {
80     return TextTool::prefsPath;
81 }
82 
83 const std::string TextTool::prefsPath = "/tools/text";
84 
85 
TextTool()86 TextTool::TextTool()
87     : ToolBase("text.svg")
88 {
89 }
90 
~TextTool()91 TextTool::~TextTool() {
92     delete this->shape_editor;
93     this->shape_editor = nullptr;
94 
95     ungrabCanvasEvents();
96 
97     Inkscape::Rubberband::get(this->desktop)->stop();
98 }
99 
setup()100 void TextTool::setup() {
101     GtkSettings* settings = gtk_settings_get_default();
102     gint timeout = 0;
103     g_object_get( settings, "gtk-cursor-blink-time", &timeout, NULL );
104 
105     if (timeout < 0) {
106         timeout = 200;
107     } else {
108         timeout /= 2;
109     }
110 
111     cursor = new Inkscape::CanvasItemCurve(desktop->getCanvasControls());
112     cursor->set_stroke(0x000000ff);
113     cursor->hide();
114 
115     // The rectangle box tightly wrapping text object when selected or under cursor.
116     indicator = new Inkscape::CanvasItemRect(desktop->getCanvasControls());
117     indicator->set_stroke(0x0000ff7f);
118     indicator->set_shadow(0xffffff7f, 1);
119     indicator->hide();
120 
121     // The rectangle box outlining wrapping the shape for text in a shape.
122     frame = new Inkscape::CanvasItemBpath(desktop->getCanvasControls());
123     frame->set_fill(0x00 /* zero alpha */, SP_WIND_RULE_NONZERO);
124     frame->set_stroke(0x0000ff7f);
125     frame->hide();
126 
127     this->timeout = g_timeout_add(timeout, (GSourceFunc) sp_text_context_timeout, this);
128 
129     this->imc = gtk_im_multicontext_new();
130     if (this->imc) {
131         GtkWidget *canvas = GTK_WIDGET(desktop->getCanvas()->gobj());
132 
133         /* im preedit handling is very broken in inkscape for
134          * multi-byte characters.  See bug 1086769.
135          * We need to let the IM handle the preediting, and
136          * just take in the characters when they're finished being
137          * entered.
138          */
139         gtk_im_context_set_use_preedit(this->imc, FALSE);
140         gtk_im_context_set_client_window(this->imc,
141             gtk_widget_get_window (canvas));
142 
143         g_signal_connect(G_OBJECT(canvas), "focus_in_event", G_CALLBACK(sptc_focus_in), this);
144         g_signal_connect(G_OBJECT(canvas), "focus_out_event", G_CALLBACK(sptc_focus_out), this);
145         g_signal_connect(G_OBJECT(this->imc), "commit", G_CALLBACK(sptc_commit), this);
146 
147         if (gtk_widget_has_focus(canvas)) {
148             sptc_focus_in(canvas, nullptr, this);
149         }
150     }
151 
152     ToolBase::setup();
153 
154     this->shape_editor = new ShapeEditor(this->desktop);
155 
156     SPItem *item = this->desktop->getSelection()->singleItem();
157     if (item && (
158             (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) ||
159             (SP_IS_TEXT(item) && !SP_TEXT(item)->has_shape_inside())           )
160         ) {
161         this->shape_editor->set_item(item);
162     }
163 
164     this->sel_changed_connection = desktop->getSelection()->connectChangedFirst(
165         sigc::mem_fun(*this, &TextTool::_selectionChanged)
166     );
167     this->sel_modified_connection = desktop->getSelection()->connectModifiedFirst(
168         sigc::mem_fun(*this, &TextTool::_selectionModified)
169     );
170     this->style_set_connection = desktop->connectSetStyle(
171         sigc::mem_fun(*this, &TextTool::_styleSet)
172     );
173     this->style_query_connection = desktop->connectQueryStyle(
174         sigc::mem_fun(*this, &TextTool::_styleQueried)
175     );
176 
177     _selectionChanged(desktop->getSelection());
178 
179     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
180     if (prefs->getBool("/tools/text/selcue")) {
181         this->enableSelectionCue();
182     }
183     if (prefs->getBool("/tools/text/gradientdrag")) {
184         this->enableGrDrag();
185     }
186 }
187 
finish()188 void TextTool::finish() {
189     if (this->desktop) {
190         sp_signal_disconnect_by_data(this->desktop->getCanvas()->gobj(), this);
191     }
192 
193     this->enableGrDrag(false);
194 
195     this->style_set_connection.disconnect();
196     this->style_query_connection.disconnect();
197     this->sel_changed_connection.disconnect();
198     this->sel_modified_connection.disconnect();
199 
200     sp_text_context_forget_text(SP_TEXT_CONTEXT(this));
201 
202     if (this->imc) {
203         g_object_unref(G_OBJECT(this->imc));
204         this->imc = nullptr;
205     }
206 
207     if (this->timeout) {
208         g_source_remove(this->timeout);
209         this->timeout = 0;
210     }
211 
212     if (cursor) {
213         delete cursor;
214         cursor = nullptr;
215     }
216 
217     if (this->indicator) {
218         delete indicator;
219         this->indicator = nullptr;
220     }
221 
222     if (this->frame) {
223         delete frame;
224         this->frame = nullptr;
225     }
226 
227     for (auto & text_selection_quad : text_selection_quads) {
228         text_selection_quad->hide();
229         delete text_selection_quad;
230     }
231     text_selection_quads.clear();
232 
233     ToolBase::finish();
234 }
235 
item_handler(SPItem * item,GdkEvent * event)236 bool TextTool::item_handler(SPItem* item, GdkEvent* event) {
237     SPItem *item_ungrouped;
238 
239     gint ret = FALSE;
240     sp_text_context_validate_cursor_iterators(this);
241     Inkscape::Text::Layout::iterator old_start = this->text_sel_start;
242 
243     switch (event->type) {
244         case GDK_BUTTON_PRESS:
245             if (event->button.button == 1) {
246                 // this var allow too much lees subbselection queries
247                 // reducing it to cursor iteracion, mouseup and down
248                 // find out clicked item, disregarding groups
249                 item_ungrouped = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE);
250                 if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
251                     desktop->getSelection()->set(item_ungrouped);
252                     if (this->text) {
253                         // find out click point in document coordinates
254                         Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
255                         // set the cursor closest to that point
256                         if (event->button.state & GDK_SHIFT_MASK) {
257                             this->text_sel_start = old_start;
258                             this->text_sel_end = sp_te_get_position_by_coords(this->text, p);
259                         } else {
260                             this->text_sel_start = this->text_sel_end = sp_te_get_position_by_coords(this->text, p);
261                         }
262                         // update display
263                         sp_text_context_update_cursor(this);
264                         sp_text_context_update_text_selection(this);
265                         this->dragging = 1;
266                     }
267                     ret = TRUE;
268                 }
269             }
270             break;
271         case GDK_2BUTTON_PRESS:
272             if (event->button.button == 1 && this->text && this->dragging) {
273                 Inkscape::Text::Layout const *layout = te_get_layout(this->text);
274                 if (layout) {
275                     if (!layout->isStartOfWord(this->text_sel_start))
276                         this->text_sel_start.prevStartOfWord();
277                     if (!layout->isEndOfWord(this->text_sel_end))
278                         this->text_sel_end.nextEndOfWord();
279                     sp_text_context_update_cursor(this);
280                     sp_text_context_update_text_selection(this);
281                     this->dragging = 2;
282                     ret = TRUE;
283                 }
284             }
285             break;
286         case GDK_3BUTTON_PRESS:
287             if (event->button.button == 1 && this->text && this->dragging) {
288                 this->text_sel_start.thisStartOfLine();
289                 this->text_sel_end.thisEndOfLine();
290                 sp_text_context_update_cursor(this);
291                 sp_text_context_update_text_selection(this);
292                 this->dragging = 3;
293                 ret = TRUE;
294             }
295             break;
296         case GDK_BUTTON_RELEASE:
297             if (event->button.button == 1 && this->dragging) {
298                 this->dragging = 0;
299                 sp_event_context_discard_delayed_snap_event(this);
300                 ret = TRUE;
301                 desktop->emitToolSubselectionChanged((gpointer)this);
302             }
303             break;
304         case GDK_MOTION_NOTIFY:
305             break;
306         default:
307             break;
308     }
309 
310     if (!ret) {
311         ret = ToolBase::item_handler(item, event);
312     }
313 
314     return ret;
315 }
316 
sp_text_context_setup_text(TextTool * tc)317 static void sp_text_context_setup_text(TextTool *tc)
318 {
319     SPDesktop *desktop = tc->getDesktop();
320 
321     /* Create <text> */
322     Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
323     Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
324     rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
325 
326     /* Set style */
327     sp_desktop_apply_style_tool(desktop, rtext, "/tools/text", true);
328 
329     sp_repr_set_svg_double(rtext, "x", tc->pdoc[Geom::X]);
330     sp_repr_set_svg_double(rtext, "y", tc->pdoc[Geom::Y]);
331 
332     /* Create <tspan> */
333     Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
334     rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
335     rtext->addChild(rtspan, nullptr);
336     Inkscape::GC::release(rtspan);
337 
338     /* Create TEXT */
339     Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
340     rtspan->addChild(rstring, nullptr);
341     Inkscape::GC::release(rstring);
342     SPItem *text_item = SP_ITEM(desktop->currentLayer()->appendChildRepr(rtext));
343     /* fixme: Is selection::changed really immediate? */
344     /* yes, it's immediate .. why does it matter? */
345     desktop->getSelection()->set(text_item);
346     Inkscape::GC::release(rtext);
347     text_item->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
348 
349     text_item->updateRepr();
350     text_item->doWriteTransform(text_item->transform, nullptr, true);
351     DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_TEXT,
352                _("Create text"));
353 }
354 
355 /**
356  * Insert the character indicated by tc.uni to replace the current selection,
357  * and reset tc.uni/tc.unipos to empty string.
358  *
359  * \pre tc.uni/tc.unipos non-empty.
360  */
insert_uni_char(TextTool * const tc)361 static void insert_uni_char(TextTool *const tc)
362 {
363     g_return_if_fail(tc->unipos
364                      && tc->unipos < sizeof(tc->uni)
365                      && tc->uni[tc->unipos] == '\0');
366     unsigned int uv;
367     std::stringstream ss;
368     ss << std::hex << tc->uni;
369     ss >> uv;
370     tc->unipos = 0;
371     tc->uni[tc->unipos] = '\0';
372 
373     if ( !g_unichar_isprint(static_cast<gunichar>(uv))
374          && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
375         // This may be due to bad input, so it goes to statusbar.
376         tc->getDesktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE,
377                                                 _("Non-printable character"));
378     } else {
379         if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
380             sp_text_context_setup_text(tc);
381             tc->nascent_object = false; // we don't need it anymore, having created a real <text>
382         }
383 
384         gchar u[10];
385         guint const len = g_unichar_to_utf8(uv, u);
386         u[len] = '\0';
387 
388         tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
389         sp_text_context_update_cursor(tc);
390         sp_text_context_update_text_selection(tc);
391         DocumentUndo::done(tc->getDesktop()->getDocument(), SP_VERB_DIALOG_TRANSFORM,
392                _("Insert Unicode character"));
393     }
394 }
395 
hex_to_printable_utf8_buf(char const * const ehex,char * utf8)396 static void hex_to_printable_utf8_buf(char const *const ehex, char *utf8)
397 {
398     unsigned int uv;
399     std::stringstream ss;
400     ss << std::hex << ehex;
401     ss >> uv;
402     if (!g_unichar_isprint((gunichar) uv)) {
403         uv = 0xfffd;
404     }
405     guint const len = g_unichar_to_utf8(uv, utf8);
406     utf8[len] = '\0';
407 }
408 
show_curr_uni_char(TextTool * const tc)409 static void show_curr_uni_char(TextTool *const tc)
410 {
411     g_return_if_fail(tc->unipos < sizeof(tc->uni)
412                      && tc->uni[tc->unipos] == '\0');
413     if (tc->unipos) {
414         char utf8[10];
415         hex_to_printable_utf8_buf(tc->uni, utf8);
416 
417         /* Status bar messages are in pango markup, so we need xml escaping. */
418         if (utf8[1] == '\0') {
419             switch(utf8[0]) {
420                 case '<': strcpy(utf8, "&lt;"); break;
421                 case '>': strcpy(utf8, "&gt;"); break;
422                 case '&': strcpy(utf8, "&amp;"); break;
423                 default: break;
424             }
425         }
426         tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
427                                           _("Unicode (<b>Enter</b> to finish): %s: %s"), tc->uni, utf8);
428     } else {
429         tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
430     }
431 }
432 
root_handler(GdkEvent * event)433 bool TextTool::root_handler(GdkEvent* event) {
434 
435 #if EVENT_DEBUG
436     ui_dump_event(reinterpret_cast<GdkEvent *>(event), "TextTool::root_handler");
437 #endif
438 
439     indicator->hide();
440 
441     sp_text_context_validate_cursor_iterators(this);
442 
443     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
444     tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
445 
446     switch (event->type) {
447         case GDK_BUTTON_PRESS:
448             if (event->button.button == 1) {
449 
450                 if (Inkscape::have_viable_layer(desktop, desktop->getMessageStack()) == false) {
451                     return TRUE;
452                 }
453 
454                 // save drag origin
455                 this->xp = (gint) event->button.x;
456                 this->yp = (gint) event->button.y;
457                 this->within_tolerance = true;
458 
459                 Geom::Point const button_pt(event->button.x, event->button.y);
460                 Geom::Point button_dt(desktop->w2d(button_pt));
461 
462                 SnapManager &m = desktop->namedview->snap_manager;
463                 m.setup(desktop);
464                 m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE);
465                 m.unSetup();
466 
467                 this->p0 = button_dt;
468                 Inkscape::Rubberband::get(desktop)->start(desktop, this->p0);
469 
470                 grabCanvasEvents();
471 
472                 this->creating = true;
473 
474                 /* Processed */
475                 return TRUE;
476             }
477             break;
478         case GDK_MOTION_NOTIFY: {
479             if (this->creating && (event->motion.state & GDK_BUTTON1_MASK)) {
480                 if ( this->within_tolerance
481                      && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance )
482                      && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) {
483                     break; // do not drag if we're within tolerance from origin
484                 }
485                 // Once the user has moved farther than tolerance from the original location
486                 // (indicating they intend to draw, not click), then always process the
487                 // motion notify coordinates as given (no snapping back to origin)
488                 this->within_tolerance = false;
489 
490                 Geom::Point const motion_pt(event->motion.x, event->motion.y);
491                 Geom::Point p = desktop->w2d(motion_pt);
492 
493                 SnapManager &m = desktop->namedview->snap_manager;
494                 m.setup(desktop);
495                 m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE);
496                 m.unSetup();
497 
498                 Inkscape::Rubberband::get(desktop)->move(p);
499                 gobble_motion_events(GDK_BUTTON1_MASK);
500 
501                 // status text
502                 Inkscape::Util::Quantity x_q = Inkscape::Util::Quantity(fabs((p - this->p0)[Geom::X]), "px");
503                 Inkscape::Util::Quantity y_q = Inkscape::Util::Quantity(fabs((p - this->p0)[Geom::Y]), "px");
504                 Glib::ustring xs = x_q.string(desktop->namedview->display_units);
505                 Glib::ustring ys = y_q.string(desktop->namedview->display_units);
506                 this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Flowed text frame</b>: %s &#215; %s"), xs.c_str(), ys.c_str());
507             } else if (!this->sp_event_context_knot_mouseover()) {
508                 SnapManager &m = desktop->namedview->snap_manager;
509                 m.setup(desktop);
510 
511                 Geom::Point const motion_w(event->motion.x, event->motion.y);
512                 Geom::Point motion_dt(desktop->w2d(motion_w));
513                 m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE));
514                 m.unSetup();
515             }
516             if ((event->motion.state & GDK_BUTTON1_MASK) && this->dragging) {
517                 Inkscape::Text::Layout const *layout = te_get_layout(this->text);
518                 if (!layout)
519                     break;
520                 // find out click point in document coordinates
521                 Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
522                 // set the cursor closest to that point
523                 Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(this->text, p);
524                 if (this->dragging == 2) {
525                     // double-click dragging: go by word
526                     if (new_end < this->text_sel_start) {
527                         if (!layout->isStartOfWord(new_end))
528                             new_end.prevStartOfWord();
529                     } else if (!layout->isEndOfWord(new_end))
530                         new_end.nextEndOfWord();
531                 } else if (this->dragging == 3) {
532                     // triple-click dragging: go by line
533                     if (new_end < this->text_sel_start)
534                         new_end.thisStartOfLine();
535                     else
536                         new_end.thisEndOfLine();
537                 }
538                 // update display
539                 if (this->text_sel_end != new_end) {
540                     this->text_sel_end = new_end;
541                     sp_text_context_update_cursor(this);
542                     sp_text_context_update_text_selection(this);
543                 }
544                 gobble_motion_events(GDK_BUTTON1_MASK);
545                 break;
546             }
547             // find out item under mouse, disregarding groups
548             SPItem *item_ungrouped =
549                 desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE, nullptr);
550             if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
551                 Inkscape::Text::Layout const *layout = te_get_layout(item_ungrouped);
552                 if (layout->inputTruncated()) {
553                     indicator->set_stroke(0xff0000ff);
554                 } else {
555                     indicator->set_stroke(0x0000ff7f);
556                 }
557                 Geom::OptRect ibbox = item_ungrouped->desktopVisualBounds();
558                 if (ibbox) {
559                     indicator->set_rect(*ibbox);
560                 }
561                 indicator->show();
562 
563                 cursor_filename = "text-insert.svg";
564                 this->sp_event_context_update_cursor();
565                 sp_text_context_update_text_selection(this);
566                 if (SP_IS_TEXT(item_ungrouped)) {
567                     desktop->event_context->defaultMessageContext()->set(
568                         Inkscape::NORMAL_MESSAGE,
569                         _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
570                 } else {
571                     desktop->event_context->defaultMessageContext()->set(
572                         Inkscape::NORMAL_MESSAGE,
573                         _("<b>Click</b> to edit the flowed text, <b>drag</b> to select part of the text."));
574                 }
575                 this->over_text = true;
576             } else {
577                 this->over_text = false;
578                 // update cursor and statusbar: we are not over a text object now
579                 cursor_filename = "text.svg";
580                 this->sp_event_context_update_cursor();
581                 desktop->event_context->defaultMessageContext()->clear();
582             }
583         } break;
584 
585         case GDK_BUTTON_RELEASE:
586             if (event->button.button == 1) {
587                 sp_event_context_discard_delayed_snap_event(this);
588 
589                 Geom::Point p1 = desktop->w2d(Geom::Point(event->button.x, event->button.y));
590 
591                 SnapManager &m = desktop->namedview->snap_manager;
592                 m.setup(desktop);
593                 m.freeSnapReturnByRef(p1, Inkscape::SNAPSOURCE_NODE_HANDLE);
594                 m.unSetup();
595 
596                 ungrabCanvasEvents();
597 
598                 Inkscape::Rubberband::get(desktop)->stop();
599 
600                 if (this->creating && this->within_tolerance) {
601                     /* Button 1, set X & Y & new item */
602                     desktop->getSelection()->clear();
603                     this->pdoc = desktop->dt2doc(p1);
604                     this->show = TRUE;
605                     this->phase = true;
606                     this->nascent_object = true; // new object was just created
607 
608                     /* Cursor */
609                     cursor->show();
610                     // Cursor height is defined by the new text object's font size; it needs to be set
611                     // artificially here, for the text object does not exist yet:
612                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
613                     auto const y_dir = desktop->yaxisdir();
614                     Geom::Point const cursor_size(0, y_dir * cursor_height);
615                     cursor->set_coords(p1, p1 - cursor_size);
616                     if (this->imc) {
617                         GdkRectangle im_cursor;
618                         Geom::Point const top_left = desktop->get_display_area().corner(0);
619                         Geom::Point const im_d0 = desktop->d2w(p1 - top_left);
620                         Geom::Point const im_d1 = desktop->d2w(p1 - cursor_size - top_left);
621                         Geom::Rect const im_rect(im_d0, im_d1);
622                         im_cursor.x = (int) floor(im_rect.left());
623                         im_cursor.y = (int) floor(im_rect.top());
624                         im_cursor.width = (int) floor(im_rect.width());
625                         im_cursor.height = (int) floor(im_rect.height());
626                         gtk_im_context_set_cursor_location(this->imc, &im_cursor);
627                     }
628                     this->message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line.")); // FIXME:: this is a copy of a string from _update_cursor below, do not desync
629 
630                     this->within_tolerance = false;
631                 } else if (this->creating) {
632                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
633                     if (fabs(p1[Geom::Y] - this->p0[Geom::Y]) > cursor_height) {
634                         // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
635 
636                         if (prefs->getBool("/tools/text/use_svg2", true)) {
637                             // SVG 2 text
638 
639                             SPItem *text = create_text_with_rectangle (desktop, this->p0, p1);
640 
641                             desktop->getSelection()->set(text);
642 
643                         } else {
644                             // SVG 1.2 text
645 
646                             SPItem *ft = create_flowtext_with_internal_frame (desktop, this->p0, p1);
647 
648                             desktop->getSelection()->set(ft);
649                         }
650 
651                         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
652                         DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_TEXT, _("Create flowed text"));
653 
654                     } else {
655                         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
656                     }
657                 }
658                 this->creating = false;
659                 desktop->emitToolSubselectionChanged((gpointer)this);
660                 return TRUE;
661             }
662             break;
663         case GDK_KEY_PRESS: {
664             guint const group0_keyval = get_latin_keyval(&event->key);
665 
666             if (group0_keyval == GDK_KEY_KP_Add ||
667                 group0_keyval == GDK_KEY_KP_Subtract) {
668                 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
669                     break; // otherwise pass on keypad +/- so they can zoom
670             }
671 
672             if ((this->text) || (this->nascent_object)) {
673                 // there is an active text object in this context, or a new object was just created
674 
675                 if (this->unimode || !this->imc
676                     || (MOD__CTRL(event) && MOD__SHIFT(event))    // input methods tend to steal this for unimode,
677                                                     // but we have our own so make sure they don't swallow it
678                     || !gtk_im_context_filter_keypress(this->imc, (GdkEventKey*) event)) {
679                     //IM did not consume the key, or we're in unimode
680 
681                     if (!MOD__CTRL_ONLY(event) && this->unimode) {
682                             /* TODO: ISO 14755 (section 3 Definitions) says that we should also
683                                accept the first 6 characters of alphabets other than the latin
684                                alphabet "if the Latin alphabet is not used".  The below is also
685                                reasonable (viz. hope that the user's keyboard includes latin
686                                characters and force latin interpretation -- just as we do for our
687                                keyboard shortcuts), but differs from the ISO 14755
688                                recommendation. */
689                             switch (group0_keyval) {
690                                 case GDK_KEY_space:
691                                 case GDK_KEY_KP_Space: {
692                                     if (this->unipos) {
693                                         insert_uni_char(this);
694                                     }
695                                     /* Stay in unimode. */
696                                     show_curr_uni_char(this);
697                                     return TRUE;
698                                 }
699 
700                                 case GDK_KEY_BackSpace: {
701                                     g_return_val_if_fail(this->unipos < sizeof(this->uni), TRUE);
702                                     if (this->unipos) {
703                                         this->uni[--this->unipos] = '\0';
704                                     }
705                                     show_curr_uni_char(this);
706                                     return TRUE;
707                                 }
708 
709                                 case GDK_KEY_Return:
710                                 case GDK_KEY_KP_Enter: {
711                                     if (this->unipos) {
712                                         insert_uni_char(this);
713                                     }
714                                     /* Exit unimode. */
715                                     this->unimode = false;
716                                     this->defaultMessageContext()->clear();
717                                     return TRUE;
718                                 }
719 
720                                 case GDK_KEY_Escape: {
721                                     // Cancel unimode.
722                                     this->unimode = false;
723                                     gtk_im_context_reset(this->imc);
724                                     this->defaultMessageContext()->clear();
725                                     return TRUE;
726                                 }
727 
728                                 case GDK_KEY_Shift_L:
729                                 case GDK_KEY_Shift_R:
730                                     break;
731 
732                                 default: {
733                                     if (g_ascii_isxdigit(group0_keyval)) {
734                                         g_return_val_if_fail(this->unipos < sizeof(this->uni) - 1, TRUE);
735                                         this->uni[this->unipos++] = group0_keyval;
736                                         this->uni[this->unipos] = '\0';
737                                         if (this->unipos == 8) {
738                                             /* This behaviour is partly to allow us to continue to
739                                                use a fixed-length buffer for tc->uni.  Reason for
740                                                choosing the number 8 is that it's the length of
741                                                ``canonical form'' mentioned in the ISO 14755 spec.
742                                                An advantage over choosing 6 is that it allows using
743                                                backspace for typos & misremembering when entering a
744                                                6-digit number. */
745                                             insert_uni_char(this);
746                                         }
747                                         show_curr_uni_char(this);
748                                         return TRUE;
749                                     } else {
750                                         /* The intent is to ignore but consume characters that could be
751                                            typos for hex digits.  Gtk seems to ignore & consume all
752                                            non-hex-digits, and we do similar here.  Though note that some
753                                            shortcuts (like keypad +/- for zoom) get processed before
754                                            reaching this code. */
755                                         return TRUE;
756                                     }
757                                 }
758                             }
759                         }
760 
761                         Inkscape::Text::Layout::iterator old_start = this->text_sel_start;
762                         Inkscape::Text::Layout::iterator old_end = this->text_sel_end;
763                         bool cursor_moved = false;
764                         int screenlines = 1;
765                         if (this->text) {
766                             double spacing = sp_te_get_average_linespacing(this->text);
767                             Geom::Rect const d = desktop->get_display_area().bounds();
768                             screenlines = (int) floor(fabs(d.min()[Geom::Y] - d.max()[Geom::Y])/spacing) - 1;
769                             if (screenlines <= 0)
770                                 screenlines = 1;
771                         }
772 
773                         /* Neither unimode nor IM consumed key; process text tool shortcuts */
774                         switch (group0_keyval) {
775                             case GDK_KEY_x:
776                             case GDK_KEY_X:
777                                 if (MOD__ALT_ONLY(event)) {
778                                     desktop->setToolboxFocusTo("TextFontFamilyAction_entry");
779                                     return TRUE;
780                                 }
781                                 break;
782                             case GDK_KEY_space:
783                                 if (MOD__CTRL_ONLY(event)) {
784                                     /* No-break space */
785                                     if (!this->text) { // printable key; create text if none (i.e. if nascent_object)
786                                         sp_text_context_setup_text(this);
787                                         this->nascent_object = false; // we don't need it anymore, having created a real <text>
788                                     }
789                                     this->text_sel_start = this->text_sel_end = sp_te_replace(this->text, this->text_sel_start, this->text_sel_end, "\302\240");
790                                     sp_text_context_update_cursor(this);
791                                     sp_text_context_update_text_selection(this);
792                                     desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
793                                     DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_TEXT, _("Insert no-break space"));
794                                     return TRUE;
795                                 }
796                                 break;
797                             case GDK_KEY_U:
798                             case GDK_KEY_u:
799                                 if (MOD__CTRL_ONLY(event) || (MOD__CTRL(event) && MOD__SHIFT(event))) {
800                                     if (this->unimode) {
801                                         this->unimode = false;
802                                         this->defaultMessageContext()->clear();
803                                     } else {
804                                         this->unimode = true;
805                                         this->unipos = 0;
806                                         this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
807                                     }
808                                     if (this->imc) {
809                                         gtk_im_context_reset(this->imc);
810                                     }
811                                     return TRUE;
812                                 }
813                                 break;
814                             case GDK_KEY_B:
815                             case GDK_KEY_b:
816                                 if (MOD__CTRL_ONLY(event) && this->text) {
817                                     SPStyle const *style = sp_te_style_at_position(this->text, std::min(this->text_sel_start, this->text_sel_end));
818                                     SPCSSAttr *css = sp_repr_css_attr_new();
819                                     if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
820                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
821                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
822                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
823                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
824                                         sp_repr_css_set_property(css, "font-weight", "bold");
825                                     else
826                                         sp_repr_css_set_property(css, "font-weight", "normal");
827                                     sp_te_apply_style(this->text, this->text_sel_start, this->text_sel_end, css);
828                                     sp_repr_css_attr_unref(css);
829                                     DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_TEXT, _("Make bold"));
830                                     sp_text_context_update_cursor(this);
831                                     sp_text_context_update_text_selection(this);
832                                     return TRUE;
833                                 }
834                                 break;
835                             case GDK_KEY_I:
836                             case GDK_KEY_i:
837                                 if (MOD__CTRL_ONLY(event) && this->text) {
838                                     SPStyle const *style = sp_te_style_at_position(this->text, std::min(this->text_sel_start, this->text_sel_end));
839                                     SPCSSAttr *css = sp_repr_css_attr_new();
840                                     if (style->font_style.computed != SP_CSS_FONT_STYLE_NORMAL)
841                                         sp_repr_css_set_property(css, "font-style", "normal");
842                                     else
843                                         sp_repr_css_set_property(css, "font-style", "italic");
844                                     sp_te_apply_style(this->text, this->text_sel_start, this->text_sel_end, css);
845                                     sp_repr_css_attr_unref(css);
846                                     DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_TEXT, _("Make italic"));
847                                     sp_text_context_update_cursor(this);
848                                     sp_text_context_update_text_selection(this);
849                                     return TRUE;
850                                 }
851                                 break;
852 
853                             case GDK_KEY_A:
854                             case GDK_KEY_a:
855                                 if (MOD__CTRL_ONLY(event) && this->text) {
856                                     Inkscape::Text::Layout const *layout = te_get_layout(this->text);
857                                     if (layout) {
858                                         this->text_sel_start = layout->begin();
859                                         this->text_sel_end = layout->end();
860                                         sp_text_context_update_cursor(this);
861                                         sp_text_context_update_text_selection(this);
862                                         return TRUE;
863                                     }
864                                 }
865                                 break;
866 
867                             case GDK_KEY_Return:
868                             case GDK_KEY_KP_Enter:
869                             {
870                                 if (!this->text) { // printable key; create text if none (i.e. if nascent_object)
871                                     sp_text_context_setup_text(this);
872                                     this->nascent_object = false; // we don't need it anymore, having created a real <text>
873                                 }
874 
875                                 SPText* text_element = dynamic_cast<SPText*>(text);
876                                 if (text_element && (text_element->has_shape_inside() || text_element->has_inline_size())) {
877                                     // Handle new line like any other character.
878                                     this->text_sel_start = this->text_sel_end = sp_te_insert(this->text, this->text_sel_start, "\n");
879                                 } else {
880                                     // Replace new line by either <tspan sodipodi:role="line" or <flowPara>.
881                                     iterator_pair enter_pair;
882                                     bool success = sp_te_delete(this->text, this->text_sel_start, this->text_sel_end, enter_pair);
883                                     (void)success; // TODO cleanup
884                                     this->text_sel_start = this->text_sel_end = enter_pair.first;
885                                     this->text_sel_start = this->text_sel_end = sp_te_insert_line(this->text, this->text_sel_start);
886                                 }
887 
888                                 sp_text_context_update_cursor(this);
889                                 sp_text_context_update_text_selection(this);
890                                 DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_TEXT, _("New line"));
891                                 return TRUE;
892                             }
893                             case GDK_KEY_BackSpace:
894                                 if (this->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
895 
896                                     bool noSelection = false;
897 
898                                     if (MOD__CTRL(event)) {
899                                         this->text_sel_start = this->text_sel_end;
900                                     }
901 
902                                     if (this->text_sel_start == this->text_sel_end) {
903                                         if (MOD__CTRL(event)) {
904                                             this->text_sel_start.prevStartOfWord();
905                                         } else {
906                                             this->text_sel_start.prevCursorPosition();
907                                         }
908                                         noSelection = true;
909                                     }
910 
911                                     iterator_pair bspace_pair;
912                                     bool success = sp_te_delete(this->text, this->text_sel_start, this->text_sel_end, bspace_pair);
913 
914                                     if (noSelection) {
915                                         if (success) {
916                                             this->text_sel_start = this->text_sel_end = bspace_pair.first;
917                                         } else { // nothing deleted
918                                             this->text_sel_start = this->text_sel_end = bspace_pair.second;
919                                         }
920                                     } else {
921                                         if (success) {
922                                             this->text_sel_start = this->text_sel_end = bspace_pair.first;
923                                         } else { // nothing deleted
924                                             this->text_sel_start = bspace_pair.first;
925                                             this->text_sel_end = bspace_pair.second;
926                                         }
927                                     }
928 
929                                     sp_text_context_update_cursor(this);
930                                     sp_text_context_update_text_selection(this);
931                                     DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_TEXT, _("Backspace"));
932                                 }
933                                 return TRUE;
934                             case GDK_KEY_Delete:
935                             case GDK_KEY_KP_Delete:
936                                 if (this->text) {
937                                     bool noSelection = false;
938 
939                                     if (MOD__CTRL(event)) {
940                                         this->text_sel_start = this->text_sel_end;
941                                     }
942 
943                                     if (this->text_sel_start == this->text_sel_end) {
944                                         if (MOD__CTRL(event)) {
945                                             this->text_sel_end.nextEndOfWord();
946                                         } else {
947                                             this->text_sel_end.nextCursorPosition();
948                                         }
949                                         noSelection = true;
950                                     }
951 
952                                     iterator_pair del_pair;
953                                     bool success = sp_te_delete(this->text, this->text_sel_start, this->text_sel_end, del_pair);
954 
955                                     if (noSelection) {
956                                         this->text_sel_start = this->text_sel_end = del_pair.first;
957                                     } else {
958                                         if (success) {
959                                             this->text_sel_start = this->text_sel_end = del_pair.first;
960                                         } else { // nothing deleted
961                                             this->text_sel_start = del_pair.first;
962                                             this->text_sel_end = del_pair.second;
963                                         }
964                                     }
965 
966 
967                                     sp_text_context_update_cursor(this);
968                                     sp_text_context_update_text_selection(this);
969                                     DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_TEXT, _("Delete"));
970                                 }
971                                 return TRUE;
972                             case GDK_KEY_Left:
973                             case GDK_KEY_KP_Left:
974                             case GDK_KEY_KP_4:
975                                 if (this->text) {
976                                     if (MOD__ALT(event)) {
977                                         gint mul = 1 + gobble_key_events(
978                                             get_latin_keyval(&event->key), 0); // with any mask
979                                         if (MOD__SHIFT(event))
980                                             sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*-10, 0));
981                                         else
982                                             sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*-1, 0));
983                                         sp_text_context_update_cursor(this);
984                                         sp_text_context_update_text_selection(this);
985                                         DocumentUndo::maybeDone(desktop->getDocument(), "kern:left", SP_VERB_CONTEXT_TEXT, _("Kern to the left"));
986                                     } else {
987                                         if (MOD__CTRL(event))
988                                             this->text_sel_end.cursorLeftWithControl();
989                                         else
990                                             this->text_sel_end.cursorLeft();
991                                         cursor_moved = true;
992                                         break;
993                                     }
994                                 }
995                                 return TRUE;
996                             case GDK_KEY_Right:
997                             case GDK_KEY_KP_Right:
998                             case GDK_KEY_KP_6:
999                                 if (this->text) {
1000                                     if (MOD__ALT(event)) {
1001                                         gint mul = 1 + gobble_key_events(
1002                                             get_latin_keyval(&event->key), 0); // with any mask
1003                                         if (MOD__SHIFT(event))
1004                                             sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*10, 0));
1005                                         else
1006                                             sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*1, 0));
1007                                         sp_text_context_update_cursor(this);
1008                                         sp_text_context_update_text_selection(this);
1009                                         DocumentUndo::maybeDone(desktop->getDocument(), "kern:right", SP_VERB_CONTEXT_TEXT, _("Kern to the right"));
1010                                     } else {
1011                                         if (MOD__CTRL(event))
1012                                             this->text_sel_end.cursorRightWithControl();
1013                                         else
1014                                             this->text_sel_end.cursorRight();
1015                                         cursor_moved = true;
1016                                         break;
1017                                     }
1018                                 }
1019                                 return TRUE;
1020                             case GDK_KEY_Up:
1021                             case GDK_KEY_KP_Up:
1022                             case GDK_KEY_KP_8:
1023                                 if (this->text) {
1024                                     if (MOD__ALT(event)) {
1025                                         gint mul = 1 + gobble_key_events(
1026                                             get_latin_keyval(&event->key), 0); // with any mask
1027                                         if (MOD__SHIFT(event))
1028                                             sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*-10));
1029                                         else
1030                                             sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*-1));
1031                                         sp_text_context_update_cursor(this);
1032                                         sp_text_context_update_text_selection(this);
1033                                         DocumentUndo::maybeDone(desktop->getDocument(), "kern:up", SP_VERB_CONTEXT_TEXT, _("Kern up"));
1034                                     } else {
1035                                         if (MOD__CTRL(event))
1036                                             this->text_sel_end.cursorUpWithControl();
1037                                         else
1038                                             this->text_sel_end.cursorUp();
1039                                         cursor_moved = true;
1040                                         break;
1041                                     }
1042                                 }
1043                                 return TRUE;
1044                             case GDK_KEY_Down:
1045                             case GDK_KEY_KP_Down:
1046                             case GDK_KEY_KP_2:
1047                                 if (this->text) {
1048                                     if (MOD__ALT(event)) {
1049                                         gint mul = 1 + gobble_key_events(
1050                                             get_latin_keyval(&event->key), 0); // with any mask
1051                                         if (MOD__SHIFT(event))
1052                                             sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*10));
1053                                         else
1054                                             sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*1));
1055                                         sp_text_context_update_cursor(this);
1056                                         sp_text_context_update_text_selection(this);
1057                                         DocumentUndo::maybeDone(desktop->getDocument(), "kern:down", SP_VERB_CONTEXT_TEXT, _("Kern down"));
1058                                     } else {
1059                                         if (MOD__CTRL(event))
1060                                             this->text_sel_end.cursorDownWithControl();
1061                                         else
1062                                             this->text_sel_end.cursorDown();
1063                                         cursor_moved = true;
1064                                         break;
1065                                     }
1066                                 }
1067                                 return TRUE;
1068                             case GDK_KEY_Home:
1069                             case GDK_KEY_KP_Home:
1070                                 if (this->text) {
1071                                     if (MOD__CTRL(event))
1072                                         this->text_sel_end.thisStartOfShape();
1073                                     else
1074                                         this->text_sel_end.thisStartOfLine();
1075                                     cursor_moved = true;
1076                                     break;
1077                                 }
1078                                 return TRUE;
1079                             case GDK_KEY_End:
1080                             case GDK_KEY_KP_End:
1081                                 if (this->text) {
1082                                     if (MOD__CTRL(event))
1083                                         this->text_sel_end.nextStartOfShape();
1084                                     else
1085                                         this->text_sel_end.thisEndOfLine();
1086                                     cursor_moved = true;
1087                                     break;
1088                                 }
1089                                 return TRUE;
1090                             case GDK_KEY_Page_Down:
1091                             case GDK_KEY_KP_Page_Down:
1092                                 if (this->text) {
1093                                     this->text_sel_end.cursorDown(screenlines);
1094                                     cursor_moved = true;
1095                                     break;
1096                                 }
1097                                 return TRUE;
1098                             case GDK_KEY_Page_Up:
1099                             case GDK_KEY_KP_Page_Up:
1100                                 if (this->text) {
1101                                     this->text_sel_end.cursorUp(screenlines);
1102                                     cursor_moved = true;
1103                                     break;
1104                                 }
1105                                 return TRUE;
1106                             case GDK_KEY_Escape:
1107                                 if (this->creating) {
1108                                     this->creating = false;
1109                                     ungrabCanvasEvents();
1110                                     Inkscape::Rubberband::get(desktop)->stop();
1111                                 } else {
1112                                     desktop->getSelection()->clear();
1113                                 }
1114                                 this->nascent_object = FALSE;
1115                                 return TRUE;
1116                             case GDK_KEY_bracketleft:
1117                                 if (this->text) {
1118                                     if (MOD__ALT(event) || MOD__CTRL(event)) {
1119                                         if (MOD__ALT(event)) {
1120                                             if (MOD__SHIFT(event)) {
1121                                                 // FIXME: alt+shift+[] does not work, don't know why
1122                                                 sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -10);
1123                                             } else {
1124                                                 sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -1);
1125                                             }
1126                                         } else {
1127                                             sp_te_adjust_rotation(this->text, this->text_sel_start, this->text_sel_end, desktop, -90);
1128                                         }
1129                                         DocumentUndo::maybeDone(desktop->getDocument(), "textrot:ccw", SP_VERB_CONTEXT_TEXT, _("Rotate counterclockwise"));
1130                                         sp_text_context_update_cursor(this);
1131                                         sp_text_context_update_text_selection(this);
1132                                         return TRUE;
1133                                     }
1134                                 }
1135                                 break;
1136                             case GDK_KEY_bracketright:
1137                                 if (this->text) {
1138                                     if (MOD__ALT(event) || MOD__CTRL(event)) {
1139                                         if (MOD__ALT(event)) {
1140                                             if (MOD__SHIFT(event)) {
1141                                                 // FIXME: alt+shift+[] does not work, don't know why
1142                                                 sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 10);
1143                                             } else {
1144                                                 sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 1);
1145                                             }
1146                                         } else {
1147                                             sp_te_adjust_rotation(this->text, this->text_sel_start, this->text_sel_end, desktop, 90);
1148                                         }
1149                                         DocumentUndo::maybeDone(desktop->getDocument(), "textrot:cw", SP_VERB_CONTEXT_TEXT, _("Rotate clockwise"));
1150                                         sp_text_context_update_cursor(this);
1151                                         sp_text_context_update_text_selection(this);
1152                                         return TRUE;
1153                                     }
1154                                 }
1155                                 break;
1156                             case GDK_KEY_less:
1157                             case GDK_KEY_comma:
1158                                 if (this->text) {
1159                                     if (MOD__ALT(event)) {
1160                                         if (MOD__CTRL(event)) {
1161                                             if (MOD__SHIFT(event))
1162                                                 sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -10);
1163                                             else
1164                                                 sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -1);
1165                                             DocumentUndo::maybeDone(desktop->getDocument(), "linespacing:dec", SP_VERB_CONTEXT_TEXT, _("Contract line spacing"));
1166                                         } else {
1167                                             if (MOD__SHIFT(event))
1168                                                 sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -10);
1169                                             else
1170                                                 sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -1);
1171                                             DocumentUndo::maybeDone(desktop->getDocument(), "letterspacing:dec", SP_VERB_CONTEXT_TEXT, _("Contract letter spacing"));
1172                                         }
1173                                         sp_text_context_update_cursor(this);
1174                                         sp_text_context_update_text_selection(this);
1175                                         return TRUE;
1176                                     }
1177                                 }
1178                                 break;
1179                             case GDK_KEY_greater:
1180                             case GDK_KEY_period:
1181                                 if (this->text) {
1182                                     if (MOD__ALT(event)) {
1183                                         if (MOD__CTRL(event)) {
1184                                             if (MOD__SHIFT(event))
1185                                                 sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 10);
1186                                             else
1187                                                 sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 1);
1188                                             DocumentUndo::maybeDone(desktop->getDocument(), "linespacing:inc", SP_VERB_CONTEXT_TEXT, _("Expand line spacing"));
1189                                         } else {
1190                                             if (MOD__SHIFT(event))
1191                                                 sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 10);
1192                                             else
1193                                                 sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 1);
1194                                             DocumentUndo::maybeDone(desktop->getDocument(), "letterspacing:inc", SP_VERB_CONTEXT_TEXT, _("Expand letter spacing"));\
1195                                         }
1196                                         sp_text_context_update_cursor(this);
1197                                         sp_text_context_update_text_selection(this);
1198                                         return TRUE;
1199                                     }
1200                                 }
1201                                 break;
1202                             default:
1203                                 break;
1204                         }
1205 
1206                         if (cursor_moved) {
1207                             if (!MOD__SHIFT(event))
1208                                 this->text_sel_start = this->text_sel_end;
1209                             if (old_start != this->text_sel_start || old_end != this->text_sel_end) {
1210                                 sp_text_context_update_cursor(this);
1211                                 sp_text_context_update_text_selection(this);
1212                             }
1213                             return TRUE;
1214                         }
1215 
1216                 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1217             } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1218                 // except up/down that are swallowed to prevent the zoom field from activation
1219                 if ((group0_keyval == GDK_KEY_Up    ||
1220                      group0_keyval == GDK_KEY_Down  ||
1221                      group0_keyval == GDK_KEY_KP_Up ||
1222                      group0_keyval == GDK_KEY_KP_Down )
1223                     && !MOD__CTRL_ONLY(event)) {
1224                     return TRUE;
1225                 } else if (group0_keyval == GDK_KEY_Escape) { // cancel rubberband
1226                     if (this->creating) {
1227                         this->creating = false;
1228                         ungrabCanvasEvents();
1229                         Inkscape::Rubberband::get(desktop)->stop();
1230                     }
1231                 } else if ((group0_keyval == GDK_KEY_x || group0_keyval == GDK_KEY_X) && MOD__ALT_ONLY(event)) {
1232                     desktop->setToolboxFocusTo("TextFontFamilyAction_entry");
1233                     return TRUE;
1234                 }
1235             }
1236             break;
1237         }
1238 
1239         case GDK_KEY_RELEASE:
1240             if (!this->unimode && this->imc && gtk_im_context_filter_keypress(this->imc, (GdkEventKey*) event)) {
1241                 return TRUE;
1242             }
1243             break;
1244         default:
1245             break;
1246     }
1247 
1248     // if nobody consumed it so far
1249 //    if ((SP_EVENT_CONTEXT_CLASS(sp_text_context_parent_class))->root_handler) { // and there's a handler in parent context,
1250 //        return (SP_EVENT_CONTEXT_CLASS(sp_text_context_parent_class))->root_handler(event_context, event); // send event to parent
1251 //    } else {
1252 //        return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1253 //    }
1254     return ToolBase::root_handler(event);
1255 
1256 }
1257 
1258 /**
1259  Attempts to paste system clipboard into the currently edited text, returns true on success
1260  */
sp_text_paste_inline(ToolBase * ec)1261 bool sp_text_paste_inline(ToolBase *ec)
1262 {
1263     if (!SP_IS_TEXT_CONTEXT(ec))
1264         return false;
1265     TextTool *tc = SP_TEXT_CONTEXT(ec);
1266 
1267     if ((tc->text) || (tc->nascent_object)) {
1268         // there is an active text object in this context, or a new object was just created
1269 
1270         Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1271         Glib::ustring const clip_text = refClipboard->wait_for_text();
1272 
1273         if (!clip_text.empty()) {
1274 
1275             bool is_svg2 = false;
1276             SPText *textitem = dynamic_cast<SPText *>(tc->text);
1277             if (textitem) {
1278                 is_svg2 = textitem->has_shape_inside() /*|| textitem->has_inline_size()*/; // Do now since hiding messes this up.
1279                 textitem->hide_shape_inside();
1280             }
1281 
1282             SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(tc->text);
1283             if (flowtext) {
1284                 flowtext->fix_overflow_flowregion(false);
1285             }
1286 
1287             // Fix for 244940
1288             // The XML standard defines the following as valid characters
1289             // (Extensible Markup Language (XML) 1.0 (Fourth Edition) paragraph 2.2)
1290             // char ::=     #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
1291             // Since what comes in off the paste buffer will go right into XML, clean
1292             // the text here.
1293             Glib::ustring text(clip_text);
1294             Glib::ustring::iterator itr = text.begin();
1295             gunichar paste_string_uchar;
1296 
1297             while(itr != text.end())
1298             {
1299                 paste_string_uchar = *itr;
1300 
1301                 // Make sure we don't have a control character. We should really check
1302                 // for the whole range above... Add the rest of the invalid cases from
1303                 // above if we find additional issues
1304                 if(paste_string_uchar >= 0x00000020 ||
1305                    paste_string_uchar == 0x00000009 ||
1306                    paste_string_uchar == 0x0000000A ||
1307                    paste_string_uchar == 0x0000000D) {
1308                     ++itr;
1309                 } else {
1310                     itr = text.erase(itr);
1311                 }
1312             }
1313 
1314             if (!tc->text) { // create text if none (i.e. if nascent_object)
1315                 sp_text_context_setup_text(tc);
1316                 tc->nascent_object = false; // we don't need it anymore, having created a real <text>
1317             }
1318 
1319             // using indices is slow in ustrings. Whatever.
1320             Glib::ustring::size_type begin = 0;
1321             for ( ; ; ) {
1322                 Glib::ustring::size_type end = text.find('\n', begin);
1323 
1324                 if (end == Glib::ustring::npos || is_svg2) {
1325                     // Paste everything
1326                     if (begin != text.length())
1327                         tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, text.substr(begin).c_str());
1328                     break;
1329                 }
1330 
1331                 // Paste up to new line, add line, repeat.
1332                 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, text.substr(begin, end - begin).c_str());
1333                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1334                 begin = end + 1;
1335             }
1336             if (textitem) {
1337                 textitem->show_shape_inside();
1338             }
1339             if (flowtext) {
1340                 flowtext->fix_overflow_flowregion(true);
1341             }
1342             DocumentUndo::done(ec->getDesktop()->getDocument(), SP_VERB_CONTEXT_TEXT,
1343                                _("Paste text"));
1344 
1345             return true;
1346         }
1347 
1348     } // FIXME: else create and select a new object under cursor!
1349 
1350     return false;
1351 }
1352 
1353 /**
1354  Gets the raw characters that comprise the currently selected text, converting line
1355  breaks into lf characters.
1356 */
sp_text_get_selected_text(ToolBase const * ec)1357 Glib::ustring sp_text_get_selected_text(ToolBase const *ec)
1358 {
1359     if (!SP_IS_TEXT_CONTEXT(ec))
1360         return "";
1361     TextTool const *tc = SP_TEXT_CONTEXT(ec);
1362     if (tc->text == nullptr)
1363         return "";
1364 
1365     return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1366 }
1367 
sp_text_get_style_at_cursor(ToolBase const * ec)1368 SPCSSAttr *sp_text_get_style_at_cursor(ToolBase const *ec)
1369 {
1370     if (!SP_IS_TEXT_CONTEXT(ec))
1371         return nullptr;
1372     TextTool const *tc = SP_TEXT_CONTEXT(ec);
1373     if (tc->text == nullptr)
1374         return nullptr;
1375 
1376     SPObject const *obj = sp_te_object_at_position(tc->text, tc->text_sel_end);
1377 
1378     if (obj) {
1379         return take_style_from_item(const_cast<SPObject*>(obj));
1380     }
1381 
1382     return nullptr;
1383 }
1384 // this two functions are commented because are used on clipboard
1385 // and because slow the text pastinbg and usage a lot
1386 // and couldent get it working properly we miss font size font style or never work
1387 // and user usualy want paste as plain text and get the position context
1388 // style. Anyway I retain for further usage.
1389 
1390 /* static bool css_attrs_are_equal(SPCSSAttr const *first, SPCSSAttr const *second)
1391 {
1392 //    Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = first->attributeList();
1393     for ( ; attrs ; attrs++) {
1394         gchar const *other_attr = second->attribute(g_quark_to_string(attrs->key));
1395         if (other_attr == nullptr || strcmp(attrs->value, other_attr))
1396             return false;
1397     }
1398     attrs = second->attributeList();
1399     for ( ; attrs ; attrs++) {
1400         gchar const *other_attr = first->attribute(g_quark_to_string(attrs->key));
1401         if (other_attr == nullptr || strcmp(attrs->value, other_attr))
1402             return false;
1403     }
1404     return true;
1405 }
1406 
1407 std::vector<SPCSSAttr*> sp_text_get_selected_style(ToolBase const *ec, unsigned *k, int *b, std::vector<unsigned>
1408 *positions)
1409 {
1410     std::vector<SPCSSAttr*> vec;
1411     SPCSSAttr *css, *css_new;
1412     TextTool *tc = SP_TEXT_CONTEXT(ec);
1413     Inkscape::Text::Layout::iterator i = std::min(tc->text_sel_start, tc->text_sel_end);
1414     SPObject const *obj = sp_te_object_at_position(tc->text, i);
1415     if (obj) {
1416         css = take_style_from_item(const_cast<SPObject*>(obj));
1417     }
1418     vec.push_back(css);
1419     positions->push_back(0);
1420     i.nextCharacter();
1421     *k = 1;
1422     *b = 1;
1423     while (i != std::max(tc->text_sel_start, tc->text_sel_end))
1424     {
1425         obj = sp_te_object_at_position(tc->text, i);
1426         if (obj) {
1427             css_new = take_style_from_item(const_cast<SPObject*>(obj));
1428         }
1429         if(!css_attrs_are_equal(css, css_new))
1430         {
1431             vec.push_back(css_new);
1432             css = sp_repr_css_attr_new();
1433             sp_repr_css_merge(css, css_new);
1434             positions->push_back(*k);
1435             (*b)++;
1436         }
1437         i.nextCharacter();
1438         (*k)++;
1439     }
1440     positions->push_back(*k);
1441     return vec;
1442 }
1443  */
1444 
1445 /**
1446  Deletes the currently selected characters. Returns false if there is no
1447  text selection currently.
1448 */
sp_text_delete_selection(ToolBase * ec)1449 bool sp_text_delete_selection(ToolBase *ec)
1450 {
1451     if (!SP_IS_TEXT_CONTEXT(ec))
1452         return false;
1453     TextTool *tc = SP_TEXT_CONTEXT(ec);
1454     if (tc->text == nullptr)
1455         return false;
1456 
1457     if (tc->text_sel_start == tc->text_sel_end)
1458         return false;
1459 
1460     iterator_pair pair;
1461     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair);
1462 
1463 
1464     if (success) {
1465         tc->text_sel_start = tc->text_sel_end = pair.first;
1466     } else { // nothing deleted
1467         tc->text_sel_start = pair.first;
1468         tc->text_sel_end = pair.second;
1469     }
1470 
1471     sp_text_context_update_cursor(tc);
1472     sp_text_context_update_text_selection(tc);
1473 
1474     return true;
1475 }
1476 
1477 /**
1478  * \param selection Should not be NULL.
1479  */
_selectionChanged(Inkscape::Selection * selection)1480 void TextTool::_selectionChanged(Inkscape::Selection *selection)
1481 {
1482     g_assert(selection != nullptr);
1483 
1484     shape_editor->unset_item();
1485     SPItem *item = selection->singleItem();
1486     if (item && (
1487             (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) ||
1488             (SP_IS_TEXT(item) &&
1489              !(SP_TEXT(item)->has_shape_inside() && !SP_TEXT(item)->get_first_rectangle()))
1490             )) {
1491         shape_editor->set_item(item);
1492     }
1493 
1494     if (this->text && (item != this->text)) {
1495         sp_text_context_forget_text(this);
1496     }
1497     this->text = nullptr;
1498 
1499     if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1500         this->text = item;
1501         Inkscape::Text::Layout const *layout = te_get_layout(this->text);
1502         if (layout)
1503             this->text_sel_start = this->text_sel_end = layout->end();
1504     } else {
1505         this->text = nullptr;
1506     }
1507 
1508     // we update cursor without scrolling, because this position may not be final;
1509     // item_handler moves cusros to the point of click immediately
1510     sp_text_context_update_cursor(this, false);
1511     sp_text_context_update_text_selection(this);
1512 }
1513 
_selectionModified(Inkscape::Selection *,guint)1514 void TextTool::_selectionModified(Inkscape::Selection */*selection*/, guint /*flags*/)
1515 {
1516     sp_text_context_update_cursor(this);
1517     sp_text_context_update_text_selection(this);
1518 }
1519 
_styleSet(SPCSSAttr const * css)1520 bool TextTool::_styleSet(SPCSSAttr const *css)
1521 {
1522     if (this->text == nullptr)
1523         return false;
1524     if (this->text_sel_start == this->text_sel_end)
1525         return false;    // will get picked up by the parent and applied to the whole text object
1526 
1527     sp_te_apply_style(this->text, this->text_sel_start, this->text_sel_end, css);
1528 
1529     // This is a bandaid fix... whenever a style is changed it might cause the text layout to
1530     // change which requires rewriting the 'x' and 'y' attributes of the tpsans for Inkscape
1531     // multi-line text (with sodipodi:role="line"). We need to rewrite the repr after this is
1532     // done. rebuldLayout() will be called a second time unnecessarily.
1533     SPText* sptext = dynamic_cast<SPText*>(text);
1534     if (sptext) {
1535         sptext->rebuildLayout();
1536         sptext->updateRepr();
1537     }
1538 
1539     DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_TEXT,
1540                _("Set text style"));
1541     sp_text_context_update_cursor(this);
1542     sp_text_context_update_text_selection(this);
1543     return true;
1544 }
1545 
_styleQueried(SPStyle * style,int property)1546 int TextTool::_styleQueried(SPStyle *style, int property)
1547 {
1548     if (this->text == nullptr) {
1549         return QUERY_STYLE_NOTHING;
1550     }
1551     const Inkscape::Text::Layout *layout = te_get_layout(this->text);
1552     if (layout == nullptr) {
1553         return QUERY_STYLE_NOTHING;
1554     }
1555     sp_text_context_validate_cursor_iterators(this);
1556 
1557     std::vector<SPItem*> styles_list;
1558 
1559     Inkscape::Text::Layout::iterator begin_it, end_it;
1560     if (this->text_sel_start < this->text_sel_end) {
1561         begin_it = this->text_sel_start;
1562         end_it = this->text_sel_end;
1563     } else {
1564         begin_it = this->text_sel_end;
1565         end_it = this->text_sel_start;
1566     }
1567     if (begin_it == end_it) {
1568         if (!begin_it.prevCharacter()) {
1569             end_it.nextCharacter();
1570         }
1571     }
1572     for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1573         SPObject *pos_obj = nullptr;
1574         layout->getSourceOfCharacter(it, &pos_obj);
1575         if (!pos_obj) {
1576             continue;
1577         }
1578         if (! pos_obj->parent) // the string is not in the document anymore (deleted)
1579             return 0;
1580 
1581         if ( SP_IS_STRING(pos_obj) ) {
1582            pos_obj = pos_obj->parent;   // SPStrings don't have style
1583         }
1584         styles_list.insert(styles_list.begin(),(SPItem*)pos_obj);
1585     }
1586 
1587     int result = sp_desktop_query_style_from_list (styles_list, style, property);
1588 
1589     return result;
1590 }
1591 
sp_text_context_validate_cursor_iterators(TextTool * tc)1592 static void sp_text_context_validate_cursor_iterators(TextTool *tc)
1593 {
1594     if (tc->text == nullptr)
1595         return;
1596     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1597     if (layout) {     // undo can change the text length without us knowing it
1598         layout->validateIterator(&tc->text_sel_start);
1599         layout->validateIterator(&tc->text_sel_end);
1600     }
1601 }
1602 
sp_text_context_update_cursor(TextTool * tc,bool scroll_to_see)1603 static void sp_text_context_update_cursor(TextTool *tc,  bool scroll_to_see)
1604 {
1605     // due to interruptible display, tc may already be destroyed during a display update before
1606     // the cursor update (can't do both atomically, alas)
1607     if (!tc->getDesktop()) return;
1608 
1609     SPDesktop* desktop = tc->getDesktop();
1610 
1611     if (tc->text) {
1612         Geom::Point p0, p1;
1613         sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1614         Geom::Point const d0 = p0 * tc->text->i2dt_affine();
1615         Geom::Point const d1 = p1 * tc->text->i2dt_affine();
1616 
1617         // scroll to show cursor
1618         if (scroll_to_see) {
1619 
1620             // We don't want to scroll outside the text box area (i.e. when there is hidden text)
1621             // or we could end up in Timbuktu.
1622             bool scroll = true;
1623             if (SP_IS_TEXT(tc->text)) {
1624                 Geom::OptRect opt_frame = SP_TEXT(tc->text)->get_frame();
1625                 if (opt_frame && (!opt_frame->contains(p0))) {
1626                     scroll = false;
1627                 }
1628             } else if (SP_IS_FLOWTEXT(tc->text)) {
1629                 SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame(nullptr); // first frame only
1630                 Geom::OptRect opt_frame = frame->geometricBounds();
1631                 if (opt_frame && (!opt_frame->contains(p0))) {
1632                     scroll = false;
1633                 }
1634             }
1635 
1636             if (scroll) {
1637                 Geom::Point const center = desktop->current_center();
1638                 if (Geom::L2(d0 - center) > Geom::L2(d1 - center))
1639                     // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1640                     desktop->scroll_to_point(d0, 1.0);
1641                 else
1642                     desktop->scroll_to_point(d1, 1.0);
1643             }
1644         }
1645 
1646         tc->cursor->set_coords(d0, d1);
1647         tc->cursor->show();
1648 
1649         /* fixme: ... need another transformation to get canvas widget coordinate space? */
1650         if (tc->imc) {
1651             GdkRectangle im_cursor = { 0, 0, 1, 1 };
1652             Geom::Point const top_left = desktop->get_display_area().corner(0);
1653             Geom::Point const im_d0 =    desktop->d2w(d0 - top_left);
1654             Geom::Point const im_d1 =    desktop->d2w(d1 - top_left);
1655             Geom::Rect const im_rect(im_d0, im_d1);
1656             im_cursor.x = (int) floor(im_rect.left());
1657             im_cursor.y = (int) floor(im_rect.top());
1658             im_cursor.width = (int) floor(im_rect.width());
1659             im_cursor.height = (int) floor(im_rect.height());
1660             gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1661         }
1662 
1663         tc->show = TRUE;
1664         tc->phase = true;
1665 
1666         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1667         int const nChars = layout->iteratorToCharIndex(layout->end());
1668         char const *trunc = "";
1669         bool truncated = false;
1670         if (layout->inputTruncated()) {
1671             truncated = true;
1672             trunc = _(" [truncated]");
1673         }
1674 
1675         if (truncated) {
1676             tc->frame->set_stroke(0xff0000ff);
1677         } else {
1678             tc->frame->set_stroke(0x0000ff7f);
1679         }
1680 
1681         std::vector<SPItem const *> shapes;
1682 
1683         // Frame around text
1684         if (SP_IS_FLOWTEXT(tc->text)) {
1685             SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (nullptr); // first frame only
1686             shapes.push_back(frame);
1687 
1688             tc->message_context->setF(Inkscape::NORMAL_MESSAGE, ngettext("Type or edit flowed text (%d character%s); <b>Enter</b> to start new paragraph.", "Type or edit flowed text (%d characters%s); <b>Enter</b> to start new paragraph.", nChars), nChars, trunc);
1689 
1690         } else if (SP_IS_TEXT(tc->text)) {
1691             if (tc->text->style->shape_inside.set) {
1692                 for (auto const *href : tc->text->style->shape_inside.hrefs) {
1693                     shapes.push_back(href->getObject());
1694                 }
1695             } else {
1696                 for (SPObject &child : tc->text->children) {
1697                     if (auto textpath = dynamic_cast<SPTextPath *>(&child)) {
1698                         shapes.push_back(sp_textpath_get_path_item(textpath));
1699                     }
1700                 }
1701             }
1702 
1703         } else {
1704 
1705             tc->message_context->setF(Inkscape::NORMAL_MESSAGE, ngettext("Type or edit text (%d character%s); <b>Enter</b> to start new line.", "Type or edit text (%d characters%s); <b>Enter</b> to start new line.", nChars), nChars, trunc);
1706         }
1707 
1708         SPCurve curve;
1709         for (auto const *shape_item : shapes) {
1710             if (auto shape = dynamic_cast<SPShape const *>(shape_item)) {
1711                 auto c = SPCurve::copy(shape->curve());
1712                 if (c) {
1713                     c->transform(shape->transform);
1714                     curve.append(*c);
1715                 }
1716             }
1717         }
1718 
1719         if (!curve.is_empty()) {
1720             curve.transform(tc->text->i2dt_affine());
1721             tc->frame->set_bpath(&curve);
1722             tc->frame->show();
1723         } else {
1724             tc->frame->hide();
1725         }
1726 
1727     } else {
1728         tc->cursor->hide();
1729         tc->frame->hide();
1730         tc->show = FALSE;
1731         if (!tc->nascent_object) {
1732             tc->message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to select or create text, <b>drag</b> to create flowed text; then type.")); // FIXME: this is a copy of string from tools-switch, do not desync
1733         }
1734     }
1735 
1736     desktop->emitToolSubselectionChanged((gpointer)tc);
1737 }
1738 
sp_text_context_update_text_selection(TextTool * tc)1739 static void sp_text_context_update_text_selection(TextTool *tc)
1740 {
1741     // due to interruptible display, tc may already be destroyed during a display update before
1742     // the selection update (can't do both atomically, alas)
1743     if (!tc->getDesktop()) return;
1744 
1745     for (auto & text_selection_quad : tc->text_selection_quads) {
1746         text_selection_quad->hide();
1747         delete text_selection_quad;
1748     }
1749     tc->text_selection_quads.clear();
1750 
1751     std::vector<Geom::Point> quads;
1752     if (tc->text != nullptr)
1753         quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, (tc->text)->i2dt_affine());
1754     for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1755         auto quad = new CanvasItemQuad(tc->getDesktop()->getCanvasControls(), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1756         quad->set_fill(0x00777777); // Semi-transparent blue as Cairo cannot do inversion.
1757         quad->show();
1758         tc->text_selection_quads.push_back(quad);
1759     }
1760 
1761     if (tc->shape_editor != nullptr) {
1762         if (tc->shape_editor->knotholder) {
1763             tc->shape_editor->knotholder->update_knots();
1764         }
1765     }
1766 }
1767 
sp_text_context_timeout(TextTool * tc)1768 static gint sp_text_context_timeout(TextTool *tc)
1769 {
1770     if (tc->show) {
1771         if (tc->phase) {
1772             tc->phase = false;
1773             tc->cursor->set_stroke(0x000000ff);
1774         } else {
1775             tc->phase = true;
1776             tc->cursor->set_stroke(0xffffffff);
1777         }
1778         tc->cursor->show();
1779     }
1780 
1781     return TRUE;
1782 }
1783 
sp_text_context_forget_text(TextTool * tc)1784 static void sp_text_context_forget_text(TextTool *tc)
1785 {
1786     if (! tc->text) return;
1787     SPItem *ti = tc->text;
1788     (void)ti;
1789     /* We have to set it to zero,
1790      * or selection changed signal messes everything up */
1791     tc->text = nullptr;
1792 
1793 /* FIXME: this automatic deletion when nothing is inputted crashes the XML edittor and also crashes when duplicating an empty flowtext.
1794     So don't create an empty flowtext in the first place? Create it when first character is typed.
1795     */
1796 /*
1797     if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1798         Inkscape::XML::Node *text_repr = ti->getRepr();
1799         // the repr may already have been unparented
1800         // if we were called e.g. as the result of
1801         // an undo or the element being removed from
1802         // the XML editor
1803         if ( text_repr && text_repr->parent() ) {
1804             sp_repr_unparent(text_repr);
1805             SPDocumentUndo::done(tc->desktop->getDocument(), SP_VERB_CONTEXT_TEXT,
1806                      _("Remove empty text"));
1807         }
1808     }
1809 */
1810 }
1811 
sptc_focus_in(GtkWidget * widget,GdkEventFocus *,TextTool * tc)1812 gint sptc_focus_in(GtkWidget *widget, GdkEventFocus */*event*/, TextTool *tc)
1813 {
1814     gtk_im_context_focus_in(tc->imc);
1815     return FALSE;
1816 }
1817 
sptc_focus_out(GtkWidget *,GdkEventFocus *,TextTool * tc)1818 gint sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, TextTool *tc)
1819 {
1820     gtk_im_context_focus_out(tc->imc);
1821     return FALSE;
1822 }
1823 
sptc_commit(GtkIMContext *,gchar * string,TextTool * tc)1824 static void sptc_commit(GtkIMContext */*imc*/, gchar *string, TextTool *tc)
1825 {
1826     if (!tc->text) {
1827         sp_text_context_setup_text(tc);
1828         tc->nascent_object = false; // we don't need it anymore, having created a real <text>
1829     }
1830 
1831     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1832     sp_text_context_update_cursor(tc);
1833     sp_text_context_update_text_selection(tc);
1834 
1835     DocumentUndo::done(tc->text->document, SP_VERB_CONTEXT_TEXT,
1836                _("Type text"));
1837 }
1838 
sp_text_context_place_cursor(TextTool * tc,SPObject * text,Inkscape::Text::Layout::iterator where)1839 void sp_text_context_place_cursor (TextTool *tc, SPObject *text, Inkscape::Text::Layout::iterator where)
1840 {
1841     tc->getDesktop()->selection->set (text);
1842     tc->text_sel_start = tc->text_sel_end = where;
1843     sp_text_context_update_cursor(tc);
1844     sp_text_context_update_text_selection(tc);
1845 }
1846 
sp_text_context_place_cursor_at(TextTool * tc,SPObject * text,Geom::Point const p)1847 void sp_text_context_place_cursor_at (TextTool *tc, SPObject *text, Geom::Point const p)
1848 {
1849     tc->getDesktop()->selection->set (text);
1850     sp_text_context_place_cursor (tc, text, sp_te_get_position_by_coords(tc->text, p));
1851 }
1852 
sp_text_context_get_cursor_position(TextTool * tc,SPObject * text)1853 Inkscape::Text::Layout::iterator *sp_text_context_get_cursor_position(TextTool *tc, SPObject *text)
1854 {
1855     if (text != tc->text)
1856         return nullptr;
1857     return &(tc->text_sel_end);
1858 }
1859 
1860 }
1861 }
1862 }
1863 
1864 /*
1865   Local Variables:
1866   mode:c++
1867   c-file-style:"stroustrup"
1868   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1869   indent-tabs-mode:nil
1870   fill-column:99
1871   End:
1872 */
1873 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
1874