Code

A simple layout document as to what, why and how is cppification.
[inkscape.git] / src / text-context.cpp
1 #define __SP_TEXT_CONTEXT_C__
3 /*
4  * SPTextContext
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   bulia byak <buliabyak@users.sf.net>
9  *
10  * Copyright (C) 1999-2005 authors
11  * Copyright (C) 2001 Ximian, Inc.
12  *
13  * Released under GNU GPL, read the file 'COPYING' for more information
14  */
16 #ifdef HAVE_CONFIG_H
17 # include <config.h>
18 #endif
20 #include <gdk/gdkkeysyms.h>
21 #include <gtk/gtkmain.h>
22 #include <display/sp-ctrlline.h>
23 #include <display/sodipodi-ctrlrect.h>
24 #include <display/sp-ctrlquadr.h>
25 #include <gtk/gtkimmulticontext.h>
26 #include <gtkmm/clipboard.h>
28 #include "macros.h"
29 #include "sp-text.h"
30 #include "sp-flowtext.h"
31 #include "document.h"
32 #include "sp-namedview.h"
33 #include "style.h"
34 #include "selection.h"
35 #include "desktop.h"
36 #include "desktop-style.h"
37 #include "desktop-handles.h"
38 #include "message-stack.h"
39 #include "message-context.h"
40 #include "pixmaps/cursor-text.xpm"
41 #include "pixmaps/cursor-text-insert.xpm"
42 #include <glibmm/i18n.h>
43 #include "object-edit.h"
44 #include "xml/repr.h"
45 #include "xml/node-event-vector.h"
46 #include "preferences.h"
47 #include "rubberband.h"
48 #include "sp-metrics.h"
49 #include "context-fns.h"
50 #include "verbs.h"
51 #include "shape-editor.h"
52 #include "selection-chemistry.h"
53 #include "text-editing.h"
55 #include "text-context.h"
58 static void sp_text_context_class_init(SPTextContextClass *klass);
59 static void sp_text_context_init(SPTextContext *text_context);
60 static void sp_text_context_dispose(GObject *obj);
62 static void sp_text_context_setup(SPEventContext *ec);
63 static void sp_text_context_finish(SPEventContext *ec);
64 static gint sp_text_context_root_handler(SPEventContext *event_context, GdkEvent *event);
65 static gint sp_text_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
67 static void sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc);
68 static void sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc);
69 static bool sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc);
70 static int sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc);
72 static void sp_text_context_validate_cursor_iterators(SPTextContext *tc);
73 static void sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see = true);
74 static void sp_text_context_update_text_selection(SPTextContext *tc);
75 static gint sp_text_context_timeout(SPTextContext *tc);
76 static void sp_text_context_forget_text(SPTextContext *tc);
78 static gint sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc);
79 static gint sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc);
80 static void sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc);
82 static SPEventContextClass *parent_class;
84 GType
85 sp_text_context_get_type()
86 {
87     static GType type = 0;
88     if (!type) {
89         GTypeInfo info = {
90             sizeof(SPTextContextClass),
91             NULL, NULL,
92             (GClassInitFunc) sp_text_context_class_init,
93             NULL, NULL,
94             sizeof(SPTextContext),
95             4,
96             (GInstanceInitFunc) sp_text_context_init,
97             NULL,   /* value_table */
98         };
99         type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPTextContext", &info, (GTypeFlags)0);
100     }
101     return type;
104 static void
105 sp_text_context_class_init(SPTextContextClass *klass)
107     GObjectClass *object_class=(GObjectClass *)klass;
108     SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
110     parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
112     object_class->dispose = sp_text_context_dispose;
114     event_context_class->setup = sp_text_context_setup;
115     event_context_class->finish = sp_text_context_finish;
116     event_context_class->root_handler = sp_text_context_root_handler;
117     event_context_class->item_handler = sp_text_context_item_handler;
120 static void
121 sp_text_context_init(SPTextContext *tc)
123     SPEventContext *event_context = SP_EVENT_CONTEXT(tc);
125     event_context->cursor_shape = cursor_text_xpm;
126     event_context->hot_x = 7;
127     event_context->hot_y = 7;
129     event_context->xp = 0;
130     event_context->yp = 0;
131     event_context->tolerance = 0;
132     event_context->within_tolerance = false;
134     tc->imc = NULL;
136     tc->text = NULL;
137     tc->pdoc = Geom::Point(0, 0);
138     new (&tc->text_sel_start) Inkscape::Text::Layout::iterator();
139     new (&tc->text_sel_end) Inkscape::Text::Layout::iterator();
140     new (&tc->text_selection_quads) std::vector<SPCanvasItem*>();
142     tc->unimode = false;
144     tc->cursor = NULL;
145     tc->indicator = NULL;
146     tc->frame = NULL;
147     tc->grabbed = NULL;
148     tc->timeout = 0;
149     tc->show = FALSE;
150     tc->phase = 0;
151     tc->nascent_object = 0;
152     tc->over_text = 0;
153     tc->dragging = 0;
154     tc->creating = 0;
156     new (&tc->sel_changed_connection) sigc::connection();
157     new (&tc->sel_modified_connection) sigc::connection();
158     new (&tc->style_set_connection) sigc::connection();
159     new (&tc->style_query_connection) sigc::connection();
162 static void
163 sp_text_context_dispose(GObject *obj)
165     SPTextContext *tc = SP_TEXT_CONTEXT(obj);
166     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
167     tc->style_query_connection.~connection();
168     tc->style_set_connection.~connection();
169     tc->sel_changed_connection.~connection();
170     tc->sel_modified_connection.~connection();
172     delete ec->shape_editor;
173     ec->shape_editor = NULL;
175     tc->text_sel_end.~iterator();
176     tc->text_sel_start.~iterator();
177     tc->text_selection_quads.~vector();
178     if (G_OBJECT_CLASS(parent_class)->dispose) {
179         G_OBJECT_CLASS(parent_class)->dispose(obj);
180     }
181     if (tc->grabbed) {
182         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
183         tc->grabbed = NULL;
184     }
186     Inkscape::Rubberband::get(ec->desktop)->stop();
189 static void
190 sp_text_context_setup(SPEventContext *ec)
192     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
193     SPDesktop *desktop = ec->desktop;
194     GtkSettings* settings = gtk_settings_get_default();
195     gint timeout = 0;
196     g_object_get( settings, "gtk-cursor-blink-time", &timeout, NULL );
197     if (timeout < 0) {
198         timeout = 200;
199     } else {
200         timeout /= 2;
201     }
203     tc->cursor = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLLINE, NULL);
204     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), 100, 0, 100, 100);
205     sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
206     sp_canvas_item_hide(tc->cursor);
208     tc->indicator = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
209     SP_CTRLRECT(tc->indicator)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100)));
210     SP_CTRLRECT(tc->indicator)->setColor(0x0000ff7f, false, 0);
211     sp_canvas_item_hide(tc->indicator);
213     tc->frame = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
214     SP_CTRLRECT(tc->frame)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100)));
215     SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0);
216     sp_canvas_item_hide(tc->frame);
218     tc->timeout = gtk_timeout_add(timeout, (GtkFunction) sp_text_context_timeout, ec);
220     tc->imc = gtk_im_multicontext_new();
221     if (tc->imc) {
222         GtkWidget *canvas = GTK_WIDGET(sp_desktop_canvas(desktop));
224         /* im preedit handling is very broken in inkscape for
225          * multi-byte characters.  See bug 1086769.
226          * We need to let the IM handle the preediting, and
227          * just take in the characters when they're finished being
228          * entered.
229          */
230         gtk_im_context_set_use_preedit(tc->imc, FALSE);
231         gtk_im_context_set_client_window(tc->imc, canvas->window);
233         g_signal_connect(G_OBJECT(canvas), "focus_in_event", G_CALLBACK(sptc_focus_in), tc);
234         g_signal_connect(G_OBJECT(canvas), "focus_out_event", G_CALLBACK(sptc_focus_out), tc);
235         g_signal_connect(G_OBJECT(tc->imc), "commit", G_CALLBACK(sptc_commit), tc);
237         if (GTK_WIDGET_HAS_FOCUS(canvas)) {
238             sptc_focus_in(canvas, NULL, tc);
239         }
240     }
242     if (((SPEventContextClass *) parent_class)->setup)
243         ((SPEventContextClass *) parent_class)->setup(ec);
245     ec->shape_editor = new ShapeEditor(ec->desktop);
247     SPItem *item = sp_desktop_selection(ec->desktop)->singleItem();
248     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
249         ec->shape_editor->set_item(item, SH_KNOTHOLDER);
250     }
252     tc->sel_changed_connection = sp_desktop_selection(desktop)->connectChanged(
253         sigc::bind(sigc::ptr_fun(&sp_text_context_selection_changed), tc)
254         );
255     tc->sel_modified_connection = sp_desktop_selection(desktop)->connectModified(
256         sigc::bind(sigc::ptr_fun(&sp_text_context_selection_modified), tc)
257         );
258     tc->style_set_connection = desktop->connectSetStyle(
259         sigc::bind(sigc::ptr_fun(&sp_text_context_style_set), tc)
260         );
261     tc->style_query_connection = desktop->connectQueryStyle(
262         sigc::bind(sigc::ptr_fun(&sp_text_context_style_query), tc)
263         );
265     sp_text_context_selection_changed(sp_desktop_selection(desktop), tc);
267     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
268     if (prefs->getBool("/tools/text/selcue")) {
269         ec->enableSelectionCue();
270     }
271     if (prefs->getBool("/tools/text/gradientdrag")) {
272         ec->enableGrDrag();
273     }
276 static void
277 sp_text_context_finish(SPEventContext *ec)
279     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
281     if (ec->desktop) {
282         sp_signal_disconnect_by_data(sp_desktop_canvas(ec->desktop), tc);
283     }
285     ec->enableGrDrag(false);
287     tc->style_set_connection.disconnect();
288     tc->style_query_connection.disconnect();
289     tc->sel_changed_connection.disconnect();
290     tc->sel_modified_connection.disconnect();
292     sp_text_context_forget_text(SP_TEXT_CONTEXT(ec));
294     if (tc->imc) {
295         g_object_unref(G_OBJECT(tc->imc));
296         tc->imc = NULL;
297     }
299     if (tc->timeout) {
300         gtk_timeout_remove(tc->timeout);
301         tc->timeout = 0;
302     }
304     if (tc->cursor) {
305         gtk_object_destroy(GTK_OBJECT(tc->cursor));
306         tc->cursor = NULL;
307     }
309     if (tc->indicator) {
310         gtk_object_destroy(GTK_OBJECT(tc->indicator));
311         tc->indicator = NULL;
312     }
314     if (tc->frame) {
315         gtk_object_destroy(GTK_OBJECT(tc->frame));
316         tc->frame = NULL;
317     }
319     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ;
320          it != tc->text_selection_quads.end() ; ++it) {
321         sp_canvas_item_hide(*it);
322         gtk_object_destroy(*it);
323     }
324     tc->text_selection_quads.clear();
328 static gint
329 sp_text_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
331     SPTextContext *tc = SP_TEXT_CONTEXT(event_context);
332     SPDesktop *desktop = event_context->desktop;
333     SPItem *item_ungrouped;
335     gint ret = FALSE;
337     sp_text_context_validate_cursor_iterators(tc);
339     switch (event->type) {
340         case GDK_BUTTON_PRESS:
341             if (event->button.button == 1 && !event_context->space_panning) {
342                 // find out clicked item, disregarding groups
343                 item_ungrouped = desktop->item_at_point(Geom::Point(event->button.x, event->button.y), TRUE);
344                 if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
345                     sp_desktop_selection(desktop)->set(item_ungrouped);
346                     if (tc->text) {
347                         // find out click point in document coordinates
348                         Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
349                         // set the cursor closest to that point
350                         tc->text_sel_start = tc->text_sel_end = sp_te_get_position_by_coords(tc->text, p);
351                         // update display
352                         sp_text_context_update_cursor(tc);
353                         sp_text_context_update_text_selection(tc);
354                         tc->dragging = 1;
355                     }
356                     ret = TRUE;
357                 }
358             }
359             break;
360         case GDK_2BUTTON_PRESS:
361             if (event->button.button == 1 && tc->text) {
362                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
363                 if (layout) {
364                     if (!layout->isStartOfWord(tc->text_sel_start))
365                         tc->text_sel_start.prevStartOfWord();
366                     if (!layout->isEndOfWord(tc->text_sel_end))
367                         tc->text_sel_end.nextEndOfWord();
368                     sp_text_context_update_cursor(tc);
369                     sp_text_context_update_text_selection(tc);
370                     tc->dragging = 2;
371                     ret = TRUE;
372                 }
373             }
374             break;
375         case GDK_3BUTTON_PRESS:
376             if (event->button.button == 1 && tc->text) {
377                 tc->text_sel_start.thisStartOfLine();
378                 tc->text_sel_end.thisEndOfLine();
379                 sp_text_context_update_cursor(tc);
380                 sp_text_context_update_text_selection(tc);
381                 tc->dragging = 3;
382                 ret = TRUE;
383             }
384             break;
385         case GDK_BUTTON_RELEASE:
386             if (event->button.button == 1 && tc->dragging && !event_context->space_panning) {
387                 tc->dragging = 0;
388                 sp_event_context_discard_delayed_snap_event(event_context);
389                 ret = TRUE;
390             }
391             break;
392         case GDK_MOTION_NOTIFY:
393             if (event->motion.state & GDK_BUTTON1_MASK && tc->dragging && !event_context->space_panning) {
394                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
395                 if (!layout) break;
396                 // find out click point in document coordinates
397                 Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
398                 // set the cursor closest to that point
399                 Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(tc->text, p);
400                 if (tc->dragging == 2) {
401                     // double-click dragging: go by word
402                     if (new_end < tc->text_sel_start) {
403                         if (!layout->isStartOfWord(new_end))
404                             new_end.prevStartOfWord();
405                     } else
406                         if (!layout->isEndOfWord(new_end))
407                             new_end.nextEndOfWord();
408                 } else if (tc->dragging == 3) {
409                     // triple-click dragging: go by line
410                     if (new_end < tc->text_sel_start)
411                         new_end.thisStartOfLine();
412                     else
413                         new_end.thisEndOfLine();
414                 }
415                 // update display
416                 if (tc->text_sel_end != new_end) {
417                     tc->text_sel_end = new_end;
418                     sp_text_context_update_cursor(tc);
419                     sp_text_context_update_text_selection(tc);
420                 }
421                 gobble_motion_events(GDK_BUTTON1_MASK);
422                 ret = TRUE;
423                 break;
424             }
425             // find out item under mouse, disregarding groups
426             item_ungrouped = desktop->item_at_point(Geom::Point(event->button.x, event->button.y), TRUE);
427             if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
429                 Inkscape::Text::Layout const *layout = te_get_layout(item_ungrouped);
430                 if (layout->inputTruncated()) {
431                     SP_CTRLRECT(tc->indicator)->setColor(0xff0000ff, false, 0);
432                 } else {
433                     SP_CTRLRECT(tc->indicator)->setColor(0x0000ff7f, false, 0);
434                 }
435                 Geom::OptRect ibbox = item_ungrouped->getBboxDesktop();
436                 if (ibbox) {
437                     SP_CTRLRECT(tc->indicator)->setRectangle(*ibbox);
438                 }
439                 sp_canvas_item_show(tc->indicator);
441                 event_context->cursor_shape = cursor_text_insert_xpm;
442                 event_context->hot_x = 7;
443                 event_context->hot_y = 10;
444                 sp_event_context_update_cursor(event_context);
445                 sp_text_context_update_text_selection(tc);
447                 if (SP_IS_TEXT (item_ungrouped)) {
448                     desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
449                 } else {
450                     desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the flowed text, <b>drag</b> to select part of the text."));
451                 }
453                 tc->over_text = true;
455                 ret = TRUE;
456             }
457             break;
458         default:
459             break;
460     }
462     if (!ret) {
463         if (((SPEventContextClass *) parent_class)->item_handler)
464             ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
465     }
467     return ret;
470 static void
471 sp_text_context_setup_text(SPTextContext *tc)
473     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
475     /* Create <text> */
476     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_EVENT_CONTEXT_DESKTOP(ec)->doc());
477     Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
478     rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
480     /* Set style */
481     sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "/tools/text", true);
483     sp_repr_set_svg_double(rtext, "x", tc->pdoc[Geom::X]);
484     sp_repr_set_svg_double(rtext, "y", tc->pdoc[Geom::Y]);
486     /* Create <tspan> */
487     Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
488     rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
489     rtext->addChild(rtspan, NULL);
490     Inkscape::GC::release(rtspan);
492     /* Create TEXT */
493     Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
494     rtspan->addChild(rstring, NULL);
495     Inkscape::GC::release(rstring);
496     SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
497     /* fixme: Is selection::changed really immediate? */
498     /* yes, it's immediate .. why does it matter? */
499     sp_desktop_selection(ec->desktop)->set(text_item);
500     Inkscape::GC::release(rtext);
501     text_item->transform = SP_ITEM(ec->desktop->currentLayer())->i2doc_affine().inverse();
503     text_item->updateRepr();
504     SPDocumentUndo::done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
505                      _("Create text"));
508 /**
509  * Insert the character indicated by tc.uni to replace the current selection,
510  * and reset tc.uni/tc.unipos to empty string.
511  *
512  * \pre tc.uni/tc.unipos non-empty.
513  */
514 static void
515 insert_uni_char(SPTextContext *const tc)
517     g_return_if_fail(tc->unipos
518                      && tc->unipos < sizeof(tc->uni)
519                      && tc->uni[tc->unipos] == '\0');
520     unsigned int uv;
521     sscanf(tc->uni, "%x", &uv);
522     tc->unipos = 0;
523     tc->uni[tc->unipos] = '\0';
525     if ( !g_unichar_isprint(static_cast<gunichar>(uv))
526          && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
527         // This may be due to bad input, so it goes to statusbar.
528         tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
529                                            _("Non-printable character"));
530     } else {
531         if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
532             sp_text_context_setup_text(tc);
533             tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
534         }
536         gchar u[10];
537         guint const len = g_unichar_to_utf8(uv, u);
538         u[len] = '\0';
540         tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
541         sp_text_context_update_cursor(tc);
542         sp_text_context_update_text_selection(tc);
543         SPDocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM,
544                          _("Insert Unicode character"));
545     }
548 static void
549 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
551     unsigned int uv;
552     sscanf(hex, "%x", &uv);
553     if (!g_unichar_isprint((gunichar) uv)) {
554         uv = 0xfffd;
555     }
556     guint const len = g_unichar_to_utf8(uv, utf8);
557     utf8[len] = '\0';
560 static void
561 show_curr_uni_char(SPTextContext *const tc)
563     g_return_if_fail(tc->unipos < sizeof(tc->uni)
564                      && tc->uni[tc->unipos] == '\0');
565     if (tc->unipos) {
566         char utf8[10];
567         hex_to_printable_utf8_buf(tc->uni, utf8);
569         /* Status bar messages are in pango markup, so we need xml escaping. */
570         if (utf8[1] == '\0') {
571             switch(utf8[0]) {
572                 case '<': strcpy(utf8, "&lt;"); break;
573                 case '>': strcpy(utf8, "&gt;"); break;
574                 case '&': strcpy(utf8, "&amp;"); break;
575                 default: break;
576             }
577         }
578         tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
579                                           _("Unicode (<b>Enter</b> to finish): %s: %s"), tc->uni, utf8);
580     } else {
581         tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
582     }
585 static gint
586 sp_text_context_root_handler(SPEventContext *const event_context, GdkEvent *const event)
588     SPTextContext *const tc = SP_TEXT_CONTEXT(event_context);
590     SPDesktop *desktop = event_context->desktop;
592     sp_canvas_item_hide(tc->indicator);
594     sp_text_context_validate_cursor_iterators(tc);
596     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
597     event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
599     switch (event->type) {
600         case GDK_BUTTON_PRESS:
601             if (event->button.button == 1 && !event_context->space_panning) {
603                 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
604                     return TRUE;
605                 }
607                 // save drag origin
608                 event_context->xp = (gint) event->button.x;
609                 event_context->yp = (gint) event->button.y;
610                 event_context->within_tolerance = true;
612                 Geom::Point const button_pt(event->button.x, event->button.y);
613                 tc->p0 = desktop->w2d(button_pt);
614                 Inkscape::Rubberband::get(desktop)->start(desktop, tc->p0);
615                 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
616                                     GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
617                                     NULL, event->button.time);
618                 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
619                 tc->creating = 1;
621                 /* Processed */
622                 return TRUE;
623             }
624             break;
625         case GDK_MOTION_NOTIFY:
626             if (tc->over_text) {
627                 tc->over_text = 0;
628                 // update cursor and statusbar: we are not over a text object now
629                 event_context->cursor_shape = cursor_text_xpm;
630                 event_context->hot_x = 7;
631                 event_context->hot_y = 7;
632                 sp_event_context_update_cursor(event_context);
633                 desktop->event_context->defaultMessageContext()->clear();
634             }
636             if (tc->creating && event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
637                 if ( event_context->within_tolerance
638                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
639                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
640                     break; // do not drag if we're within tolerance from origin
641                 }
642                 // Once the user has moved farther than tolerance from the original location
643                 // (indicating they intend to draw, not click), then always process the
644                 // motion notify coordinates as given (no snapping back to origin)
645                 event_context->within_tolerance = false;
647                 Geom::Point const motion_pt(event->motion.x, event->motion.y);
648                 Geom::Point const p = desktop->w2d(motion_pt);
650                 Inkscape::Rubberband::get(desktop)->move(p);
651                 gobble_motion_events(GDK_BUTTON1_MASK);
653                 // status text
654                 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::X]), desktop->namedview->getDefaultMetric());
655                 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::Y]), desktop->namedview->getDefaultMetric());
656                 event_context->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Flowed text frame</b>: %s &#215; %s"), xs->str, ys->str);
657                 g_string_free(xs, FALSE);
658                 g_string_free(ys, FALSE);
660             }
661             break;
662         case GDK_BUTTON_RELEASE:
663             if (event->button.button == 1 && !event_context->space_panning) {
665                 if (tc->grabbed) {
666                     sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
667                     tc->grabbed = NULL;
668                 }
670                 Inkscape::Rubberband::get(desktop)->stop();
672                 if (tc->creating && event_context->within_tolerance) {
673                     /* Button 1, set X & Y & new item */
674                     sp_desktop_selection(desktop)->clear();
675                     Geom::Point dtp = desktop->w2d(Geom::Point(event->button.x, event->button.y));
676                     tc->pdoc = desktop->dt2doc(dtp);
678                     tc->show = TRUE;
679                     tc->phase = 1;
680                     tc->nascent_object = 1; // new object was just created
682                     /* Cursor */
683                     sp_canvas_item_show(tc->cursor);
684                     // Cursor height is defined by the new text object's font size; it needs to be set
685                     // articifically here, for the text object does not exist yet:
686                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
687                     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), dtp, dtp + Geom::Point(0, cursor_height));
688                     event_context->_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
690                     event_context->within_tolerance = false;
691                 } else if (tc->creating) {
692                     Geom::Point const button_pt(event->button.x, event->button.y);
693                     Geom::Point p1 = desktop->w2d(button_pt);
694                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
695                     if (fabs(p1[Geom::Y] - tc->p0[Geom::Y]) > cursor_height) {
696                         // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
697                         SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
698                         /* Set style */
699                         sp_desktop_apply_style_tool(desktop, SP_OBJECT_REPR(ft), "/tools/text", true);
700                         sp_desktop_selection(desktop)->set(ft);
701                         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
702                         SPDocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
703                                          _("Create flowed text"));
704                     } else {
705                         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
706                     }
707                 }
708                 tc->creating = false;
709                 return TRUE;
710             }
711             break;
712         case GDK_KEY_PRESS: {
713             guint const group0_keyval = get_group0_keyval(&event->key);
715             if (group0_keyval == GDK_KP_Add ||
716                 group0_keyval == GDK_KP_Subtract) {
717                 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
718                     break; // otherwise pass on keypad +/- so they can zoom
719             }
721             if ((tc->text) || (tc->nascent_object)) {
722                 // there is an active text object in this context, or a new object was just created
724                 if (tc->unimode || !tc->imc
725                     || (MOD__CTRL && MOD__SHIFT)    // input methods tend to steal this for unimode,
726                                                     // but we have our own so make sure they don't swallow it
727                     || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
728                     //IM did not consume the key, or we're in unimode
730                         if (!MOD__CTRL_ONLY && tc->unimode) {
731                             /* TODO: ISO 14755 (section 3 Definitions) says that we should also
732                                accept the first 6 characters of alphabets other than the latin
733                                alphabet "if the Latin alphabet is not used".  The below is also
734                                reasonable (viz. hope that the user's keyboard includes latin
735                                characters and force latin interpretation -- just as we do for our
736                                keyboard shortcuts), but differs from the ISO 14755
737                                recommendation. */
738                             switch (group0_keyval) {
739                                 case GDK_space:
740                                 case GDK_KP_Space: {
741                                     if (tc->unipos) {
742                                         insert_uni_char(tc);
743                                     }
744                                     /* Stay in unimode. */
745                                     show_curr_uni_char(tc);
746                                     return TRUE;
747                                 }
749                                 case GDK_BackSpace: {
750                                     g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
751                                     if (tc->unipos) {
752                                         tc->uni[--tc->unipos] = '\0';
753                                     }
754                                     show_curr_uni_char(tc);
755                                     return TRUE;
756                                 }
758                                 case GDK_Return:
759                                 case GDK_KP_Enter: {
760                                     if (tc->unipos) {
761                                         insert_uni_char(tc);
762                                     }
763                                     /* Exit unimode. */
764                                     tc->unimode = false;
765                                     event_context->defaultMessageContext()->clear();
766                                     return TRUE;
767                                 }
769                                 case GDK_Escape: {
770                                     // Cancel unimode.
771                                     tc->unimode = false;
772                                     gtk_im_context_reset(tc->imc);
773                                     event_context->defaultMessageContext()->clear();
774                                     return TRUE;
775                                 }
777                                 case GDK_Shift_L:
778                                 case GDK_Shift_R:
779                                     break;
781                                 default: {
782                                     if (g_ascii_isxdigit(group0_keyval)) {
783                                         g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
784                                         tc->uni[tc->unipos++] = group0_keyval;
785                                         tc->uni[tc->unipos] = '\0';
786                                         if (tc->unipos == 8) {
787                                             /* This behaviour is partly to allow us to continue to
788                                                use a fixed-length buffer for tc->uni.  Reason for
789                                                choosing the number 8 is that it's the length of
790                                                ``canonical form'' mentioned in the ISO 14755 spec.
791                                                An advantage over choosing 6 is that it allows using
792                                                backspace for typos & misremembering when entering a
793                                                6-digit number. */
794                                             insert_uni_char(tc);
795                                         }
796                                         show_curr_uni_char(tc);
797                                         return TRUE;
798                                     } else {
799                                         /* The intent is to ignore but consume characters that could be
800                                            typos for hex digits.  Gtk seems to ignore & consume all
801                                            non-hex-digits, and we do similar here.  Though note that some
802                                            shortcuts (like keypad +/- for zoom) get processed before
803                                            reaching this code. */
804                                         return TRUE;
805                                     }
806                                 }
807                             }
808                         }
810                         Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
811                         Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
812                         bool cursor_moved = false;
813                         int screenlines = 1;
814                         if (tc->text) {
815                             double spacing = sp_te_get_average_linespacing(tc->text);
816                             Geom::Rect const d = desktop->get_display_area();
817                             screenlines = (int) floor(fabs(d.min()[Geom::Y] - d.max()[Geom::Y])/spacing) - 1;
818                             if (screenlines <= 0)
819                                 screenlines = 1;
820                         }
822                         /* Neither unimode nor IM consumed key; process text tool shortcuts */
823                         switch (group0_keyval) {
824                             case GDK_x:
825                             case GDK_X:
826                                 if (MOD__ALT_ONLY) {
827                                     desktop->setToolboxFocusTo ("altx-text");
828                                     return TRUE;
829                                 }
830                                 break;
831                             case GDK_space:
832                                 if (MOD__CTRL_ONLY) {
833                                     /* No-break space */
834                                     if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
835                                         sp_text_context_setup_text(tc);
836                                         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
837                                     }
838                                     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
839                                     sp_text_context_update_cursor(tc);
840                                     sp_text_context_update_text_selection(tc);
841                                     desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
842                                     SPDocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
843                                                      _("Insert no-break space"));
844                                     return TRUE;
845                                 }
846                                 break;
847                             case GDK_U:
848                             case GDK_u:
849                                 if (MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT)) {
850                                     if (tc->unimode) {
851                                         tc->unimode = false;
852                                         event_context->defaultMessageContext()->clear();
853                                     } else {
854                                         tc->unimode = true;
855                                         tc->unipos = 0;
856                                         event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
857                                     }
858                                     if (tc->imc) {
859                                         gtk_im_context_reset(tc->imc);
860                                     }
861                                     return TRUE;
862                                 }
863                                 break;
864                             case GDK_B:
865                             case GDK_b:
866                                 if (MOD__CTRL_ONLY && tc->text) {
867                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
868                                     SPCSSAttr *css = sp_repr_css_attr_new();
869                                     if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
870                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
871                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
872                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
873                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
874                                         sp_repr_css_set_property(css, "font-weight", "bold");
875                                     else
876                                         sp_repr_css_set_property(css, "font-weight", "normal");
877                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
878                                     sp_repr_css_attr_unref(css);
879                                     SPDocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
880                                                      _("Make bold"));
881                                     sp_text_context_update_cursor(tc);
882                                     sp_text_context_update_text_selection(tc);
883                                     return TRUE;
884                                 }
885                                 break;
886                             case GDK_I:
887                             case GDK_i:
888                                 if (MOD__CTRL_ONLY && tc->text) {
889                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
890                                     SPCSSAttr *css = sp_repr_css_attr_new();
891                                     if (style->font_style.computed != SP_CSS_FONT_STYLE_NORMAL)
892                                         sp_repr_css_set_property(css, "font-style", "normal");
893                                     else
894                                         sp_repr_css_set_property(css, "font-style", "italic");
895                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
896                                     sp_repr_css_attr_unref(css);
897                                     SPDocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
898                                                      _("Make italic"));
899                                     sp_text_context_update_cursor(tc);
900                                     sp_text_context_update_text_selection(tc);
901                                     return TRUE;
902                                 }
903                                 break;
905                             case GDK_A:
906                             case GDK_a:
907                                 if (MOD__CTRL_ONLY && tc->text) {
908                                     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
909                                     if (layout) {
910                                         tc->text_sel_start = layout->begin();
911                                         tc->text_sel_end = layout->end();
912                                         sp_text_context_update_cursor(tc);
913                                         sp_text_context_update_text_selection(tc);
914                                         return TRUE;
915                                     }
916                                 }
917                                 break;
919                             case GDK_Return:
920                             case GDK_KP_Enter:
921                             {
922                                 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
923                                     sp_text_context_setup_text(tc);
924                                     tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
925                                 }
927                                 iterator_pair enter_pair;
928                                 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, enter_pair);
929                                 (void)success; // TODO cleanup
930                                 tc->text_sel_start = tc->text_sel_end = enter_pair.first;
932                                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
934                                 sp_text_context_update_cursor(tc);
935                                 sp_text_context_update_text_selection(tc);
936                                 SPDocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
937                                                  _("New line"));
938                                 return TRUE;
939                             }
940                             case GDK_BackSpace:
941                                 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
943                                     bool noSelection = false;
945                                         if (tc->text_sel_start == tc->text_sel_end) {
946                                         tc->text_sel_start.prevCursorPosition();
947                                         noSelection = true;
948                                     }
950                                         iterator_pair bspace_pair;
951                                         bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, bspace_pair);
953                                     if (noSelection) {
954                                         if (success) {
955                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
956                                         } else { // nothing deleted
957                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.second;
958                                         }
959                                     } else {
960                                         if (success) {
961                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
962                                         } else { // nothing deleted
963                                             tc->text_sel_start = bspace_pair.first;
964                                             tc->text_sel_end = bspace_pair.second;
965                                         }
966                                     }
968                                     sp_text_context_update_cursor(tc);
969                                     sp_text_context_update_text_selection(tc);
970                                     SPDocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
971                                                      _("Backspace"));
972                                 }
973                                 return TRUE;
974                             case GDK_Delete:
975                             case GDK_KP_Delete:
976                                 if (tc->text) {
977                                     bool noSelection = false;
979                                     if (tc->text_sel_start == tc->text_sel_end) {
980                                         tc->text_sel_end.nextCursorPosition();
981                                         noSelection = true;
982                                     }
984                                     iterator_pair del_pair;
985                                     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, del_pair);
987                                     if (noSelection) {
988                                         tc->text_sel_start = tc->text_sel_end = del_pair.first;
989                                     } else {
990                                         if (success) {
991                                             tc->text_sel_start = tc->text_sel_end = del_pair.first;
992                                         } else { // nothing deleted
993                                             tc->text_sel_start = del_pair.first;
994                                             tc->text_sel_end = del_pair.second;
995                                         }
996                                     }
999                                     sp_text_context_update_cursor(tc);
1000                                     sp_text_context_update_text_selection(tc);
1001                                     SPDocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
1002                                                      _("Delete"));
1003                                 }
1004                                 return TRUE;
1005                             case GDK_Left:
1006                             case GDK_KP_Left:
1007                             case GDK_KP_4:
1008                                 if (tc->text) {
1009                                     if (MOD__ALT) {
1010                                         gint mul = 1 + gobble_key_events(
1011                                             get_group0_keyval(&event->key), 0); // with any mask
1012                                         if (MOD__SHIFT)
1013                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-10, 0));
1014                                         else
1015                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-1, 0));
1016                                         sp_text_context_update_cursor(tc);
1017                                         sp_text_context_update_text_selection(tc);
1018                                         SPDocumentUndo::maybe_done(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT,
1019                                                                _("Kern to the left"));
1020                                     } else {
1021                                         if (MOD__CTRL)
1022                                             tc->text_sel_end.cursorLeftWithControl();
1023                                         else
1024                                             tc->text_sel_end.cursorLeft();
1025                                         cursor_moved = true;
1026                                         break;
1027                                     }
1028                                 }
1029                                 return TRUE;
1030                             case GDK_Right:
1031                             case GDK_KP_Right:
1032                             case GDK_KP_6:
1033                                 if (tc->text) {
1034                                     if (MOD__ALT) {
1035                                         gint mul = 1 + gobble_key_events(
1036                                             get_group0_keyval(&event->key), 0); // with any mask
1037                                         if (MOD__SHIFT)
1038                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*10, 0));
1039                                         else
1040                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*1, 0));
1041                                         sp_text_context_update_cursor(tc);
1042                                         sp_text_context_update_text_selection(tc);
1043                                         SPDocumentUndo::maybe_done(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT,
1044                                                                _("Kern to the right"));
1045                                     } else {
1046                                         if (MOD__CTRL)
1047                                             tc->text_sel_end.cursorRightWithControl();
1048                                         else
1049                                             tc->text_sel_end.cursorRight();
1050                                         cursor_moved = true;
1051                                         break;
1052                                     }
1053                                 }
1054                                 return TRUE;
1055                             case GDK_Up:
1056                             case GDK_KP_Up:
1057                             case GDK_KP_8:
1058                                 if (tc->text) {
1059                                     if (MOD__ALT) {
1060                                         gint mul = 1 + gobble_key_events(
1061                                             get_group0_keyval(&event->key), 0); // with any mask
1062                                         if (MOD__SHIFT)
1063                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-10));
1064                                         else
1065                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-1));
1066                                         sp_text_context_update_cursor(tc);
1067                                         sp_text_context_update_text_selection(tc);
1068                                         SPDocumentUndo::maybe_done(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT,
1069                                                                _("Kern up"));
1071                                     } else {
1072                                         if (MOD__CTRL)
1073                                             tc->text_sel_end.cursorUpWithControl();
1074                                         else
1075                                             tc->text_sel_end.cursorUp();
1076                                         cursor_moved = true;
1077                                         break;
1078                                     }
1079                                 }
1080                                 return TRUE;
1081                             case GDK_Down:
1082                             case GDK_KP_Down:
1083                             case GDK_KP_2:
1084                                 if (tc->text) {
1085                                     if (MOD__ALT) {
1086                                         gint mul = 1 + gobble_key_events(
1087                                             get_group0_keyval(&event->key), 0); // with any mask
1088                                         if (MOD__SHIFT)
1089                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*10));
1090                                         else
1091                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*1));
1092                                         sp_text_context_update_cursor(tc);
1093                                         sp_text_context_update_text_selection(tc);
1094                                         SPDocumentUndo::maybe_done(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT,
1095                                                                _("Kern down"));
1097                                     } else {
1098                                         if (MOD__CTRL)
1099                                             tc->text_sel_end.cursorDownWithControl();
1100                                         else
1101                                             tc->text_sel_end.cursorDown();
1102                                         cursor_moved = true;
1103                                         break;
1104                                     }
1105                                 }
1106                                 return TRUE;
1107                             case GDK_Home:
1108                             case GDK_KP_Home:
1109                                 if (tc->text) {
1110                                     if (MOD__CTRL)
1111                                         tc->text_sel_end.thisStartOfShape();
1112                                     else
1113                                         tc->text_sel_end.thisStartOfLine();
1114                                     cursor_moved = true;
1115                                     break;
1116                                 }
1117                                 return TRUE;
1118                             case GDK_End:
1119                             case GDK_KP_End:
1120                                 if (tc->text) {
1121                                     if (MOD__CTRL)
1122                                         tc->text_sel_end.nextStartOfShape();
1123                                     else
1124                                         tc->text_sel_end.thisEndOfLine();
1125                                     cursor_moved = true;
1126                                     break;
1127                                 }
1128                                 return TRUE;
1129                             case GDK_Page_Down:
1130                             case GDK_KP_Page_Down:
1131                                 if (tc->text) {
1132                                     tc->text_sel_end.cursorDown(screenlines);
1133                                     cursor_moved = true;
1134                                     break;
1135                                 }
1136                                 return TRUE;
1137                             case GDK_Page_Up:
1138                             case GDK_KP_Page_Up:
1139                                 if (tc->text) {
1140                                     tc->text_sel_end.cursorUp(screenlines);
1141                                     cursor_moved = true;
1142                                     break;
1143                                 }
1144                                 return TRUE;
1145                             case GDK_Escape:
1146                                 if (tc->creating) {
1147                                     tc->creating = 0;
1148                                     if (tc->grabbed) {
1149                                         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1150                                         tc->grabbed = NULL;
1151                                     }
1152                                     Inkscape::Rubberband::get(desktop)->stop();
1153                                 } else {
1154                                     sp_desktop_selection(desktop)->clear();
1155                                 }
1156                                 tc->nascent_object = FALSE;
1157                                 return TRUE;
1158                             case GDK_bracketleft:
1159                                 if (tc->text) {
1160                                     if (MOD__ALT || MOD__CTRL) {
1161                                         if (MOD__ALT) {
1162                                             if (MOD__SHIFT) {
1163                                                 // FIXME: alt+shift+[] does not work, don't know why
1164                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1165                                             } else {
1166                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1167                                             }
1168                                         } else {
1169                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -90);
1170                                         }
1171                                         SPDocumentUndo::maybe_done(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT,
1172                                                                _("Rotate counterclockwise"));
1173                                         sp_text_context_update_cursor(tc);
1174                                         sp_text_context_update_text_selection(tc);
1175                                         return TRUE;
1176                                     }
1177                                 }
1178                                 break;
1179                             case GDK_bracketright:
1180                                 if (tc->text) {
1181                                     if (MOD__ALT || MOD__CTRL) {
1182                                         if (MOD__ALT) {
1183                                             if (MOD__SHIFT) {
1184                                                 // FIXME: alt+shift+[] does not work, don't know why
1185                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1186                                             } else {
1187                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1188                                             }
1189                                         } else {
1190                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 90);
1191                                         }
1192                                         SPDocumentUndo::maybe_done(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT,
1193                                                                 _("Rotate clockwise"));
1194                                         sp_text_context_update_cursor(tc);
1195                                         sp_text_context_update_text_selection(tc);
1196                                         return TRUE;
1197                                     }
1198                                 }
1199                                 break;
1200                             case GDK_less:
1201                             case GDK_comma:
1202                                 if (tc->text) {
1203                                     if (MOD__ALT) {
1204                                         if (MOD__CTRL) {
1205                                             if (MOD__SHIFT)
1206                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1207                                             else
1208                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1209                                             SPDocumentUndo::maybe_done(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT,
1210                                                                     _("Contract line spacing"));
1212                                         } else {
1213                                             if (MOD__SHIFT)
1214                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1215                                             else
1216                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1217                                             SPDocumentUndo::maybe_done(sp_desktop_document(desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT,
1218                                                                     _("Contract letter spacing"));
1220                                         }
1221                                         sp_text_context_update_cursor(tc);
1222                                         sp_text_context_update_text_selection(tc);
1223                                         return TRUE;
1224                                     }
1225                                 }
1226                                 break;
1227                             case GDK_greater:
1228                             case GDK_period:
1229                                 if (tc->text) {
1230                                     if (MOD__ALT) {
1231                                         if (MOD__CTRL) {
1232                                             if (MOD__SHIFT)
1233                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1234                                             else
1235                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1236                                             SPDocumentUndo::maybe_done(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT,
1237                                                                     _("Expand line spacing"));
1239                                         } else {
1240                                             if (MOD__SHIFT)
1241                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1242                                             else
1243                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1244                                             SPDocumentUndo::maybe_done(sp_desktop_document(desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT,
1245                                                                     _("Expand letter spacing"));
1247                                         }
1248                                         sp_text_context_update_cursor(tc);
1249                                         sp_text_context_update_text_selection(tc);
1250                                         return TRUE;
1251                                     }
1252                                 }
1253                                 break;
1254                             default:
1255                                 break;
1256                         }
1258                         if (cursor_moved) {
1259                             if (!MOD__SHIFT)
1260                                 tc->text_sel_start = tc->text_sel_end;
1261                             if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1262                                 sp_text_context_update_cursor(tc);
1263                                 sp_text_context_update_text_selection(tc);
1264                             }
1265                             return TRUE;
1266                         }
1268                 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1269             } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1270                 // except up/down that are swallowed to prevent the zoom field from activation
1271                 if ((group0_keyval == GDK_Up    ||
1272                      group0_keyval == GDK_Down  ||
1273                      group0_keyval == GDK_KP_Up ||
1274                      group0_keyval == GDK_KP_Down )
1275                     && !MOD__CTRL_ONLY) {
1276                     return TRUE;
1277                 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1278                     if (tc->creating) {
1279                         tc->creating = 0;
1280                         if (tc->grabbed) {
1281                             sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1282                             tc->grabbed = NULL;
1283                         }
1284                         Inkscape::Rubberband::get(desktop)->stop();
1285                     }
1286                 } else if ((group0_keyval == GDK_x || group0_keyval == GDK_X) && MOD__ALT_ONLY) {
1287                     desktop->setToolboxFocusTo ("altx-text");
1288                     return TRUE;
1289                 }
1290             }
1291             break;
1292         }
1294         case GDK_KEY_RELEASE:
1295             if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1296                 return TRUE;
1297             }
1298             break;
1299         default:
1300             break;
1301     }
1303     // if nobody consumed it so far
1304     if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1305         return ((SPEventContextClass *) parent_class)->root_handler(event_context, event); // send event to parent
1306     } else {
1307         return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1308     }
1311 /**
1312  Attempts to paste system clipboard into the currently edited text, returns true on success
1313  */
1314 bool
1315 sp_text_paste_inline(SPEventContext *ec)
1317     if (!SP_IS_TEXT_CONTEXT(ec))
1318         return false;
1320     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1322     if ((tc->text) || (tc->nascent_object)) {
1323         // there is an active text object in this context, or a new object was just created
1325         Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1326         Glib::ustring const clip_text = refClipboard->wait_for_text();
1328         if (!clip_text.empty()) {
1329                 // Fix for 244940
1330                 // The XML standard defines the following as valid characters
1331                 // (Extensible Markup Language (XML) 1.0 (Fourth Edition) paragraph 2.2)
1332                 // char ::=     #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
1333                 // Since what comes in off the paste buffer will go right into XML, clean
1334                 // the text here.
1335                 Glib::ustring text(clip_text);
1336                 Glib::ustring::iterator itr = text.begin();
1337                 gunichar paste_string_uchar;
1339                 while(itr != text.end())
1340                 {
1341                     paste_string_uchar = *itr;
1343                     // Make sure we don't have a control character. We should really check
1344                     // for the whole range above... Add the rest of the invalid cases from
1345                     // above if we find additional issues
1346                     if(paste_string_uchar >= 0x00000020 ||
1347                        paste_string_uchar == 0x00000009 ||
1348                        paste_string_uchar == 0x0000000A ||
1349                        paste_string_uchar == 0x0000000D) {
1350                         itr++;
1351                     } else {
1352                         itr = text.erase(itr);
1353                     }
1354                 }
1356             if (!tc->text) { // create text if none (i.e. if nascent_object)
1357                 sp_text_context_setup_text(tc);
1358                 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1359             }
1361             // using indices is slow in ustrings. Whatever.
1362             Glib::ustring::size_type begin = 0;
1363             for ( ; ; ) {
1364                 Glib::ustring::size_type end = text.find('\n', begin);
1365                 if (end == Glib::ustring::npos) {
1366                     if (begin != text.length())
1367                         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());
1368                     break;
1369                 }
1370                 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());
1371                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1372                 begin = end + 1;
1373             }
1374             SPDocumentUndo::done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
1375                              _("Paste text"));
1377             return true;
1378         }
1379     } // FIXME: else create and select a new object under cursor!
1381     return false;
1384 /**
1385  Gets the raw characters that comprise the currently selected text, converting line
1386  breaks into lf characters.
1387 */
1388 Glib::ustring
1389 sp_text_get_selected_text(SPEventContext const *ec)
1391     if (!SP_IS_TEXT_CONTEXT(ec))
1392         return "";
1393     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1394     if (tc->text == NULL)
1395         return "";
1397     return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1400 SPCSSAttr *
1401 sp_text_get_style_at_cursor(SPEventContext const *ec)
1403     if (!SP_IS_TEXT_CONTEXT(ec))
1404         return NULL;
1405     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1406     if (tc->text == NULL)
1407         return NULL;
1409     SPObject const *obj = sp_te_object_at_position(tc->text, tc->text_sel_end);
1410     if (obj)
1411         return take_style_from_item((SPItem *) obj);
1412     return NULL;
1415 /**
1416  Deletes the currently selected characters. Returns false if there is no
1417  text selection currently.
1418 */
1419 bool sp_text_delete_selection(SPEventContext *ec)
1421     if (!SP_IS_TEXT_CONTEXT(ec))
1422         return false;
1423     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1424     if (tc->text == NULL)
1425         return false;
1427     if (tc->text_sel_start == tc->text_sel_end)
1428         return false;
1430     iterator_pair pair;
1431     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair);
1434     if (success) {
1435         tc->text_sel_start = tc->text_sel_end = pair.first;
1436     } else { // nothing deleted
1437         tc->text_sel_start = pair.first;
1438         tc->text_sel_end = pair.second;
1439     }
1441     sp_text_context_update_cursor(tc);
1442     sp_text_context_update_text_selection(tc);
1444     return true;
1447 /**
1448  * \param selection Should not be NULL.
1449  */
1450 static void
1451 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1453     g_assert(selection != NULL);
1455     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1457     ec->shape_editor->unset_item(SH_KNOTHOLDER);
1458     SPItem *item = selection->singleItem();
1459     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1460         ec->shape_editor->set_item(item, SH_KNOTHOLDER);
1461     }
1463     if (tc->text && (item != tc->text)) {
1464         sp_text_context_forget_text(tc);
1465     }
1466     tc->text = NULL;
1468     if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1469         tc->text = item;
1470         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1471         if (layout)
1472             tc->text_sel_start = tc->text_sel_end = layout->end();
1473     } else {
1474         tc->text = NULL;
1475     }
1477     // we update cursor without scrolling, because this position may not be final;
1478     // item_handler moves cusros to the point of click immediately
1479     sp_text_context_update_cursor(tc, false);
1480     sp_text_context_update_text_selection(tc);
1483 static void
1484 sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, SPTextContext *tc)
1486     sp_text_context_update_cursor(tc);
1487     sp_text_context_update_text_selection(tc);
1490 static bool
1491 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1493     if (tc->text == NULL)
1494         return false;
1495     if (tc->text_sel_start == tc->text_sel_end)
1496         return false;    // will get picked up by the parent and applied to the whole text object
1498     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1499     SPDocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1500                      _("Set text style"));
1501     sp_text_context_update_cursor(tc);
1502     sp_text_context_update_text_selection(tc);
1504     return true;
1507 static int
1508 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1510     if (tc->text == NULL)
1511         return QUERY_STYLE_NOTHING;
1512     const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1513     if (layout == NULL)
1514         return QUERY_STYLE_NOTHING;
1515     sp_text_context_validate_cursor_iterators(tc);
1517     GSList *styles_list = NULL;
1519     Inkscape::Text::Layout::iterator begin_it, end_it;
1520     if (tc->text_sel_start < tc->text_sel_end) {
1521         begin_it = tc->text_sel_start;
1522         end_it = tc->text_sel_end;
1523     } else {
1524         begin_it = tc->text_sel_end;
1525         end_it = tc->text_sel_start;
1526     }
1527     if (begin_it == end_it)
1528         if (!begin_it.prevCharacter())
1529             end_it.nextCharacter();
1530     for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1531         SPObject const *pos_obj = 0;
1532         void *rawptr = 0;
1533         layout->getSourceOfCharacter(it, &rawptr);
1534         if (!rawptr || !SP_IS_OBJECT(rawptr))
1535             continue;
1536         pos_obj = SP_OBJECT(rawptr);
1537         while (SP_IS_STRING(pos_obj) && SP_OBJECT_PARENT(pos_obj)) {
1538            pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
1539         }
1540         styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1541     }
1543     int result = sp_desktop_query_style_from_list (styles_list, style, property);
1545     g_slist_free(styles_list);
1546     return result;
1549 static void
1550 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1552     if (tc->text == NULL)
1553         return;
1554     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1555     if (layout) {     // undo can change the text length without us knowing it
1556         layout->validateIterator(&tc->text_sel_start);
1557         layout->validateIterator(&tc->text_sel_end);
1558     }
1561 static void
1562 sp_text_context_update_cursor(SPTextContext *tc,  bool scroll_to_see)
1564     GdkRectangle im_cursor = { 0, 0, 1, 1 };
1566     // due to interruptible display, tc may already be destroyed during a display update before
1567     // the cursor update (can't do both atomically, alas)
1568     if (!tc->desktop) return;
1570     if (tc->text) {
1571         Geom::Point p0, p1;
1572         sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1573         Geom::Point const d0 = p0 * SP_ITEM(tc->text)->i2d_affine();
1574         Geom::Point const d1 = p1 * SP_ITEM(tc->text)->i2d_affine();
1576         // scroll to show cursor
1577         if (scroll_to_see) {
1578             Geom::Point const center = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().midpoint();
1579             if (Geom::L2(d0 - center) > Geom::L2(d1 - center))
1580                 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1581                 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d0, 1.0);
1582             else
1583                 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d1, 1.0);
1584         }
1586         sp_canvas_item_show(tc->cursor);
1587         sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1589         /* fixme: ... need another transformation to get canvas widget coordinate space? */
1590         im_cursor.x = (int) floor(d0[Geom::X]);
1591         im_cursor.y = (int) floor(d0[Geom::Y]);
1592         im_cursor.width = (int) floor(d1[Geom::X]) - im_cursor.x;
1593         im_cursor.height = (int) floor(d1[Geom::Y]) - im_cursor.y;
1595         tc->show = TRUE;
1596         tc->phase = 1;
1598         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1599         int const nChars = layout->iteratorToCharIndex(layout->end());
1600         char const *trunc = "";
1601         bool truncated = false;
1602         if (layout->inputTruncated()) {
1603             truncated = true;
1604             trunc = _(" [truncated]");
1605         }
1606         if (SP_IS_FLOWTEXT(tc->text)) {
1607             SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1608             if (frame) {
1609                 if (truncated) {
1610                     SP_CTRLRECT(tc->frame)->setColor(0xff0000ff, false, 0);
1611                 } else {
1612                     SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0);
1613                 }
1614                 sp_canvas_item_show(tc->frame);
1615                 Geom::OptRect frame_bbox = frame->getBboxDesktop();
1616                 if (frame_bbox) {
1617                     SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
1618                 }
1619             }
1621             SP_EVENT_CONTEXT(tc)->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("Type or edit flowed text (%d characters%s); <b>Enter</b> to start new paragraph."), nChars, trunc);
1622         } else {
1623             SP_EVENT_CONTEXT(tc)->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("Type or edit text (%d characters%s); <b>Enter</b> to start new line."), nChars, trunc);
1624         }
1626     } else {
1627         sp_canvas_item_hide(tc->cursor);
1628         sp_canvas_item_hide(tc->frame);
1629         tc->show = FALSE;
1630         if (!tc->nascent_object) {
1631             SP_EVENT_CONTEXT(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
1632         }
1633     }
1635     if (tc->imc) {
1636         gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1637     }
1638     SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1641 static void sp_text_context_update_text_selection(SPTextContext *tc)
1643     // due to interruptible display, tc may already be destroyed during a display update before
1644     // the selection update (can't do both atomically, alas)
1645     if (!tc->desktop) return;
1647     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1648         sp_canvas_item_hide(*it);
1649         gtk_object_destroy(*it);
1650     }
1651     tc->text_selection_quads.clear();
1653     std::vector<Geom::Point> quads;
1654     if (tc->text != NULL)
1655         quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, (tc->text)->i2d_affine());
1656     for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1657         SPCanvasItem *quad_canvasitem;
1658         quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1659         // FIXME: make the color settable in prefs
1660         // for now, use semitrasparent blue, as cairo cannot do inversion :(
1661         sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x00777777);
1662         sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1663         sp_canvas_item_show(quad_canvasitem);
1664         tc->text_selection_quads.push_back(quad_canvasitem);
1665     }
1668 static gint
1669 sp_text_context_timeout(SPTextContext *tc)
1671     if (tc->show) {
1672         sp_canvas_item_show(tc->cursor);
1673         if (tc->phase) {
1674             tc->phase = 0;
1675             sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
1676         } else {
1677             tc->phase = 1;
1678             sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0xffffffff);
1679         }
1680     }
1682     return TRUE;
1685 static void
1686 sp_text_context_forget_text(SPTextContext *tc)
1688     if (! tc->text) return;
1689     SPItem *ti = tc->text;
1690     (void)ti;
1691     /* We have to set it to zero,
1692      * or selection changed signal messes everything up */
1693     tc->text = NULL;
1695 /* FIXME: this automatic deletion when nothing is inputted crashes the XML edittor and also crashes when duplicating an empty flowtext.
1696     So don't create an empty flowtext in the first place? Create it when first character is typed.
1697     */
1698 /*
1699     if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1700         Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1701         // the repr may already have been unparented
1702         // if we were called e.g. as the result of
1703         // an undo or the element being removed from
1704         // the XML editor
1705         if ( text_repr && sp_repr_parent(text_repr) ) {
1706             sp_repr_unparent(text_repr);
1707             SPDocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1708                      _("Remove empty text"));
1709         }
1710     }
1711 */
1714 gint
1715 sptc_focus_in(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1717     gtk_im_context_focus_in(tc->imc);
1718     return FALSE;
1721 gint
1722 sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1724     gtk_im_context_focus_out(tc->imc);
1725     return FALSE;
1728 static void
1729 sptc_commit(GtkIMContext */*imc*/, gchar *string, SPTextContext *tc)
1731     if (!tc->text) {
1732         sp_text_context_setup_text(tc);
1733         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1734     }
1736     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1737     sp_text_context_update_cursor(tc);
1738     sp_text_context_update_text_selection(tc);
1740     SPDocumentUndo::done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT,
1741                      _("Type text"));
1744 void
1745 sp_text_context_place_cursor (SPTextContext *tc, SPObject *text, Inkscape::Text::Layout::iterator where)
1747     SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text);
1748     tc->text_sel_start = tc->text_sel_end = where;
1749     sp_text_context_update_cursor(tc);
1750     sp_text_context_update_text_selection(tc);
1753 void
1754 sp_text_context_place_cursor_at (SPTextContext *tc, SPObject *text, Geom::Point const p)
1756     SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text);
1757     sp_text_context_place_cursor (tc, text, sp_te_get_position_by_coords(tc->text, p));
1760 Inkscape::Text::Layout::iterator *sp_text_context_get_cursor_position(SPTextContext *tc, SPObject *text)
1762     if (text != tc->text)
1763         return NULL;
1764     return &(tc->text_sel_end);
1768 /*
1769   Local Variables:
1770   mode:c++
1771   c-file-style:"stroustrup"
1772   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1773   indent-tabs-mode:nil
1774   fill-column:99
1775   End:
1776 */
1777 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :