Code

0410ab3b4679a7eb830505b5d8924fc7cb562ede
[inkscape.git] / src / text-context.cpp
1 #define __SP_TEXT_CONTEXT_C__
3 /*
4  * SPTextContext
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   bulia byak <buliabyak@users.sf.net>
9  *
10  * Copyright (C) 1999-2005 authors
11  * Copyright (C) 2001 Ximian, Inc.
12  *
13  * Released under GNU GPL, read the file 'COPYING' for more information
14  */
16 #ifdef HAVE_CONFIG_H
17 # include <config.h>
18 #endif
20 #include <gdk/gdkkeysyms.h>
21 #include <gtk/gtkmain.h>
22 #include <display/sp-ctrlline.h>
23 #include <display/sodipodi-ctrlrect.h>
24 #include <display/sp-ctrlquadr.h>
25 #include <gtk/gtkimmulticontext.h>
26 #include <gtkmm/clipboard.h>
28 #include "macros.h"
29 #include "sp-text.h"
30 #include "sp-flowtext.h"
31 #include "document.h"
32 #include "sp-namedview.h"
33 #include "style.h"
34 #include "selection.h"
35 #include "desktop.h"
36 #include "desktop-style.h"
37 #include "desktop-handles.h"
38 #include "desktop-affine.h"
39 #include "message-stack.h"
40 #include "message-context.h"
41 #include "pixmaps/cursor-text.xpm"
42 #include "pixmaps/cursor-text-insert.xpm"
43 #include <glibmm/i18n.h>
44 #include "object-edit.h"
45 #include "xml/repr.h"
46 #include "xml/node-event-vector.h"
47 #include "prefs-utils.h"
48 #include "rubberband.h"
49 #include "sp-metrics.h"
50 #include "context-fns.h"
51 #include "verbs.h"
53 #include "text-editing.h"
55 #include "text-context.h"
58 static void sp_text_context_class_init(SPTextContextClass *klass);
59 static void sp_text_context_init(SPTextContext *text_context);
60 static void sp_text_context_dispose(GObject *obj);
62 static void sp_text_context_setup(SPEventContext *ec);
63 static void sp_text_context_finish(SPEventContext *ec);
64 static gint sp_text_context_root_handler(SPEventContext *event_context, GdkEvent *event);
65 static gint sp_text_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
67 static void sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc);
68 static void sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc);
69 static bool sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc);
70 static int sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc);
72 static void sp_text_context_validate_cursor_iterators(SPTextContext *tc);
73 static void sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see = true);
74 static void sp_text_context_update_text_selection(SPTextContext *tc);
75 static gint sp_text_context_timeout(SPTextContext *tc);
76 static void sp_text_context_forget_text(SPTextContext *tc);
78 static gint sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc);
79 static gint sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc);
80 static void sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc);
82 static SPEventContextClass *parent_class;
84 GType
85 sp_text_context_get_type()
86 {
87     static GType type = 0;
88     if (!type) {
89         GTypeInfo info = {
90             sizeof(SPTextContextClass),
91             NULL, NULL,
92             (GClassInitFunc) sp_text_context_class_init,
93             NULL, NULL,
94             sizeof(SPTextContext),
95             4,
96             (GInstanceInitFunc) sp_text_context_init,
97             NULL,   /* value_table */
98         };
99         type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPTextContext", &info, (GTypeFlags)0);
100     }
101     return type;
104 static void
105 sp_text_context_class_init(SPTextContextClass *klass)
107     GObjectClass *object_class=(GObjectClass *)klass;
108     SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
110     parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
112     object_class->dispose = sp_text_context_dispose;
114     event_context_class->setup = sp_text_context_setup;
115     event_context_class->finish = sp_text_context_finish;
116     event_context_class->root_handler = sp_text_context_root_handler;
117     event_context_class->item_handler = sp_text_context_item_handler;
120 static void
121 sp_text_context_init(SPTextContext *tc)
123     SPEventContext *event_context = SP_EVENT_CONTEXT(tc);
125     event_context->cursor_shape = cursor_text_xpm;
126     event_context->hot_x = 7;
127     event_context->hot_y = 7;
129     event_context->xp = 0;
130     event_context->yp = 0;
131     event_context->tolerance = 0;
132     event_context->within_tolerance = false;
134     event_context->shape_repr = NULL;
135     event_context->shape_knot_holder = NULL;
137     tc->imc = NULL;
139     tc->text = NULL;
140     tc->pdoc = NR::Point(0, 0);
141     new (&tc->text_sel_start) Inkscape::Text::Layout::iterator();
142     new (&tc->text_sel_end) Inkscape::Text::Layout::iterator();
143     new (&tc->text_selection_quads) std::vector<SPCanvasItem*>();
145     tc->unimode = false;
147     tc->cursor = NULL;
148     tc->indicator = NULL;
149     tc->frame = NULL;
150     tc->grabbed = NULL;
151     tc->timeout = 0;
152     tc->show = FALSE;
153     tc->phase = 0;
154     tc->nascent_object = 0;
155     tc->over_text = 0;
156     tc->dragging = 0;
157     tc->creating = 0;
159     new (&tc->sel_changed_connection) sigc::connection();
160     new (&tc->sel_modified_connection) sigc::connection();
161     new (&tc->style_set_connection) sigc::connection();
162     new (&tc->style_query_connection) sigc::connection();
165 static void
166 sp_text_context_dispose(GObject *obj)
168     SPTextContext *tc = SP_TEXT_CONTEXT(obj);
169     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
170     tc->style_query_connection.~connection();
171     tc->style_set_connection.~connection();
172     tc->sel_changed_connection.~connection();
173     tc->sel_modified_connection.~connection();
174     tc->text_sel_end.~iterator();
175     tc->text_sel_start.~iterator();
176     tc->text_selection_quads.~vector();
177     if (G_OBJECT_CLASS(parent_class)->dispose) {
178         G_OBJECT_CLASS(parent_class)->dispose(obj);
179     }
180     if (tc->grabbed) {
181         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
182         tc->grabbed = NULL;
183     }
185     Inkscape::Rubberband::get()->stop();
187     if (ec->shape_knot_holder) {
188         sp_knot_holder_destroy(ec->shape_knot_holder);
189         ec->shape_knot_holder = NULL;
190     }
191     if (ec->shape_repr) { // remove old listener
192         sp_repr_remove_listener_by_data(ec->shape_repr, ec);
193         Inkscape::GC::release(ec->shape_repr);
194         ec->shape_repr = 0;
195     }
198 static Inkscape::XML::NodeEventVector ec_shape_repr_events = {
199     NULL, /* child_added */
200     NULL, /* child_removed */
201     ec_shape_event_attr_changed,
202     NULL, /* content_changed */
203     NULL  /* order_changed */
204 };
206 static void
207 sp_text_context_setup(SPEventContext *ec)
209     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
210     SPDesktop *desktop = ec->desktop;
212     tc->cursor = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLLINE, NULL);
213     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), 100, 0, 100, 100);
214     sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
215     sp_canvas_item_hide(tc->cursor);
217     tc->indicator = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
218     SP_CTRLRECT(tc->indicator)->setRectangle(NR::Rect(NR::Point(0, 0), NR::Point(100, 100)));
219     SP_CTRLRECT(tc->indicator)->setColor(0x0000ff7f, false, 0);
220     sp_canvas_item_hide(tc->indicator);
222     tc->frame = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
223     SP_CTRLRECT(tc->frame)->setRectangle(NR::Rect(NR::Point(0, 0), NR::Point(100, 100)));
224     SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0);
225     sp_canvas_item_hide(tc->frame);
227     tc->timeout = gtk_timeout_add(250, (GtkFunction) sp_text_context_timeout, ec);
229     tc->imc = gtk_im_multicontext_new();
230     if (tc->imc) {
231         GtkWidget *canvas = GTK_WIDGET(sp_desktop_canvas(desktop));
233         /* im preedit handling is very broken in inkscape for
234          * multi-byte characters.  See bug 1086769.
235          * We need to let the IM handle the preediting, and
236          * just take in the characters when they're finished being
237          * entered.
238          */
239         gtk_im_context_set_use_preedit(tc->imc, FALSE);
240         gtk_im_context_set_client_window(tc->imc, canvas->window);
242         g_signal_connect(G_OBJECT(canvas), "focus_in_event", G_CALLBACK(sptc_focus_in), tc);
243         g_signal_connect(G_OBJECT(canvas), "focus_out_event", G_CALLBACK(sptc_focus_out), tc);
244         g_signal_connect(G_OBJECT(tc->imc), "commit", G_CALLBACK(sptc_commit), tc);
246         if (GTK_WIDGET_HAS_FOCUS(canvas)) {
247             sptc_focus_in(canvas, NULL, tc);
248         }
249     }
251     if (((SPEventContextClass *) parent_class)->setup)
252         ((SPEventContextClass *) parent_class)->setup(ec);
254     SPItem *item = sp_desktop_selection(ec->desktop)->singleItem();
255     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
256         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
257         Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
258         if (shape_repr) {
259             ec->shape_repr = shape_repr;
260             Inkscape::GC::anchor(shape_repr);
261             sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
262         }
263     }
265     tc->sel_changed_connection = sp_desktop_selection(desktop)->connectChanged(
266         sigc::bind(sigc::ptr_fun(&sp_text_context_selection_changed), tc)
267         );
268     tc->sel_modified_connection = sp_desktop_selection(desktop)->connectModified(
269         sigc::bind(sigc::ptr_fun(&sp_text_context_selection_modified), tc)
270         );
271     tc->style_set_connection = desktop->connectSetStyle(
272         sigc::bind(sigc::ptr_fun(&sp_text_context_style_set), tc)
273         );
274     tc->style_query_connection = desktop->connectQueryStyle(
275         sigc::bind(sigc::ptr_fun(&sp_text_context_style_query), tc)
276         );
278     sp_text_context_selection_changed(sp_desktop_selection(desktop), tc);
280     if (prefs_get_int_attribute("tools.text", "selcue", 0) != 0) {
281         ec->enableSelectionCue();
282     }
283     if (prefs_get_int_attribute("tools.text", "gradientdrag", 0) != 0) {
284         ec->enableGrDrag();
285     }
288 static void
289 sp_text_context_finish(SPEventContext *ec)
291     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
293     if (ec->desktop) {
294         sp_signal_disconnect_by_data(sp_desktop_canvas(ec->desktop), tc);
295     }
297     ec->enableGrDrag(false);
299     tc->style_set_connection.disconnect();
300     tc->style_query_connection.disconnect();
301     tc->sel_changed_connection.disconnect();
302     tc->sel_modified_connection.disconnect();
304     sp_text_context_forget_text(SP_TEXT_CONTEXT(ec));
306     if (tc->imc) {
307         g_object_unref(G_OBJECT(tc->imc));
308         tc->imc = NULL;
309     }
311     if (tc->timeout) {
312         gtk_timeout_remove(tc->timeout);
313         tc->timeout = 0;
314     }
316     if (tc->cursor) {
317         gtk_object_destroy(GTK_OBJECT(tc->cursor));
318         tc->cursor = NULL;
319     }
321     if (tc->indicator) {
322         gtk_object_destroy(GTK_OBJECT(tc->indicator));
323         tc->indicator = NULL;
324     }
326     if (tc->frame) {
327         gtk_object_destroy(GTK_OBJECT(tc->frame));
328         tc->frame = NULL;
329     }
331     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ;
332          it != tc->text_selection_quads.end() ; ++it) {
333         sp_canvas_item_hide(*it);
334         gtk_object_destroy(*it);
335     }
336     tc->text_selection_quads.clear();
340 static gint
341 sp_text_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event)
343     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
344     SPDesktop *desktop = ec->desktop;
345     SPItem *item_ungrouped;
347     gint ret = FALSE;
349     sp_text_context_validate_cursor_iterators(tc);
351     switch (event->type) {
352         case GDK_BUTTON_PRESS:
353             if (event->button.button == 1) {
354                 // find out clicked item, disregarding groups
355                 item_ungrouped = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE);
356                 if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
357                     sp_desktop_selection(ec->desktop)->set(item_ungrouped);
358                     if (tc->text) {
359                         // find out click point in document coordinates
360                         NR::Point p = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
361                         // set the cursor closest to that point
362                         tc->text_sel_start = tc->text_sel_end = sp_te_get_position_by_coords(tc->text, p);
363                         // update display
364                         sp_text_context_update_cursor(tc);
365                         sp_text_context_update_text_selection(tc);
366                         tc->dragging = 1;
367                     }
368                     ret = TRUE;
369                 }
370             }
371             break;
372         case GDK_2BUTTON_PRESS:
373             if (event->button.button == 1 && tc->text) {
374                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
375                 if (layout) {
376                     if (!layout->isStartOfWord(tc->text_sel_start))
377                         tc->text_sel_start.prevStartOfWord();
378                     if (!layout->isEndOfWord(tc->text_sel_end))
379                         tc->text_sel_end.nextEndOfWord();
380                     sp_text_context_update_cursor(tc);
381                     sp_text_context_update_text_selection(tc);
382                     tc->dragging = 2;
383                     ret = TRUE;
384                 }
385             }
386             break;
387         case GDK_3BUTTON_PRESS:
388             if (event->button.button == 1 && tc->text) {
389                 tc->text_sel_start.thisStartOfLine();
390                 tc->text_sel_end.thisEndOfLine();
391                 sp_text_context_update_cursor(tc);
392                 sp_text_context_update_text_selection(tc);
393                 tc->dragging = 3;
394                 ret = TRUE;
395             }
396             break;
397         case GDK_BUTTON_RELEASE:
398             if (event->button.button == 1 && tc->dragging) {
399                 tc->dragging = 0;
400                 ret = TRUE;
401             }
402             break;
403         case GDK_MOTION_NOTIFY:
404             if (event->motion.state & GDK_BUTTON1_MASK && tc->dragging) {
405                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
406                 if (!layout) break;
407                 // find out click point in document coordinates
408                 NR::Point p = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
409                 // set the cursor closest to that point
410                 Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(tc->text, p);
411                 if (tc->dragging == 2) {
412                     // double-click dragging: go by word
413                     if (new_end < tc->text_sel_start) {
414                         if (!layout->isStartOfWord(new_end))
415                             new_end.prevStartOfWord();
416                     } else
417                         if (!layout->isEndOfWord(new_end))
418                             new_end.nextEndOfWord();
419                 } else if (tc->dragging == 3) {
420                     // triple-click dragging: go by line
421                     if (new_end < tc->text_sel_start)
422                         new_end.thisStartOfLine();
423                     else
424                         new_end.thisEndOfLine();
425                 }
426                 // update display
427                 if (tc->text_sel_end != new_end) {
428                     tc->text_sel_end = new_end;
429                     sp_text_context_update_cursor(tc);
430                     sp_text_context_update_text_selection(tc);
431                 }
432                 ret = TRUE;
433                 break;
434             }
435             // find out item under mouse, disregarding groups
436             item_ungrouped = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE);
437             if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
438                 sp_canvas_item_show(tc->indicator);
439                 NR::Maybe<NR::Rect> ibbox = sp_item_bbox_desktop(item_ungrouped);
440                 if (ibbox) {
441                     SP_CTRLRECT(tc->indicator)->setRectangle(*ibbox);
442                 }
444                 ec->cursor_shape = cursor_text_insert_xpm;
445                 ec->hot_x = 7;
446                 ec->hot_y = 10;
447                 sp_event_context_update_cursor(ec);
448                 sp_text_context_update_text_selection(tc);
450                 if (SP_IS_TEXT (item_ungrouped)) {
451                     desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
452                 } else {
453                     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."));
454                 }
456                 tc->over_text = true;
458                 ret = TRUE;
459             }
460             break;
461         default:
462             break;
463     }
465     if (!ret) {
466         if (((SPEventContextClass *) parent_class)->item_handler)
467             ret = ((SPEventContextClass *) parent_class)->item_handler(ec, item, event);
468     }
470     return ret;
473 static void
474 sp_text_context_setup_text(SPTextContext *tc)
476     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
478     /* Create <text> */
479     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_EVENT_CONTEXT_DESKTOP(ec)->doc());
480     Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
481     rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
483     /* Set style */
484     sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "tools.text", true);
486     sp_repr_set_svg_double(rtext, "x", tc->pdoc[NR::X]);
487     sp_repr_set_svg_double(rtext, "y", tc->pdoc[NR::Y]);
489     /* Create <tspan> */
490     Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
491     rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
492     rtext->addChild(rtspan, NULL);
493     Inkscape::GC::release(rtspan);
495     /* Create TEXT */
496     Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
497     rtspan->addChild(rstring, NULL);
498     Inkscape::GC::release(rstring);
499     SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
500     /* fixme: Is selection::changed really immediate? */
501     /* yes, it's immediate .. why does it matter? */
502     sp_desktop_selection(ec->desktop)->set(text_item);
503     Inkscape::GC::release(rtext);
504     text_item->transform = SP_ITEM(ec->desktop->currentRoot())->getRelativeTransform(ec->desktop->currentLayer());
505     text_item->updateRepr();
506     sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, 
507                      _("Create text"));
510 /**
511  * Insert the character indicated by tc.uni to replace the current selection,
512  * and reset tc.uni/tc.unipos to empty string.
513  *
514  * \pre tc.uni/tc.unipos non-empty.
515  */
516 static void
517 insert_uni_char(SPTextContext *const tc)
519     g_return_if_fail(tc->unipos
520                      && tc->unipos < sizeof(tc->uni)
521                      && tc->uni[tc->unipos] == '\0');
522     unsigned int uv;
523     sscanf(tc->uni, "%x", &uv);
524     tc->unipos = 0;
525     tc->uni[tc->unipos] = '\0';
527     if ( !g_unichar_isprint(static_cast<gunichar>(uv))
528          && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
529         // This may be due to bad input, so it goes to statusbar.
530         tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
531                                            _("Non-printable character"));
532     } else {
533         if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
534             sp_text_context_setup_text(tc);
535             tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
536         }
538         gchar u[10];
539         guint const len = g_unichar_to_utf8(uv, u);
540         u[len] = '\0';
542         tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
543         sp_text_context_update_cursor(tc);
544         sp_text_context_update_text_selection(tc);
545         sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM, 
546                          _("Insert Unicode character"));
547     }
550 static void
551 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
553     unsigned int uv;
554     sscanf(hex, "%x", &uv);
555     if (!g_unichar_isprint((gunichar) uv)) {
556         uv = 0xfffd;
557     }
558     guint const len = g_unichar_to_utf8(uv, utf8);
559     utf8[len] = '\0';
562 static void
563 show_curr_uni_char(SPTextContext *const tc)
565     g_return_if_fail(tc->unipos < sizeof(tc->uni)
566                      && tc->uni[tc->unipos] == '\0');
567     if (tc->unipos) {
568         char utf8[10];
569         hex_to_printable_utf8_buf(tc->uni, utf8);
571         /* Status bar messages are in pango markup, so we need xml escaping. */
572         if (utf8[1] == '\0') {
573             switch(utf8[0]) {
574                 case '<': strcpy(utf8, "&lt;"); break;
575                 case '>': strcpy(utf8, "&gt;"); break;
576                 case '&': strcpy(utf8, "&amp;"); break;
577                 default: break;
578             }
579         }
580         tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
581                                           _("Unicode (<b>Enter</b> to finish): %s: %s"), tc->uni, utf8);
582     } else {
583         tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
584     }
587 static gint
588 sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
590     SPTextContext *const tc = SP_TEXT_CONTEXT(ec);
592     SPDesktop *desktop = ec->desktop;
594     sp_canvas_item_hide(tc->indicator);
596     sp_text_context_validate_cursor_iterators(tc);
598     ec->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100);
600     switch (event->type) {
601         case GDK_BUTTON_PRESS:
602             if (event->button.button == 1) {
604                 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(ec);
606                 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
607                     return TRUE;
608                 }
610                 // save drag origin
611                 ec->xp = (gint) event->button.x;
612                 ec->yp = (gint) event->button.y;
613                 ec->within_tolerance = true;
615                 NR::Point const button_pt(event->button.x, event->button.y);
616                 tc->p0 = desktop->w2d(button_pt);
617                 Inkscape::Rubberband::get()->start(desktop, tc->p0);
618                 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
619                                     GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
620                                         GDK_POINTER_MOTION_MASK,
621                                     NULL, event->button.time);
622                 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
623                 tc->creating = 1;
625                 /* Processed */
626                 return TRUE;
627             }
628             break;
629         case GDK_MOTION_NOTIFY:
630             if (tc->over_text) {
631                 tc->over_text = 0;
632                 // update cursor and statusbar: we are not over a text object now
633                 ec->cursor_shape = cursor_text_xpm;
634                 ec->hot_x = 7;
635                 ec->hot_y = 7;
636                 sp_event_context_update_cursor(ec);
637                 desktop->event_context->defaultMessageContext()->clear();
638             }
640             if (tc->creating && event->motion.state & GDK_BUTTON1_MASK) {
641                 if ( ec->within_tolerance
642                      && ( abs( (gint) event->motion.x - ec->xp ) < ec->tolerance )
643                      && ( abs( (gint) event->motion.y - ec->yp ) < ec->tolerance ) ) {
644                     break; // do not drag if we're within tolerance from origin
645                 }
646                 // Once the user has moved farther than tolerance from the original location
647                 // (indicating they intend to draw, not click), then always process the
648                 // motion notify coordinates as given (no snapping back to origin)
649                 ec->within_tolerance = false;
651                 NR::Point const motion_pt(event->motion.x, event->motion.y);
652                 NR::Point const p = desktop->w2d(motion_pt);
654                 Inkscape::Rubberband::get()->move(p);
655                 gobble_motion_events(GDK_BUTTON1_MASK);
657                 // status text
658                 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::X]), desktop->namedview->getDefaultMetric());
659                 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::Y]), desktop->namedview->getDefaultMetric());
660                 ec->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("<b>Flowed text frame</b>: %s &#215; %s"), xs->str, ys->str);
661                 g_string_free(xs, FALSE);
662                 g_string_free(ys, FALSE);
664             }
665             break;
666         case GDK_BUTTON_RELEASE:
667             if (event->button.button == 1) {
669                 if (tc->grabbed) {
670                     sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
671                     tc->grabbed = NULL;
672                 }
674                 Inkscape::Rubberband::get()->stop();
676                 if (tc->creating && ec->within_tolerance) {
677                     /* Button 1, set X & Y & new item */
678                     sp_desktop_selection(desktop)->clear();
679                     NR::Point dtp = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
680                     tc->pdoc = sp_desktop_dt2root_xy_point(ec->desktop, dtp);
682                     tc->show = TRUE;
683                     tc->phase = 1;
684                     tc->nascent_object = 1; // new object was just created
686                     /* Cursor */
687                     sp_canvas_item_show(tc->cursor);
688                     // Cursor height is defined by the new text object's font size; it needs to be set
689                     // articifically here, for the text object does not exist yet:
690                     double cursor_height = sp_desktop_get_font_size_tool(ec->desktop);
691                     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), dtp, dtp + NR::Point(0, cursor_height));
692                     ec->_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
694                     ec->within_tolerance = false;
695                 } else if (tc->creating) {
696                     NR::Point const button_pt(event->button.x, event->button.y);
697                     NR::Point p1 = desktop->w2d(button_pt);
698                     double cursor_height = sp_desktop_get_font_size_tool(ec->desktop);
699                     if (fabs(p1[NR::Y] - tc->p0[NR::Y]) > cursor_height) {
700                         // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
701                         SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
702                         /* Set style */
703                         sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), SP_OBJECT_REPR(ft), "tools.text", true);
704                         sp_desktop_selection(desktop)->set(ft);
705                         ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
706                         sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, 
707                                          _("Create flowed text"));
708                     } else {
709                         ec->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
710                     }
711                 }
712                 tc->creating = false;
713                 return TRUE;
714             }
715             break;
716         case GDK_KEY_PRESS: {
717             guint const group0_keyval = get_group0_keyval(&event->key);
719             if (group0_keyval == GDK_KP_Add ||
720                 group0_keyval == GDK_KP_Subtract) {
721                 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
722                     break; // otherwise pass on keypad +/- so they can zoom
723             }
725             if ((tc->text) || (tc->nascent_object)) {
726                 // there is an active text object in this context, or a new object was just created
728                 if (tc->unimode || !tc->imc
729                     || (MOD__CTRL && MOD__SHIFT)    // input methods tend to steal this for unimode,
730                                                     // but we have our own so make sure they don't swallow it
731                     || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
732                     //IM did not consume the key, or we're in unimode
734                         if (!MOD__CTRL_ONLY && tc->unimode) {
735                             /* TODO: ISO 14755 (section 3 Definitions) says that we should also
736                                accept the first 6 characters of alphabets other than the latin
737                                alphabet "if the Latin alphabet is not used".  The below is also
738                                reasonable (viz. hope that the user's keyboard includes latin
739                                characters and force latin interpretation -- just as we do for our
740                                keyboard shortcuts), but differs from the ISO 14755
741                                recommendation. */
742                             switch (group0_keyval) {
743                                 case GDK_space:
744                                 case GDK_KP_Space: {
745                                     if (tc->unipos) {
746                                         insert_uni_char(tc);
747                                     }
748                                     /* Stay in unimode. */
749                                     show_curr_uni_char(tc);
750                                     return TRUE;
751                                 }
753                                 case GDK_BackSpace: {
754                                     g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
755                                     if (tc->unipos) {
756                                         tc->uni[--tc->unipos] = '\0';
757                                     }
758                                     show_curr_uni_char(tc);
759                                     return TRUE;
760                                 }
762                                 case GDK_Return:
763                                 case GDK_KP_Enter: {
764                                     if (tc->unipos) {
765                                         insert_uni_char(tc);
766                                     }
767                                     /* Exit unimode. */
768                                     tc->unimode = false;
769                                     ec->defaultMessageContext()->clear();
770                                     return TRUE;
771                                 }
773                                 case GDK_Escape: {
774                                     // Cancel unimode.
775                                     tc->unimode = false;
776                                     gtk_im_context_reset(tc->imc);
777                                     ec->defaultMessageContext()->clear();
778                                     return TRUE;
779                                 }
781                                 case GDK_Shift_L:
782                                 case GDK_Shift_R:
783                                     break;
785                                 default: {
786                                     if (g_ascii_isxdigit(group0_keyval)) {
787                                         g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
788                                         tc->uni[tc->unipos++] = group0_keyval;
789                                         tc->uni[tc->unipos] = '\0';
790                                         if (tc->unipos == 8) {
791                                             /* This behaviour is partly to allow us to continue to
792                                                use a fixed-length buffer for tc->uni.  Reason for
793                                                choosing the number 8 is that it's the length of
794                                                ``canonical form'' mentioned in the ISO 14755 spec.
795                                                An advantage over choosing 6 is that it allows using
796                                                backspace for typos & misremembering when entering a
797                                                6-digit number. */
798                                             insert_uni_char(tc);
799                                         }
800                                         show_curr_uni_char(tc);
801                                         return TRUE;
802                                     } else {
803                                         /* The intent is to ignore but consume characters that could be
804                                            typos for hex digits.  Gtk seems to ignore & consume all
805                                            non-hex-digits, and we do similar here.  Though note that some
806                                            shortcuts (like keypad +/- for zoom) get processed before
807                                            reaching this code. */
808                                         return TRUE;
809                                     }
810                                 }
811                             }
812                         }
814                         bool (Inkscape::Text::Layout::iterator::*cursor_movement_operator)() = NULL;
816                         /* Neither unimode nor IM consumed key; process text tool shortcuts */
817                         switch (group0_keyval) {
818                             case GDK_x:
819                             case GDK_X:
820                                 if (MOD__ALT_ONLY) {
821                                     desktop->setToolboxFocusTo ("altx-text");
822                                     return TRUE;
823                                 }
824                                 break;
825                             case GDK_space:
826                                 if (MOD__CTRL_ONLY) {
827                                     /* No-break space */
828                                     if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
829                                         sp_text_context_setup_text(tc);
830                                         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
831                                     }
832                                     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
833                                     sp_text_context_update_cursor(tc);
834                                     sp_text_context_update_text_selection(tc);
835                                     ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
836                                     sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, 
837                                                      _("Insert no-break space"));
838                                     return TRUE;
839                                 }
840                                 break;
841                             case GDK_U:
842                             case GDK_u:
843                                 if (MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT)) {
844                                     if (tc->unimode) {
845                                         tc->unimode = false;
846                                         ec->defaultMessageContext()->clear();
847                                     } else {
848                                         tc->unimode = true;
849                                         tc->unipos = 0;
850                                         ec->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
851                                     }
852                                     if (tc->imc) {
853                                         gtk_im_context_reset(tc->imc);
854                                     }
855                                     return TRUE;
856                                 }
857                                 break;
858                             case GDK_B:
859                             case GDK_b:
860                                 if (MOD__CTRL_ONLY && tc->text) {
861                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
862                                     SPCSSAttr *css = sp_repr_css_attr_new();
863                                     if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
864                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
865                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
866                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
867                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
868                                         sp_repr_css_set_property(css, "font-weight", "bold");
869                                     else
870                                         sp_repr_css_set_property(css, "font-weight", "normal");
871                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
872                                     sp_repr_css_attr_unref(css);
873                                     sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, 
874                                                      _("Make bold"));
875                                     sp_text_context_update_cursor(tc);
876                                     sp_text_context_update_text_selection(tc);
877                                     return TRUE;
878                                 }
879                                 break;
880                             case GDK_I:
881                             case GDK_i:
882                                 if (MOD__CTRL_ONLY && tc->text) {
883                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
884                                     SPCSSAttr *css = sp_repr_css_attr_new();
885                                     if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL)
886                                         sp_repr_css_set_property(css, "font-style", "italic");
887                                     else
888                                         sp_repr_css_set_property(css, "font-style", "normal");
889                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
890                                     sp_repr_css_attr_unref(css);
891                                     sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, 
892                                                      _("Make italic"));
893                                     sp_text_context_update_cursor(tc);
894                                     sp_text_context_update_text_selection(tc);
895                                     return TRUE;
896                                 }
897                                 break;
899                             case GDK_A:
900                             case GDK_a:
901                                 if (MOD__CTRL_ONLY && tc->text) {
902                                     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
903                                     if (layout) {
904                                         tc->text_sel_start = layout->begin();
905                                         tc->text_sel_end = layout->end();
906                                         sp_text_context_update_cursor(tc);
907                                         sp_text_context_update_text_selection(tc);
908                                         return TRUE;
909                                     }
910                                 }
911                                 break;
913                             case GDK_Return:
914                             case GDK_KP_Enter:
915                             {
916                                 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
917                                     sp_text_context_setup_text(tc);
918                                     tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
919                                 }
920                                 
921                                 iterator_pair enter_pair;
922                                 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, enter_pair);
923                                 tc->text_sel_start = tc->text_sel_end = enter_pair.first;
924                                 
925                                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
926                                 
927                                 sp_text_context_update_cursor(tc);
928                                 sp_text_context_update_text_selection(tc);
929                                 sp_document_done(sp_desktop_document(ec->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
935                                     
936                                     bool noSelection = false;
937                                     
938                                         if (tc->text_sel_start == tc->text_sel_end) {
939                                         tc->text_sel_start.prevCursorPosition();
940                                         noSelection = true;
941                                     }
942                                         
943                                         iterator_pair bspace_pair;
944                                         bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, bspace_pair);
945                                     
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                                     }
960                                     
961                                     sp_text_context_update_cursor(tc);
962                                     sp_text_context_update_text_selection(tc);
963                                     sp_document_done(sp_desktop_document(ec->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;
971                                     
972                                     if (tc->text_sel_start == tc->text_sel_end) {
973                                         tc->text_sel_end.nextCursorPosition();
974                                         noSelection = true;
975                                     }
976                                     
977                                     iterator_pair del_pair;
978                                     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, del_pair);
979                                     
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                                     }
990                                     
991                                     
992                                     sp_text_context_update_cursor(tc);
993                                     sp_text_context_update_text_selection(tc);
994                                     sp_document_done(sp_desktop_document(ec->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                                         if (MOD__SHIFT)
1004                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-10, 0));
1005                                         else
1006                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-1, 0));
1007                                         sp_text_context_update_cursor(tc);
1008                                         sp_text_context_update_text_selection(tc);
1009                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:left", SP_VERB_CONTEXT_TEXT, 
1010                                                                _("Kern to the left"));
1011                                     } else {
1012                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorLeftWithControl
1013                                                                              : &Inkscape::Text::Layout::iterator::cursorLeft;
1014                                         break;
1015                                     }
1016                                 }
1017                                 return TRUE;
1018                             case GDK_Right:
1019                             case GDK_KP_Right:
1020                             case GDK_KP_6:
1021                                 if (tc->text) {
1022                                     if (MOD__ALT) {
1023                                         if (MOD__SHIFT)
1024                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(10, 0));
1025                                         else
1026                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(1, 0));
1027                                         sp_text_context_update_cursor(tc);
1028                                         sp_text_context_update_text_selection(tc);
1029                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:right", SP_VERB_CONTEXT_TEXT, 
1030                                                                _("Kern to the right"));
1031                                     } else {
1032                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorRightWithControl
1033                                                                              : &Inkscape::Text::Layout::iterator::cursorRight;
1034                                         break;
1035                                     }
1036                                 }
1037                                 return TRUE;
1038                             case GDK_Up:
1039                             case GDK_KP_Up:
1040                             case GDK_KP_8:
1041                                 if (tc->text) {
1042                                     if (MOD__ALT) {
1043                                         if (MOD__SHIFT)
1044                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -10));
1045                                         else
1046                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -1));
1047                                         sp_text_context_update_cursor(tc);
1048                                         sp_text_context_update_text_selection(tc);
1049                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:up", SP_VERB_CONTEXT_TEXT, 
1050                                                                _("Kern up"));
1052                                     } else {
1053                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorUpWithControl
1054                                                                              : &Inkscape::Text::Layout::iterator::cursorUp;
1055                                         break;
1056                                     }
1057                                 }
1058                                 return TRUE;
1059                             case GDK_Down:
1060                             case GDK_KP_Down:
1061                             case GDK_KP_2:
1062                                 if (tc->text) {
1063                                     if (MOD__ALT) {
1064                                         if (MOD__SHIFT)
1065                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 10));
1066                                         else
1067                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 1));
1068                                         sp_text_context_update_cursor(tc);
1069                                         sp_text_context_update_text_selection(tc);
1070                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:down", SP_VERB_CONTEXT_TEXT, 
1071                                                                _("Kern down"));
1073                                     } else {
1074                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorDownWithControl
1075                                                                              : &Inkscape::Text::Layout::iterator::cursorDown;
1076                                         break;
1077                                     }
1078                                 }
1079                                 return TRUE;
1080                             case GDK_Home:
1081                             case GDK_KP_Home:
1082                                 if (tc->text) {
1083                                     if (MOD__CTRL)
1084                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfShape;
1085                                     else
1086                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfLine;
1087                                     break;
1088                                 }
1089                                 return TRUE;
1090                             case GDK_End:
1091                             case GDK_KP_End:
1092                                 if (tc->text) {
1093                                     if (MOD__CTRL)
1094                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::nextStartOfShape;
1095                                     else
1096                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisEndOfLine;
1097                                     break;
1098                                 }
1099                                 return TRUE;
1100                             case GDK_Escape:
1101                                 if (tc->creating) {
1102                                     tc->creating = 0;
1103                                     if (tc->grabbed) {
1104                                         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1105                                         tc->grabbed = NULL;
1106                                     }
1107                                     Inkscape::Rubberband::get()->stop();
1108                                 } else {
1109                                     sp_desktop_selection(ec->desktop)->clear();
1110                                 }
1111                                 tc->nascent_object = FALSE;
1112                                 return TRUE;
1113                             case GDK_bracketleft:
1114                                 if (tc->text) {
1115                                     if (MOD__ALT || MOD__CTRL) {
1116                                         if (MOD__ALT) {
1117                                             if (MOD__SHIFT) {
1118                                                 // FIXME: alt+shift+[] does not work, don't know why
1119                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1120                                             } else {
1121                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1122                                             }
1123                                         } else {
1124                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -90);
1125                                         }
1126                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT, 
1127                                                                _("Rotate counterclockwise"));
1128                                         sp_text_context_update_cursor(tc);
1129                                         sp_text_context_update_text_selection(tc);
1130                                         return TRUE;
1131                                     }
1132                                 }
1133                                 break;
1134                             case GDK_bracketright:
1135                                 if (tc->text) {
1136                                     if (MOD__ALT || MOD__CTRL) {
1137                                         if (MOD__ALT) {
1138                                             if (MOD__SHIFT) {
1139                                                 // FIXME: alt+shift+[] does not work, don't know why
1140                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1141                                             } else {
1142                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1143                                             }
1144                                         } else {
1145                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 90);
1146                                         }
1147                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT, 
1148                                                                 _("Rotate clockwise"));
1149                                         sp_text_context_update_cursor(tc);
1150                                         sp_text_context_update_text_selection(tc);
1151                                         return TRUE;
1152                                     }
1153                                 }
1154                                 break;
1155                             case GDK_less:
1156                             case GDK_comma:
1157                                 if (tc->text) {
1158                                     if (MOD__ALT) {
1159                                         if (MOD__CTRL) {
1160                                             if (MOD__SHIFT)
1161                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1162                                             else
1163                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1164                                             sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT, 
1165                                                                     _("Contract line spacing"));
1167                                         } else {
1168                                             if (MOD__SHIFT)
1169                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1170                                             else
1171                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1172                                             sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT, 
1173                                                                     _("Contract letter spacing"));
1175                                         }
1176                                         sp_text_context_update_cursor(tc);
1177                                         sp_text_context_update_text_selection(tc);
1178                                         return TRUE;
1179                                     }
1180                                 }
1181                                 break;
1182                             case GDK_greater:
1183                             case GDK_period:
1184                                 if (tc->text) {
1185                                     if (MOD__ALT) {
1186                                         if (MOD__CTRL) {
1187                                             if (MOD__SHIFT)
1188                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1189                                             else
1190                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1191                                             sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT, 
1192                                                                     _("Expand line spacing"));
1194                                         } else {
1195                                             if (MOD__SHIFT)
1196                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1197                                             else
1198                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1199                                             sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT, 
1200                                                                     _("Expand letter spacing"));
1202                                         }
1203                                         sp_text_context_update_cursor(tc);
1204                                         sp_text_context_update_text_selection(tc);
1205                                         return TRUE;
1206                                     }
1207                                 }
1208                                 break;
1209                             default:
1210                                 break;
1211                         }
1213                         if (cursor_movement_operator) {
1214                             Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
1215                             Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
1216                             (tc->text_sel_end.*cursor_movement_operator)();
1217                             if (!MOD__SHIFT)
1218                                 tc->text_sel_start = tc->text_sel_end;
1219                             if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1220                                 sp_text_context_update_cursor(tc);
1221                                 sp_text_context_update_text_selection(tc);
1222                             }
1223                             return TRUE;
1224                         }
1226                 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1227             } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1228                 // except up/down that are swallowed to prevent the zoom field from activation
1229                 if ((group0_keyval == GDK_Up    ||
1230                      group0_keyval == GDK_Down  ||
1231                      group0_keyval == GDK_KP_Up ||
1232                      group0_keyval == GDK_KP_Down )
1233                     && !MOD__CTRL_ONLY) {
1234                     return TRUE;
1235                 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1236                     if (tc->creating) {
1237                         tc->creating = 0;
1238                         if (tc->grabbed) {
1239                             sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1240                             tc->grabbed = NULL;
1241                         }
1242                         Inkscape::Rubberband::get()->stop();
1243                     }
1244                 }
1245             }
1246             break;
1247         }
1249         case GDK_KEY_RELEASE:
1250             if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1251                 return TRUE;
1252             }
1253             break;
1254         default:
1255             break;
1256     }
1258     // if nobody consumed it so far
1259     if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1260         return ((SPEventContextClass *) parent_class)->root_handler(ec, event); // send event to parent
1261     } else {
1262         return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1263     }
1266 /**
1267  Attempts to paste system clipboard into the currently edited text, returns true on success
1268  */
1269 bool
1270 sp_text_paste_inline(SPEventContext *ec)
1272     if (!SP_IS_TEXT_CONTEXT(ec))
1273         return false;
1275     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1277     if ((tc->text) || (tc->nascent_object)) {
1278         // there is an active text object in this context, or a new object was just created
1280         Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1281         Glib::ustring const text = refClipboard->wait_for_text();
1283         if (!text.empty()) {
1285             if (!tc->text) { // create text if none (i.e. if nascent_object)
1286                 sp_text_context_setup_text(tc);
1287                 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1288             }
1290             // using indices is slow in ustrings. Whatever.
1291             Glib::ustring::size_type begin = 0;
1292             for ( ; ; ) {
1293                 Glib::ustring::size_type end = text.find('\n', begin);
1294                 if (end == Glib::ustring::npos) {
1295                     if (begin != text.length())
1296                         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());
1297                     break;
1298                 }
1299                 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());
1300                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1301                 begin = end + 1;
1302             }
1303             sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, 
1304                              _("Paste text"));
1306             return true;
1307         }
1308     } // FIXME: else create and select a new object under cursor!
1310     return false;
1313 /**
1314  Gets the raw characters that comprise the currently selected text, converting line
1315  breaks into lf characters.
1316 */
1317 Glib::ustring
1318 sp_text_get_selected_text(SPEventContext const *ec)
1320     if (!SP_IS_TEXT_CONTEXT(ec))
1321         return "";
1322     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1323     if (tc->text == NULL)
1324         return "";
1326     return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1329 /**
1330  Deletes the currently selected characters. Returns false if there is no
1331  text selection currently.
1332 */
1333 bool sp_text_delete_selection(SPEventContext *ec)
1335     if (!SP_IS_TEXT_CONTEXT(ec))
1336         return false;
1337     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1338     if (tc->text == NULL)
1339         return false;
1341     if (tc->text_sel_start == tc->text_sel_end)
1342         return false;
1343     
1344     iterator_pair pair;
1345     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair);
1346     
1347     
1348     if (success) {
1349         tc->text_sel_start = tc->text_sel_end = pair.first;
1350     } else { // nothing deleted
1351         tc->text_sel_start = pair.first;
1352         tc->text_sel_end = pair.second;
1353     }
1354     
1355     sp_text_context_update_cursor(tc);
1356     sp_text_context_update_text_selection(tc);
1357     
1358     return true;
1361 /**
1362  * \param selection Should not be NULL.
1363  */
1364 static void
1365 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1367     g_assert(selection != NULL);
1369     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1371     if (ec->shape_knot_holder) { // destroy knotholder
1372         sp_knot_holder_destroy(ec->shape_knot_holder);
1373         ec->shape_knot_holder = NULL;
1374     }
1376     if (ec->shape_repr) { // remove old listener
1377         sp_repr_remove_listener_by_data(ec->shape_repr, ec);
1378         Inkscape::GC::release(ec->shape_repr);
1379         ec->shape_repr = 0;
1380     }
1382     SPItem *item = selection->singleItem();
1383     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1384         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
1385         Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
1386         if (shape_repr) {
1387             ec->shape_repr = shape_repr;
1388             Inkscape::GC::anchor(shape_repr);
1389             sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
1390         }
1391     }
1393     if (tc->text && (item != tc->text)) {
1394         sp_text_context_forget_text(tc);
1395     }
1396     tc->text = NULL;
1398     if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1399         tc->text = item;
1400         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1401         if (layout)
1402             tc->text_sel_start = tc->text_sel_end = layout->end();
1403     } else {
1404         tc->text = NULL;
1405     }
1407     // we update cursor without scrolling, because this position may not be final;
1408     // item_handler moves cusros to the point of click immediately
1409     sp_text_context_update_cursor(tc, false);
1410     sp_text_context_update_text_selection(tc);
1413 static void
1414 sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc)
1416     sp_text_context_update_cursor(tc);
1417     sp_text_context_update_text_selection(tc);
1420 static bool
1421 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1423     if (tc->text == NULL)
1424         return false;
1425     if (tc->text_sel_start == tc->text_sel_end)
1426         return false;    // will get picked up by the parent and applied to the whole text object
1428     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1429     sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT, 
1430                      _("Set text style"));
1431     sp_text_context_update_cursor(tc);
1432     sp_text_context_update_text_selection(tc);
1434     return true;
1437 static int
1438 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1440     if (tc->text == NULL)
1441         return QUERY_STYLE_NOTHING;
1442     const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1443     if (layout == NULL)
1444         return QUERY_STYLE_NOTHING;
1445     sp_text_context_validate_cursor_iterators(tc);
1447     GSList *styles_list = NULL;
1449     Inkscape::Text::Layout::iterator begin_it, end_it;
1450     if (tc->text_sel_start < tc->text_sel_end) {
1451         begin_it = tc->text_sel_start;
1452         end_it = tc->text_sel_end;
1453     } else {
1454         begin_it = tc->text_sel_end;
1455         end_it = tc->text_sel_start;
1456     }
1457     if (begin_it == end_it)
1458         if (!begin_it.prevCharacter())
1459             end_it.nextCharacter();
1460     for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1461         SPObject const *pos_obj = 0;
1462         void *rawptr = 0;
1463         layout->getSourceOfCharacter(it, &rawptr);
1464         pos_obj = SP_OBJECT(rawptr);
1465         if (pos_obj == 0) continue;
1466         while (SP_IS_STRING(pos_obj) && SP_OBJECT_PARENT(pos_obj)) {
1467            pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
1468         }
1469         styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1470     }
1472     int result = sp_desktop_query_style_from_list (styles_list, style, property);
1474     g_slist_free(styles_list);
1475     return result;
1478 static void
1479 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1481     if (tc->text == NULL)
1482         return;
1483     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1484     if (layout) {     // undo can change the text length without us knowing it
1485         layout->validateIterator(&tc->text_sel_start);
1486         layout->validateIterator(&tc->text_sel_end);
1487     }
1490 static void
1491 sp_text_context_update_cursor(SPTextContext *tc,  bool scroll_to_see)
1493     GdkRectangle im_cursor = { 0, 0, 1, 1 };
1495     // due to interruptible display, tc may already be destroyed during a display update before
1496     // the cursor update (can't do both atomically, alas)
1497     if (!tc->desktop) return;
1499     if (tc->text) {
1500         NR::Point p0, p1;
1501         sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1502         NR::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1503         NR::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1505         // scroll to show cursor
1506         if (scroll_to_see) {
1507             NR::Point const dm = (d0 + d1) / 2;
1508             // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1509             SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(&dm, 1.0);
1510         }
1512         sp_canvas_item_show(tc->cursor);
1513         sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1515         /* fixme: ... need another transformation to get canvas widget coordinate space? */
1516         im_cursor.x = (int) floor(d0[NR::X]);
1517         im_cursor.y = (int) floor(d0[NR::Y]);
1518         im_cursor.width = (int) floor(d1[NR::X]) - im_cursor.x;
1519         im_cursor.height = (int) floor(d1[NR::Y]) - im_cursor.y;
1521         tc->show = TRUE;
1522         tc->phase = 1;
1524         if (SP_IS_FLOWTEXT(tc->text)) {
1525             SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1526             if (frame) {
1527                 sp_canvas_item_show(tc->frame);
1528                 NR::Maybe<NR::Rect> frame_bbox = sp_item_bbox_desktop(frame);
1529                 if (frame_bbox) {
1530                     SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
1531                 }
1532             }
1533             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1534         } else {
1535             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1536         }
1538     } else {
1539         sp_canvas_item_hide(tc->cursor);
1540         sp_canvas_item_hide(tc->frame);
1541         tc->show = FALSE;
1542         if (!tc->nascent_object) {
1543             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
1544         }
1545     }
1547     if (tc->imc) {
1548         gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1549     }
1550     SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1553 static void sp_text_context_update_text_selection(SPTextContext *tc)
1555     // due to interruptible display, tc may already be destroyed during a display update before
1556     // the selection update (can't do both atomically, alas)
1557     if (!tc->desktop) return;
1559     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1560         sp_canvas_item_hide(*it);
1561         gtk_object_destroy(*it);
1562     }
1563     tc->text_selection_quads.clear();
1565     std::vector<NR::Point> quads;
1566     if (tc->text != NULL)
1567         quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1568     for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1569         SPCanvasItem *quad_canvasitem;
1570         quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1571         sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x000000ff);
1572         sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1573         sp_canvas_item_show(quad_canvasitem);
1574         tc->text_selection_quads.push_back(quad_canvasitem);
1575     }
1578 static gint
1579 sp_text_context_timeout(SPTextContext *tc)
1581     if (tc->show) {
1582         if (tc->phase) {
1583             tc->phase = 0;
1584             sp_canvas_item_hide(tc->cursor);
1585         } else {
1586             tc->phase = 1;
1587             sp_canvas_item_show(tc->cursor);
1588         }
1589     }
1591     return TRUE;
1594 static void
1595 sp_text_context_forget_text(SPTextContext *tc)
1597     if (! tc->text) return;
1598     SPItem *ti = tc->text;
1599     /* We have to set it to zero,
1600      * or selection changed signal messes everything up */
1601     tc->text = NULL;
1602     if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1603         Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1604         // the repr may already have been unparented
1605         // if we were called e.g. as the result of
1606         // an undo or the element being removed from
1607         // the XML editor
1608         if ( text_repr && sp_repr_parent(text_repr) ) {
1609             sp_repr_unparent(text_repr);
1610             sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT, 
1611                      _("Remove empty text"));
1612         }
1613     }
1616 gint
1617 sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1619     gtk_im_context_focus_in(tc->imc);
1620     return FALSE;
1623 gint
1624 sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1626     gtk_im_context_focus_out(tc->imc);
1627     return FALSE;
1630 static void
1631 sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc)
1633     if (!tc->text) {
1634         sp_text_context_setup_text(tc);
1635         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1636     }
1638     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1639     sp_text_context_update_cursor(tc);
1640     sp_text_context_update_text_selection(tc);
1642     sp_document_done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT, 
1643                      _("Type text"));
1647 /*
1648   Local Variables:
1649   mode:c++
1650   c-file-style:"stroustrup"
1651   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1652   indent-tabs-mode:nil
1653   fill-column:99
1654   End:
1655 */
1656 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :