Code

Connector tool: make connectors avoid the convex hull of shapes.
[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)) {
428                 sp_canvas_item_show(tc->indicator);
429                 Geom::OptRect ibbox = sp_item_bbox_desktop(item_ungrouped);
430                 if (ibbox) {
431                     SP_CTRLRECT(tc->indicator)->setRectangle(*ibbox);
432                 }
434                 event_context->cursor_shape = cursor_text_insert_xpm;
435                 event_context->hot_x = 7;
436                 event_context->hot_y = 10;
437                 sp_event_context_update_cursor(event_context);
438                 sp_text_context_update_text_selection(tc);
440                 if (SP_IS_TEXT (item_ungrouped)) {
441                     desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
442                 } else {
443                     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."));
444                 }
446                 tc->over_text = true;
448                 ret = TRUE;
449             }
450             break;
451         default:
452             break;
453     }
455     if (!ret) {
456         if (((SPEventContextClass *) parent_class)->item_handler)
457             ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
458     }
460     return ret;
463 static void
464 sp_text_context_setup_text(SPTextContext *tc)
466     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
468     /* Create <text> */
469     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_EVENT_CONTEXT_DESKTOP(ec)->doc());
470     Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
471     rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
473     /* Set style */
474     sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "/tools/text", true);
476     sp_repr_set_svg_double(rtext, "x", tc->pdoc[Geom::X]);
477     sp_repr_set_svg_double(rtext, "y", tc->pdoc[Geom::Y]);
479     /* Create <tspan> */
480     Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
481     rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
482     rtext->addChild(rtspan, NULL);
483     Inkscape::GC::release(rtspan);
485     /* Create TEXT */
486     Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
487     rtspan->addChild(rstring, NULL);
488     Inkscape::GC::release(rstring);
489     SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
490     /* fixme: Is selection::changed really immediate? */
491     /* yes, it's immediate .. why does it matter? */
492     sp_desktop_selection(ec->desktop)->set(text_item);
493     Inkscape::GC::release(rtext);
494     text_item->transform = sp_item_i2doc_affine(SP_ITEM(ec->desktop->currentLayer())).inverse();
496     text_item->updateRepr();
497     sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
498                      _("Create text"));
501 /**
502  * Insert the character indicated by tc.uni to replace the current selection,
503  * and reset tc.uni/tc.unipos to empty string.
504  *
505  * \pre tc.uni/tc.unipos non-empty.
506  */
507 static void
508 insert_uni_char(SPTextContext *const tc)
510     g_return_if_fail(tc->unipos
511                      && tc->unipos < sizeof(tc->uni)
512                      && tc->uni[tc->unipos] == '\0');
513     unsigned int uv;
514     sscanf(tc->uni, "%x", &uv);
515     tc->unipos = 0;
516     tc->uni[tc->unipos] = '\0';
518     if ( !g_unichar_isprint(static_cast<gunichar>(uv))
519          && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
520         // This may be due to bad input, so it goes to statusbar.
521         tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
522                                            _("Non-printable character"));
523     } else {
524         if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
525             sp_text_context_setup_text(tc);
526             tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
527         }
529         gchar u[10];
530         guint const len = g_unichar_to_utf8(uv, u);
531         u[len] = '\0';
533         tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
534         sp_text_context_update_cursor(tc);
535         sp_text_context_update_text_selection(tc);
536         sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM,
537                          _("Insert Unicode character"));
538     }
541 static void
542 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
544     unsigned int uv;
545     sscanf(hex, "%x", &uv);
546     if (!g_unichar_isprint((gunichar) uv)) {
547         uv = 0xfffd;
548     }
549     guint const len = g_unichar_to_utf8(uv, utf8);
550     utf8[len] = '\0';
553 static void
554 show_curr_uni_char(SPTextContext *const tc)
556     g_return_if_fail(tc->unipos < sizeof(tc->uni)
557                      && tc->uni[tc->unipos] == '\0');
558     if (tc->unipos) {
559         char utf8[10];
560         hex_to_printable_utf8_buf(tc->uni, utf8);
562         /* Status bar messages are in pango markup, so we need xml escaping. */
563         if (utf8[1] == '\0') {
564             switch(utf8[0]) {
565                 case '<': strcpy(utf8, "&lt;"); break;
566                 case '>': strcpy(utf8, "&gt;"); break;
567                 case '&': strcpy(utf8, "&amp;"); break;
568                 default: break;
569             }
570         }
571         tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
572                                           _("Unicode (<b>Enter</b> to finish): %s: %s"), tc->uni, utf8);
573     } else {
574         tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
575     }
578 static gint
579 sp_text_context_root_handler(SPEventContext *const event_context, GdkEvent *const event)
581     SPTextContext *const tc = SP_TEXT_CONTEXT(event_context);
583     SPDesktop *desktop = event_context->desktop;
585     sp_canvas_item_hide(tc->indicator);
587     sp_text_context_validate_cursor_iterators(tc);
589     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
590     event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
592     switch (event->type) {
593         case GDK_BUTTON_PRESS:
594             if (event->button.button == 1 && !event_context->space_panning) {
596                 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
597                     return TRUE;
598                 }
600                 // save drag origin
601                 event_context->xp = (gint) event->button.x;
602                 event_context->yp = (gint) event->button.y;
603                 event_context->within_tolerance = true;
605                 Geom::Point const button_pt(event->button.x, event->button.y);
606                 tc->p0 = desktop->w2d(button_pt);
607                 Inkscape::Rubberband::get(desktop)->start(desktop, tc->p0);
608                 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
609                                     GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
610                                     NULL, event->button.time);
611                 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
612                 tc->creating = 1;
614                 /* Processed */
615                 return TRUE;
616             }
617             break;
618         case GDK_MOTION_NOTIFY:
619             if (tc->over_text) {
620                 tc->over_text = 0;
621                 // update cursor and statusbar: we are not over a text object now
622                 event_context->cursor_shape = cursor_text_xpm;
623                 event_context->hot_x = 7;
624                 event_context->hot_y = 7;
625                 sp_event_context_update_cursor(event_context);
626                 desktop->event_context->defaultMessageContext()->clear();
627             }
629             if (tc->creating && event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
630                 if ( event_context->within_tolerance
631                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
632                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
633                     break; // do not drag if we're within tolerance from origin
634                 }
635                 // Once the user has moved farther than tolerance from the original location
636                 // (indicating they intend to draw, not click), then always process the
637                 // motion notify coordinates as given (no snapping back to origin)
638                 event_context->within_tolerance = false;
640                 Geom::Point const motion_pt(event->motion.x, event->motion.y);
641                 Geom::Point const p = desktop->w2d(motion_pt);
643                 Inkscape::Rubberband::get(desktop)->move(p);
644                 gobble_motion_events(GDK_BUTTON1_MASK);
646                 // status text
647                 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::X]), desktop->namedview->getDefaultMetric());
648                 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::Y]), desktop->namedview->getDefaultMetric());
649                 event_context->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Flowed text frame</b>: %s &#215; %s"), xs->str, ys->str);
650                 g_string_free(xs, FALSE);
651                 g_string_free(ys, FALSE);
653             }
654             break;
655         case GDK_BUTTON_RELEASE:
656             if (event->button.button == 1 && !event_context->space_panning) {
658                 if (tc->grabbed) {
659                     sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
660                     tc->grabbed = NULL;
661                 }
663                 Inkscape::Rubberband::get(desktop)->stop();
665                 if (tc->creating && event_context->within_tolerance) {
666                     /* Button 1, set X & Y & new item */
667                     sp_desktop_selection(desktop)->clear();
668                     Geom::Point dtp = desktop->w2d(Geom::Point(event->button.x, event->button.y));
669                     tc->pdoc = desktop->dt2doc(dtp);
671                     tc->show = TRUE;
672                     tc->phase = 1;
673                     tc->nascent_object = 1; // new object was just created
675                     /* Cursor */
676                     sp_canvas_item_show(tc->cursor);
677                     // Cursor height is defined by the new text object's font size; it needs to be set
678                     // articifically here, for the text object does not exist yet:
679                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
680                     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), dtp, dtp + Geom::Point(0, cursor_height));
681                     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
683                     event_context->within_tolerance = false;
684                 } else if (tc->creating) {
685                     Geom::Point const button_pt(event->button.x, event->button.y);
686                     Geom::Point p1 = desktop->w2d(button_pt);
687                     double cursor_height = sp_desktop_get_font_size_tool(desktop);
688                     if (fabs(p1[Geom::Y] - tc->p0[Geom::Y]) > cursor_height) {
689                         // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
690                         SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
691                         /* Set style */
692                         sp_desktop_apply_style_tool(desktop, SP_OBJECT_REPR(ft), "/tools/text", true);
693                         sp_desktop_selection(desktop)->set(ft);
694                         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
695                         sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
696                                          _("Create flowed text"));
697                     } else {
698                         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
699                     }
700                 }
701                 tc->creating = false;
702                 return TRUE;
703             }
704             break;
705         case GDK_KEY_PRESS: {
706             guint const group0_keyval = get_group0_keyval(&event->key);
708             if (group0_keyval == GDK_KP_Add ||
709                 group0_keyval == GDK_KP_Subtract) {
710                 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
711                     break; // otherwise pass on keypad +/- so they can zoom
712             }
714             if ((tc->text) || (tc->nascent_object)) {
715                 // there is an active text object in this context, or a new object was just created
717                 if (tc->unimode || !tc->imc
718                     || (MOD__CTRL && MOD__SHIFT)    // input methods tend to steal this for unimode,
719                                                     // but we have our own so make sure they don't swallow it
720                     || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
721                     //IM did not consume the key, or we're in unimode
723                         if (!MOD__CTRL_ONLY && tc->unimode) {
724                             /* TODO: ISO 14755 (section 3 Definitions) says that we should also
725                                accept the first 6 characters of alphabets other than the latin
726                                alphabet "if the Latin alphabet is not used".  The below is also
727                                reasonable (viz. hope that the user's keyboard includes latin
728                                characters and force latin interpretation -- just as we do for our
729                                keyboard shortcuts), but differs from the ISO 14755
730                                recommendation. */
731                             switch (group0_keyval) {
732                                 case GDK_space:
733                                 case GDK_KP_Space: {
734                                     if (tc->unipos) {
735                                         insert_uni_char(tc);
736                                     }
737                                     /* Stay in unimode. */
738                                     show_curr_uni_char(tc);
739                                     return TRUE;
740                                 }
742                                 case GDK_BackSpace: {
743                                     g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
744                                     if (tc->unipos) {
745                                         tc->uni[--tc->unipos] = '\0';
746                                     }
747                                     show_curr_uni_char(tc);
748                                     return TRUE;
749                                 }
751                                 case GDK_Return:
752                                 case GDK_KP_Enter: {
753                                     if (tc->unipos) {
754                                         insert_uni_char(tc);
755                                     }
756                                     /* Exit unimode. */
757                                     tc->unimode = false;
758                                     event_context->defaultMessageContext()->clear();
759                                     return TRUE;
760                                 }
762                                 case GDK_Escape: {
763                                     // Cancel unimode.
764                                     tc->unimode = false;
765                                     gtk_im_context_reset(tc->imc);
766                                     event_context->defaultMessageContext()->clear();
767                                     return TRUE;
768                                 }
770                                 case GDK_Shift_L:
771                                 case GDK_Shift_R:
772                                     break;
774                                 default: {
775                                     if (g_ascii_isxdigit(group0_keyval)) {
776                                         g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
777                                         tc->uni[tc->unipos++] = group0_keyval;
778                                         tc->uni[tc->unipos] = '\0';
779                                         if (tc->unipos == 8) {
780                                             /* This behaviour is partly to allow us to continue to
781                                                use a fixed-length buffer for tc->uni.  Reason for
782                                                choosing the number 8 is that it's the length of
783                                                ``canonical form'' mentioned in the ISO 14755 spec.
784                                                An advantage over choosing 6 is that it allows using
785                                                backspace for typos & misremembering when entering a
786                                                6-digit number. */
787                                             insert_uni_char(tc);
788                                         }
789                                         show_curr_uni_char(tc);
790                                         return TRUE;
791                                     } else {
792                                         /* The intent is to ignore but consume characters that could be
793                                            typos for hex digits.  Gtk seems to ignore & consume all
794                                            non-hex-digits, and we do similar here.  Though note that some
795                                            shortcuts (like keypad +/- for zoom) get processed before
796                                            reaching this code. */
797                                         return TRUE;
798                                     }
799                                 }
800                             }
801                         }
803                         Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
804                         Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
805                         bool cursor_moved = false;
806                         int screenlines = 1;
807                         if (tc->text) {
808                             double spacing = sp_te_get_average_linespacing(tc->text);
809                             Geom::Rect const d = desktop->get_display_area();
810                             screenlines = (int) floor(fabs(d.min()[Geom::Y] - d.max()[Geom::Y])/spacing) - 1;
811                             if (screenlines <= 0)
812                                 screenlines = 1;
813                         }
815                         /* Neither unimode nor IM consumed key; process text tool shortcuts */
816                         switch (group0_keyval) {
817                             case GDK_x:
818                             case GDK_X:
819                                 if (MOD__ALT_ONLY) {
820                                     desktop->setToolboxFocusTo ("altx-text");
821                                     return TRUE;
822                                 }
823                                 break;
824                             case GDK_space:
825                                 if (MOD__CTRL_ONLY) {
826                                     /* No-break space */
827                                     if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
828                                         sp_text_context_setup_text(tc);
829                                         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
830                                     }
831                                     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
832                                     sp_text_context_update_cursor(tc);
833                                     sp_text_context_update_text_selection(tc);
834                                     desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
835                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
836                                                      _("Insert no-break space"));
837                                     return TRUE;
838                                 }
839                                 break;
840                             case GDK_U:
841                             case GDK_u:
842                                 if (MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT)) {
843                                     if (tc->unimode) {
844                                         tc->unimode = false;
845                                         event_context->defaultMessageContext()->clear();
846                                     } else {
847                                         tc->unimode = true;
848                                         tc->unipos = 0;
849                                         event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
850                                     }
851                                     if (tc->imc) {
852                                         gtk_im_context_reset(tc->imc);
853                                     }
854                                     return TRUE;
855                                 }
856                                 break;
857                             case GDK_B:
858                             case GDK_b:
859                                 if (MOD__CTRL_ONLY && tc->text) {
860                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
861                                     SPCSSAttr *css = sp_repr_css_attr_new();
862                                     if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
863                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
864                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
865                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
866                                         || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
867                                         sp_repr_css_set_property(css, "font-weight", "bold");
868                                     else
869                                         sp_repr_css_set_property(css, "font-weight", "normal");
870                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
871                                     sp_repr_css_attr_unref(css);
872                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
873                                                      _("Make bold"));
874                                     sp_text_context_update_cursor(tc);
875                                     sp_text_context_update_text_selection(tc);
876                                     return TRUE;
877                                 }
878                                 break;
879                             case GDK_I:
880                             case GDK_i:
881                                 if (MOD__CTRL_ONLY && tc->text) {
882                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
883                                     SPCSSAttr *css = sp_repr_css_attr_new();
884                                     if (style->font_style.computed != SP_CSS_FONT_STYLE_NORMAL)
885                                         sp_repr_css_set_property(css, "font-style", "normal");
886                                     else
887                                         sp_repr_css_set_property(css, "font-style", "italic");
888                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
889                                     sp_repr_css_attr_unref(css);
890                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
891                                                      _("Make italic"));
892                                     sp_text_context_update_cursor(tc);
893                                     sp_text_context_update_text_selection(tc);
894                                     return TRUE;
895                                 }
896                                 break;
898                             case GDK_A:
899                             case GDK_a:
900                                 if (MOD__CTRL_ONLY && tc->text) {
901                                     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
902                                     if (layout) {
903                                         tc->text_sel_start = layout->begin();
904                                         tc->text_sel_end = layout->end();
905                                         sp_text_context_update_cursor(tc);
906                                         sp_text_context_update_text_selection(tc);
907                                         return TRUE;
908                                     }
909                                 }
910                                 break;
912                             case GDK_Return:
913                             case GDK_KP_Enter:
914                             {
915                                 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
916                                     sp_text_context_setup_text(tc);
917                                     tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
918                                 }
920                                 iterator_pair enter_pair;
921                                 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, enter_pair);
922                                 (void)success; // TODO cleanup
923                                 tc->text_sel_start = tc->text_sel_end = enter_pair.first;
925                                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
927                                 sp_text_context_update_cursor(tc);
928                                 sp_text_context_update_text_selection(tc);
929                                 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
930                                                  _("New line"));
931                                 return TRUE;
932                             }
933                             case GDK_BackSpace:
934                                 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
936                                     bool noSelection = false;
938                                         if (tc->text_sel_start == tc->text_sel_end) {
939                                         tc->text_sel_start.prevCursorPosition();
940                                         noSelection = true;
941                                     }
943                                         iterator_pair bspace_pair;
944                                         bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, bspace_pair);
946                                     if (noSelection) {
947                                         if (success) {
948                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
949                                         } else { // nothing deleted
950                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.second;
951                                         }
952                                     } else {
953                                         if (success) {
954                                             tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
955                                         } else { // nothing deleted
956                                             tc->text_sel_start = bspace_pair.first;
957                                             tc->text_sel_end = bspace_pair.second;
958                                         }
959                                     }
961                                     sp_text_context_update_cursor(tc);
962                                     sp_text_context_update_text_selection(tc);
963                                     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
964                                                      _("Backspace"));
965                                 }
966                                 return TRUE;
967                             case GDK_Delete:
968                             case GDK_KP_Delete:
969                                 if (tc->text) {
970                                     bool noSelection = false;
972                                     if (tc->text_sel_start == tc->text_sel_end) {
973                                         tc->text_sel_end.nextCursorPosition();
974                                         noSelection = true;
975                                     }
977                                     iterator_pair del_pair;
978                                     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, del_pair);
980                                     if (noSelection) {
981                                         tc->text_sel_start = tc->text_sel_end = del_pair.first;
982                                     } else {
983                                         if (success) {
984                                             tc->text_sel_start = tc->text_sel_end = del_pair.first;
985                                         } else { // nothing deleted
986                                             tc->text_sel_start = del_pair.first;
987                                             tc->text_sel_end = del_pair.second;
988                                         }
989                                     }
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                                                      _("Delete"));
996                                 }
997                                 return TRUE;
998                             case GDK_Left:
999                             case GDK_KP_Left:
1000                             case GDK_KP_4:
1001                                 if (tc->text) {
1002                                     if (MOD__ALT) {
1003                                         gint mul = 1 + gobble_key_events(
1004                                             get_group0_keyval(&event->key), 0); // with any mask
1005                                         if (MOD__SHIFT)
1006                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-10, 0));
1007                                         else
1008                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-1, 0));
1009                                         sp_text_context_update_cursor(tc);
1010                                         sp_text_context_update_text_selection(tc);
1011                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT,
1012                                                                _("Kern to the left"));
1013                                     } else {
1014                                         if (MOD__CTRL)
1015                                             tc->text_sel_end.cursorLeftWithControl();
1016                                         else
1017                                             tc->text_sel_end.cursorLeft();
1018                                         cursor_moved = true;
1019                                         break;
1020                                     }
1021                                 }
1022                                 return TRUE;
1023                             case GDK_Right:
1024                             case GDK_KP_Right:
1025                             case GDK_KP_6:
1026                                 if (tc->text) {
1027                                     if (MOD__ALT) {
1028                                         gint mul = 1 + gobble_key_events(
1029                                             get_group0_keyval(&event->key), 0); // with any mask
1030                                         if (MOD__SHIFT)
1031                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*10, 0));
1032                                         else
1033                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*1, 0));
1034                                         sp_text_context_update_cursor(tc);
1035                                         sp_text_context_update_text_selection(tc);
1036                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT,
1037                                                                _("Kern to the right"));
1038                                     } else {
1039                                         if (MOD__CTRL)
1040                                             tc->text_sel_end.cursorRightWithControl();
1041                                         else
1042                                             tc->text_sel_end.cursorRight();
1043                                         cursor_moved = true;
1044                                         break;
1045                                     }
1046                                 }
1047                                 return TRUE;
1048                             case GDK_Up:
1049                             case GDK_KP_Up:
1050                             case GDK_KP_8:
1051                                 if (tc->text) {
1052                                     if (MOD__ALT) {
1053                                         gint mul = 1 + gobble_key_events(
1054                                             get_group0_keyval(&event->key), 0); // with any mask
1055                                         if (MOD__SHIFT)
1056                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-10));
1057                                         else
1058                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-1));
1059                                         sp_text_context_update_cursor(tc);
1060                                         sp_text_context_update_text_selection(tc);
1061                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT,
1062                                                                _("Kern up"));
1064                                     } else {
1065                                         if (MOD__CTRL)
1066                                             tc->text_sel_end.cursorUpWithControl();
1067                                         else
1068                                             tc->text_sel_end.cursorUp();
1069                                         cursor_moved = true;
1070                                         break;
1071                                     }
1072                                 }
1073                                 return TRUE;
1074                             case GDK_Down:
1075                             case GDK_KP_Down:
1076                             case GDK_KP_2:
1077                                 if (tc->text) {
1078                                     if (MOD__ALT) {
1079                                         gint mul = 1 + gobble_key_events(
1080                                             get_group0_keyval(&event->key), 0); // with any mask
1081                                         if (MOD__SHIFT)
1082                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*10));
1083                                         else
1084                                             sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*1));
1085                                         sp_text_context_update_cursor(tc);
1086                                         sp_text_context_update_text_selection(tc);
1087                                         sp_document_maybe_done(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT,
1088                                                                _("Kern down"));
1090                                     } else {
1091                                         if (MOD__CTRL)
1092                                             tc->text_sel_end.cursorDownWithControl();
1093                                         else
1094                                             tc->text_sel_end.cursorDown();
1095                                         cursor_moved = true;
1096                                         break;
1097                                     }
1098                                 }
1099                                 return TRUE;
1100                             case GDK_Home:
1101                             case GDK_KP_Home:
1102                                 if (tc->text) {
1103                                     if (MOD__CTRL)
1104                                         tc->text_sel_end.thisStartOfShape();
1105                                     else
1106                                         tc->text_sel_end.thisStartOfLine();
1107                                     cursor_moved = true;
1108                                     break;
1109                                 }
1110                                 return TRUE;
1111                             case GDK_End:
1112                             case GDK_KP_End:
1113                                 if (tc->text) {
1114                                     if (MOD__CTRL)
1115                                         tc->text_sel_end.nextStartOfShape();
1116                                     else
1117                                         tc->text_sel_end.thisEndOfLine();
1118                                     cursor_moved = true;
1119                                     break;
1120                                 }
1121                                 return TRUE;
1122                             case GDK_Page_Down:
1123                             case GDK_KP_Page_Down:
1124                                 if (tc->text) {
1125                                     tc->text_sel_end.cursorDown(screenlines);
1126                                     cursor_moved = true;
1127                                     break;
1128                                 }
1129                                 return TRUE;
1130                             case GDK_Page_Up:
1131                             case GDK_KP_Page_Up:
1132                                 if (tc->text) {
1133                                     tc->text_sel_end.cursorUp(screenlines);
1134                                     cursor_moved = true;
1135                                     break;
1136                                 }
1137                                 return TRUE;
1138                             case GDK_Escape:
1139                                 if (tc->creating) {
1140                                     tc->creating = 0;
1141                                     if (tc->grabbed) {
1142                                         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1143                                         tc->grabbed = NULL;
1144                                     }
1145                                     Inkscape::Rubberband::get(desktop)->stop();
1146                                 } else {
1147                                     sp_desktop_selection(desktop)->clear();
1148                                 }
1149                                 tc->nascent_object = FALSE;
1150                                 return TRUE;
1151                             case GDK_bracketleft:
1152                                 if (tc->text) {
1153                                     if (MOD__ALT || MOD__CTRL) {
1154                                         if (MOD__ALT) {
1155                                             if (MOD__SHIFT) {
1156                                                 // FIXME: alt+shift+[] does not work, don't know why
1157                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1158                                             } else {
1159                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1160                                             }
1161                                         } else {
1162                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -90);
1163                                         }
1164                                         sp_document_maybe_done(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT,
1165                                                                _("Rotate counterclockwise"));
1166                                         sp_text_context_update_cursor(tc);
1167                                         sp_text_context_update_text_selection(tc);
1168                                         return TRUE;
1169                                     }
1170                                 }
1171                                 break;
1172                             case GDK_bracketright:
1173                                 if (tc->text) {
1174                                     if (MOD__ALT || MOD__CTRL) {
1175                                         if (MOD__ALT) {
1176                                             if (MOD__SHIFT) {
1177                                                 // FIXME: alt+shift+[] does not work, don't know why
1178                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1179                                             } else {
1180                                                 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1181                                             }
1182                                         } else {
1183                                             sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 90);
1184                                         }
1185                                         sp_document_maybe_done(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT,
1186                                                                 _("Rotate clockwise"));
1187                                         sp_text_context_update_cursor(tc);
1188                                         sp_text_context_update_text_selection(tc);
1189                                         return TRUE;
1190                                     }
1191                                 }
1192                                 break;
1193                             case GDK_less:
1194                             case GDK_comma:
1195                                 if (tc->text) {
1196                                     if (MOD__ALT) {
1197                                         if (MOD__CTRL) {
1198                                             if (MOD__SHIFT)
1199                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1200                                             else
1201                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1202                                             sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT,
1203                                                                     _("Contract line spacing"));
1205                                         } else {
1206                                             if (MOD__SHIFT)
1207                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1208                                             else
1209                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1210                                             sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT,
1211                                                                     _("Contract letter spacing"));
1213                                         }
1214                                         sp_text_context_update_cursor(tc);
1215                                         sp_text_context_update_text_selection(tc);
1216                                         return TRUE;
1217                                     }
1218                                 }
1219                                 break;
1220                             case GDK_greater:
1221                             case GDK_period:
1222                                 if (tc->text) {
1223                                     if (MOD__ALT) {
1224                                         if (MOD__CTRL) {
1225                                             if (MOD__SHIFT)
1226                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1227                                             else
1228                                                 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1229                                             sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT,
1230                                                                     _("Expand line spacing"));
1232                                         } else {
1233                                             if (MOD__SHIFT)
1234                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1235                                             else
1236                                                 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1237                                             sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT,
1238                                                                     _("Expand letter spacing"));
1240                                         }
1241                                         sp_text_context_update_cursor(tc);
1242                                         sp_text_context_update_text_selection(tc);
1243                                         return TRUE;
1244                                     }
1245                                 }
1246                                 break;
1247                             default:
1248                                 break;
1249                         }
1251                         if (cursor_moved) {
1252                             if (!MOD__SHIFT)
1253                                 tc->text_sel_start = tc->text_sel_end;
1254                             if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1255                                 sp_text_context_update_cursor(tc);
1256                                 sp_text_context_update_text_selection(tc);
1257                             }
1258                             return TRUE;
1259                         }
1261                 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1262             } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1263                 // except up/down that are swallowed to prevent the zoom field from activation
1264                 if ((group0_keyval == GDK_Up    ||
1265                      group0_keyval == GDK_Down  ||
1266                      group0_keyval == GDK_KP_Up ||
1267                      group0_keyval == GDK_KP_Down )
1268                     && !MOD__CTRL_ONLY) {
1269                     return TRUE;
1270                 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1271                     if (tc->creating) {
1272                         tc->creating = 0;
1273                         if (tc->grabbed) {
1274                             sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1275                             tc->grabbed = NULL;
1276                         }
1277                         Inkscape::Rubberband::get(desktop)->stop();
1278                     }
1279                 } else if ((group0_keyval == GDK_x || group0_keyval == GDK_X) && MOD__ALT_ONLY) {
1280                     desktop->setToolboxFocusTo ("altx-text");
1281                     return TRUE;
1282                 }
1283             }
1284             break;
1285         }
1287         case GDK_KEY_RELEASE:
1288             if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1289                 return TRUE;
1290             }
1291             break;
1292         default:
1293             break;
1294     }
1296     // if nobody consumed it so far
1297     if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1298         return ((SPEventContextClass *) parent_class)->root_handler(event_context, event); // send event to parent
1299     } else {
1300         return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1301     }
1304 /**
1305  Attempts to paste system clipboard into the currently edited text, returns true on success
1306  */
1307 bool
1308 sp_text_paste_inline(SPEventContext *ec)
1310     if (!SP_IS_TEXT_CONTEXT(ec))
1311         return false;
1313     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1315     if ((tc->text) || (tc->nascent_object)) {
1316         // there is an active text object in this context, or a new object was just created
1318         Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1319         Glib::ustring const clip_text = refClipboard->wait_for_text();
1321         if (!clip_text.empty()) {
1322                 // Fix for 244940
1323                 // The XML standard defines the following as valid characters
1324                 // (Extensible Markup Language (XML) 1.0 (Fourth Edition) paragraph 2.2)
1325                 // char ::=     #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
1326                 // Since what comes in off the paste buffer will go right into XML, clean
1327                 // the text here.
1328                 Glib::ustring text(clip_text);
1329                 Glib::ustring::iterator itr = text.begin();
1330                 gunichar paste_string_uchar;
1332                 while(itr != text.end())
1333                 {
1334                     paste_string_uchar = *itr;
1336                     // Make sure we don't have a control character. We should really check
1337                     // for the whole range above... Add the rest of the invalid cases from
1338                     // above if we find additional issues
1339                     if(paste_string_uchar >= 0x00000020 ||
1340                        paste_string_uchar == 0x00000009 ||
1341                        paste_string_uchar == 0x0000000A ||
1342                        paste_string_uchar == 0x0000000D) {
1343                         itr++;
1344                     } else {
1345                         itr = text.erase(itr);
1346                     }
1347                 }
1349             if (!tc->text) { // create text if none (i.e. if nascent_object)
1350                 sp_text_context_setup_text(tc);
1351                 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1352             }
1354             // using indices is slow in ustrings. Whatever.
1355             Glib::ustring::size_type begin = 0;
1356             for ( ; ; ) {
1357                 Glib::ustring::size_type end = text.find('\n', begin);
1358                 if (end == Glib::ustring::npos) {
1359                     if (begin != text.length())
1360                         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());
1361                     break;
1362                 }
1363                 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());
1364                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1365                 begin = end + 1;
1366             }
1367             sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
1368                              _("Paste text"));
1370             return true;
1371         }
1372     } // FIXME: else create and select a new object under cursor!
1374     return false;
1377 /**
1378  Gets the raw characters that comprise the currently selected text, converting line
1379  breaks into lf characters.
1380 */
1381 Glib::ustring
1382 sp_text_get_selected_text(SPEventContext const *ec)
1384     if (!SP_IS_TEXT_CONTEXT(ec))
1385         return "";
1386     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1387     if (tc->text == NULL)
1388         return "";
1390     return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1393 SPCSSAttr *
1394 sp_text_get_style_at_cursor(SPEventContext const *ec)
1396     if (!SP_IS_TEXT_CONTEXT(ec))
1397         return NULL;
1398     SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1399     if (tc->text == NULL)
1400         return NULL;
1402     SPObject const *obj = sp_te_object_at_position(tc->text, tc->text_sel_end);
1403     if (obj)
1404         return take_style_from_item((SPItem *) obj);
1405     return NULL;
1408 /**
1409  Deletes the currently selected characters. Returns false if there is no
1410  text selection currently.
1411 */
1412 bool sp_text_delete_selection(SPEventContext *ec)
1414     if (!SP_IS_TEXT_CONTEXT(ec))
1415         return false;
1416     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1417     if (tc->text == NULL)
1418         return false;
1420     if (tc->text_sel_start == tc->text_sel_end)
1421         return false;
1423     iterator_pair pair;
1424     bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair);
1427     if (success) {
1428         tc->text_sel_start = tc->text_sel_end = pair.first;
1429     } else { // nothing deleted
1430         tc->text_sel_start = pair.first;
1431         tc->text_sel_end = pair.second;
1432     }
1434     sp_text_context_update_cursor(tc);
1435     sp_text_context_update_text_selection(tc);
1437     return true;
1440 /**
1441  * \param selection Should not be NULL.
1442  */
1443 static void
1444 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1446     g_assert(selection != NULL);
1448     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1450     ec->shape_editor->unset_item(SH_KNOTHOLDER);
1451     SPItem *item = selection->singleItem();
1452     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1453         ec->shape_editor->set_item(item, SH_KNOTHOLDER);
1454     }
1456     if (tc->text && (item != tc->text)) {
1457         sp_text_context_forget_text(tc);
1458     }
1459     tc->text = NULL;
1461     if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1462         tc->text = item;
1463         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1464         if (layout)
1465             tc->text_sel_start = tc->text_sel_end = layout->end();
1466     } else {
1467         tc->text = NULL;
1468     }
1470     // we update cursor without scrolling, because this position may not be final;
1471     // item_handler moves cusros to the point of click immediately
1472     sp_text_context_update_cursor(tc, false);
1473     sp_text_context_update_text_selection(tc);
1476 static void
1477 sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, SPTextContext *tc)
1479     sp_text_context_update_cursor(tc);
1480     sp_text_context_update_text_selection(tc);
1483 static bool
1484 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1486     if (tc->text == NULL)
1487         return false;
1488     if (tc->text_sel_start == tc->text_sel_end)
1489         return false;    // will get picked up by the parent and applied to the whole text object
1491     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1492     sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1493                      _("Set text style"));
1494     sp_text_context_update_cursor(tc);
1495     sp_text_context_update_text_selection(tc);
1497     return true;
1500 static int
1501 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1503     if (tc->text == NULL)
1504         return QUERY_STYLE_NOTHING;
1505     const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1506     if (layout == NULL)
1507         return QUERY_STYLE_NOTHING;
1508     sp_text_context_validate_cursor_iterators(tc);
1510     GSList *styles_list = NULL;
1512     Inkscape::Text::Layout::iterator begin_it, end_it;
1513     if (tc->text_sel_start < tc->text_sel_end) {
1514         begin_it = tc->text_sel_start;
1515         end_it = tc->text_sel_end;
1516     } else {
1517         begin_it = tc->text_sel_end;
1518         end_it = tc->text_sel_start;
1519     }
1520     if (begin_it == end_it)
1521         if (!begin_it.prevCharacter())
1522             end_it.nextCharacter();
1523     for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1524         SPObject const *pos_obj = 0;
1525         void *rawptr = 0;
1526         layout->getSourceOfCharacter(it, &rawptr);
1527         if (!rawptr || !SP_IS_OBJECT(rawptr))
1528             continue;
1529         pos_obj = SP_OBJECT(rawptr);
1530         while (SP_IS_STRING(pos_obj) && SP_OBJECT_PARENT(pos_obj)) {
1531            pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
1532         }
1533         styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1534     }
1536     int result = sp_desktop_query_style_from_list (styles_list, style, property);
1538     g_slist_free(styles_list);
1539     return result;
1542 static void
1543 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1545     if (tc->text == NULL)
1546         return;
1547     Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1548     if (layout) {     // undo can change the text length without us knowing it
1549         layout->validateIterator(&tc->text_sel_start);
1550         layout->validateIterator(&tc->text_sel_end);
1551     }
1554 static void
1555 sp_text_context_update_cursor(SPTextContext *tc,  bool scroll_to_see)
1557     GdkRectangle im_cursor = { 0, 0, 1, 1 };
1559     // due to interruptible display, tc may already be destroyed during a display update before
1560     // the cursor update (can't do both atomically, alas)
1561     if (!tc->desktop) return;
1563     if (tc->text) {
1564         Geom::Point p0, p1;
1565         sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1566         Geom::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1567         Geom::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1569         // scroll to show cursor
1570         if (scroll_to_see) {
1571             Geom::Point const center = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().midpoint();
1572             if (Geom::L2(d0 - center) > Geom::L2(d1 - center))
1573                 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1574                 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d0, 1.0);
1575             else
1576                 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d1, 1.0);
1577         }
1579         sp_canvas_item_show(tc->cursor);
1580         sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1582         /* fixme: ... need another transformation to get canvas widget coordinate space? */
1583         im_cursor.x = (int) floor(d0[Geom::X]);
1584         im_cursor.y = (int) floor(d0[Geom::Y]);
1585         im_cursor.width = (int) floor(d1[Geom::X]) - im_cursor.x;
1586         im_cursor.height = (int) floor(d1[Geom::Y]) - im_cursor.y;
1588         tc->show = TRUE;
1589         tc->phase = 1;
1591         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1592         int const nChars = layout->iteratorToCharIndex(layout->end());
1593         if (SP_IS_FLOWTEXT(tc->text)) {
1594             SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1595             if (frame) {
1596                 sp_canvas_item_show(tc->frame);
1597                 Geom::OptRect frame_bbox = sp_item_bbox_desktop(frame);
1598                 if (frame_bbox) {
1599                     SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
1600                 }
1601             }
1602             SP_EVENT_CONTEXT(tc)->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("Type or edit flowed text (%d characters); <b>Enter</b> to start new paragraph."), nChars);
1603         } else {
1604             SP_EVENT_CONTEXT(tc)->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("Type or edit text (%d characters); <b>Enter</b> to start new line."), nChars);
1605         }
1607     } else {
1608         sp_canvas_item_hide(tc->cursor);
1609         sp_canvas_item_hide(tc->frame);
1610         tc->show = FALSE;
1611         if (!tc->nascent_object) {
1612             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
1613         }
1614     }
1616     if (tc->imc) {
1617         gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1618     }
1619     SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1622 static void sp_text_context_update_text_selection(SPTextContext *tc)
1624     // due to interruptible display, tc may already be destroyed during a display update before
1625     // the selection update (can't do both atomically, alas)
1626     if (!tc->desktop) return;
1628     for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1629         sp_canvas_item_hide(*it);
1630         gtk_object_destroy(*it);
1631     }
1632     tc->text_selection_quads.clear();
1634     std::vector<Geom::Point> quads;
1635     if (tc->text != NULL)
1636         quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1637     for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1638         SPCanvasItem *quad_canvasitem;
1639         quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1640         // FIXME: make the color settable in prefs
1641         // for now, use semitrasparent blue, as cairo cannot do inversion :(
1642         sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x00777777);
1643         sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1644         sp_canvas_item_show(quad_canvasitem);
1645         tc->text_selection_quads.push_back(quad_canvasitem);
1646     }
1649 static gint
1650 sp_text_context_timeout(SPTextContext *tc)
1652     if (tc->show) {
1653         sp_canvas_item_show(tc->cursor);
1654         if (tc->phase) {
1655             tc->phase = 0;
1656             sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
1657         } else {
1658             tc->phase = 1;
1659             sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0xffffffff);
1660         }
1661     }
1663     return TRUE;
1666 static void
1667 sp_text_context_forget_text(SPTextContext *tc)
1669     if (! tc->text) return;
1670     SPItem *ti = tc->text;
1671     (void)ti;
1672     /* We have to set it to zero,
1673      * or selection changed signal messes everything up */
1674     tc->text = NULL;
1676 /* FIXME: this automatic deletion when nothing is inputted crashes the XML edittor and also crashes when duplicating an empty flowtext.
1677     So don't create an empty flowtext in the first place? Create it when first character is typed.
1678     */
1679 /*
1680     if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1681         Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1682         // the repr may already have been unparented
1683         // if we were called e.g. as the result of
1684         // an undo or the element being removed from
1685         // the XML editor
1686         if ( text_repr && sp_repr_parent(text_repr) ) {
1687             sp_repr_unparent(text_repr);
1688             sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1689                      _("Remove empty text"));
1690         }
1691     }
1692 */
1695 gint
1696 sptc_focus_in(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1698     gtk_im_context_focus_in(tc->imc);
1699     return FALSE;
1702 gint
1703 sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1705     gtk_im_context_focus_out(tc->imc);
1706     return FALSE;
1709 static void
1710 sptc_commit(GtkIMContext */*imc*/, gchar *string, SPTextContext *tc)
1712     if (!tc->text) {
1713         sp_text_context_setup_text(tc);
1714         tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1715     }
1717     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1718     sp_text_context_update_cursor(tc);
1719     sp_text_context_update_text_selection(tc);
1721     sp_document_done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT,
1722                      _("Type text"));
1725 void
1726 sp_text_context_place_cursor (SPTextContext *tc, SPObject *text, Inkscape::Text::Layout::iterator where)
1728     SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text);
1729     tc->text_sel_start = tc->text_sel_end = where;
1730     sp_text_context_update_cursor(tc);
1731     sp_text_context_update_text_selection(tc);
1734 void
1735 sp_text_context_place_cursor_at (SPTextContext *tc, SPObject *text, Geom::Point const p)
1737     SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text);
1738     sp_text_context_place_cursor (tc, text, sp_te_get_position_by_coords(tc->text, p));
1741 Inkscape::Text::Layout::iterator *sp_text_context_get_cursor_position(SPTextContext *tc, SPObject *text)
1743     if (text != tc->text)
1744         return NULL;
1745     return &(tc->text_sel_end);
1749 /*
1750   Local Variables:
1751   mode:c++
1752   c-file-style:"stroustrup"
1753   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1754   indent-tabs-mode:nil
1755   fill-column:99
1756   End:
1757 */
1758 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :