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, "<"); break;
421 case '>': strcpy(utf8, ">"); break;
422 case '&': strcpy(utf8, "&"); 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 × %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