Code

Merge and cleanup of GSoC C++-ification project.
[inkscape.git] / src / text-context.cpp
1 /*
2  * SPTextContext
3  *
4  * Authors:
5  *   Lauris Kaplinski <lauris@kaplinski.com>
6  *   bulia byak <buliabyak@users.sf.net>
7  *   Jon A. Cruz <jon@joncruz.org>
8  *   Abhishek Sharma
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"
57 using Inkscape::DocumentUndo;
59 static void sp_text_context_class_init(SPTextContextClass *klass);
60 static void sp_text_context_init(SPTextContext *text_context);
61 static void sp_text_context_dispose(GObject *obj);
63 static void sp_text_context_setup(SPEventContext *ec);
64 static void sp_text_context_finish(SPEventContext *ec);
65 static gint sp_text_context_root_handler(SPEventContext *event_context, GdkEvent *event);
66 static gint sp_text_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
68 static void sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc);
69 static void sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc);
70 static bool sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc);
71 static int sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc);
73 static void sp_text_context_validate_cursor_iterators(SPTextContext *tc);
74 static void sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see = true);
75 static void sp_text_context_update_text_selection(SPTextContext *tc);
76 static gint sp_text_context_timeout(SPTextContext *tc);
77 static void sp_text_context_forget_text(SPTextContext *tc);
79 static gint sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc);
80 static gint sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc);
81 static void sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc);
83 static SPEventContextClass *parent_class;
85 GType
86 sp_text_context_get_type()
87 {
88     static GType type = 0;
89     if (!type) {
90         GTypeInfo info = {
91             sizeof(SPTextContextClass),
92             NULL, NULL,
93             (GClassInitFunc) sp_text_context_class_init,
94             NULL, NULL,
95             sizeof(SPTextContext),
96             4,
97             (GInstanceInitFunc) sp_text_context_init,
98             NULL,   /* value_table */
99         };
100         type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPTextContext", &info, (GTypeFlags)0);
101     }
102     return type;
105 static void
106 sp_text_context_class_init(SPTextContextClass *klass)
108     GObjectClass *object_class=(GObjectClass *)klass;
109     SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
111     parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
113     object_class->dispose = sp_text_context_dispose;
115     event_context_class->setup = sp_text_context_setup;
116     event_context_class->finish = sp_text_context_finish;
117     event_context_class->root_handler = sp_text_context_root_handler;
118     event_context_class->item_handler = sp_text_context_item_handler;
121 static void
122 sp_text_context_init(SPTextContext *tc)
124     SPEventContext *event_context = SP_EVENT_CONTEXT(tc);
126     event_context->cursor_shape = cursor_text_xpm;
127     event_context->hot_x = 7;
128     event_context->hot_y = 7;
130     event_context->xp = 0;
131     event_context->yp = 0;
132     event_context->tolerance = 0;
133     event_context->within_tolerance = false;
135     tc->imc = NULL;
137     tc->text = NULL;
138     tc->pdoc = Geom::Point(0, 0);
139     new (&tc->text_sel_start) Inkscape::Text::Layout::iterator();
140     new (&tc->text_sel_end) Inkscape::Text::Layout::iterator();
141     new (&tc->text_selection_quads) std::vector<SPCanvasItem*>();
143     tc->unimode = false;
145     tc->cursor = NULL;
146     tc->indicator = NULL;
147     tc->frame = NULL;
148     tc->grabbed = NULL;
149     tc->timeout = 0;
150     tc->show = FALSE;
151     tc->phase = 0;
152     tc->nascent_object = 0;
153     tc->over_text = 0;
154     tc->dragging = 0;
155     tc->creating = 0;
157     new (&tc->sel_changed_connection) sigc::connection();
158     new (&tc->sel_modified_connection) sigc::connection();
159     new (&tc->style_set_connection) sigc::connection();
160     new (&tc->style_query_connection) sigc::connection();
163 static void
164 sp_text_context_dispose(GObject *obj)
166     SPTextContext *tc = SP_TEXT_CONTEXT(obj);
167     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
168     tc->style_query_connection.~connection();
169     tc->style_set_connection.~connection();
170     tc->sel_changed_connection.~connection();
171     tc->sel_modified_connection.~connection();
173     delete ec->shape_editor;
174     ec->shape_editor = NULL;
176     tc->text_sel_end.~iterator();
177     tc->text_sel_start.~iterator();
178     tc->text_selection_quads.~vector();
179     if (G_OBJECT_CLASS(parent_class)->dispose) {
180         G_OBJECT_CLASS(parent_class)->dispose(obj);
181     }
182     if (tc->grabbed) {
183         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
184         tc->grabbed = NULL;
185     }
187     Inkscape::Rubberband::get(ec->desktop)->stop();
190 static void
191 sp_text_context_setup(SPEventContext *ec)
193     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
194     SPDesktop *desktop = ec->desktop;
195     GtkSettings* settings = gtk_settings_get_default();
196     gint timeout = 0;
197     g_object_get( settings, "gtk-cursor-blink-time", &timeout, NULL );
198     if (timeout < 0) {
199         timeout = 200;
200     } else {
201         timeout /= 2;
202     }
204     tc->cursor = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLLINE, NULL);
205     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), 100, 0, 100, 100);
206     sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
207     sp_canvas_item_hide(tc->cursor);
209     tc->indicator = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
210     SP_CTRLRECT(tc->indicator)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100)));
211     SP_CTRLRECT(tc->indicator)->setColor(0x0000ff7f, false, 0);
212     sp_canvas_item_hide(tc->indicator);
214     tc->frame = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
215     SP_CTRLRECT(tc->frame)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100)));
216     SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0);
217     sp_canvas_item_hide(tc->frame);
219     tc->timeout = gtk_timeout_add(timeout, (GtkFunction) sp_text_context_timeout, ec);
221     tc->imc = gtk_im_multicontext_new();
222     if (tc->imc) {
223         GtkWidget *canvas = GTK_WIDGET(sp_desktop_canvas(desktop));
225         /* im preedit handling is very broken in inkscape for
226          * multi-byte characters.  See bug 1086769.
227          * We need to let the IM handle the preediting, and
228          * just take in the characters when they're finished being
229          * entered.
230          */
231         gtk_im_context_set_use_preedit(tc->imc, FALSE);
232         gtk_im_context_set_client_window(tc->imc, canvas->window);
234         g_signal_connect(G_OBJECT(canvas), "focus_in_event", G_CALLBACK(sptc_focus_in), tc);
235         g_signal_connect(G_OBJECT(canvas), "focus_out_event", G_CALLBACK(sptc_focus_out), tc);
236         g_signal_connect(G_OBJECT(tc->imc), "commit", G_CALLBACK(sptc_commit), tc);
238         if (GTK_WIDGET_HAS_FOCUS(canvas)) {
239             sptc_focus_in(canvas, NULL, tc);
240         }
241     }
243     if (((SPEventContextClass *) parent_class)->setup)
244         ((SPEventContextClass *) parent_class)->setup(ec);
246     ec->shape_editor = new ShapeEditor(ec->desktop);
248     SPItem *item = sp_desktop_selection(ec->desktop)->singleItem();
249     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
250         ec->shape_editor->set_item(item, SH_KNOTHOLDER);
251     }
253     tc->sel_changed_connection = sp_desktop_selection(desktop)->connectChanged(
254         sigc::bind(sigc::ptr_fun(&sp_text_context_selection_changed), tc)
255         );
256     tc->sel_modified_connection = sp_desktop_selection(desktop)->connectModified(
257         sigc::bind(sigc::ptr_fun(&sp_text_context_selection_modified), tc)
258         );
259     tc->style_set_connection = desktop->connectSetStyle(
260         sigc::bind(sigc::ptr_fun(&sp_text_context_style_set), tc)
261         );
262     tc->style_query_connection = desktop->connectQueryStyle(
263         sigc::bind(sigc::ptr_fun(&sp_text_context_style_query), tc)
264         );
266     sp_text_context_selection_changed(sp_desktop_selection(desktop), tc);
268     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
269     if (prefs->getBool("/tools/text/selcue")) {
270         ec->enableSelectionCue();
271     }
272     if (prefs->getBool("/tools/text/gradientdrag")) {
273         ec->enableGrDrag();
274     }
277 static void
278 sp_text_context_finish(SPEventContext *ec)
280     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
282     if (ec->desktop) {
283         sp_signal_disconnect_by_data(sp_desktop_canvas(ec->desktop), tc);
284     }
286     ec->enableGrDrag(false);
288     tc->style_set_connection.disconnect();
289     tc->style_query_connection.disconnect();
290     tc->sel_changed_connection.disconnect();
291     tc->sel_modified_connection.disconnect();
293     sp_text_context_forget_text(SP_TEXT_CONTEXT(ec));
295     if (tc->imc) {
296         g_object_unref(G_OBJECT(tc->imc));
297         tc->imc = NULL;
298     }
300     if (tc->timeout) {
301         gtk_timeout_remove(tc->timeout);
302         tc->timeout = 0;
303     }
305     if (tc->cursor) {
306         gtk_object_destroy(GTK_OBJECT(tc->cursor));
307         tc->cursor = NULL;
308     }
310     if (tc->indicator) {
311         gtk_object_destroy(GTK_OBJECT(tc->indicator));
312         tc->indicator = NULL;
313     }
315     if (tc->frame) {
316         gtk_object_destroy(GTK_OBJECT(tc->frame));
317         tc->frame = NULL;
318     }
320     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ;
321          it != tc->text_selection_quads.end() ; ++it) {
322         sp_canvas_item_hide(*it);
323         gtk_object_destroy(*it);
324     }
325     tc->text_selection_quads.clear();
329 static gint
330 sp_text_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
332     SPTextContext *tc = SP_TEXT_CONTEXT(event_context);
333     SPDesktop *desktop = event_context->desktop;
334     SPItem *item_ungrouped;
336     gint ret = FALSE;
338     sp_text_context_validate_cursor_iterators(tc);
340     switch (event->type) {
341         case GDK_BUTTON_PRESS:
342             if (event->button.button == 1 && !event_context->space_panning) {
343                 // find out clicked item, disregarding groups
344                 item_ungrouped = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE);
345                 if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
346                     sp_desktop_selection(desktop)->set(item_ungrouped);
347                     if (tc->text) {
348                         // find out click point in document coordinates
349                         Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
350                         // set the cursor closest to that point
351                         tc->text_sel_start = tc->text_sel_end = sp_te_get_position_by_coords(tc->text, p);
352                         // update display
353                         sp_text_context_update_cursor(tc);
354                         sp_text_context_update_text_selection(tc);
355                         tc->dragging = 1;
356                     }
357                     ret = TRUE;
358                 }
359             }
360             break;
361         case GDK_2BUTTON_PRESS:
362             if (event->button.button == 1 && tc->text) {
363                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
364                 if (layout) {
365                     if (!layout->isStartOfWord(tc->text_sel_start))
366                         tc->text_sel_start.prevStartOfWord();
367                     if (!layout->isEndOfWord(tc->text_sel_end))
368                         tc->text_sel_end.nextEndOfWord();
369                     sp_text_context_update_cursor(tc);
370                     sp_text_context_update_text_selection(tc);
371                     tc->dragging = 2;
372                     ret = TRUE;
373                 }
374             }
375             break;
376         case GDK_3BUTTON_PRESS:
377             if (event->button.button == 1 && tc->text) {
378                 tc->text_sel_start.thisStartOfLine();
379                 tc->text_sel_end.thisEndOfLine();
380                 sp_text_context_update_cursor(tc);
381                 sp_text_context_update_text_selection(tc);
382                 tc->dragging = 3;
383                 ret = TRUE;
384             }
385             break;
386         case GDK_BUTTON_RELEASE:
387             if (event->button.button == 1 && tc->dragging && !event_context->space_panning) {
388                 tc->dragging = 0;
389                 sp_event_context_discard_delayed_snap_event(event_context);
390                 ret = TRUE;
391             }
392             break;
393         case GDK_MOTION_NOTIFY:
394             if (event->motion.state & GDK_BUTTON1_MASK && tc->dragging && !event_context->space_panning) {
395                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
396                 if (!layout) break;
397                 // find out click point in document coordinates
398                 Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
399                 // set the cursor closest to that point
400                 Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(tc->text, p);
401                 if (tc->dragging == 2) {
402                     // double-click dragging: go by word
403                     if (new_end < tc->text_sel_start) {
404                         if (!layout->isStartOfWord(new_end))
405                             new_end.prevStartOfWord();
406                     } else
407                         if (!layout->isEndOfWord(new_end))
408                             new_end.nextEndOfWord();
409                 } else if (tc->dragging == 3) {
410                     // triple-click dragging: go by line
411                     if (new_end < tc->text_sel_start)
412                         new_end.thisStartOfLine();
413                     else
414                         new_end.thisEndOfLine();
415                 }
416                 // update display
417                 if (tc->text_sel_end != new_end) {
418                     tc->text_sel_end = new_end;
419                     sp_text_context_update_cursor(tc);
420                     sp_text_context_update_text_selection(tc);
421                 }
422                 gobble_motion_events(GDK_BUTTON1_MASK);
423                 ret = TRUE;
424                 break;
425             }
426             // find out item under mouse, disregarding groups
427             item_ungrouped = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE);
428             if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
430                 Inkscape::Text::Layout const *layout = te_get_layout(item_ungrouped);
431                 if (layout->inputTruncated()) {
432                     SP_CTRLRECT(tc->indicator)->setColor(0xff0000ff, false, 0);
433                 } else {
434                     SP_CTRLRECT(tc->indicator)->setColor(0x0000ff7f, false, 0);
435                 }
436                 Geom::OptRect ibbox = item_ungrouped->getBboxDesktop();
437                 if (ibbox) {
438                     SP_CTRLRECT(tc->indicator)->setRectangle(*ibbox);
439                 }
440                 sp_canvas_item_show(tc->indicator);
442                 event_context->cursor_shape = cursor_text_insert_xpm;
443                 event_context->hot_x = 7;
444                 event_context->hot_y = 10;
445                 sp_event_context_update_cursor(event_context);
446                 sp_text_context_update_text_selection(tc);
448                 if (SP_IS_TEXT (item_ungrouped)) {
449                     desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
450                 } else {
451                     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."));
452                 }
454                 tc->over_text = true;
456                 ret = TRUE;
457             }
458             break;
459         default:
460             break;
461     }
463     if (!ret) {
464         if (((SPEventContextClass *) parent_class)->item_handler)
465             ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
466     }
468     return ret;
471 static void
472 sp_text_context_setup_text(SPTextContext *tc)
474     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
476     /* Create <text> */
477     Inkscape::XML::Document *xml_doc = SP_EVENT_CONTEXT_DESKTOP(ec)->doc()->getReprDoc();
478     Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
479     rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
481     /* Set style */
482     sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "/tools/text", true);
484     sp_repr_set_svg_double(rtext, "x", tc->pdoc[Geom::X]);
485     sp_repr_set_svg_double(rtext, "y", tc->pdoc[Geom::Y]);
487     /* Create <tspan> */
488     Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
489     rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
490     rtext->addChild(rtspan, NULL);
491     Inkscape::GC::release(rtspan);
493     /* Create TEXT */
494     Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
495     rtspan->addChild(rstring, NULL);
496     Inkscape::GC::release(rstring);
497     SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
498     /* fixme: Is selection::changed really immediate? */
499     /* yes, it's immediate .. why does it matter? */
500     sp_desktop_selection(ec->desktop)->set(text_item);
501     Inkscape::GC::release(rtext);
502     text_item->transform = SP_ITEM(ec->desktop->currentLayer())->i2doc_affine().inverse();
504     text_item->updateRepr();
505     DocumentUndo::done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
506                        _("Create text"));
509 /**
510  * Insert the character indicated by tc.uni to replace the current selection,
511  * and reset tc.uni/tc.unipos to empty string.
512  *
513  * \pre tc.uni/tc.unipos non-empty.
514  */
515 static void
516 insert_uni_char(SPTextContext *const tc)
518     g_return_if_fail(tc->unipos
519                      && tc->unipos < sizeof(tc->uni)
520                      && tc->uni[tc->unipos] == '\0');
521     unsigned int uv;
522     sscanf(tc->uni, "%x", &uv);
523     tc->unipos = 0;
524     tc->uni[tc->unipos] = '\0';
526     if ( !g_unichar_isprint(static_cast<gunichar>(uv))
527          && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
528         // This may be due to bad input, so it goes to statusbar.
529         tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
530                                            _("Non-printable character"));
531     } else {
532         if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
533             sp_text_context_setup_text(tc);
534             tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
535         }
537         gchar u[10];
538         guint const len = g_unichar_to_utf8(uv, u);
539         u[len] = '\0';
541         tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
542         sp_text_context_update_cursor(tc);
543         sp_text_context_update_text_selection(tc);
544         DocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM,
545                            _("Insert Unicode character"));
546     }
549 static void
550 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
552     unsigned int uv;
553     sscanf(hex, "%x", &uv);
554     if (!g_unichar_isprint((gunichar) uv)) {
555         uv = 0xfffd;
556     }
557     guint const len = g_unichar_to_utf8(uv, utf8);
558     utf8[len] = '\0';
561 static void
562 show_curr_uni_char(SPTextContext *const tc)
564     g_return_if_fail(tc->unipos < sizeof(tc->uni)
565                      && tc->uni[tc->unipos] == '\0');
566     if (tc->unipos) {
567         char utf8[10];
568         hex_to_printable_utf8_buf(tc->uni, utf8);
570         /* Status bar messages are in pango markup, so we need xml escaping. */
571         if (utf8[1] == '\0') {
572             switch(utf8[0]) {
573                 case '<': strcpy(utf8, "&lt;"); break;
574                 case '>': strcpy(utf8, "&gt;"); break;
575                 case '&': strcpy(utf8, "&amp;"); break;
576                 default: break;
577             }
578         }
579         tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
580                                           _("Unicode (<b>Enter</b> to finish): %s: %s"), tc->uni, utf8);
581     } else {
582         tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
583     }
586 static gint
587 sp_text_context_root_handler(SPEventContext *const event_context, GdkEvent *const event)
589     SPTextContext *const tc = SP_TEXT_CONTEXT(event_context);
591     SPDesktop *desktop = event_context->desktop;
593     sp_canvas_item_hide(tc->indicator);
595     sp_text_context_validate_cursor_iterators(tc);
597     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
598     event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
600     switch (event->type) {
601         case GDK_BUTTON_PRESS:
602             if (event->button.button == 1 && !event_context->space_panning) {
604                 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
605                     return TRUE;
606                 }
608                 // save drag origin
609                 event_context->xp = (gint) event->button.x;
610                 event_context->yp = (gint) event->button.y;
611                 event_context->within_tolerance = true;
613                 Geom::Point const button_pt(event->button.x, event->button.y);
614                 Geom::Point button_dt(desktop->w2d(button_pt));
616                 SnapManager &m = desktop->namedview->snap_manager;
617                 m.setup(desktop);
618                 m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE);
619                 m.unSetup();
621                 tc->p0 = button_dt;
622                 Inkscape::Rubberband::get(desktop)->start(desktop, tc->p0);
623                 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
624                                     GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
625                                     NULL, event->button.time);
626                 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
627                 tc->creating = 1;
629                 /* Processed */
630                 return TRUE;
631             }
632             break;
633         case GDK_MOTION_NOTIFY:
634             if (tc->over_text) {
635                 tc->over_text = 0;
636                 // update cursor and statusbar: we are not over a text object now
637                 event_context->cursor_shape = cursor_text_xpm;
638                 event_context->hot_x = 7;
639                 event_context->hot_y = 7;
640                 sp_event_context_update_cursor(event_context);
641                 desktop->event_context->defaultMessageContext()->clear();
642             }
644             if (tc->creating && event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
645                 if ( event_context->within_tolerance
646                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
647                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
648                     break; // do not drag if we're within tolerance from origin
649                 }
650                 // Once the user has moved farther than tolerance from the original location
651                 // (indicating they intend to draw, not click), then always process the
652                 // motion notify coordinates as given (no snapping back to origin)
653                 event_context->within_tolerance = false;
655                 Geom::Point const motion_pt(event->motion.x, event->motion.y);
656                 Geom::Point p = desktop->w2d(motion_pt);
658                 SnapManager &m = desktop->namedview->snap_manager;
659                 m.setup(desktop);
660                 m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE);
661                 m.unSetup();
663                 Inkscape::Rubberband::get(desktop)->move(p);
664                 gobble_motion_events(GDK_BUTTON1_MASK);
666                 // status text
667                 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::X]), desktop->namedview->getDefaultMetric());
668                 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::Y]), desktop->namedview->getDefaultMetric());
669                 event_context->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Flowed text frame</b>: %s &#215; %s"), xs->str, ys->str);
670                 g_string_free(xs, FALSE);
671                 g_string_free(ys, FALSE);
673             } else if (!sp_event_context_knot_mouseover(event_context)) {
674                 SnapManager &m = desktop->namedview->snap_manager;
675                 m.setup(desktop);
677                 Geom::Point const motion_w(event->motion.x, event->motion.y);
678                 Geom::Point motion_dt(desktop->w2d(motion_w));
679                 m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE));
680                 m.unSetup();
681             }
682             break;
683         case GDK_BUTTON_RELEASE:
684             if (event->button.button == 1 && !event_context->space_panning) {
685                 sp_event_context_discard_delayed_snap_event(event_context);
687                 Geom::Point p1 = desktop->w2d(Geom::Point(event->button.x, event->button.y));
689                 SnapManager &m = desktop->namedview->snap_manager;
690                 m.setup(desktop);
691                 m.freeSnapReturnByRef(p1, Inkscape::SNAPSOURCE_NODE_HANDLE);
692                 m.unSetup();
694                 if (tc->grabbed) {
695                     sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
696                     tc->grabbed = NULL;
697                 }
699                 Inkscape::Rubberband::get(desktop)->stop();
701                 if (tc->creating && event_context->within_tolerance) {
702                     /* Button 1, set X & Y & new item */
703                     sp_desktop_selection(desktop)->clear();
704                     tc->pdoc = desktop->dt2doc(p1);
705                     tc->show = TRUE;
706                     tc->phase = 1;
707                     tc->nascent_object = 1; // new object was just created
709                     /* Cursor */
710                     sp_canvas_item_show(tc->cursor);
711                     // Cursor height is defined by the new text object's font size; it needs to be set
712                     // artificially here, for the text object does not exist yet:
713                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
714                     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), p1, p1 + Geom::Point(0, cursor_height));
715                     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
717                     event_context->within_tolerance = false;
718                 } else if (tc->creating) {
719                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
720                     if (fabs(p1[Geom::Y] - tc->p0[Geom::Y]) > cursor_height) {
721                         // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
722                         SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
723                         /* Set style */
724                         sp_desktop_apply_style_tool(desktop, SP_OBJECT_REPR(ft), "/tools/text", true);
725                         sp_desktop_selection(desktop)->set(ft);
726                         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
727                         DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
728                                            _("Create flowed text"));
729                     } else {
730                         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
731                     }
732                 }
733                 tc->creating = false;
734                 return TRUE;
735             }
736             break;
737         case GDK_KEY_PRESS: {
738             guint const group0_keyval = get_group0_keyval(&event->key);
740             if (group0_keyval == GDK_KP_Add ||
741                 group0_keyval == GDK_KP_Subtract) {
742                 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
743                     break; // otherwise pass on keypad +/- so they can zoom
744             }
746             if ((tc->text) || (tc->nascent_object)) {
747                 // there is an active text object in this context, or a new object was just created
749                 if (tc->unimode || !tc->imc
750                     || (MOD__CTRL && MOD__SHIFT)    // input methods tend to steal this for unimode,
751                                                     // but we have our own so make sure they don't swallow it
752                     || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
753                     //IM did not consume the key, or we're in unimode
755                         if (!MOD__CTRL_ONLY && tc->unimode) {
756                             /* TODO: ISO 14755 (section 3 Definitions) says that we should also
757                                accept the first 6 characters of alphabets other than the latin
758                                alphabet "if the Latin alphabet is not used".  The below is also
759                                reasonable (viz. hope that the user's keyboard includes latin
760                                characters and force latin interpretation -- just as we do for our
761                                keyboard shortcuts), but differs from the ISO 14755
762                                recommendation. */
763                             switch (group0_keyval) {
764                                 case GDK_space:
765                                 case GDK_KP_Space: {
766                                     if (tc->unipos) {
767                                         insert_uni_char(tc);
768                                     }
769                                     /* Stay in unimode. */
770                                     show_curr_uni_char(tc);
771                                     return TRUE;
772                                 }
774                                 case GDK_BackSpace: {
775                                     g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
776                                     if (tc->unipos) {
777                                         tc->uni[--tc->unipos] = '\0';
778                                     }
779                                     show_curr_uni_char(tc);
780                                     return TRUE;
781                                 }
783                                 case GDK_Return:
784                                 case GDK_KP_Enter: {
785                                     if (tc->unipos) {
786                                         insert_uni_char(tc);
787                                     }
788                                     /* Exit unimode. */
789                                     tc->unimode = false;
790                                     event_context->defaultMessageContext()->clear();
791                                     return TRUE;
792                                 }
794                                 case GDK_Escape: {
795                                     // Cancel unimode.
796                                     tc->unimode = false;
797                                     gtk_im_context_reset(tc->imc);
798                                     event_context->defaultMessageContext()->clear();
799                                     return TRUE;
800                                 }
802                                 case GDK_Shift_L:
803                                 case GDK_Shift_R:
804                                     break;
806                                 default: {
807                                     if (g_ascii_isxdigit(group0_keyval)) {
808                                         g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
809                                         tc->uni[tc->unipos++] = group0_keyval;
810                                         tc->uni[tc->unipos] = '\0';
811                                         if (tc->unipos == 8) {
812                                             /* This behaviour is partly to allow us to continue to
813                                                use a fixed-length buffer for tc->uni.  Reason for
814                                                choosing the number 8 is that it's the length of
815                                                ``canonical form'' mentioned in the ISO 14755 spec.
816                                                An advantage over choosing 6 is that it allows using
817                                                backspace for typos & misremembering when entering a
818                                                6-digit number. */
819                                             insert_uni_char(tc);
820                                         }
821                                         show_curr_uni_char(tc);
822                                         return TRUE;
823                                     } else {
824                                         /* The intent is to ignore but consume characters that could be
825                                            typos for hex digits.  Gtk seems to ignore & consume all
826                                            non-hex-digits, and we do similar here.  Though note that some
827                                            shortcuts (like keypad +/- for zoom) get processed before
828                                            reaching this code. */
829                                         return TRUE;
830                                     }
831                                 }
832                             }
833                         }
835                         Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
836                         Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
837                         bool cursor_moved = false;
838                         int screenlines = 1;
839                         if (tc->text) {
840                             double spacing = sp_te_get_average_linespacing(tc->text);
841                             Geom::Rect const d = desktop->get_display_area();
842                             screenlines = (int) floor(fabs(d.min()[Geom::Y] - d.max()[Geom::Y])/spacing) - 1;
843                             if (screenlines <= 0)
844                                 screenlines = 1;
845                         }
847                         /* Neither unimode nor IM consumed key; process text tool shortcuts */
848                         switch (group0_keyval) {
849                             case GDK_x:
850                             case GDK_X:
851                                 if (MOD__ALT_ONLY) {
852                                     desktop->setToolboxFocusTo ("altx-text");
853                                     return TRUE;
854                                 }
855                                 break;
856                             case GDK_space:
857                                 if (MOD__CTRL_ONLY) {
858                                     /* No-break space */
859                                     if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
860                                         sp_text_context_setup_text(tc);
861                                         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
862                                     }
863                                     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
864                                     sp_text_context_update_cursor(tc);
865                                     sp_text_context_update_text_selection(tc);
866                                     desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
867                                     DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
868                                                        _("Insert no-break space"));
869                                     return TRUE;
870                                 }
871                                 break;
872                             case GDK_U:
873                             case GDK_u:
874                                 if (MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT)) {
875                                     if (tc->unimode) {
876                                         tc->unimode = false;
877                                         event_context->defaultMessageContext()->clear();
878                                     } else {
879                                         tc->unimode = true;
880                                         tc->unipos = 0;
881                                         event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
882                                     }
883                                     if (tc->imc) {
884                                         gtk_im_context_reset(tc->imc);
885                                     }
886                                     return TRUE;
887                                 }
888                                 break;
889                             case GDK_B:
890                             case GDK_b:
891                                 if (MOD__CTRL_ONLY && tc->text) {
892                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
893                                     SPCSSAttr *css = sp_repr_css_attr_new();
894                                     if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
895                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
896                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
897                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
898                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
899                                         sp_repr_css_set_property(css, "font-weight", "bold");
900                                     else
901                                         sp_repr_css_set_property(css, "font-weight", "normal");
902                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
903                                     sp_repr_css_attr_unref(css);
904                                     DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
905                                                        _("Make bold"));
906                                     sp_text_context_update_cursor(tc);
907                                     sp_text_context_update_text_selection(tc);
908                                     return TRUE;
909                                 }
910                                 break;
911                             case GDK_I:
912                             case GDK_i:
913                                 if (MOD__CTRL_ONLY && tc->text) {
914                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
915                                     SPCSSAttr *css = sp_repr_css_attr_new();
916                                     if (style->font_style.computed != SP_CSS_FONT_STYLE_NORMAL)
917                                         sp_repr_css_set_property(css, "font-style", "normal");
918                                     else
919                                         sp_repr_css_set_property(css, "font-style", "italic");
920                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
921                                     sp_repr_css_attr_unref(css);
922                                     DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
923                                                        _("Make italic"));
924                                     sp_text_context_update_cursor(tc);
925                                     sp_text_context_update_text_selection(tc);
926                                     return TRUE;
927                                 }
928                                 break;
930                             case GDK_A:
931                             case GDK_a:
932                                 if (MOD__CTRL_ONLY && tc->text) {
933                                     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
934                                     if (layout) {
935                                         tc->text_sel_start = layout->begin();
936                                         tc->text_sel_end = layout->end();
937                                         sp_text_context_update_cursor(tc);
938                                         sp_text_context_update_text_selection(tc);
939                                         return TRUE;
940                                     }
941                                 }
942                                 break;
944                             case GDK_Return:
945                             case GDK_KP_Enter:
946                             {
947                                 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
948                                     sp_text_context_setup_text(tc);
949                                     tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
950                                 }
952                                 iterator_pair enter_pair;
953                                 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, enter_pair);
954                                 (void)success; // TODO cleanup
955                                 tc->text_sel_start = tc->text_sel_end = enter_pair.first;
957                                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
959                                 sp_text_context_update_cursor(tc);
960                                 sp_text_context_update_text_selection(tc);
961                                 DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
962                                                    _("New line"));
963                                 return TRUE;
964                             }
965                             case GDK_BackSpace:
966                                 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
968                                     bool noSelection = false;
970                                         if (tc->text_sel_start == tc->text_sel_end) {
971                                         tc->text_sel_start.prevCursorPosition();
972                                         noSelection = true;
973                                     }
975                                         iterator_pair bspace_pair;
976                                         bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, bspace_pair);
978                                     if (noSelection) {
979                                         if (success) {
980                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
981                                         } else { // nothing deleted
982                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.second;
983                                         }
984                                     } else {
985                                         if (success) {
986                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
987                                         } else { // nothing deleted
988                                             tc->text_sel_start = bspace_pair.first;
989                                             tc->text_sel_end = bspace_pair.second;
990                                         }
991                                     }
993                                     sp_text_context_update_cursor(tc);
994                                     sp_text_context_update_text_selection(tc);
995                                     DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
996                                                        _("Backspace"));
997                                 }
998                                 return TRUE;
999                             case GDK_Delete:
1000                             case GDK_KP_Delete:
1001                                 if (tc->text) {
1002                                     bool noSelection = false;
1004                                     if (tc->text_sel_start == tc->text_sel_end) {
1005                                         tc->text_sel_end.nextCursorPosition();
1006                                         noSelection = true;
1007                                     }
1009                                     iterator_pair del_pair;
1010                                     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, del_pair);
1012                                     if (noSelection) {
1013                                         tc->text_sel_start = tc->text_sel_end = del_pair.first;
1014                                     } else {
1015                                         if (success) {
1016                                             tc->text_sel_start = tc->text_sel_end = del_pair.first;
1017                                         } else { // nothing deleted
1018                                             tc->text_sel_start = del_pair.first;
1019                                             tc->text_sel_end = del_pair.second;
1020                                         }
1021                                     }
1024                                     sp_text_context_update_cursor(tc);
1025                                     sp_text_context_update_text_selection(tc);
1026                                     DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
1027                                                        _("Delete"));
1028                                 }
1029                                 return TRUE;
1030                             case GDK_Left:
1031                             case GDK_KP_Left:
1032                             case GDK_KP_4:
1033                                 if (tc->text) {
1034                                     if (MOD__ALT) {
1035                                         gint mul = 1 + gobble_key_events(
1036                                             get_group0_keyval(&event->key), 0); // with any mask
1037                                         if (MOD__SHIFT)
1038                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-10, 0));
1039                                         else
1040                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-1, 0));
1041                                         sp_text_context_update_cursor(tc);
1042                                         sp_text_context_update_text_selection(tc);
1043                                         DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT,
1044                                                                 _("Kern to the left"));
1045                                     } else {
1046                                         if (MOD__CTRL)
1047                                             tc->text_sel_end.cursorLeftWithControl();
1048                                         else
1049                                             tc->text_sel_end.cursorLeft();
1050                                         cursor_moved = true;
1051                                         break;
1052                                     }
1053                                 }
1054                                 return TRUE;
1055                             case GDK_Right:
1056                             case GDK_KP_Right:
1057                             case GDK_KP_6:
1058                                 if (tc->text) {
1059                                     if (MOD__ALT) {
1060                                         gint mul = 1 + gobble_key_events(
1061                                             get_group0_keyval(&event->key), 0); // with any mask
1062                                         if (MOD__SHIFT)
1063                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*10, 0));
1064                                         else
1065                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*1, 0));
1066                                         sp_text_context_update_cursor(tc);
1067                                         sp_text_context_update_text_selection(tc);
1068                                         DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT,
1069                                                                 _("Kern to the right"));
1070                                     } else {
1071                                         if (MOD__CTRL)
1072                                             tc->text_sel_end.cursorRightWithControl();
1073                                         else
1074                                             tc->text_sel_end.cursorRight();
1075                                         cursor_moved = true;
1076                                         break;
1077                                     }
1078                                 }
1079                                 return TRUE;
1080                             case GDK_Up:
1081                             case GDK_KP_Up:
1082                             case GDK_KP_8:
1083                                 if (tc->text) {
1084                                     if (MOD__ALT) {
1085                                         gint mul = 1 + gobble_key_events(
1086                                             get_group0_keyval(&event->key), 0); // with any mask
1087                                         if (MOD__SHIFT)
1088                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-10));
1089                                         else
1090                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-1));
1091                                         sp_text_context_update_cursor(tc);
1092                                         sp_text_context_update_text_selection(tc);
1093                                         DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT,
1094                                                                 _("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                                         DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT,
1119                                                                 _("Kern down"));
1120                                     } else {
1121                                         if (MOD__CTRL)
1122                                             tc->text_sel_end.cursorDownWithControl();
1123                                         else
1124                                             tc->text_sel_end.cursorDown();
1125                                         cursor_moved = true;
1126                                         break;
1127                                     }
1128                                 }
1129                                 return TRUE;
1130                             case GDK_Home:
1131                             case GDK_KP_Home:
1132                                 if (tc->text) {
1133                                     if (MOD__CTRL)
1134                                         tc->text_sel_end.thisStartOfShape();
1135                                     else
1136                                         tc->text_sel_end.thisStartOfLine();
1137                                     cursor_moved = true;
1138                                     break;
1139                                 }
1140                                 return TRUE;
1141                             case GDK_End:
1142                             case GDK_KP_End:
1143                                 if (tc->text) {
1144                                     if (MOD__CTRL)
1145                                         tc->text_sel_end.nextStartOfShape();
1146                                     else
1147                                         tc->text_sel_end.thisEndOfLine();
1148                                     cursor_moved = true;
1149                                     break;
1150                                 }
1151                                 return TRUE;
1152                             case GDK_Page_Down:
1153                             case GDK_KP_Page_Down:
1154                                 if (tc->text) {
1155                                     tc->text_sel_end.cursorDown(screenlines);
1156                                     cursor_moved = true;
1157                                     break;
1158                                 }
1159                                 return TRUE;
1160                             case GDK_Page_Up:
1161                             case GDK_KP_Page_Up:
1162                                 if (tc->text) {
1163                                     tc->text_sel_end.cursorUp(screenlines);
1164                                     cursor_moved = true;
1165                                     break;
1166                                 }
1167                                 return TRUE;
1168                             case GDK_Escape:
1169                                 if (tc->creating) {
1170                                     tc->creating = 0;
1171                                     if (tc->grabbed) {
1172                                         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1173                                         tc->grabbed = NULL;
1174                                     }
1175                                     Inkscape::Rubberband::get(desktop)->stop();
1176                                 } else {
1177                                     sp_desktop_selection(desktop)->clear();
1178                                 }
1179                                 tc->nascent_object = FALSE;
1180                                 return TRUE;
1181                             case GDK_bracketleft:
1182                                 if (tc->text) {
1183                                     if (MOD__ALT || MOD__CTRL) {
1184                                         if (MOD__ALT) {
1185                                             if (MOD__SHIFT) {
1186                                                 // FIXME: alt+shift+[] does not work, don't know why
1187                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1188                                             } else {
1189                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1190                                             }
1191                                         } else {
1192                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -90);
1193                                         }
1194                                         DocumentUndo::maybeDone(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT,
1195                                                                 _("Rotate counterclockwise"));
1196                                         sp_text_context_update_cursor(tc);
1197                                         sp_text_context_update_text_selection(tc);
1198                                         return TRUE;
1199                                     }
1200                                 }
1201                                 break;
1202                             case GDK_bracketright:
1203                                 if (tc->text) {
1204                                     if (MOD__ALT || MOD__CTRL) {
1205                                         if (MOD__ALT) {
1206                                             if (MOD__SHIFT) {
1207                                                 // FIXME: alt+shift+[] does not work, don't know why
1208                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1209                                             } else {
1210                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1211                                             }
1212                                         } else {
1213                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 90);
1214                                         }
1215                                         DocumentUndo::maybeDone(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT,
1216                                                                 _("Rotate clockwise"));
1217                                         sp_text_context_update_cursor(tc);
1218                                         sp_text_context_update_text_selection(tc);
1219                                         return TRUE;
1220                                     }
1221                                 }
1222                                 break;
1223                             case GDK_less:
1224                             case GDK_comma:
1225                                 if (tc->text) {
1226                                     if (MOD__ALT) {
1227                                         if (MOD__CTRL) {
1228                                             if (MOD__SHIFT)
1229                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1230                                             else
1231                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1232                                             DocumentUndo::maybeDone(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT,
1233                                                                     _("Contract line spacing"));
1234                                         } else {
1235                                             if (MOD__SHIFT)
1236                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1237                                             else
1238                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1239                                             DocumentUndo::maybeDone(sp_desktop_document(desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT,
1240                                                                     _("Contract letter spacing"));
1241                                         }
1242                                         sp_text_context_update_cursor(tc);
1243                                         sp_text_context_update_text_selection(tc);
1244                                         return TRUE;
1245                                     }
1246                                 }
1247                                 break;
1248                             case GDK_greater:
1249                             case GDK_period:
1250                                 if (tc->text) {
1251                                     if (MOD__ALT) {
1252                                         if (MOD__CTRL) {
1253                                             if (MOD__SHIFT)
1254                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1255                                             else
1256                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1257                                             DocumentUndo::maybeDone(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT,
1258                                                                     _("Expand line spacing"));
1259                                         } else {
1260                                             if (MOD__SHIFT)
1261                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1262                                             else
1263                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1264                                             DocumentUndo::maybeDone(sp_desktop_document(desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT,
1265                                                                     _("Expand letter spacing"));\
1266                                         }
1267                                         sp_text_context_update_cursor(tc);
1268                                         sp_text_context_update_text_selection(tc);
1269                                         return TRUE;
1270                                     }
1271                                 }
1272                                 break;
1273                             default:
1274                                 break;
1275                         }
1277                         if (cursor_moved) {
1278                             if (!MOD__SHIFT)
1279                                 tc->text_sel_start = tc->text_sel_end;
1280                             if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1281                                 sp_text_context_update_cursor(tc);
1282                                 sp_text_context_update_text_selection(tc);
1283                             }
1284                             return TRUE;
1285                         }
1287                 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1288             } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1289                 // except up/down that are swallowed to prevent the zoom field from activation
1290                 if ((group0_keyval == GDK_Up    ||
1291                      group0_keyval == GDK_Down  ||
1292                      group0_keyval == GDK_KP_Up ||
1293                      group0_keyval == GDK_KP_Down )
1294                     && !MOD__CTRL_ONLY) {
1295                     return TRUE;
1296                 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1297                     if (tc->creating) {
1298                         tc->creating = 0;
1299                         if (tc->grabbed) {
1300                             sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1301                             tc->grabbed = NULL;
1302                         }
1303                         Inkscape::Rubberband::get(desktop)->stop();
1304                     }
1305                 } else if ((group0_keyval == GDK_x || group0_keyval == GDK_X) && MOD__ALT_ONLY) {
1306                     desktop->setToolboxFocusTo ("altx-text");
1307                     return TRUE;
1308                 }
1309             }
1310             break;
1311         }
1313         case GDK_KEY_RELEASE:
1314             if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1315                 return TRUE;
1316             }
1317             break;
1318         default:
1319             break;
1320     }
1322     // if nobody consumed it so far
1323     if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1324         return ((SPEventContextClass *) parent_class)->root_handler(event_context, event); // send event to parent
1325     } else {
1326         return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1327     }
1330 /**
1331  Attempts to paste system clipboard into the currently edited text, returns true on success
1332  */
1333 bool
1334 sp_text_paste_inline(SPEventContext *ec)
1336     if (!SP_IS_TEXT_CONTEXT(ec))
1337         return false;
1339     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1341     if ((tc->text) || (tc->nascent_object)) {
1342         // there is an active text object in this context, or a new object was just created
1344         Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1345         Glib::ustring const clip_text = refClipboard->wait_for_text();
1347         if (!clip_text.empty()) {
1348                 // Fix for 244940
1349                 // The XML standard defines the following as valid characters
1350                 // (Extensible Markup Language (XML) 1.0 (Fourth Edition) paragraph 2.2)
1351                 // char ::=     #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
1352                 // Since what comes in off the paste buffer will go right into XML, clean
1353                 // the text here.
1354                 Glib::ustring text(clip_text);
1355                 Glib::ustring::iterator itr = text.begin();
1356                 gunichar paste_string_uchar;
1358                 while(itr != text.end())
1359                 {
1360                     paste_string_uchar = *itr;
1362                     // Make sure we don't have a control character. We should really check
1363                     // for the whole range above... Add the rest of the invalid cases from
1364                     // above if we find additional issues
1365                     if(paste_string_uchar >= 0x00000020 ||
1366                        paste_string_uchar == 0x00000009 ||
1367                        paste_string_uchar == 0x0000000A ||
1368                        paste_string_uchar == 0x0000000D) {
1369                         itr++;
1370                     } else {
1371                         itr = text.erase(itr);
1372                     }
1373                 }
1375             if (!tc->text) { // create text if none (i.e. if nascent_object)
1376                 sp_text_context_setup_text(tc);
1377                 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1378             }
1380             // using indices is slow in ustrings. Whatever.
1381             Glib::ustring::size_type begin = 0;
1382             for ( ; ; ) {
1383                 Glib::ustring::size_type end = text.find('\n', begin);
1384                 if (end == Glib::ustring::npos) {
1385                     if (begin != text.length())
1386                         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());
1387                     break;
1388                 }
1389                 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());
1390                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1391                 begin = end + 1;
1392             }
1393             DocumentUndo::done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
1394                                _("Paste text"));
1396             return true;
1397         }
1398     } // FIXME: else create and select a new object under cursor!
1400     return false;
1403 /**
1404  Gets the raw characters that comprise the currently selected text, converting line
1405  breaks into lf characters.
1406 */
1407 Glib::ustring
1408 sp_text_get_selected_text(SPEventContext const *ec)
1410     if (!SP_IS_TEXT_CONTEXT(ec))
1411         return "";
1412     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1413     if (tc->text == NULL)
1414         return "";
1416     return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1419 SPCSSAttr *
1420 sp_text_get_style_at_cursor(SPEventContext const *ec)
1422     if (!SP_IS_TEXT_CONTEXT(ec))
1423         return NULL;
1424     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1425     if (tc->text == NULL)
1426         return NULL;
1428     SPObject const *obj = sp_te_object_at_position(tc->text, tc->text_sel_end);
1429     if (obj)
1430         return take_style_from_item((SPItem *) obj);
1431     return NULL;
1434 /**
1435  Deletes the currently selected characters. Returns false if there is no
1436  text selection currently.
1437 */
1438 bool sp_text_delete_selection(SPEventContext *ec)
1440     if (!SP_IS_TEXT_CONTEXT(ec))
1441         return false;
1442     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1443     if (tc->text == NULL)
1444         return false;
1446     if (tc->text_sel_start == tc->text_sel_end)
1447         return false;
1449     iterator_pair pair;
1450     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair);
1453     if (success) {
1454         tc->text_sel_start = tc->text_sel_end = pair.first;
1455     } else { // nothing deleted
1456         tc->text_sel_start = pair.first;
1457         tc->text_sel_end = pair.second;
1458     }
1460     sp_text_context_update_cursor(tc);
1461     sp_text_context_update_text_selection(tc);
1463     return true;
1466 /**
1467  * \param selection Should not be NULL.
1468  */
1469 static void
1470 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1472     g_assert(selection != NULL);
1474     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1476     ec->shape_editor->unset_item(SH_KNOTHOLDER);
1477     SPItem *item = selection->singleItem();
1478     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1479         ec->shape_editor->set_item(item, SH_KNOTHOLDER);
1480     }
1482     if (tc->text && (item != tc->text)) {
1483         sp_text_context_forget_text(tc);
1484     }
1485     tc->text = NULL;
1487     if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1488         tc->text = item;
1489         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1490         if (layout)
1491             tc->text_sel_start = tc->text_sel_end = layout->end();
1492     } else {
1493         tc->text = NULL;
1494     }
1496     // we update cursor without scrolling, because this position may not be final;
1497     // item_handler moves cusros to the point of click immediately
1498     sp_text_context_update_cursor(tc, false);
1499     sp_text_context_update_text_selection(tc);
1502 static void
1503 sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, SPTextContext *tc)
1505     sp_text_context_update_cursor(tc);
1506     sp_text_context_update_text_selection(tc);
1509 static bool
1510 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1512     if (tc->text == NULL)
1513         return false;
1514     if (tc->text_sel_start == tc->text_sel_end)
1515         return false;    // will get picked up by the parent and applied to the whole text object
1517     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1518     DocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1519                        _("Set text style"));
1520     sp_text_context_update_cursor(tc);
1521     sp_text_context_update_text_selection(tc);
1523     return true;
1526 static int
1527 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1529     if (tc->text == NULL)
1530         return QUERY_STYLE_NOTHING;
1531     const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1532     if (layout == NULL)
1533         return QUERY_STYLE_NOTHING;
1534     sp_text_context_validate_cursor_iterators(tc);
1536     GSList *styles_list = NULL;
1538     Inkscape::Text::Layout::iterator begin_it, end_it;
1539     if (tc->text_sel_start < tc->text_sel_end) {
1540         begin_it = tc->text_sel_start;
1541         end_it = tc->text_sel_end;
1542     } else {
1543         begin_it = tc->text_sel_end;
1544         end_it = tc->text_sel_start;
1545     }
1546     if (begin_it == end_it)
1547         if (!begin_it.prevCharacter())
1548             end_it.nextCharacter();
1549     for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1550         SPObject const *pos_obj = 0;
1551         void *rawptr = 0;
1552         layout->getSourceOfCharacter(it, &rawptr);
1553         if (!rawptr || !SP_IS_OBJECT(rawptr))
1554             continue;
1555         pos_obj = SP_OBJECT(rawptr);
1556         while (SP_IS_STRING(pos_obj) && SP_OBJECT_PARENT(pos_obj)) {
1557            pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
1558         }
1559         styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1560     }
1562     int result = sp_desktop_query_style_from_list (styles_list, style, property);
1564     g_slist_free(styles_list);
1565     return result;
1568 static void
1569 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1571     if (tc->text == NULL)
1572         return;
1573     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1574     if (layout) {     // undo can change the text length without us knowing it
1575         layout->validateIterator(&tc->text_sel_start);
1576         layout->validateIterator(&tc->text_sel_end);
1577     }
1580 static void
1581 sp_text_context_update_cursor(SPTextContext *tc,  bool scroll_to_see)
1583     GdkRectangle im_cursor = { 0, 0, 1, 1 };
1585     // due to interruptible display, tc may already be destroyed during a display update before
1586     // the cursor update (can't do both atomically, alas)
1587     if (!tc->desktop) return;
1589     if (tc->text) {
1590         Geom::Point p0, p1;
1591         sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1592         Geom::Point const d0 = p0 * SP_ITEM(tc->text)->i2d_affine();
1593         Geom::Point const d1 = p1 * SP_ITEM(tc->text)->i2d_affine();
1595         // scroll to show cursor
1596         if (scroll_to_see) {
1597             Geom::Point const center = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().midpoint();
1598             if (Geom::L2(d0 - center) > Geom::L2(d1 - center))
1599                 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1600                 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d0, 1.0);
1601             else
1602                 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d1, 1.0);
1603         }
1605         sp_canvas_item_show(tc->cursor);
1606         sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1608         /* fixme: ... need another transformation to get canvas widget coordinate space? */
1609         im_cursor.x = (int) floor(d0[Geom::X]);
1610         im_cursor.y = (int) floor(d0[Geom::Y]);
1611         im_cursor.width = (int) floor(d1[Geom::X]) - im_cursor.x;
1612         im_cursor.height = (int) floor(d1[Geom::Y]) - im_cursor.y;
1614         tc->show = TRUE;
1615         tc->phase = 1;
1617         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1618         int const nChars = layout->iteratorToCharIndex(layout->end());
1619         char const *trunc = "";
1620         bool truncated = false;
1621         if (layout->inputTruncated()) {
1622             truncated = true;
1623             trunc = _(" [truncated]");
1624         }
1625         if (SP_IS_FLOWTEXT(tc->text)) {
1626             SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1627             if (frame) {
1628                 if (truncated) {
1629                     SP_CTRLRECT(tc->frame)->setColor(0xff0000ff, false, 0);
1630                 } else {
1631                     SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0);
1632                 }
1633                 sp_canvas_item_show(tc->frame);
1634                 Geom::OptRect frame_bbox = frame->getBboxDesktop();
1635                 if (frame_bbox) {
1636                     SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
1637                 }
1638             }
1640             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);
1641         } else {
1642             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);
1643         }
1645     } else {
1646         sp_canvas_item_hide(tc->cursor);
1647         sp_canvas_item_hide(tc->frame);
1648         tc->show = FALSE;
1649         if (!tc->nascent_object) {
1650             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
1651         }
1652     }
1654     if (tc->imc) {
1655         gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1656     }
1657     SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1660 static void sp_text_context_update_text_selection(SPTextContext *tc)
1662     // due to interruptible display, tc may already be destroyed during a display update before
1663     // the selection update (can't do both atomically, alas)
1664     if (!tc->desktop) return;
1666     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1667         sp_canvas_item_hide(*it);
1668         gtk_object_destroy(*it);
1669     }
1670     tc->text_selection_quads.clear();
1672     std::vector<Geom::Point> quads;
1673     if (tc->text != NULL)
1674         quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, (tc->text)->i2d_affine());
1675     for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1676         SPCanvasItem *quad_canvasitem;
1677         quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1678         // FIXME: make the color settable in prefs
1679         // for now, use semitrasparent blue, as cairo cannot do inversion :(
1680         sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x00777777);
1681         sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1682         sp_canvas_item_show(quad_canvasitem);
1683         tc->text_selection_quads.push_back(quad_canvasitem);
1684     }
1687 static gint
1688 sp_text_context_timeout(SPTextContext *tc)
1690     if (tc->show) {
1691         sp_canvas_item_show(tc->cursor);
1692         if (tc->phase) {
1693             tc->phase = 0;
1694             sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
1695         } else {
1696             tc->phase = 1;
1697             sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0xffffffff);
1698         }
1699     }
1701     return TRUE;
1704 static void
1705 sp_text_context_forget_text(SPTextContext *tc)
1707     if (! tc->text) return;
1708     SPItem *ti = tc->text;
1709     (void)ti;
1710     /* We have to set it to zero,
1711      * or selection changed signal messes everything up */
1712     tc->text = NULL;
1714 /* FIXME: this automatic deletion when nothing is inputted crashes the XML edittor and also crashes when duplicating an empty flowtext.
1715     So don't create an empty flowtext in the first place? Create it when first character is typed.
1716     */
1717 /*
1718     if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1719         Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1720         // the repr may already have been unparented
1721         // if we were called e.g. as the result of
1722         // an undo or the element being removed from
1723         // the XML editor
1724         if ( text_repr && sp_repr_parent(text_repr) ) {
1725             sp_repr_unparent(text_repr);
1726             SPDocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1727                      _("Remove empty text"));
1728         }
1729     }
1730 */
1733 gint
1734 sptc_focus_in(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1736     gtk_im_context_focus_in(tc->imc);
1737     return FALSE;
1740 gint
1741 sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1743     gtk_im_context_focus_out(tc->imc);
1744     return FALSE;
1747 static void
1748 sptc_commit(GtkIMContext */*imc*/, gchar *string, SPTextContext *tc)
1750     if (!tc->text) {
1751         sp_text_context_setup_text(tc);
1752         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1753     }
1755     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1756     sp_text_context_update_cursor(tc);
1757     sp_text_context_update_text_selection(tc);
1759     DocumentUndo::done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT,
1760                        _("Type text"));
1763 void
1764 sp_text_context_place_cursor (SPTextContext *tc, SPObject *text, Inkscape::Text::Layout::iterator where)
1766     SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text);
1767     tc->text_sel_start = tc->text_sel_end = where;
1768     sp_text_context_update_cursor(tc);
1769     sp_text_context_update_text_selection(tc);
1772 void
1773 sp_text_context_place_cursor_at (SPTextContext *tc, SPObject *text, Geom::Point const p)
1775     SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text);
1776     sp_text_context_place_cursor (tc, text, sp_te_get_position_by_coords(tc->text, p));
1779 Inkscape::Text::Layout::iterator *sp_text_context_get_cursor_position(SPTextContext *tc, SPObject *text)
1781     if (text != tc->text)
1782         return NULL;
1783     return &(tc->text_sel_end);
1787 /*
1788   Local Variables:
1789   mode:c++
1790   c-file-style:"stroustrup"
1791   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1792   indent-tabs-mode:nil
1793   fill-column:99
1794   End:
1795 */
1796 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :