Code

switch to sigc++ signal for "release"
[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.pixbuf"
43 #include "pixmaps/cursor-text-insert.xpm"
44 #include "pixmaps/cursor-text-insert.pixbuf"
45 #include <glibmm/i18n.h>
46 #include "object-edit.h"
47 #include "xml/repr.h"
48 #include "xml/node-event-vector.h"
49 #include "prefs-utils.h"
50 #include "rubberband.h"
51 #include "sp-metrics.h"
52 #include "context-fns.h"
53 #include "verbs.h"
55 #include "text-editing.h"
57 #include "text-context.h"
60 static void sp_text_context_class_init(SPTextContextClass *klass);
61 static void sp_text_context_init(SPTextContext *text_context);
62 static void sp_text_context_dispose(GObject *obj);
64 static void sp_text_context_setup(SPEventContext *ec);
65 static void sp_text_context_finish(SPEventContext *ec);
66 static gint sp_text_context_root_handler(SPEventContext *event_context, GdkEvent *event);
67 static gint sp_text_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
69 static void sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc);
70 static void sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc);
71 static bool sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc);
72 static int sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc);
74 static void sp_text_context_validate_cursor_iterators(SPTextContext *tc);
75 static void sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see = true);
76 static void sp_text_context_update_text_selection(SPTextContext *tc);
77 static gint sp_text_context_timeout(SPTextContext *tc);
78 static void sp_text_context_forget_text(SPTextContext *tc);
80 static gint sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc);
81 static gint sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc);
82 static void sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc);
84 static SPEventContextClass *parent_class;
86 GType
87 sp_text_context_get_type()
88 {
89     static GType type = 0;
90     if (!type) {
91         GTypeInfo info = {
92             sizeof(SPTextContextClass),
93             NULL, NULL,
94             (GClassInitFunc) sp_text_context_class_init,
95             NULL, NULL,
96             sizeof(SPTextContext),
97             4,
98             (GInstanceInitFunc) sp_text_context_init,
99             NULL,   /* value_table */
100         };
101         type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPTextContext", &info, (GTypeFlags)0);
102     }
103     return type;
106 static void
107 sp_text_context_class_init(SPTextContextClass *klass)
109     GObjectClass *object_class=(GObjectClass *)klass;
110     SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
112     parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
114     object_class->dispose = sp_text_context_dispose;
116     event_context_class->setup = sp_text_context_setup;
117     event_context_class->finish = sp_text_context_finish;
118     event_context_class->root_handler = sp_text_context_root_handler;
119     event_context_class->item_handler = sp_text_context_item_handler;
122 static void
123 sp_text_context_init(SPTextContext *tc)
125     SPEventContext *event_context = SP_EVENT_CONTEXT(tc);
127     event_context->cursor_shape = cursor_text_xpm;
128     event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
129             -1,
130             cursor_text_pixbuf,
131             FALSE,
132             NULL);  
133     event_context->hot_x = 7;
134     event_context->hot_y = 7;
136     event_context->xp = 0;
137     event_context->yp = 0;
138     event_context->tolerance = 0;
139     event_context->within_tolerance = false;
141     event_context->shape_repr = NULL;
142     event_context->shape_knot_holder = NULL;
144     tc->imc = NULL;
146     tc->text = NULL;
147     tc->pdoc = NR::Point(0, 0);
148     new (&tc->text_sel_start) Inkscape::Text::Layout::iterator();
149     new (&tc->text_sel_end) Inkscape::Text::Layout::iterator();
150     new (&tc->text_selection_quads) std::vector<SPCanvasItem*>();
152     tc->unimode = false;
154     tc->cursor = NULL;
155     tc->indicator = NULL;
156     tc->frame = NULL;
157     tc->grabbed = NULL;
158     tc->timeout = 0;
159     tc->show = FALSE;
160     tc->phase = 0;
161     tc->nascent_object = 0;
162     tc->over_text = 0;
163     tc->dragging = 0;
164     tc->creating = 0;
166     new (&tc->sel_changed_connection) sigc::connection();
167     new (&tc->sel_modified_connection) sigc::connection();
168     new (&tc->style_set_connection) sigc::connection();
169     new (&tc->style_query_connection) sigc::connection();
172 static void
173 sp_text_context_dispose(GObject *obj)
175     SPTextContext *tc = SP_TEXT_CONTEXT(obj);
176     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
177     tc->style_query_connection.~connection();
178     tc->style_set_connection.~connection();
179     tc->sel_changed_connection.~connection();
180     tc->sel_modified_connection.~connection();
181     tc->text_sel_end.~iterator();
182     tc->text_sel_start.~iterator();
183     tc->text_selection_quads.~vector();
184     if (G_OBJECT_CLASS(parent_class)->dispose) {
185         G_OBJECT_CLASS(parent_class)->dispose(obj);
186     }
187     if (tc->grabbed) {
188         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
189         tc->grabbed = NULL;
190     }
192     Inkscape::Rubberband::get()->stop();
194     if (ec->shape_knot_holder) {
195         sp_knot_holder_destroy(ec->shape_knot_holder);
196         ec->shape_knot_holder = NULL;
197     }
198     if (ec->shape_repr) { // remove old listener
199         sp_repr_remove_listener_by_data(ec->shape_repr, ec);
200         Inkscape::GC::release(ec->shape_repr);
201         ec->shape_repr = 0;
202     }
205 static Inkscape::XML::NodeEventVector ec_shape_repr_events = {
206     NULL, /* child_added */
207     NULL, /* child_removed */
208     ec_shape_event_attr_changed,
209     NULL, /* content_changed */
210     NULL  /* order_changed */
211 };
213 static void
214 sp_text_context_setup(SPEventContext *ec)
216     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
217     SPDesktop *desktop = ec->desktop;
219     tc->cursor = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLLINE, NULL);
220     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), 100, 0, 100, 100);
221     sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
222     sp_canvas_item_hide(tc->cursor);
224     tc->indicator = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
225     SP_CTRLRECT(tc->indicator)->setRectangle(NR::Rect(NR::Point(0, 0), NR::Point(100, 100)));
226     SP_CTRLRECT(tc->indicator)->setColor(0x0000ff7f, false, 0);
227     sp_canvas_item_hide(tc->indicator);
229     tc->frame = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
230     SP_CTRLRECT(tc->frame)->setRectangle(NR::Rect(NR::Point(0, 0), NR::Point(100, 100)));
231     SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0);
232     sp_canvas_item_hide(tc->frame);
234     tc->timeout = gtk_timeout_add(250, (GtkFunction) sp_text_context_timeout, ec);
236     tc->imc = gtk_im_multicontext_new();
237     if (tc->imc) {
238         GtkWidget *canvas = GTK_WIDGET(sp_desktop_canvas(desktop));
240         /* im preedit handling is very broken in inkscape for
241          * multi-byte characters.  See bug 1086769.
242          * We need to let the IM handle the preediting, and
243          * just take in the characters when they're finished being
244          * entered.
245          */
246         gtk_im_context_set_use_preedit(tc->imc, FALSE);
247         gtk_im_context_set_client_window(tc->imc, canvas->window);
249         g_signal_connect(G_OBJECT(canvas), "focus_in_event", G_CALLBACK(sptc_focus_in), tc);
250         g_signal_connect(G_OBJECT(canvas), "focus_out_event", G_CALLBACK(sptc_focus_out), tc);
251         g_signal_connect(G_OBJECT(tc->imc), "commit", G_CALLBACK(sptc_commit), tc);
253         if (GTK_WIDGET_HAS_FOCUS(canvas)) {
254             sptc_focus_in(canvas, NULL, tc);
255         }
256     }
258     if (((SPEventContextClass *) parent_class)->setup)
259         ((SPEventContextClass *) parent_class)->setup(ec);
261     SPItem *item = sp_desktop_selection(ec->desktop)->singleItem();
262     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
263         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
264         Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
265         if (shape_repr) {
266             ec->shape_repr = shape_repr;
267             Inkscape::GC::anchor(shape_repr);
268             sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
269         }
270     }
272     tc->sel_changed_connection = sp_desktop_selection(desktop)->connectChanged(
273         sigc::bind(sigc::ptr_fun(&sp_text_context_selection_changed), tc)
274         );
275     tc->sel_modified_connection = sp_desktop_selection(desktop)->connectModified(
276         sigc::bind(sigc::ptr_fun(&sp_text_context_selection_modified), tc)
277         );
278     tc->style_set_connection = desktop->connectSetStyle(
279         sigc::bind(sigc::ptr_fun(&sp_text_context_style_set), tc)
280         );
281     tc->style_query_connection = desktop->connectQueryStyle(
282         sigc::bind(sigc::ptr_fun(&sp_text_context_style_query), tc)
283         );
285     sp_text_context_selection_changed(sp_desktop_selection(desktop), tc);
287     if (prefs_get_int_attribute("tools.text", "selcue", 0) != 0) {
288         ec->enableSelectionCue();
289     }
290     if (prefs_get_int_attribute("tools.text", "gradientdrag", 0) != 0) {
291         ec->enableGrDrag();
292     }
295 static void
296 sp_text_context_finish(SPEventContext *ec)
298     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
300     ec->enableGrDrag(false);
302     tc->style_set_connection.disconnect();
303     tc->style_query_connection.disconnect();
304     tc->sel_changed_connection.disconnect();
305     tc->sel_modified_connection.disconnect();
307     sp_text_context_forget_text(SP_TEXT_CONTEXT(ec));
309     if (tc->imc) {
310         g_object_unref(G_OBJECT(tc->imc));
311         tc->imc = NULL;
312     }
314     if (tc->timeout) {
315         gtk_timeout_remove(tc->timeout);
316         tc->timeout = 0;
317     }
319     if (tc->cursor) {
320         gtk_object_destroy(GTK_OBJECT(tc->cursor));
321         tc->cursor = NULL;
322     }
324     if (tc->indicator) {
325         gtk_object_destroy(GTK_OBJECT(tc->indicator));
326         tc->indicator = NULL;
327     }
329     if (tc->frame) {
330         gtk_object_destroy(GTK_OBJECT(tc->frame));
331         tc->frame = NULL;
332     }
334     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ;
335          it != tc->text_selection_quads.end() ; ++it) {
336         sp_canvas_item_hide(*it);
337         gtk_object_destroy(*it);
338     }
339     tc->text_selection_quads.clear();
341     if (ec->desktop) {
342         sp_signal_disconnect_by_data(sp_desktop_canvas(ec->desktop), tc);
343     }
347 static gint
348 sp_text_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event)
350     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
351     SPDesktop *desktop = ec->desktop;
352     SPItem *item_ungrouped;
354     gint ret = FALSE;
356     sp_text_context_validate_cursor_iterators(tc);
358     switch (event->type) {
359         case GDK_BUTTON_PRESS:
360             if (event->button.button == 1) {
361                 // find out clicked item, disregarding groups
362                 item_ungrouped = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE);
363                 if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
364                     sp_desktop_selection(ec->desktop)->set(item_ungrouped);
365                     if (tc->text) {
366                         // find out click point in document coordinates
367                         NR::Point p = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
368                         // set the cursor closest to that point
369                         tc->text_sel_start = tc->text_sel_end = sp_te_get_position_by_coords(tc->text, p);
370                         // update display
371                         sp_text_context_update_cursor(tc);
372                         sp_text_context_update_text_selection(tc);
373                         tc->dragging = 1;
374                     }
375                     ret = TRUE;
376                 }
377             }
378             break;
379         case GDK_2BUTTON_PRESS:
380             if (event->button.button == 1 && tc->text) {
381                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
382                 if (layout) {
383                     if (!layout->isStartOfWord(tc->text_sel_start))
384                         tc->text_sel_start.prevStartOfWord();
385                     if (!layout->isEndOfWord(tc->text_sel_end))
386                         tc->text_sel_end.nextEndOfWord();
387                     sp_text_context_update_cursor(tc);
388                     sp_text_context_update_text_selection(tc);
389                     tc->dragging = 2;
390                     ret = TRUE;
391                 }
392             }
393             break;
394         case GDK_3BUTTON_PRESS:
395             if (event->button.button == 1 && tc->text) {
396                 tc->text_sel_start.thisStartOfLine();
397                 tc->text_sel_end.thisEndOfLine();
398                 sp_text_context_update_cursor(tc);
399                 sp_text_context_update_text_selection(tc);
400                 tc->dragging = 3;
401                 ret = TRUE;
402             }
403             break;
404         case GDK_BUTTON_RELEASE:
405             if (event->button.button == 1 && tc->dragging) {
406                 tc->dragging = 0;
407                 ret = TRUE;
408             }
409             break;
410         case GDK_MOTION_NOTIFY:
411             if (event->motion.state & GDK_BUTTON1_MASK && tc->dragging) {
412                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
413                 if (!layout) break;
414                 // find out click point in document coordinates
415                 NR::Point p = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
416                 // set the cursor closest to that point
417                 Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(tc->text, p);
418                 if (tc->dragging == 2) {
419                     // double-click dragging: go by word
420                     if (new_end < tc->text_sel_start) {
421                         if (!layout->isStartOfWord(new_end))
422                             new_end.prevStartOfWord();
423                     } else
424                         if (!layout->isEndOfWord(new_end))
425                             new_end.nextEndOfWord();
426                 } else if (tc->dragging == 3) {
427                     // triple-click dragging: go by line
428                     if (new_end < tc->text_sel_start)
429                         new_end.thisStartOfLine();
430                     else
431                         new_end.thisEndOfLine();
432                 }
433                 // update display
434                 if (tc->text_sel_end != new_end) {
435                     tc->text_sel_end = new_end;
436                     sp_text_context_update_cursor(tc);
437                     sp_text_context_update_text_selection(tc);
438                 }
439                 ret = TRUE;
440                 break;
441             }
442             // find out item under mouse, disregarding groups
443             item_ungrouped = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE);
444             if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
445                 sp_canvas_item_show(tc->indicator);
446                 SP_CTRLRECT(tc->indicator)->setRectangle(sp_item_bbox_desktop(item_ungrouped));
448                 ec->cursor_shape = cursor_text_insert_xpm;
449                 ec->cursor_pixbuf = gdk_pixbuf_new_from_inline(
450                         -1,
451                         cursor_text_insert_pixbuf,
452                         FALSE,
453                         NULL);  
454                 ec->hot_x = 7;
455                 ec->hot_y = 10;
456                 sp_event_context_update_cursor(ec);
457                 sp_text_context_update_text_selection(tc);
459                 if (SP_IS_TEXT (item_ungrouped)) {
460                     desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
461                 } else {
462                     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."));
463                 }
465                 tc->over_text = true;
467                 ret = TRUE;
468             }
469             break;
470         default:
471             break;
472     }
474     if (!ret) {
475         if (((SPEventContextClass *) parent_class)->item_handler)
476             ret = ((SPEventContextClass *) parent_class)->item_handler(ec, item, event);
477     }
479     return ret;
482 static void
483 sp_text_context_setup_text(SPTextContext *tc)
485     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
487     /* Create <text> */
488     Inkscape::XML::Node *rtext = sp_repr_new("svg:text");
489     rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
491     /* Set style */
492     sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "tools.text", true);
494     sp_repr_set_svg_double(rtext, "x", tc->pdoc[NR::X]);
495     sp_repr_set_svg_double(rtext, "y", tc->pdoc[NR::Y]);
497     /* Create <tspan> */
498     Inkscape::XML::Node *rtspan = sp_repr_new("svg:tspan");
499     rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
500     rtext->addChild(rtspan, NULL);
501     Inkscape::GC::release(rtspan);
503     /* Create TEXT */
504     Inkscape::XML::Node *rstring = sp_repr_new_text("");
505     rtspan->addChild(rstring, NULL);
506     Inkscape::GC::release(rstring);
507     SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
508     /* fixme: Is selection::changed really immediate? */
509     /* yes, it's immediate .. why does it matter? */
510     sp_desktop_selection(ec->desktop)->set(text_item);
511     Inkscape::GC::release(rtext);
512     text_item->transform = SP_ITEM(ec->desktop->currentRoot())->getRelativeTransform(ec->desktop->currentLayer());
513     text_item->updateRepr();
514     sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, 
515                      /* TODO: annotate */ "text-context.cpp:515");
518 /**
519  * Insert the character indicated by tc.uni to replace the current selection,
520  * and reset tc.uni/tc.unipos to empty string.
521  *
522  * \pre tc.uni/tc.unipos non-empty.
523  */
524 static void
525 insert_uni_char(SPTextContext *const tc)
527     g_return_if_fail(tc->unipos
528                      && tc->unipos < sizeof(tc->uni)
529                      && tc->uni[tc->unipos] == '\0');
530     unsigned int uv;
531     sscanf(tc->uni, "%x", &uv);
532     tc->unipos = 0;
533     tc->uni[tc->unipos] = '\0';
535     if ( !g_unichar_isprint(static_cast<gunichar>(uv))
536          && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
537         // This may be due to bad input, so it goes to statusbar.
538         tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
539                                            _("Non-printable character"));
540     } else {
541         if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
542             sp_text_context_setup_text(tc);
543             tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
544         }
546         gchar u[10];
547         guint const len = g_unichar_to_utf8(uv, u);
548         u[len] = '\0';
550         tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
551         sp_text_context_update_cursor(tc);
552         sp_text_context_update_text_selection(tc);
553         sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM, 
554                          /* TODO: annotate */ "text-context.cpp:554");
555     }
558 static void
559 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
561     unsigned int uv;
562     sscanf(hex, "%x", &uv);
563     if (!g_unichar_isprint((gunichar) uv)) {
564         uv = 0xfffd;
565     }
566     guint const len = g_unichar_to_utf8(uv, utf8);
567     utf8[len] = '\0';
570 static void
571 show_curr_uni_char(SPTextContext *const tc)
573     g_return_if_fail(tc->unipos < sizeof(tc->uni)
574                      && tc->uni[tc->unipos] == '\0');
575     if (tc->unipos) {
576         char utf8[10];
577         hex_to_printable_utf8_buf(tc->uni, utf8);
579         /* Status bar messages are in pango markup, so we need xml escaping. */
580         if (utf8[1] == '\0') {
581             switch(utf8[0]) {
582                 case '<': strcpy(utf8, "&lt;"); break;
583                 case '>': strcpy(utf8, "&gt;"); break;
584                 case '&': strcpy(utf8, "&amp;"); break;
585                 default: break;
586             }
587         }
588         tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
589                                           _("Unicode: %s: %s"), tc->uni, utf8);
590     } else {
591         tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode: "));
592     }
595 static gint
596 sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
598     SPTextContext *const tc = SP_TEXT_CONTEXT(ec);
600     SPDesktop *desktop = ec->desktop;
602     sp_canvas_item_hide(tc->indicator);
604     sp_text_context_validate_cursor_iterators(tc);
606     ec->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100);
608     switch (event->type) {
609         case GDK_BUTTON_PRESS:
610             if (event->button.button == 1) {
612                 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(ec);
614                 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
615                     return TRUE;
616                 }
618                 // save drag origin
619                 ec->xp = (gint) event->button.x;
620                 ec->yp = (gint) event->button.y;
621                 ec->within_tolerance = true;
623                 NR::Point const button_pt(event->button.x, event->button.y);
624                 tc->p0 = desktop->w2d(button_pt);
625                 Inkscape::Rubberband::get()->start(desktop, tc->p0);
626                 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
627                                     GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
628                                         GDK_POINTER_MOTION_MASK,
629                                     NULL, event->button.time);
630                 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
631                 tc->creating = 1;
633                 /* Processed */
634                 return TRUE;
635             }
636             break;
637         case GDK_MOTION_NOTIFY:
638             if (tc->over_text) {
639                 tc->over_text = 0;
640                 // update cursor and statusbar: we are not over a text object now
641                 ec->cursor_shape = cursor_text_xpm;
642                 ec->cursor_pixbuf = gdk_pixbuf_new_from_inline(
643                         -1,
644                         cursor_text_pixbuf,
645                         FALSE,
646                         NULL);  
647                 ec->hot_x = 7;
648                 ec->hot_y = 7;
649                 sp_event_context_update_cursor(ec);
650                 desktop->event_context->defaultMessageContext()->clear();
651             }
653             if (tc->creating && event->motion.state & GDK_BUTTON1_MASK) {
654                 if ( ec->within_tolerance
655                      && ( abs( (gint) event->motion.x - ec->xp ) < ec->tolerance )
656                      && ( abs( (gint) event->motion.y - ec->yp ) < ec->tolerance ) ) {
657                     break; // do not drag if we're within tolerance from origin
658                 }
659                 // Once the user has moved farther than tolerance from the original location
660                 // (indicating they intend to draw, not click), then always process the
661                 // motion notify coordinates as given (no snapping back to origin)
662                 ec->within_tolerance = false;
664                 NR::Point const motion_pt(event->motion.x, event->motion.y);
665                 NR::Point const p = desktop->w2d(motion_pt);
667                 Inkscape::Rubberband::get()->move(p);
668                 gobble_motion_events(GDK_BUTTON1_MASK);
670                 // status text
671                 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::X]), desktop->namedview->getDefaultMetric());
672                 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::Y]), desktop->namedview->getDefaultMetric());
673                 ec->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("<b>Flowed text frame</b>: %s &#215; %s"), xs->str, ys->str);
674                 g_string_free(xs, FALSE);
675                 g_string_free(ys, FALSE);
677             }
678             break;
679         case GDK_BUTTON_RELEASE:
680             if (event->button.button == 1) {
682                 if (tc->grabbed) {
683                     sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
684                     tc->grabbed = NULL;
685                 }
687                 Inkscape::Rubberband::get()->stop();
689                 if (tc->creating && ec->within_tolerance) {
690                     /* Button 1, set X & Y & new item */
691                     sp_desktop_selection(desktop)->clear();
692                     NR::Point dtp = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
693                     tc->pdoc = sp_desktop_dt2root_xy_point(ec->desktop, dtp);
695                     tc->show = TRUE;
696                     tc->phase = 1;
697                     tc->nascent_object = 1; // new object was just created
699                     /* Cursor */
700                     sp_canvas_item_show(tc->cursor);
701                     // Cursor height is defined by the new text object's font size; it needs to be set
702                     // articifically here, for the text object does not exist yet:
703                     double cursor_height = sp_desktop_get_font_size_tool(ec->desktop);
704                     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), dtp, dtp + NR::Point(0, cursor_height));
705                     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
707                     ec->within_tolerance = false;
708                 } else if (tc->creating) {
709                     NR::Point const button_pt(event->button.x, event->button.y);
710                     NR::Point p1 = desktop->w2d(button_pt);
711                     double cursor_height = sp_desktop_get_font_size_tool(ec->desktop);
712                     if (fabs(p1[NR::Y] - tc->p0[NR::Y]) > cursor_height) {
713                         // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
714                         SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
715                         sp_desktop_selection(desktop)->set(ft);
716                         ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
717                         sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, 
718                                          /* TODO: annotate */ "text-context.cpp:718");
719                     } else {
720                         ec->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
721                     }
722                 }
723                 tc->creating = false;
724                 return TRUE;
725             }
726             break;
727         case GDK_KEY_PRESS: {
728             guint const group0_keyval = get_group0_keyval(&event->key);
730             if (group0_keyval == GDK_KP_Add ||
731                 group0_keyval == GDK_KP_Subtract) {
732                 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
733                     break; // otherwise pass on keypad +/- so they can zoom
734             }
736             if ((tc->text) || (tc->nascent_object)) {
737                 // there is an active text object in this context, or a new object was just created
739                 if (tc->unimode || !tc->imc
740                     || (MOD__CTRL && MOD__SHIFT)    // input methods tend to steal this for unimode,
741                                                     // but we have our own so make sure they don't swallow it
742                     || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
743                     //IM did not consume the key, or we're in unimode
745                         if (!MOD__CTRL_ONLY && tc->unimode) {
746                             /* TODO: ISO 14755 (section 3 Definitions) says that we should also
747                                accept the first 6 characters of alphabets other than the latin
748                                alphabet "if the Latin alphabet is not used".  The below is also
749                                reasonable (viz. hope that the user's keyboard includes latin
750                                characters and force latin interpretation -- just as we do for our
751                                keyboard shortcuts), but differs from the ISO 14755
752                                recommendation. */
753                             switch (group0_keyval) {
754                                 case GDK_space:
755                                 case GDK_KP_Space: {
756                                     if (tc->unipos) {
757                                         insert_uni_char(tc);
758                                     }
759                                     /* Stay in unimode. */
760                                     show_curr_uni_char(tc);
761                                     return TRUE;
762                                 }
764                                 case GDK_BackSpace: {
765                                     g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
766                                     if (tc->unipos) {
767                                         tc->uni[--tc->unipos] = '\0';
768                                     }
769                                     show_curr_uni_char(tc);
770                                     return TRUE;
771                                 }
773                                 case GDK_Return:
774                                 case GDK_KP_Enter: {
775                                     if (tc->unipos) {
776                                         insert_uni_char(tc);
777                                     }
778                                     /* Exit unimode. */
779                                     tc->unimode = false;
780                                     ec->defaultMessageContext()->clear();
781                                     return TRUE;
782                                 }
784                                 case GDK_Escape: {
785                                     // Cancel unimode.
786                                     tc->unimode = false;
787                                     gtk_im_context_reset(tc->imc);
788                                     ec->defaultMessageContext()->clear();
789                                     return TRUE;
790                                 }
792                                 case GDK_Shift_L:
793                                 case GDK_Shift_R:
794                                     break;
796                                 default: {
797                                     if (g_ascii_isxdigit(group0_keyval)) {
798                                         g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
799                                         tc->uni[tc->unipos++] = group0_keyval;
800                                         tc->uni[tc->unipos] = '\0';
801                                         if (tc->unipos == 8) {
802                                             /* This behaviour is partly to allow us to continue to
803                                                use a fixed-length buffer for tc->uni.  Reason for
804                                                choosing the number 8 is that it's the length of
805                                                ``canonical form'' mentioned in the ISO 14755 spec.
806                                                An advantage over choosing 6 is that it allows using
807                                                backspace for typos & misremembering when entering a
808                                                6-digit number. */
809                                             insert_uni_char(tc);
810                                         }
811                                         show_curr_uni_char(tc);
812                                         return TRUE;
813                                     } else {
814                                         /* The intent is to ignore but consume characters that could be
815                                            typos for hex digits.  Gtk seems to ignore & consume all
816                                            non-hex-digits, and we do similar here.  Though note that some
817                                            shortcuts (like keypad +/- for zoom) get processed before
818                                            reaching this code. */
819                                         return TRUE;
820                                     }
821                                 }
822                             }
823                         }
825                         bool (Inkscape::Text::Layout::iterator::*cursor_movement_operator)() = NULL;
827                         /* Neither unimode nor IM consumed key; process text tool shortcuts */
828                         switch (group0_keyval) {
829                             case GDK_x:
830                             case GDK_X:
831                                 if (MOD__ALT_ONLY) {
832                                     desktop->setToolboxFocusTo ("altx-text");
833                                     return TRUE;
834                                 }
835                                 break;
836                             case GDK_space:
837                                 if (MOD__CTRL_ONLY) {
838                                     /* No-break space */
839                                     if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
840                                         sp_text_context_setup_text(tc);
841                                         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
842                                     }
843                                     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
844                                     sp_text_context_update_cursor(tc);
845                                     sp_text_context_update_text_selection(tc);
846                                     ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
847                                     sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, 
848                                                      /* TODO: annotate */ "text-context.cpp:848");
849                                     return TRUE;
850                                 }
851                                 break;
852                             case GDK_U:
853                             case GDK_u:
854                                 if (MOD__CTRL_ONLY) {
855                                     if (tc->unimode) {
856                                         tc->unimode = false;
857                                         ec->defaultMessageContext()->clear();
858                                     } else {
859                                         tc->unimode = true;
860                                         tc->unipos = 0;
861                                         ec->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode: "));
862                                     }
863                                     if (tc->imc) {
864                                         gtk_im_context_reset(tc->imc);
865                                     }
866                                     return TRUE;
867                                 }
868                                 break;
869                             case GDK_B:
870                             case GDK_b:
871                                 if (MOD__CTRL_ONLY && tc->text) {
872                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
873                                     SPCSSAttr *css = sp_repr_css_attr_new();
874                                     if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
875                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
876                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
877                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
878                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
879                                         sp_repr_css_set_property(css, "font-weight", "bold");
880                                     else
881                                         sp_repr_css_set_property(css, "font-weight", "normal");
882                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
883                                     sp_repr_css_attr_unref(css);
884                                     sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, 
885                                                      /* TODO: annotate */ "text-context.cpp:885");
886                                     sp_text_context_update_cursor(tc);
887                                     sp_text_context_update_text_selection(tc);
888                                     return TRUE;
889                                 }
890                                 break;
891                             case GDK_I:
892                             case GDK_i:
893                                 if (MOD__CTRL_ONLY && tc->text) {
894                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
895                                     SPCSSAttr *css = sp_repr_css_attr_new();
896                                     if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL)
897                                         sp_repr_css_set_property(css, "font-style", "italic");
898                                     else
899                                         sp_repr_css_set_property(css, "font-style", "normal");
900                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
901                                     sp_repr_css_attr_unref(css);
902                                     sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, 
903                                                      /* TODO: annotate */ "text-context.cpp:903");
904                                     sp_text_context_update_cursor(tc);
905                                     sp_text_context_update_text_selection(tc);
906                                     return TRUE;
907                                 }
908                                 break;
910                             case GDK_A:
911                             case GDK_a:
912                                 if (MOD__CTRL_ONLY && tc->text) {
913                                     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
914                                     if (layout) {
915                                         tc->text_sel_start = layout->begin();
916                                         tc->text_sel_end = layout->end();
917                                         sp_text_context_update_cursor(tc);
918                                         sp_text_context_update_text_selection(tc);
919                                         return TRUE;
920                                     }
921                                 }
922                                 break;
924                             case GDK_Return:
925                             case GDK_KP_Enter:
926                                 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
927                                     sp_text_context_setup_text(tc);
928                                     tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
929                                 }
930                                 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
931                                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
932                                 sp_text_context_update_cursor(tc);
933                                 sp_text_context_update_text_selection(tc);
934                                 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, 
935                                                  /* TODO: annotate */ "text-context.cpp:935");
936                                 return TRUE;
937                             case GDK_BackSpace:
938                                 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
939                                     if (tc->text_sel_start == tc->text_sel_end)
940                                         tc->text_sel_start.prevCursorPosition();
941                                     tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
942                                     sp_text_context_update_cursor(tc);
943                                     sp_text_context_update_text_selection(tc);
944                                     sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, 
945                                                      /* TODO: annotate */ "text-context.cpp:945");
946                                 }
947                                 return TRUE;
948                             case GDK_Delete:
949                             case GDK_KP_Delete:
950                                 if (tc->text) {
951                                     if (tc->text_sel_start == tc->text_sel_end)
952                                         tc->text_sel_end.nextCursorPosition();
953                                     tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
954                                     sp_text_context_update_cursor(tc);
955                                     sp_text_context_update_text_selection(tc);
956                                     sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, 
957                                                      /* TODO: annotate */ "text-context.cpp:957");
958                                 }
959                                 return TRUE;
960                             case GDK_Left:
961                             case GDK_KP_Left:
962                             case GDK_KP_4:
963                                 if (tc->text) {
964                                     if (MOD__ALT) {
965                                         if (MOD__SHIFT)
966                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-10, 0));
967                                         else
968                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-1, 0));
969                                         sp_text_context_update_cursor(tc);
970                                         sp_text_context_update_text_selection(tc);
971                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:left", SP_VERB_CONTEXT_TEXT, 
972                                                                /* TODO: annotate */ "text-context.cpp:972");
973                                     } else {
974                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorLeftWithControl
975                                                                              : &Inkscape::Text::Layout::iterator::cursorLeft;
976                                         break;
977                                     }
978                                 }
979                                 return TRUE;
980                             case GDK_Right:
981                             case GDK_KP_Right:
982                             case GDK_KP_6:
983                                 if (tc->text) {
984                                     if (MOD__ALT) {
985                                         if (MOD__SHIFT)
986                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(10, 0));
987                                         else
988                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(1, 0));
989                                         sp_text_context_update_cursor(tc);
990                                         sp_text_context_update_text_selection(tc);
991                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:right", SP_VERB_CONTEXT_TEXT, 
992                                                                /* TODO: annotate */ "text-context.cpp:992");
993                                     } else {
994                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorRightWithControl
995                                                                              : &Inkscape::Text::Layout::iterator::cursorRight;
996                                         break;
997                                     }
998                                 }
999                                 return TRUE;
1000                             case GDK_Up:
1001                             case GDK_KP_Up:
1002                             case GDK_KP_8:
1003                                 if (tc->text) {
1004                                     if (MOD__ALT) {
1005                                         if (MOD__SHIFT)
1006                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -10));
1007                                         else
1008                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -1));
1009                                         sp_text_context_update_cursor(tc);
1010                                         sp_text_context_update_text_selection(tc);
1011                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:up", SP_VERB_CONTEXT_TEXT, 
1012                                                                /* TODO: annotate */ "text-context.cpp:1012");
1014                                     } else {
1015                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorUpWithControl
1016                                                                              : &Inkscape::Text::Layout::iterator::cursorUp;
1017                                         break;
1018                                     }
1019                                 }
1020                                 return TRUE;
1021                             case GDK_Down:
1022                             case GDK_KP_Down:
1023                             case GDK_KP_2:
1024                                 if (tc->text) {
1025                                     if (MOD__ALT) {
1026                                         if (MOD__SHIFT)
1027                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 10));
1028                                         else
1029                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 1));
1030                                         sp_text_context_update_cursor(tc);
1031                                         sp_text_context_update_text_selection(tc);
1032                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:down", SP_VERB_CONTEXT_TEXT, 
1033                                                                /* TODO: annotate */ "text-context.cpp:1033");
1035                                     } else {
1036                                         cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorDownWithControl
1037                                                                              : &Inkscape::Text::Layout::iterator::cursorDown;
1038                                         break;
1039                                     }
1040                                 }
1041                                 return TRUE;
1042                             case GDK_Home:
1043                             case GDK_KP_Home:
1044                                 if (tc->text) {
1045                                     if (MOD__CTRL)
1046                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfShape;
1047                                     else
1048                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfLine;
1049                                     break;
1050                                 }
1051                                 return TRUE;
1052                             case GDK_End:
1053                             case GDK_KP_End:
1054                                 if (tc->text) {
1055                                     if (MOD__CTRL)
1056                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::nextStartOfShape;
1057                                     else
1058                                         cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisEndOfLine;
1059                                     break;
1060                                 }
1061                                 return TRUE;
1062                             case GDK_Escape:
1063                                 if (tc->creating) {
1064                                     tc->creating = 0;
1065                                     if (tc->grabbed) {
1066                                         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1067                                         tc->grabbed = NULL;
1068                                     }
1069                                     Inkscape::Rubberband::get()->stop();
1070                                 } else {
1071                                     sp_desktop_selection(ec->desktop)->clear();
1072                                 }
1073                                 tc->nascent_object = FALSE;
1074                                 return TRUE;
1075                             case GDK_bracketleft:
1076                                 if (tc->text) {
1077                                     if (MOD__ALT || MOD__CTRL) {
1078                                         if (MOD__ALT) {
1079                                             if (MOD__SHIFT) {
1080                                                 // FIXME: alt+shift+[] does not work, don't know why
1081                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1082                                             } else {
1083                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1084                                             }
1085                                         } else {
1086                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -90);
1087                                         }
1088                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT, 
1089                                                                /* TODO: annotate */ "text-context.cpp:1089");
1090                                         sp_text_context_update_cursor(tc);
1091                                         sp_text_context_update_text_selection(tc);
1092                                         return TRUE;
1093                                     }
1094                                 }
1095                                 break;
1096                             case GDK_bracketright:
1097                                 if (tc->text) {
1098                                     if (MOD__ALT || MOD__CTRL) {
1099                                         if (MOD__ALT) {
1100                                             if (MOD__SHIFT) {
1101                                                 // FIXME: alt+shift+[] does not work, don't know why
1102                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1103                                             } else {
1104                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1105                                             }
1106                                         } else {
1107                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 90);
1108                                         }
1109                                         sp_document_maybe_done(sp_desktop_document(ec->desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT, 
1110                                                                /* TODO: annotate */ "text-context.cpp:1110");
1111                                         sp_text_context_update_cursor(tc);
1112                                         sp_text_context_update_text_selection(tc);
1113                                         return TRUE;
1114                                     }
1115                                 }
1116                                 break;
1117                             case GDK_less:
1118                             case GDK_comma:
1119                                 if (tc->text) {
1120                                     if (MOD__ALT) {
1121                                         if (MOD__CTRL) {
1122                                             if (MOD__SHIFT)
1123                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1124                                             else
1125                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1126                                             sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT, 
1127                                                                    /* TODO: annotate */ "text-context.cpp:1127");
1129                                         } else {
1130                                             if (MOD__SHIFT)
1131                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1132                                             else
1133                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1134                                             sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT, 
1135                                                                    /* TODO: annotate */ "text-context.cpp:1135");
1137                                         }
1138                                         sp_text_context_update_cursor(tc);
1139                                         sp_text_context_update_text_selection(tc);
1140                                         return TRUE;
1141                                     }
1142                                 }
1143                                 break;
1144                             case GDK_greater:
1145                             case GDK_period:
1146                                 if (tc->text) {
1147                                     if (MOD__ALT) {
1148                                         if (MOD__CTRL) {
1149                                             if (MOD__SHIFT)
1150                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1151                                             else
1152                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1153                                             sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT, 
1154                                                                    /* TODO: annotate */ "text-context.cpp:1154");
1156                                         } else {
1157                                             if (MOD__SHIFT)
1158                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1159                                             else
1160                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1161                                             sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT, 
1162                                                                    /* TODO: annotate */ "text-context.cpp:1162");
1164                                         }
1165                                         sp_text_context_update_cursor(tc);
1166                                         sp_text_context_update_text_selection(tc);
1167                                         return TRUE;
1168                                     }
1169                                 }
1170                                 break;
1171                             default:
1172                                 break;
1173                         }
1175                         if (cursor_movement_operator) {
1176                             Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
1177                             Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
1178                             (tc->text_sel_end.*cursor_movement_operator)();
1179                             if (!MOD__SHIFT)
1180                                 tc->text_sel_start = tc->text_sel_end;
1181                             if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1182                                 sp_text_context_update_cursor(tc);
1183                                 sp_text_context_update_text_selection(tc);
1184                             }
1185                             return TRUE;
1186                         }
1188                 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1189             } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1190                 // except up/down that are swallowed to prevent the zoom field from activation
1191                 if ((group0_keyval == GDK_Up    ||
1192                      group0_keyval == GDK_Down  ||
1193                      group0_keyval == GDK_KP_Up ||
1194                      group0_keyval == GDK_KP_Down )
1195                     && !MOD__CTRL_ONLY) {
1196                     return TRUE;
1197                 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1198                     if (tc->creating) {
1199                         tc->creating = 0;
1200                         if (tc->grabbed) {
1201                             sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1202                             tc->grabbed = NULL;
1203                         }
1204                         Inkscape::Rubberband::get()->stop();
1205                     }
1206                 }
1207             }
1208             break;
1209         }
1211         case GDK_KEY_RELEASE:
1212             if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1213                 return TRUE;
1214             }
1215             break;
1216         default:
1217             break;
1218     }
1220     // if nobody consumed it so far
1221     if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1222         return ((SPEventContextClass *) parent_class)->root_handler(ec, event); // send event to parent
1223     } else {
1224         return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1225     }
1228 /**
1229  Attempts to paste system clipboard into the currently edited text, returns true on success
1230  */
1231 bool
1232 sp_text_paste_inline(SPEventContext *ec)
1234     if (!SP_IS_TEXT_CONTEXT(ec))
1235         return false;
1237     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1239     if ((tc->text) || (tc->nascent_object)) {
1240         // there is an active text object in this context, or a new object was just created
1242         Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1243         Glib::ustring const text = refClipboard->wait_for_text();
1245         if (!text.empty()) {
1247             if (!tc->text) { // create text if none (i.e. if nascent_object)
1248                 sp_text_context_setup_text(tc);
1249                 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1250             }
1252             // using indices is slow in ustrings. Whatever.
1253             Glib::ustring::size_type begin = 0;
1254             for ( ; ; ) {
1255                 Glib::ustring::size_type end = text.find('\n', begin);
1256                 if (end == Glib::ustring::npos) {
1257                     if (begin != text.length())
1258                         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());
1259                     break;
1260                 }
1261                 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());
1262                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1263                 begin = end + 1;
1264             }
1265             sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, 
1266                              /* TODO: annotate */ "text-context.cpp:1266");
1268             return true;
1269         }
1270     } // FIXME: else create and select a new object under cursor!
1272     return false;
1275 /**
1276  Gets the raw characters that comprise the currently selected text, converting line
1277  breaks into lf characters.
1278 */
1279 Glib::ustring
1280 sp_text_get_selected_text(SPEventContext const *ec)
1282     if (!SP_IS_TEXT_CONTEXT(ec))
1283         return "";
1284     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1285     if (tc->text == NULL)
1286         return "";
1288     return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1291 /**
1292  Deletes the currently selected characters. Returns false if there is no
1293  text selection currently.
1294 */
1295 bool sp_text_delete_selection(SPEventContext *ec)
1297     if (!SP_IS_TEXT_CONTEXT(ec))
1298         return false;
1299     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1300     if (tc->text == NULL)
1301         return false;
1303     if (tc->text_sel_start == tc->text_sel_end)
1304         return false;
1305     tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
1306     sp_text_context_update_cursor(tc);
1307     sp_text_context_update_text_selection(tc);
1308     return true;
1311 /**
1312  * \param selection Should not be NULL.
1313  */
1314 static void
1315 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1317     g_assert(selection != NULL);
1319     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1321     if (ec->shape_knot_holder) { // destroy knotholder
1322         sp_knot_holder_destroy(ec->shape_knot_holder);
1323         ec->shape_knot_holder = NULL;
1324     }
1326     if (ec->shape_repr) { // remove old listener
1327         sp_repr_remove_listener_by_data(ec->shape_repr, ec);
1328         Inkscape::GC::release(ec->shape_repr);
1329         ec->shape_repr = 0;
1330     }
1332     SPItem *item = selection->singleItem();
1333     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1334         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
1335         Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
1336         if (shape_repr) {
1337             ec->shape_repr = shape_repr;
1338             Inkscape::GC::anchor(shape_repr);
1339             sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
1340         }
1341     }
1343     if (tc->text && (item != tc->text)) {
1344         sp_text_context_forget_text(tc);
1345     }
1346     tc->text = NULL;
1348     if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1349         tc->text = item;
1350         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1351         if (layout)
1352             tc->text_sel_start = tc->text_sel_end = layout->end();
1353     } else {
1354         tc->text = NULL;
1355     }
1357     // we update cursor without scrolling, because this position may not be final;
1358     // item_handler moves cusros to the point of click immediately
1359     sp_text_context_update_cursor(tc, false);
1360     sp_text_context_update_text_selection(tc);
1363 static void
1364 sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc)
1366     sp_text_context_update_cursor(tc);
1367     sp_text_context_update_text_selection(tc);
1370 static bool
1371 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1373     if (tc->text == NULL)
1374         return false;
1375     if (tc->text_sel_start == tc->text_sel_end)
1376         return false;    // will get picked up by the parent and applied to the whole text object
1378     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1379     sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT, 
1380                      /* TODO: annotate */ "text-context.cpp:1380");
1381     sp_text_context_update_cursor(tc);
1382     sp_text_context_update_text_selection(tc);
1384     return true;
1387 static int
1388 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1390     if (tc->text == NULL)
1391         return QUERY_STYLE_NOTHING;
1392     const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1393     if (layout == NULL)
1394         return QUERY_STYLE_NOTHING;
1395     sp_text_context_validate_cursor_iterators(tc);
1397     GSList *styles_list = NULL;
1399     Inkscape::Text::Layout::iterator begin_it, end_it;
1400     if (tc->text_sel_start < tc->text_sel_end) {
1401         begin_it = tc->text_sel_start;
1402         end_it = tc->text_sel_end;
1403     } else {
1404         begin_it = tc->text_sel_end;
1405         end_it = tc->text_sel_start;
1406     }
1407     if (begin_it == end_it)
1408         if (!begin_it.prevCharacter())
1409             end_it.nextCharacter();
1410     for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1411         SPObject const *pos_obj = 0;
1412         void *rawptr = 0;
1413         layout->getSourceOfCharacter(it, &rawptr);
1414         pos_obj = SP_OBJECT(rawptr);
1415         if (pos_obj == 0) continue;
1416         while (SP_OBJECT_STYLE(pos_obj) == NULL && SP_OBJECT_PARENT(pos_obj))
1417             pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
1418         styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1419     }
1421     int result = sp_desktop_query_style_from_list (styles_list, style, property);
1423     g_slist_free(styles_list);
1424     return result;
1427 static void
1428 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1430     if (tc->text == NULL)
1431         return;
1432     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1433     if (layout) {     // undo can change the text length without us knowing it
1434         layout->validateIterator(&tc->text_sel_start);
1435         layout->validateIterator(&tc->text_sel_end);
1436     }
1439 static void
1440 sp_text_context_update_cursor(SPTextContext *tc,  bool scroll_to_see)
1442     GdkRectangle im_cursor = { 0, 0, 1, 1 };
1444     if (tc->text) {
1445         NR::Point p0, p1;
1446         sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1447         NR::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1448         NR::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1450         // scroll to show cursor
1451         if (scroll_to_see) {
1452             NR::Point const dm = (d0 + d1) / 2;
1453             // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1454             SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(&dm, 1.0);
1455         }
1457         sp_canvas_item_show(tc->cursor);
1458         sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1460         /* fixme: ... need another transformation to get canvas widget coordinate space? */
1461         im_cursor.x = (int) floor(d0[NR::X]);
1462         im_cursor.y = (int) floor(d0[NR::Y]);
1463         im_cursor.width = (int) floor(d1[NR::X]) - im_cursor.x;
1464         im_cursor.height = (int) floor(d1[NR::Y]) - im_cursor.y;
1466         tc->show = TRUE;
1467         tc->phase = 1;
1469         if (SP_IS_FLOWTEXT(tc->text)) {
1470             SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1471             if (frame) {
1472                 sp_canvas_item_show(tc->frame);
1473                 SP_CTRLRECT(tc->frame)->setRectangle(sp_item_bbox_desktop(frame));
1474             }
1475             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1476         } else {
1477             SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1478         }
1480     } else {
1481         sp_canvas_item_hide(tc->cursor);
1482         sp_canvas_item_hide(tc->frame);
1483         tc->show = FALSE;
1484         if (!tc->nascent_object) {
1485             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
1486         }
1487     }
1489     if (tc->imc) {
1490         gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1491     }
1492     SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1495 static void sp_text_context_update_text_selection(SPTextContext *tc)
1497     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1498         sp_canvas_item_hide(*it);
1499         gtk_object_destroy(*it);
1500     }
1501     tc->text_selection_quads.clear();
1503     std::vector<NR::Point> quads;
1504     if (tc->text != NULL)
1505         quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1506     for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1507         SPCanvasItem *quad_canvasitem;
1508         quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1509         sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x000000ff);
1510         sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1511         sp_canvas_item_show(quad_canvasitem);
1512         tc->text_selection_quads.push_back(quad_canvasitem);
1513     }
1516 static gint
1517 sp_text_context_timeout(SPTextContext *tc)
1519     if (tc->show) {
1520         if (tc->phase) {
1521             tc->phase = 0;
1522             sp_canvas_item_hide(tc->cursor);
1523         } else {
1524             tc->phase = 1;
1525             sp_canvas_item_show(tc->cursor);
1526         }
1527     }
1529     return TRUE;
1532 static void
1533 sp_text_context_forget_text(SPTextContext *tc)
1535     if (! tc->text) return;
1536     SPItem *ti = tc->text;
1537     /* We have to set it to zero,
1538      * or selection changed signal messes everything up */
1539     tc->text = NULL;
1540     if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1541         Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1542         // the repr may already have been unparented
1543         // if we were called e.g. as the result of
1544         // an undo or the element being removed from
1545         // the XML editor
1546         if ( text_repr && sp_repr_parent(text_repr) ) {
1547             sp_repr_unparent(text_repr);
1548         }
1549     }
1552 gint
1553 sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1555     gtk_im_context_focus_in(tc->imc);
1556     return FALSE;
1559 gint
1560 sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1562     gtk_im_context_focus_out(tc->imc);
1563     return FALSE;
1566 static void
1567 sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc)
1569     if (!tc->text) {
1570         sp_text_context_setup_text(tc);
1571         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1572     }
1574     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1575     sp_text_context_update_cursor(tc);
1576     sp_text_context_update_text_selection(tc);
1578     sp_document_done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT, 
1579                      /* TODO: annotate */ "text-context.cpp:1579");
1583 /*
1584   Local Variables:
1585   mode:c++
1586   c-file-style:"stroustrup"
1587   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1588   indent-tabs-mode:nil
1589   fill-column:99
1590   End:
1591 */
1592 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :