Code

excise never-used code and stale comments
[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             sp_repr_synthesize_events(shape_repr, &ec_shape_repr_events, ec);
262         }
263     }
265     tc->sel_changed_connection = SP_DT_SELECTION(desktop)->connectChanged(
266         sigc::bind(sigc::ptr_fun(&sp_text_context_selection_changed), tc)
267         );
268     tc->sel_modified_connection = SP_DT_SELECTION(desktop)->connectModified(
269         sigc::bind(sigc::ptr_fun(&sp_text_context_selection_modified), tc)
270         );
271     tc->style_set_connection = desktop->connectSetStyle(
272         sigc::bind(sigc::ptr_fun(&sp_text_context_style_set), tc)
273         );
274     tc->style_query_connection = desktop->connectQueryStyle(
275         sigc::bind(sigc::ptr_fun(&sp_text_context_style_query), tc)
276         );
278     sp_text_context_selection_changed(SP_DT_SELECTION(desktop), tc);
280     if (prefs_get_int_attribute("tools.text", "selcue", 0) != 0) {
281         ec->enableSelectionCue();
282     }
283     if (prefs_get_int_attribute("tools.text", "gradientdrag", 0) != 0) {
284         ec->enableGrDrag();
285     }
288 static void
289 sp_text_context_finish(SPEventContext *ec)
291     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
293     ec->enableGrDrag(false);
295     tc->style_set_connection.disconnect();
296     tc->style_query_connection.disconnect();
297     tc->sel_changed_connection.disconnect();
298     tc->sel_modified_connection.disconnect();
300     sp_text_context_forget_text(SP_TEXT_CONTEXT(ec));
302     if (tc->imc) {
303         g_object_unref(G_OBJECT(tc->imc));
304         tc->imc = NULL;
305     }
307     if (tc->timeout) {
308         gtk_timeout_remove(tc->timeout);
309         tc->timeout = 0;
310     }
312     if (tc->cursor) {
313         gtk_object_destroy(GTK_OBJECT(tc->cursor));
314         tc->cursor = NULL;
315     }
317     if (tc->indicator) {
318         gtk_object_destroy(GTK_OBJECT(tc->indicator));
319         tc->indicator = NULL;
320     }
322     if (tc->frame) {
323         gtk_object_destroy(GTK_OBJECT(tc->frame));
324         tc->frame = NULL;
325     }
327     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ;
328          it != tc->text_selection_quads.end() ; ++it) {
329         sp_canvas_item_hide(*it);
330         gtk_object_destroy(*it);
331     }
332     tc->text_selection_quads.clear();
334     if (ec->desktop) {
335         sp_signal_disconnect_by_data(SP_DT_CANVAS(ec->desktop), tc);
336     }
340 static gint
341 sp_text_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event)
343     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
344     SPDesktop *desktop = ec->desktop;
345     SPItem *item_ungrouped;
347     gint ret = FALSE;
349     sp_text_context_validate_cursor_iterators(tc);
351     switch (event->type) {
352         case GDK_BUTTON_PRESS:
353             if (event->button.button == 1) {
354                 // find out clicked item, disregarding groups
355                 item_ungrouped = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE);
356                 if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
357                     SP_DT_SELECTION(ec->desktop)->set(item_ungrouped);
358                     if (tc->text) {
359                         // find out click point in document coordinates
360                         NR::Point p = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
361                         // set the cursor closest to that point
362                         tc->text_sel_start = tc->text_sel_end = sp_te_get_position_by_coords(tc->text, p);
363                         // update display
364                         sp_text_context_update_cursor(tc);
365                         sp_text_context_update_text_selection(tc);
366                         tc->dragging = 1;
367                     }
368                     ret = TRUE;
369                 }
370             }
371             break;
372         case GDK_2BUTTON_PRESS:
373             if (event->button.button == 1 && tc->text) {
374                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
375                 if (layout) {
376                     if (!layout->isStartOfWord(tc->text_sel_start))
377                         tc->text_sel_start.prevStartOfWord();
378                     if (!layout->isEndOfWord(tc->text_sel_end))
379                         tc->text_sel_end.nextEndOfWord();
380                     sp_text_context_update_cursor(tc);
381                     sp_text_context_update_text_selection(tc);
382                     tc->dragging = 2;
383                     ret = TRUE;
384                 }
385             }
386             break;
387         case GDK_3BUTTON_PRESS:
388             if (event->button.button == 1 && tc->text) {
389                 tc->text_sel_start.thisStartOfLine();
390                 tc->text_sel_end.thisEndOfLine();
391                 sp_text_context_update_cursor(tc);
392                 sp_text_context_update_text_selection(tc);
393                 tc->dragging = 3;
394                 ret = TRUE;
395             }
396             break;
397         case GDK_BUTTON_RELEASE:
398             if (event->button.button == 1 && tc->dragging) {
399                 tc->dragging = 0;
400                 ret = TRUE;
401             }
402             break;
403         case GDK_MOTION_NOTIFY:
404             if (event->motion.state & GDK_BUTTON1_MASK && tc->dragging) {
405                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
406                 if (!layout) break;
407                 // find out click point in document coordinates
408                 NR::Point p = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
409                 // set the cursor closest to that point
410                 Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(tc->text, p);
411                 if (tc->dragging == 2) {
412                     // double-click dragging: go by word
413                     if (new_end < tc->text_sel_start) {
414                         if (!layout->isStartOfWord(new_end))
415                             new_end.prevStartOfWord();
416                     } else
417                         if (!layout->isEndOfWord(new_end))
418                             new_end.nextEndOfWord();
419                 } else if (tc->dragging == 3) {
420                     // triple-click dragging: go by line
421                     if (new_end < tc->text_sel_start)
422                         new_end.thisStartOfLine();
423                     else
424                         new_end.thisEndOfLine();
425                 }
426                 // update display
427                 if (tc->text_sel_end != new_end) {
428                     tc->text_sel_end = new_end;
429                     sp_text_context_update_cursor(tc);
430                     sp_text_context_update_text_selection(tc);
431                 }
432                 ret = TRUE;
433                 break;
434             }
435             // find out item under mouse, disregarding groups
436             item_ungrouped = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE);
437             if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
438                 sp_canvas_item_show(tc->indicator);
439                 SP_CTRLRECT(tc->indicator)->setRectangle(sp_item_bbox_desktop(item_ungrouped));
441                 ec->cursor_shape = cursor_text_insert_xpm;
442                 ec->hot_x = 7;
443                 ec->hot_y = 10;
444                 sp_event_context_update_cursor(ec);
445                 sp_text_context_update_text_selection(tc);
447                 if (SP_IS_TEXT (item_ungrouped)) {
448                     desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
449                 } else {
450                     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."));
451                 }
453                 tc->over_text = true;
455                 ret = TRUE;
456             }
457             break;
458         default:
459             break;
460     }
462     if (!ret) {
463         if (((SPEventContextClass *) parent_class)->item_handler)
464             ret = ((SPEventContextClass *) parent_class)->item_handler(ec, item, event);
465     }
467     return ret;
470 static void
471 sp_text_context_setup_text(SPTextContext *tc)
473     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
475     /* Create <text> */
476     Inkscape::XML::Node *rtext = sp_repr_new("svg:text");
477     rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
479     /* Set style */
480     sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "tools.text", true);
482     sp_repr_set_svg_double(rtext, "x", tc->pdoc[NR::X]);
483     sp_repr_set_svg_double(rtext, "y", tc->pdoc[NR::Y]);
485     /* Create <tspan> */
486     Inkscape::XML::Node *rtspan = sp_repr_new("svg:tspan");
487     rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
488     rtext->addChild(rtspan, NULL);
489     Inkscape::GC::release(rtspan);
491     /* Create TEXT */
492     Inkscape::XML::Node *rstring = sp_repr_new_text("");
493     rtspan->addChild(rstring, NULL);
494     Inkscape::GC::release(rstring);
495     SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
496     /* fixme: Is selection::changed really immediate? */
497     /* yes, it's immediate .. why does it matter? */
498     SP_DT_SELECTION(ec->desktop)->set(text_item);
499     Inkscape::GC::release(rtext);
500     text_item->transform = SP_ITEM(ec->desktop->currentRoot())->getRelativeTransform(ec->desktop->currentLayer());
501     text_item->updateRepr();
502     sp_document_done(SP_DT_DOCUMENT(ec->desktop));
505 /**
506  * Insert the character indicated by tc.uni to replace the current selection,
507  * and reset tc.uni/tc.unipos to empty string.
508  *
509  * \pre tc.uni/tc.unipos non-empty.
510  */
511 static void
512 insert_uni_char(SPTextContext *const tc)
514     g_return_if_fail(tc->unipos
515                      && tc->unipos < sizeof(tc->uni)
516                      && tc->uni[tc->unipos] == '\0');
517     unsigned int uv;
518     sscanf(tc->uni, "%x", &uv);
519     tc->unipos = 0;
520     tc->uni[tc->unipos] = '\0';
522     if ( !g_unichar_isprint(static_cast<gunichar>(uv))
523          && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
524         // This may be due to bad input, so it goes to statusbar.
525         tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
526                                            _("Non-printable character"));
527     } else {
528         if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
529             sp_text_context_setup_text(tc);
530             tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
531         }
533         gchar u[10];
534         guint const len = g_unichar_to_utf8(uv, u);
535         u[len] = '\0';
537         tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
538         sp_text_context_update_cursor(tc);
539         sp_text_context_update_text_selection(tc);
540         sp_document_done(SP_DT_DOCUMENT(tc->desktop));
541     }
544 static void
545 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
547     unsigned int uv;
548     sscanf(hex, "%x", &uv);
549     if (!g_unichar_isprint((gunichar) uv)) {
550         uv = 0xfffd;
551     }
552     guint const len = g_unichar_to_utf8(uv, utf8);
553     utf8[len] = '\0';
556 static void
557 show_curr_uni_char(SPTextContext *const tc)
559     g_return_if_fail(tc->unipos < sizeof(tc->uni)
560                      && tc->uni[tc->unipos] == '\0');
561     if (tc->unipos) {
562         char utf8[10];
563         hex_to_printable_utf8_buf(tc->uni, utf8);
565         /* Status bar messages are in pango markup, so we need xml escaping. */
566         if (utf8[1] == '\0') {
567             switch(utf8[0]) {
568                 case '<': strcpy(utf8, "&lt;"); break;
569                 case '>': strcpy(utf8, "&gt;"); break;
570                 case '&': strcpy(utf8, "&amp;"); break;
571                 default: break;
572             }
573         }
574         tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
575                                           _("Unicode: %s: %s"), tc->uni, utf8);
576     } else {
577         tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode: "));
578     }
581 static gint
582 sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
584     SPTextContext *const tc = SP_TEXT_CONTEXT(ec);
586     SPDesktop *desktop = ec->desktop;
588     sp_canvas_item_hide(tc->indicator);
590     sp_text_context_validate_cursor_iterators(tc);
592     ec->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100);
594     switch (event->type) {
595         case GDK_BUTTON_PRESS:
596             if (event->button.button == 1) {
598                 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(ec);
600                 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
601                     return TRUE;
602                 }
604                 // save drag origin
605                 ec->xp = (gint) event->button.x;
606                 ec->yp = (gint) event->button.y;
607                 ec->within_tolerance = true;
609                 NR::Point const button_pt(event->button.x, event->button.y);
610                 tc->p0 = desktop->w2d(button_pt);
611                 Inkscape::Rubberband::get()->start(desktop, tc->p0);
612                 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
613                                     GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
614                                         GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
615                                     NULL, event->button.time);
616                 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
617                 tc->creating = 1;
619                 /* Processed */
620                 return TRUE;
621             }
622             break;
623         case GDK_MOTION_NOTIFY:
624             if (tc->over_text) {
625                 tc->over_text = 0;
626                 // update cursor and statusbar: we are not over a text object now
627                 ec->cursor_shape = cursor_text_xpm;
628                 ec->hot_x = 7;
629                 ec->hot_y = 7;
630                 sp_event_context_update_cursor(ec);
631                 desktop->event_context->defaultMessageContext()->clear();
632             }
634             if (tc->creating && event->motion.state & GDK_BUTTON1_MASK) {
635                 if ( ec->within_tolerance
636                      && ( abs( (gint) event->motion.x - ec->xp ) < ec->tolerance )
637                      && ( abs( (gint) event->motion.y - ec->yp ) < ec->tolerance ) ) {
638                     break; // do not drag if we're within tolerance from origin
639                 }
640                 // Once the user has moved farther than tolerance from the original location
641                 // (indicating they intend to draw, not click), then always process the
642                 // motion notify coordinates as given (no snapping back to origin)
643                 ec->within_tolerance = false;
645                 NR::Point const motion_pt(event->motion.x, event->motion.y);
646                 NR::Point const p = desktop->w2d(motion_pt);
648                 Inkscape::Rubberband::get()->move(p);
649                 gobble_motion_events(GDK_BUTTON1_MASK);
651                 // status text
652                 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::X]), desktop->namedview->getDefaultMetric());
653                 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::Y]), desktop->namedview->getDefaultMetric());
654                 ec->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("<b>Flowed text frame</b>: %s &#215; %s"), xs->str, ys->str);
655                 g_string_free(xs, FALSE);
656                 g_string_free(ys, FALSE);
658             }
659             break;
660         case GDK_BUTTON_RELEASE:
661             if (event->button.button == 1) {
663                 if (tc->grabbed) {
664                     sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
665                     tc->grabbed = NULL;
666                 }
668                 Inkscape::Rubberband::get()->stop();
670                 if (tc->creating && ec->within_tolerance) {
671                     /* Button 1, set X & Y & new item */
672                     SP_DT_SELECTION(desktop)->clear();
673                     NR::Point dtp = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
674                     tc->pdoc = sp_desktop_dt2root_xy_point(ec->desktop, dtp);
676                     tc->show = TRUE;
677                     tc->phase = 1;
678                     tc->nascent_object = 1; // new object was just created
680                     /* Cursor */
681                     sp_canvas_item_show(tc->cursor);
682                     // Cursor height is defined by the new text object's font size; it needs to be set
683                     // articifically here, for the text object does not exist yet:
684                     double cursor_height = sp_desktop_get_font_size_tool(ec->desktop);
685                     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), dtp, dtp + NR::Point(0, cursor_height));
686                     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
688                     ec->within_tolerance = false;
689                 } else if (tc->creating) {
690                     NR::Point const button_pt(event->button.x, event->button.y);
691                     NR::Point p1 = desktop->w2d(button_pt);
692                     double cursor_height = sp_desktop_get_font_size_tool(ec->desktop);
693                     if (fabs(p1[NR::Y] - tc->p0[NR::Y]) > cursor_height) {
694                         // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
695                         SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
696                         SP_DT_SELECTION(desktop)->set(ft);
697                         ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
698                         sp_document_done(SP_DT_DOCUMENT(desktop));
699                     } else {
700                         ec->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
701                     }
702                 }
703                 tc->creating = false;
704                 return TRUE;
705             }
706             break;
707         case GDK_KEY_PRESS: {
708             guint const group0_keyval = get_group0_keyval(&event->key);
710             if (group0_keyval == GDK_KP_Add ||
711                 group0_keyval == GDK_KP_Subtract) {
712                 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
713                     break; // otherwise pass on keypad +/- so they can zoom
714             }
716             if ((tc->text) || (tc->nascent_object)) {
717                 // there is an active text object in this context, or a new object was just created
719                 if (tc->unimode || !tc->imc
720                     || (MOD__CTRL && MOD__SHIFT)    // input methods tend to steal this for unimode,
721                                                     // but we have our own so make sure they don't swallow it
722                     || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
723                     //IM did not consume the key, or we're in unimode
725                         if (!MOD__CTRL_ONLY && tc->unimode) {
726                             /* TODO: ISO 14755 (section 3 Definitions) says that we should also
727                                accept the first 6 characters of alphabets other than the latin
728                                alphabet "if the Latin alphabet is not used".  The below is also
729                                reasonable (viz. hope that the user's keyboard includes latin
730                                characters and force latin interpretation -- just as we do for our
731                                keyboard shortcuts), but differs from the ISO 14755
732                                recommendation. */
733                             switch (group0_keyval) {
734                                 case GDK_space:
735                                 case GDK_KP_Space: {
736                                     if (tc->unipos) {
737                                         insert_uni_char(tc);
738                                     }
739                                     /* Stay in unimode. */
740                                     show_curr_uni_char(tc);
741                                     return TRUE;
742                                 }
744                                 case GDK_BackSpace: {
745                                     g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
746                                     if (tc->unipos) {
747                                         tc->uni[--tc->unipos] = '\0';
748                                     }
749                                     show_curr_uni_char(tc);
750                                     return TRUE;
751                                 }
753                                 case GDK_Return:
754                                 case GDK_KP_Enter: {
755                                     if (tc->unipos) {
756                                         insert_uni_char(tc);
757                                     }
758                                     /* Exit unimode. */
759                                     tc->unimode = false;
760                                     ec->defaultMessageContext()->clear();
761                                     return TRUE;
762                                 }
764                                 case GDK_Escape: {
765                                     // Cancel unimode.
766                                     tc->unimode = false;
767                                     gtk_im_context_reset(tc->imc);
768                                     ec->defaultMessageContext()->clear();
769                                     return TRUE;
770                                 }
772                                 case GDK_Shift_L:
773                                 case GDK_Shift_R:
774                                     break;
776                                 default: {
777                                     if (g_ascii_isxdigit(group0_keyval)) {
778                                         g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
779                                         tc->uni[tc->unipos++] = group0_keyval;
780                                         tc->uni[tc->unipos] = '\0';
781                                         if (tc->unipos == 8) {
782                                             /* This behaviour is partly to allow us to continue to
783                                                use a fixed-length buffer for tc->uni.  Reason for
784                                                choosing the number 8 is that it's the length of
785                                                ``canonical form'' mentioned in the ISO 14755 spec.
786                                                An advantage over choosing 6 is that it allows using
787                                                backspace for typos & misremembering when entering a
788                                                6-digit number. */
789                                             insert_uni_char(tc);
790                                         }
791                                         show_curr_uni_char(tc);
792                                         return TRUE;
793                                     } else {
794                                         /* The intent is to ignore but consume characters that could be
795                                            typos for hex digits.  Gtk seems to ignore & consume all
796                                            non-hex-digits, and we do similar here.  Though note that some
797                                            shortcuts (like keypad +/- for zoom) get processed before
798                                            reaching this code. */
799                                         return TRUE;
800                                     }
801                                 }
802                             }
803                         }
805                         bool (Inkscape::Text::Layout::iterator::*cursor_movement_operator)() = NULL;
807                         /* Neither unimode nor IM consumed key; process text tool shortcuts */
808                         switch (group0_keyval) {
809                             case GDK_space:
810                                 if (MOD__CTRL_ONLY) {
811                                     /* No-break space */
812                                     if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
813                                         sp_text_context_setup_text(tc);
814                                         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
815                                     }
816                                     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
817                                     sp_text_context_update_cursor(tc);
818                                     sp_text_context_update_text_selection(tc);
819                                     ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
820                                     sp_document_done(SP_DT_DOCUMENT(ec->desktop));
821                                     return TRUE;
822                                 }
823                                 break;
824                             case GDK_U:
825                             case GDK_u:
826                                 if (MOD__CTRL_ONLY) {
827                                     if (tc->unimode) {
828                                         tc->unimode = false;
829                                         ec->defaultMessageContext()->clear();
830                                     } else {
831                                         tc->unimode = true;
832                                         tc->unipos = 0;
833                                         ec->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode: "));
834                                     }
835                                     if (tc->imc) {
836                                         gtk_im_context_reset(tc->imc);
837                                     }
838                                     return TRUE;
839                                 }
840                                 break;
841                             case GDK_B:
842                             case GDK_b:
843                                 if (MOD__CTRL_ONLY && tc->text) {
844                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
845                                     SPCSSAttr *css = sp_repr_css_attr_new();
846                                     if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
847                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
848                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
849                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
850                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
851                                         sp_repr_css_set_property(css, "font-weight", "bold");
852                                     else
853                                         sp_repr_css_set_property(css, "font-weight", "normal");
854                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
855                                     sp_repr_css_attr_unref(css);
856                                     sp_document_done(SP_DT_DOCUMENT(ec->desktop));
857                                     sp_text_context_update_cursor(tc);
858                                     sp_text_context_update_text_selection(tc);
859                                     return TRUE;
860                                 }
861                                 break;
862                             case GDK_I:
863                             case GDK_i:
864                                 if (MOD__CTRL_ONLY && tc->text) {
865                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
866                                     SPCSSAttr *css = sp_repr_css_attr_new();
867                                     if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL)
868                                         sp_repr_css_set_property(css, "font-style", "italic");
869                                     else
870                                         sp_repr_css_set_property(css, "font-style", "normal");
871                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
872                                     sp_repr_css_attr_unref(css);
873                                     sp_document_done(SP_DT_DOCUMENT(ec->desktop));
874                                     sp_text_context_update_cursor(tc);
875                                     sp_text_context_update_text_selection(tc);
876                                     return TRUE;
877                                 }
878                                 break;
880                             case GDK_A:
881                             case GDK_a:
882                                 if (MOD__CTRL_ONLY && tc->text) {
883                                     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
884                                     if (layout) {
885                                         tc->text_sel_start = layout->begin();
886                                         tc->text_sel_end = layout->end();
887                                         sp_text_context_update_cursor(tc);
888                                         sp_text_context_update_text_selection(tc);
889                                         return TRUE;
890                                     }
891                                 }
892                                 break;
894                             case GDK_Return:
895                             case GDK_KP_Enter:
896                                 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
897                                     sp_text_context_setup_text(tc);
898                                     tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
899                                 }
900                                 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
901                                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
902                                 sp_text_context_update_cursor(tc);
903                                 sp_text_context_update_text_selection(tc);
904                                 sp_document_done(SP_DT_DOCUMENT(ec->desktop));
905                                 return TRUE;
906                             case GDK_BackSpace:
907                                 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
908                                     if (tc->text_sel_start == tc->text_sel_end)
909                                         tc->text_sel_start.prevCursorPosition();
910                                     tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
911                                     sp_text_context_update_cursor(tc);
912                                     sp_text_context_update_text_selection(tc);
913                                     sp_document_done(SP_DT_DOCUMENT(ec->desktop));
914                                 }
915                                 return TRUE;
916                             case GDK_Delete:
917                             case GDK_KP_Delete:
918                                 if (tc->text) {
919                                     if (tc->text_sel_start == tc->text_sel_end)
920                                         tc->text_sel_end.nextCursorPosition();
921                                     tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
922                                     sp_text_context_update_cursor(tc);
923                                     sp_text_context_update_text_selection(tc);
924                                     sp_document_done(SP_DT_DOCUMENT(ec->desktop));
925                                 }
926                                 return TRUE;
927                             case GDK_Left:
928                             case GDK_KP_Left:
929                             case GDK_KP_4:
930                                 if (tc->text) {
931                                     if (MOD__ALT) {
932                                         if (MOD__SHIFT)
933                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-10, 0));
934                                         else
935                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-1, 0));
936                                         sp_text_context_update_cursor(tc);
937                                         sp_text_context_update_text_selection(tc);
938                                         sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "kern:left");
939                                     } else {
940                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorLeftWithControl
941                                                                              : &Inkscape::Text::Layout::iterator::cursorLeft;
942                                         break;
943                                     }
944                                 }
945                                 return TRUE;
946                             case GDK_Right:
947                             case GDK_KP_Right:
948                             case GDK_KP_6:
949                                 if (tc->text) {
950                                     if (MOD__ALT) {
951                                         if (MOD__SHIFT)
952                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(10, 0));
953                                         else
954                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(1, 0));
955                                         sp_text_context_update_cursor(tc);
956                                         sp_text_context_update_text_selection(tc);
957                                         sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "kern:right");
958                                     } else {
959                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorRightWithControl
960                                                                              : &Inkscape::Text::Layout::iterator::cursorRight;
961                                         break;
962                                     }
963                                 }
964                                 return TRUE;
965                             case GDK_Up:
966                             case GDK_KP_Up:
967                             case GDK_KP_8:
968                                 if (tc->text) {
969                                     if (MOD__ALT) {
970                                         if (MOD__SHIFT)
971                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -10));
972                                         else
973                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -1));
974                                         sp_text_context_update_cursor(tc);
975                                         sp_text_context_update_text_selection(tc);
976                                         sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "kern:up");
977                                     } else {
978                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorUpWithControl
979                                                                              : &Inkscape::Text::Layout::iterator::cursorUp;
980                                         break;
981                                     }
982                                 }
983                                 return TRUE;
984                             case GDK_Down:
985                             case GDK_KP_Down:
986                             case GDK_KP_2:
987                                 if (tc->text) {
988                                     if (MOD__ALT) {
989                                         if (MOD__SHIFT)
990                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 10));
991                                         else
992                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 1));
993                                         sp_text_context_update_cursor(tc);
994                                         sp_text_context_update_text_selection(tc);
995                                         sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "kern:down");
996                                     } else {
997                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorDownWithControl
998                                                                              : &Inkscape::Text::Layout::iterator::cursorDown;
999                                         break;
1000                                     }
1001                                 }
1002                                 return TRUE;
1003                             case GDK_Home:
1004                             case GDK_KP_Home:
1005                                 if (tc->text) {
1006                                     if (MOD__CTRL)
1007                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfShape;
1008                                     else
1009                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfLine;
1010                                     break;
1011                                 }
1012                                 return TRUE;
1013                             case GDK_End:
1014                             case GDK_KP_End:
1015                                 if (tc->text) {
1016                                     if (MOD__CTRL)
1017                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::nextStartOfShape;
1018                                     else
1019                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisEndOfLine;
1020                                     break;
1021                                 }
1022                                 return TRUE;
1023                             case GDK_Escape:
1024                                 if (tc->creating) {
1025                                     tc->creating = 0;
1026                                     if (tc->grabbed) {
1027                                         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1028                                         tc->grabbed = NULL;
1029                                     }
1030                                     Inkscape::Rubberband::get()->stop();
1031                                 } else {
1032                                     SP_DT_SELECTION(ec->desktop)->clear();
1033                                 }
1034                                 return TRUE;
1035                             case GDK_bracketleft:
1036                                 if (tc->text) {
1037                                     if (MOD__ALT || MOD__CTRL) {
1038                                         if (MOD__ALT) {
1039                                             if (MOD__SHIFT) {
1040                                                 // FIXME: alt+shift+[] does not work, don't know why
1041                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1042                                             } else {
1043                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1044                                             }
1045                                         } else {
1046                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -90);
1047                                         }
1048                                         sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "textrot:ccw");
1049                                         sp_text_context_update_cursor(tc);
1050                                         sp_text_context_update_text_selection(tc);
1051                                         return TRUE;
1052                                     }
1053                                 }
1054                                 break;
1055                             case GDK_bracketright:
1056                                 if (tc->text) {
1057                                     if (MOD__ALT || MOD__CTRL) {
1058                                         if (MOD__ALT) {
1059                                             if (MOD__SHIFT) {
1060                                                 // FIXME: alt+shift+[] does not work, don't know why
1061                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1062                                             } else {
1063                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1064                                             }
1065                                         } else {
1066                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 90);
1067                                         }
1068                                         sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "textrot:cw");
1069                                         sp_text_context_update_cursor(tc);
1070                                         sp_text_context_update_text_selection(tc);
1071                                         return TRUE;
1072                                     }
1073                                 }
1074                                 break;
1075                             case GDK_less:
1076                             case GDK_comma:
1077                                 if (tc->text) {
1078                                     if (MOD__ALT) {
1079                                         if (MOD__CTRL) {
1080                                             if (MOD__SHIFT)
1081                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1082                                             else
1083                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1084                                             sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "linespacing:dec");
1085                                         } else {
1086                                             if (MOD__SHIFT)
1087                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1088                                             else
1089                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1090                                             sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "letterspacing:dec");
1091                                         }
1092                                         sp_text_context_update_cursor(tc);
1093                                         sp_text_context_update_text_selection(tc);
1094                                         return TRUE;
1095                                     }
1096                                 }
1097                                 break;
1098                             case GDK_greater:
1099                             case GDK_period:
1100                                 if (tc->text) {
1101                                     if (MOD__ALT) {
1102                                         if (MOD__CTRL) {
1103                                             if (MOD__SHIFT)
1104                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1105                                             else
1106                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1107                                             sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "linespacing:inc");
1108                                         } else {
1109                                             if (MOD__SHIFT)
1110                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1111                                             else
1112                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1113                                             sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "letterspacing:inc");
1114                                         }
1115                                         sp_text_context_update_cursor(tc);
1116                                         sp_text_context_update_text_selection(tc);
1117                                         return TRUE;
1118                                     }
1119                                 }
1120                                 break;
1121                             default:
1122                                 break;
1123                         }
1125                         if (cursor_movement_operator) {
1126                             Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
1127                             Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
1128                             (tc->text_sel_end.*cursor_movement_operator)();
1129                             if (!MOD__SHIFT)
1130                                 tc->text_sel_start = tc->text_sel_end;
1131                             if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1132                                 sp_text_context_update_cursor(tc);
1133                                 sp_text_context_update_text_selection(tc);
1134                             }
1135                             return TRUE;
1136                         }
1138                 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1139             } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1140                 // except up/down that are swallowed to prevent the zoom field from activation
1141                 if ((group0_keyval == GDK_Up    ||
1142                      group0_keyval == GDK_Down  ||
1143                      group0_keyval == GDK_KP_Up ||
1144                      group0_keyval == GDK_KP_Down )
1145                     && !MOD__CTRL_ONLY) {
1146                     return TRUE;
1147                 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1148                     if (tc->creating) {
1149                         tc->creating = 0;
1150                         if (tc->grabbed) {
1151                             sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1152                             tc->grabbed = NULL;
1153                         }
1154                         Inkscape::Rubberband::get()->stop();
1155                     }
1156                 }
1157             }
1158             break;
1159         }
1161         case GDK_KEY_RELEASE:
1162             if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1163                 return TRUE;
1164             }
1165             break;
1166         default:
1167             break;
1168     }
1170     // if nobody consumed it so far
1171     if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1172         return ((SPEventContextClass *) parent_class)->root_handler(ec, event); // send event to parent
1173     } else {
1174         return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1175     }
1178 /**
1179  Attempts to paste system clipboard into the currently edited text, returns true on success
1180  */
1181 bool
1182 sp_text_paste_inline(SPEventContext *ec)
1184     if (!SP_IS_TEXT_CONTEXT(ec))
1185         return false;
1187     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1189     if ((tc->text) || (tc->nascent_object)) {
1190         // there is an active text object in this context, or a new object was just created
1192         Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1193         Glib::ustring const text = refClipboard->wait_for_text();
1195         if (!text.empty()) {
1197             if (!tc->text) { // create text if none (i.e. if nascent_object)
1198                 sp_text_context_setup_text(tc);
1199                 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1200             }
1202             // using indices is slow in ustrings. Whatever.
1203             Glib::ustring::size_type begin = 0;
1204             for ( ; ; ) {
1205                 Glib::ustring::size_type end = text.find('\n', begin);
1206                 if (end == Glib::ustring::npos) {
1207                     if (begin != text.length())
1208                         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());
1209                     break;
1210                 }
1211                 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());
1212                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1213                 begin = end + 1;
1214             }
1215             sp_document_done(SP_DT_DOCUMENT(ec->desktop));
1217             return true;
1218         }
1219     } // FIXME: else create and select a new object under cursor!
1221     return false;
1224 /**
1225  Gets the raw characters that comprise the currently selected text, converting line
1226  breaks into lf characters.
1227 */
1228 Glib::ustring
1229 sp_text_get_selected_text(SPEventContext const *ec)
1231     if (!SP_IS_TEXT_CONTEXT(ec))
1232         return "";
1233     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1234     if (tc->text == NULL)
1235         return "";
1237     return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1240 /**
1241  Deletes the currently selected characters. Returns false if there is no
1242  text selection currently.
1243 */
1244 bool sp_text_delete_selection(SPEventContext *ec)
1246     if (!SP_IS_TEXT_CONTEXT(ec))
1247         return false;
1248     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1249     if (tc->text == NULL)
1250         return false;
1252     if (tc->text_sel_start == tc->text_sel_end)
1253         return false;
1254     tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
1255     sp_text_context_update_cursor(tc);
1256     sp_text_context_update_text_selection(tc);
1257     return true;
1260 /**
1261  * \param selection Should not be NULL.
1262  */
1263 static void
1264 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1266     g_assert(selection != NULL);
1268     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1270     if (ec->shape_knot_holder) { // destroy knotholder
1271         sp_knot_holder_destroy(ec->shape_knot_holder);
1272         ec->shape_knot_holder = NULL;
1273     }
1275     if (ec->shape_repr) { // remove old listener
1276         sp_repr_remove_listener_by_data(ec->shape_repr, ec);
1277         Inkscape::GC::release(ec->shape_repr);
1278         ec->shape_repr = 0;
1279     }
1281     SPItem *item = selection->singleItem();
1282     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1283         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
1284         Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
1285         if (shape_repr) {
1286             ec->shape_repr = shape_repr;
1287             Inkscape::GC::anchor(shape_repr);
1288             sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
1289             sp_repr_synthesize_events(shape_repr, &ec_shape_repr_events, ec);
1290         }
1291     }
1293     if (tc->text && (item != tc->text)) {
1294         sp_text_context_forget_text(tc);
1295     }
1296     tc->text = NULL;
1298     if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1299         tc->text = item;
1300         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1301         if (layout)
1302             tc->text_sel_start = tc->text_sel_end = layout->end();
1303     } else {
1304         tc->text = NULL;
1305     }
1307     // we update cursor without scrolling, because this position may not be final;
1308     // item_handler moves cusros to the point of click immediately
1309     sp_text_context_update_cursor(tc, false);
1310     sp_text_context_update_text_selection(tc);
1313 static void
1314 sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc)
1316     sp_text_context_update_cursor(tc);
1317     sp_text_context_update_text_selection(tc);
1320 static bool
1321 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1323     if (tc->text == NULL)
1324         return false;
1325     if (tc->text_sel_start == tc->text_sel_end)
1326         return false;    // will get picked up by the parent and applied to the whole text object
1328     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1329     sp_document_done(SP_DT_DOCUMENT(tc->desktop));
1330     sp_text_context_update_cursor(tc);
1331     sp_text_context_update_text_selection(tc);
1333     return true;
1336 static int
1337 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1339     if (tc->text == NULL)
1340         return QUERY_STYLE_NOTHING;
1341     const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1342     if (layout == NULL)
1343         return QUERY_STYLE_NOTHING;
1344     sp_text_context_validate_cursor_iterators(tc);
1346     GSList *styles_list = NULL;
1348     Inkscape::Text::Layout::iterator begin_it, end_it;
1349     if (tc->text_sel_start < tc->text_sel_end) {
1350         begin_it = tc->text_sel_start;
1351         end_it = tc->text_sel_end;
1352     } else {
1353         begin_it = tc->text_sel_end;
1354         end_it = tc->text_sel_start;
1355     }
1356     if (begin_it == end_it)
1357         if (!begin_it.prevCharacter())
1358             end_it.nextCharacter();
1359     for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1360         SPObject const *pos_obj = NULL;
1361         layout->getSourceOfCharacter(it, (void**)&pos_obj);
1362         if (pos_obj == NULL) continue;
1363         while (SP_OBJECT_STYLE(pos_obj) == NULL && SP_OBJECT_PARENT(pos_obj))
1364             pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
1365         styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1366     }
1368     int result = sp_desktop_query_style_from_list (styles_list, style, property);
1370     g_slist_free(styles_list);
1371     return result;
1374 static void
1375 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1377     if (tc->text == NULL)
1378         return;
1379     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1380     if (layout) {     // undo can change the text length without us knowing it
1381         layout->validateIterator(&tc->text_sel_start);
1382         layout->validateIterator(&tc->text_sel_end);
1383     }
1386 static void
1387 sp_text_context_update_cursor(SPTextContext *tc,  bool scroll_to_see)
1389     GdkRectangle im_cursor = { 0, 0, 1, 1 };
1391     if (tc->text) {
1392         NR::Point p0, p1;
1393         sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1394         NR::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1395         NR::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1397         // scroll to show cursor
1398         if (scroll_to_see) {
1399             NR::Point const dm = (d0 + d1) / 2;
1400             // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1401             SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(&dm, 1.0);
1402         }
1404         sp_canvas_item_show(tc->cursor);
1405         sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1407         /* fixme: ... need another transformation to get canvas widget coordinate space? */
1408         im_cursor.x = (int) floor(d0[NR::X]);
1409         im_cursor.y = (int) floor(d0[NR::Y]);
1410         im_cursor.width = (int) floor(d1[NR::X]) - im_cursor.x;
1411         im_cursor.height = (int) floor(d1[NR::Y]) - im_cursor.y;
1413         tc->show = TRUE;
1414         tc->phase = 1;
1416         if (SP_IS_FLOWTEXT(tc->text)) {
1417             SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1418             if (frame) {
1419                 sp_canvas_item_show(tc->frame);
1420                 SP_CTRLRECT(tc->frame)->setRectangle(sp_item_bbox_desktop(frame));
1421             }
1422             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1423         } else {
1424             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1425         }
1427     } else {
1428         sp_canvas_item_hide(tc->cursor);
1429         sp_canvas_item_hide(tc->frame);
1430         tc->show = FALSE;
1431         if (!tc->nascent_object) {
1432             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
1433         }
1434     }
1436     if (tc->imc) {
1437         gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1438     }
1439     SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1442 static void sp_text_context_update_text_selection(SPTextContext *tc)
1444     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1445         sp_canvas_item_hide(*it);
1446         gtk_object_destroy(*it);
1447     }
1448     tc->text_selection_quads.clear();
1450     std::vector<NR::Point> quads;
1451     if (tc->text != NULL)
1452         quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1453     for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1454         SPCanvasItem *quad_canvasitem;
1455         quad_canvasitem = sp_canvas_item_new(SP_DT_CONTROLS(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1456         sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x000000ff);
1457         sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1458         sp_canvas_item_show(quad_canvasitem);
1459         tc->text_selection_quads.push_back(quad_canvasitem);
1460     }
1463 static gint
1464 sp_text_context_timeout(SPTextContext *tc)
1466     if (tc->show) {
1467         if (tc->phase) {
1468             tc->phase = 0;
1469             sp_canvas_item_hide(tc->cursor);
1470         } else {
1471             tc->phase = 1;
1472             sp_canvas_item_show(tc->cursor);
1473         }
1474     }
1476     return TRUE;
1479 static void
1480 sp_text_context_forget_text(SPTextContext *tc)
1482     if (! tc->text) return;
1483     SPItem *ti = tc->text;
1484     /* We have to set it to zero,
1485      * or selection changed signal messes everything up */
1486     tc->text = NULL;
1487     if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1488         Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1489         // the repr may already have been unparented
1490         // if we were called e.g. as the result of
1491         // an undo or the element being removed from
1492         // the XML editor
1493         if ( text_repr && sp_repr_parent(text_repr) ) {
1494             sp_repr_unparent(text_repr);
1495         }
1496     }
1499 gint
1500 sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1502     gtk_im_context_focus_in(tc->imc);
1503     return FALSE;
1506 gint
1507 sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1509     gtk_im_context_focus_out(tc->imc);
1510     return FALSE;
1513 static void
1514 sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc)
1516     if (!tc->text) {
1517         sp_text_context_setup_text(tc);
1518         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1519     }
1521     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1522     sp_text_context_update_cursor(tc);
1523     sp_text_context_update_text_selection(tc);
1525     sp_document_done(SP_OBJECT_DOCUMENT(tc->text));
1529 /*
1530   Local Variables:
1531   mode:c++
1532   c-file-style:"stroustrup"
1533   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1534   indent-tabs-mode:nil
1535   fill-column:99
1536   End:
1537 */
1538 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :