Code

8f6d6f1e379ca16f6836d404382bfde68db48a09
[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         sp_knot_holder_destroy(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                 ret = TRUE;
433                 break;
434             }
435             // find out item under mouse, disregarding groups
436             item_ungrouped = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE);
437             if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
438                 sp_canvas_item_show(tc->indicator);
439                 NR::Maybe<NR::Rect> ibbox = sp_item_bbox_desktop(item_ungrouped);
440                 if (ibbox) {
441                     SP_CTRLRECT(tc->indicator)->setRectangle(*ibbox);
442                 }
444                 event_context->cursor_shape = cursor_text_insert_xpm;
445                 event_context->hot_x = 7;
446                 event_context->hot_y = 10;
447                 sp_event_context_update_cursor(event_context);
448                 sp_text_context_update_text_selection(tc);
450                 if (SP_IS_TEXT (item_ungrouped)) {
451                     desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
452                 } else {
453                     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."));
454                 }
456                 tc->over_text = true;
458                 ret = TRUE;
459             }
460             break;
461         default:
462             break;
463     }
465     if (!ret) {
466         if (((SPEventContextClass *) parent_class)->item_handler)
467             ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
468     }
470     return ret;
473 static void
474 sp_text_context_setup_text(SPTextContext *tc)
476     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
478     /* Create <text> */
479     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_EVENT_CONTEXT_DESKTOP(ec)->doc());
480     Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
481     rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
483     /* Set style */
484     sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "tools.text", true);
486     sp_repr_set_svg_double(rtext, "x", tc->pdoc[NR::X]);
487     sp_repr_set_svg_double(rtext, "y", tc->pdoc[NR::Y]);
489     /* Create <tspan> */
490     Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
491     rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
492     rtext->addChild(rtspan, NULL);
493     Inkscape::GC::release(rtspan);
495     /* Create TEXT */
496     Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
497     rtspan->addChild(rstring, NULL);
498     Inkscape::GC::release(rstring);
499     SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
500     /* fixme: Is selection::changed really immediate? */
501     /* yes, it's immediate .. why does it matter? */
502     sp_desktop_selection(ec->desktop)->set(text_item);
503     Inkscape::GC::release(rtext);
504     text_item->transform = SP_ITEM(ec->desktop->currentRoot())->getRelativeTransform(ec->desktop->currentLayer());
505     text_item->updateRepr();
506     sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, 
507                      _("Create text"));
510 /**
511  * Insert the character indicated by tc.uni to replace the current selection,
512  * and reset tc.uni/tc.unipos to empty string.
513  *
514  * \pre tc.uni/tc.unipos non-empty.
515  */
516 static void
517 insert_uni_char(SPTextContext *const tc)
519     g_return_if_fail(tc->unipos
520                      && tc->unipos < sizeof(tc->uni)
521                      && tc->uni[tc->unipos] == '\0');
522     unsigned int uv;
523     sscanf(tc->uni, "%x", &uv);
524     tc->unipos = 0;
525     tc->uni[tc->unipos] = '\0';
527     if ( !g_unichar_isprint(static_cast<gunichar>(uv))
528          && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
529         // This may be due to bad input, so it goes to statusbar.
530         tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
531                                            _("Non-printable character"));
532     } else {
533         if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
534             sp_text_context_setup_text(tc);
535             tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
536         }
538         gchar u[10];
539         guint const len = g_unichar_to_utf8(uv, u);
540         u[len] = '\0';
542         tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
543         sp_text_context_update_cursor(tc);
544         sp_text_context_update_text_selection(tc);
545         sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM, 
546                          _("Insert Unicode character"));
547     }
550 static void
551 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
553     unsigned int uv;
554     sscanf(hex, "%x", &uv);
555     if (!g_unichar_isprint((gunichar) uv)) {
556         uv = 0xfffd;
557     }
558     guint const len = g_unichar_to_utf8(uv, utf8);
559     utf8[len] = '\0';
562 static void
563 show_curr_uni_char(SPTextContext *const tc)
565     g_return_if_fail(tc->unipos < sizeof(tc->uni)
566                      && tc->uni[tc->unipos] == '\0');
567     if (tc->unipos) {
568         char utf8[10];
569         hex_to_printable_utf8_buf(tc->uni, utf8);
571         /* Status bar messages are in pango markup, so we need xml escaping. */
572         if (utf8[1] == '\0') {
573             switch(utf8[0]) {
574                 case '<': strcpy(utf8, "&lt;"); break;
575                 case '>': strcpy(utf8, "&gt;"); break;
576                 case '&': strcpy(utf8, "&amp;"); break;
577                 default: break;
578             }
579         }
580         tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
581                                           _("Unicode (<b>Enter</b> to finish): %s: %s"), tc->uni, utf8);
582     } else {
583         tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
584     }
587 static gint
588 sp_text_context_root_handler(SPEventContext *const event_context, GdkEvent *const event)
590     SPTextContext *const tc = SP_TEXT_CONTEXT(event_context);
592     SPDesktop *desktop = event_context->desktop;
594     sp_canvas_item_hide(tc->indicator);
596     sp_text_context_validate_cursor_iterators(tc);
598     event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100);
600     switch (event->type) {
601         case GDK_BUTTON_PRESS:
602             if (event->button.button == 1 && !event_context->space_panning) {
604                 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
605                     return TRUE;
606                 }
608                 // save drag origin
609                 event_context->xp = (gint) event->button.x;
610                 event_context->yp = (gint) event->button.y;
611                 event_context->within_tolerance = true;
613                 NR::Point const button_pt(event->button.x, event->button.y);
614                 tc->p0 = desktop->w2d(button_pt);
615                 Inkscape::Rubberband::get()->start(desktop, tc->p0);
616                 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
617                                     GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
618                                         GDK_POINTER_MOTION_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::NORMAL_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                                 }
918                                 
919                                 iterator_pair enter_pair;
920                                 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, enter_pair);
921                                 tc->text_sel_start = tc->text_sel_end = enter_pair.first;
922                                 
923                                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
924                                 
925                                 sp_text_context_update_cursor(tc);
926                                 sp_text_context_update_text_selection(tc);
927                                 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, 
928                                                  _("New line"));
929                                 return TRUE;
930                             }
931                             case GDK_BackSpace:
932                                 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
933                                     
934                                     bool noSelection = false;
935                                     
936                                         if (tc->text_sel_start == tc->text_sel_end) {
937                                         tc->text_sel_start.prevCursorPosition();
938                                         noSelection = true;
939                                     }
940                                         
941                                         iterator_pair bspace_pair;
942                                         bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, bspace_pair);
943                                     
944                                     if (noSelection) {
945                                         if (success) {
946                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
947                                         } else { // nothing deleted
948                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.second;
949                                         }
950                                     } else {
951                                         if (success) {
952                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
953                                         } else { // nothing deleted
954                                             tc->text_sel_start = bspace_pair.first;
955                                             tc->text_sel_end = bspace_pair.second;
956                                         }
957                                     }
958                                     
959                                     sp_text_context_update_cursor(tc);
960                                     sp_text_context_update_text_selection(tc);
961                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, 
962                                                      _("Backspace"));
963                                 }
964                                 return TRUE;
965                             case GDK_Delete:
966                             case GDK_KP_Delete:
967                                 if (tc->text) {
968                                     bool noSelection = false;
969                                     
970                                     if (tc->text_sel_start == tc->text_sel_end) {
971                                         tc->text_sel_end.nextCursorPosition();
972                                         noSelection = true;
973                                     }
974                                     
975                                     iterator_pair del_pair;
976                                     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, del_pair);
977                                     
978                                     if (noSelection) {
979                                         tc->text_sel_start = tc->text_sel_end = del_pair.first;
980                                     } else {
981                                         if (success) {
982                                             tc->text_sel_start = tc->text_sel_end = del_pair.first;
983                                         } else { // nothing deleted
984                                             tc->text_sel_start = del_pair.first;
985                                             tc->text_sel_end = del_pair.second;
986                                         }
987                                     }
988                                     
989                                     
990                                     sp_text_context_update_cursor(tc);
991                                     sp_text_context_update_text_selection(tc);
992                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, 
993                                                      _("Delete"));
994                                 }
995                                 return TRUE;
996                             case GDK_Left:
997                             case GDK_KP_Left:
998                             case GDK_KP_4:
999                                 if (tc->text) {
1000                                     if (MOD__ALT) {
1001                                         if (MOD__SHIFT)
1002                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, NR::Point(-10, 0));
1003                                         else
1004                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, NR::Point(-1, 0));
1005                                         sp_text_context_update_cursor(tc);
1006                                         sp_text_context_update_text_selection(tc);
1007                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT, 
1008                                                                _("Kern to the left"));
1009                                     } else {
1010                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorLeftWithControl
1011                                                                              : &Inkscape::Text::Layout::iterator::cursorLeft;
1012                                         break;
1013                                     }
1014                                 }
1015                                 return TRUE;
1016                             case GDK_Right:
1017                             case GDK_KP_Right:
1018                             case GDK_KP_6:
1019                                 if (tc->text) {
1020                                     if (MOD__ALT) {
1021                                         if (MOD__SHIFT)
1022                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, NR::Point(10, 0));
1023                                         else
1024                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, NR::Point(1, 0));
1025                                         sp_text_context_update_cursor(tc);
1026                                         sp_text_context_update_text_selection(tc);
1027                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT, 
1028                                                                _("Kern to the right"));
1029                                     } else {
1030                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorRightWithControl
1031                                                                              : &Inkscape::Text::Layout::iterator::cursorRight;
1032                                         break;
1033                                     }
1034                                 }
1035                                 return TRUE;
1036                             case GDK_Up:
1037                             case GDK_KP_Up:
1038                             case GDK_KP_8:
1039                                 if (tc->text) {
1040                                     if (MOD__ALT) {
1041                                         if (MOD__SHIFT)
1042                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, NR::Point(0, -10));
1043                                         else
1044                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, NR::Point(0, -1));
1045                                         sp_text_context_update_cursor(tc);
1046                                         sp_text_context_update_text_selection(tc);
1047                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT, 
1048                                                                _("Kern up"));
1050                                     } else {
1051                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorUpWithControl
1052                                                                              : &Inkscape::Text::Layout::iterator::cursorUp;
1053                                         break;
1054                                     }
1055                                 }
1056                                 return TRUE;
1057                             case GDK_Down:
1058                             case GDK_KP_Down:
1059                             case GDK_KP_2:
1060                                 if (tc->text) {
1061                                     if (MOD__ALT) {
1062                                         if (MOD__SHIFT)
1063                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, NR::Point(0, 10));
1064                                         else
1065                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, NR::Point(0, 1));
1066                                         sp_text_context_update_cursor(tc);
1067                                         sp_text_context_update_text_selection(tc);
1068                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT, 
1069                                                                _("Kern down"));
1071                                     } else {
1072                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorDownWithControl
1073                                                                              : &Inkscape::Text::Layout::iterator::cursorDown;
1074                                         break;
1075                                     }
1076                                 }
1077                                 return TRUE;
1078                             case GDK_Home:
1079                             case GDK_KP_Home:
1080                                 if (tc->text) {
1081                                     if (MOD__CTRL)
1082                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfShape;
1083                                     else
1084                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfLine;
1085                                     break;
1086                                 }
1087                                 return TRUE;
1088                             case GDK_End:
1089                             case GDK_KP_End:
1090                                 if (tc->text) {
1091                                     if (MOD__CTRL)
1092                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::nextStartOfShape;
1093                                     else
1094                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisEndOfLine;
1095                                     break;
1096                                 }
1097                                 return TRUE;
1098                             case GDK_Escape:
1099                                 if (tc->creating) {
1100                                     tc->creating = 0;
1101                                     if (tc->grabbed) {
1102                                         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1103                                         tc->grabbed = NULL;
1104                                     }
1105                                     Inkscape::Rubberband::get()->stop();
1106                                 } else {
1107                                     sp_desktop_selection(desktop)->clear();
1108                                 }
1109                                 tc->nascent_object = FALSE;
1110                                 return TRUE;
1111                             case GDK_bracketleft:
1112                                 if (tc->text) {
1113                                     if (MOD__ALT || MOD__CTRL) {
1114                                         if (MOD__ALT) {
1115                                             if (MOD__SHIFT) {
1116                                                 // FIXME: alt+shift+[] does not work, don't know why
1117                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1118                                             } else {
1119                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1120                                             }
1121                                         } else {
1122                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -90);
1123                                         }
1124                                         sp_document_maybe_done(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT, 
1125                                                                _("Rotate counterclockwise"));
1126                                         sp_text_context_update_cursor(tc);
1127                                         sp_text_context_update_text_selection(tc);
1128                                         return TRUE;
1129                                     }
1130                                 }
1131                                 break;
1132                             case GDK_bracketright:
1133                                 if (tc->text) {
1134                                     if (MOD__ALT || MOD__CTRL) {
1135                                         if (MOD__ALT) {
1136                                             if (MOD__SHIFT) {
1137                                                 // FIXME: alt+shift+[] does not work, don't know why
1138                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1139                                             } else {
1140                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1141                                             }
1142                                         } else {
1143                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 90);
1144                                         }
1145                                         sp_document_maybe_done(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT, 
1146                                                                 _("Rotate clockwise"));
1147                                         sp_text_context_update_cursor(tc);
1148                                         sp_text_context_update_text_selection(tc);
1149                                         return TRUE;
1150                                     }
1151                                 }
1152                                 break;
1153                             case GDK_less:
1154                             case GDK_comma:
1155                                 if (tc->text) {
1156                                     if (MOD__ALT) {
1157                                         if (MOD__CTRL) {
1158                                             if (MOD__SHIFT)
1159                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1160                                             else
1161                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1162                                             sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT, 
1163                                                                     _("Contract line spacing"));
1165                                         } else {
1166                                             if (MOD__SHIFT)
1167                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1168                                             else
1169                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1170                                             sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT, 
1171                                                                     _("Contract letter spacing"));
1173                                         }
1174                                         sp_text_context_update_cursor(tc);
1175                                         sp_text_context_update_text_selection(tc);
1176                                         return TRUE;
1177                                     }
1178                                 }
1179                                 break;
1180                             case GDK_greater:
1181                             case GDK_period:
1182                                 if (tc->text) {
1183                                     if (MOD__ALT) {
1184                                         if (MOD__CTRL) {
1185                                             if (MOD__SHIFT)
1186                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1187                                             else
1188                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1189                                             sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT, 
1190                                                                     _("Expand line spacing"));
1192                                         } else {
1193                                             if (MOD__SHIFT)
1194                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1195                                             else
1196                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1197                                             sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT, 
1198                                                                     _("Expand letter spacing"));
1200                                         }
1201                                         sp_text_context_update_cursor(tc);
1202                                         sp_text_context_update_text_selection(tc);
1203                                         return TRUE;
1204                                     }
1205                                 }
1206                                 break;
1207                             default:
1208                                 break;
1209                         }
1211                         if (cursor_movement_operator) {
1212                             Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
1213                             Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
1214                             (tc->text_sel_end.*cursor_movement_operator)();
1215                             if (!MOD__SHIFT)
1216                                 tc->text_sel_start = tc->text_sel_end;
1217                             if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1218                                 sp_text_context_update_cursor(tc);
1219                                 sp_text_context_update_text_selection(tc);
1220                             }
1221                             return TRUE;
1222                         }
1224                 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1225             } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1226                 // except up/down that are swallowed to prevent the zoom field from activation
1227                 if ((group0_keyval == GDK_Up    ||
1228                      group0_keyval == GDK_Down  ||
1229                      group0_keyval == GDK_KP_Up ||
1230                      group0_keyval == GDK_KP_Down )
1231                     && !MOD__CTRL_ONLY) {
1232                     return TRUE;
1233                 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1234                     if (tc->creating) {
1235                         tc->creating = 0;
1236                         if (tc->grabbed) {
1237                             sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1238                             tc->grabbed = NULL;
1239                         }
1240                         Inkscape::Rubberband::get()->stop();
1241                     }
1242                 }
1243             }
1244             break;
1245         }
1247         case GDK_KEY_RELEASE:
1248             if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1249                 return TRUE;
1250             }
1251             break;
1252         default:
1253             break;
1254     }
1256     // if nobody consumed it so far
1257     if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1258         return ((SPEventContextClass *) parent_class)->root_handler(event_context, event); // send event to parent
1259     } else {
1260         return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1261     }
1264 /**
1265  Attempts to paste system clipboard into the currently edited text, returns true on success
1266  */
1267 bool
1268 sp_text_paste_inline(SPEventContext *ec)
1270     if (!SP_IS_TEXT_CONTEXT(ec))
1271         return false;
1273     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1275     if ((tc->text) || (tc->nascent_object)) {
1276         // there is an active text object in this context, or a new object was just created
1278         Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1279         Glib::ustring const text = refClipboard->wait_for_text();
1281         if (!text.empty()) {
1283             if (!tc->text) { // create text if none (i.e. if nascent_object)
1284                 sp_text_context_setup_text(tc);
1285                 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1286             }
1288             // using indices is slow in ustrings. Whatever.
1289             Glib::ustring::size_type begin = 0;
1290             for ( ; ; ) {
1291                 Glib::ustring::size_type end = text.find('\n', begin);
1292                 if (end == Glib::ustring::npos) {
1293                     if (begin != text.length())
1294                         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());
1295                     break;
1296                 }
1297                 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());
1298                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1299                 begin = end + 1;
1300             }
1301             sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, 
1302                              _("Paste text"));
1304             return true;
1305         }
1306     } // FIXME: else create and select a new object under cursor!
1308     return false;
1311 /**
1312  Gets the raw characters that comprise the currently selected text, converting line
1313  breaks into lf characters.
1314 */
1315 Glib::ustring
1316 sp_text_get_selected_text(SPEventContext const *ec)
1318     if (!SP_IS_TEXT_CONTEXT(ec))
1319         return "";
1320     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1321     if (tc->text == NULL)
1322         return "";
1324     return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1327 /**
1328  Deletes the currently selected characters. Returns false if there is no
1329  text selection currently.
1330 */
1331 bool sp_text_delete_selection(SPEventContext *ec)
1333     if (!SP_IS_TEXT_CONTEXT(ec))
1334         return false;
1335     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1336     if (tc->text == NULL)
1337         return false;
1339     if (tc->text_sel_start == tc->text_sel_end)
1340         return false;
1341     
1342     iterator_pair pair;
1343     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair);
1344     
1345     
1346     if (success) {
1347         tc->text_sel_start = tc->text_sel_end = pair.first;
1348     } else { // nothing deleted
1349         tc->text_sel_start = pair.first;
1350         tc->text_sel_end = pair.second;
1351     }
1352     
1353     sp_text_context_update_cursor(tc);
1354     sp_text_context_update_text_selection(tc);
1355     
1356     return true;
1359 /**
1360  * \param selection Should not be NULL.
1361  */
1362 static void
1363 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1365     g_assert(selection != NULL);
1367     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1369     if (ec->shape_knot_holder) { // destroy knotholder
1370         sp_knot_holder_destroy(ec->shape_knot_holder);
1371         ec->shape_knot_holder = NULL;
1372     }
1374     if (ec->shape_repr) { // remove old listener
1375         sp_repr_remove_listener_by_data(ec->shape_repr, ec);
1376         Inkscape::GC::release(ec->shape_repr);
1377         ec->shape_repr = 0;
1378     }
1380     SPItem *item = selection->singleItem();
1381     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1382         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
1383         Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
1384         if (shape_repr) {
1385             ec->shape_repr = shape_repr;
1386             Inkscape::GC::anchor(shape_repr);
1387             sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
1388         }
1389     }
1391     if (tc->text && (item != tc->text)) {
1392         sp_text_context_forget_text(tc);
1393     }
1394     tc->text = NULL;
1396     if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1397         tc->text = item;
1398         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1399         if (layout)
1400             tc->text_sel_start = tc->text_sel_end = layout->end();
1401     } else {
1402         tc->text = NULL;
1403     }
1405     // we update cursor without scrolling, because this position may not be final;
1406     // item_handler moves cusros to the point of click immediately
1407     sp_text_context_update_cursor(tc, false);
1408     sp_text_context_update_text_selection(tc);
1411 static void
1412 sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc)
1414     sp_text_context_update_cursor(tc);
1415     sp_text_context_update_text_selection(tc);
1418 static bool
1419 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1421     if (tc->text == NULL)
1422         return false;
1423     if (tc->text_sel_start == tc->text_sel_end)
1424         return false;    // will get picked up by the parent and applied to the whole text object
1426     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1427     sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT, 
1428                      _("Set text style"));
1429     sp_text_context_update_cursor(tc);
1430     sp_text_context_update_text_selection(tc);
1432     return true;
1435 static int
1436 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1438     if (tc->text == NULL)
1439         return QUERY_STYLE_NOTHING;
1440     const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1441     if (layout == NULL)
1442         return QUERY_STYLE_NOTHING;
1443     sp_text_context_validate_cursor_iterators(tc);
1445     GSList *styles_list = NULL;
1447     Inkscape::Text::Layout::iterator begin_it, end_it;
1448     if (tc->text_sel_start < tc->text_sel_end) {
1449         begin_it = tc->text_sel_start;
1450         end_it = tc->text_sel_end;
1451     } else {
1452         begin_it = tc->text_sel_end;
1453         end_it = tc->text_sel_start;
1454     }
1455     if (begin_it == end_it)
1456         if (!begin_it.prevCharacter())
1457             end_it.nextCharacter();
1458     for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1459         SPObject const *pos_obj = 0;
1460         void *rawptr = 0;
1461         layout->getSourceOfCharacter(it, &rawptr);
1462         pos_obj = SP_OBJECT(rawptr);
1463         if (pos_obj == 0) continue;
1464         while (SP_IS_STRING(pos_obj) && SP_OBJECT_PARENT(pos_obj)) {
1465            pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
1466         }
1467         styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1468     }
1470     int result = sp_desktop_query_style_from_list (styles_list, style, property);
1472     g_slist_free(styles_list);
1473     return result;
1476 static void
1477 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1479     if (tc->text == NULL)
1480         return;
1481     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1482     if (layout) {     // undo can change the text length without us knowing it
1483         layout->validateIterator(&tc->text_sel_start);
1484         layout->validateIterator(&tc->text_sel_end);
1485     }
1488 static void
1489 sp_text_context_update_cursor(SPTextContext *tc,  bool scroll_to_see)
1491     GdkRectangle im_cursor = { 0, 0, 1, 1 };
1493     // due to interruptible display, tc may already be destroyed during a display update before
1494     // the cursor update (can't do both atomically, alas)
1495     if (!tc->desktop) return;
1497     if (tc->text) {
1498         NR::Point p0, p1;
1499         sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1500         NR::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1501         NR::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1503         // scroll to show cursor
1504         if (scroll_to_see) {
1505             NR::Point const dm = (d0 + d1) / 2;
1506             // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1507             SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(&dm, 1.0);
1508         }
1510         sp_canvas_item_show(tc->cursor);
1511         sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1513         /* fixme: ... need another transformation to get canvas widget coordinate space? */
1514         im_cursor.x = (int) floor(d0[NR::X]);
1515         im_cursor.y = (int) floor(d0[NR::Y]);
1516         im_cursor.width = (int) floor(d1[NR::X]) - im_cursor.x;
1517         im_cursor.height = (int) floor(d1[NR::Y]) - im_cursor.y;
1519         tc->show = TRUE;
1520         tc->phase = 1;
1522         if (SP_IS_FLOWTEXT(tc->text)) {
1523             SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1524             if (frame) {
1525                 sp_canvas_item_show(tc->frame);
1526                 NR::Maybe<NR::Rect> frame_bbox = sp_item_bbox_desktop(frame);
1527                 if (frame_bbox) {
1528                     SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
1529                 }
1530             }
1531             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1532         } else {
1533             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1534         }
1536     } else {
1537         sp_canvas_item_hide(tc->cursor);
1538         sp_canvas_item_hide(tc->frame);
1539         tc->show = FALSE;
1540         if (!tc->nascent_object) {
1541             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
1542         }
1543     }
1545     if (tc->imc) {
1546         gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1547     }
1548     SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1551 static void sp_text_context_update_text_selection(SPTextContext *tc)
1553     // due to interruptible display, tc may already be destroyed during a display update before
1554     // the selection update (can't do both atomically, alas)
1555     if (!tc->desktop) return;
1557     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1558         sp_canvas_item_hide(*it);
1559         gtk_object_destroy(*it);
1560     }
1561     tc->text_selection_quads.clear();
1563     std::vector<NR::Point> quads;
1564     if (tc->text != NULL)
1565         quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1566     for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1567         SPCanvasItem *quad_canvasitem;
1568         quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1569         sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x000000ff);
1570         sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1571         sp_canvas_item_show(quad_canvasitem);
1572         tc->text_selection_quads.push_back(quad_canvasitem);
1573     }
1576 static gint
1577 sp_text_context_timeout(SPTextContext *tc)
1579     if (tc->show) {
1580         if (tc->phase) {
1581             tc->phase = 0;
1582             sp_canvas_item_hide(tc->cursor);
1583         } else {
1584             tc->phase = 1;
1585             sp_canvas_item_show(tc->cursor);
1586         }
1587     }
1589     return TRUE;
1592 static void
1593 sp_text_context_forget_text(SPTextContext *tc)
1595     if (! tc->text) return;
1596     SPItem *ti = tc->text;
1597     /* We have to set it to zero,
1598      * or selection changed signal messes everything up */
1599     tc->text = NULL;
1600     if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1601         Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1602         // the repr may already have been unparented
1603         // if we were called e.g. as the result of
1604         // an undo or the element being removed from
1605         // the XML editor
1606         if ( text_repr && sp_repr_parent(text_repr) ) {
1607             sp_repr_unparent(text_repr);
1608             sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT, 
1609                      _("Remove empty text"));
1610         }
1611     }
1614 gint
1615 sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1617     gtk_im_context_focus_in(tc->imc);
1618     return FALSE;
1621 gint
1622 sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1624     gtk_im_context_focus_out(tc->imc);
1625     return FALSE;
1628 static void
1629 sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc)
1631     if (!tc->text) {
1632         sp_text_context_setup_text(tc);
1633         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1634     }
1636     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1637     sp_text_context_update_cursor(tc);
1638     sp_text_context_update_text_selection(tc);
1640     sp_document_done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT, 
1641                      _("Type text"));
1645 /*
1646   Local Variables:
1647   mode:c++
1648   c-file-style:"stroustrup"
1649   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1650   indent-tabs-mode:nil
1651   fill-column:99
1652   End:
1653 */
1654 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :