Code

- Move snap delay mechanism to the event context (used to be in SPCanvas)
[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                         sp_event_context_snap_window_open(event_context);
356                     }
357                     ret = TRUE;
358                 }
359             }
360             break;
361         case GDK_2BUTTON_PRESS:
362             if (event->button.button == 1 && tc->text) {
363                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
364                 if (layout) {
365                     if (!layout->isStartOfWord(tc->text_sel_start))
366                         tc->text_sel_start.prevStartOfWord();
367                     if (!layout->isEndOfWord(tc->text_sel_end))
368                         tc->text_sel_end.nextEndOfWord();
369                     sp_text_context_update_cursor(tc);
370                     sp_text_context_update_text_selection(tc);
371                     tc->dragging = 2;
372                     sp_event_context_snap_window_open(event_context);
373                     ret = TRUE;
374                 }
375             }
376             break;
377         case GDK_3BUTTON_PRESS:
378             if (event->button.button == 1 && tc->text) {
379                 tc->text_sel_start.thisStartOfLine();
380                 tc->text_sel_end.thisEndOfLine();
381                 sp_text_context_update_cursor(tc);
382                 sp_text_context_update_text_selection(tc);
383                 tc->dragging = 3;
384                 sp_event_context_snap_window_open(event_context);
385                 ret = TRUE;
386             }
387             break;
388         case GDK_BUTTON_RELEASE:
389             if (event->button.button == 1 && tc->dragging && !event_context->space_panning) {
390                 tc->dragging = 0;
391                 sp_event_context_snap_window_closed(event_context);
392                 ret = TRUE;
393             }
394             break;
395         case GDK_MOTION_NOTIFY:
396             if (event->motion.state & GDK_BUTTON1_MASK && tc->dragging && !event_context->space_panning) {
397                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
398                 if (!layout) break;
399                 // find out click point in document coordinates
400                 Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
401                 // set the cursor closest to that point
402                 Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(tc->text, p);
403                 if (tc->dragging == 2) {
404                     // double-click dragging: go by word
405                     if (new_end < tc->text_sel_start) {
406                         if (!layout->isStartOfWord(new_end))
407                             new_end.prevStartOfWord();
408                     } else
409                         if (!layout->isEndOfWord(new_end))
410                             new_end.nextEndOfWord();
411                 } else if (tc->dragging == 3) {
412                     // triple-click dragging: go by line
413                     if (new_end < tc->text_sel_start)
414                         new_end.thisStartOfLine();
415                     else
416                         new_end.thisEndOfLine();
417                 }
418                 // update display
419                 if (tc->text_sel_end != new_end) {
420                     tc->text_sel_end = new_end;
421                     sp_text_context_update_cursor(tc);
422                     sp_text_context_update_text_selection(tc);
423                 }
424                 gobble_motion_events(GDK_BUTTON1_MASK);
425                 ret = TRUE;
426                 break;
427             }
428             // find out item under mouse, disregarding groups
429             item_ungrouped = desktop->item_at_point(Geom::Point(event->button.x, event->button.y), TRUE);
430             if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
431                 sp_canvas_item_show(tc->indicator);
432                 Geom::OptRect ibbox = sp_item_bbox_desktop(item_ungrouped);
433                 if (ibbox) {
434                     SP_CTRLRECT(tc->indicator)->setRectangle(*ibbox);
435                 }
437                 event_context->cursor_shape = cursor_text_insert_xpm;
438                 event_context->hot_x = 7;
439                 event_context->hot_y = 10;
440                 sp_event_context_update_cursor(event_context);
441                 sp_text_context_update_text_selection(tc);
443                 if (SP_IS_TEXT (item_ungrouped)) {
444                     desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
445                 } else {
446                     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."));
447                 }
449                 tc->over_text = true;
451                 ret = TRUE;
452             }
453             break;
454         default:
455             break;
456     }
458     if (!ret) {
459         if (((SPEventContextClass *) parent_class)->item_handler)
460             ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
461     }
463     return ret;
466 static void
467 sp_text_context_setup_text(SPTextContext *tc)
469     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
471     /* Create <text> */
472     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_EVENT_CONTEXT_DESKTOP(ec)->doc());
473     Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
474     rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
476     /* Set style */
477     sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "/tools/text", true);
479     sp_repr_set_svg_double(rtext, "x", tc->pdoc[Geom::X]);
480     sp_repr_set_svg_double(rtext, "y", tc->pdoc[Geom::Y]);
482     /* Create <tspan> */
483     Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
484     rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
485     rtext->addChild(rtspan, NULL);
486     Inkscape::GC::release(rtspan);
488     /* Create TEXT */
489     Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
490     rtspan->addChild(rstring, NULL);
491     Inkscape::GC::release(rstring);
492     SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
493     /* fixme: Is selection::changed really immediate? */
494     /* yes, it's immediate .. why does it matter? */
495     sp_desktop_selection(ec->desktop)->set(text_item);
496     Inkscape::GC::release(rtext);
497     text_item->transform = sp_item_i2doc_affine(SP_ITEM(ec->desktop->currentLayer())).inverse();
499     text_item->updateRepr();
500     sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
501                      _("Create text"));
504 /**
505  * Insert the character indicated by tc.uni to replace the current selection,
506  * and reset tc.uni/tc.unipos to empty string.
507  *
508  * \pre tc.uni/tc.unipos non-empty.
509  */
510 static void
511 insert_uni_char(SPTextContext *const tc)
513     g_return_if_fail(tc->unipos
514                      && tc->unipos < sizeof(tc->uni)
515                      && tc->uni[tc->unipos] == '\0');
516     unsigned int uv;
517     sscanf(tc->uni, "%x", &uv);
518     tc->unipos = 0;
519     tc->uni[tc->unipos] = '\0';
521     if ( !g_unichar_isprint(static_cast<gunichar>(uv))
522          && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
523         // This may be due to bad input, so it goes to statusbar.
524         tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
525                                            _("Non-printable character"));
526     } else {
527         if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
528             sp_text_context_setup_text(tc);
529             tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
530         }
532         gchar u[10];
533         guint const len = g_unichar_to_utf8(uv, u);
534         u[len] = '\0';
536         tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
537         sp_text_context_update_cursor(tc);
538         sp_text_context_update_text_selection(tc);
539         sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM,
540                          _("Insert Unicode character"));
541     }
544 static void
545 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
547     unsigned int uv;
548     sscanf(hex, "%x", &uv);
549     if (!g_unichar_isprint((gunichar) uv)) {
550         uv = 0xfffd;
551     }
552     guint const len = g_unichar_to_utf8(uv, utf8);
553     utf8[len] = '\0';
556 static void
557 show_curr_uni_char(SPTextContext *const tc)
559     g_return_if_fail(tc->unipos < sizeof(tc->uni)
560                      && tc->uni[tc->unipos] == '\0');
561     if (tc->unipos) {
562         char utf8[10];
563         hex_to_printable_utf8_buf(tc->uni, utf8);
565         /* Status bar messages are in pango markup, so we need xml escaping. */
566         if (utf8[1] == '\0') {
567             switch(utf8[0]) {
568                 case '<': strcpy(utf8, "&lt;"); break;
569                 case '>': strcpy(utf8, "&gt;"); break;
570                 case '&': strcpy(utf8, "&amp;"); break;
571                 default: break;
572             }
573         }
574         tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
575                                           _("Unicode (<b>Enter</b> to finish): %s: %s"), tc->uni, utf8);
576     } else {
577         tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
578     }
581 static gint
582 sp_text_context_root_handler(SPEventContext *const event_context, GdkEvent *const event)
584     SPTextContext *const tc = SP_TEXT_CONTEXT(event_context);
586     SPDesktop *desktop = event_context->desktop;
588     sp_canvas_item_hide(tc->indicator);
590     sp_text_context_validate_cursor_iterators(tc);
592     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
593     event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
595     switch (event->type) {
596         case GDK_BUTTON_PRESS:
597             if (event->button.button == 1 && !event_context->space_panning) {
599                 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
600                     return TRUE;
601                 }
603                 // save drag origin
604                 event_context->xp = (gint) event->button.x;
605                 event_context->yp = (gint) event->button.y;
606                 event_context->within_tolerance = true;
608                 Geom::Point const button_pt(event->button.x, event->button.y);
609                 tc->p0 = desktop->w2d(button_pt);
610                 Inkscape::Rubberband::get(desktop)->start(desktop, tc->p0);
611                 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
612                                     GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
613                                     NULL, event->button.time);
614                 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
615                 tc->creating = 1;
617                 /* Processed */
618                 return TRUE;
619             }
620             break;
621         case GDK_MOTION_NOTIFY:
622             if (tc->over_text) {
623                 tc->over_text = 0;
624                 // update cursor and statusbar: we are not over a text object now
625                 event_context->cursor_shape = cursor_text_xpm;
626                 event_context->hot_x = 7;
627                 event_context->hot_y = 7;
628                 sp_event_context_update_cursor(event_context);
629                 desktop->event_context->defaultMessageContext()->clear();
630             }
632             if (tc->creating && event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
633                 if ( event_context->within_tolerance
634                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
635                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
636                     break; // do not drag if we're within tolerance from origin
637                 }
638                 // Once the user has moved farther than tolerance from the original location
639                 // (indicating they intend to draw, not click), then always process the
640                 // motion notify coordinates as given (no snapping back to origin)
641                 event_context->within_tolerance = false;
643                 Geom::Point const motion_pt(event->motion.x, event->motion.y);
644                 Geom::Point const p = desktop->w2d(motion_pt);
646                 Inkscape::Rubberband::get(desktop)->move(p);
647                 gobble_motion_events(GDK_BUTTON1_MASK);
649                 // status text
650                 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::X]), desktop->namedview->getDefaultMetric());
651                 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::Y]), desktop->namedview->getDefaultMetric());
652                 event_context->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Flowed text frame</b>: %s &#215; %s"), xs->str, ys->str);
653                 g_string_free(xs, FALSE);
654                 g_string_free(ys, FALSE);
656             }
657             break;
658         case GDK_BUTTON_RELEASE:
659             if (event->button.button == 1 && !event_context->space_panning) {
661                 if (tc->grabbed) {
662                     sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
663                     tc->grabbed = NULL;
664                 }
666                 Inkscape::Rubberband::get(desktop)->stop();
668                 if (tc->creating && event_context->within_tolerance) {
669                     /* Button 1, set X & Y & new item */
670                     sp_desktop_selection(desktop)->clear();
671                     Geom::Point dtp = desktop->w2d(Geom::Point(event->button.x, event->button.y));
672                     tc->pdoc = desktop->dt2doc(dtp);
674                     tc->show = TRUE;
675                     tc->phase = 1;
676                     tc->nascent_object = 1; // new object was just created
678                     /* Cursor */
679                     sp_canvas_item_show(tc->cursor);
680                     // Cursor height is defined by the new text object's font size; it needs to be set
681                     // articifically here, for the text object does not exist yet:
682                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
683                     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), dtp, dtp + Geom::Point(0, cursor_height));
684                     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
686                     event_context->within_tolerance = false;
687                 } else if (tc->creating) {
688                     Geom::Point const button_pt(event->button.x, event->button.y);
689                     Geom::Point p1 = desktop->w2d(button_pt);
690                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
691                     if (fabs(p1[Geom::Y] - tc->p0[Geom::Y]) > cursor_height) {
692                         // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
693                         SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
694                         /* Set style */
695                         sp_desktop_apply_style_tool(desktop, SP_OBJECT_REPR(ft), "/tools/text", true);
696                         sp_desktop_selection(desktop)->set(ft);
697                         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
698                         sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
699                                          _("Create flowed text"));
700                     } else {
701                         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
702                     }
703                 }
704                 tc->creating = false;
705                 return TRUE;
706             }
707             break;
708         case GDK_KEY_PRESS: {
709             guint const group0_keyval = get_group0_keyval(&event->key);
711             if (group0_keyval == GDK_KP_Add ||
712                 group0_keyval == GDK_KP_Subtract) {
713                 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
714                     break; // otherwise pass on keypad +/- so they can zoom
715             }
717             if ((tc->text) || (tc->nascent_object)) {
718                 // there is an active text object in this context, or a new object was just created
720                 if (tc->unimode || !tc->imc
721                     || (MOD__CTRL && MOD__SHIFT)    // input methods tend to steal this for unimode,
722                                                     // but we have our own so make sure they don't swallow it
723                     || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
724                     //IM did not consume the key, or we're in unimode
726                         if (!MOD__CTRL_ONLY && tc->unimode) {
727                             /* TODO: ISO 14755 (section 3 Definitions) says that we should also
728                                accept the first 6 characters of alphabets other than the latin
729                                alphabet "if the Latin alphabet is not used".  The below is also
730                                reasonable (viz. hope that the user's keyboard includes latin
731                                characters and force latin interpretation -- just as we do for our
732                                keyboard shortcuts), but differs from the ISO 14755
733                                recommendation. */
734                             switch (group0_keyval) {
735                                 case GDK_space:
736                                 case GDK_KP_Space: {
737                                     if (tc->unipos) {
738                                         insert_uni_char(tc);
739                                     }
740                                     /* Stay in unimode. */
741                                     show_curr_uni_char(tc);
742                                     return TRUE;
743                                 }
745                                 case GDK_BackSpace: {
746                                     g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
747                                     if (tc->unipos) {
748                                         tc->uni[--tc->unipos] = '\0';
749                                     }
750                                     show_curr_uni_char(tc);
751                                     return TRUE;
752                                 }
754                                 case GDK_Return:
755                                 case GDK_KP_Enter: {
756                                     if (tc->unipos) {
757                                         insert_uni_char(tc);
758                                     }
759                                     /* Exit unimode. */
760                                     tc->unimode = false;
761                                     event_context->defaultMessageContext()->clear();
762                                     return TRUE;
763                                 }
765                                 case GDK_Escape: {
766                                     // Cancel unimode.
767                                     tc->unimode = false;
768                                     gtk_im_context_reset(tc->imc);
769                                     event_context->defaultMessageContext()->clear();
770                                     return TRUE;
771                                 }
773                                 case GDK_Shift_L:
774                                 case GDK_Shift_R:
775                                     break;
777                                 default: {
778                                     if (g_ascii_isxdigit(group0_keyval)) {
779                                         g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
780                                         tc->uni[tc->unipos++] = group0_keyval;
781                                         tc->uni[tc->unipos] = '\0';
782                                         if (tc->unipos == 8) {
783                                             /* This behaviour is partly to allow us to continue to
784                                                use a fixed-length buffer for tc->uni.  Reason for
785                                                choosing the number 8 is that it's the length of
786                                                ``canonical form'' mentioned in the ISO 14755 spec.
787                                                An advantage over choosing 6 is that it allows using
788                                                backspace for typos & misremembering when entering a
789                                                6-digit number. */
790                                             insert_uni_char(tc);
791                                         }
792                                         show_curr_uni_char(tc);
793                                         return TRUE;
794                                     } else {
795                                         /* The intent is to ignore but consume characters that could be
796                                            typos for hex digits.  Gtk seems to ignore & consume all
797                                            non-hex-digits, and we do similar here.  Though note that some
798                                            shortcuts (like keypad +/- for zoom) get processed before
799                                            reaching this code. */
800                                         return TRUE;
801                                     }
802                                 }
803                             }
804                         }
806                         Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
807                         Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
808                         bool cursor_moved = false;
809                         int screenlines = 1;
810                         if (tc->text) {
811                             double spacing = sp_te_get_average_linespacing(tc->text);
812                             Geom::Rect const d = desktop->get_display_area();
813                             screenlines = (int) floor(fabs(d.min()[Geom::Y] - d.max()[Geom::Y])/spacing) - 1;
814                             if (screenlines <= 0)
815                                 screenlines = 1;
816                         }
818                         /* Neither unimode nor IM consumed key; process text tool shortcuts */
819                         switch (group0_keyval) {
820                             case GDK_x:
821                             case GDK_X:
822                                 if (MOD__ALT_ONLY) {
823                                     desktop->setToolboxFocusTo ("altx-text");
824                                     return TRUE;
825                                 }
826                                 break;
827                             case GDK_space:
828                                 if (MOD__CTRL_ONLY) {
829                                     /* No-break space */
830                                     if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
831                                         sp_text_context_setup_text(tc);
832                                         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
833                                     }
834                                     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
835                                     sp_text_context_update_cursor(tc);
836                                     sp_text_context_update_text_selection(tc);
837                                     desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
838                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
839                                                      _("Insert no-break space"));
840                                     return TRUE;
841                                 }
842                                 break;
843                             case GDK_U:
844                             case GDK_u:
845                                 if (MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT)) {
846                                     if (tc->unimode) {
847                                         tc->unimode = false;
848                                         event_context->defaultMessageContext()->clear();
849                                     } else {
850                                         tc->unimode = true;
851                                         tc->unipos = 0;
852                                         event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
853                                     }
854                                     if (tc->imc) {
855                                         gtk_im_context_reset(tc->imc);
856                                     }
857                                     return TRUE;
858                                 }
859                                 break;
860                             case GDK_B:
861                             case GDK_b:
862                                 if (MOD__CTRL_ONLY && tc->text) {
863                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
864                                     SPCSSAttr *css = sp_repr_css_attr_new();
865                                     if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
866                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
867                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
868                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
869                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
870                                         sp_repr_css_set_property(css, "font-weight", "bold");
871                                     else
872                                         sp_repr_css_set_property(css, "font-weight", "normal");
873                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
874                                     sp_repr_css_attr_unref(css);
875                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
876                                                      _("Make bold"));
877                                     sp_text_context_update_cursor(tc);
878                                     sp_text_context_update_text_selection(tc);
879                                     return TRUE;
880                                 }
881                                 break;
882                             case GDK_I:
883                             case GDK_i:
884                                 if (MOD__CTRL_ONLY && tc->text) {
885                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
886                                     SPCSSAttr *css = sp_repr_css_attr_new();
887                                     if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL)
888                                         sp_repr_css_set_property(css, "font-style", "italic");
889                                     else
890                                         sp_repr_css_set_property(css, "font-style", "normal");
891                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
892                                     sp_repr_css_attr_unref(css);
893                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
894                                                      _("Make italic"));
895                                     sp_text_context_update_cursor(tc);
896                                     sp_text_context_update_text_selection(tc);
897                                     return TRUE;
898                                 }
899                                 break;
901                             case GDK_A:
902                             case GDK_a:
903                                 if (MOD__CTRL_ONLY && tc->text) {
904                                     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
905                                     if (layout) {
906                                         tc->text_sel_start = layout->begin();
907                                         tc->text_sel_end = layout->end();
908                                         sp_text_context_update_cursor(tc);
909                                         sp_text_context_update_text_selection(tc);
910                                         return TRUE;
911                                     }
912                                 }
913                                 break;
915                             case GDK_Return:
916                             case GDK_KP_Enter:
917                             {
918                                 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
919                                     sp_text_context_setup_text(tc);
920                                     tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
921                                 }
923                                 iterator_pair enter_pair;
924                                 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, enter_pair);
925                                 (void)success; // TODO cleanup
926                                 tc->text_sel_start = tc->text_sel_end = enter_pair.first;
928                                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
930                                 sp_text_context_update_cursor(tc);
931                                 sp_text_context_update_text_selection(tc);
932                                 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
933                                                  _("New line"));
934                                 return TRUE;
935                             }
936                             case GDK_BackSpace:
937                                 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
939                                     bool noSelection = false;
941                                         if (tc->text_sel_start == tc->text_sel_end) {
942                                         tc->text_sel_start.prevCursorPosition();
943                                         noSelection = true;
944                                     }
946                                         iterator_pair bspace_pair;
947                                         bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, bspace_pair);
949                                     if (noSelection) {
950                                         if (success) {
951                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
952                                         } else { // nothing deleted
953                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.second;
954                                         }
955                                     } else {
956                                         if (success) {
957                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
958                                         } else { // nothing deleted
959                                             tc->text_sel_start = bspace_pair.first;
960                                             tc->text_sel_end = bspace_pair.second;
961                                         }
962                                     }
964                                     sp_text_context_update_cursor(tc);
965                                     sp_text_context_update_text_selection(tc);
966                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
967                                                      _("Backspace"));
968                                 }
969                                 return TRUE;
970                             case GDK_Delete:
971                             case GDK_KP_Delete:
972                                 if (tc->text) {
973                                     bool noSelection = false;
975                                     if (tc->text_sel_start == tc->text_sel_end) {
976                                         tc->text_sel_end.nextCursorPosition();
977                                         noSelection = true;
978                                     }
980                                     iterator_pair del_pair;
981                                     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, del_pair);
983                                     if (noSelection) {
984                                         tc->text_sel_start = tc->text_sel_end = del_pair.first;
985                                     } else {
986                                         if (success) {
987                                             tc->text_sel_start = tc->text_sel_end = del_pair.first;
988                                         } else { // nothing deleted
989                                             tc->text_sel_start = del_pair.first;
990                                             tc->text_sel_end = del_pair.second;
991                                         }
992                                     }
995                                     sp_text_context_update_cursor(tc);
996                                     sp_text_context_update_text_selection(tc);
997                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
998                                                      _("Delete"));
999                                 }
1000                                 return TRUE;
1001                             case GDK_Left:
1002                             case GDK_KP_Left:
1003                             case GDK_KP_4:
1004                                 if (tc->text) {
1005                                     if (MOD__ALT) {
1006                                         gint mul = 1 + gobble_key_events(
1007                                             get_group0_keyval(&event->key), 0); // with any mask
1008                                         if (MOD__SHIFT)
1009                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-10, 0));
1010                                         else
1011                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-1, 0));
1012                                         sp_text_context_update_cursor(tc);
1013                                         sp_text_context_update_text_selection(tc);
1014                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT,
1015                                                                _("Kern to the left"));
1016                                     } else {
1017                                         if (MOD__CTRL)
1018                                             tc->text_sel_end.cursorLeftWithControl();
1019                                         else
1020                                             tc->text_sel_end.cursorLeft();
1021                                         cursor_moved = true;
1022                                         break;
1023                                     }
1024                                 }
1025                                 return TRUE;
1026                             case GDK_Right:
1027                             case GDK_KP_Right:
1028                             case GDK_KP_6:
1029                                 if (tc->text) {
1030                                     if (MOD__ALT) {
1031                                         gint mul = 1 + gobble_key_events(
1032                                             get_group0_keyval(&event->key), 0); // with any mask
1033                                         if (MOD__SHIFT)
1034                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*10, 0));
1035                                         else
1036                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*1, 0));
1037                                         sp_text_context_update_cursor(tc);
1038                                         sp_text_context_update_text_selection(tc);
1039                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT,
1040                                                                _("Kern to the right"));
1041                                     } else {
1042                                         if (MOD__CTRL)
1043                                             tc->text_sel_end.cursorRightWithControl();
1044                                         else
1045                                             tc->text_sel_end.cursorRight();
1046                                         cursor_moved = true;
1047                                         break;
1048                                     }
1049                                 }
1050                                 return TRUE;
1051                             case GDK_Up:
1052                             case GDK_KP_Up:
1053                             case GDK_KP_8:
1054                                 if (tc->text) {
1055                                     if (MOD__ALT) {
1056                                         gint mul = 1 + gobble_key_events(
1057                                             get_group0_keyval(&event->key), 0); // with any mask
1058                                         if (MOD__SHIFT)
1059                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-10));
1060                                         else
1061                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-1));
1062                                         sp_text_context_update_cursor(tc);
1063                                         sp_text_context_update_text_selection(tc);
1064                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT,
1065                                                                _("Kern up"));
1067                                     } else {
1068                                         if (MOD__CTRL)
1069                                             tc->text_sel_end.cursorUpWithControl();
1070                                         else
1071                                             tc->text_sel_end.cursorUp();
1072                                         cursor_moved = true;
1073                                         break;
1074                                     }
1075                                 }
1076                                 return TRUE;
1077                             case GDK_Down:
1078                             case GDK_KP_Down:
1079                             case GDK_KP_2:
1080                                 if (tc->text) {
1081                                     if (MOD__ALT) {
1082                                         gint mul = 1 + gobble_key_events(
1083                                             get_group0_keyval(&event->key), 0); // with any mask
1084                                         if (MOD__SHIFT)
1085                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*10));
1086                                         else
1087                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*1));
1088                                         sp_text_context_update_cursor(tc);
1089                                         sp_text_context_update_text_selection(tc);
1090                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT,
1091                                                                _("Kern down"));
1093                                     } else {
1094                                         if (MOD__CTRL)
1095                                             tc->text_sel_end.cursorDownWithControl();
1096                                         else
1097                                             tc->text_sel_end.cursorDown();
1098                                         cursor_moved = true;
1099                                         break;
1100                                     }
1101                                 }
1102                                 return TRUE;
1103                             case GDK_Home:
1104                             case GDK_KP_Home:
1105                                 if (tc->text) {
1106                                     if (MOD__CTRL)
1107                                         tc->text_sel_end.thisStartOfShape();
1108                                     else
1109                                         tc->text_sel_end.thisStartOfLine();
1110                                     cursor_moved = true;
1111                                     break;
1112                                 }
1113                                 return TRUE;
1114                             case GDK_End:
1115                             case GDK_KP_End:
1116                                 if (tc->text) {
1117                                     if (MOD__CTRL)
1118                                         tc->text_sel_end.nextStartOfShape();
1119                                     else
1120                                         tc->text_sel_end.thisEndOfLine();
1121                                     cursor_moved = true;
1122                                     break;
1123                                 }
1124                                 return TRUE;
1125                             case GDK_Page_Down:
1126                             case GDK_KP_Page_Down:
1127                                 if (tc->text) {
1128                                     tc->text_sel_end.cursorDown(screenlines);
1129                                     cursor_moved = true;
1130                                     break;
1131                                 }
1132                                 return TRUE;
1133                             case GDK_Page_Up:
1134                             case GDK_KP_Page_Up:
1135                                 if (tc->text) {
1136                                     tc->text_sel_end.cursorUp(screenlines);
1137                                     cursor_moved = true;
1138                                     break;
1139                                 }
1140                                 return TRUE;
1141                             case GDK_Escape:
1142                                 if (tc->creating) {
1143                                     tc->creating = 0;
1144                                     if (tc->grabbed) {
1145                                         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1146                                         tc->grabbed = NULL;
1147                                     }
1148                                     Inkscape::Rubberband::get(desktop)->stop();
1149                                 } else {
1150                                     sp_desktop_selection(desktop)->clear();
1151                                 }
1152                                 tc->nascent_object = FALSE;
1153                                 return TRUE;
1154                             case GDK_bracketleft:
1155                                 if (tc->text) {
1156                                     if (MOD__ALT || MOD__CTRL) {
1157                                         if (MOD__ALT) {
1158                                             if (MOD__SHIFT) {
1159                                                 // FIXME: alt+shift+[] does not work, don't know why
1160                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1161                                             } else {
1162                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1163                                             }
1164                                         } else {
1165                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -90);
1166                                         }
1167                                         sp_document_maybe_done(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT,
1168                                                                _("Rotate counterclockwise"));
1169                                         sp_text_context_update_cursor(tc);
1170                                         sp_text_context_update_text_selection(tc);
1171                                         return TRUE;
1172                                     }
1173                                 }
1174                                 break;
1175                             case GDK_bracketright:
1176                                 if (tc->text) {
1177                                     if (MOD__ALT || MOD__CTRL) {
1178                                         if (MOD__ALT) {
1179                                             if (MOD__SHIFT) {
1180                                                 // FIXME: alt+shift+[] does not work, don't know why
1181                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1182                                             } else {
1183                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1184                                             }
1185                                         } else {
1186                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 90);
1187                                         }
1188                                         sp_document_maybe_done(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT,
1189                                                                 _("Rotate clockwise"));
1190                                         sp_text_context_update_cursor(tc);
1191                                         sp_text_context_update_text_selection(tc);
1192                                         return TRUE;
1193                                     }
1194                                 }
1195                                 break;
1196                             case GDK_less:
1197                             case GDK_comma:
1198                                 if (tc->text) {
1199                                     if (MOD__ALT) {
1200                                         if (MOD__CTRL) {
1201                                             if (MOD__SHIFT)
1202                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1203                                             else
1204                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1205                                             sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT,
1206                                                                     _("Contract line spacing"));
1208                                         } else {
1209                                             if (MOD__SHIFT)
1210                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1211                                             else
1212                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1213                                             sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT,
1214                                                                     _("Contract letter spacing"));
1216                                         }
1217                                         sp_text_context_update_cursor(tc);
1218                                         sp_text_context_update_text_selection(tc);
1219                                         return TRUE;
1220                                     }
1221                                 }
1222                                 break;
1223                             case GDK_greater:
1224                             case GDK_period:
1225                                 if (tc->text) {
1226                                     if (MOD__ALT) {
1227                                         if (MOD__CTRL) {
1228                                             if (MOD__SHIFT)
1229                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1230                                             else
1231                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1232                                             sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT,
1233                                                                     _("Expand line spacing"));
1235                                         } else {
1236                                             if (MOD__SHIFT)
1237                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1238                                             else
1239                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1240                                             sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT,
1241                                                                     _("Expand letter spacing"));
1243                                         }
1244                                         sp_text_context_update_cursor(tc);
1245                                         sp_text_context_update_text_selection(tc);
1246                                         return TRUE;
1247                                     }
1248                                 }
1249                                 break;
1250                             default:
1251                                 break;
1252                         }
1254                         if (cursor_moved) {
1255                             if (!MOD__SHIFT)
1256                                 tc->text_sel_start = tc->text_sel_end;
1257                             if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1258                                 sp_text_context_update_cursor(tc);
1259                                 sp_text_context_update_text_selection(tc);
1260                             }
1261                             return TRUE;
1262                         }
1264                 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1265             } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1266                 // except up/down that are swallowed to prevent the zoom field from activation
1267                 if ((group0_keyval == GDK_Up    ||
1268                      group0_keyval == GDK_Down  ||
1269                      group0_keyval == GDK_KP_Up ||
1270                      group0_keyval == GDK_KP_Down )
1271                     && !MOD__CTRL_ONLY) {
1272                     return TRUE;
1273                 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1274                     if (tc->creating) {
1275                         tc->creating = 0;
1276                         if (tc->grabbed) {
1277                             sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1278                             tc->grabbed = NULL;
1279                         }
1280                         Inkscape::Rubberband::get(desktop)->stop();
1281                     }
1282                 }
1283             }
1284             break;
1285         }
1287         case GDK_KEY_RELEASE:
1288             if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1289                 return TRUE;
1290             }
1291             break;
1292         default:
1293             break;
1294     }
1296     // if nobody consumed it so far
1297     if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1298         return ((SPEventContextClass *) parent_class)->root_handler(event_context, event); // send event to parent
1299     } else {
1300         return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1301     }
1304 /**
1305  Attempts to paste system clipboard into the currently edited text, returns true on success
1306  */
1307 bool
1308 sp_text_paste_inline(SPEventContext *ec)
1310     if (!SP_IS_TEXT_CONTEXT(ec))
1311         return false;
1313     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1315     if ((tc->text) || (tc->nascent_object)) {
1316         // there is an active text object in this context, or a new object was just created
1318         Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1319         Glib::ustring const clip_text = refClipboard->wait_for_text();
1321         if (!clip_text.empty()) {
1322                 // Fix for 244940
1323                 // The XML standard defines the following as valid characters
1324                 // (Extensible Markup Language (XML) 1.0 (Fourth Edition) paragraph 2.2)
1325                 // char ::=     #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
1326                 // Since what comes in off the paste buffer will go right into XML, clean
1327                 // the text here.
1328                 Glib::ustring text(clip_text);
1329                 Glib::ustring::iterator itr = text.begin();
1330                 gunichar paste_string_uchar;
1332                 while(itr != text.end())
1333                 {
1334                     paste_string_uchar = *itr;
1336                     // Make sure we don't have a control character. We should really check
1337                     // for the whole range above... Add the rest of the invalid cases from
1338                     // above if we find additional issues
1339                     if(paste_string_uchar >= 0x00000020 ||
1340                        paste_string_uchar == 0x00000009 ||
1341                        paste_string_uchar == 0x0000000A ||
1342                        paste_string_uchar == 0x0000000D) {
1343                         itr++;
1344                     } else {
1345                         itr = text.erase(itr);
1346                     }
1347                 }
1349             if (!tc->text) { // create text if none (i.e. if nascent_object)
1350                 sp_text_context_setup_text(tc);
1351                 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1352             }
1354             // using indices is slow in ustrings. Whatever.
1355             Glib::ustring::size_type begin = 0;
1356             for ( ; ; ) {
1357                 Glib::ustring::size_type end = text.find('\n', begin);
1358                 if (end == Glib::ustring::npos) {
1359                     if (begin != text.length())
1360                         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());
1361                     break;
1362                 }
1363                 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());
1364                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1365                 begin = end + 1;
1366             }
1367             sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
1368                              _("Paste text"));
1370             return true;
1371         }
1372     } // FIXME: else create and select a new object under cursor!
1374     return false;
1377 /**
1378  Gets the raw characters that comprise the currently selected text, converting line
1379  breaks into lf characters.
1380 */
1381 Glib::ustring
1382 sp_text_get_selected_text(SPEventContext const *ec)
1384     if (!SP_IS_TEXT_CONTEXT(ec))
1385         return "";
1386     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1387     if (tc->text == NULL)
1388         return "";
1390     return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1393 SPCSSAttr *
1394 sp_text_get_style_at_cursor(SPEventContext const *ec)
1396     if (!SP_IS_TEXT_CONTEXT(ec))
1397         return NULL;
1398     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1399     if (tc->text == NULL)
1400         return NULL;
1402     SPObject const *obj = sp_te_object_at_position(tc->text, tc->text_sel_end);
1403     if (obj)
1404         return take_style_from_item((SPItem *) obj);
1405     return NULL;
1408 /**
1409  Deletes the currently selected characters. Returns false if there is no
1410  text selection currently.
1411 */
1412 bool sp_text_delete_selection(SPEventContext *ec)
1414     if (!SP_IS_TEXT_CONTEXT(ec))
1415         return false;
1416     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1417     if (tc->text == NULL)
1418         return false;
1420     if (tc->text_sel_start == tc->text_sel_end)
1421         return false;
1423     iterator_pair pair;
1424     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair);
1427     if (success) {
1428         tc->text_sel_start = tc->text_sel_end = pair.first;
1429     } else { // nothing deleted
1430         tc->text_sel_start = pair.first;
1431         tc->text_sel_end = pair.second;
1432     }
1434     sp_text_context_update_cursor(tc);
1435     sp_text_context_update_text_selection(tc);
1437     return true;
1440 /**
1441  * \param selection Should not be NULL.
1442  */
1443 static void
1444 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1446     g_assert(selection != NULL);
1448     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1450     ec->shape_editor->unset_item(SH_KNOTHOLDER);
1451     SPItem *item = selection->singleItem();
1452     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1453         ec->shape_editor->set_item(item, SH_KNOTHOLDER);
1454     }
1456     if (tc->text && (item != tc->text)) {
1457         sp_text_context_forget_text(tc);
1458     }
1459     tc->text = NULL;
1461     if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1462         tc->text = item;
1463         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1464         if (layout)
1465             tc->text_sel_start = tc->text_sel_end = layout->end();
1466     } else {
1467         tc->text = NULL;
1468     }
1470     // we update cursor without scrolling, because this position may not be final;
1471     // item_handler moves cusros to the point of click immediately
1472     sp_text_context_update_cursor(tc, false);
1473     sp_text_context_update_text_selection(tc);
1476 static void
1477 sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, SPTextContext *tc)
1479     sp_text_context_update_cursor(tc);
1480     sp_text_context_update_text_selection(tc);
1483 static bool
1484 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1486     if (tc->text == NULL)
1487         return false;
1488     if (tc->text_sel_start == tc->text_sel_end)
1489         return false;    // will get picked up by the parent and applied to the whole text object
1491     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1492     sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1493                      _("Set text style"));
1494     sp_text_context_update_cursor(tc);
1495     sp_text_context_update_text_selection(tc);
1497     return true;
1500 static int
1501 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1503     if (tc->text == NULL)
1504         return QUERY_STYLE_NOTHING;
1505     const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1506     if (layout == NULL)
1507         return QUERY_STYLE_NOTHING;
1508     sp_text_context_validate_cursor_iterators(tc);
1510     GSList *styles_list = NULL;
1512     Inkscape::Text::Layout::iterator begin_it, end_it;
1513     if (tc->text_sel_start < tc->text_sel_end) {
1514         begin_it = tc->text_sel_start;
1515         end_it = tc->text_sel_end;
1516     } else {
1517         begin_it = tc->text_sel_end;
1518         end_it = tc->text_sel_start;
1519     }
1520     if (begin_it == end_it)
1521         if (!begin_it.prevCharacter())
1522             end_it.nextCharacter();
1523     for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1524         SPObject const *pos_obj = 0;
1525         void *rawptr = 0;
1526         layout->getSourceOfCharacter(it, &rawptr);
1527         if (!rawptr || !SP_IS_OBJECT(rawptr))
1528             continue;
1529         pos_obj = SP_OBJECT(rawptr);
1530         while (SP_IS_STRING(pos_obj) && SP_OBJECT_PARENT(pos_obj)) {
1531            pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
1532         }
1533         styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1534     }
1536     int result = sp_desktop_query_style_from_list (styles_list, style, property);
1538     g_slist_free(styles_list);
1539     return result;
1542 static void
1543 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1545     if (tc->text == NULL)
1546         return;
1547     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1548     if (layout) {     // undo can change the text length without us knowing it
1549         layout->validateIterator(&tc->text_sel_start);
1550         layout->validateIterator(&tc->text_sel_end);
1551     }
1554 static void
1555 sp_text_context_update_cursor(SPTextContext *tc,  bool scroll_to_see)
1557     GdkRectangle im_cursor = { 0, 0, 1, 1 };
1559     // due to interruptible display, tc may already be destroyed during a display update before
1560     // the cursor update (can't do both atomically, alas)
1561     if (!tc->desktop) return;
1563     if (tc->text) {
1564         Geom::Point p0, p1;
1565         sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1566         Geom::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1567         Geom::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1569         // scroll to show cursor
1570         if (scroll_to_see) {
1571             Geom::Point const center = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().midpoint();
1572             if (Geom::L2(d0 - center) > Geom::L2(d1 - center))
1573                 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1574                 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d0, 1.0);
1575             else
1576                 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d1, 1.0);
1577         }
1579         sp_canvas_item_show(tc->cursor);
1580         sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1582         /* fixme: ... need another transformation to get canvas widget coordinate space? */
1583         im_cursor.x = (int) floor(d0[Geom::X]);
1584         im_cursor.y = (int) floor(d0[Geom::Y]);
1585         im_cursor.width = (int) floor(d1[Geom::X]) - im_cursor.x;
1586         im_cursor.height = (int) floor(d1[Geom::Y]) - im_cursor.y;
1588         tc->show = TRUE;
1589         tc->phase = 1;
1591         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1592         int const nChars = layout->iteratorToCharIndex(layout->end());
1593         if (SP_IS_FLOWTEXT(tc->text)) {
1594             SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1595             if (frame) {
1596                 sp_canvas_item_show(tc->frame);
1597                 Geom::OptRect frame_bbox = sp_item_bbox_desktop(frame);
1598                 if (frame_bbox) {
1599                     SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
1600                 }
1601             }
1602             SP_EVENT_CONTEXT(tc)->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("Type or edit flowed text (%d characters); <b>Enter</b> to start new paragraph."), nChars);
1603         } else {
1604             SP_EVENT_CONTEXT(tc)->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("Type or edit text (%d characters); <b>Enter</b> to start new line."), nChars);
1605         }
1607     } else {
1608         sp_canvas_item_hide(tc->cursor);
1609         sp_canvas_item_hide(tc->frame);
1610         tc->show = FALSE;
1611         if (!tc->nascent_object) {
1612             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
1613         }
1614     }
1616     if (tc->imc) {
1617         gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1618     }
1619     SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1622 static void sp_text_context_update_text_selection(SPTextContext *tc)
1624     // due to interruptible display, tc may already be destroyed during a display update before
1625     // the selection update (can't do both atomically, alas)
1626     if (!tc->desktop) return;
1628     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1629         sp_canvas_item_hide(*it);
1630         gtk_object_destroy(*it);
1631     }
1632     tc->text_selection_quads.clear();
1634     std::vector<Geom::Point> quads;
1635     if (tc->text != NULL)
1636         quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1637     for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1638         SPCanvasItem *quad_canvasitem;
1639         quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1640         // FIXME: make the color settable in prefs
1641         // for now, use semitrasparent blue, as cairo cannot do inversion :(
1642         sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x00777777);
1643         sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1644         sp_canvas_item_show(quad_canvasitem);
1645         tc->text_selection_quads.push_back(quad_canvasitem);
1646     }
1649 static gint
1650 sp_text_context_timeout(SPTextContext *tc)
1652     if (tc->show) {
1653         sp_canvas_item_show(tc->cursor);
1654         if (tc->phase) {
1655             tc->phase = 0;
1656             sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
1657         } else {
1658             tc->phase = 1;
1659             sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0xffffffff);
1660         }
1661     }
1663     return TRUE;
1666 static void
1667 sp_text_context_forget_text(SPTextContext *tc)
1669     if (! tc->text) return;
1670     SPItem *ti = tc->text;
1671     (void)ti;
1672     /* We have to set it to zero,
1673      * or selection changed signal messes everything up */
1674     tc->text = NULL;
1676 /* FIXME: this automatic deletion when nothing is inputted crashes the XML edittor and also crashes when duplicating an empty flowtext.
1677     So don't create an empty flowtext in the first place? Create it when first character is typed.
1678     */
1679 /*
1680     if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1681         Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1682         // the repr may already have been unparented
1683         // if we were called e.g. as the result of
1684         // an undo or the element being removed from
1685         // the XML editor
1686         if ( text_repr && sp_repr_parent(text_repr) ) {
1687             sp_repr_unparent(text_repr);
1688             sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1689                      _("Remove empty text"));
1690         }
1691     }
1692 */
1695 gint
1696 sptc_focus_in(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1698     gtk_im_context_focus_in(tc->imc);
1699     return FALSE;
1702 gint
1703 sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1705     gtk_im_context_focus_out(tc->imc);
1706     return FALSE;
1709 static void
1710 sptc_commit(GtkIMContext */*imc*/, gchar *string, SPTextContext *tc)
1712     if (!tc->text) {
1713         sp_text_context_setup_text(tc);
1714         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1715     }
1717     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1718     sp_text_context_update_cursor(tc);
1719     sp_text_context_update_text_selection(tc);
1721     sp_document_done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT,
1722                      _("Type text"));
1725 void
1726 sp_text_context_place_cursor (SPTextContext *tc, SPObject *text, Inkscape::Text::Layout::iterator where)
1728     SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text);
1729     tc->text_sel_start = tc->text_sel_end = where;
1730     sp_text_context_update_cursor(tc);
1731     sp_text_context_update_text_selection(tc);
1734 void
1735 sp_text_context_place_cursor_at (SPTextContext *tc, SPObject *text, Geom::Point const p)
1737     SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text);
1738     sp_text_context_place_cursor (tc, text, sp_te_get_position_by_coords(tc->text, p));
1741 Inkscape::Text::Layout::iterator *sp_text_context_get_cursor_position(SPTextContext *tc, SPObject *text)
1743     if (text != tc->text)
1744         return NULL;
1745     return &(tc->text_sel_end);
1749 /*
1750   Local Variables:
1751   mode:c++
1752   c-file-style:"stroustrup"
1753   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1754   indent-tabs-mode:nil
1755   fill-column:99
1756   End:
1757 */
1758 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :