Code

Tweaked smaller size to be 3/4ths the menu size
[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_DT_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_DT_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_DT_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_DT_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_DT_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_DT_SELECTION(desktop)->connectChanged(
265         sigc::bind(sigc::ptr_fun(&sp_text_context_selection_changed), tc)
266         );
267     tc->sel_modified_connection = SP_DT_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_DT_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_DT_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_DT_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_DT_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_DT_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_DT_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 | GDK_POINTER_MOTION_HINT_MASK,
614                                     NULL, event->button.time);
615                 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
616                 tc->creating = 1;
618                 /* Processed */
619                 return TRUE;
620             }
621             break;
622         case GDK_MOTION_NOTIFY:
623             if (tc->over_text) {
624                 tc->over_text = 0;
625                 // update cursor and statusbar: we are not over a text object now
626                 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_DT_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_DT_SELECTION(desktop)->set(ft);
696                         ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
697                         sp_document_done(SP_DT_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_space:
809                                 if (MOD__CTRL_ONLY) {
810                                     /* No-break space */
811                                     if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
812                                         sp_text_context_setup_text(tc);
813                                         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
814                                     }
815                                     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
816                                     sp_text_context_update_cursor(tc);
817                                     sp_text_context_update_text_selection(tc);
818                                     ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
819                                     sp_document_done(SP_DT_DOCUMENT(ec->desktop));
820                                     return TRUE;
821                                 }
822                                 break;
823                             case GDK_U:
824                             case GDK_u:
825                                 if (MOD__CTRL_ONLY) {
826                                     if (tc->unimode) {
827                                         tc->unimode = false;
828                                         ec->defaultMessageContext()->clear();
829                                     } else {
830                                         tc->unimode = true;
831                                         tc->unipos = 0;
832                                         ec->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode: "));
833                                     }
834                                     if (tc->imc) {
835                                         gtk_im_context_reset(tc->imc);
836                                     }
837                                     return TRUE;
838                                 }
839                                 break;
840                             case GDK_B:
841                             case GDK_b:
842                                 if (MOD__CTRL_ONLY && tc->text) {
843                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
844                                     SPCSSAttr *css = sp_repr_css_attr_new();
845                                     if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
846                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
847                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
848                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
849                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
850                                         sp_repr_css_set_property(css, "font-weight", "bold");
851                                     else
852                                         sp_repr_css_set_property(css, "font-weight", "normal");
853                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
854                                     sp_repr_css_attr_unref(css);
855                                     sp_document_done(SP_DT_DOCUMENT(ec->desktop));
856                                     sp_text_context_update_cursor(tc);
857                                     sp_text_context_update_text_selection(tc);
858                                     return TRUE;
859                                 }
860                                 break;
861                             case GDK_I:
862                             case GDK_i:
863                                 if (MOD__CTRL_ONLY && tc->text) {
864                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
865                                     SPCSSAttr *css = sp_repr_css_attr_new();
866                                     if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL)
867                                         sp_repr_css_set_property(css, "font-style", "italic");
868                                     else
869                                         sp_repr_css_set_property(css, "font-style", "normal");
870                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
871                                     sp_repr_css_attr_unref(css);
872                                     sp_document_done(SP_DT_DOCUMENT(ec->desktop));
873                                     sp_text_context_update_cursor(tc);
874                                     sp_text_context_update_text_selection(tc);
875                                     return TRUE;
876                                 }
877                                 break;
879                             case GDK_A:
880                             case GDK_a:
881                                 if (MOD__CTRL_ONLY && tc->text) {
882                                     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
883                                     if (layout) {
884                                         tc->text_sel_start = layout->begin();
885                                         tc->text_sel_end = layout->end();
886                                         sp_text_context_update_cursor(tc);
887                                         sp_text_context_update_text_selection(tc);
888                                         return TRUE;
889                                     }
890                                 }
891                                 break;
893                             case GDK_Return:
894                             case GDK_KP_Enter:
895                                 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
896                                     sp_text_context_setup_text(tc);
897                                     tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
898                                 }
899                                 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
900                                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
901                                 sp_text_context_update_cursor(tc);
902                                 sp_text_context_update_text_selection(tc);
903                                 sp_document_done(SP_DT_DOCUMENT(ec->desktop));
904                                 return TRUE;
905                             case GDK_BackSpace:
906                                 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
907                                     if (tc->text_sel_start == tc->text_sel_end)
908                                         tc->text_sel_start.prevCursorPosition();
909                                     tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
910                                     sp_text_context_update_cursor(tc);
911                                     sp_text_context_update_text_selection(tc);
912                                     sp_document_done(SP_DT_DOCUMENT(ec->desktop));
913                                 }
914                                 return TRUE;
915                             case GDK_Delete:
916                             case GDK_KP_Delete:
917                                 if (tc->text) {
918                                     if (tc->text_sel_start == tc->text_sel_end)
919                                         tc->text_sel_end.nextCursorPosition();
920                                     tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
921                                     sp_text_context_update_cursor(tc);
922                                     sp_text_context_update_text_selection(tc);
923                                     sp_document_done(SP_DT_DOCUMENT(ec->desktop));
924                                 }
925                                 return TRUE;
926                             case GDK_Left:
927                             case GDK_KP_Left:
928                             case GDK_KP_4:
929                                 if (tc->text) {
930                                     if (MOD__ALT) {
931                                         if (MOD__SHIFT)
932                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-10, 0));
933                                         else
934                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-1, 0));
935                                         sp_text_context_update_cursor(tc);
936                                         sp_text_context_update_text_selection(tc);
937                                         sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "kern:left");
938                                     } else {
939                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorLeftWithControl
940                                                                              : &Inkscape::Text::Layout::iterator::cursorLeft;
941                                         break;
942                                     }
943                                 }
944                                 return TRUE;
945                             case GDK_Right:
946                             case GDK_KP_Right:
947                             case GDK_KP_6:
948                                 if (tc->text) {
949                                     if (MOD__ALT) {
950                                         if (MOD__SHIFT)
951                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(10, 0));
952                                         else
953                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(1, 0));
954                                         sp_text_context_update_cursor(tc);
955                                         sp_text_context_update_text_selection(tc);
956                                         sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "kern:right");
957                                     } else {
958                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorRightWithControl
959                                                                              : &Inkscape::Text::Layout::iterator::cursorRight;
960                                         break;
961                                     }
962                                 }
963                                 return TRUE;
964                             case GDK_Up:
965                             case GDK_KP_Up:
966                             case GDK_KP_8:
967                                 if (tc->text) {
968                                     if (MOD__ALT) {
969                                         if (MOD__SHIFT)
970                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -10));
971                                         else
972                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -1));
973                                         sp_text_context_update_cursor(tc);
974                                         sp_text_context_update_text_selection(tc);
975                                         sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "kern:up");
976                                     } else {
977                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorUpWithControl
978                                                                              : &Inkscape::Text::Layout::iterator::cursorUp;
979                                         break;
980                                     }
981                                 }
982                                 return TRUE;
983                             case GDK_Down:
984                             case GDK_KP_Down:
985                             case GDK_KP_2:
986                                 if (tc->text) {
987                                     if (MOD__ALT) {
988                                         if (MOD__SHIFT)
989                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 10));
990                                         else
991                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 1));
992                                         sp_text_context_update_cursor(tc);
993                                         sp_text_context_update_text_selection(tc);
994                                         sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "kern:down");
995                                     } else {
996                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorDownWithControl
997                                                                              : &Inkscape::Text::Layout::iterator::cursorDown;
998                                         break;
999                                     }
1000                                 }
1001                                 return TRUE;
1002                             case GDK_Home:
1003                             case GDK_KP_Home:
1004                                 if (tc->text) {
1005                                     if (MOD__CTRL)
1006                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfShape;
1007                                     else
1008                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfLine;
1009                                     break;
1010                                 }
1011                                 return TRUE;
1012                             case GDK_End:
1013                             case GDK_KP_End:
1014                                 if (tc->text) {
1015                                     if (MOD__CTRL)
1016                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::nextStartOfShape;
1017                                     else
1018                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisEndOfLine;
1019                                     break;
1020                                 }
1021                                 return TRUE;
1022                             case GDK_Escape:
1023                                 if (tc->creating) {
1024                                     tc->creating = 0;
1025                                     if (tc->grabbed) {
1026                                         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1027                                         tc->grabbed = NULL;
1028                                     }
1029                                     Inkscape::Rubberband::get()->stop();
1030                                 } else {
1031                                     SP_DT_SELECTION(ec->desktop)->clear();
1032                                 }
1033                                 return TRUE;
1034                             case GDK_bracketleft:
1035                                 if (tc->text) {
1036                                     if (MOD__ALT || MOD__CTRL) {
1037                                         if (MOD__ALT) {
1038                                             if (MOD__SHIFT) {
1039                                                 // FIXME: alt+shift+[] does not work, don't know why
1040                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1041                                             } else {
1042                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1043                                             }
1044                                         } else {
1045                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -90);
1046                                         }
1047                                         sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "textrot:ccw");
1048                                         sp_text_context_update_cursor(tc);
1049                                         sp_text_context_update_text_selection(tc);
1050                                         return TRUE;
1051                                     }
1052                                 }
1053                                 break;
1054                             case GDK_bracketright:
1055                                 if (tc->text) {
1056                                     if (MOD__ALT || MOD__CTRL) {
1057                                         if (MOD__ALT) {
1058                                             if (MOD__SHIFT) {
1059                                                 // FIXME: alt+shift+[] does not work, don't know why
1060                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1061                                             } else {
1062                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1063                                             }
1064                                         } else {
1065                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 90);
1066                                         }
1067                                         sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "textrot:cw");
1068                                         sp_text_context_update_cursor(tc);
1069                                         sp_text_context_update_text_selection(tc);
1070                                         return TRUE;
1071                                     }
1072                                 }
1073                                 break;
1074                             case GDK_less:
1075                             case GDK_comma:
1076                                 if (tc->text) {
1077                                     if (MOD__ALT) {
1078                                         if (MOD__CTRL) {
1079                                             if (MOD__SHIFT)
1080                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1081                                             else
1082                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1083                                             sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "linespacing:dec");
1084                                         } else {
1085                                             if (MOD__SHIFT)
1086                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1087                                             else
1088                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1089                                             sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "letterspacing:dec");
1090                                         }
1091                                         sp_text_context_update_cursor(tc);
1092                                         sp_text_context_update_text_selection(tc);
1093                                         return TRUE;
1094                                     }
1095                                 }
1096                                 break;
1097                             case GDK_greater:
1098                             case GDK_period:
1099                                 if (tc->text) {
1100                                     if (MOD__ALT) {
1101                                         if (MOD__CTRL) {
1102                                             if (MOD__SHIFT)
1103                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1104                                             else
1105                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1106                                             sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "linespacing:inc");
1107                                         } else {
1108                                             if (MOD__SHIFT)
1109                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1110                                             else
1111                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1112                                             sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "letterspacing:inc");
1113                                         }
1114                                         sp_text_context_update_cursor(tc);
1115                                         sp_text_context_update_text_selection(tc);
1116                                         return TRUE;
1117                                     }
1118                                 }
1119                                 break;
1120                             default:
1121                                 break;
1122                         }
1124                         if (cursor_movement_operator) {
1125                             Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
1126                             Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
1127                             (tc->text_sel_end.*cursor_movement_operator)();
1128                             if (!MOD__SHIFT)
1129                                 tc->text_sel_start = tc->text_sel_end;
1130                             if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1131                                 sp_text_context_update_cursor(tc);
1132                                 sp_text_context_update_text_selection(tc);
1133                             }
1134                             return TRUE;
1135                         }
1137                 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1138             } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1139                 // except up/down that are swallowed to prevent the zoom field from activation
1140                 if ((group0_keyval == GDK_Up    ||
1141                      group0_keyval == GDK_Down  ||
1142                      group0_keyval == GDK_KP_Up ||
1143                      group0_keyval == GDK_KP_Down )
1144                     && !MOD__CTRL_ONLY) {
1145                     return TRUE;
1146                 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1147                     if (tc->creating) {
1148                         tc->creating = 0;
1149                         if (tc->grabbed) {
1150                             sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1151                             tc->grabbed = NULL;
1152                         }
1153                         Inkscape::Rubberband::get()->stop();
1154                     }
1155                 }
1156             }
1157             break;
1158         }
1160         case GDK_KEY_RELEASE:
1161             if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1162                 return TRUE;
1163             }
1164             break;
1165         default:
1166             break;
1167     }
1169     // if nobody consumed it so far
1170     if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1171         return ((SPEventContextClass *) parent_class)->root_handler(ec, event); // send event to parent
1172     } else {
1173         return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1174     }
1177 /**
1178  Attempts to paste system clipboard into the currently edited text, returns true on success
1179  */
1180 bool
1181 sp_text_paste_inline(SPEventContext *ec)
1183     if (!SP_IS_TEXT_CONTEXT(ec))
1184         return false;
1186     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1188     if ((tc->text) || (tc->nascent_object)) {
1189         // there is an active text object in this context, or a new object was just created
1191         Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1192         Glib::ustring const text = refClipboard->wait_for_text();
1194         if (!text.empty()) {
1196             if (!tc->text) { // create text if none (i.e. if nascent_object)
1197                 sp_text_context_setup_text(tc);
1198                 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1199             }
1201             // using indices is slow in ustrings. Whatever.
1202             Glib::ustring::size_type begin = 0;
1203             for ( ; ; ) {
1204                 Glib::ustring::size_type end = text.find('\n', begin);
1205                 if (end == Glib::ustring::npos) {
1206                     if (begin != text.length())
1207                         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());
1208                     break;
1209                 }
1210                 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());
1211                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1212                 begin = end + 1;
1213             }
1214             sp_document_done(SP_DT_DOCUMENT(ec->desktop));
1216             return true;
1217         }
1218     } // FIXME: else create and select a new object under cursor!
1220     return false;
1223 /**
1224  Gets the raw characters that comprise the currently selected text, converting line
1225  breaks into lf characters.
1226 */
1227 Glib::ustring
1228 sp_text_get_selected_text(SPEventContext const *ec)
1230     if (!SP_IS_TEXT_CONTEXT(ec))
1231         return "";
1232     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1233     if (tc->text == NULL)
1234         return "";
1236     return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1239 /**
1240  Deletes the currently selected characters. Returns false if there is no
1241  text selection currently.
1242 */
1243 bool sp_text_delete_selection(SPEventContext *ec)
1245     if (!SP_IS_TEXT_CONTEXT(ec))
1246         return false;
1247     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1248     if (tc->text == NULL)
1249         return false;
1251     if (tc->text_sel_start == tc->text_sel_end)
1252         return false;
1253     tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
1254     sp_text_context_update_cursor(tc);
1255     sp_text_context_update_text_selection(tc);
1256     return true;
1259 /**
1260  * \param selection Should not be NULL.
1261  */
1262 static void
1263 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1265     g_assert(selection != NULL);
1267     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1269     if (ec->shape_knot_holder) { // destroy knotholder
1270         sp_knot_holder_destroy(ec->shape_knot_holder);
1271         ec->shape_knot_holder = NULL;
1272     }
1274     if (ec->shape_repr) { // remove old listener
1275         sp_repr_remove_listener_by_data(ec->shape_repr, ec);
1276         Inkscape::GC::release(ec->shape_repr);
1277         ec->shape_repr = 0;
1278     }
1280     SPItem *item = selection->singleItem();
1281     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1282         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
1283         Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
1284         if (shape_repr) {
1285             ec->shape_repr = shape_repr;
1286             Inkscape::GC::anchor(shape_repr);
1287             sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
1288         }
1289     }
1291     if (tc->text && (item != tc->text)) {
1292         sp_text_context_forget_text(tc);
1293     }
1294     tc->text = NULL;
1296     if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1297         tc->text = item;
1298         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1299         if (layout)
1300             tc->text_sel_start = tc->text_sel_end = layout->end();
1301     } else {
1302         tc->text = NULL;
1303     }
1305     // we update cursor without scrolling, because this position may not be final;
1306     // item_handler moves cusros to the point of click immediately
1307     sp_text_context_update_cursor(tc, false);
1308     sp_text_context_update_text_selection(tc);
1311 static void
1312 sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc)
1314     sp_text_context_update_cursor(tc);
1315     sp_text_context_update_text_selection(tc);
1318 static bool
1319 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1321     if (tc->text == NULL)
1322         return false;
1323     if (tc->text_sel_start == tc->text_sel_end)
1324         return false;    // will get picked up by the parent and applied to the whole text object
1326     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1327     sp_document_done(SP_DT_DOCUMENT(tc->desktop));
1328     sp_text_context_update_cursor(tc);
1329     sp_text_context_update_text_selection(tc);
1331     return true;
1334 static int
1335 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1337     if (tc->text == NULL)
1338         return QUERY_STYLE_NOTHING;
1339     const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1340     if (layout == NULL)
1341         return QUERY_STYLE_NOTHING;
1342     sp_text_context_validate_cursor_iterators(tc);
1344     GSList *styles_list = NULL;
1346     Inkscape::Text::Layout::iterator begin_it, end_it;
1347     if (tc->text_sel_start < tc->text_sel_end) {
1348         begin_it = tc->text_sel_start;
1349         end_it = tc->text_sel_end;
1350     } else {
1351         begin_it = tc->text_sel_end;
1352         end_it = tc->text_sel_start;
1353     }
1354     if (begin_it == end_it)
1355         if (!begin_it.prevCharacter())
1356             end_it.nextCharacter();
1357     for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1358         SPObject const *pos_obj = NULL;
1359         layout->getSourceOfCharacter(it, (void**)&pos_obj);
1360         if (pos_obj == NULL) continue;
1361         while (SP_OBJECT_STYLE(pos_obj) == NULL && SP_OBJECT_PARENT(pos_obj))
1362             pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
1363         styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1364     }
1366     int result = sp_desktop_query_style_from_list (styles_list, style, property);
1368     g_slist_free(styles_list);
1369     return result;
1372 static void
1373 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1375     if (tc->text == NULL)
1376         return;
1377     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1378     if (layout) {     // undo can change the text length without us knowing it
1379         layout->validateIterator(&tc->text_sel_start);
1380         layout->validateIterator(&tc->text_sel_end);
1381     }
1384 static void
1385 sp_text_context_update_cursor(SPTextContext *tc,  bool scroll_to_see)
1387     GdkRectangle im_cursor = { 0, 0, 1, 1 };
1389     if (tc->text) {
1390         NR::Point p0, p1;
1391         sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1392         NR::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1393         NR::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1395         // scroll to show cursor
1396         if (scroll_to_see) {
1397             NR::Point const dm = (d0 + d1) / 2;
1398             // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1399             SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(&dm, 1.0);
1400         }
1402         sp_canvas_item_show(tc->cursor);
1403         sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1405         /* fixme: ... need another transformation to get canvas widget coordinate space? */
1406         im_cursor.x = (int) floor(d0[NR::X]);
1407         im_cursor.y = (int) floor(d0[NR::Y]);
1408         im_cursor.width = (int) floor(d1[NR::X]) - im_cursor.x;
1409         im_cursor.height = (int) floor(d1[NR::Y]) - im_cursor.y;
1411         tc->show = TRUE;
1412         tc->phase = 1;
1414         if (SP_IS_FLOWTEXT(tc->text)) {
1415             SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1416             if (frame) {
1417                 sp_canvas_item_show(tc->frame);
1418                 SP_CTRLRECT(tc->frame)->setRectangle(sp_item_bbox_desktop(frame));
1419             }
1420             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1421         } else {
1422             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1423         }
1425     } else {
1426         sp_canvas_item_hide(tc->cursor);
1427         sp_canvas_item_hide(tc->frame);
1428         tc->show = FALSE;
1429         if (!tc->nascent_object) {
1430             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
1431         }
1432     }
1434     if (tc->imc) {
1435         gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1436     }
1437     SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1440 static void sp_text_context_update_text_selection(SPTextContext *tc)
1442     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1443         sp_canvas_item_hide(*it);
1444         gtk_object_destroy(*it);
1445     }
1446     tc->text_selection_quads.clear();
1448     std::vector<NR::Point> quads;
1449     if (tc->text != NULL)
1450         quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1451     for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1452         SPCanvasItem *quad_canvasitem;
1453         quad_canvasitem = sp_canvas_item_new(SP_DT_CONTROLS(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1454         sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x000000ff);
1455         sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1456         sp_canvas_item_show(quad_canvasitem);
1457         tc->text_selection_quads.push_back(quad_canvasitem);
1458     }
1461 static gint
1462 sp_text_context_timeout(SPTextContext *tc)
1464     if (tc->show) {
1465         if (tc->phase) {
1466             tc->phase = 0;
1467             sp_canvas_item_hide(tc->cursor);
1468         } else {
1469             tc->phase = 1;
1470             sp_canvas_item_show(tc->cursor);
1471         }
1472     }
1474     return TRUE;
1477 static void
1478 sp_text_context_forget_text(SPTextContext *tc)
1480     if (! tc->text) return;
1481     SPItem *ti = tc->text;
1482     /* We have to set it to zero,
1483      * or selection changed signal messes everything up */
1484     tc->text = NULL;
1485     if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1486         Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1487         // the repr may already have been unparented
1488         // if we were called e.g. as the result of
1489         // an undo or the element being removed from
1490         // the XML editor
1491         if ( text_repr && sp_repr_parent(text_repr) ) {
1492             sp_repr_unparent(text_repr);
1493         }
1494     }
1497 gint
1498 sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1500     gtk_im_context_focus_in(tc->imc);
1501     return FALSE;
1504 gint
1505 sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1507     gtk_im_context_focus_out(tc->imc);
1508     return FALSE;
1511 static void
1512 sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc)
1514     if (!tc->text) {
1515         sp_text_context_setup_text(tc);
1516         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1517     }
1519     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1520     sp_text_context_update_cursor(tc);
1521     sp_text_context_update_text_selection(tc);
1523     sp_document_done(SP_OBJECT_DOCUMENT(tc->text));
1527 /*
1528   Local Variables:
1529   mode:c++
1530   c-file-style:"stroustrup"
1531   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1532   indent-tabs-mode:nil
1533   fill-column:99
1534   End:
1535 */
1536 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :