Code

Patch to fix build on Natty by Alex Valavanis
[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 "message-stack.h"
39 #include "message-context.h"
40 #include "pixmaps/cursor-text.xpm"
41 #include "pixmaps/cursor-text-insert.xpm"
42 #include <glibmm/i18n.h>
43 #include "object-edit.h"
44 #include "xml/repr.h"
45 #include "xml/node-event-vector.h"
46 #include "preferences.h"
47 #include "rubberband.h"
48 #include "sp-metrics.h"
49 #include "context-fns.h"
50 #include "verbs.h"
51 #include "shape-editor.h"
52 #include "selection-chemistry.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     tc->imc = NULL;
136     tc->text = NULL;
137     tc->pdoc = Geom::Point(0, 0);
138     new (&tc->text_sel_start) Inkscape::Text::Layout::iterator();
139     new (&tc->text_sel_end) Inkscape::Text::Layout::iterator();
140     new (&tc->text_selection_quads) std::vector<SPCanvasItem*>();
142     tc->unimode = false;
144     tc->cursor = NULL;
145     tc->indicator = NULL;
146     tc->frame = NULL;
147     tc->grabbed = NULL;
148     tc->timeout = 0;
149     tc->show = FALSE;
150     tc->phase = 0;
151     tc->nascent_object = 0;
152     tc->over_text = 0;
153     tc->dragging = 0;
154     tc->creating = 0;
156     new (&tc->sel_changed_connection) sigc::connection();
157     new (&tc->sel_modified_connection) sigc::connection();
158     new (&tc->style_set_connection) sigc::connection();
159     new (&tc->style_query_connection) sigc::connection();
162 static void
163 sp_text_context_dispose(GObject *obj)
165     SPTextContext *tc = SP_TEXT_CONTEXT(obj);
166     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
167     tc->style_query_connection.~connection();
168     tc->style_set_connection.~connection();
169     tc->sel_changed_connection.~connection();
170     tc->sel_modified_connection.~connection();
172     delete ec->shape_editor;
173     ec->shape_editor = NULL;
175     tc->text_sel_end.~iterator();
176     tc->text_sel_start.~iterator();
177     tc->text_selection_quads.~vector();
178     if (G_OBJECT_CLASS(parent_class)->dispose) {
179         G_OBJECT_CLASS(parent_class)->dispose(obj);
180     }
181     if (tc->grabbed) {
182         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
183         tc->grabbed = NULL;
184     }
186     Inkscape::Rubberband::get(ec->desktop)->stop();
189 static void
190 sp_text_context_setup(SPEventContext *ec)
192     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
193     SPDesktop *desktop = ec->desktop;
194     GtkSettings* settings = gtk_settings_get_default();
195     gint timeout = 0;
196     g_object_get( settings, "gtk-cursor-blink-time", &timeout, NULL );
197     if (timeout < 0) {
198         timeout = 200;
199     } else {
200         timeout /= 2;
201     }
203     tc->cursor = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLLINE, NULL);
204     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), 100, 0, 100, 100);
205     sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
206     sp_canvas_item_hide(tc->cursor);
208     tc->indicator = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
209     SP_CTRLRECT(tc->indicator)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100)));
210     SP_CTRLRECT(tc->indicator)->setColor(0x0000ff7f, false, 0);
211     sp_canvas_item_hide(tc->indicator);
213     tc->frame = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
214     SP_CTRLRECT(tc->frame)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100)));
215     SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0);
216     sp_canvas_item_hide(tc->frame);
218     tc->timeout = gtk_timeout_add(timeout, (GtkFunction) sp_text_context_timeout, ec);
220     tc->imc = gtk_im_multicontext_new();
221     if (tc->imc) {
222         GtkWidget *canvas = GTK_WIDGET(sp_desktop_canvas(desktop));
224         /* im preedit handling is very broken in inkscape for
225          * multi-byte characters.  See bug 1086769.
226          * We need to let the IM handle the preediting, and
227          * just take in the characters when they're finished being
228          * entered.
229          */
230         gtk_im_context_set_use_preedit(tc->imc, FALSE);
231         gtk_im_context_set_client_window(tc->imc, canvas->window);
233         g_signal_connect(G_OBJECT(canvas), "focus_in_event", G_CALLBACK(sptc_focus_in), tc);
234         g_signal_connect(G_OBJECT(canvas), "focus_out_event", G_CALLBACK(sptc_focus_out), tc);
235         g_signal_connect(G_OBJECT(tc->imc), "commit", G_CALLBACK(sptc_commit), tc);
237         if (GTK_WIDGET_HAS_FOCUS(canvas)) {
238             sptc_focus_in(canvas, NULL, tc);
239         }
240     }
242     if (((SPEventContextClass *) parent_class)->setup)
243         ((SPEventContextClass *) parent_class)->setup(ec);
245     ec->shape_editor = new ShapeEditor(ec->desktop);
247     SPItem *item = sp_desktop_selection(ec->desktop)->singleItem();
248     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
249         ec->shape_editor->set_item(item, SH_KNOTHOLDER);
250     }
252     tc->sel_changed_connection = sp_desktop_selection(desktop)->connectChanged(
253         sigc::bind(sigc::ptr_fun(&sp_text_context_selection_changed), tc)
254         );
255     tc->sel_modified_connection = sp_desktop_selection(desktop)->connectModified(
256         sigc::bind(sigc::ptr_fun(&sp_text_context_selection_modified), tc)
257         );
258     tc->style_set_connection = desktop->connectSetStyle(
259         sigc::bind(sigc::ptr_fun(&sp_text_context_style_set), tc)
260         );
261     tc->style_query_connection = desktop->connectQueryStyle(
262         sigc::bind(sigc::ptr_fun(&sp_text_context_style_query), tc)
263         );
265     sp_text_context_selection_changed(sp_desktop_selection(desktop), tc);
267     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
268     if (prefs->getBool("/tools/text/selcue")) {
269         ec->enableSelectionCue();
270     }
271     if (prefs->getBool("/tools/text/gradientdrag")) {
272         ec->enableGrDrag();
273     }
276 static void
277 sp_text_context_finish(SPEventContext *ec)
279     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
281     if (ec->desktop) {
282         sp_signal_disconnect_by_data(sp_desktop_canvas(ec->desktop), tc);
283     }
285     ec->enableGrDrag(false);
287     tc->style_set_connection.disconnect();
288     tc->style_query_connection.disconnect();
289     tc->sel_changed_connection.disconnect();
290     tc->sel_modified_connection.disconnect();
292     sp_text_context_forget_text(SP_TEXT_CONTEXT(ec));
294     if (tc->imc) {
295         g_object_unref(G_OBJECT(tc->imc));
296         tc->imc = NULL;
297     }
299     if (tc->timeout) {
300         gtk_timeout_remove(tc->timeout);
301         tc->timeout = 0;
302     }
304     if (tc->cursor) {
305         gtk_object_destroy(GTK_OBJECT(tc->cursor));
306         tc->cursor = NULL;
307     }
309     if (tc->indicator) {
310         gtk_object_destroy(GTK_OBJECT(tc->indicator));
311         tc->indicator = NULL;
312     }
314     if (tc->frame) {
315         gtk_object_destroy(GTK_OBJECT(tc->frame));
316         tc->frame = NULL;
317     }
319     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ;
320          it != tc->text_selection_quads.end() ; ++it) {
321         sp_canvas_item_hide(*it);
322         gtk_object_destroy(*it);
323     }
324     tc->text_selection_quads.clear();
328 static gint
329 sp_text_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
331     SPTextContext *tc = SP_TEXT_CONTEXT(event_context);
332     SPDesktop *desktop = event_context->desktop;
333     SPItem *item_ungrouped;
335     gint ret = FALSE;
337     sp_text_context_validate_cursor_iterators(tc);
339     switch (event->type) {
340         case GDK_BUTTON_PRESS:
341             if (event->button.button == 1 && !event_context->space_panning) {
342                 // find out clicked item, disregarding groups
343                 item_ungrouped = desktop->item_at_point(Geom::Point(event->button.x, event->button.y), TRUE);
344                 if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
345                     sp_desktop_selection(desktop)->set(item_ungrouped);
346                     if (tc->text) {
347                         // find out click point in document coordinates
348                         Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
349                         // set the cursor closest to that point
350                         tc->text_sel_start = tc->text_sel_end = sp_te_get_position_by_coords(tc->text, p);
351                         // update display
352                         sp_text_context_update_cursor(tc);
353                         sp_text_context_update_text_selection(tc);
354                         tc->dragging = 1;
355                     }
356                     ret = TRUE;
357                 }
358             }
359             break;
360         case GDK_2BUTTON_PRESS:
361             if (event->button.button == 1 && tc->text) {
362                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
363                 if (layout) {
364                     if (!layout->isStartOfWord(tc->text_sel_start))
365                         tc->text_sel_start.prevStartOfWord();
366                     if (!layout->isEndOfWord(tc->text_sel_end))
367                         tc->text_sel_end.nextEndOfWord();
368                     sp_text_context_update_cursor(tc);
369                     sp_text_context_update_text_selection(tc);
370                     tc->dragging = 2;
371                     ret = TRUE;
372                 }
373             }
374             break;
375         case GDK_3BUTTON_PRESS:
376             if (event->button.button == 1 && tc->text) {
377                 tc->text_sel_start.thisStartOfLine();
378                 tc->text_sel_end.thisEndOfLine();
379                 sp_text_context_update_cursor(tc);
380                 sp_text_context_update_text_selection(tc);
381                 tc->dragging = 3;
382                 ret = TRUE;
383             }
384             break;
385         case GDK_BUTTON_RELEASE:
386             if (event->button.button == 1 && tc->dragging && !event_context->space_panning) {
387                 tc->dragging = 0;
388                 sp_event_context_discard_delayed_snap_event(event_context);
389                 ret = TRUE;
390             }
391             break;
392         case GDK_MOTION_NOTIFY:
393             if (event->motion.state & GDK_BUTTON1_MASK && tc->dragging && !event_context->space_panning) {
394                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
395                 if (!layout) break;
396                 // find out click point in document coordinates
397                 Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
398                 // set the cursor closest to that point
399                 Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(tc->text, p);
400                 if (tc->dragging == 2) {
401                     // double-click dragging: go by word
402                     if (new_end < tc->text_sel_start) {
403                         if (!layout->isStartOfWord(new_end))
404                             new_end.prevStartOfWord();
405                     } else
406                         if (!layout->isEndOfWord(new_end))
407                             new_end.nextEndOfWord();
408                 } else if (tc->dragging == 3) {
409                     // triple-click dragging: go by line
410                     if (new_end < tc->text_sel_start)
411                         new_end.thisStartOfLine();
412                     else
413                         new_end.thisEndOfLine();
414                 }
415                 // update display
416                 if (tc->text_sel_end != new_end) {
417                     tc->text_sel_end = new_end;
418                     sp_text_context_update_cursor(tc);
419                     sp_text_context_update_text_selection(tc);
420                 }
421                 gobble_motion_events(GDK_BUTTON1_MASK);
422                 ret = TRUE;
423                 break;
424             }
425             // find out item under mouse, disregarding groups
426             item_ungrouped = desktop->item_at_point(Geom::Point(event->button.x, event->button.y), TRUE);
427             if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
429                 Inkscape::Text::Layout const *layout = te_get_layout(item_ungrouped);
430                 if (layout->inputTruncated()) {
431                     SP_CTRLRECT(tc->indicator)->setColor(0xff0000ff, false, 0);
432                 } else {
433                     SP_CTRLRECT(tc->indicator)->setColor(0x0000ff7f, false, 0);
434                 }
435                 Geom::OptRect ibbox = sp_item_bbox_desktop(item_ungrouped);
436                 if (ibbox) {
437                     SP_CTRLRECT(tc->indicator)->setRectangle(*ibbox);
438                 }
439                 sp_canvas_item_show(tc->indicator);
441                 event_context->cursor_shape = cursor_text_insert_xpm;
442                 event_context->hot_x = 7;
443                 event_context->hot_y = 10;
444                 sp_event_context_update_cursor(event_context);
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(event_context, item, event);
465     }
467     return ret;
470 static void
471 sp_text_context_setup_text(SPTextContext *tc)
473     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
475     /* Create <text> */
476     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_EVENT_CONTEXT_DESKTOP(ec)->doc());
477     Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
478     rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
480     /* Set style */
481     sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "/tools/text", true);
483     sp_repr_set_svg_double(rtext, "x", tc->pdoc[Geom::X]);
484     sp_repr_set_svg_double(rtext, "y", tc->pdoc[Geom::Y]);
486     /* Create <tspan> */
487     Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
488     rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
489     rtext->addChild(rtspan, NULL);
490     Inkscape::GC::release(rtspan);
492     /* Create TEXT */
493     Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
494     rtspan->addChild(rstring, NULL);
495     Inkscape::GC::release(rstring);
496     SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
497     /* fixme: Is selection::changed really immediate? */
498     /* yes, it's immediate .. why does it matter? */
499     sp_desktop_selection(ec->desktop)->set(text_item);
500     Inkscape::GC::release(rtext);
501     text_item->transform = sp_item_i2doc_affine(SP_ITEM(ec->desktop->currentLayer())).inverse();
503     text_item->updateRepr();
504     sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
505                      _("Create text"));
508 /**
509  * Insert the character indicated by tc.uni to replace the current selection,
510  * and reset tc.uni/tc.unipos to empty string.
511  *
512  * \pre tc.uni/tc.unipos non-empty.
513  */
514 static void
515 insert_uni_char(SPTextContext *const tc)
517     g_return_if_fail(tc->unipos
518                      && tc->unipos < sizeof(tc->uni)
519                      && tc->uni[tc->unipos] == '\0');
520     unsigned int uv;
521     sscanf(tc->uni, "%x", &uv);
522     tc->unipos = 0;
523     tc->uni[tc->unipos] = '\0';
525     if ( !g_unichar_isprint(static_cast<gunichar>(uv))
526          && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
527         // This may be due to bad input, so it goes to statusbar.
528         tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
529                                            _("Non-printable character"));
530     } else {
531         if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
532             sp_text_context_setup_text(tc);
533             tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
534         }
536         gchar u[10];
537         guint const len = g_unichar_to_utf8(uv, u);
538         u[len] = '\0';
540         tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
541         sp_text_context_update_cursor(tc);
542         sp_text_context_update_text_selection(tc);
543         sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM,
544                          _("Insert Unicode character"));
545     }
548 static void
549 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
551     unsigned int uv;
552     sscanf(hex, "%x", &uv);
553     if (!g_unichar_isprint((gunichar) uv)) {
554         uv = 0xfffd;
555     }
556     guint const len = g_unichar_to_utf8(uv, utf8);
557     utf8[len] = '\0';
560 static void
561 show_curr_uni_char(SPTextContext *const tc)
563     g_return_if_fail(tc->unipos < sizeof(tc->uni)
564                      && tc->uni[tc->unipos] == '\0');
565     if (tc->unipos) {
566         char utf8[10];
567         hex_to_printable_utf8_buf(tc->uni, utf8);
569         /* Status bar messages are in pango markup, so we need xml escaping. */
570         if (utf8[1] == '\0') {
571             switch(utf8[0]) {
572                 case '<': strcpy(utf8, "&lt;"); break;
573                 case '>': strcpy(utf8, "&gt;"); break;
574                 case '&': strcpy(utf8, "&amp;"); break;
575                 default: break;
576             }
577         }
578         tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
579                                           _("Unicode (<b>Enter</b> to finish): %s: %s"), tc->uni, utf8);
580     } else {
581         tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
582     }
585 static gint
586 sp_text_context_root_handler(SPEventContext *const event_context, GdkEvent *const event)
588     SPTextContext *const tc = SP_TEXT_CONTEXT(event_context);
590     SPDesktop *desktop = event_context->desktop;
592     sp_canvas_item_hide(tc->indicator);
594     sp_text_context_validate_cursor_iterators(tc);
596     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
597     event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
599     switch (event->type) {
600         case GDK_BUTTON_PRESS:
601             if (event->button.button == 1 && !event_context->space_panning) {
603                 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
604                     return TRUE;
605                 }
607                 // save drag origin
608                 event_context->xp = (gint) event->button.x;
609                 event_context->yp = (gint) event->button.y;
610                 event_context->within_tolerance = true;
612                 Geom::Point const button_pt(event->button.x, event->button.y);
613                 Geom::Point button_dt(desktop->w2d(button_pt));
615                 SnapManager &m = desktop->namedview->snap_manager;
616                 m.setup(desktop);
617                 m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE);
618                 m.unSetup();
620                 tc->p0 = button_dt;
621                 Inkscape::Rubberband::get(desktop)->start(desktop, tc->p0);
622                 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
623                                     GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
624                                     NULL, event->button.time);
625                 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
626                 tc->creating = 1;
628                 /* Processed */
629                 return TRUE;
630             }
631             break;
632         case GDK_MOTION_NOTIFY:
633             if (tc->over_text) {
634                 tc->over_text = 0;
635                 // update cursor and statusbar: we are not over a text object now
636                 event_context->cursor_shape = cursor_text_xpm;
637                 event_context->hot_x = 7;
638                 event_context->hot_y = 7;
639                 sp_event_context_update_cursor(event_context);
640                 desktop->event_context->defaultMessageContext()->clear();
641             }
643             if (tc->creating && event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
644                 if ( event_context->within_tolerance
645                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
646                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
647                     break; // do not drag if we're within tolerance from origin
648                 }
649                 // Once the user has moved farther than tolerance from the original location
650                 // (indicating they intend to draw, not click), then always process the
651                 // motion notify coordinates as given (no snapping back to origin)
652                 event_context->within_tolerance = false;
654                 Geom::Point const motion_pt(event->motion.x, event->motion.y);
655                 Geom::Point p = desktop->w2d(motion_pt);
657                 SnapManager &m = desktop->namedview->snap_manager;
658                 m.setup(desktop);
659                 m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE);
660                 m.unSetup();
662                 Inkscape::Rubberband::get(desktop)->move(p);
663                 gobble_motion_events(GDK_BUTTON1_MASK);
665                 // status text
666                 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::X]), desktop->namedview->getDefaultMetric());
667                 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::Y]), desktop->namedview->getDefaultMetric());
668                 event_context->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Flowed text frame</b>: %s &#215; %s"), xs->str, ys->str);
669                 g_string_free(xs, FALSE);
670                 g_string_free(ys, FALSE);
672             } else if (!sp_event_context_knot_mouseover(event_context)) {
673                 SnapManager &m = desktop->namedview->snap_manager;
674                 m.setup(desktop);
676                 Geom::Point const motion_w(event->motion.x, event->motion.y);
677                 Geom::Point motion_dt(desktop->w2d(motion_w));
678                 m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE));
679                 m.unSetup();
680             }
681             break;
682         case GDK_BUTTON_RELEASE:
683             if (event->button.button == 1 && !event_context->space_panning) {
684                 sp_event_context_discard_delayed_snap_event(event_context);
686                 Geom::Point p1 = desktop->w2d(Geom::Point(event->button.x, event->button.y));
688                 SnapManager &m = desktop->namedview->snap_manager;
689                 m.setup(desktop);
690                 m.freeSnapReturnByRef(p1, Inkscape::SNAPSOURCE_NODE_HANDLE);
691                 m.unSetup();
693                 if (tc->grabbed) {
694                     sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
695                     tc->grabbed = NULL;
696                 }
698                 Inkscape::Rubberband::get(desktop)->stop();
700                 if (tc->creating && event_context->within_tolerance) {
701                     /* Button 1, set X & Y & new item */
702                     sp_desktop_selection(desktop)->clear();
703                     tc->pdoc = desktop->dt2doc(p1);
704                     tc->show = TRUE;
705                     tc->phase = 1;
706                     tc->nascent_object = 1; // new object was just created
708                     /* Cursor */
709                     sp_canvas_item_show(tc->cursor);
710                     // Cursor height is defined by the new text object's font size; it needs to be set
711                     // artificially here, for the text object does not exist yet:
712                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
713                     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), p1, p1 + Geom::Point(0, cursor_height));
714                     event_context->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line.")); // FIXME:: this is a copy of a string from _update_cursor below, do not desync
716                     event_context->within_tolerance = false;
717                 } else if (tc->creating) {
718                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
719                     if (fabs(p1[Geom::Y] - tc->p0[Geom::Y]) > cursor_height) {
720                         // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
721                         SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
722                         /* Set style */
723                         sp_desktop_apply_style_tool(desktop, SP_OBJECT_REPR(ft), "/tools/text", true);
724                         sp_desktop_selection(desktop)->set(ft);
725                         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
726                         sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
727                                          _("Create flowed text"));
728                     } else {
729                         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
730                     }
731                 }
732                 tc->creating = false;
733                 return TRUE;
734             }
735             break;
736         case GDK_KEY_PRESS: {
737             guint const group0_keyval = get_group0_keyval(&event->key);
739             if (group0_keyval == GDK_KP_Add ||
740                 group0_keyval == GDK_KP_Subtract) {
741                 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
742                     break; // otherwise pass on keypad +/- so they can zoom
743             }
745             if ((tc->text) || (tc->nascent_object)) {
746                 // there is an active text object in this context, or a new object was just created
748                 if (tc->unimode || !tc->imc
749                     || (MOD__CTRL && MOD__SHIFT)    // input methods tend to steal this for unimode,
750                                                     // but we have our own so make sure they don't swallow it
751                     || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
752                     //IM did not consume the key, or we're in unimode
754                         if (!MOD__CTRL_ONLY && tc->unimode) {
755                             /* TODO: ISO 14755 (section 3 Definitions) says that we should also
756                                accept the first 6 characters of alphabets other than the latin
757                                alphabet "if the Latin alphabet is not used".  The below is also
758                                reasonable (viz. hope that the user's keyboard includes latin
759                                characters and force latin interpretation -- just as we do for our
760                                keyboard shortcuts), but differs from the ISO 14755
761                                recommendation. */
762                             switch (group0_keyval) {
763                                 case GDK_space:
764                                 case GDK_KP_Space: {
765                                     if (tc->unipos) {
766                                         insert_uni_char(tc);
767                                     }
768                                     /* Stay in unimode. */
769                                     show_curr_uni_char(tc);
770                                     return TRUE;
771                                 }
773                                 case GDK_BackSpace: {
774                                     g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
775                                     if (tc->unipos) {
776                                         tc->uni[--tc->unipos] = '\0';
777                                     }
778                                     show_curr_uni_char(tc);
779                                     return TRUE;
780                                 }
782                                 case GDK_Return:
783                                 case GDK_KP_Enter: {
784                                     if (tc->unipos) {
785                                         insert_uni_char(tc);
786                                     }
787                                     /* Exit unimode. */
788                                     tc->unimode = false;
789                                     event_context->defaultMessageContext()->clear();
790                                     return TRUE;
791                                 }
793                                 case GDK_Escape: {
794                                     // Cancel unimode.
795                                     tc->unimode = false;
796                                     gtk_im_context_reset(tc->imc);
797                                     event_context->defaultMessageContext()->clear();
798                                     return TRUE;
799                                 }
801                                 case GDK_Shift_L:
802                                 case GDK_Shift_R:
803                                     break;
805                                 default: {
806                                     if (g_ascii_isxdigit(group0_keyval)) {
807                                         g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
808                                         tc->uni[tc->unipos++] = group0_keyval;
809                                         tc->uni[tc->unipos] = '\0';
810                                         if (tc->unipos == 8) {
811                                             /* This behaviour is partly to allow us to continue to
812                                                use a fixed-length buffer for tc->uni.  Reason for
813                                                choosing the number 8 is that it's the length of
814                                                ``canonical form'' mentioned in the ISO 14755 spec.
815                                                An advantage over choosing 6 is that it allows using
816                                                backspace for typos & misremembering when entering a
817                                                6-digit number. */
818                                             insert_uni_char(tc);
819                                         }
820                                         show_curr_uni_char(tc);
821                                         return TRUE;
822                                     } else {
823                                         /* The intent is to ignore but consume characters that could be
824                                            typos for hex digits.  Gtk seems to ignore & consume all
825                                            non-hex-digits, and we do similar here.  Though note that some
826                                            shortcuts (like keypad +/- for zoom) get processed before
827                                            reaching this code. */
828                                         return TRUE;
829                                     }
830                                 }
831                             }
832                         }
834                         Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
835                         Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
836                         bool cursor_moved = false;
837                         int screenlines = 1;
838                         if (tc->text) {
839                             double spacing = sp_te_get_average_linespacing(tc->text);
840                             Geom::Rect const d = desktop->get_display_area();
841                             screenlines = (int) floor(fabs(d.min()[Geom::Y] - d.max()[Geom::Y])/spacing) - 1;
842                             if (screenlines <= 0)
843                                 screenlines = 1;
844                         }
846                         /* Neither unimode nor IM consumed key; process text tool shortcuts */
847                         switch (group0_keyval) {
848                             case GDK_x:
849                             case GDK_X:
850                                 if (MOD__ALT_ONLY) {
851                                     desktop->setToolboxFocusTo ("altx-text");
852                                     return TRUE;
853                                 }
854                                 break;
855                             case GDK_space:
856                                 if (MOD__CTRL_ONLY) {
857                                     /* No-break space */
858                                     if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
859                                         sp_text_context_setup_text(tc);
860                                         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
861                                     }
862                                     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
863                                     sp_text_context_update_cursor(tc);
864                                     sp_text_context_update_text_selection(tc);
865                                     desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
866                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
867                                                      _("Insert no-break space"));
868                                     return TRUE;
869                                 }
870                                 break;
871                             case GDK_U:
872                             case GDK_u:
873                                 if (MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT)) {
874                                     if (tc->unimode) {
875                                         tc->unimode = false;
876                                         event_context->defaultMessageContext()->clear();
877                                     } else {
878                                         tc->unimode = true;
879                                         tc->unipos = 0;
880                                         event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
881                                     }
882                                     if (tc->imc) {
883                                         gtk_im_context_reset(tc->imc);
884                                     }
885                                     return TRUE;
886                                 }
887                                 break;
888                             case GDK_B:
889                             case GDK_b:
890                                 if (MOD__CTRL_ONLY && tc->text) {
891                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
892                                     SPCSSAttr *css = sp_repr_css_attr_new();
893                                     if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
894                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
895                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
896                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
897                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
898                                         sp_repr_css_set_property(css, "font-weight", "bold");
899                                     else
900                                         sp_repr_css_set_property(css, "font-weight", "normal");
901                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
902                                     sp_repr_css_attr_unref(css);
903                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
904                                                      _("Make bold"));
905                                     sp_text_context_update_cursor(tc);
906                                     sp_text_context_update_text_selection(tc);
907                                     return TRUE;
908                                 }
909                                 break;
910                             case GDK_I:
911                             case GDK_i:
912                                 if (MOD__CTRL_ONLY && tc->text) {
913                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
914                                     SPCSSAttr *css = sp_repr_css_attr_new();
915                                     if (style->font_style.computed != SP_CSS_FONT_STYLE_NORMAL)
916                                         sp_repr_css_set_property(css, "font-style", "normal");
917                                     else
918                                         sp_repr_css_set_property(css, "font-style", "italic");
919                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
920                                     sp_repr_css_attr_unref(css);
921                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
922                                                      _("Make italic"));
923                                     sp_text_context_update_cursor(tc);
924                                     sp_text_context_update_text_selection(tc);
925                                     return TRUE;
926                                 }
927                                 break;
929                             case GDK_A:
930                             case GDK_a:
931                                 if (MOD__CTRL_ONLY && tc->text) {
932                                     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
933                                     if (layout) {
934                                         tc->text_sel_start = layout->begin();
935                                         tc->text_sel_end = layout->end();
936                                         sp_text_context_update_cursor(tc);
937                                         sp_text_context_update_text_selection(tc);
938                                         return TRUE;
939                                     }
940                                 }
941                                 break;
943                             case GDK_Return:
944                             case GDK_KP_Enter:
945                             {
946                                 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
947                                     sp_text_context_setup_text(tc);
948                                     tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
949                                 }
951                                 iterator_pair enter_pair;
952                                 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, enter_pair);
953                                 (void)success; // TODO cleanup
954                                 tc->text_sel_start = tc->text_sel_end = enter_pair.first;
956                                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
958                                 sp_text_context_update_cursor(tc);
959                                 sp_text_context_update_text_selection(tc);
960                                 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
961                                                  _("New line"));
962                                 return TRUE;
963                             }
964                             case GDK_BackSpace:
965                                 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
967                                     bool noSelection = false;
969                                         if (tc->text_sel_start == tc->text_sel_end) {
970                                         tc->text_sel_start.prevCursorPosition();
971                                         noSelection = true;
972                                     }
974                                         iterator_pair bspace_pair;
975                                         bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, bspace_pair);
977                                     if (noSelection) {
978                                         if (success) {
979                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
980                                         } else { // nothing deleted
981                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.second;
982                                         }
983                                     } else {
984                                         if (success) {
985                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
986                                         } else { // nothing deleted
987                                             tc->text_sel_start = bspace_pair.first;
988                                             tc->text_sel_end = bspace_pair.second;
989                                         }
990                                     }
992                                     sp_text_context_update_cursor(tc);
993                                     sp_text_context_update_text_selection(tc);
994                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
995                                                      _("Backspace"));
996                                 }
997                                 return TRUE;
998                             case GDK_Delete:
999                             case GDK_KP_Delete:
1000                                 if (tc->text) {
1001                                     bool noSelection = false;
1003                                     if (tc->text_sel_start == tc->text_sel_end) {
1004                                         tc->text_sel_end.nextCursorPosition();
1005                                         noSelection = true;
1006                                     }
1008                                     iterator_pair del_pair;
1009                                     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, del_pair);
1011                                     if (noSelection) {
1012                                         tc->text_sel_start = tc->text_sel_end = del_pair.first;
1013                                     } else {
1014                                         if (success) {
1015                                             tc->text_sel_start = tc->text_sel_end = del_pair.first;
1016                                         } else { // nothing deleted
1017                                             tc->text_sel_start = del_pair.first;
1018                                             tc->text_sel_end = del_pair.second;
1019                                         }
1020                                     }
1023                                     sp_text_context_update_cursor(tc);
1024                                     sp_text_context_update_text_selection(tc);
1025                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
1026                                                      _("Delete"));
1027                                 }
1028                                 return TRUE;
1029                             case GDK_Left:
1030                             case GDK_KP_Left:
1031                             case GDK_KP_4:
1032                                 if (tc->text) {
1033                                     if (MOD__ALT) {
1034                                         gint mul = 1 + gobble_key_events(
1035                                             get_group0_keyval(&event->key), 0); // with any mask
1036                                         if (MOD__SHIFT)
1037                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-10, 0));
1038                                         else
1039                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-1, 0));
1040                                         sp_text_context_update_cursor(tc);
1041                                         sp_text_context_update_text_selection(tc);
1042                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT,
1043                                                                _("Kern to the left"));
1044                                     } else {
1045                                         if (MOD__CTRL)
1046                                             tc->text_sel_end.cursorLeftWithControl();
1047                                         else
1048                                             tc->text_sel_end.cursorLeft();
1049                                         cursor_moved = true;
1050                                         break;
1051                                     }
1052                                 }
1053                                 return TRUE;
1054                             case GDK_Right:
1055                             case GDK_KP_Right:
1056                             case GDK_KP_6:
1057                                 if (tc->text) {
1058                                     if (MOD__ALT) {
1059                                         gint mul = 1 + gobble_key_events(
1060                                             get_group0_keyval(&event->key), 0); // with any mask
1061                                         if (MOD__SHIFT)
1062                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*10, 0));
1063                                         else
1064                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*1, 0));
1065                                         sp_text_context_update_cursor(tc);
1066                                         sp_text_context_update_text_selection(tc);
1067                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT,
1068                                                                _("Kern to the right"));
1069                                     } else {
1070                                         if (MOD__CTRL)
1071                                             tc->text_sel_end.cursorRightWithControl();
1072                                         else
1073                                             tc->text_sel_end.cursorRight();
1074                                         cursor_moved = true;
1075                                         break;
1076                                     }
1077                                 }
1078                                 return TRUE;
1079                             case GDK_Up:
1080                             case GDK_KP_Up:
1081                             case GDK_KP_8:
1082                                 if (tc->text) {
1083                                     if (MOD__ALT) {
1084                                         gint mul = 1 + gobble_key_events(
1085                                             get_group0_keyval(&event->key), 0); // with any mask
1086                                         if (MOD__SHIFT)
1087                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-10));
1088                                         else
1089                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-1));
1090                                         sp_text_context_update_cursor(tc);
1091                                         sp_text_context_update_text_selection(tc);
1092                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT,
1093                                                                _("Kern up"));
1095                                     } else {
1096                                         if (MOD__CTRL)
1097                                             tc->text_sel_end.cursorUpWithControl();
1098                                         else
1099                                             tc->text_sel_end.cursorUp();
1100                                         cursor_moved = true;
1101                                         break;
1102                                     }
1103                                 }
1104                                 return TRUE;
1105                             case GDK_Down:
1106                             case GDK_KP_Down:
1107                             case GDK_KP_2:
1108                                 if (tc->text) {
1109                                     if (MOD__ALT) {
1110                                         gint mul = 1 + gobble_key_events(
1111                                             get_group0_keyval(&event->key), 0); // with any mask
1112                                         if (MOD__SHIFT)
1113                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*10));
1114                                         else
1115                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*1));
1116                                         sp_text_context_update_cursor(tc);
1117                                         sp_text_context_update_text_selection(tc);
1118                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT,
1119                                                                _("Kern down"));
1121                                     } else {
1122                                         if (MOD__CTRL)
1123                                             tc->text_sel_end.cursorDownWithControl();
1124                                         else
1125                                             tc->text_sel_end.cursorDown();
1126                                         cursor_moved = true;
1127                                         break;
1128                                     }
1129                                 }
1130                                 return TRUE;
1131                             case GDK_Home:
1132                             case GDK_KP_Home:
1133                                 if (tc->text) {
1134                                     if (MOD__CTRL)
1135                                         tc->text_sel_end.thisStartOfShape();
1136                                     else
1137                                         tc->text_sel_end.thisStartOfLine();
1138                                     cursor_moved = true;
1139                                     break;
1140                                 }
1141                                 return TRUE;
1142                             case GDK_End:
1143                             case GDK_KP_End:
1144                                 if (tc->text) {
1145                                     if (MOD__CTRL)
1146                                         tc->text_sel_end.nextStartOfShape();
1147                                     else
1148                                         tc->text_sel_end.thisEndOfLine();
1149                                     cursor_moved = true;
1150                                     break;
1151                                 }
1152                                 return TRUE;
1153                             case GDK_Page_Down:
1154                             case GDK_KP_Page_Down:
1155                                 if (tc->text) {
1156                                     tc->text_sel_end.cursorDown(screenlines);
1157                                     cursor_moved = true;
1158                                     break;
1159                                 }
1160                                 return TRUE;
1161                             case GDK_Page_Up:
1162                             case GDK_KP_Page_Up:
1163                                 if (tc->text) {
1164                                     tc->text_sel_end.cursorUp(screenlines);
1165                                     cursor_moved = true;
1166                                     break;
1167                                 }
1168                                 return TRUE;
1169                             case GDK_Escape:
1170                                 if (tc->creating) {
1171                                     tc->creating = 0;
1172                                     if (tc->grabbed) {
1173                                         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1174                                         tc->grabbed = NULL;
1175                                     }
1176                                     Inkscape::Rubberband::get(desktop)->stop();
1177                                 } else {
1178                                     sp_desktop_selection(desktop)->clear();
1179                                 }
1180                                 tc->nascent_object = FALSE;
1181                                 return TRUE;
1182                             case GDK_bracketleft:
1183                                 if (tc->text) {
1184                                     if (MOD__ALT || MOD__CTRL) {
1185                                         if (MOD__ALT) {
1186                                             if (MOD__SHIFT) {
1187                                                 // FIXME: alt+shift+[] does not work, don't know why
1188                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1189                                             } else {
1190                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1191                                             }
1192                                         } else {
1193                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -90);
1194                                         }
1195                                         sp_document_maybe_done(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT,
1196                                                                _("Rotate counterclockwise"));
1197                                         sp_text_context_update_cursor(tc);
1198                                         sp_text_context_update_text_selection(tc);
1199                                         return TRUE;
1200                                     }
1201                                 }
1202                                 break;
1203                             case GDK_bracketright:
1204                                 if (tc->text) {
1205                                     if (MOD__ALT || MOD__CTRL) {
1206                                         if (MOD__ALT) {
1207                                             if (MOD__SHIFT) {
1208                                                 // FIXME: alt+shift+[] does not work, don't know why
1209                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1210                                             } else {
1211                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1212                                             }
1213                                         } else {
1214                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 90);
1215                                         }
1216                                         sp_document_maybe_done(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT,
1217                                                                 _("Rotate clockwise"));
1218                                         sp_text_context_update_cursor(tc);
1219                                         sp_text_context_update_text_selection(tc);
1220                                         return TRUE;
1221                                     }
1222                                 }
1223                                 break;
1224                             case GDK_less:
1225                             case GDK_comma:
1226                                 if (tc->text) {
1227                                     if (MOD__ALT) {
1228                                         if (MOD__CTRL) {
1229                                             if (MOD__SHIFT)
1230                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1231                                             else
1232                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1233                                             sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT,
1234                                                                     _("Contract line spacing"));
1236                                         } else {
1237                                             if (MOD__SHIFT)
1238                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1239                                             else
1240                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1241                                             sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT,
1242                                                                     _("Contract letter spacing"));
1244                                         }
1245                                         sp_text_context_update_cursor(tc);
1246                                         sp_text_context_update_text_selection(tc);
1247                                         return TRUE;
1248                                     }
1249                                 }
1250                                 break;
1251                             case GDK_greater:
1252                             case GDK_period:
1253                                 if (tc->text) {
1254                                     if (MOD__ALT) {
1255                                         if (MOD__CTRL) {
1256                                             if (MOD__SHIFT)
1257                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1258                                             else
1259                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1260                                             sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT,
1261                                                                     _("Expand line spacing"));
1263                                         } else {
1264                                             if (MOD__SHIFT)
1265                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1266                                             else
1267                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1268                                             sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT,
1269                                                                     _("Expand letter spacing"));
1271                                         }
1272                                         sp_text_context_update_cursor(tc);
1273                                         sp_text_context_update_text_selection(tc);
1274                                         return TRUE;
1275                                     }
1276                                 }
1277                                 break;
1278                             default:
1279                                 break;
1280                         }
1282                         if (cursor_moved) {
1283                             if (!MOD__SHIFT)
1284                                 tc->text_sel_start = tc->text_sel_end;
1285                             if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1286                                 sp_text_context_update_cursor(tc);
1287                                 sp_text_context_update_text_selection(tc);
1288                             }
1289                             return TRUE;
1290                         }
1292                 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1293             } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1294                 // except up/down that are swallowed to prevent the zoom field from activation
1295                 if ((group0_keyval == GDK_Up    ||
1296                      group0_keyval == GDK_Down  ||
1297                      group0_keyval == GDK_KP_Up ||
1298                      group0_keyval == GDK_KP_Down )
1299                     && !MOD__CTRL_ONLY) {
1300                     return TRUE;
1301                 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1302                     if (tc->creating) {
1303                         tc->creating = 0;
1304                         if (tc->grabbed) {
1305                             sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1306                             tc->grabbed = NULL;
1307                         }
1308                         Inkscape::Rubberband::get(desktop)->stop();
1309                     }
1310                 } else if ((group0_keyval == GDK_x || group0_keyval == GDK_X) && MOD__ALT_ONLY) {
1311                     desktop->setToolboxFocusTo ("altx-text");
1312                     return TRUE;
1313                 }
1314             }
1315             break;
1316         }
1318         case GDK_KEY_RELEASE:
1319             if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1320                 return TRUE;
1321             }
1322             break;
1323         default:
1324             break;
1325     }
1327     // if nobody consumed it so far
1328     if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1329         return ((SPEventContextClass *) parent_class)->root_handler(event_context, event); // send event to parent
1330     } else {
1331         return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1332     }
1335 /**
1336  Attempts to paste system clipboard into the currently edited text, returns true on success
1337  */
1338 bool
1339 sp_text_paste_inline(SPEventContext *ec)
1341     if (!SP_IS_TEXT_CONTEXT(ec))
1342         return false;
1344     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1346     if ((tc->text) || (tc->nascent_object)) {
1347         // there is an active text object in this context, or a new object was just created
1349         Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1350         Glib::ustring const clip_text = refClipboard->wait_for_text();
1352         if (!clip_text.empty()) {
1353                 // Fix for 244940
1354                 // The XML standard defines the following as valid characters
1355                 // (Extensible Markup Language (XML) 1.0 (Fourth Edition) paragraph 2.2)
1356                 // char ::=     #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
1357                 // Since what comes in off the paste buffer will go right into XML, clean
1358                 // the text here.
1359                 Glib::ustring text(clip_text);
1360                 Glib::ustring::iterator itr = text.begin();
1361                 gunichar paste_string_uchar;
1363                 while(itr != text.end())
1364                 {
1365                     paste_string_uchar = *itr;
1367                     // Make sure we don't have a control character. We should really check
1368                     // for the whole range above... Add the rest of the invalid cases from
1369                     // above if we find additional issues
1370                     if(paste_string_uchar >= 0x00000020 ||
1371                        paste_string_uchar == 0x00000009 ||
1372                        paste_string_uchar == 0x0000000A ||
1373                        paste_string_uchar == 0x0000000D) {
1374                         itr++;
1375                     } else {
1376                         itr = text.erase(itr);
1377                     }
1378                 }
1380             if (!tc->text) { // create text if none (i.e. if nascent_object)
1381                 sp_text_context_setup_text(tc);
1382                 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1383             }
1385             // using indices is slow in ustrings. Whatever.
1386             Glib::ustring::size_type begin = 0;
1387             for ( ; ; ) {
1388                 Glib::ustring::size_type end = text.find('\n', begin);
1389                 if (end == Glib::ustring::npos) {
1390                     if (begin != text.length())
1391                         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());
1392                     break;
1393                 }
1394                 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());
1395                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1396                 begin = end + 1;
1397             }
1398             sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
1399                              _("Paste text"));
1401             return true;
1402         }
1403     } // FIXME: else create and select a new object under cursor!
1405     return false;
1408 /**
1409  Gets the raw characters that comprise the currently selected text, converting line
1410  breaks into lf characters.
1411 */
1412 Glib::ustring
1413 sp_text_get_selected_text(SPEventContext const *ec)
1415     if (!SP_IS_TEXT_CONTEXT(ec))
1416         return "";
1417     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1418     if (tc->text == NULL)
1419         return "";
1421     return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1424 SPCSSAttr *
1425 sp_text_get_style_at_cursor(SPEventContext const *ec)
1427     if (!SP_IS_TEXT_CONTEXT(ec))
1428         return NULL;
1429     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1430     if (tc->text == NULL)
1431         return NULL;
1433     SPObject const *obj = sp_te_object_at_position(tc->text, tc->text_sel_end);
1434     if (obj)
1435         return take_style_from_item((SPItem *) obj);
1436     return NULL;
1439 /**
1440  Deletes the currently selected characters. Returns false if there is no
1441  text selection currently.
1442 */
1443 bool sp_text_delete_selection(SPEventContext *ec)
1445     if (!SP_IS_TEXT_CONTEXT(ec))
1446         return false;
1447     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1448     if (tc->text == NULL)
1449         return false;
1451     if (tc->text_sel_start == tc->text_sel_end)
1452         return false;
1454     iterator_pair pair;
1455     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair);
1458     if (success) {
1459         tc->text_sel_start = tc->text_sel_end = pair.first;
1460     } else { // nothing deleted
1461         tc->text_sel_start = pair.first;
1462         tc->text_sel_end = pair.second;
1463     }
1465     sp_text_context_update_cursor(tc);
1466     sp_text_context_update_text_selection(tc);
1468     return true;
1471 /**
1472  * \param selection Should not be NULL.
1473  */
1474 static void
1475 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1477     g_assert(selection != NULL);
1479     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1481     ec->shape_editor->unset_item(SH_KNOTHOLDER);
1482     SPItem *item = selection->singleItem();
1483     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1484         ec->shape_editor->set_item(item, SH_KNOTHOLDER);
1485     }
1487     if (tc->text && (item != tc->text)) {
1488         sp_text_context_forget_text(tc);
1489     }
1490     tc->text = NULL;
1492     if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1493         tc->text = item;
1494         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1495         if (layout)
1496             tc->text_sel_start = tc->text_sel_end = layout->end();
1497     } else {
1498         tc->text = NULL;
1499     }
1501     // we update cursor without scrolling, because this position may not be final;
1502     // item_handler moves cusros to the point of click immediately
1503     sp_text_context_update_cursor(tc, false);
1504     sp_text_context_update_text_selection(tc);
1507 static void
1508 sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, SPTextContext *tc)
1510     sp_text_context_update_cursor(tc);
1511     sp_text_context_update_text_selection(tc);
1514 static bool
1515 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1517     if (tc->text == NULL)
1518         return false;
1519     if (tc->text_sel_start == tc->text_sel_end)
1520         return false;    // will get picked up by the parent and applied to the whole text object
1522     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1523     sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1524                      _("Set text style"));
1525     sp_text_context_update_cursor(tc);
1526     sp_text_context_update_text_selection(tc);
1528     return true;
1531 static int
1532 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1534     if (tc->text == NULL)
1535         return QUERY_STYLE_NOTHING;
1536     const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1537     if (layout == NULL)
1538         return QUERY_STYLE_NOTHING;
1539     sp_text_context_validate_cursor_iterators(tc);
1541     GSList *styles_list = NULL;
1543     Inkscape::Text::Layout::iterator begin_it, end_it;
1544     if (tc->text_sel_start < tc->text_sel_end) {
1545         begin_it = tc->text_sel_start;
1546         end_it = tc->text_sel_end;
1547     } else {
1548         begin_it = tc->text_sel_end;
1549         end_it = tc->text_sel_start;
1550     }
1551     if (begin_it == end_it)
1552         if (!begin_it.prevCharacter())
1553             end_it.nextCharacter();
1554     for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1555         SPObject const *pos_obj = 0;
1556         void *rawptr = 0;
1557         layout->getSourceOfCharacter(it, &rawptr);
1558         if (!rawptr || !SP_IS_OBJECT(rawptr))
1559             continue;
1560         pos_obj = SP_OBJECT(rawptr);
1561         while (SP_IS_STRING(pos_obj) && SP_OBJECT_PARENT(pos_obj)) {
1562            pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
1563         }
1564         styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1565     }
1567     int result = sp_desktop_query_style_from_list (styles_list, style, property);
1569     g_slist_free(styles_list);
1570     return result;
1573 static void
1574 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1576     if (tc->text == NULL)
1577         return;
1578     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1579     if (layout) {     // undo can change the text length without us knowing it
1580         layout->validateIterator(&tc->text_sel_start);
1581         layout->validateIterator(&tc->text_sel_end);
1582     }
1585 static void
1586 sp_text_context_update_cursor(SPTextContext *tc,  bool scroll_to_see)
1588     GdkRectangle im_cursor = { 0, 0, 1, 1 };
1590     // due to interruptible display, tc may already be destroyed during a display update before
1591     // the cursor update (can't do both atomically, alas)
1592     if (!tc->desktop) return;
1594     if (tc->text) {
1595         Geom::Point p0, p1;
1596         sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1597         Geom::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1598         Geom::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1600         // scroll to show cursor
1601         if (scroll_to_see) {
1602             Geom::Point const center = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().midpoint();
1603             if (Geom::L2(d0 - center) > Geom::L2(d1 - center))
1604                 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1605                 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d0, 1.0);
1606             else
1607                 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d1, 1.0);
1608         }
1610         sp_canvas_item_show(tc->cursor);
1611         sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1613         /* fixme: ... need another transformation to get canvas widget coordinate space? */
1614         im_cursor.x = (int) floor(d0[Geom::X]);
1615         im_cursor.y = (int) floor(d0[Geom::Y]);
1616         im_cursor.width = (int) floor(d1[Geom::X]) - im_cursor.x;
1617         im_cursor.height = (int) floor(d1[Geom::Y]) - im_cursor.y;
1619         tc->show = TRUE;
1620         tc->phase = 1;
1622         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1623         int const nChars = layout->iteratorToCharIndex(layout->end());
1624         char const *trunc = "";
1625         bool truncated = false;
1626         if (layout->inputTruncated()) {
1627             truncated = true;
1628             trunc = _(" [truncated]");
1629         }
1630         if (SP_IS_FLOWTEXT(tc->text)) {
1631             SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1632             if (frame) {
1633                 if (truncated) {
1634                     SP_CTRLRECT(tc->frame)->setColor(0xff0000ff, false, 0);
1635                 } else {
1636                     SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0);
1637                 }
1638                 sp_canvas_item_show(tc->frame);
1639                 Geom::OptRect frame_bbox = sp_item_bbox_desktop(frame);
1640                 if (frame_bbox) {
1641                     SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
1642                 }
1643             }
1645             SP_EVENT_CONTEXT(tc)->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("Type or edit flowed text (%d characters%s); <b>Enter</b> to start new paragraph."), nChars, trunc);
1646         } else {
1647             SP_EVENT_CONTEXT(tc)->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("Type or edit text (%d characters%s); <b>Enter</b> to start new line."), nChars, trunc);
1648         }
1650     } else {
1651         sp_canvas_item_hide(tc->cursor);
1652         sp_canvas_item_hide(tc->frame);
1653         tc->show = FALSE;
1654         if (!tc->nascent_object) {
1655             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
1656         }
1657     }
1659     if (tc->imc) {
1660         gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1661     }
1662     SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1665 static void sp_text_context_update_text_selection(SPTextContext *tc)
1667     // due to interruptible display, tc may already be destroyed during a display update before
1668     // the selection update (can't do both atomically, alas)
1669     if (!tc->desktop) return;
1671     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1672         sp_canvas_item_hide(*it);
1673         gtk_object_destroy(*it);
1674     }
1675     tc->text_selection_quads.clear();
1677     std::vector<Geom::Point> quads;
1678     if (tc->text != NULL)
1679         quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1680     for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1681         SPCanvasItem *quad_canvasitem;
1682         quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1683         // FIXME: make the color settable in prefs
1684         // for now, use semitrasparent blue, as cairo cannot do inversion :(
1685         sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x00777777);
1686         sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1687         sp_canvas_item_show(quad_canvasitem);
1688         tc->text_selection_quads.push_back(quad_canvasitem);
1689     }
1692 static gint
1693 sp_text_context_timeout(SPTextContext *tc)
1695     if (tc->show) {
1696         sp_canvas_item_show(tc->cursor);
1697         if (tc->phase) {
1698             tc->phase = 0;
1699             sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
1700         } else {
1701             tc->phase = 1;
1702             sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0xffffffff);
1703         }
1704     }
1706     return TRUE;
1709 static void
1710 sp_text_context_forget_text(SPTextContext *tc)
1712     if (! tc->text) return;
1713     SPItem *ti = tc->text;
1714     (void)ti;
1715     /* We have to set it to zero,
1716      * or selection changed signal messes everything up */
1717     tc->text = NULL;
1719 /* FIXME: this automatic deletion when nothing is inputted crashes the XML edittor and also crashes when duplicating an empty flowtext.
1720     So don't create an empty flowtext in the first place? Create it when first character is typed.
1721     */
1722 /*
1723     if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1724         Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1725         // the repr may already have been unparented
1726         // if we were called e.g. as the result of
1727         // an undo or the element being removed from
1728         // the XML editor
1729         if ( text_repr && sp_repr_parent(text_repr) ) {
1730             sp_repr_unparent(text_repr);
1731             sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1732                      _("Remove empty text"));
1733         }
1734     }
1735 */
1738 gint
1739 sptc_focus_in(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1741     gtk_im_context_focus_in(tc->imc);
1742     return FALSE;
1745 gint
1746 sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1748     gtk_im_context_focus_out(tc->imc);
1749     return FALSE;
1752 static void
1753 sptc_commit(GtkIMContext */*imc*/, gchar *string, SPTextContext *tc)
1755     if (!tc->text) {
1756         sp_text_context_setup_text(tc);
1757         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1758     }
1760     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1761     sp_text_context_update_cursor(tc);
1762     sp_text_context_update_text_selection(tc);
1764     sp_document_done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT,
1765                      _("Type text"));
1768 void
1769 sp_text_context_place_cursor (SPTextContext *tc, SPObject *text, Inkscape::Text::Layout::iterator where)
1771     SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text);
1772     tc->text_sel_start = tc->text_sel_end = where;
1773     sp_text_context_update_cursor(tc);
1774     sp_text_context_update_text_selection(tc);
1777 void
1778 sp_text_context_place_cursor_at (SPTextContext *tc, SPObject *text, Geom::Point const p)
1780     SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text);
1781     sp_text_context_place_cursor (tc, text, sp_te_get_position_by_coords(tc->text, p));
1784 Inkscape::Text::Layout::iterator *sp_text_context_get_cursor_position(SPTextContext *tc, SPObject *text)
1786     if (text != tc->text)
1787         return NULL;
1788     return &(tc->text_sel_end);
1792 /*
1793   Local Variables:
1794   mode:c++
1795   c-file-style:"stroustrup"
1796   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1797   indent-tabs-mode:nil
1798   fill-column:99
1799   End:
1800 */
1801 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :