Code

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