Code

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