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