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