Code

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