Code

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