Code

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