Code

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