Code

Make all tools consider full parent transform (up to document, not just up to root)
[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 "desktop-affine.h"
39 #include "message-stack.h"
40 #include "message-context.h"
41 #include "pixmaps/cursor-text.xpm"
42 #include "pixmaps/cursor-text-insert.xpm"
43 #include <glibmm/i18n.h>
44 #include "object-edit.h"
45 #include "xml/repr.h"
46 #include "xml/node-event-vector.h"
47 #include "preferences.h"
48 #include "rubberband.h"
49 #include "sp-metrics.h"
50 #include "context-fns.h"
51 #include "verbs.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     event_context->shape_repr = NULL;
135     event_context->shape_knot_holder = NULL;
137     tc->imc = NULL;
139     tc->text = NULL;
140     tc->pdoc = Geom::Point(0, 0);
141     new (&tc->text_sel_start) Inkscape::Text::Layout::iterator();
142     new (&tc->text_sel_end) Inkscape::Text::Layout::iterator();
143     new (&tc->text_selection_quads) std::vector<SPCanvasItem*>();
145     tc->unimode = false;
147     tc->cursor = NULL;
148     tc->indicator = NULL;
149     tc->frame = NULL;
150     tc->grabbed = NULL;
151     tc->timeout = 0;
152     tc->show = FALSE;
153     tc->phase = 0;
154     tc->nascent_object = 0;
155     tc->over_text = 0;
156     tc->dragging = 0;
157     tc->creating = 0;
159     new (&tc->sel_changed_connection) sigc::connection();
160     new (&tc->sel_modified_connection) sigc::connection();
161     new (&tc->style_set_connection) sigc::connection();
162     new (&tc->style_query_connection) sigc::connection();
165 static void
166 sp_text_context_dispose(GObject *obj)
168     SPTextContext *tc = SP_TEXT_CONTEXT(obj);
169     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
170     tc->style_query_connection.~connection();
171     tc->style_set_connection.~connection();
172     tc->sel_changed_connection.~connection();
173     tc->sel_modified_connection.~connection();
174     tc->text_sel_end.~iterator();
175     tc->text_sel_start.~iterator();
176     tc->text_selection_quads.~vector();
177     if (G_OBJECT_CLASS(parent_class)->dispose) {
178         G_OBJECT_CLASS(parent_class)->dispose(obj);
179     }
180     if (tc->grabbed) {
181         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
182         tc->grabbed = NULL;
183     }
185     Inkscape::Rubberband::get(ec->desktop)->stop();
187     if (ec->shape_knot_holder) {
188         delete ec->shape_knot_holder;
189         ec->shape_knot_holder = NULL;
190     }
191     if (ec->shape_repr) { // remove old listener
192         sp_repr_remove_listener_by_data(ec->shape_repr, ec);
193         Inkscape::GC::release(ec->shape_repr);
194         ec->shape_repr = 0;
195     }
198 static Inkscape::XML::NodeEventVector ec_shape_repr_events = {
199     NULL, /* child_added */
200     NULL, /* child_removed */
201     ec_shape_event_attr_changed,
202     NULL, /* content_changed */
203     NULL  /* order_changed */
204 };
206 static void
207 sp_text_context_setup(SPEventContext *ec)
209     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
210     SPDesktop *desktop = ec->desktop;
211     GtkSettings* settings = gtk_settings_get_default();
212     gint timeout = 0;
213     g_object_get( settings, "gtk-cursor-blink-time", &timeout, NULL );
214     if (timeout < 0) {
215         timeout = 200;
216     } else {
217         timeout /= 2;
218     }
220     tc->cursor = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLLINE, NULL);
221     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), 100, 0, 100, 100);
222     sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
223     sp_canvas_item_hide(tc->cursor);
225     tc->indicator = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
226     SP_CTRLRECT(tc->indicator)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100)));
227     SP_CTRLRECT(tc->indicator)->setColor(0x0000ff7f, false, 0);
228     sp_canvas_item_hide(tc->indicator);
230     tc->frame = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
231     SP_CTRLRECT(tc->frame)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100)));
232     SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0);
233     sp_canvas_item_hide(tc->frame);
235     tc->timeout = gtk_timeout_add(timeout, (GtkFunction) sp_text_context_timeout, ec);
237     tc->imc = gtk_im_multicontext_new();
238     if (tc->imc) {
239         GtkWidget *canvas = GTK_WIDGET(sp_desktop_canvas(desktop));
241         /* im preedit handling is very broken in inkscape for
242          * multi-byte characters.  See bug 1086769.
243          * We need to let the IM handle the preediting, and
244          * just take in the characters when they're finished being
245          * entered.
246          */
247         gtk_im_context_set_use_preedit(tc->imc, FALSE);
248         gtk_im_context_set_client_window(tc->imc, canvas->window);
250         g_signal_connect(G_OBJECT(canvas), "focus_in_event", G_CALLBACK(sptc_focus_in), tc);
251         g_signal_connect(G_OBJECT(canvas), "focus_out_event", G_CALLBACK(sptc_focus_out), tc);
252         g_signal_connect(G_OBJECT(tc->imc), "commit", G_CALLBACK(sptc_commit), tc);
254         if (GTK_WIDGET_HAS_FOCUS(canvas)) {
255             sptc_focus_in(canvas, NULL, tc);
256         }
257     }
259     if (((SPEventContextClass *) parent_class)->setup)
260         ((SPEventContextClass *) parent_class)->setup(ec);
262     SPItem *item = sp_desktop_selection(ec->desktop)->singleItem();
263     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
264         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
265         Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
266         if (shape_repr) {
267             ec->shape_repr = shape_repr;
268             Inkscape::GC::anchor(shape_repr);
269             sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
270         }
271     }
273     tc->sel_changed_connection = sp_desktop_selection(desktop)->connectChanged(
274         sigc::bind(sigc::ptr_fun(&sp_text_context_selection_changed), tc)
275         );
276     tc->sel_modified_connection = sp_desktop_selection(desktop)->connectModified(
277         sigc::bind(sigc::ptr_fun(&sp_text_context_selection_modified), tc)
278         );
279     tc->style_set_connection = desktop->connectSetStyle(
280         sigc::bind(sigc::ptr_fun(&sp_text_context_style_set), tc)
281         );
282     tc->style_query_connection = desktop->connectQueryStyle(
283         sigc::bind(sigc::ptr_fun(&sp_text_context_style_query), tc)
284         );
286     sp_text_context_selection_changed(sp_desktop_selection(desktop), tc);
288     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
289     if (prefs->getBool("/tools/text/selcue")) {
290         ec->enableSelectionCue();
291     }
292     if (prefs->getBool("/tools/text/gradientdrag")) {
293         ec->enableGrDrag();
294     }
297 static void
298 sp_text_context_finish(SPEventContext *ec)
300     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
302     if (ec->desktop) {
303         sp_signal_disconnect_by_data(sp_desktop_canvas(ec->desktop), tc);
304     }
306     ec->enableGrDrag(false);
308     tc->style_set_connection.disconnect();
309     tc->style_query_connection.disconnect();
310     tc->sel_changed_connection.disconnect();
311     tc->sel_modified_connection.disconnect();
313     sp_text_context_forget_text(SP_TEXT_CONTEXT(ec));
315     if (tc->imc) {
316         g_object_unref(G_OBJECT(tc->imc));
317         tc->imc = NULL;
318     }
320     if (tc->timeout) {
321         gtk_timeout_remove(tc->timeout);
322         tc->timeout = 0;
323     }
325     if (tc->cursor) {
326         gtk_object_destroy(GTK_OBJECT(tc->cursor));
327         tc->cursor = NULL;
328     }
330     if (tc->indicator) {
331         gtk_object_destroy(GTK_OBJECT(tc->indicator));
332         tc->indicator = NULL;
333     }
335     if (tc->frame) {
336         gtk_object_destroy(GTK_OBJECT(tc->frame));
337         tc->frame = NULL;
338     }
340     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ;
341          it != tc->text_selection_quads.end() ; ++it) {
342         sp_canvas_item_hide(*it);
343         gtk_object_destroy(*it);
344     }
345     tc->text_selection_quads.clear();
349 static gint
350 sp_text_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
352     SPTextContext *tc = SP_TEXT_CONTEXT(event_context);
353     SPDesktop *desktop = event_context->desktop;
354     SPItem *item_ungrouped;
356     gint ret = FALSE;
358     sp_text_context_validate_cursor_iterators(tc);
360     switch (event->type) {
361         case GDK_BUTTON_PRESS:
362             if (event->button.button == 1 && !event_context->space_panning) {
363                 // find out clicked item, disregarding groups
364                 item_ungrouped = desktop->item_at_point(Geom::Point(event->button.x, event->button.y), TRUE);
365                 if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
366                     sp_desktop_selection(desktop)->set(item_ungrouped);
367                     if (tc->text) {
368                         // find out click point in document coordinates
369                         Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
370                         // set the cursor closest to that point
371                         tc->text_sel_start = tc->text_sel_end = sp_te_get_position_by_coords(tc->text, p);
372                         // update display
373                         sp_text_context_update_cursor(tc);
374                         sp_text_context_update_text_selection(tc);
375                         tc->dragging = 1;
376                     }
377                     ret = TRUE;
378                 }
379             }
380             break;
381         case GDK_2BUTTON_PRESS:
382             if (event->button.button == 1 && tc->text) {
383                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
384                 if (layout) {
385                     if (!layout->isStartOfWord(tc->text_sel_start))
386                         tc->text_sel_start.prevStartOfWord();
387                     if (!layout->isEndOfWord(tc->text_sel_end))
388                         tc->text_sel_end.nextEndOfWord();
389                     sp_text_context_update_cursor(tc);
390                     sp_text_context_update_text_selection(tc);
391                     tc->dragging = 2;
392                     ret = TRUE;
393                 }
394             }
395             break;
396         case GDK_3BUTTON_PRESS:
397             if (event->button.button == 1 && tc->text) {
398                 tc->text_sel_start.thisStartOfLine();
399                 tc->text_sel_end.thisEndOfLine();
400                 sp_text_context_update_cursor(tc);
401                 sp_text_context_update_text_selection(tc);
402                 tc->dragging = 3;
403                 ret = TRUE;
404             }
405             break;
406         case GDK_BUTTON_RELEASE:
407             if (event->button.button == 1 && tc->dragging && !event_context->space_panning) {
408                 tc->dragging = 0;
409                 ret = TRUE;
410             }
411             break;
412         case GDK_MOTION_NOTIFY:
413             if (event->motion.state & GDK_BUTTON1_MASK && tc->dragging && !event_context->space_panning) {
414                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
415                 if (!layout) break;
416                 // find out click point in document coordinates
417                 Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
418                 // set the cursor closest to that point
419                 Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(tc->text, p);
420                 if (tc->dragging == 2) {
421                     // double-click dragging: go by word
422                     if (new_end < tc->text_sel_start) {
423                         if (!layout->isStartOfWord(new_end))
424                             new_end.prevStartOfWord();
425                     } else
426                         if (!layout->isEndOfWord(new_end))
427                             new_end.nextEndOfWord();
428                 } else if (tc->dragging == 3) {
429                     // triple-click dragging: go by line
430                     if (new_end < tc->text_sel_start)
431                         new_end.thisStartOfLine();
432                     else
433                         new_end.thisEndOfLine();
434                 }
435                 // update display
436                 if (tc->text_sel_end != new_end) {
437                     tc->text_sel_end = new_end;
438                     sp_text_context_update_cursor(tc);
439                     sp_text_context_update_text_selection(tc);
440                 }
441                 gobble_motion_events(GDK_BUTTON1_MASK);
442                 ret = TRUE;
443                 break;
444             }
445             // find out item under mouse, disregarding groups
446             item_ungrouped = desktop->item_at_point(Geom::Point(event->button.x, event->button.y), TRUE);
447             if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
448                 sp_canvas_item_show(tc->indicator);
449                 Geom::OptRect ibbox = sp_item_bbox_desktop(item_ungrouped);
450                 if (ibbox) {
451                     SP_CTRLRECT(tc->indicator)->setRectangle(*ibbox);
452                 }
454                 event_context->cursor_shape = cursor_text_insert_xpm;
455                 event_context->hot_x = 7;
456                 event_context->hot_y = 10;
457                 sp_event_context_update_cursor(event_context);
458                 sp_text_context_update_text_selection(tc);
460                 if (SP_IS_TEXT (item_ungrouped)) {
461                     desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
462                 } else {
463                     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."));
464                 }
466                 tc->over_text = true;
468                 ret = TRUE;
469             }
470             break;
471         default:
472             break;
473     }
475     if (!ret) {
476         if (((SPEventContextClass *) parent_class)->item_handler)
477             ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
478     }
480     return ret;
483 static void
484 sp_text_context_setup_text(SPTextContext *tc)
486     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
488     /* Create <text> */
489     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_EVENT_CONTEXT_DESKTOP(ec)->doc());
490     Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
491     rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
493     /* Set style */
494     sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "/tools/text", true);
496     sp_repr_set_svg_double(rtext, "x", tc->pdoc[Geom::X]);
497     sp_repr_set_svg_double(rtext, "y", tc->pdoc[Geom::Y]);
499     /* Create <tspan> */
500     Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
501     rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
502     rtext->addChild(rtspan, NULL);
503     Inkscape::GC::release(rtspan);
505     /* Create TEXT */
506     Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
507     rtspan->addChild(rstring, NULL);
508     Inkscape::GC::release(rstring);
509     SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
510     /* fixme: Is selection::changed really immediate? */
511     /* yes, it's immediate .. why does it matter? */
512     sp_desktop_selection(ec->desktop)->set(text_item);
513     Inkscape::GC::release(rtext);
514     text_item->transform = sp_item_i2doc_affine(SP_ITEM(ec->desktop->currentLayer())).inverse();
516     text_item->updateRepr();
517     sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
518                      _("Create text"));
521 /**
522  * Insert the character indicated by tc.uni to replace the current selection,
523  * and reset tc.uni/tc.unipos to empty string.
524  *
525  * \pre tc.uni/tc.unipos non-empty.
526  */
527 static void
528 insert_uni_char(SPTextContext *const tc)
530     g_return_if_fail(tc->unipos
531                      && tc->unipos < sizeof(tc->uni)
532                      && tc->uni[tc->unipos] == '\0');
533     unsigned int uv;
534     sscanf(tc->uni, "%x", &uv);
535     tc->unipos = 0;
536     tc->uni[tc->unipos] = '\0';
538     if ( !g_unichar_isprint(static_cast<gunichar>(uv))
539          && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
540         // This may be due to bad input, so it goes to statusbar.
541         tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
542                                            _("Non-printable character"));
543     } else {
544         if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
545             sp_text_context_setup_text(tc);
546             tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
547         }
549         gchar u[10];
550         guint const len = g_unichar_to_utf8(uv, u);
551         u[len] = '\0';
553         tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
554         sp_text_context_update_cursor(tc);
555         sp_text_context_update_text_selection(tc);
556         sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM,
557                          _("Insert Unicode character"));
558     }
561 static void
562 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
564     unsigned int uv;
565     sscanf(hex, "%x", &uv);
566     if (!g_unichar_isprint((gunichar) uv)) {
567         uv = 0xfffd;
568     }
569     guint const len = g_unichar_to_utf8(uv, utf8);
570     utf8[len] = '\0';
573 static void
574 show_curr_uni_char(SPTextContext *const tc)
576     g_return_if_fail(tc->unipos < sizeof(tc->uni)
577                      && tc->uni[tc->unipos] == '\0');
578     if (tc->unipos) {
579         char utf8[10];
580         hex_to_printable_utf8_buf(tc->uni, utf8);
582         /* Status bar messages are in pango markup, so we need xml escaping. */
583         if (utf8[1] == '\0') {
584             switch(utf8[0]) {
585                 case '<': strcpy(utf8, "&lt;"); break;
586                 case '>': strcpy(utf8, "&gt;"); break;
587                 case '&': strcpy(utf8, "&amp;"); break;
588                 default: break;
589             }
590         }
591         tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
592                                           _("Unicode (<b>Enter</b> to finish): %s: %s"), tc->uni, utf8);
593     } else {
594         tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
595     }
598 static gint
599 sp_text_context_root_handler(SPEventContext *const event_context, GdkEvent *const event)
601     SPTextContext *const tc = SP_TEXT_CONTEXT(event_context);
603     SPDesktop *desktop = event_context->desktop;
605     sp_canvas_item_hide(tc->indicator);
607     sp_text_context_validate_cursor_iterators(tc);
609     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
610     event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
612     switch (event->type) {
613         case GDK_BUTTON_PRESS:
614             if (event->button.button == 1 && !event_context->space_panning) {
616                 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
617                     return TRUE;
618                 }
620                 // save drag origin
621                 event_context->xp = (gint) event->button.x;
622                 event_context->yp = (gint) event->button.y;
623                 event_context->within_tolerance = true;
625                 Geom::Point const button_pt(event->button.x, event->button.y);
626                 tc->p0 = desktop->w2d(button_pt);
627                 Inkscape::Rubberband::get(desktop)->start(desktop, tc->p0);
628                 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
629                                     GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
630                                     NULL, event->button.time);
631                 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
632                 tc->creating = 1;
634                 /* Processed */
635                 return TRUE;
636             }
637             break;
638         case GDK_MOTION_NOTIFY:
639             if (tc->over_text) {
640                 tc->over_text = 0;
641                 // update cursor and statusbar: we are not over a text object now
642                 event_context->cursor_shape = cursor_text_xpm;
643                 event_context->hot_x = 7;
644                 event_context->hot_y = 7;
645                 sp_event_context_update_cursor(event_context);
646                 desktop->event_context->defaultMessageContext()->clear();
647             }
649             if (tc->creating && event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
650                 if ( event_context->within_tolerance
651                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
652                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
653                     break; // do not drag if we're within tolerance from origin
654                 }
655                 // Once the user has moved farther than tolerance from the original location
656                 // (indicating they intend to draw, not click), then always process the
657                 // motion notify coordinates as given (no snapping back to origin)
658                 event_context->within_tolerance = false;
660                 Geom::Point const motion_pt(event->motion.x, event->motion.y);
661                 Geom::Point const p = desktop->w2d(motion_pt);
663                 Inkscape::Rubberband::get(desktop)->move(p);
664                 gobble_motion_events(GDK_BUTTON1_MASK);
666                 // status text
667                 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::X]), desktop->namedview->getDefaultMetric());
668                 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::Y]), desktop->namedview->getDefaultMetric());
669                 event_context->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Flowed text frame</b>: %s &#215; %s"), xs->str, ys->str);
670                 g_string_free(xs, FALSE);
671                 g_string_free(ys, FALSE);
673             }
674             break;
675         case GDK_BUTTON_RELEASE:
676             if (event->button.button == 1 && !event_context->space_panning) {
678                 if (tc->grabbed) {
679                     sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
680                     tc->grabbed = NULL;
681                 }
683                 Inkscape::Rubberband::get(desktop)->stop();
685                 if (tc->creating && event_context->within_tolerance) {
686                     /* Button 1, set X & Y & new item */
687                     sp_desktop_selection(desktop)->clear();
688                     Geom::Point dtp = desktop->w2d(Geom::Point(event->button.x, event->button.y));
689                     tc->pdoc = sp_desktop_dt2doc_xy_point(desktop, dtp);
691                     tc->show = TRUE;
692                     tc->phase = 1;
693                     tc->nascent_object = 1; // new object was just created
695                     /* Cursor */
696                     sp_canvas_item_show(tc->cursor);
697                     // Cursor height is defined by the new text object's font size; it needs to be set
698                     // articifically here, for the text object does not exist yet:
699                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
700                     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), dtp, dtp + Geom::Point(0, cursor_height));
701                     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
703                     event_context->within_tolerance = false;
704                 } else if (tc->creating) {
705                     Geom::Point const button_pt(event->button.x, event->button.y);
706                     Geom::Point p1 = desktop->w2d(button_pt);
707                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
708                     if (fabs(p1[Geom::Y] - tc->p0[Geom::Y]) > cursor_height) {
709                         // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
710                         SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
711                         /* Set style */
712                         sp_desktop_apply_style_tool(desktop, SP_OBJECT_REPR(ft), "/tools/text", true);
713                         sp_desktop_selection(desktop)->set(ft);
714                         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
715                         sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
716                                          _("Create flowed text"));
717                     } else {
718                         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
719                     }
720                 }
721                 tc->creating = false;
722                 return TRUE;
723             }
724             break;
725         case GDK_KEY_PRESS: {
726             guint const group0_keyval = get_group0_keyval(&event->key);
728             if (group0_keyval == GDK_KP_Add ||
729                 group0_keyval == GDK_KP_Subtract) {
730                 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
731                     break; // otherwise pass on keypad +/- so they can zoom
732             }
734             if ((tc->text) || (tc->nascent_object)) {
735                 // there is an active text object in this context, or a new object was just created
737                 if (tc->unimode || !tc->imc
738                     || (MOD__CTRL && MOD__SHIFT)    // input methods tend to steal this for unimode,
739                                                     // but we have our own so make sure they don't swallow it
740                     || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
741                     //IM did not consume the key, or we're in unimode
743                         if (!MOD__CTRL_ONLY && tc->unimode) {
744                             /* TODO: ISO 14755 (section 3 Definitions) says that we should also
745                                accept the first 6 characters of alphabets other than the latin
746                                alphabet "if the Latin alphabet is not used".  The below is also
747                                reasonable (viz. hope that the user's keyboard includes latin
748                                characters and force latin interpretation -- just as we do for our
749                                keyboard shortcuts), but differs from the ISO 14755
750                                recommendation. */
751                             switch (group0_keyval) {
752                                 case GDK_space:
753                                 case GDK_KP_Space: {
754                                     if (tc->unipos) {
755                                         insert_uni_char(tc);
756                                     }
757                                     /* Stay in unimode. */
758                                     show_curr_uni_char(tc);
759                                     return TRUE;
760                                 }
762                                 case GDK_BackSpace: {
763                                     g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
764                                     if (tc->unipos) {
765                                         tc->uni[--tc->unipos] = '\0';
766                                     }
767                                     show_curr_uni_char(tc);
768                                     return TRUE;
769                                 }
771                                 case GDK_Return:
772                                 case GDK_KP_Enter: {
773                                     if (tc->unipos) {
774                                         insert_uni_char(tc);
775                                     }
776                                     /* Exit unimode. */
777                                     tc->unimode = false;
778                                     event_context->defaultMessageContext()->clear();
779                                     return TRUE;
780                                 }
782                                 case GDK_Escape: {
783                                     // Cancel unimode.
784                                     tc->unimode = false;
785                                     gtk_im_context_reset(tc->imc);
786                                     event_context->defaultMessageContext()->clear();
787                                     return TRUE;
788                                 }
790                                 case GDK_Shift_L:
791                                 case GDK_Shift_R:
792                                     break;
794                                 default: {
795                                     if (g_ascii_isxdigit(group0_keyval)) {
796                                         g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
797                                         tc->uni[tc->unipos++] = group0_keyval;
798                                         tc->uni[tc->unipos] = '\0';
799                                         if (tc->unipos == 8) {
800                                             /* This behaviour is partly to allow us to continue to
801                                                use a fixed-length buffer for tc->uni.  Reason for
802                                                choosing the number 8 is that it's the length of
803                                                ``canonical form'' mentioned in the ISO 14755 spec.
804                                                An advantage over choosing 6 is that it allows using
805                                                backspace for typos & misremembering when entering a
806                                                6-digit number. */
807                                             insert_uni_char(tc);
808                                         }
809                                         show_curr_uni_char(tc);
810                                         return TRUE;
811                                     } else {
812                                         /* The intent is to ignore but consume characters that could be
813                                            typos for hex digits.  Gtk seems to ignore & consume all
814                                            non-hex-digits, and we do similar here.  Though note that some
815                                            shortcuts (like keypad +/- for zoom) get processed before
816                                            reaching this code. */
817                                         return TRUE;
818                                     }
819                                 }
820                             }
821                         }
823                         Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
824                         Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
825                         bool cursor_moved = false;
826                         int screenlines = 1;
827                         if (tc->text) {
828                             double spacing = sp_te_get_average_linespacing(tc->text);
829                             Geom::Rect const d = desktop->get_display_area();
830                             screenlines = (int) floor(fabs(d.min()[Geom::Y] - d.max()[Geom::Y])/spacing) - 1;
831                             if (screenlines <= 0)
832                                 screenlines = 1;
833                         }
835                         /* Neither unimode nor IM consumed key; process text tool shortcuts */
836                         switch (group0_keyval) {
837                             case GDK_x:
838                             case GDK_X:
839                                 if (MOD__ALT_ONLY) {
840                                     desktop->setToolboxFocusTo ("altx-text");
841                                     return TRUE;
842                                 }
843                                 break;
844                             case GDK_space:
845                                 if (MOD__CTRL_ONLY) {
846                                     /* No-break space */
847                                     if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
848                                         sp_text_context_setup_text(tc);
849                                         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
850                                     }
851                                     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
852                                     sp_text_context_update_cursor(tc);
853                                     sp_text_context_update_text_selection(tc);
854                                     desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
855                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
856                                                      _("Insert no-break space"));
857                                     return TRUE;
858                                 }
859                                 break;
860                             case GDK_U:
861                             case GDK_u:
862                                 if (MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT)) {
863                                     if (tc->unimode) {
864                                         tc->unimode = false;
865                                         event_context->defaultMessageContext()->clear();
866                                     } else {
867                                         tc->unimode = true;
868                                         tc->unipos = 0;
869                                         event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
870                                     }
871                                     if (tc->imc) {
872                                         gtk_im_context_reset(tc->imc);
873                                     }
874                                     return TRUE;
875                                 }
876                                 break;
877                             case GDK_B:
878                             case GDK_b:
879                                 if (MOD__CTRL_ONLY && tc->text) {
880                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
881                                     SPCSSAttr *css = sp_repr_css_attr_new();
882                                     if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
883                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
884                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
885                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
886                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
887                                         sp_repr_css_set_property(css, "font-weight", "bold");
888                                     else
889                                         sp_repr_css_set_property(css, "font-weight", "normal");
890                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
891                                     sp_repr_css_attr_unref(css);
892                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
893                                                      _("Make bold"));
894                                     sp_text_context_update_cursor(tc);
895                                     sp_text_context_update_text_selection(tc);
896                                     return TRUE;
897                                 }
898                                 break;
899                             case GDK_I:
900                             case GDK_i:
901                                 if (MOD__CTRL_ONLY && tc->text) {
902                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
903                                     SPCSSAttr *css = sp_repr_css_attr_new();
904                                     if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL)
905                                         sp_repr_css_set_property(css, "font-style", "italic");
906                                     else
907                                         sp_repr_css_set_property(css, "font-style", "normal");
908                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
909                                     sp_repr_css_attr_unref(css);
910                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
911                                                      _("Make italic"));
912                                     sp_text_context_update_cursor(tc);
913                                     sp_text_context_update_text_selection(tc);
914                                     return TRUE;
915                                 }
916                                 break;
918                             case GDK_A:
919                             case GDK_a:
920                                 if (MOD__CTRL_ONLY && tc->text) {
921                                     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
922                                     if (layout) {
923                                         tc->text_sel_start = layout->begin();
924                                         tc->text_sel_end = layout->end();
925                                         sp_text_context_update_cursor(tc);
926                                         sp_text_context_update_text_selection(tc);
927                                         return TRUE;
928                                     }
929                                 }
930                                 break;
932                             case GDK_Return:
933                             case GDK_KP_Enter:
934                             {
935                                 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
936                                     sp_text_context_setup_text(tc);
937                                     tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
938                                 }
940                                 iterator_pair enter_pair;
941                                 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, enter_pair);
942                                 (void)success; // TODO cleanup
943                                 tc->text_sel_start = tc->text_sel_end = enter_pair.first;
945                                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
947                                 sp_text_context_update_cursor(tc);
948                                 sp_text_context_update_text_selection(tc);
949                                 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
950                                                  _("New line"));
951                                 return TRUE;
952                             }
953                             case GDK_BackSpace:
954                                 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
956                                     bool noSelection = false;
958                                         if (tc->text_sel_start == tc->text_sel_end) {
959                                         tc->text_sel_start.prevCursorPosition();
960                                         noSelection = true;
961                                     }
963                                         iterator_pair bspace_pair;
964                                         bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, bspace_pair);
966                                     if (noSelection) {
967                                         if (success) {
968                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
969                                         } else { // nothing deleted
970                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.second;
971                                         }
972                                     } else {
973                                         if (success) {
974                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
975                                         } else { // nothing deleted
976                                             tc->text_sel_start = bspace_pair.first;
977                                             tc->text_sel_end = bspace_pair.second;
978                                         }
979                                     }
981                                     sp_text_context_update_cursor(tc);
982                                     sp_text_context_update_text_selection(tc);
983                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
984                                                      _("Backspace"));
985                                 }
986                                 return TRUE;
987                             case GDK_Delete:
988                             case GDK_KP_Delete:
989                                 if (tc->text) {
990                                     bool noSelection = false;
992                                     if (tc->text_sel_start == tc->text_sel_end) {
993                                         tc->text_sel_end.nextCursorPosition();
994                                         noSelection = true;
995                                     }
997                                     iterator_pair del_pair;
998                                     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, del_pair);
1000                                     if (noSelection) {
1001                                         tc->text_sel_start = tc->text_sel_end = del_pair.first;
1002                                     } else {
1003                                         if (success) {
1004                                             tc->text_sel_start = tc->text_sel_end = del_pair.first;
1005                                         } else { // nothing deleted
1006                                             tc->text_sel_start = del_pair.first;
1007                                             tc->text_sel_end = del_pair.second;
1008                                         }
1009                                     }
1012                                     sp_text_context_update_cursor(tc);
1013                                     sp_text_context_update_text_selection(tc);
1014                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
1015                                                      _("Delete"));
1016                                 }
1017                                 return TRUE;
1018                             case GDK_Left:
1019                             case GDK_KP_Left:
1020                             case GDK_KP_4:
1021                                 if (tc->text) {
1022                                     if (MOD__ALT) {
1023                                         gint mul = 1 + gobble_key_events(
1024                                             get_group0_keyval(&event->key), 0); // with any mask
1025                                         if (MOD__SHIFT)
1026                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-10, 0));
1027                                         else
1028                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-1, 0));
1029                                         sp_text_context_update_cursor(tc);
1030                                         sp_text_context_update_text_selection(tc);
1031                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT,
1032                                                                _("Kern to the left"));
1033                                     } else {
1034                                         if (MOD__CTRL) 
1035                                             tc->text_sel_end.cursorLeftWithControl();
1036                                         else
1037                                             tc->text_sel_end.cursorLeft();
1038                                         cursor_moved = true;
1039                                         break;
1040                                     }
1041                                 }
1042                                 return TRUE;
1043                             case GDK_Right:
1044                             case GDK_KP_Right:
1045                             case GDK_KP_6:
1046                                 if (tc->text) {
1047                                     if (MOD__ALT) {
1048                                         gint mul = 1 + gobble_key_events(
1049                                             get_group0_keyval(&event->key), 0); // with any mask
1050                                         if (MOD__SHIFT)
1051                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*10, 0));
1052                                         else
1053                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*1, 0));
1054                                         sp_text_context_update_cursor(tc);
1055                                         sp_text_context_update_text_selection(tc);
1056                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT,
1057                                                                _("Kern to the right"));
1058                                     } else {
1059                                         if (MOD__CTRL)
1060                                             tc->text_sel_end.cursorRightWithControl();
1061                                         else
1062                                             tc->text_sel_end.cursorRight();
1063                                         cursor_moved = true;
1064                                         break;
1065                                     }
1066                                 }
1067                                 return TRUE;
1068                             case GDK_Up:
1069                             case GDK_KP_Up:
1070                             case GDK_KP_8:
1071                                 if (tc->text) {
1072                                     if (MOD__ALT) {
1073                                         gint mul = 1 + gobble_key_events(
1074                                             get_group0_keyval(&event->key), 0); // with any mask
1075                                         if (MOD__SHIFT)
1076                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-10));
1077                                         else
1078                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-1));
1079                                         sp_text_context_update_cursor(tc);
1080                                         sp_text_context_update_text_selection(tc);
1081                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT,
1082                                                                _("Kern up"));
1084                                     } else {
1085                                         if (MOD__CTRL)
1086                                             tc->text_sel_end.cursorUpWithControl();
1087                                         else
1088                                             tc->text_sel_end.cursorUp();
1089                                         cursor_moved = true;
1090                                         break;
1091                                     }
1092                                 }
1093                                 return TRUE;
1094                             case GDK_Down:
1095                             case GDK_KP_Down:
1096                             case GDK_KP_2:
1097                                 if (tc->text) {
1098                                     if (MOD__ALT) {
1099                                         gint mul = 1 + gobble_key_events(
1100                                             get_group0_keyval(&event->key), 0); // with any mask
1101                                         if (MOD__SHIFT)
1102                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*10));
1103                                         else
1104                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*1));
1105                                         sp_text_context_update_cursor(tc);
1106                                         sp_text_context_update_text_selection(tc);
1107                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT,
1108                                                                _("Kern down"));
1110                                     } else {
1111                                         if (MOD__CTRL)
1112                                             tc->text_sel_end.cursorDownWithControl();
1113                                         else
1114                                             tc->text_sel_end.cursorDown();
1115                                         cursor_moved = true;
1116                                         break;
1117                                     }
1118                                 }
1119                                 return TRUE;
1120                             case GDK_Home:
1121                             case GDK_KP_Home:
1122                                 if (tc->text) {
1123                                     if (MOD__CTRL)
1124                                         tc->text_sel_end.thisStartOfShape();
1125                                     else
1126                                         tc->text_sel_end.thisStartOfLine();
1127                                     cursor_moved = true;
1128                                     break;
1129                                 }
1130                                 return TRUE;
1131                             case GDK_End:
1132                             case GDK_KP_End:
1133                                 if (tc->text) {
1134                                     if (MOD__CTRL)
1135                                         tc->text_sel_end.nextStartOfShape();
1136                                     else
1137                                         tc->text_sel_end.thisEndOfLine();
1138                                     cursor_moved = true;
1139                                     break;
1140                                 }
1141                                 return TRUE;
1142                             case GDK_Page_Down:
1143                             case GDK_KP_Page_Down:
1144                                 if (tc->text) {
1145                                     tc->text_sel_end.cursorDown(screenlines);
1146                                     cursor_moved = true;
1147                                     break;
1148                                 }
1149                                 return TRUE;
1150                             case GDK_Page_Up:
1151                             case GDK_KP_Page_Up:
1152                                 if (tc->text) {
1153                                     tc->text_sel_end.cursorUp(screenlines);
1154                                     cursor_moved = true;
1155                                     break;
1156                                 }
1157                                 return TRUE;
1158                             case GDK_Escape:
1159                                 if (tc->creating) {
1160                                     tc->creating = 0;
1161                                     if (tc->grabbed) {
1162                                         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1163                                         tc->grabbed = NULL;
1164                                     }
1165                                     Inkscape::Rubberband::get(desktop)->stop();
1166                                 } else {
1167                                     sp_desktop_selection(desktop)->clear();
1168                                 }
1169                                 tc->nascent_object = FALSE;
1170                                 return TRUE;
1171                             case GDK_bracketleft:
1172                                 if (tc->text) {
1173                                     if (MOD__ALT || MOD__CTRL) {
1174                                         if (MOD__ALT) {
1175                                             if (MOD__SHIFT) {
1176                                                 // FIXME: alt+shift+[] does not work, don't know why
1177                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1178                                             } else {
1179                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1180                                             }
1181                                         } else {
1182                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -90);
1183                                         }
1184                                         sp_document_maybe_done(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT,
1185                                                                _("Rotate counterclockwise"));
1186                                         sp_text_context_update_cursor(tc);
1187                                         sp_text_context_update_text_selection(tc);
1188                                         return TRUE;
1189                                     }
1190                                 }
1191                                 break;
1192                             case GDK_bracketright:
1193                                 if (tc->text) {
1194                                     if (MOD__ALT || MOD__CTRL) {
1195                                         if (MOD__ALT) {
1196                                             if (MOD__SHIFT) {
1197                                                 // FIXME: alt+shift+[] does not work, don't know why
1198                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1199                                             } else {
1200                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1201                                             }
1202                                         } else {
1203                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 90);
1204                                         }
1205                                         sp_document_maybe_done(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT,
1206                                                                 _("Rotate clockwise"));
1207                                         sp_text_context_update_cursor(tc);
1208                                         sp_text_context_update_text_selection(tc);
1209                                         return TRUE;
1210                                     }
1211                                 }
1212                                 break;
1213                             case GDK_less:
1214                             case GDK_comma:
1215                                 if (tc->text) {
1216                                     if (MOD__ALT) {
1217                                         if (MOD__CTRL) {
1218                                             if (MOD__SHIFT)
1219                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1220                                             else
1221                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1222                                             sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT,
1223                                                                     _("Contract line spacing"));
1225                                         } else {
1226                                             if (MOD__SHIFT)
1227                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1228                                             else
1229                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1230                                             sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT,
1231                                                                     _("Contract letter spacing"));
1233                                         }
1234                                         sp_text_context_update_cursor(tc);
1235                                         sp_text_context_update_text_selection(tc);
1236                                         return TRUE;
1237                                     }
1238                                 }
1239                                 break;
1240                             case GDK_greater:
1241                             case GDK_period:
1242                                 if (tc->text) {
1243                                     if (MOD__ALT) {
1244                                         if (MOD__CTRL) {
1245                                             if (MOD__SHIFT)
1246                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1247                                             else
1248                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1249                                             sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT,
1250                                                                     _("Expand line spacing"));
1252                                         } else {
1253                                             if (MOD__SHIFT)
1254                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1255                                             else
1256                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1257                                             sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT,
1258                                                                     _("Expand letter spacing"));
1260                                         }
1261                                         sp_text_context_update_cursor(tc);
1262                                         sp_text_context_update_text_selection(tc);
1263                                         return TRUE;
1264                                     }
1265                                 }
1266                                 break;
1267                             default:
1268                                 break;
1269                         }
1271                         if (cursor_moved) {
1272                             if (!MOD__SHIFT)
1273                                 tc->text_sel_start = tc->text_sel_end;
1274                             if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1275                                 sp_text_context_update_cursor(tc);
1276                                 sp_text_context_update_text_selection(tc);
1277                             }
1278                             return TRUE;
1279                         }
1281                 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1282             } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1283                 // except up/down that are swallowed to prevent the zoom field from activation
1284                 if ((group0_keyval == GDK_Up    ||
1285                      group0_keyval == GDK_Down  ||
1286                      group0_keyval == GDK_KP_Up ||
1287                      group0_keyval == GDK_KP_Down )
1288                     && !MOD__CTRL_ONLY) {
1289                     return TRUE;
1290                 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1291                     if (tc->creating) {
1292                         tc->creating = 0;
1293                         if (tc->grabbed) {
1294                             sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1295                             tc->grabbed = NULL;
1296                         }
1297                         Inkscape::Rubberband::get(desktop)->stop();
1298                     }
1299                 }
1300             }
1301             break;
1302         }
1304         case GDK_KEY_RELEASE:
1305             if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1306                 return TRUE;
1307             }
1308             break;
1309         default:
1310             break;
1311     }
1313     // if nobody consumed it so far
1314     if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1315         return ((SPEventContextClass *) parent_class)->root_handler(event_context, event); // send event to parent
1316     } else {
1317         return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1318     }
1321 /**
1322  Attempts to paste system clipboard into the currently edited text, returns true on success
1323  */
1324 bool
1325 sp_text_paste_inline(SPEventContext *ec)
1327     if (!SP_IS_TEXT_CONTEXT(ec))
1328         return false;
1330     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1332     if ((tc->text) || (tc->nascent_object)) {
1333         // there is an active text object in this context, or a new object was just created
1335         Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1336         Glib::ustring const clip_text = refClipboard->wait_for_text();
1337         
1338         if (!clip_text.empty()) {
1339                 // Fix for 244940
1340                 // The XML standard defines the following as valid characters
1341                 // (Extensible Markup Language (XML) 1.0 (Fourth Edition) paragraph 2.2)
1342                 // char ::=     #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
1343                 // Since what comes in off the paste buffer will go right into XML, clean
1344                 // the text here.
1345                 Glib::ustring text(clip_text);
1346                 Glib::ustring::iterator itr = text.begin();
1347                 gunichar paste_string_uchar;
1349                 while(itr != text.end())
1350                 {
1351                     paste_string_uchar = *itr;
1353                     // Make sure we don't have a control character. We should really check
1354                     // for the whole range above... Add the rest of the invalid cases from
1355                     // above if we find additional issues
1356                     if(paste_string_uchar >= 0x00000020 ||
1357                        paste_string_uchar == 0x00000009 ||
1358                        paste_string_uchar == 0x0000000A ||
1359                        paste_string_uchar == 0x0000000D) {
1360                         itr++;
1361                     } else {
1362                         itr = text.erase(itr);
1363                     }
1364                 }
1365                 
1366             if (!tc->text) { // create text if none (i.e. if nascent_object)
1367                 sp_text_context_setup_text(tc);
1368                 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1369             }
1371             // using indices is slow in ustrings. Whatever.
1372             Glib::ustring::size_type begin = 0;
1373             for ( ; ; ) {
1374                 Glib::ustring::size_type end = text.find('\n', begin);
1375                 if (end == Glib::ustring::npos) {
1376                     if (begin != text.length())
1377                         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());
1378                     break;
1379                 }
1380                 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());
1381                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1382                 begin = end + 1;
1383             }
1384             sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
1385                              _("Paste text"));
1387             return true;
1388         }
1389     } // FIXME: else create and select a new object under cursor!
1391     return false;
1394 /**
1395  Gets the raw characters that comprise the currently selected text, converting line
1396  breaks into lf characters.
1397 */
1398 Glib::ustring
1399 sp_text_get_selected_text(SPEventContext const *ec)
1401     if (!SP_IS_TEXT_CONTEXT(ec))
1402         return "";
1403     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1404     if (tc->text == NULL)
1405         return "";
1407     return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1410 /**
1411  Deletes the currently selected characters. Returns false if there is no
1412  text selection currently.
1413 */
1414 bool sp_text_delete_selection(SPEventContext *ec)
1416     if (!SP_IS_TEXT_CONTEXT(ec))
1417         return false;
1418     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1419     if (tc->text == NULL)
1420         return false;
1422     if (tc->text_sel_start == tc->text_sel_end)
1423         return false;
1425     iterator_pair pair;
1426     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair);
1429     if (success) {
1430         tc->text_sel_start = tc->text_sel_end = pair.first;
1431     } else { // nothing deleted
1432         tc->text_sel_start = pair.first;
1433         tc->text_sel_end = pair.second;
1434     }
1436     sp_text_context_update_cursor(tc);
1437     sp_text_context_update_text_selection(tc);
1439     return true;
1442 /**
1443  * \param selection Should not be NULL.
1444  */
1445 static void
1446 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1448     g_assert(selection != NULL);
1450     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1452     if (ec->shape_knot_holder) { // destroy knotholder
1453         delete ec->shape_knot_holder;
1454         ec->shape_knot_holder = NULL;
1455     }
1457     if (ec->shape_repr) { // remove old listener
1458         sp_repr_remove_listener_by_data(ec->shape_repr, ec);
1459         Inkscape::GC::release(ec->shape_repr);
1460         ec->shape_repr = 0;
1461     }
1463     SPItem *item = selection->singleItem();
1464     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1465         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
1466         Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
1467         if (shape_repr) {
1468             ec->shape_repr = shape_repr;
1469             Inkscape::GC::anchor(shape_repr);
1470             sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
1471         }
1472     }
1474     if (tc->text && (item != tc->text)) {
1475         sp_text_context_forget_text(tc);
1476     }
1477     tc->text = NULL;
1479     if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1480         tc->text = item;
1481         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1482         if (layout)
1483             tc->text_sel_start = tc->text_sel_end = layout->end();
1484     } else {
1485         tc->text = NULL;
1486     }
1488     // we update cursor without scrolling, because this position may not be final;
1489     // item_handler moves cusros to the point of click immediately
1490     sp_text_context_update_cursor(tc, false);
1491     sp_text_context_update_text_selection(tc);
1494 static void
1495 sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, SPTextContext *tc)
1497     sp_text_context_update_cursor(tc);
1498     sp_text_context_update_text_selection(tc);
1501 static bool
1502 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1504     if (tc->text == NULL)
1505         return false;
1506     if (tc->text_sel_start == tc->text_sel_end)
1507         return false;    // will get picked up by the parent and applied to the whole text object
1509     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1510     sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1511                      _("Set text style"));
1512     sp_text_context_update_cursor(tc);
1513     sp_text_context_update_text_selection(tc);
1515     return true;
1518 static int
1519 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1521     if (tc->text == NULL)
1522         return QUERY_STYLE_NOTHING;
1523     const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1524     if (layout == NULL)
1525         return QUERY_STYLE_NOTHING;
1526     sp_text_context_validate_cursor_iterators(tc);
1528     GSList *styles_list = NULL;
1530     Inkscape::Text::Layout::iterator begin_it, end_it;
1531     if (tc->text_sel_start < tc->text_sel_end) {
1532         begin_it = tc->text_sel_start;
1533         end_it = tc->text_sel_end;
1534     } else {
1535         begin_it = tc->text_sel_end;
1536         end_it = tc->text_sel_start;
1537     }
1538     if (begin_it == end_it)
1539         if (!begin_it.prevCharacter())
1540             end_it.nextCharacter();
1541     for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1542         SPObject const *pos_obj = 0;
1543         void *rawptr = 0;
1544         layout->getSourceOfCharacter(it, &rawptr);
1545         if (!rawptr || !SP_IS_OBJECT(rawptr))
1546             continue;
1547         pos_obj = SP_OBJECT(rawptr);
1548         while (SP_IS_STRING(pos_obj) && SP_OBJECT_PARENT(pos_obj)) {
1549            pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
1550         }
1551         styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1552     }
1554     int result = sp_desktop_query_style_from_list (styles_list, style, property);
1556     g_slist_free(styles_list);
1557     return result;
1560 static void
1561 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1563     if (tc->text == NULL)
1564         return;
1565     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1566     if (layout) {     // undo can change the text length without us knowing it
1567         layout->validateIterator(&tc->text_sel_start);
1568         layout->validateIterator(&tc->text_sel_end);
1569     }
1572 static void
1573 sp_text_context_update_cursor(SPTextContext *tc,  bool scroll_to_see)
1575     GdkRectangle im_cursor = { 0, 0, 1, 1 };
1577     // due to interruptible display, tc may already be destroyed during a display update before
1578     // the cursor update (can't do both atomically, alas)
1579     if (!tc->desktop) return;
1581     if (tc->text) {
1582         Geom::Point p0, p1;
1583         sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1584         Geom::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1585         Geom::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1587         // scroll to show cursor
1588         if (scroll_to_see) {
1589             Geom::Point const center = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().midpoint();
1590             if (Geom::L2(d0 - center) > Geom::L2(d1 - center))
1591                 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1592                 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d0, 1.0);
1593             else
1594                 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d1, 1.0);
1595         }
1597         sp_canvas_item_show(tc->cursor);
1598         sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1600         /* fixme: ... need another transformation to get canvas widget coordinate space? */
1601         im_cursor.x = (int) floor(d0[Geom::X]);
1602         im_cursor.y = (int) floor(d0[Geom::Y]);
1603         im_cursor.width = (int) floor(d1[Geom::X]) - im_cursor.x;
1604         im_cursor.height = (int) floor(d1[Geom::Y]) - im_cursor.y;
1606         tc->show = TRUE;
1607         tc->phase = 1;
1609         if (SP_IS_FLOWTEXT(tc->text)) {
1610             SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1611             if (frame) {
1612                 sp_canvas_item_show(tc->frame);
1613                 Geom::OptRect frame_bbox = sp_item_bbox_desktop(frame);
1614                 if (frame_bbox) {
1615                     SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
1616                 }
1617             }
1618             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1619         } else {
1620             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1621         }
1623     } else {
1624         sp_canvas_item_hide(tc->cursor);
1625         sp_canvas_item_hide(tc->frame);
1626         tc->show = FALSE;
1627         if (!tc->nascent_object) {
1628             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
1629         }
1630     }
1632     if (tc->imc) {
1633         gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1634     }
1635     SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1638 static void sp_text_context_update_text_selection(SPTextContext *tc)
1640     // due to interruptible display, tc may already be destroyed during a display update before
1641     // the selection update (can't do both atomically, alas)
1642     if (!tc->desktop) return;
1644     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1645         sp_canvas_item_hide(*it);
1646         gtk_object_destroy(*it);
1647     }
1648     tc->text_selection_quads.clear();
1650     std::vector<Geom::Point> quads;
1651     if (tc->text != NULL)
1652         quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1653     for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1654         SPCanvasItem *quad_canvasitem;
1655         quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1656         // FIXME: make the color settable in prefs
1657         // for now, use semitrasparent blue, as cairo cannot do inversion :(
1658         sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x00777777);
1659         sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1660         sp_canvas_item_show(quad_canvasitem);
1661         tc->text_selection_quads.push_back(quad_canvasitem);
1662     }
1665 static gint
1666 sp_text_context_timeout(SPTextContext *tc)
1668     if (tc->show) {
1669         sp_canvas_item_show(tc->cursor);
1670         if (tc->phase) {
1671             tc->phase = 0;
1672             sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
1673         } else {
1674             tc->phase = 1;
1675             sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0xffffffff);
1676         }
1677     }
1679     return TRUE;
1682 static void
1683 sp_text_context_forget_text(SPTextContext *tc)
1685     if (! tc->text) return;
1686     SPItem *ti = tc->text;
1687     (void)ti;
1688     /* We have to set it to zero,
1689      * or selection changed signal messes everything up */
1690     tc->text = NULL;
1692 /* FIXME: this automatic deletion when nothing is inputted crashes the XML edittor and also crashes when duplicating an empty flowtext.
1693     So don't create an empty flowtext in the first place? Create it when first character is typed.
1694     */
1695 /*
1696     if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1697         Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1698         // the repr may already have been unparented
1699         // if we were called e.g. as the result of
1700         // an undo or the element being removed from
1701         // the XML editor
1702         if ( text_repr && sp_repr_parent(text_repr) ) {
1703             sp_repr_unparent(text_repr);
1704             sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1705                      _("Remove empty text"));
1706         }
1707     }
1708 */
1711 gint
1712 sptc_focus_in(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1714     gtk_im_context_focus_in(tc->imc);
1715     return FALSE;
1718 gint
1719 sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1721     gtk_im_context_focus_out(tc->imc);
1722     return FALSE;
1725 static void
1726 sptc_commit(GtkIMContext */*imc*/, gchar *string, SPTextContext *tc)
1728     if (!tc->text) {
1729         sp_text_context_setup_text(tc);
1730         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1731     }
1733     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1734     sp_text_context_update_cursor(tc);
1735     sp_text_context_update_text_selection(tc);
1737     sp_document_done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT,
1738                      _("Type text"));
1742 /*
1743   Local Variables:
1744   mode:c++
1745   c-file-style:"stroustrup"
1746   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1747   indent-tabs-mode:nil
1748   fill-column:99
1749   End:
1750 */
1751 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :