Code

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