Code

initial color cursor implementation (reads from pixbufs, will work on reading from...
[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.pixbuf"
43 #include "pixmaps/cursor-text-insert.xpm"
44 #include "pixmaps/cursor-text-insert.pixbuf"
45 #include <glibmm/i18n.h>
46 #include "object-edit.h"
47 #include "xml/repr.h"
48 #include "xml/node-event-vector.h"
49 #include "prefs-utils.h"
50 #include "rubberband.h"
51 #include "sp-metrics.h"
52 #include "context-fns.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->cursor_pixbuf = gdk_pixbuf_new_from_inline(
128             -1,
129             cursor_text_pixbuf,
130             FALSE,
131             NULL);  
132     event_context->hot_x = 7;
133     event_context->hot_y = 7;
135     event_context->xp = 0;
136     event_context->yp = 0;
137     event_context->tolerance = 0;
138     event_context->within_tolerance = false;
140     event_context->shape_repr = NULL;
141     event_context->shape_knot_holder = NULL;
143     tc->imc = NULL;
145     tc->text = NULL;
146     tc->pdoc = NR::Point(0, 0);
147     new (&tc->text_sel_start) Inkscape::Text::Layout::iterator();
148     new (&tc->text_sel_end) Inkscape::Text::Layout::iterator();
149     new (&tc->text_selection_quads) std::vector<SPCanvasItem*>();
151     tc->unimode = false;
153     tc->cursor = NULL;
154     tc->indicator = NULL;
155     tc->frame = NULL;
156     tc->grabbed = NULL;
157     tc->timeout = 0;
158     tc->show = FALSE;
159     tc->phase = 0;
160     tc->nascent_object = 0;
161     tc->over_text = 0;
162     tc->dragging = 0;
163     tc->creating = 0;
165     new (&tc->sel_changed_connection) sigc::connection();
166     new (&tc->sel_modified_connection) sigc::connection();
167     new (&tc->style_set_connection) sigc::connection();
168     new (&tc->style_query_connection) sigc::connection();
171 static void
172 sp_text_context_dispose(GObject *obj)
174     SPTextContext *tc = SP_TEXT_CONTEXT(obj);
175     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
176     tc->style_query_connection.~connection();
177     tc->style_set_connection.~connection();
178     tc->sel_changed_connection.~connection();
179     tc->sel_modified_connection.~connection();
180     tc->text_sel_end.~iterator();
181     tc->text_sel_start.~iterator();
182     tc->text_selection_quads.~vector();
183     if (G_OBJECT_CLASS(parent_class)->dispose) {
184         G_OBJECT_CLASS(parent_class)->dispose(obj);
185     }
186     if (tc->grabbed) {
187         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
188         tc->grabbed = NULL;
189     }
191     Inkscape::Rubberband::get()->stop();
193     if (ec->shape_knot_holder) {
194         sp_knot_holder_destroy(ec->shape_knot_holder);
195         ec->shape_knot_holder = NULL;
196     }
197     if (ec->shape_repr) { // remove old listener
198         sp_repr_remove_listener_by_data(ec->shape_repr, ec);
199         Inkscape::GC::release(ec->shape_repr);
200         ec->shape_repr = 0;
201     }
204 static Inkscape::XML::NodeEventVector ec_shape_repr_events = {
205     NULL, /* child_added */
206     NULL, /* child_removed */
207     ec_shape_event_attr_changed,
208     NULL, /* content_changed */
209     NULL  /* order_changed */
210 };
212 static void
213 sp_text_context_setup(SPEventContext *ec)
215     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
216     SPDesktop *desktop = ec->desktop;
218     tc->cursor = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLLINE, NULL);
219     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), 100, 0, 100, 100);
220     sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
221     sp_canvas_item_hide(tc->cursor);
223     tc->indicator = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
224     SP_CTRLRECT(tc->indicator)->setRectangle(NR::Rect(NR::Point(0, 0), NR::Point(100, 100)));
225     SP_CTRLRECT(tc->indicator)->setColor(0x0000ff7f, false, 0);
226     sp_canvas_item_hide(tc->indicator);
228     tc->frame = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
229     SP_CTRLRECT(tc->frame)->setRectangle(NR::Rect(NR::Point(0, 0), NR::Point(100, 100)));
230     SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0);
231     sp_canvas_item_hide(tc->frame);
233     tc->timeout = gtk_timeout_add(250, (GtkFunction) sp_text_context_timeout, ec);
235     tc->imc = gtk_im_multicontext_new();
236     if (tc->imc) {
237         GtkWidget *canvas = GTK_WIDGET(sp_desktop_canvas(desktop));
239         /* im preedit handling is very broken in inkscape for
240          * multi-byte characters.  See bug 1086769.
241          * We need to let the IM handle the preediting, and
242          * just take in the characters when they're finished being
243          * entered.
244          */
245         gtk_im_context_set_use_preedit(tc->imc, FALSE);
246         gtk_im_context_set_client_window(tc->imc, canvas->window);
248         g_signal_connect(G_OBJECT(canvas), "focus_in_event", G_CALLBACK(sptc_focus_in), tc);
249         g_signal_connect(G_OBJECT(canvas), "focus_out_event", G_CALLBACK(sptc_focus_out), tc);
250         g_signal_connect(G_OBJECT(tc->imc), "commit", G_CALLBACK(sptc_commit), tc);
252         if (GTK_WIDGET_HAS_FOCUS(canvas)) {
253             sptc_focus_in(canvas, NULL, tc);
254         }
255     }
257     if (((SPEventContextClass *) parent_class)->setup)
258         ((SPEventContextClass *) parent_class)->setup(ec);
260     SPItem *item = sp_desktop_selection(ec->desktop)->singleItem();
261     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
262         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
263         Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
264         if (shape_repr) {
265             ec->shape_repr = shape_repr;
266             Inkscape::GC::anchor(shape_repr);
267             sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
268         }
269     }
271     tc->sel_changed_connection = sp_desktop_selection(desktop)->connectChanged(
272         sigc::bind(sigc::ptr_fun(&sp_text_context_selection_changed), tc)
273         );
274     tc->sel_modified_connection = sp_desktop_selection(desktop)->connectModified(
275         sigc::bind(sigc::ptr_fun(&sp_text_context_selection_modified), tc)
276         );
277     tc->style_set_connection = desktop->connectSetStyle(
278         sigc::bind(sigc::ptr_fun(&sp_text_context_style_set), tc)
279         );
280     tc->style_query_connection = desktop->connectQueryStyle(
281         sigc::bind(sigc::ptr_fun(&sp_text_context_style_query), tc)
282         );
284     sp_text_context_selection_changed(sp_desktop_selection(desktop), tc);
286     if (prefs_get_int_attribute("tools.text", "selcue", 0) != 0) {
287         ec->enableSelectionCue();
288     }
289     if (prefs_get_int_attribute("tools.text", "gradientdrag", 0) != 0) {
290         ec->enableGrDrag();
291     }
294 static void
295 sp_text_context_finish(SPEventContext *ec)
297     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
299     ec->enableGrDrag(false);
301     tc->style_set_connection.disconnect();
302     tc->style_query_connection.disconnect();
303     tc->sel_changed_connection.disconnect();
304     tc->sel_modified_connection.disconnect();
306     sp_text_context_forget_text(SP_TEXT_CONTEXT(ec));
308     if (tc->imc) {
309         g_object_unref(G_OBJECT(tc->imc));
310         tc->imc = NULL;
311     }
313     if (tc->timeout) {
314         gtk_timeout_remove(tc->timeout);
315         tc->timeout = 0;
316     }
318     if (tc->cursor) {
319         gtk_object_destroy(GTK_OBJECT(tc->cursor));
320         tc->cursor = NULL;
321     }
323     if (tc->indicator) {
324         gtk_object_destroy(GTK_OBJECT(tc->indicator));
325         tc->indicator = NULL;
326     }
328     if (tc->frame) {
329         gtk_object_destroy(GTK_OBJECT(tc->frame));
330         tc->frame = NULL;
331     }
333     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ;
334          it != tc->text_selection_quads.end() ; ++it) {
335         sp_canvas_item_hide(*it);
336         gtk_object_destroy(*it);
337     }
338     tc->text_selection_quads.clear();
340     if (ec->desktop) {
341         sp_signal_disconnect_by_data(sp_desktop_canvas(ec->desktop), tc);
342     }
346 static gint
347 sp_text_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event)
349     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
350     SPDesktop *desktop = ec->desktop;
351     SPItem *item_ungrouped;
353     gint ret = FALSE;
355     sp_text_context_validate_cursor_iterators(tc);
357     switch (event->type) {
358         case GDK_BUTTON_PRESS:
359             if (event->button.button == 1) {
360                 // find out clicked item, disregarding groups
361                 item_ungrouped = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE);
362                 if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
363                     sp_desktop_selection(ec->desktop)->set(item_ungrouped);
364                     if (tc->text) {
365                         // find out click point in document coordinates
366                         NR::Point p = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
367                         // set the cursor closest to that point
368                         tc->text_sel_start = tc->text_sel_end = sp_te_get_position_by_coords(tc->text, p);
369                         // update display
370                         sp_text_context_update_cursor(tc);
371                         sp_text_context_update_text_selection(tc);
372                         tc->dragging = 1;
373                     }
374                     ret = TRUE;
375                 }
376             }
377             break;
378         case GDK_2BUTTON_PRESS:
379             if (event->button.button == 1 && tc->text) {
380                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
381                 if (layout) {
382                     if (!layout->isStartOfWord(tc->text_sel_start))
383                         tc->text_sel_start.prevStartOfWord();
384                     if (!layout->isEndOfWord(tc->text_sel_end))
385                         tc->text_sel_end.nextEndOfWord();
386                     sp_text_context_update_cursor(tc);
387                     sp_text_context_update_text_selection(tc);
388                     tc->dragging = 2;
389                     ret = TRUE;
390                 }
391             }
392             break;
393         case GDK_3BUTTON_PRESS:
394             if (event->button.button == 1 && tc->text) {
395                 tc->text_sel_start.thisStartOfLine();
396                 tc->text_sel_end.thisEndOfLine();
397                 sp_text_context_update_cursor(tc);
398                 sp_text_context_update_text_selection(tc);
399                 tc->dragging = 3;
400                 ret = TRUE;
401             }
402             break;
403         case GDK_BUTTON_RELEASE:
404             if (event->button.button == 1 && tc->dragging) {
405                 tc->dragging = 0;
406                 ret = TRUE;
407             }
408             break;
409         case GDK_MOTION_NOTIFY:
410             if (event->motion.state & GDK_BUTTON1_MASK && tc->dragging) {
411                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
412                 if (!layout) break;
413                 // find out click point in document coordinates
414                 NR::Point p = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
415                 // set the cursor closest to that point
416                 Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(tc->text, p);
417                 if (tc->dragging == 2) {
418                     // double-click dragging: go by word
419                     if (new_end < tc->text_sel_start) {
420                         if (!layout->isStartOfWord(new_end))
421                             new_end.prevStartOfWord();
422                     } else
423                         if (!layout->isEndOfWord(new_end))
424                             new_end.nextEndOfWord();
425                 } else if (tc->dragging == 3) {
426                     // triple-click dragging: go by line
427                     if (new_end < tc->text_sel_start)
428                         new_end.thisStartOfLine();
429                     else
430                         new_end.thisEndOfLine();
431                 }
432                 // update display
433                 if (tc->text_sel_end != new_end) {
434                     tc->text_sel_end = new_end;
435                     sp_text_context_update_cursor(tc);
436                     sp_text_context_update_text_selection(tc);
437                 }
438                 ret = TRUE;
439                 break;
440             }
441             // find out item under mouse, disregarding groups
442             item_ungrouped = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE);
443             if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
444                 sp_canvas_item_show(tc->indicator);
445                 SP_CTRLRECT(tc->indicator)->setRectangle(sp_item_bbox_desktop(item_ungrouped));
447                 ec->cursor_shape = cursor_text_insert_xpm;
448                 ec->cursor_pixbuf = gdk_pixbuf_new_from_inline(
449                         -1,
450                         cursor_text_insert_pixbuf,
451                         FALSE,
452                         NULL);  
453                 ec->hot_x = 7;
454                 ec->hot_y = 10;
455                 sp_event_context_update_cursor(ec);
456                 sp_text_context_update_text_selection(tc);
458                 if (SP_IS_TEXT (item_ungrouped)) {
459                     desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
460                 } else {
461                     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."));
462                 }
464                 tc->over_text = true;
466                 ret = TRUE;
467             }
468             break;
469         default:
470             break;
471     }
473     if (!ret) {
474         if (((SPEventContextClass *) parent_class)->item_handler)
475             ret = ((SPEventContextClass *) parent_class)->item_handler(ec, item, event);
476     }
478     return ret;
481 static void
482 sp_text_context_setup_text(SPTextContext *tc)
484     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
486     /* Create <text> */
487     Inkscape::XML::Node *rtext = sp_repr_new("svg:text");
488     rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
490     /* Set style */
491     sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "tools.text", true);
493     sp_repr_set_svg_double(rtext, "x", tc->pdoc[NR::X]);
494     sp_repr_set_svg_double(rtext, "y", tc->pdoc[NR::Y]);
496     /* Create <tspan> */
497     Inkscape::XML::Node *rtspan = sp_repr_new("svg:tspan");
498     rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
499     rtext->addChild(rtspan, NULL);
500     Inkscape::GC::release(rtspan);
502     /* Create TEXT */
503     Inkscape::XML::Node *rstring = sp_repr_new_text("");
504     rtspan->addChild(rstring, NULL);
505     Inkscape::GC::release(rstring);
506     SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
507     /* fixme: Is selection::changed really immediate? */
508     /* yes, it's immediate .. why does it matter? */
509     sp_desktop_selection(ec->desktop)->set(text_item);
510     Inkscape::GC::release(rtext);
511     text_item->transform = SP_ITEM(ec->desktop->currentRoot())->getRelativeTransform(ec->desktop->currentLayer());
512     text_item->updateRepr();
513     sp_document_done(sp_desktop_document(ec->desktop));
516 /**
517  * Insert the character indicated by tc.uni to replace the current selection,
518  * and reset tc.uni/tc.unipos to empty string.
519  *
520  * \pre tc.uni/tc.unipos non-empty.
521  */
522 static void
523 insert_uni_char(SPTextContext *const tc)
525     g_return_if_fail(tc->unipos
526                      && tc->unipos < sizeof(tc->uni)
527                      && tc->uni[tc->unipos] == '\0');
528     unsigned int uv;
529     sscanf(tc->uni, "%x", &uv);
530     tc->unipos = 0;
531     tc->uni[tc->unipos] = '\0';
533     if ( !g_unichar_isprint(static_cast<gunichar>(uv))
534          && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
535         // This may be due to bad input, so it goes to statusbar.
536         tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
537                                            _("Non-printable character"));
538     } else {
539         if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
540             sp_text_context_setup_text(tc);
541             tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
542         }
544         gchar u[10];
545         guint const len = g_unichar_to_utf8(uv, u);
546         u[len] = '\0';
548         tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
549         sp_text_context_update_cursor(tc);
550         sp_text_context_update_text_selection(tc);
551         sp_document_done(sp_desktop_document(tc->desktop));
552     }
555 static void
556 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
558     unsigned int uv;
559     sscanf(hex, "%x", &uv);
560     if (!g_unichar_isprint((gunichar) uv)) {
561         uv = 0xfffd;
562     }
563     guint const len = g_unichar_to_utf8(uv, utf8);
564     utf8[len] = '\0';
567 static void
568 show_curr_uni_char(SPTextContext *const tc)
570     g_return_if_fail(tc->unipos < sizeof(tc->uni)
571                      && tc->uni[tc->unipos] == '\0');
572     if (tc->unipos) {
573         char utf8[10];
574         hex_to_printable_utf8_buf(tc->uni, utf8);
576         /* Status bar messages are in pango markup, so we need xml escaping. */
577         if (utf8[1] == '\0') {
578             switch(utf8[0]) {
579                 case '<': strcpy(utf8, "&lt;"); break;
580                 case '>': strcpy(utf8, "&gt;"); break;
581                 case '&': strcpy(utf8, "&amp;"); break;
582                 default: break;
583             }
584         }
585         tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
586                                           _("Unicode: %s: %s"), tc->uni, utf8);
587     } else {
588         tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode: "));
589     }
592 static gint
593 sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
595     SPTextContext *const tc = SP_TEXT_CONTEXT(ec);
597     SPDesktop *desktop = ec->desktop;
599     sp_canvas_item_hide(tc->indicator);
601     sp_text_context_validate_cursor_iterators(tc);
603     ec->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100);
605     switch (event->type) {
606         case GDK_BUTTON_PRESS:
607             if (event->button.button == 1) {
609                 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(ec);
611                 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
612                     return TRUE;
613                 }
615                 // save drag origin
616                 ec->xp = (gint) event->button.x;
617                 ec->yp = (gint) event->button.y;
618                 ec->within_tolerance = true;
620                 NR::Point const button_pt(event->button.x, event->button.y);
621                 tc->p0 = desktop->w2d(button_pt);
622                 Inkscape::Rubberband::get()->start(desktop, tc->p0);
623                 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
624                                     GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
625                                         GDK_POINTER_MOTION_MASK,
626                                     NULL, event->button.time);
627                 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
628                 tc->creating = 1;
630                 /* Processed */
631                 return TRUE;
632             }
633             break;
634         case GDK_MOTION_NOTIFY:
635             if (tc->over_text) {
636                 tc->over_text = 0;
637                 // update cursor and statusbar: we are not over a text object now
638                 ec->cursor_shape = cursor_text_xpm;
639                 ec->cursor_pixbuf = gdk_pixbuf_new_from_inline(
640                         -1,
641                         cursor_text_pixbuf,
642                         FALSE,
643                         NULL);  
644                 ec->hot_x = 7;
645                 ec->hot_y = 7;
646                 sp_event_context_update_cursor(ec);
647                 desktop->event_context->defaultMessageContext()->clear();
648             }
650             if (tc->creating && event->motion.state & GDK_BUTTON1_MASK) {
651                 if ( ec->within_tolerance
652                      && ( abs( (gint) event->motion.x - ec->xp ) < ec->tolerance )
653                      && ( abs( (gint) event->motion.y - ec->yp ) < ec->tolerance ) ) {
654                     break; // do not drag if we're within tolerance from origin
655                 }
656                 // Once the user has moved farther than tolerance from the original location
657                 // (indicating they intend to draw, not click), then always process the
658                 // motion notify coordinates as given (no snapping back to origin)
659                 ec->within_tolerance = false;
661                 NR::Point const motion_pt(event->motion.x, event->motion.y);
662                 NR::Point const p = desktop->w2d(motion_pt);
664                 Inkscape::Rubberband::get()->move(p);
665                 gobble_motion_events(GDK_BUTTON1_MASK);
667                 // status text
668                 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::X]), desktop->namedview->getDefaultMetric());
669                 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::Y]), desktop->namedview->getDefaultMetric());
670                 ec->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("<b>Flowed text frame</b>: %s &#215; %s"), xs->str, ys->str);
671                 g_string_free(xs, FALSE);
672                 g_string_free(ys, FALSE);
674             }
675             break;
676         case GDK_BUTTON_RELEASE:
677             if (event->button.button == 1) {
679                 if (tc->grabbed) {
680                     sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
681                     tc->grabbed = NULL;
682                 }
684                 Inkscape::Rubberband::get()->stop();
686                 if (tc->creating && ec->within_tolerance) {
687                     /* Button 1, set X & Y & new item */
688                     sp_desktop_selection(desktop)->clear();
689                     NR::Point dtp = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
690                     tc->pdoc = sp_desktop_dt2root_xy_point(ec->desktop, dtp);
692                     tc->show = TRUE;
693                     tc->phase = 1;
694                     tc->nascent_object = 1; // new object was just created
696                     /* Cursor */
697                     sp_canvas_item_show(tc->cursor);
698                     // Cursor height is defined by the new text object's font size; it needs to be set
699                     // articifically here, for the text object does not exist yet:
700                     double cursor_height = sp_desktop_get_font_size_tool(ec->desktop);
701                     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), dtp, dtp + NR::Point(0, cursor_height));
702                     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
704                     ec->within_tolerance = false;
705                 } else if (tc->creating) {
706                     NR::Point const button_pt(event->button.x, event->button.y);
707                     NR::Point p1 = desktop->w2d(button_pt);
708                     double cursor_height = sp_desktop_get_font_size_tool(ec->desktop);
709                     if (fabs(p1[NR::Y] - tc->p0[NR::Y]) > cursor_height) {
710                         // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
711                         SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
712                         sp_desktop_selection(desktop)->set(ft);
713                         ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
714                         sp_document_done(sp_desktop_document(desktop));
715                     } else {
716                         ec->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
717                     }
718                 }
719                 tc->creating = false;
720                 return TRUE;
721             }
722             break;
723         case GDK_KEY_PRESS: {
724             guint const group0_keyval = get_group0_keyval(&event->key);
726             if (group0_keyval == GDK_KP_Add ||
727                 group0_keyval == GDK_KP_Subtract) {
728                 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
729                     break; // otherwise pass on keypad +/- so they can zoom
730             }
732             if ((tc->text) || (tc->nascent_object)) {
733                 // there is an active text object in this context, or a new object was just created
735                 if (tc->unimode || !tc->imc
736                     || (MOD__CTRL && MOD__SHIFT)    // input methods tend to steal this for unimode,
737                                                     // but we have our own so make sure they don't swallow it
738                     || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
739                     //IM did not consume the key, or we're in unimode
741                         if (!MOD__CTRL_ONLY && tc->unimode) {
742                             /* TODO: ISO 14755 (section 3 Definitions) says that we should also
743                                accept the first 6 characters of alphabets other than the latin
744                                alphabet "if the Latin alphabet is not used".  The below is also
745                                reasonable (viz. hope that the user's keyboard includes latin
746                                characters and force latin interpretation -- just as we do for our
747                                keyboard shortcuts), but differs from the ISO 14755
748                                recommendation. */
749                             switch (group0_keyval) {
750                                 case GDK_space:
751                                 case GDK_KP_Space: {
752                                     if (tc->unipos) {
753                                         insert_uni_char(tc);
754                                     }
755                                     /* Stay in unimode. */
756                                     show_curr_uni_char(tc);
757                                     return TRUE;
758                                 }
760                                 case GDK_BackSpace: {
761                                     g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
762                                     if (tc->unipos) {
763                                         tc->uni[--tc->unipos] = '\0';
764                                     }
765                                     show_curr_uni_char(tc);
766                                     return TRUE;
767                                 }
769                                 case GDK_Return:
770                                 case GDK_KP_Enter: {
771                                     if (tc->unipos) {
772                                         insert_uni_char(tc);
773                                     }
774                                     /* Exit unimode. */
775                                     tc->unimode = false;
776                                     ec->defaultMessageContext()->clear();
777                                     return TRUE;
778                                 }
780                                 case GDK_Escape: {
781                                     // Cancel unimode.
782                                     tc->unimode = false;
783                                     gtk_im_context_reset(tc->imc);
784                                     ec->defaultMessageContext()->clear();
785                                     return TRUE;
786                                 }
788                                 case GDK_Shift_L:
789                                 case GDK_Shift_R:
790                                     break;
792                                 default: {
793                                     if (g_ascii_isxdigit(group0_keyval)) {
794                                         g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
795                                         tc->uni[tc->unipos++] = group0_keyval;
796                                         tc->uni[tc->unipos] = '\0';
797                                         if (tc->unipos == 8) {
798                                             /* This behaviour is partly to allow us to continue to
799                                                use a fixed-length buffer for tc->uni.  Reason for
800                                                choosing the number 8 is that it's the length of
801                                                ``canonical form'' mentioned in the ISO 14755 spec.
802                                                An advantage over choosing 6 is that it allows using
803                                                backspace for typos & misremembering when entering a
804                                                6-digit number. */
805                                             insert_uni_char(tc);
806                                         }
807                                         show_curr_uni_char(tc);
808                                         return TRUE;
809                                     } else {
810                                         /* The intent is to ignore but consume characters that could be
811                                            typos for hex digits.  Gtk seems to ignore & consume all
812                                            non-hex-digits, and we do similar here.  Though note that some
813                                            shortcuts (like keypad +/- for zoom) get processed before
814                                            reaching this code. */
815                                         return TRUE;
816                                     }
817                                 }
818                             }
819                         }
821                         bool (Inkscape::Text::Layout::iterator::*cursor_movement_operator)() = NULL;
823                         /* Neither unimode nor IM consumed key; process text tool shortcuts */
824                         switch (group0_keyval) {
825                             case GDK_x:
826                             case GDK_X:
827                                 if (MOD__ALT_ONLY) {
828                                     desktop->setToolboxFocusTo ("altx-text");
829                                     return TRUE;
830                                 }
831                                 break;
832                             case GDK_space:
833                                 if (MOD__CTRL_ONLY) {
834                                     /* No-break space */
835                                     if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
836                                         sp_text_context_setup_text(tc);
837                                         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
838                                     }
839                                     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
840                                     sp_text_context_update_cursor(tc);
841                                     sp_text_context_update_text_selection(tc);
842                                     ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
843                                     sp_document_done(sp_desktop_document(ec->desktop));
844                                     return TRUE;
845                                 }
846                                 break;
847                             case GDK_U:
848                             case GDK_u:
849                                 if (MOD__CTRL_ONLY) {
850                                     if (tc->unimode) {
851                                         tc->unimode = false;
852                                         ec->defaultMessageContext()->clear();
853                                     } else {
854                                         tc->unimode = true;
855                                         tc->unipos = 0;
856                                         ec->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode: "));
857                                     }
858                                     if (tc->imc) {
859                                         gtk_im_context_reset(tc->imc);
860                                     }
861                                     return TRUE;
862                                 }
863                                 break;
864                             case GDK_B:
865                             case GDK_b:
866                                 if (MOD__CTRL_ONLY && tc->text) {
867                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
868                                     SPCSSAttr *css = sp_repr_css_attr_new();
869                                     if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
870                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
871                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
872                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
873                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
874                                         sp_repr_css_set_property(css, "font-weight", "bold");
875                                     else
876                                         sp_repr_css_set_property(css, "font-weight", "normal");
877                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
878                                     sp_repr_css_attr_unref(css);
879                                     sp_document_done(sp_desktop_document(ec->desktop));
880                                     sp_text_context_update_cursor(tc);
881                                     sp_text_context_update_text_selection(tc);
882                                     return TRUE;
883                                 }
884                                 break;
885                             case GDK_I:
886                             case GDK_i:
887                                 if (MOD__CTRL_ONLY && tc->text) {
888                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
889                                     SPCSSAttr *css = sp_repr_css_attr_new();
890                                     if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL)
891                                         sp_repr_css_set_property(css, "font-style", "italic");
892                                     else
893                                         sp_repr_css_set_property(css, "font-style", "normal");
894                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
895                                     sp_repr_css_attr_unref(css);
896                                     sp_document_done(sp_desktop_document(ec->desktop));
897                                     sp_text_context_update_cursor(tc);
898                                     sp_text_context_update_text_selection(tc);
899                                     return TRUE;
900                                 }
901                                 break;
903                             case GDK_A:
904                             case GDK_a:
905                                 if (MOD__CTRL_ONLY && tc->text) {
906                                     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
907                                     if (layout) {
908                                         tc->text_sel_start = layout->begin();
909                                         tc->text_sel_end = layout->end();
910                                         sp_text_context_update_cursor(tc);
911                                         sp_text_context_update_text_selection(tc);
912                                         return TRUE;
913                                     }
914                                 }
915                                 break;
917                             case GDK_Return:
918                             case GDK_KP_Enter:
919                                 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
920                                     sp_text_context_setup_text(tc);
921                                     tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
922                                 }
923                                 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
924                                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
925                                 sp_text_context_update_cursor(tc);
926                                 sp_text_context_update_text_selection(tc);
927                                 sp_document_done(sp_desktop_document(ec->desktop));
928                                 return TRUE;
929                             case GDK_BackSpace:
930                                 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
931                                     if (tc->text_sel_start == tc->text_sel_end)
932                                         tc->text_sel_start.prevCursorPosition();
933                                     tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
934                                     sp_text_context_update_cursor(tc);
935                                     sp_text_context_update_text_selection(tc);
936                                     sp_document_done(sp_desktop_document(ec->desktop));
937                                 }
938                                 return TRUE;
939                             case GDK_Delete:
940                             case GDK_KP_Delete:
941                                 if (tc->text) {
942                                     if (tc->text_sel_start == tc->text_sel_end)
943                                         tc->text_sel_end.nextCursorPosition();
944                                     tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
945                                     sp_text_context_update_cursor(tc);
946                                     sp_text_context_update_text_selection(tc);
947                                     sp_document_done(sp_desktop_document(ec->desktop));
948                                 }
949                                 return TRUE;
950                             case GDK_Left:
951                             case GDK_KP_Left:
952                             case GDK_KP_4:
953                                 if (tc->text) {
954                                     if (MOD__ALT) {
955                                         if (MOD__SHIFT)
956                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-10, 0));
957                                         else
958                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-1, 0));
959                                         sp_text_context_update_cursor(tc);
960                                         sp_text_context_update_text_selection(tc);
961                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:left");
962                                     } else {
963                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorLeftWithControl
964                                                                              : &Inkscape::Text::Layout::iterator::cursorLeft;
965                                         break;
966                                     }
967                                 }
968                                 return TRUE;
969                             case GDK_Right:
970                             case GDK_KP_Right:
971                             case GDK_KP_6:
972                                 if (tc->text) {
973                                     if (MOD__ALT) {
974                                         if (MOD__SHIFT)
975                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(10, 0));
976                                         else
977                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(1, 0));
978                                         sp_text_context_update_cursor(tc);
979                                         sp_text_context_update_text_selection(tc);
980                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:right");
981                                     } else {
982                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorRightWithControl
983                                                                              : &Inkscape::Text::Layout::iterator::cursorRight;
984                                         break;
985                                     }
986                                 }
987                                 return TRUE;
988                             case GDK_Up:
989                             case GDK_KP_Up:
990                             case GDK_KP_8:
991                                 if (tc->text) {
992                                     if (MOD__ALT) {
993                                         if (MOD__SHIFT)
994                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -10));
995                                         else
996                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -1));
997                                         sp_text_context_update_cursor(tc);
998                                         sp_text_context_update_text_selection(tc);
999                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:up");
1000                                     } else {
1001                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorUpWithControl
1002                                                                              : &Inkscape::Text::Layout::iterator::cursorUp;
1003                                         break;
1004                                     }
1005                                 }
1006                                 return TRUE;
1007                             case GDK_Down:
1008                             case GDK_KP_Down:
1009                             case GDK_KP_2:
1010                                 if (tc->text) {
1011                                     if (MOD__ALT) {
1012                                         if (MOD__SHIFT)
1013                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 10));
1014                                         else
1015                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 1));
1016                                         sp_text_context_update_cursor(tc);
1017                                         sp_text_context_update_text_selection(tc);
1018                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:down");
1019                                     } else {
1020                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorDownWithControl
1021                                                                              : &Inkscape::Text::Layout::iterator::cursorDown;
1022                                         break;
1023                                     }
1024                                 }
1025                                 return TRUE;
1026                             case GDK_Home:
1027                             case GDK_KP_Home:
1028                                 if (tc->text) {
1029                                     if (MOD__CTRL)
1030                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfShape;
1031                                     else
1032                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfLine;
1033                                     break;
1034                                 }
1035                                 return TRUE;
1036                             case GDK_End:
1037                             case GDK_KP_End:
1038                                 if (tc->text) {
1039                                     if (MOD__CTRL)
1040                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::nextStartOfShape;
1041                                     else
1042                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisEndOfLine;
1043                                     break;
1044                                 }
1045                                 return TRUE;
1046                             case GDK_Escape:
1047                                 if (tc->creating) {
1048                                     tc->creating = 0;
1049                                     if (tc->grabbed) {
1050                                         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1051                                         tc->grabbed = NULL;
1052                                     }
1053                                     Inkscape::Rubberband::get()->stop();
1054                                 } else {
1055                                     sp_desktop_selection(ec->desktop)->clear();
1056                                 }
1057                                 tc->nascent_object = FALSE;
1058                                 return TRUE;
1059                             case GDK_bracketleft:
1060                                 if (tc->text) {
1061                                     if (MOD__ALT || MOD__CTRL) {
1062                                         if (MOD__ALT) {
1063                                             if (MOD__SHIFT) {
1064                                                 // FIXME: alt+shift+[] does not work, don't know why
1065                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1066                                             } else {
1067                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1068                                             }
1069                                         } else {
1070                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -90);
1071                                         }
1072                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "textrot:ccw");
1073                                         sp_text_context_update_cursor(tc);
1074                                         sp_text_context_update_text_selection(tc);
1075                                         return TRUE;
1076                                     }
1077                                 }
1078                                 break;
1079                             case GDK_bracketright:
1080                                 if (tc->text) {
1081                                     if (MOD__ALT || MOD__CTRL) {
1082                                         if (MOD__ALT) {
1083                                             if (MOD__SHIFT) {
1084                                                 // FIXME: alt+shift+[] does not work, don't know why
1085                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1086                                             } else {
1087                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1088                                             }
1089                                         } else {
1090                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 90);
1091                                         }
1092                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "textrot:cw");
1093                                         sp_text_context_update_cursor(tc);
1094                                         sp_text_context_update_text_selection(tc);
1095                                         return TRUE;
1096                                     }
1097                                 }
1098                                 break;
1099                             case GDK_less:
1100                             case GDK_comma:
1101                                 if (tc->text) {
1102                                     if (MOD__ALT) {
1103                                         if (MOD__CTRL) {
1104                                             if (MOD__SHIFT)
1105                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1106                                             else
1107                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1108                                             sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:dec");
1109                                         } else {
1110                                             if (MOD__SHIFT)
1111                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1112                                             else
1113                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1114                                             sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:dec");
1115                                         }
1116                                         sp_text_context_update_cursor(tc);
1117                                         sp_text_context_update_text_selection(tc);
1118                                         return TRUE;
1119                                     }
1120                                 }
1121                                 break;
1122                             case GDK_greater:
1123                             case GDK_period:
1124                                 if (tc->text) {
1125                                     if (MOD__ALT) {
1126                                         if (MOD__CTRL) {
1127                                             if (MOD__SHIFT)
1128                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1129                                             else
1130                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1131                                             sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:inc");
1132                                         } else {
1133                                             if (MOD__SHIFT)
1134                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1135                                             else
1136                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1137                                             sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:inc");
1138                                         }
1139                                         sp_text_context_update_cursor(tc);
1140                                         sp_text_context_update_text_selection(tc);
1141                                         return TRUE;
1142                                     }
1143                                 }
1144                                 break;
1145                             default:
1146                                 break;
1147                         }
1149                         if (cursor_movement_operator) {
1150                             Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
1151                             Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
1152                             (tc->text_sel_end.*cursor_movement_operator)();
1153                             if (!MOD__SHIFT)
1154                                 tc->text_sel_start = tc->text_sel_end;
1155                             if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1156                                 sp_text_context_update_cursor(tc);
1157                                 sp_text_context_update_text_selection(tc);
1158                             }
1159                             return TRUE;
1160                         }
1162                 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1163             } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1164                 // except up/down that are swallowed to prevent the zoom field from activation
1165                 if ((group0_keyval == GDK_Up    ||
1166                      group0_keyval == GDK_Down  ||
1167                      group0_keyval == GDK_KP_Up ||
1168                      group0_keyval == GDK_KP_Down )
1169                     && !MOD__CTRL_ONLY) {
1170                     return TRUE;
1171                 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1172                     if (tc->creating) {
1173                         tc->creating = 0;
1174                         if (tc->grabbed) {
1175                             sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1176                             tc->grabbed = NULL;
1177                         }
1178                         Inkscape::Rubberband::get()->stop();
1179                     }
1180                 }
1181             }
1182             break;
1183         }
1185         case GDK_KEY_RELEASE:
1186             if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1187                 return TRUE;
1188             }
1189             break;
1190         default:
1191             break;
1192     }
1194     // if nobody consumed it so far
1195     if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1196         return ((SPEventContextClass *) parent_class)->root_handler(ec, event); // send event to parent
1197     } else {
1198         return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1199     }
1202 /**
1203  Attempts to paste system clipboard into the currently edited text, returns true on success
1204  */
1205 bool
1206 sp_text_paste_inline(SPEventContext *ec)
1208     if (!SP_IS_TEXT_CONTEXT(ec))
1209         return false;
1211     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1213     if ((tc->text) || (tc->nascent_object)) {
1214         // there is an active text object in this context, or a new object was just created
1216         Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1217         Glib::ustring const text = refClipboard->wait_for_text();
1219         if (!text.empty()) {
1221             if (!tc->text) { // create text if none (i.e. if nascent_object)
1222                 sp_text_context_setup_text(tc);
1223                 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1224             }
1226             // using indices is slow in ustrings. Whatever.
1227             Glib::ustring::size_type begin = 0;
1228             for ( ; ; ) {
1229                 Glib::ustring::size_type end = text.find('\n', begin);
1230                 if (end == Glib::ustring::npos) {
1231                     if (begin != text.length())
1232                         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());
1233                     break;
1234                 }
1235                 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());
1236                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1237                 begin = end + 1;
1238             }
1239             sp_document_done(sp_desktop_document(ec->desktop));
1241             return true;
1242         }
1243     } // FIXME: else create and select a new object under cursor!
1245     return false;
1248 /**
1249  Gets the raw characters that comprise the currently selected text, converting line
1250  breaks into lf characters.
1251 */
1252 Glib::ustring
1253 sp_text_get_selected_text(SPEventContext const *ec)
1255     if (!SP_IS_TEXT_CONTEXT(ec))
1256         return "";
1257     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1258     if (tc->text == NULL)
1259         return "";
1261     return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1264 /**
1265  Deletes the currently selected characters. Returns false if there is no
1266  text selection currently.
1267 */
1268 bool sp_text_delete_selection(SPEventContext *ec)
1270     if (!SP_IS_TEXT_CONTEXT(ec))
1271         return false;
1272     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1273     if (tc->text == NULL)
1274         return false;
1276     if (tc->text_sel_start == tc->text_sel_end)
1277         return false;
1278     tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
1279     sp_text_context_update_cursor(tc);
1280     sp_text_context_update_text_selection(tc);
1281     return true;
1284 /**
1285  * \param selection Should not be NULL.
1286  */
1287 static void
1288 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1290     g_assert(selection != NULL);
1292     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1294     if (ec->shape_knot_holder) { // destroy knotholder
1295         sp_knot_holder_destroy(ec->shape_knot_holder);
1296         ec->shape_knot_holder = NULL;
1297     }
1299     if (ec->shape_repr) { // remove old listener
1300         sp_repr_remove_listener_by_data(ec->shape_repr, ec);
1301         Inkscape::GC::release(ec->shape_repr);
1302         ec->shape_repr = 0;
1303     }
1305     SPItem *item = selection->singleItem();
1306     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1307         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
1308         Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
1309         if (shape_repr) {
1310             ec->shape_repr = shape_repr;
1311             Inkscape::GC::anchor(shape_repr);
1312             sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
1313         }
1314     }
1316     if (tc->text && (item != tc->text)) {
1317         sp_text_context_forget_text(tc);
1318     }
1319     tc->text = NULL;
1321     if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1322         tc->text = item;
1323         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1324         if (layout)
1325             tc->text_sel_start = tc->text_sel_end = layout->end();
1326     } else {
1327         tc->text = NULL;
1328     }
1330     // we update cursor without scrolling, because this position may not be final;
1331     // item_handler moves cusros to the point of click immediately
1332     sp_text_context_update_cursor(tc, false);
1333     sp_text_context_update_text_selection(tc);
1336 static void
1337 sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc)
1339     sp_text_context_update_cursor(tc);
1340     sp_text_context_update_text_selection(tc);
1343 static bool
1344 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1346     if (tc->text == NULL)
1347         return false;
1348     if (tc->text_sel_start == tc->text_sel_end)
1349         return false;    // will get picked up by the parent and applied to the whole text object
1351     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1352     sp_document_done(sp_desktop_document(tc->desktop));
1353     sp_text_context_update_cursor(tc);
1354     sp_text_context_update_text_selection(tc);
1356     return true;
1359 static int
1360 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1362     if (tc->text == NULL)
1363         return QUERY_STYLE_NOTHING;
1364     const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1365     if (layout == NULL)
1366         return QUERY_STYLE_NOTHING;
1367     sp_text_context_validate_cursor_iterators(tc);
1369     GSList *styles_list = NULL;
1371     Inkscape::Text::Layout::iterator begin_it, end_it;
1372     if (tc->text_sel_start < tc->text_sel_end) {
1373         begin_it = tc->text_sel_start;
1374         end_it = tc->text_sel_end;
1375     } else {
1376         begin_it = tc->text_sel_end;
1377         end_it = tc->text_sel_start;
1378     }
1379     if (begin_it == end_it)
1380         if (!begin_it.prevCharacter())
1381             end_it.nextCharacter();
1382     for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1383         SPObject const *pos_obj = 0;
1384         void *rawptr = 0;
1385         layout->getSourceOfCharacter(it, &rawptr);
1386         pos_obj = SP_OBJECT(rawptr);
1387         if (pos_obj == 0) continue;
1388         while (SP_OBJECT_STYLE(pos_obj) == NULL && SP_OBJECT_PARENT(pos_obj))
1389             pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
1390         styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1391     }
1393     int result = sp_desktop_query_style_from_list (styles_list, style, property);
1395     g_slist_free(styles_list);
1396     return result;
1399 static void
1400 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1402     if (tc->text == NULL)
1403         return;
1404     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1405     if (layout) {     // undo can change the text length without us knowing it
1406         layout->validateIterator(&tc->text_sel_start);
1407         layout->validateIterator(&tc->text_sel_end);
1408     }
1411 static void
1412 sp_text_context_update_cursor(SPTextContext *tc,  bool scroll_to_see)
1414     GdkRectangle im_cursor = { 0, 0, 1, 1 };
1416     if (tc->text) {
1417         NR::Point p0, p1;
1418         sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1419         NR::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1420         NR::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1422         // scroll to show cursor
1423         if (scroll_to_see) {
1424             NR::Point const dm = (d0 + d1) / 2;
1425             // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1426             SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(&dm, 1.0);
1427         }
1429         sp_canvas_item_show(tc->cursor);
1430         sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1432         /* fixme: ... need another transformation to get canvas widget coordinate space? */
1433         im_cursor.x = (int) floor(d0[NR::X]);
1434         im_cursor.y = (int) floor(d0[NR::Y]);
1435         im_cursor.width = (int) floor(d1[NR::X]) - im_cursor.x;
1436         im_cursor.height = (int) floor(d1[NR::Y]) - im_cursor.y;
1438         tc->show = TRUE;
1439         tc->phase = 1;
1441         if (SP_IS_FLOWTEXT(tc->text)) {
1442             SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1443             if (frame) {
1444                 sp_canvas_item_show(tc->frame);
1445                 SP_CTRLRECT(tc->frame)->setRectangle(sp_item_bbox_desktop(frame));
1446             }
1447             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1448         } else {
1449             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1450         }
1452     } else {
1453         sp_canvas_item_hide(tc->cursor);
1454         sp_canvas_item_hide(tc->frame);
1455         tc->show = FALSE;
1456         if (!tc->nascent_object) {
1457             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
1458         }
1459     }
1461     if (tc->imc) {
1462         gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1463     }
1464     SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1467 static void sp_text_context_update_text_selection(SPTextContext *tc)
1469     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1470         sp_canvas_item_hide(*it);
1471         gtk_object_destroy(*it);
1472     }
1473     tc->text_selection_quads.clear();
1475     std::vector<NR::Point> quads;
1476     if (tc->text != NULL)
1477         quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1478     for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1479         SPCanvasItem *quad_canvasitem;
1480         quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1481         sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x000000ff);
1482         sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1483         sp_canvas_item_show(quad_canvasitem);
1484         tc->text_selection_quads.push_back(quad_canvasitem);
1485     }
1488 static gint
1489 sp_text_context_timeout(SPTextContext *tc)
1491     if (tc->show) {
1492         if (tc->phase) {
1493             tc->phase = 0;
1494             sp_canvas_item_hide(tc->cursor);
1495         } else {
1496             tc->phase = 1;
1497             sp_canvas_item_show(tc->cursor);
1498         }
1499     }
1501     return TRUE;
1504 static void
1505 sp_text_context_forget_text(SPTextContext *tc)
1507     if (! tc->text) return;
1508     SPItem *ti = tc->text;
1509     /* We have to set it to zero,
1510      * or selection changed signal messes everything up */
1511     tc->text = NULL;
1512     if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1513         Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1514         // the repr may already have been unparented
1515         // if we were called e.g. as the result of
1516         // an undo or the element being removed from
1517         // the XML editor
1518         if ( text_repr && sp_repr_parent(text_repr) ) {
1519             sp_repr_unparent(text_repr);
1520         }
1521     }
1524 gint
1525 sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1527     gtk_im_context_focus_in(tc->imc);
1528     return FALSE;
1531 gint
1532 sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1534     gtk_im_context_focus_out(tc->imc);
1535     return FALSE;
1538 static void
1539 sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc)
1541     if (!tc->text) {
1542         sp_text_context_setup_text(tc);
1543         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1544     }
1546     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1547     sp_text_context_update_cursor(tc);
1548     sp_text_context_update_text_selection(tc);
1550     sp_document_done(SP_OBJECT_DOCUMENT(tc->text));
1554 /*
1555   Local Variables:
1556   mode:c++
1557   c-file-style:"stroustrup"
1558   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1559   indent-tabs-mode:nil
1560   fill-column:99
1561   End:
1562 */
1563 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :