Code

Merge from fe-moved
[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(ec->desktop->currentRoot())->getRelativeTransform(ec->desktop->currentLayer());
515     text_item->updateRepr();
516     sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
517                      _("Create text"));
520 /**
521  * Insert the character indicated by tc.uni to replace the current selection,
522  * and reset tc.uni/tc.unipos to empty string.
523  *
524  * \pre tc.uni/tc.unipos non-empty.
525  */
526 static void
527 insert_uni_char(SPTextContext *const tc)
529     g_return_if_fail(tc->unipos
530                      && tc->unipos < sizeof(tc->uni)
531                      && tc->uni[tc->unipos] == '\0');
532     unsigned int uv;
533     sscanf(tc->uni, "%x", &uv);
534     tc->unipos = 0;
535     tc->uni[tc->unipos] = '\0';
537     if ( !g_unichar_isprint(static_cast<gunichar>(uv))
538          && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
539         // This may be due to bad input, so it goes to statusbar.
540         tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
541                                            _("Non-printable character"));
542     } else {
543         if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
544             sp_text_context_setup_text(tc);
545             tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
546         }
548         gchar u[10];
549         guint const len = g_unichar_to_utf8(uv, u);
550         u[len] = '\0';
552         tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
553         sp_text_context_update_cursor(tc);
554         sp_text_context_update_text_selection(tc);
555         sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM,
556                          _("Insert Unicode character"));
557     }
560 static void
561 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
563     unsigned int uv;
564     sscanf(hex, "%x", &uv);
565     if (!g_unichar_isprint((gunichar) uv)) {
566         uv = 0xfffd;
567     }
568     guint const len = g_unichar_to_utf8(uv, utf8);
569     utf8[len] = '\0';
572 static void
573 show_curr_uni_char(SPTextContext *const tc)
575     g_return_if_fail(tc->unipos < sizeof(tc->uni)
576                      && tc->uni[tc->unipos] == '\0');
577     if (tc->unipos) {
578         char utf8[10];
579         hex_to_printable_utf8_buf(tc->uni, utf8);
581         /* Status bar messages are in pango markup, so we need xml escaping. */
582         if (utf8[1] == '\0') {
583             switch(utf8[0]) {
584                 case '<': strcpy(utf8, "&lt;"); break;
585                 case '>': strcpy(utf8, "&gt;"); break;
586                 case '&': strcpy(utf8, "&amp;"); break;
587                 default: break;
588             }
589         }
590         tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
591                                           _("Unicode (<b>Enter</b> to finish): %s: %s"), tc->uni, utf8);
592     } else {
593         tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
594     }
597 static gint
598 sp_text_context_root_handler(SPEventContext *const event_context, GdkEvent *const event)
600     SPTextContext *const tc = SP_TEXT_CONTEXT(event_context);
602     SPDesktop *desktop = event_context->desktop;
604     sp_canvas_item_hide(tc->indicator);
606     sp_text_context_validate_cursor_iterators(tc);
608     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
609     event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
611     switch (event->type) {
612         case GDK_BUTTON_PRESS:
613             if (event->button.button == 1 && !event_context->space_panning) {
615                 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
616                     return TRUE;
617                 }
619                 // save drag origin
620                 event_context->xp = (gint) event->button.x;
621                 event_context->yp = (gint) event->button.y;
622                 event_context->within_tolerance = true;
624                 Geom::Point const button_pt(event->button.x, event->button.y);
625                 tc->p0 = desktop->w2d(button_pt);
626                 Inkscape::Rubberband::get(desktop)->start(desktop, tc->p0);
627                 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
628                                     GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
629                                     NULL, event->button.time);
630                 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
631                 tc->creating = 1;
633                 /* Processed */
634                 return TRUE;
635             }
636             break;
637         case GDK_MOTION_NOTIFY:
638             if (tc->over_text) {
639                 tc->over_text = 0;
640                 // update cursor and statusbar: we are not over a text object now
641                 event_context->cursor_shape = cursor_text_xpm;
642                 event_context->hot_x = 7;
643                 event_context->hot_y = 7;
644                 sp_event_context_update_cursor(event_context);
645                 desktop->event_context->defaultMessageContext()->clear();
646             }
648             if (tc->creating && event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
649                 if ( event_context->within_tolerance
650                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
651                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
652                     break; // do not drag if we're within tolerance from origin
653                 }
654                 // Once the user has moved farther than tolerance from the original location
655                 // (indicating they intend to draw, not click), then always process the
656                 // motion notify coordinates as given (no snapping back to origin)
657                 event_context->within_tolerance = false;
659                 Geom::Point const motion_pt(event->motion.x, event->motion.y);
660                 Geom::Point const p = desktop->w2d(motion_pt);
662                 Inkscape::Rubberband::get(desktop)->move(p);
663                 gobble_motion_events(GDK_BUTTON1_MASK);
665                 // status text
666                 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::X]), desktop->namedview->getDefaultMetric());
667                 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::Y]), desktop->namedview->getDefaultMetric());
668                 event_context->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Flowed text frame</b>: %s &#215; %s"), xs->str, ys->str);
669                 g_string_free(xs, FALSE);
670                 g_string_free(ys, FALSE);
672             }
673             break;
674         case GDK_BUTTON_RELEASE:
675             if (event->button.button == 1 && !event_context->space_panning) {
677                 if (tc->grabbed) {
678                     sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
679                     tc->grabbed = NULL;
680                 }
682                 Inkscape::Rubberband::get(desktop)->stop();
684                 if (tc->creating && event_context->within_tolerance) {
685                     /* Button 1, set X & Y & new item */
686                     sp_desktop_selection(desktop)->clear();
687                     Geom::Point dtp = desktop->w2d(Geom::Point(event->button.x, event->button.y));
688                     tc->pdoc = sp_desktop_dt2root_xy_point(desktop, dtp);
690                     tc->show = TRUE;
691                     tc->phase = 1;
692                     tc->nascent_object = 1; // new object was just created
694                     /* Cursor */
695                     sp_canvas_item_show(tc->cursor);
696                     // Cursor height is defined by the new text object's font size; it needs to be set
697                     // articifically here, for the text object does not exist yet:
698                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
699                     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), dtp, dtp + Geom::Point(0, cursor_height));
700                     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
702                     event_context->within_tolerance = false;
703                 } else if (tc->creating) {
704                     Geom::Point const button_pt(event->button.x, event->button.y);
705                     Geom::Point p1 = desktop->w2d(button_pt);
706                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
707                     if (fabs(p1[Geom::Y] - tc->p0[Geom::Y]) > cursor_height) {
708                         // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
709                         SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
710                         /* Set style */
711                         sp_desktop_apply_style_tool(desktop, SP_OBJECT_REPR(ft), "/tools/text", true);
712                         sp_desktop_selection(desktop)->set(ft);
713                         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
714                         sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
715                                          _("Create flowed text"));
716                     } else {
717                         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
718                     }
719                 }
720                 tc->creating = false;
721                 return TRUE;
722             }
723             break;
724         case GDK_KEY_PRESS: {
725             guint const group0_keyval = get_group0_keyval(&event->key);
727             if (group0_keyval == GDK_KP_Add ||
728                 group0_keyval == GDK_KP_Subtract) {
729                 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
730                     break; // otherwise pass on keypad +/- so they can zoom
731             }
733             if ((tc->text) || (tc->nascent_object)) {
734                 // there is an active text object in this context, or a new object was just created
736                 if (tc->unimode || !tc->imc
737                     || (MOD__CTRL && MOD__SHIFT)    // input methods tend to steal this for unimode,
738                                                     // but we have our own so make sure they don't swallow it
739                     || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
740                     //IM did not consume the key, or we're in unimode
742                         if (!MOD__CTRL_ONLY && tc->unimode) {
743                             /* TODO: ISO 14755 (section 3 Definitions) says that we should also
744                                accept the first 6 characters of alphabets other than the latin
745                                alphabet "if the Latin alphabet is not used".  The below is also
746                                reasonable (viz. hope that the user's keyboard includes latin
747                                characters and force latin interpretation -- just as we do for our
748                                keyboard shortcuts), but differs from the ISO 14755
749                                recommendation. */
750                             switch (group0_keyval) {
751                                 case GDK_space:
752                                 case GDK_KP_Space: {
753                                     if (tc->unipos) {
754                                         insert_uni_char(tc);
755                                     }
756                                     /* Stay in unimode. */
757                                     show_curr_uni_char(tc);
758                                     return TRUE;
759                                 }
761                                 case GDK_BackSpace: {
762                                     g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
763                                     if (tc->unipos) {
764                                         tc->uni[--tc->unipos] = '\0';
765                                     }
766                                     show_curr_uni_char(tc);
767                                     return TRUE;
768                                 }
770                                 case GDK_Return:
771                                 case GDK_KP_Enter: {
772                                     if (tc->unipos) {
773                                         insert_uni_char(tc);
774                                     }
775                                     /* Exit unimode. */
776                                     tc->unimode = false;
777                                     event_context->defaultMessageContext()->clear();
778                                     return TRUE;
779                                 }
781                                 case GDK_Escape: {
782                                     // Cancel unimode.
783                                     tc->unimode = false;
784                                     gtk_im_context_reset(tc->imc);
785                                     event_context->defaultMessageContext()->clear();
786                                     return TRUE;
787                                 }
789                                 case GDK_Shift_L:
790                                 case GDK_Shift_R:
791                                     break;
793                                 default: {
794                                     if (g_ascii_isxdigit(group0_keyval)) {
795                                         g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
796                                         tc->uni[tc->unipos++] = group0_keyval;
797                                         tc->uni[tc->unipos] = '\0';
798                                         if (tc->unipos == 8) {
799                                             /* This behaviour is partly to allow us to continue to
800                                                use a fixed-length buffer for tc->uni.  Reason for
801                                                choosing the number 8 is that it's the length of
802                                                ``canonical form'' mentioned in the ISO 14755 spec.
803                                                An advantage over choosing 6 is that it allows using
804                                                backspace for typos & misremembering when entering a
805                                                6-digit number. */
806                                             insert_uni_char(tc);
807                                         }
808                                         show_curr_uni_char(tc);
809                                         return TRUE;
810                                     } else {
811                                         /* The intent is to ignore but consume characters that could be
812                                            typos for hex digits.  Gtk seems to ignore & consume all
813                                            non-hex-digits, and we do similar here.  Though note that some
814                                            shortcuts (like keypad +/- for zoom) get processed before
815                                            reaching this code. */
816                                         return TRUE;
817                                     }
818                                 }
819                             }
820                         }
822                         Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
823                         Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
824                         bool cursor_moved = false;
825                         int screenlines = 1;
826                         if (tc->text) {
827                             double spacing = sp_te_get_average_linespacing(tc->text);
828                             Geom::Rect const d = desktop->get_display_area();
829                             screenlines = (int) floor(fabs(d.min()[Geom::Y] - d.max()[Geom::Y])/spacing) - 1;
830                             if (screenlines <= 0)
831                                 screenlines = 1;
832                         }
834                         /* Neither unimode nor IM consumed key; process text tool shortcuts */
835                         switch (group0_keyval) {
836                             case GDK_x:
837                             case GDK_X:
838                                 if (MOD__ALT_ONLY) {
839                                     desktop->setToolboxFocusTo ("altx-text");
840                                     return TRUE;
841                                 }
842                                 break;
843                             case GDK_space:
844                                 if (MOD__CTRL_ONLY) {
845                                     /* No-break space */
846                                     if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
847                                         sp_text_context_setup_text(tc);
848                                         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
849                                     }
850                                     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
851                                     sp_text_context_update_cursor(tc);
852                                     sp_text_context_update_text_selection(tc);
853                                     desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
854                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
855                                                      _("Insert no-break space"));
856                                     return TRUE;
857                                 }
858                                 break;
859                             case GDK_U:
860                             case GDK_u:
861                                 if (MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT)) {
862                                     if (tc->unimode) {
863                                         tc->unimode = false;
864                                         event_context->defaultMessageContext()->clear();
865                                     } else {
866                                         tc->unimode = true;
867                                         tc->unipos = 0;
868                                         event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
869                                     }
870                                     if (tc->imc) {
871                                         gtk_im_context_reset(tc->imc);
872                                     }
873                                     return TRUE;
874                                 }
875                                 break;
876                             case GDK_B:
877                             case GDK_b:
878                                 if (MOD__CTRL_ONLY && tc->text) {
879                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
880                                     SPCSSAttr *css = sp_repr_css_attr_new();
881                                     if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
882                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
883                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
884                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
885                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
886                                         sp_repr_css_set_property(css, "font-weight", "bold");
887                                     else
888                                         sp_repr_css_set_property(css, "font-weight", "normal");
889                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
890                                     sp_repr_css_attr_unref(css);
891                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
892                                                      _("Make bold"));
893                                     sp_text_context_update_cursor(tc);
894                                     sp_text_context_update_text_selection(tc);
895                                     return TRUE;
896                                 }
897                                 break;
898                             case GDK_I:
899                             case GDK_i:
900                                 if (MOD__CTRL_ONLY && tc->text) {
901                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
902                                     SPCSSAttr *css = sp_repr_css_attr_new();
903                                     if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL)
904                                         sp_repr_css_set_property(css, "font-style", "italic");
905                                     else
906                                         sp_repr_css_set_property(css, "font-style", "normal");
907                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
908                                     sp_repr_css_attr_unref(css);
909                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
910                                                      _("Make italic"));
911                                     sp_text_context_update_cursor(tc);
912                                     sp_text_context_update_text_selection(tc);
913                                     return TRUE;
914                                 }
915                                 break;
917                             case GDK_A:
918                             case GDK_a:
919                                 if (MOD__CTRL_ONLY && tc->text) {
920                                     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
921                                     if (layout) {
922                                         tc->text_sel_start = layout->begin();
923                                         tc->text_sel_end = layout->end();
924                                         sp_text_context_update_cursor(tc);
925                                         sp_text_context_update_text_selection(tc);
926                                         return TRUE;
927                                     }
928                                 }
929                                 break;
931                             case GDK_Return:
932                             case GDK_KP_Enter:
933                             {
934                                 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
935                                     sp_text_context_setup_text(tc);
936                                     tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
937                                 }
939                                 iterator_pair enter_pair;
940                                 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, enter_pair);
941                                 (void)success; // TODO cleanup
942                                 tc->text_sel_start = tc->text_sel_end = enter_pair.first;
944                                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
946                                 sp_text_context_update_cursor(tc);
947                                 sp_text_context_update_text_selection(tc);
948                                 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
949                                                  _("New line"));
950                                 return TRUE;
951                             }
952                             case GDK_BackSpace:
953                                 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
955                                     bool noSelection = false;
957                                         if (tc->text_sel_start == tc->text_sel_end) {
958                                         tc->text_sel_start.prevCursorPosition();
959                                         noSelection = true;
960                                     }
962                                         iterator_pair bspace_pair;
963                                         bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, bspace_pair);
965                                     if (noSelection) {
966                                         if (success) {
967                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
968                                         } else { // nothing deleted
969                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.second;
970                                         }
971                                     } else {
972                                         if (success) {
973                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
974                                         } else { // nothing deleted
975                                             tc->text_sel_start = bspace_pair.first;
976                                             tc->text_sel_end = bspace_pair.second;
977                                         }
978                                     }
980                                     sp_text_context_update_cursor(tc);
981                                     sp_text_context_update_text_selection(tc);
982                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
983                                                      _("Backspace"));
984                                 }
985                                 return TRUE;
986                             case GDK_Delete:
987                             case GDK_KP_Delete:
988                                 if (tc->text) {
989                                     bool noSelection = false;
991                                     if (tc->text_sel_start == tc->text_sel_end) {
992                                         tc->text_sel_end.nextCursorPosition();
993                                         noSelection = true;
994                                     }
996                                     iterator_pair del_pair;
997                                     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, del_pair);
999                                     if (noSelection) {
1000                                         tc->text_sel_start = tc->text_sel_end = del_pair.first;
1001                                     } else {
1002                                         if (success) {
1003                                             tc->text_sel_start = tc->text_sel_end = del_pair.first;
1004                                         } else { // nothing deleted
1005                                             tc->text_sel_start = del_pair.first;
1006                                             tc->text_sel_end = del_pair.second;
1007                                         }
1008                                     }
1011                                     sp_text_context_update_cursor(tc);
1012                                     sp_text_context_update_text_selection(tc);
1013                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
1014                                                      _("Delete"));
1015                                 }
1016                                 return TRUE;
1017                             case GDK_Left:
1018                             case GDK_KP_Left:
1019                             case GDK_KP_4:
1020                                 if (tc->text) {
1021                                     if (MOD__ALT) {
1022                                         gint mul = 1 + gobble_key_events(
1023                                             get_group0_keyval(&event->key), 0); // with any mask
1024                                         if (MOD__SHIFT)
1025                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-10, 0));
1026                                         else
1027                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-1, 0));
1028                                         sp_text_context_update_cursor(tc);
1029                                         sp_text_context_update_text_selection(tc);
1030                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT,
1031                                                                _("Kern to the left"));
1032                                     } else {
1033                                         if (MOD__CTRL) 
1034                                             tc->text_sel_end.cursorLeftWithControl();
1035                                         else
1036                                             tc->text_sel_end.cursorLeft();
1037                                         cursor_moved = true;
1038                                         break;
1039                                     }
1040                                 }
1041                                 return TRUE;
1042                             case GDK_Right:
1043                             case GDK_KP_Right:
1044                             case GDK_KP_6:
1045                                 if (tc->text) {
1046                                     if (MOD__ALT) {
1047                                         gint mul = 1 + gobble_key_events(
1048                                             get_group0_keyval(&event->key), 0); // with any mask
1049                                         if (MOD__SHIFT)
1050                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*10, 0));
1051                                         else
1052                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*1, 0));
1053                                         sp_text_context_update_cursor(tc);
1054                                         sp_text_context_update_text_selection(tc);
1055                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT,
1056                                                                _("Kern to the right"));
1057                                     } else {
1058                                         if (MOD__CTRL)
1059                                             tc->text_sel_end.cursorRightWithControl();
1060                                         else
1061                                             tc->text_sel_end.cursorRight();
1062                                         cursor_moved = true;
1063                                         break;
1064                                     }
1065                                 }
1066                                 return TRUE;
1067                             case GDK_Up:
1068                             case GDK_KP_Up:
1069                             case GDK_KP_8:
1070                                 if (tc->text) {
1071                                     if (MOD__ALT) {
1072                                         gint mul = 1 + gobble_key_events(
1073                                             get_group0_keyval(&event->key), 0); // with any mask
1074                                         if (MOD__SHIFT)
1075                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-10));
1076                                         else
1077                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-1));
1078                                         sp_text_context_update_cursor(tc);
1079                                         sp_text_context_update_text_selection(tc);
1080                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT,
1081                                                                _("Kern up"));
1083                                     } else {
1084                                         if (MOD__CTRL)
1085                                             tc->text_sel_end.cursorUpWithControl();
1086                                         else
1087                                             tc->text_sel_end.cursorUp();
1088                                         cursor_moved = true;
1089                                         break;
1090                                     }
1091                                 }
1092                                 return TRUE;
1093                             case GDK_Down:
1094                             case GDK_KP_Down:
1095                             case GDK_KP_2:
1096                                 if (tc->text) {
1097                                     if (MOD__ALT) {
1098                                         gint mul = 1 + gobble_key_events(
1099                                             get_group0_keyval(&event->key), 0); // with any mask
1100                                         if (MOD__SHIFT)
1101                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*10));
1102                                         else
1103                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*1));
1104                                         sp_text_context_update_cursor(tc);
1105                                         sp_text_context_update_text_selection(tc);
1106                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT,
1107                                                                _("Kern down"));
1109                                     } else {
1110                                         if (MOD__CTRL)
1111                                             tc->text_sel_end.cursorDownWithControl();
1112                                         else
1113                                             tc->text_sel_end.cursorDown();
1114                                         cursor_moved = true;
1115                                         break;
1116                                     }
1117                                 }
1118                                 return TRUE;
1119                             case GDK_Home:
1120                             case GDK_KP_Home:
1121                                 if (tc->text) {
1122                                     if (MOD__CTRL)
1123                                         tc->text_sel_end.thisStartOfShape();
1124                                     else
1125                                         tc->text_sel_end.thisStartOfLine();
1126                                     cursor_moved = true;
1127                                     break;
1128                                 }
1129                                 return TRUE;
1130                             case GDK_End:
1131                             case GDK_KP_End:
1132                                 if (tc->text) {
1133                                     if (MOD__CTRL)
1134                                         tc->text_sel_end.nextStartOfShape();
1135                                     else
1136                                         tc->text_sel_end.thisEndOfLine();
1137                                     cursor_moved = true;
1138                                     break;
1139                                 }
1140                                 return TRUE;
1141                             case GDK_Page_Down:
1142                             case GDK_KP_Page_Down:
1143                                 if (tc->text) {
1144                                     tc->text_sel_end.cursorDown(screenlines);
1145                                     cursor_moved = true;
1146                                     break;
1147                                 }
1148                                 return TRUE;
1149                             case GDK_Page_Up:
1150                             case GDK_KP_Page_Up:
1151                                 if (tc->text) {
1152                                     tc->text_sel_end.cursorUp(screenlines);
1153                                     cursor_moved = true;
1154                                     break;
1155                                 }
1156                                 return TRUE;
1157                             case GDK_Escape:
1158                                 if (tc->creating) {
1159                                     tc->creating = 0;
1160                                     if (tc->grabbed) {
1161                                         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1162                                         tc->grabbed = NULL;
1163                                     }
1164                                     Inkscape::Rubberband::get(desktop)->stop();
1165                                 } else {
1166                                     sp_desktop_selection(desktop)->clear();
1167                                 }
1168                                 tc->nascent_object = FALSE;
1169                                 return TRUE;
1170                             case GDK_bracketleft:
1171                                 if (tc->text) {
1172                                     if (MOD__ALT || MOD__CTRL) {
1173                                         if (MOD__ALT) {
1174                                             if (MOD__SHIFT) {
1175                                                 // FIXME: alt+shift+[] does not work, don't know why
1176                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1177                                             } else {
1178                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1179                                             }
1180                                         } else {
1181                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -90);
1182                                         }
1183                                         sp_document_maybe_done(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT,
1184                                                                _("Rotate counterclockwise"));
1185                                         sp_text_context_update_cursor(tc);
1186                                         sp_text_context_update_text_selection(tc);
1187                                         return TRUE;
1188                                     }
1189                                 }
1190                                 break;
1191                             case GDK_bracketright:
1192                                 if (tc->text) {
1193                                     if (MOD__ALT || MOD__CTRL) {
1194                                         if (MOD__ALT) {
1195                                             if (MOD__SHIFT) {
1196                                                 // FIXME: alt+shift+[] does not work, don't know why
1197                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1198                                             } else {
1199                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1200                                             }
1201                                         } else {
1202                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 90);
1203                                         }
1204                                         sp_document_maybe_done(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT,
1205                                                                 _("Rotate clockwise"));
1206                                         sp_text_context_update_cursor(tc);
1207                                         sp_text_context_update_text_selection(tc);
1208                                         return TRUE;
1209                                     }
1210                                 }
1211                                 break;
1212                             case GDK_less:
1213                             case GDK_comma:
1214                                 if (tc->text) {
1215                                     if (MOD__ALT) {
1216                                         if (MOD__CTRL) {
1217                                             if (MOD__SHIFT)
1218                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1219                                             else
1220                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1221                                             sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT,
1222                                                                     _("Contract line spacing"));
1224                                         } else {
1225                                             if (MOD__SHIFT)
1226                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1227                                             else
1228                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1229                                             sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT,
1230                                                                     _("Contract letter spacing"));
1232                                         }
1233                                         sp_text_context_update_cursor(tc);
1234                                         sp_text_context_update_text_selection(tc);
1235                                         return TRUE;
1236                                     }
1237                                 }
1238                                 break;
1239                             case GDK_greater:
1240                             case GDK_period:
1241                                 if (tc->text) {
1242                                     if (MOD__ALT) {
1243                                         if (MOD__CTRL) {
1244                                             if (MOD__SHIFT)
1245                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1246                                             else
1247                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1248                                             sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT,
1249                                                                     _("Expand line spacing"));
1251                                         } else {
1252                                             if (MOD__SHIFT)
1253                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1254                                             else
1255                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1256                                             sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT,
1257                                                                     _("Expand letter spacing"));
1259                                         }
1260                                         sp_text_context_update_cursor(tc);
1261                                         sp_text_context_update_text_selection(tc);
1262                                         return TRUE;
1263                                     }
1264                                 }
1265                                 break;
1266                             default:
1267                                 break;
1268                         }
1270                         if (cursor_moved) {
1271                             if (!MOD__SHIFT)
1272                                 tc->text_sel_start = tc->text_sel_end;
1273                             if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1274                                 sp_text_context_update_cursor(tc);
1275                                 sp_text_context_update_text_selection(tc);
1276                             }
1277                             return TRUE;
1278                         }
1280                 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1281             } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1282                 // except up/down that are swallowed to prevent the zoom field from activation
1283                 if ((group0_keyval == GDK_Up    ||
1284                      group0_keyval == GDK_Down  ||
1285                      group0_keyval == GDK_KP_Up ||
1286                      group0_keyval == GDK_KP_Down )
1287                     && !MOD__CTRL_ONLY) {
1288                     return TRUE;
1289                 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1290                     if (tc->creating) {
1291                         tc->creating = 0;
1292                         if (tc->grabbed) {
1293                             sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1294                             tc->grabbed = NULL;
1295                         }
1296                         Inkscape::Rubberband::get(desktop)->stop();
1297                     }
1298                 }
1299             }
1300             break;
1301         }
1303         case GDK_KEY_RELEASE:
1304             if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1305                 return TRUE;
1306             }
1307             break;
1308         default:
1309             break;
1310     }
1312     // if nobody consumed it so far
1313     if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1314         return ((SPEventContextClass *) parent_class)->root_handler(event_context, event); // send event to parent
1315     } else {
1316         return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1317     }
1320 /**
1321  Attempts to paste system clipboard into the currently edited text, returns true on success
1322  */
1323 bool
1324 sp_text_paste_inline(SPEventContext *ec)
1326     if (!SP_IS_TEXT_CONTEXT(ec))
1327         return false;
1329     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1331     if ((tc->text) || (tc->nascent_object)) {
1332         // there is an active text object in this context, or a new object was just created
1334         Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1335         Glib::ustring const clip_text = refClipboard->wait_for_text();
1336         
1337         if (!clip_text.empty()) {
1338                 // Fix for 244940
1339                 // The XML standard defines the following as valid characters
1340                 // (Extensible Markup Language (XML) 1.0 (Fourth Edition) paragraph 2.2)
1341                 // char ::=     #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
1342                 // Since what comes in off the paste buffer will go right into XML, clean
1343                 // the text here.
1344                 Glib::ustring text(clip_text);
1345                 Glib::ustring::iterator itr = text.begin();
1346                 gunichar paste_string_uchar;
1348                 while(itr != text.end())
1349                 {
1350                     paste_string_uchar = *itr;
1352                     // Make sure we don't have a control character. We should really check
1353                     // for the whole range above... Add the rest of the invalid cases from
1354                     // above if we find additional issues
1355                     if(paste_string_uchar >= 0x00000020 ||
1356                        paste_string_uchar == 0x00000009 ||
1357                        paste_string_uchar == 0x0000000A ||
1358                        paste_string_uchar == 0x0000000D) {
1359                         itr++;
1360                     } else {
1361                         itr = text.erase(itr);
1362                     }
1363                 }
1364                 
1365             if (!tc->text) { // create text if none (i.e. if nascent_object)
1366                 sp_text_context_setup_text(tc);
1367                 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1368             }
1370             // using indices is slow in ustrings. Whatever.
1371             Glib::ustring::size_type begin = 0;
1372             for ( ; ; ) {
1373                 Glib::ustring::size_type end = text.find('\n', begin);
1374                 if (end == Glib::ustring::npos) {
1375                     if (begin != text.length())
1376                         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());
1377                     break;
1378                 }
1379                 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());
1380                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1381                 begin = end + 1;
1382             }
1383             sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
1384                              _("Paste text"));
1386             return true;
1387         }
1388     } // FIXME: else create and select a new object under cursor!
1390     return false;
1393 /**
1394  Gets the raw characters that comprise the currently selected text, converting line
1395  breaks into lf characters.
1396 */
1397 Glib::ustring
1398 sp_text_get_selected_text(SPEventContext const *ec)
1400     if (!SP_IS_TEXT_CONTEXT(ec))
1401         return "";
1402     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1403     if (tc->text == NULL)
1404         return "";
1406     return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1409 /**
1410  Deletes the currently selected characters. Returns false if there is no
1411  text selection currently.
1412 */
1413 bool sp_text_delete_selection(SPEventContext *ec)
1415     if (!SP_IS_TEXT_CONTEXT(ec))
1416         return false;
1417     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1418     if (tc->text == NULL)
1419         return false;
1421     if (tc->text_sel_start == tc->text_sel_end)
1422         return false;
1424     iterator_pair pair;
1425     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair);
1428     if (success) {
1429         tc->text_sel_start = tc->text_sel_end = pair.first;
1430     } else { // nothing deleted
1431         tc->text_sel_start = pair.first;
1432         tc->text_sel_end = pair.second;
1433     }
1435     sp_text_context_update_cursor(tc);
1436     sp_text_context_update_text_selection(tc);
1438     return true;
1441 /**
1442  * \param selection Should not be NULL.
1443  */
1444 static void
1445 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1447     g_assert(selection != NULL);
1449     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1451     if (ec->shape_knot_holder) { // destroy knotholder
1452         delete ec->shape_knot_holder;
1453         ec->shape_knot_holder = NULL;
1454     }
1456     if (ec->shape_repr) { // remove old listener
1457         sp_repr_remove_listener_by_data(ec->shape_repr, ec);
1458         Inkscape::GC::release(ec->shape_repr);
1459         ec->shape_repr = 0;
1460     }
1462     SPItem *item = selection->singleItem();
1463     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1464         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
1465         Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
1466         if (shape_repr) {
1467             ec->shape_repr = shape_repr;
1468             Inkscape::GC::anchor(shape_repr);
1469             sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
1470         }
1471     }
1473     if (tc->text && (item != tc->text)) {
1474         sp_text_context_forget_text(tc);
1475     }
1476     tc->text = NULL;
1478     if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1479         tc->text = item;
1480         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1481         if (layout)
1482             tc->text_sel_start = tc->text_sel_end = layout->end();
1483     } else {
1484         tc->text = NULL;
1485     }
1487     // we update cursor without scrolling, because this position may not be final;
1488     // item_handler moves cusros to the point of click immediately
1489     sp_text_context_update_cursor(tc, false);
1490     sp_text_context_update_text_selection(tc);
1493 static void
1494 sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, SPTextContext *tc)
1496     sp_text_context_update_cursor(tc);
1497     sp_text_context_update_text_selection(tc);
1500 static bool
1501 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1503     if (tc->text == NULL)
1504         return false;
1505     if (tc->text_sel_start == tc->text_sel_end)
1506         return false;    // will get picked up by the parent and applied to the whole text object
1508     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1509     sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1510                      _("Set text style"));
1511     sp_text_context_update_cursor(tc);
1512     sp_text_context_update_text_selection(tc);
1514     return true;
1517 static int
1518 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1520     if (tc->text == NULL)
1521         return QUERY_STYLE_NOTHING;
1522     const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1523     if (layout == NULL)
1524         return QUERY_STYLE_NOTHING;
1525     sp_text_context_validate_cursor_iterators(tc);
1527     GSList *styles_list = NULL;
1529     Inkscape::Text::Layout::iterator begin_it, end_it;
1530     if (tc->text_sel_start < tc->text_sel_end) {
1531         begin_it = tc->text_sel_start;
1532         end_it = tc->text_sel_end;
1533     } else {
1534         begin_it = tc->text_sel_end;
1535         end_it = tc->text_sel_start;
1536     }
1537     if (begin_it == end_it)
1538         if (!begin_it.prevCharacter())
1539             end_it.nextCharacter();
1540     for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1541         SPObject const *pos_obj = 0;
1542         void *rawptr = 0;
1543         layout->getSourceOfCharacter(it, &rawptr);
1544         if (!rawptr || !SP_IS_OBJECT(rawptr))
1545             continue;
1546         pos_obj = SP_OBJECT(rawptr);
1547         while (SP_IS_STRING(pos_obj) && SP_OBJECT_PARENT(pos_obj)) {
1548            pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
1549         }
1550         styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1551     }
1553     int result = sp_desktop_query_style_from_list (styles_list, style, property);
1555     g_slist_free(styles_list);
1556     return result;
1559 static void
1560 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1562     if (tc->text == NULL)
1563         return;
1564     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1565     if (layout) {     // undo can change the text length without us knowing it
1566         layout->validateIterator(&tc->text_sel_start);
1567         layout->validateIterator(&tc->text_sel_end);
1568     }
1571 static void
1572 sp_text_context_update_cursor(SPTextContext *tc,  bool scroll_to_see)
1574     GdkRectangle im_cursor = { 0, 0, 1, 1 };
1576     // due to interruptible display, tc may already be destroyed during a display update before
1577     // the cursor update (can't do both atomically, alas)
1578     if (!tc->desktop) return;
1580     if (tc->text) {
1581         Geom::Point p0, p1;
1582         sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1583         Geom::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1584         Geom::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1586         // scroll to show cursor
1587         if (scroll_to_see) {
1588             Geom::Point const center = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().midpoint();
1589             if (Geom::L2(d0 - center) > Geom::L2(d1 - center))
1590                 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1591                 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d0, 1.0);
1592             else
1593                 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d1, 1.0);
1594         }
1596         sp_canvas_item_show(tc->cursor);
1597         sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1599         /* fixme: ... need another transformation to get canvas widget coordinate space? */
1600         im_cursor.x = (int) floor(d0[Geom::X]);
1601         im_cursor.y = (int) floor(d0[Geom::Y]);
1602         im_cursor.width = (int) floor(d1[Geom::X]) - im_cursor.x;
1603         im_cursor.height = (int) floor(d1[Geom::Y]) - im_cursor.y;
1605         tc->show = TRUE;
1606         tc->phase = 1;
1608         if (SP_IS_FLOWTEXT(tc->text)) {
1609             SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1610             if (frame) {
1611                 sp_canvas_item_show(tc->frame);
1612                 Geom::OptRect frame_bbox = sp_item_bbox_desktop(frame);
1613                 if (frame_bbox) {
1614                     SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
1615                 }
1616             }
1617             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1618         } else {
1619             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1620         }
1622     } else {
1623         sp_canvas_item_hide(tc->cursor);
1624         sp_canvas_item_hide(tc->frame);
1625         tc->show = FALSE;
1626         if (!tc->nascent_object) {
1627             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
1628         }
1629     }
1631     if (tc->imc) {
1632         gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1633     }
1634     SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1637 static void sp_text_context_update_text_selection(SPTextContext *tc)
1639     // due to interruptible display, tc may already be destroyed during a display update before
1640     // the selection update (can't do both atomically, alas)
1641     if (!tc->desktop) return;
1643     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1644         sp_canvas_item_hide(*it);
1645         gtk_object_destroy(*it);
1646     }
1647     tc->text_selection_quads.clear();
1649     std::vector<Geom::Point> quads;
1650     if (tc->text != NULL)
1651         quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1652     for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1653         SPCanvasItem *quad_canvasitem;
1654         quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1655         // FIXME: make the color settable in prefs
1656         // for now, use semitrasparent blue, as cairo cannot do inversion :(
1657         sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x00777777);
1658         sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1659         sp_canvas_item_show(quad_canvasitem);
1660         tc->text_selection_quads.push_back(quad_canvasitem);
1661     }
1664 static gint
1665 sp_text_context_timeout(SPTextContext *tc)
1667     if (tc->show) {
1668         sp_canvas_item_show(tc->cursor);
1669         if (tc->phase) {
1670             tc->phase = 0;
1671             sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
1672         } else {
1673             tc->phase = 1;
1674             sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0xffffffff);
1675         }
1676     }
1678     return TRUE;
1681 static void
1682 sp_text_context_forget_text(SPTextContext *tc)
1684     if (! tc->text) return;
1685     SPItem *ti = tc->text;
1686     (void)ti;
1687     /* We have to set it to zero,
1688      * or selection changed signal messes everything up */
1689     tc->text = NULL;
1691 /* FIXME: this automatic deletion when nothing is inputted crashes the XML edittor and also crashes when duplicating an empty flowtext.
1692     So don't create an empty flowtext in the first place? Create it when first character is typed.
1693     */
1694 /*
1695     if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1696         Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1697         // the repr may already have been unparented
1698         // if we were called e.g. as the result of
1699         // an undo or the element being removed from
1700         // the XML editor
1701         if ( text_repr && sp_repr_parent(text_repr) ) {
1702             sp_repr_unparent(text_repr);
1703             sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1704                      _("Remove empty text"));
1705         }
1706     }
1707 */
1710 gint
1711 sptc_focus_in(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1713     gtk_im_context_focus_in(tc->imc);
1714     return FALSE;
1717 gint
1718 sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1720     gtk_im_context_focus_out(tc->imc);
1721     return FALSE;
1724 static void
1725 sptc_commit(GtkIMContext */*imc*/, gchar *string, SPTextContext *tc)
1727     if (!tc->text) {
1728         sp_text_context_setup_text(tc);
1729         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1730     }
1732     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1733     sp_text_context_update_cursor(tc);
1734     sp_text_context_update_text_selection(tc);
1736     sp_document_done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT,
1737                      _("Type text"));
1741 /*
1742   Local Variables:
1743   mode:c++
1744   c-file-style:"stroustrup"
1745   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1746   indent-tabs-mode:nil
1747   fill-column:99
1748   End:
1749 */
1750 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :