Code

I'm an idiot who forgot that MOTION_HINT_MASK still needs MOTION_MASK
[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 | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
619                                     NULL, event->button.time);
620                 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
621                 tc->creating = 1;
623                 /* Processed */
624                 return TRUE;
625             }
626             break;
627         case GDK_MOTION_NOTIFY:
628             if (tc->over_text) {
629                 tc->over_text = 0;
630                 // update cursor and statusbar: we are not over a text object now
631                 event_context->cursor_shape = cursor_text_xpm;
632                 event_context->hot_x = 7;
633                 event_context->hot_y = 7;
634                 sp_event_context_update_cursor(event_context);
635                 desktop->event_context->defaultMessageContext()->clear();
636             }
638             if (tc->creating && event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
639                 if ( event_context->within_tolerance
640                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
641                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
642                     break; // do not drag if we're within tolerance from origin
643                 }
644                 // Once the user has moved farther than tolerance from the original location
645                 // (indicating they intend to draw, not click), then always process the
646                 // motion notify coordinates as given (no snapping back to origin)
647                 event_context->within_tolerance = false;
649                 NR::Point const motion_pt(event->motion.x, event->motion.y);
650                 NR::Point const p = desktop->w2d(motion_pt);
652                 Inkscape::Rubberband::get()->move(p);
653                 gobble_motion_events(GDK_BUTTON1_MASK);
655                 // status text
656                 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::X]), desktop->namedview->getDefaultMetric());
657                 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::Y]), desktop->namedview->getDefaultMetric());
658                 event_context->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Flowed text frame</b>: %s &#215; %s"), xs->str, ys->str);
659                 g_string_free(xs, FALSE);
660                 g_string_free(ys, FALSE);
662             }
663             break;
664         case GDK_BUTTON_RELEASE:
665             if (event->button.button == 1 && !event_context->space_panning) {
667                 if (tc->grabbed) {
668                     sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
669                     tc->grabbed = NULL;
670                 }
672                 Inkscape::Rubberband::get()->stop();
674                 if (tc->creating && event_context->within_tolerance) {
675                     /* Button 1, set X & Y & new item */
676                     sp_desktop_selection(desktop)->clear();
677                     NR::Point dtp = desktop->w2d(NR::Point(event->button.x, event->button.y));
678                     tc->pdoc = sp_desktop_dt2root_xy_point(desktop, dtp);
680                     tc->show = TRUE;
681                     tc->phase = 1;
682                     tc->nascent_object = 1; // new object was just created
684                     /* Cursor */
685                     sp_canvas_item_show(tc->cursor);
686                     // Cursor height is defined by the new text object's font size; it needs to be set
687                     // articifically here, for the text object does not exist yet:
688                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
689                     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), dtp, dtp + NR::Point(0, cursor_height));
690                     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
692                     event_context->within_tolerance = false;
693                 } else if (tc->creating) {
694                     NR::Point const button_pt(event->button.x, event->button.y);
695                     NR::Point p1 = desktop->w2d(button_pt);
696                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
697                     if (fabs(p1[NR::Y] - tc->p0[NR::Y]) > cursor_height) {
698                         // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
699                         SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
700                         /* Set style */
701                         sp_desktop_apply_style_tool(desktop, SP_OBJECT_REPR(ft), "tools.text", true);
702                         sp_desktop_selection(desktop)->set(ft);
703                         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
704                         sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
705                                          _("Create flowed text"));
706                     } else {
707                         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
708                     }
709                 }
710                 tc->creating = false;
711                 return TRUE;
712             }
713             break;
714         case GDK_KEY_PRESS: {
715             guint const group0_keyval = get_group0_keyval(&event->key);
717             if (group0_keyval == GDK_KP_Add ||
718                 group0_keyval == GDK_KP_Subtract) {
719                 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
720                     break; // otherwise pass on keypad +/- so they can zoom
721             }
723             if ((tc->text) || (tc->nascent_object)) {
724                 // there is an active text object in this context, or a new object was just created
726                 if (tc->unimode || !tc->imc
727                     || (MOD__CTRL && MOD__SHIFT)    // input methods tend to steal this for unimode,
728                                                     // but we have our own so make sure they don't swallow it
729                     || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
730                     //IM did not consume the key, or we're in unimode
732                         if (!MOD__CTRL_ONLY && tc->unimode) {
733                             /* TODO: ISO 14755 (section 3 Definitions) says that we should also
734                                accept the first 6 characters of alphabets other than the latin
735                                alphabet "if the Latin alphabet is not used".  The below is also
736                                reasonable (viz. hope that the user's keyboard includes latin
737                                characters and force latin interpretation -- just as we do for our
738                                keyboard shortcuts), but differs from the ISO 14755
739                                recommendation. */
740                             switch (group0_keyval) {
741                                 case GDK_space:
742                                 case GDK_KP_Space: {
743                                     if (tc->unipos) {
744                                         insert_uni_char(tc);
745                                     }
746                                     /* Stay in unimode. */
747                                     show_curr_uni_char(tc);
748                                     return TRUE;
749                                 }
751                                 case GDK_BackSpace: {
752                                     g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
753                                     if (tc->unipos) {
754                                         tc->uni[--tc->unipos] = '\0';
755                                     }
756                                     show_curr_uni_char(tc);
757                                     return TRUE;
758                                 }
760                                 case GDK_Return:
761                                 case GDK_KP_Enter: {
762                                     if (tc->unipos) {
763                                         insert_uni_char(tc);
764                                     }
765                                     /* Exit unimode. */
766                                     tc->unimode = false;
767                                     event_context->defaultMessageContext()->clear();
768                                     return TRUE;
769                                 }
771                                 case GDK_Escape: {
772                                     // Cancel unimode.
773                                     tc->unimode = false;
774                                     gtk_im_context_reset(tc->imc);
775                                     event_context->defaultMessageContext()->clear();
776                                     return TRUE;
777                                 }
779                                 case GDK_Shift_L:
780                                 case GDK_Shift_R:
781                                     break;
783                                 default: {
784                                     if (g_ascii_isxdigit(group0_keyval)) {
785                                         g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
786                                         tc->uni[tc->unipos++] = group0_keyval;
787                                         tc->uni[tc->unipos] = '\0';
788                                         if (tc->unipos == 8) {
789                                             /* This behaviour is partly to allow us to continue to
790                                                use a fixed-length buffer for tc->uni.  Reason for
791                                                choosing the number 8 is that it's the length of
792                                                ``canonical form'' mentioned in the ISO 14755 spec.
793                                                An advantage over choosing 6 is that it allows using
794                                                backspace for typos & misremembering when entering a
795                                                6-digit number. */
796                                             insert_uni_char(tc);
797                                         }
798                                         show_curr_uni_char(tc);
799                                         return TRUE;
800                                     } else {
801                                         /* The intent is to ignore but consume characters that could be
802                                            typos for hex digits.  Gtk seems to ignore & consume all
803                                            non-hex-digits, and we do similar here.  Though note that some
804                                            shortcuts (like keypad +/- for zoom) get processed before
805                                            reaching this code. */
806                                         return TRUE;
807                                     }
808                                 }
809                             }
810                         }
812                         bool (Inkscape::Text::Layout::iterator::*cursor_movement_operator)() = NULL;
814                         /* Neither unimode nor IM consumed key; process text tool shortcuts */
815                         switch (group0_keyval) {
816                             case GDK_x:
817                             case GDK_X:
818                                 if (MOD__ALT_ONLY) {
819                                     desktop->setToolboxFocusTo ("altx-text");
820                                     return TRUE;
821                                 }
822                                 break;
823                             case GDK_space:
824                                 if (MOD__CTRL_ONLY) {
825                                     /* No-break space */
826                                     if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
827                                         sp_text_context_setup_text(tc);
828                                         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
829                                     }
830                                     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
831                                     sp_text_context_update_cursor(tc);
832                                     sp_text_context_update_text_selection(tc);
833                                     desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
834                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
835                                                      _("Insert no-break space"));
836                                     return TRUE;
837                                 }
838                                 break;
839                             case GDK_U:
840                             case GDK_u:
841                                 if (MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT)) {
842                                     if (tc->unimode) {
843                                         tc->unimode = false;
844                                         event_context->defaultMessageContext()->clear();
845                                     } else {
846                                         tc->unimode = true;
847                                         tc->unipos = 0;
848                                         event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
849                                     }
850                                     if (tc->imc) {
851                                         gtk_im_context_reset(tc->imc);
852                                     }
853                                     return TRUE;
854                                 }
855                                 break;
856                             case GDK_B:
857                             case GDK_b:
858                                 if (MOD__CTRL_ONLY && tc->text) {
859                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
860                                     SPCSSAttr *css = sp_repr_css_attr_new();
861                                     if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
862                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
863                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
864                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
865                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
866                                         sp_repr_css_set_property(css, "font-weight", "bold");
867                                     else
868                                         sp_repr_css_set_property(css, "font-weight", "normal");
869                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
870                                     sp_repr_css_attr_unref(css);
871                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
872                                                      _("Make bold"));
873                                     sp_text_context_update_cursor(tc);
874                                     sp_text_context_update_text_selection(tc);
875                                     return TRUE;
876                                 }
877                                 break;
878                             case GDK_I:
879                             case GDK_i:
880                                 if (MOD__CTRL_ONLY && tc->text) {
881                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
882                                     SPCSSAttr *css = sp_repr_css_attr_new();
883                                     if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL)
884                                         sp_repr_css_set_property(css, "font-style", "italic");
885                                     else
886                                         sp_repr_css_set_property(css, "font-style", "normal");
887                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
888                                     sp_repr_css_attr_unref(css);
889                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
890                                                      _("Make italic"));
891                                     sp_text_context_update_cursor(tc);
892                                     sp_text_context_update_text_selection(tc);
893                                     return TRUE;
894                                 }
895                                 break;
897                             case GDK_A:
898                             case GDK_a:
899                                 if (MOD__CTRL_ONLY && tc->text) {
900                                     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
901                                     if (layout) {
902                                         tc->text_sel_start = layout->begin();
903                                         tc->text_sel_end = layout->end();
904                                         sp_text_context_update_cursor(tc);
905                                         sp_text_context_update_text_selection(tc);
906                                         return TRUE;
907                                     }
908                                 }
909                                 break;
911                             case GDK_Return:
912                             case GDK_KP_Enter:
913                             {
914                                 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
915                                     sp_text_context_setup_text(tc);
916                                     tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
917                                 }
919                                 iterator_pair enter_pair;
920                                 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, enter_pair);
921                                 (void)success; // TODO cleanup
922                                 tc->text_sel_start = tc->text_sel_end = enter_pair.first;
924                                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
926                                 sp_text_context_update_cursor(tc);
927                                 sp_text_context_update_text_selection(tc);
928                                 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
929                                                  _("New line"));
930                                 return TRUE;
931                             }
932                             case GDK_BackSpace:
933                                 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
935                                     bool noSelection = false;
937                                         if (tc->text_sel_start == tc->text_sel_end) {
938                                         tc->text_sel_start.prevCursorPosition();
939                                         noSelection = true;
940                                     }
942                                         iterator_pair bspace_pair;
943                                         bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, bspace_pair);
945                                     if (noSelection) {
946                                         if (success) {
947                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
948                                         } else { // nothing deleted
949                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.second;
950                                         }
951                                     } else {
952                                         if (success) {
953                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
954                                         } else { // nothing deleted
955                                             tc->text_sel_start = bspace_pair.first;
956                                             tc->text_sel_end = bspace_pair.second;
957                                         }
958                                     }
960                                     sp_text_context_update_cursor(tc);
961                                     sp_text_context_update_text_selection(tc);
962                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
963                                                      _("Backspace"));
964                                 }
965                                 return TRUE;
966                             case GDK_Delete:
967                             case GDK_KP_Delete:
968                                 if (tc->text) {
969                                     bool noSelection = false;
971                                     if (tc->text_sel_start == tc->text_sel_end) {
972                                         tc->text_sel_end.nextCursorPosition();
973                                         noSelection = true;
974                                     }
976                                     iterator_pair del_pair;
977                                     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, del_pair);
979                                     if (noSelection) {
980                                         tc->text_sel_start = tc->text_sel_end = del_pair.first;
981                                     } else {
982                                         if (success) {
983                                             tc->text_sel_start = tc->text_sel_end = del_pair.first;
984                                         } else { // nothing deleted
985                                             tc->text_sel_start = del_pair.first;
986                                             tc->text_sel_end = del_pair.second;
987                                         }
988                                     }
991                                     sp_text_context_update_cursor(tc);
992                                     sp_text_context_update_text_selection(tc);
993                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
994                                                      _("Delete"));
995                                 }
996                                 return TRUE;
997                             case GDK_Left:
998                             case GDK_KP_Left:
999                             case GDK_KP_4:
1000                                 if (tc->text) {
1001                                     if (MOD__ALT) {
1002                                         gint mul = 1 + gobble_key_events(
1003                                             get_group0_keyval(&event->key), 0); // with any mask
1004                                         if (MOD__SHIFT)
1005                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, NR::Point(mul*-10, 0));
1006                                         else
1007                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, NR::Point(mul*-1, 0));
1008                                         sp_text_context_update_cursor(tc);
1009                                         sp_text_context_update_text_selection(tc);
1010                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT,
1011                                                                _("Kern to the left"));
1012                                     } else {
1013                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorLeftWithControl
1014                                                                              : &Inkscape::Text::Layout::iterator::cursorLeft;
1015                                         break;
1016                                     }
1017                                 }
1018                                 return TRUE;
1019                             case GDK_Right:
1020                             case GDK_KP_Right:
1021                             case GDK_KP_6:
1022                                 if (tc->text) {
1023                                     if (MOD__ALT) {
1024                                         gint mul = 1 + gobble_key_events(
1025                                             get_group0_keyval(&event->key), 0); // with any mask
1026                                         if (MOD__SHIFT)
1027                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, NR::Point(mul*10, 0));
1028                                         else
1029                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, NR::Point(mul*1, 0));
1030                                         sp_text_context_update_cursor(tc);
1031                                         sp_text_context_update_text_selection(tc);
1032                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT,
1033                                                                _("Kern to the right"));
1034                                     } else {
1035                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorRightWithControl
1036                                                                              : &Inkscape::Text::Layout::iterator::cursorRight;
1037                                         break;
1038                                     }
1039                                 }
1040                                 return TRUE;
1041                             case GDK_Up:
1042                             case GDK_KP_Up:
1043                             case GDK_KP_8:
1044                                 if (tc->text) {
1045                                     if (MOD__ALT) {
1046                                         gint mul = 1 + gobble_key_events(
1047                                             get_group0_keyval(&event->key), 0); // with any mask
1048                                         if (MOD__SHIFT)
1049                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, NR::Point(0, mul*-10));
1050                                         else
1051                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, NR::Point(0, mul*-1));
1052                                         sp_text_context_update_cursor(tc);
1053                                         sp_text_context_update_text_selection(tc);
1054                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT,
1055                                                                _("Kern up"));
1057                                     } else {
1058                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorUpWithControl
1059                                                                              : &Inkscape::Text::Layout::iterator::cursorUp;
1060                                         break;
1061                                     }
1062                                 }
1063                                 return TRUE;
1064                             case GDK_Down:
1065                             case GDK_KP_Down:
1066                             case GDK_KP_2:
1067                                 if (tc->text) {
1068                                     if (MOD__ALT) {
1069                                         gint mul = 1 + gobble_key_events(
1070                                             get_group0_keyval(&event->key), 0); // with any mask
1071                                         if (MOD__SHIFT)
1072                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, NR::Point(0, mul*10));
1073                                         else
1074                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, NR::Point(0, mul*1));
1075                                         sp_text_context_update_cursor(tc);
1076                                         sp_text_context_update_text_selection(tc);
1077                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT,
1078                                                                _("Kern down"));
1080                                     } else {
1081                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorDownWithControl
1082                                                                              : &Inkscape::Text::Layout::iterator::cursorDown;
1083                                         break;
1084                                     }
1085                                 }
1086                                 return TRUE;
1087                             case GDK_Home:
1088                             case GDK_KP_Home:
1089                                 if (tc->text) {
1090                                     if (MOD__CTRL)
1091                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfShape;
1092                                     else
1093                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfLine;
1094                                     break;
1095                                 }
1096                                 return TRUE;
1097                             case GDK_End:
1098                             case GDK_KP_End:
1099                                 if (tc->text) {
1100                                     if (MOD__CTRL)
1101                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::nextStartOfShape;
1102                                     else
1103                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisEndOfLine;
1104                                     break;
1105                                 }
1106                                 return TRUE;
1107                             case GDK_Escape:
1108                                 if (tc->creating) {
1109                                     tc->creating = 0;
1110                                     if (tc->grabbed) {
1111                                         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1112                                         tc->grabbed = NULL;
1113                                     }
1114                                     Inkscape::Rubberband::get()->stop();
1115                                 } else {
1116                                     sp_desktop_selection(desktop)->clear();
1117                                 }
1118                                 tc->nascent_object = FALSE;
1119                                 return TRUE;
1120                             case GDK_bracketleft:
1121                                 if (tc->text) {
1122                                     if (MOD__ALT || MOD__CTRL) {
1123                                         if (MOD__ALT) {
1124                                             if (MOD__SHIFT) {
1125                                                 // FIXME: alt+shift+[] does not work, don't know why
1126                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1127                                             } else {
1128                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1129                                             }
1130                                         } else {
1131                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -90);
1132                                         }
1133                                         sp_document_maybe_done(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT,
1134                                                                _("Rotate counterclockwise"));
1135                                         sp_text_context_update_cursor(tc);
1136                                         sp_text_context_update_text_selection(tc);
1137                                         return TRUE;
1138                                     }
1139                                 }
1140                                 break;
1141                             case GDK_bracketright:
1142                                 if (tc->text) {
1143                                     if (MOD__ALT || MOD__CTRL) {
1144                                         if (MOD__ALT) {
1145                                             if (MOD__SHIFT) {
1146                                                 // FIXME: alt+shift+[] does not work, don't know why
1147                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1148                                             } else {
1149                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1150                                             }
1151                                         } else {
1152                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 90);
1153                                         }
1154                                         sp_document_maybe_done(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT,
1155                                                                 _("Rotate clockwise"));
1156                                         sp_text_context_update_cursor(tc);
1157                                         sp_text_context_update_text_selection(tc);
1158                                         return TRUE;
1159                                     }
1160                                 }
1161                                 break;
1162                             case GDK_less:
1163                             case GDK_comma:
1164                                 if (tc->text) {
1165                                     if (MOD__ALT) {
1166                                         if (MOD__CTRL) {
1167                                             if (MOD__SHIFT)
1168                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1169                                             else
1170                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1171                                             sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT,
1172                                                                     _("Contract line spacing"));
1174                                         } else {
1175                                             if (MOD__SHIFT)
1176                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1177                                             else
1178                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1179                                             sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT,
1180                                                                     _("Contract letter spacing"));
1182                                         }
1183                                         sp_text_context_update_cursor(tc);
1184                                         sp_text_context_update_text_selection(tc);
1185                                         return TRUE;
1186                                     }
1187                                 }
1188                                 break;
1189                             case GDK_greater:
1190                             case GDK_period:
1191                                 if (tc->text) {
1192                                     if (MOD__ALT) {
1193                                         if (MOD__CTRL) {
1194                                             if (MOD__SHIFT)
1195                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1196                                             else
1197                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1198                                             sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT,
1199                                                                     _("Expand line spacing"));
1201                                         } else {
1202                                             if (MOD__SHIFT)
1203                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1204                                             else
1205                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1206                                             sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT,
1207                                                                     _("Expand letter spacing"));
1209                                         }
1210                                         sp_text_context_update_cursor(tc);
1211                                         sp_text_context_update_text_selection(tc);
1212                                         return TRUE;
1213                                     }
1214                                 }
1215                                 break;
1216                             default:
1217                                 break;
1218                         }
1220                         if (cursor_movement_operator) {
1221                             Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
1222                             Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
1223                             (tc->text_sel_end.*cursor_movement_operator)();
1224                             if (!MOD__SHIFT)
1225                                 tc->text_sel_start = tc->text_sel_end;
1226                             if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1227                                 sp_text_context_update_cursor(tc);
1228                                 sp_text_context_update_text_selection(tc);
1229                             }
1230                             return TRUE;
1231                         }
1233                 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1234             } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1235                 // except up/down that are swallowed to prevent the zoom field from activation
1236                 if ((group0_keyval == GDK_Up    ||
1237                      group0_keyval == GDK_Down  ||
1238                      group0_keyval == GDK_KP_Up ||
1239                      group0_keyval == GDK_KP_Down )
1240                     && !MOD__CTRL_ONLY) {
1241                     return TRUE;
1242                 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1243                     if (tc->creating) {
1244                         tc->creating = 0;
1245                         if (tc->grabbed) {
1246                             sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1247                             tc->grabbed = NULL;
1248                         }
1249                         Inkscape::Rubberband::get()->stop();
1250                     }
1251                 }
1252             }
1253             break;
1254         }
1256         case GDK_KEY_RELEASE:
1257             if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1258                 return TRUE;
1259             }
1260             break;
1261         default:
1262             break;
1263     }
1265     // if nobody consumed it so far
1266     if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1267         return ((SPEventContextClass *) parent_class)->root_handler(event_context, event); // send event to parent
1268     } else {
1269         return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1270     }
1273 /**
1274  Attempts to paste system clipboard into the currently edited text, returns true on success
1275  */
1276 bool
1277 sp_text_paste_inline(SPEventContext *ec)
1279     if (!SP_IS_TEXT_CONTEXT(ec))
1280         return false;
1282     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1284     if ((tc->text) || (tc->nascent_object)) {
1285         // there is an active text object in this context, or a new object was just created
1287         Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1288         Glib::ustring const text = refClipboard->wait_for_text();
1290         if (!text.empty()) {
1292             if (!tc->text) { // create text if none (i.e. if nascent_object)
1293                 sp_text_context_setup_text(tc);
1294                 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1295             }
1297             // using indices is slow in ustrings. Whatever.
1298             Glib::ustring::size_type begin = 0;
1299             for ( ; ; ) {
1300                 Glib::ustring::size_type end = text.find('\n', begin);
1301                 if (end == Glib::ustring::npos) {
1302                     if (begin != text.length())
1303                         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());
1304                     break;
1305                 }
1306                 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());
1307                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1308                 begin = end + 1;
1309             }
1310             sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
1311                              _("Paste text"));
1313             return true;
1314         }
1315     } // FIXME: else create and select a new object under cursor!
1317     return false;
1320 /**
1321  Gets the raw characters that comprise the currently selected text, converting line
1322  breaks into lf characters.
1323 */
1324 Glib::ustring
1325 sp_text_get_selected_text(SPEventContext const *ec)
1327     if (!SP_IS_TEXT_CONTEXT(ec))
1328         return "";
1329     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1330     if (tc->text == NULL)
1331         return "";
1333     return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1336 /**
1337  Deletes the currently selected characters. Returns false if there is no
1338  text selection currently.
1339 */
1340 bool sp_text_delete_selection(SPEventContext *ec)
1342     if (!SP_IS_TEXT_CONTEXT(ec))
1343         return false;
1344     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1345     if (tc->text == NULL)
1346         return false;
1348     if (tc->text_sel_start == tc->text_sel_end)
1349         return false;
1351     iterator_pair pair;
1352     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair);
1355     if (success) {
1356         tc->text_sel_start = tc->text_sel_end = pair.first;
1357     } else { // nothing deleted
1358         tc->text_sel_start = pair.first;
1359         tc->text_sel_end = pair.second;
1360     }
1362     sp_text_context_update_cursor(tc);
1363     sp_text_context_update_text_selection(tc);
1365     return true;
1368 /**
1369  * \param selection Should not be NULL.
1370  */
1371 static void
1372 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1374     g_assert(selection != NULL);
1376     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1378     if (ec->shape_knot_holder) { // destroy knotholder
1379         delete ec->shape_knot_holder;
1380         ec->shape_knot_holder = NULL;
1381     }
1383     if (ec->shape_repr) { // remove old listener
1384         sp_repr_remove_listener_by_data(ec->shape_repr, ec);
1385         Inkscape::GC::release(ec->shape_repr);
1386         ec->shape_repr = 0;
1387     }
1389     SPItem *item = selection->singleItem();
1390     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1391         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
1392         Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
1393         if (shape_repr) {
1394             ec->shape_repr = shape_repr;
1395             Inkscape::GC::anchor(shape_repr);
1396             sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
1397         }
1398     }
1400     if (tc->text && (item != tc->text)) {
1401         sp_text_context_forget_text(tc);
1402     }
1403     tc->text = NULL;
1405     if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1406         tc->text = item;
1407         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1408         if (layout)
1409             tc->text_sel_start = tc->text_sel_end = layout->end();
1410     } else {
1411         tc->text = NULL;
1412     }
1414     // we update cursor without scrolling, because this position may not be final;
1415     // item_handler moves cusros to the point of click immediately
1416     sp_text_context_update_cursor(tc, false);
1417     sp_text_context_update_text_selection(tc);
1420 static void
1421 sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, SPTextContext *tc)
1423     sp_text_context_update_cursor(tc);
1424     sp_text_context_update_text_selection(tc);
1427 static bool
1428 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1430     if (tc->text == NULL)
1431         return false;
1432     if (tc->text_sel_start == tc->text_sel_end)
1433         return false;    // will get picked up by the parent and applied to the whole text object
1435     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1436     sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1437                      _("Set text style"));
1438     sp_text_context_update_cursor(tc);
1439     sp_text_context_update_text_selection(tc);
1441     return true;
1444 static int
1445 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1447     if (tc->text == NULL)
1448         return QUERY_STYLE_NOTHING;
1449     const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1450     if (layout == NULL)
1451         return QUERY_STYLE_NOTHING;
1452     sp_text_context_validate_cursor_iterators(tc);
1454     GSList *styles_list = NULL;
1456     Inkscape::Text::Layout::iterator begin_it, end_it;
1457     if (tc->text_sel_start < tc->text_sel_end) {
1458         begin_it = tc->text_sel_start;
1459         end_it = tc->text_sel_end;
1460     } else {
1461         begin_it = tc->text_sel_end;
1462         end_it = tc->text_sel_start;
1463     }
1464     if (begin_it == end_it)
1465         if (!begin_it.prevCharacter())
1466             end_it.nextCharacter();
1467     for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1468         SPObject const *pos_obj = 0;
1469         void *rawptr = 0;
1470         layout->getSourceOfCharacter(it, &rawptr);
1471         if (!rawptr || !SP_IS_OBJECT(rawptr))
1472             continue;
1473         pos_obj = SP_OBJECT(rawptr);
1474         while (SP_IS_STRING(pos_obj) && SP_OBJECT_PARENT(pos_obj)) {
1475            pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
1476         }
1477         styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1478     }
1480     int result = sp_desktop_query_style_from_list (styles_list, style, property);
1482     g_slist_free(styles_list);
1483     return result;
1486 static void
1487 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1489     if (tc->text == NULL)
1490         return;
1491     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1492     if (layout) {     // undo can change the text length without us knowing it
1493         layout->validateIterator(&tc->text_sel_start);
1494         layout->validateIterator(&tc->text_sel_end);
1495     }
1498 static void
1499 sp_text_context_update_cursor(SPTextContext *tc,  bool scroll_to_see)
1501     GdkRectangle im_cursor = { 0, 0, 1, 1 };
1503     // due to interruptible display, tc may already be destroyed during a display update before
1504     // the cursor update (can't do both atomically, alas)
1505     if (!tc->desktop) return;
1507     if (tc->text) {
1508         NR::Point p0, p1;
1509         sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1510         NR::Point const d0 = p0 * from_2geom(sp_item_i2d_affine(SP_ITEM(tc->text)));
1511         NR::Point const d1 = p1 * from_2geom(sp_item_i2d_affine(SP_ITEM(tc->text)));
1513         // scroll to show cursor
1514         if (scroll_to_see) {
1515             NR::Point const dm = (d0 + d1) / 2;
1516             // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1517             SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(&dm, 1.0);
1518         }
1520         sp_canvas_item_show(tc->cursor);
1521         sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1523         /* fixme: ... need another transformation to get canvas widget coordinate space? */
1524         im_cursor.x = (int) floor(d0[NR::X]);
1525         im_cursor.y = (int) floor(d0[NR::Y]);
1526         im_cursor.width = (int) floor(d1[NR::X]) - im_cursor.x;
1527         im_cursor.height = (int) floor(d1[NR::Y]) - im_cursor.y;
1529         tc->show = TRUE;
1530         tc->phase = 1;
1532         if (SP_IS_FLOWTEXT(tc->text)) {
1533             SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1534             if (frame) {
1535                 sp_canvas_item_show(tc->frame);
1536                 NR::Maybe<NR::Rect> frame_bbox = sp_item_bbox_desktop(frame);
1537                 if (frame_bbox) {
1538                     SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
1539                 }
1540             }
1541             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1542         } else {
1543             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1544         }
1546     } else {
1547         sp_canvas_item_hide(tc->cursor);
1548         sp_canvas_item_hide(tc->frame);
1549         tc->show = FALSE;
1550         if (!tc->nascent_object) {
1551             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
1552         }
1553     }
1555     if (tc->imc) {
1556         gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1557     }
1558     SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1561 static void sp_text_context_update_text_selection(SPTextContext *tc)
1563     // due to interruptible display, tc may already be destroyed during a display update before
1564     // the selection update (can't do both atomically, alas)
1565     if (!tc->desktop) return;
1567     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1568         sp_canvas_item_hide(*it);
1569         gtk_object_destroy(*it);
1570     }
1571     tc->text_selection_quads.clear();
1573     std::vector<NR::Point> quads;
1574     if (tc->text != NULL)
1575         quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, from_2geom(sp_item_i2d_affine(tc->text)));
1576     for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1577         SPCanvasItem *quad_canvasitem;
1578         quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1579         // FIXME: make the color settable in prefs
1580         // for now, use semitrasparent blue, as cairo cannot do inversion :(
1581         sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x00777777);
1582         sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1583         sp_canvas_item_show(quad_canvasitem);
1584         tc->text_selection_quads.push_back(quad_canvasitem);
1585     }
1588 static gint
1589 sp_text_context_timeout(SPTextContext *tc)
1591     if (tc->show) {
1592         if (tc->phase) {
1593             tc->phase = 0;
1594             sp_canvas_item_hide(tc->cursor);
1595         } else {
1596             tc->phase = 1;
1597             sp_canvas_item_show(tc->cursor);
1598         }
1599     }
1601     return TRUE;
1604 static void
1605 sp_text_context_forget_text(SPTextContext *tc)
1607     if (! tc->text) return;
1608     SPItem *ti = tc->text;
1609     (void)ti;
1610     /* We have to set it to zero,
1611      * or selection changed signal messes everything up */
1612     tc->text = NULL;
1614 /* FIXME: this automatic deletion when nothing is inputted crashes the XML edittor and also crashes when duplicating an empty flowtext.
1615     So don't create an empty flowtext in the first place? Create it when first character is typed.
1616     */
1617 /*
1618     if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1619         Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1620         // the repr may already have been unparented
1621         // if we were called e.g. as the result of
1622         // an undo or the element being removed from
1623         // the XML editor
1624         if ( text_repr && sp_repr_parent(text_repr) ) {
1625             sp_repr_unparent(text_repr);
1626             sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1627                      _("Remove empty text"));
1628         }
1629     }
1630 */
1633 gint
1634 sptc_focus_in(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1636     gtk_im_context_focus_in(tc->imc);
1637     return FALSE;
1640 gint
1641 sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1643     gtk_im_context_focus_out(tc->imc);
1644     return FALSE;
1647 static void
1648 sptc_commit(GtkIMContext */*imc*/, gchar *string, SPTextContext *tc)
1650     if (!tc->text) {
1651         sp_text_context_setup_text(tc);
1652         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1653     }
1655     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1656     sp_text_context_update_cursor(tc);
1657     sp_text_context_update_text_selection(tc);
1659     sp_document_done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT,
1660                      _("Type text"));
1664 /*
1665   Local Variables:
1666   mode:c++
1667   c-file-style:"stroustrup"
1668   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1669   indent-tabs-mode:nil
1670   fill-column:99
1671   End:
1672 */
1673 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :