Code

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