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 Geom::Point button_dt(desktop->w2d(button_pt));
615 SnapManager &m = desktop->namedview->snap_manager;
616 m.setup(desktop);
617 m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE);
618 m.unSetup();
620 tc->p0 = button_dt;
621 Inkscape::Rubberband::get(desktop)->start(desktop, tc->p0);
622 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
623 GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
624 NULL, event->button.time);
625 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
626 tc->creating = 1;
628 /* Processed */
629 return TRUE;
630 }
631 break;
632 case GDK_MOTION_NOTIFY:
633 if (tc->over_text) {
634 tc->over_text = 0;
635 // update cursor and statusbar: we are not over a text object now
636 event_context->cursor_shape = cursor_text_xpm;
637 event_context->hot_x = 7;
638 event_context->hot_y = 7;
639 sp_event_context_update_cursor(event_context);
640 desktop->event_context->defaultMessageContext()->clear();
641 }
643 if (tc->creating && event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
644 if ( event_context->within_tolerance
645 && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
646 && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
647 break; // do not drag if we're within tolerance from origin
648 }
649 // Once the user has moved farther than tolerance from the original location
650 // (indicating they intend to draw, not click), then always process the
651 // motion notify coordinates as given (no snapping back to origin)
652 event_context->within_tolerance = false;
654 Geom::Point const motion_pt(event->motion.x, event->motion.y);
655 Geom::Point p = desktop->w2d(motion_pt);
657 SnapManager &m = desktop->namedview->snap_manager;
658 m.setup(desktop);
659 m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE);
660 m.unSetup();
662 Inkscape::Rubberband::get(desktop)->move(p);
663 gobble_motion_events(GDK_BUTTON1_MASK);
665 // status text
666 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::X]), desktop->namedview->getDefaultMetric());
667 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::Y]), desktop->namedview->getDefaultMetric());
668 event_context->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Flowed text frame</b>: %s × %s"), xs->str, ys->str);
669 g_string_free(xs, FALSE);
670 g_string_free(ys, FALSE);
672 } else if (!sp_event_context_knot_mouseover(event_context)) {
673 SnapManager &m = desktop->namedview->snap_manager;
674 m.setup(desktop);
676 Geom::Point const motion_w(event->motion.x, event->motion.y);
677 Geom::Point motion_dt(desktop->w2d(motion_w));
678 m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE));
679 m.unSetup();
680 }
681 break;
682 case GDK_BUTTON_RELEASE:
683 if (event->button.button == 1 && !event_context->space_panning) {
684 sp_event_context_discard_delayed_snap_event(event_context);
686 Geom::Point p1 = desktop->w2d(Geom::Point(event->button.x, event->button.y));
688 SnapManager &m = desktop->namedview->snap_manager;
689 m.setup(desktop);
690 m.freeSnapReturnByRef(p1, Inkscape::SNAPSOURCE_NODE_HANDLE);
691 m.unSetup();
693 if (tc->grabbed) {
694 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
695 tc->grabbed = NULL;
696 }
698 Inkscape::Rubberband::get(desktop)->stop();
700 if (tc->creating && event_context->within_tolerance) {
701 /* Button 1, set X & Y & new item */
702 sp_desktop_selection(desktop)->clear();
703 tc->pdoc = desktop->dt2doc(p1);
704 tc->show = TRUE;
705 tc->phase = 1;
706 tc->nascent_object = 1; // new object was just created
708 /* Cursor */
709 sp_canvas_item_show(tc->cursor);
710 // Cursor height is defined by the new text object's font size; it needs to be set
711 // artificially here, for the text object does not exist yet:
712 double cursor_height = sp_desktop_get_font_size_tool(desktop);
713 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), p1, p1 + Geom::Point(0, cursor_height));
714 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
716 event_context->within_tolerance = false;
717 } else if (tc->creating) {
718 double cursor_height = sp_desktop_get_font_size_tool(desktop);
719 if (fabs(p1[Geom::Y] - tc->p0[Geom::Y]) > cursor_height) {
720 // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
721 SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
722 /* Set style */
723 sp_desktop_apply_style_tool(desktop, SP_OBJECT_REPR(ft), "/tools/text", true);
724 sp_desktop_selection(desktop)->set(ft);
725 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
726 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
727 _("Create flowed text"));
728 } else {
729 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
730 }
731 }
732 tc->creating = false;
733 return TRUE;
734 }
735 break;
736 case GDK_KEY_PRESS: {
737 guint const group0_keyval = get_group0_keyval(&event->key);
739 if (group0_keyval == GDK_KP_Add ||
740 group0_keyval == GDK_KP_Subtract) {
741 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
742 break; // otherwise pass on keypad +/- so they can zoom
743 }
745 if ((tc->text) || (tc->nascent_object)) {
746 // there is an active text object in this context, or a new object was just created
748 if (tc->unimode || !tc->imc
749 || (MOD__CTRL && MOD__SHIFT) // input methods tend to steal this for unimode,
750 // but we have our own so make sure they don't swallow it
751 || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
752 //IM did not consume the key, or we're in unimode
754 if (!MOD__CTRL_ONLY && tc->unimode) {
755 /* TODO: ISO 14755 (section 3 Definitions) says that we should also
756 accept the first 6 characters of alphabets other than the latin
757 alphabet "if the Latin alphabet is not used". The below is also
758 reasonable (viz. hope that the user's keyboard includes latin
759 characters and force latin interpretation -- just as we do for our
760 keyboard shortcuts), but differs from the ISO 14755
761 recommendation. */
762 switch (group0_keyval) {
763 case GDK_space:
764 case GDK_KP_Space: {
765 if (tc->unipos) {
766 insert_uni_char(tc);
767 }
768 /* Stay in unimode. */
769 show_curr_uni_char(tc);
770 return TRUE;
771 }
773 case GDK_BackSpace: {
774 g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
775 if (tc->unipos) {
776 tc->uni[--tc->unipos] = '\0';
777 }
778 show_curr_uni_char(tc);
779 return TRUE;
780 }
782 case GDK_Return:
783 case GDK_KP_Enter: {
784 if (tc->unipos) {
785 insert_uni_char(tc);
786 }
787 /* Exit unimode. */
788 tc->unimode = false;
789 event_context->defaultMessageContext()->clear();
790 return TRUE;
791 }
793 case GDK_Escape: {
794 // Cancel unimode.
795 tc->unimode = false;
796 gtk_im_context_reset(tc->imc);
797 event_context->defaultMessageContext()->clear();
798 return TRUE;
799 }
801 case GDK_Shift_L:
802 case GDK_Shift_R:
803 break;
805 default: {
806 if (g_ascii_isxdigit(group0_keyval)) {
807 g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
808 tc->uni[tc->unipos++] = group0_keyval;
809 tc->uni[tc->unipos] = '\0';
810 if (tc->unipos == 8) {
811 /* This behaviour is partly to allow us to continue to
812 use a fixed-length buffer for tc->uni. Reason for
813 choosing the number 8 is that it's the length of
814 ``canonical form'' mentioned in the ISO 14755 spec.
815 An advantage over choosing 6 is that it allows using
816 backspace for typos & misremembering when entering a
817 6-digit number. */
818 insert_uni_char(tc);
819 }
820 show_curr_uni_char(tc);
821 return TRUE;
822 } else {
823 /* The intent is to ignore but consume characters that could be
824 typos for hex digits. Gtk seems to ignore & consume all
825 non-hex-digits, and we do similar here. Though note that some
826 shortcuts (like keypad +/- for zoom) get processed before
827 reaching this code. */
828 return TRUE;
829 }
830 }
831 }
832 }
834 Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
835 Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
836 bool cursor_moved = false;
837 int screenlines = 1;
838 if (tc->text) {
839 double spacing = sp_te_get_average_linespacing(tc->text);
840 Geom::Rect const d = desktop->get_display_area();
841 screenlines = (int) floor(fabs(d.min()[Geom::Y] - d.max()[Geom::Y])/spacing) - 1;
842 if (screenlines <= 0)
843 screenlines = 1;
844 }
846 /* Neither unimode nor IM consumed key; process text tool shortcuts */
847 switch (group0_keyval) {
848 case GDK_x:
849 case GDK_X:
850 if (MOD__ALT_ONLY) {
851 desktop->setToolboxFocusTo ("altx-text");
852 return TRUE;
853 }
854 break;
855 case GDK_space:
856 if (MOD__CTRL_ONLY) {
857 /* No-break space */
858 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
859 sp_text_context_setup_text(tc);
860 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
861 }
862 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
863 sp_text_context_update_cursor(tc);
864 sp_text_context_update_text_selection(tc);
865 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
866 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
867 _("Insert no-break space"));
868 return TRUE;
869 }
870 break;
871 case GDK_U:
872 case GDK_u:
873 if (MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT)) {
874 if (tc->unimode) {
875 tc->unimode = false;
876 event_context->defaultMessageContext()->clear();
877 } else {
878 tc->unimode = true;
879 tc->unipos = 0;
880 event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
881 }
882 if (tc->imc) {
883 gtk_im_context_reset(tc->imc);
884 }
885 return TRUE;
886 }
887 break;
888 case GDK_B:
889 case GDK_b:
890 if (MOD__CTRL_ONLY && tc->text) {
891 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
892 SPCSSAttr *css = sp_repr_css_attr_new();
893 if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
894 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
895 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
896 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
897 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
898 sp_repr_css_set_property(css, "font-weight", "bold");
899 else
900 sp_repr_css_set_property(css, "font-weight", "normal");
901 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
902 sp_repr_css_attr_unref(css);
903 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
904 _("Make bold"));
905 sp_text_context_update_cursor(tc);
906 sp_text_context_update_text_selection(tc);
907 return TRUE;
908 }
909 break;
910 case GDK_I:
911 case GDK_i:
912 if (MOD__CTRL_ONLY && tc->text) {
913 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
914 SPCSSAttr *css = sp_repr_css_attr_new();
915 if (style->font_style.computed != SP_CSS_FONT_STYLE_NORMAL)
916 sp_repr_css_set_property(css, "font-style", "normal");
917 else
918 sp_repr_css_set_property(css, "font-style", "italic");
919 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
920 sp_repr_css_attr_unref(css);
921 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
922 _("Make italic"));
923 sp_text_context_update_cursor(tc);
924 sp_text_context_update_text_selection(tc);
925 return TRUE;
926 }
927 break;
929 case GDK_A:
930 case GDK_a:
931 if (MOD__CTRL_ONLY && tc->text) {
932 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
933 if (layout) {
934 tc->text_sel_start = layout->begin();
935 tc->text_sel_end = layout->end();
936 sp_text_context_update_cursor(tc);
937 sp_text_context_update_text_selection(tc);
938 return TRUE;
939 }
940 }
941 break;
943 case GDK_Return:
944 case GDK_KP_Enter:
945 {
946 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
947 sp_text_context_setup_text(tc);
948 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
949 }
951 iterator_pair enter_pair;
952 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, enter_pair);
953 (void)success; // TODO cleanup
954 tc->text_sel_start = tc->text_sel_end = enter_pair.first;
956 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
958 sp_text_context_update_cursor(tc);
959 sp_text_context_update_text_selection(tc);
960 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
961 _("New line"));
962 return TRUE;
963 }
964 case GDK_BackSpace:
965 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
967 bool noSelection = false;
969 if (tc->text_sel_start == tc->text_sel_end) {
970 tc->text_sel_start.prevCursorPosition();
971 noSelection = true;
972 }
974 iterator_pair bspace_pair;
975 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, bspace_pair);
977 if (noSelection) {
978 if (success) {
979 tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
980 } else { // nothing deleted
981 tc->text_sel_start = tc->text_sel_end = bspace_pair.second;
982 }
983 } else {
984 if (success) {
985 tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
986 } else { // nothing deleted
987 tc->text_sel_start = bspace_pair.first;
988 tc->text_sel_end = bspace_pair.second;
989 }
990 }
992 sp_text_context_update_cursor(tc);
993 sp_text_context_update_text_selection(tc);
994 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
995 _("Backspace"));
996 }
997 return TRUE;
998 case GDK_Delete:
999 case GDK_KP_Delete:
1000 if (tc->text) {
1001 bool noSelection = false;
1003 if (tc->text_sel_start == tc->text_sel_end) {
1004 tc->text_sel_end.nextCursorPosition();
1005 noSelection = true;
1006 }
1008 iterator_pair del_pair;
1009 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, del_pair);
1011 if (noSelection) {
1012 tc->text_sel_start = tc->text_sel_end = del_pair.first;
1013 } else {
1014 if (success) {
1015 tc->text_sel_start = tc->text_sel_end = del_pair.first;
1016 } else { // nothing deleted
1017 tc->text_sel_start = del_pair.first;
1018 tc->text_sel_end = del_pair.second;
1019 }
1020 }
1023 sp_text_context_update_cursor(tc);
1024 sp_text_context_update_text_selection(tc);
1025 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
1026 _("Delete"));
1027 }
1028 return TRUE;
1029 case GDK_Left:
1030 case GDK_KP_Left:
1031 case GDK_KP_4:
1032 if (tc->text) {
1033 if (MOD__ALT) {
1034 gint mul = 1 + gobble_key_events(
1035 get_group0_keyval(&event->key), 0); // with any mask
1036 if (MOD__SHIFT)
1037 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-10, 0));
1038 else
1039 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-1, 0));
1040 sp_text_context_update_cursor(tc);
1041 sp_text_context_update_text_selection(tc);
1042 sp_document_maybe_done(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT,
1043 _("Kern to the left"));
1044 } else {
1045 if (MOD__CTRL)
1046 tc->text_sel_end.cursorLeftWithControl();
1047 else
1048 tc->text_sel_end.cursorLeft();
1049 cursor_moved = true;
1050 break;
1051 }
1052 }
1053 return TRUE;
1054 case GDK_Right:
1055 case GDK_KP_Right:
1056 case GDK_KP_6:
1057 if (tc->text) {
1058 if (MOD__ALT) {
1059 gint mul = 1 + gobble_key_events(
1060 get_group0_keyval(&event->key), 0); // with any mask
1061 if (MOD__SHIFT)
1062 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*10, 0));
1063 else
1064 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*1, 0));
1065 sp_text_context_update_cursor(tc);
1066 sp_text_context_update_text_selection(tc);
1067 sp_document_maybe_done(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT,
1068 _("Kern to the right"));
1069 } else {
1070 if (MOD__CTRL)
1071 tc->text_sel_end.cursorRightWithControl();
1072 else
1073 tc->text_sel_end.cursorRight();
1074 cursor_moved = true;
1075 break;
1076 }
1077 }
1078 return TRUE;
1079 case GDK_Up:
1080 case GDK_KP_Up:
1081 case GDK_KP_8:
1082 if (tc->text) {
1083 if (MOD__ALT) {
1084 gint mul = 1 + gobble_key_events(
1085 get_group0_keyval(&event->key), 0); // with any mask
1086 if (MOD__SHIFT)
1087 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-10));
1088 else
1089 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-1));
1090 sp_text_context_update_cursor(tc);
1091 sp_text_context_update_text_selection(tc);
1092 sp_document_maybe_done(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT,
1093 _("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 sp_document_maybe_done(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT,
1119 _("Kern down"));
1121 } else {
1122 if (MOD__CTRL)
1123 tc->text_sel_end.cursorDownWithControl();
1124 else
1125 tc->text_sel_end.cursorDown();
1126 cursor_moved = true;
1127 break;
1128 }
1129 }
1130 return TRUE;
1131 case GDK_Home:
1132 case GDK_KP_Home:
1133 if (tc->text) {
1134 if (MOD__CTRL)
1135 tc->text_sel_end.thisStartOfShape();
1136 else
1137 tc->text_sel_end.thisStartOfLine();
1138 cursor_moved = true;
1139 break;
1140 }
1141 return TRUE;
1142 case GDK_End:
1143 case GDK_KP_End:
1144 if (tc->text) {
1145 if (MOD__CTRL)
1146 tc->text_sel_end.nextStartOfShape();
1147 else
1148 tc->text_sel_end.thisEndOfLine();
1149 cursor_moved = true;
1150 break;
1151 }
1152 return TRUE;
1153 case GDK_Page_Down:
1154 case GDK_KP_Page_Down:
1155 if (tc->text) {
1156 tc->text_sel_end.cursorDown(screenlines);
1157 cursor_moved = true;
1158 break;
1159 }
1160 return TRUE;
1161 case GDK_Page_Up:
1162 case GDK_KP_Page_Up:
1163 if (tc->text) {
1164 tc->text_sel_end.cursorUp(screenlines);
1165 cursor_moved = true;
1166 break;
1167 }
1168 return TRUE;
1169 case GDK_Escape:
1170 if (tc->creating) {
1171 tc->creating = 0;
1172 if (tc->grabbed) {
1173 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1174 tc->grabbed = NULL;
1175 }
1176 Inkscape::Rubberband::get(desktop)->stop();
1177 } else {
1178 sp_desktop_selection(desktop)->clear();
1179 }
1180 tc->nascent_object = FALSE;
1181 return TRUE;
1182 case GDK_bracketleft:
1183 if (tc->text) {
1184 if (MOD__ALT || MOD__CTRL) {
1185 if (MOD__ALT) {
1186 if (MOD__SHIFT) {
1187 // FIXME: alt+shift+[] does not work, don't know why
1188 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1189 } else {
1190 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1191 }
1192 } else {
1193 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -90);
1194 }
1195 sp_document_maybe_done(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT,
1196 _("Rotate counterclockwise"));
1197 sp_text_context_update_cursor(tc);
1198 sp_text_context_update_text_selection(tc);
1199 return TRUE;
1200 }
1201 }
1202 break;
1203 case GDK_bracketright:
1204 if (tc->text) {
1205 if (MOD__ALT || MOD__CTRL) {
1206 if (MOD__ALT) {
1207 if (MOD__SHIFT) {
1208 // FIXME: alt+shift+[] does not work, don't know why
1209 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1210 } else {
1211 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1212 }
1213 } else {
1214 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 90);
1215 }
1216 sp_document_maybe_done(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT,
1217 _("Rotate clockwise"));
1218 sp_text_context_update_cursor(tc);
1219 sp_text_context_update_text_selection(tc);
1220 return TRUE;
1221 }
1222 }
1223 break;
1224 case GDK_less:
1225 case GDK_comma:
1226 if (tc->text) {
1227 if (MOD__ALT) {
1228 if (MOD__CTRL) {
1229 if (MOD__SHIFT)
1230 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1231 else
1232 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1233 sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT,
1234 _("Contract line spacing"));
1236 } else {
1237 if (MOD__SHIFT)
1238 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1239 else
1240 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1241 sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT,
1242 _("Contract letter spacing"));
1244 }
1245 sp_text_context_update_cursor(tc);
1246 sp_text_context_update_text_selection(tc);
1247 return TRUE;
1248 }
1249 }
1250 break;
1251 case GDK_greater:
1252 case GDK_period:
1253 if (tc->text) {
1254 if (MOD__ALT) {
1255 if (MOD__CTRL) {
1256 if (MOD__SHIFT)
1257 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1258 else
1259 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1260 sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT,
1261 _("Expand line spacing"));
1263 } else {
1264 if (MOD__SHIFT)
1265 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1266 else
1267 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1268 sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT,
1269 _("Expand letter spacing"));
1271 }
1272 sp_text_context_update_cursor(tc);
1273 sp_text_context_update_text_selection(tc);
1274 return TRUE;
1275 }
1276 }
1277 break;
1278 default:
1279 break;
1280 }
1282 if (cursor_moved) {
1283 if (!MOD__SHIFT)
1284 tc->text_sel_start = tc->text_sel_end;
1285 if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1286 sp_text_context_update_cursor(tc);
1287 sp_text_context_update_text_selection(tc);
1288 }
1289 return TRUE;
1290 }
1292 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1293 } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1294 // except up/down that are swallowed to prevent the zoom field from activation
1295 if ((group0_keyval == GDK_Up ||
1296 group0_keyval == GDK_Down ||
1297 group0_keyval == GDK_KP_Up ||
1298 group0_keyval == GDK_KP_Down )
1299 && !MOD__CTRL_ONLY) {
1300 return TRUE;
1301 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1302 if (tc->creating) {
1303 tc->creating = 0;
1304 if (tc->grabbed) {
1305 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1306 tc->grabbed = NULL;
1307 }
1308 Inkscape::Rubberband::get(desktop)->stop();
1309 }
1310 } else if ((group0_keyval == GDK_x || group0_keyval == GDK_X) && MOD__ALT_ONLY) {
1311 desktop->setToolboxFocusTo ("altx-text");
1312 return TRUE;
1313 }
1314 }
1315 break;
1316 }
1318 case GDK_KEY_RELEASE:
1319 if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1320 return TRUE;
1321 }
1322 break;
1323 default:
1324 break;
1325 }
1327 // if nobody consumed it so far
1328 if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1329 return ((SPEventContextClass *) parent_class)->root_handler(event_context, event); // send event to parent
1330 } else {
1331 return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1332 }
1333 }
1335 /**
1336 Attempts to paste system clipboard into the currently edited text, returns true on success
1337 */
1338 bool
1339 sp_text_paste_inline(SPEventContext *ec)
1340 {
1341 if (!SP_IS_TEXT_CONTEXT(ec))
1342 return false;
1344 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1346 if ((tc->text) || (tc->nascent_object)) {
1347 // there is an active text object in this context, or a new object was just created
1349 Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1350 Glib::ustring const clip_text = refClipboard->wait_for_text();
1352 if (!clip_text.empty()) {
1353 // Fix for 244940
1354 // The XML standard defines the following as valid characters
1355 // (Extensible Markup Language (XML) 1.0 (Fourth Edition) paragraph 2.2)
1356 // char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
1357 // Since what comes in off the paste buffer will go right into XML, clean
1358 // the text here.
1359 Glib::ustring text(clip_text);
1360 Glib::ustring::iterator itr = text.begin();
1361 gunichar paste_string_uchar;
1363 while(itr != text.end())
1364 {
1365 paste_string_uchar = *itr;
1367 // Make sure we don't have a control character. We should really check
1368 // for the whole range above... Add the rest of the invalid cases from
1369 // above if we find additional issues
1370 if(paste_string_uchar >= 0x00000020 ||
1371 paste_string_uchar == 0x00000009 ||
1372 paste_string_uchar == 0x0000000A ||
1373 paste_string_uchar == 0x0000000D) {
1374 itr++;
1375 } else {
1376 itr = text.erase(itr);
1377 }
1378 }
1380 if (!tc->text) { // create text if none (i.e. if nascent_object)
1381 sp_text_context_setup_text(tc);
1382 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1383 }
1385 // using indices is slow in ustrings. Whatever.
1386 Glib::ustring::size_type begin = 0;
1387 for ( ; ; ) {
1388 Glib::ustring::size_type end = text.find('\n', begin);
1389 if (end == Glib::ustring::npos) {
1390 if (begin != text.length())
1391 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());
1392 break;
1393 }
1394 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());
1395 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1396 begin = end + 1;
1397 }
1398 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
1399 _("Paste text"));
1401 return true;
1402 }
1403 } // FIXME: else create and select a new object under cursor!
1405 return false;
1406 }
1408 /**
1409 Gets the raw characters that comprise the currently selected text, converting line
1410 breaks into lf characters.
1411 */
1412 Glib::ustring
1413 sp_text_get_selected_text(SPEventContext const *ec)
1414 {
1415 if (!SP_IS_TEXT_CONTEXT(ec))
1416 return "";
1417 SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1418 if (tc->text == NULL)
1419 return "";
1421 return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1422 }
1424 SPCSSAttr *
1425 sp_text_get_style_at_cursor(SPEventContext const *ec)
1426 {
1427 if (!SP_IS_TEXT_CONTEXT(ec))
1428 return NULL;
1429 SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1430 if (tc->text == NULL)
1431 return NULL;
1433 SPObject const *obj = sp_te_object_at_position(tc->text, tc->text_sel_end);
1434 if (obj)
1435 return take_style_from_item((SPItem *) obj);
1436 return NULL;
1437 }
1439 /**
1440 Deletes the currently selected characters. Returns false if there is no
1441 text selection currently.
1442 */
1443 bool sp_text_delete_selection(SPEventContext *ec)
1444 {
1445 if (!SP_IS_TEXT_CONTEXT(ec))
1446 return false;
1447 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1448 if (tc->text == NULL)
1449 return false;
1451 if (tc->text_sel_start == tc->text_sel_end)
1452 return false;
1454 iterator_pair pair;
1455 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair);
1458 if (success) {
1459 tc->text_sel_start = tc->text_sel_end = pair.first;
1460 } else { // nothing deleted
1461 tc->text_sel_start = pair.first;
1462 tc->text_sel_end = pair.second;
1463 }
1465 sp_text_context_update_cursor(tc);
1466 sp_text_context_update_text_selection(tc);
1468 return true;
1469 }
1471 /**
1472 * \param selection Should not be NULL.
1473 */
1474 static void
1475 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1476 {
1477 g_assert(selection != NULL);
1479 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1481 ec->shape_editor->unset_item(SH_KNOTHOLDER);
1482 SPItem *item = selection->singleItem();
1483 if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1484 ec->shape_editor->set_item(item, SH_KNOTHOLDER);
1485 }
1487 if (tc->text && (item != tc->text)) {
1488 sp_text_context_forget_text(tc);
1489 }
1490 tc->text = NULL;
1492 if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1493 tc->text = item;
1494 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1495 if (layout)
1496 tc->text_sel_start = tc->text_sel_end = layout->end();
1497 } else {
1498 tc->text = NULL;
1499 }
1501 // we update cursor without scrolling, because this position may not be final;
1502 // item_handler moves cusros to the point of click immediately
1503 sp_text_context_update_cursor(tc, false);
1504 sp_text_context_update_text_selection(tc);
1505 }
1507 static void
1508 sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, SPTextContext *tc)
1509 {
1510 sp_text_context_update_cursor(tc);
1511 sp_text_context_update_text_selection(tc);
1512 }
1514 static bool
1515 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1516 {
1517 if (tc->text == NULL)
1518 return false;
1519 if (tc->text_sel_start == tc->text_sel_end)
1520 return false; // will get picked up by the parent and applied to the whole text object
1522 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1523 sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1524 _("Set text style"));
1525 sp_text_context_update_cursor(tc);
1526 sp_text_context_update_text_selection(tc);
1528 return true;
1529 }
1531 static int
1532 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1533 {
1534 if (tc->text == NULL)
1535 return QUERY_STYLE_NOTHING;
1536 const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1537 if (layout == NULL)
1538 return QUERY_STYLE_NOTHING;
1539 sp_text_context_validate_cursor_iterators(tc);
1541 GSList *styles_list = NULL;
1543 Inkscape::Text::Layout::iterator begin_it, end_it;
1544 if (tc->text_sel_start < tc->text_sel_end) {
1545 begin_it = tc->text_sel_start;
1546 end_it = tc->text_sel_end;
1547 } else {
1548 begin_it = tc->text_sel_end;
1549 end_it = tc->text_sel_start;
1550 }
1551 if (begin_it == end_it)
1552 if (!begin_it.prevCharacter())
1553 end_it.nextCharacter();
1554 for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1555 SPObject const *pos_obj = 0;
1556 void *rawptr = 0;
1557 layout->getSourceOfCharacter(it, &rawptr);
1558 if (!rawptr || !SP_IS_OBJECT(rawptr))
1559 continue;
1560 pos_obj = SP_OBJECT(rawptr);
1561 while (SP_IS_STRING(pos_obj) && SP_OBJECT_PARENT(pos_obj)) {
1562 pos_obj = SP_OBJECT_PARENT(pos_obj); // SPStrings don't have style
1563 }
1564 styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1565 }
1567 int result = sp_desktop_query_style_from_list (styles_list, style, property);
1569 g_slist_free(styles_list);
1570 return result;
1571 }
1573 static void
1574 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1575 {
1576 if (tc->text == NULL)
1577 return;
1578 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1579 if (layout) { // undo can change the text length without us knowing it
1580 layout->validateIterator(&tc->text_sel_start);
1581 layout->validateIterator(&tc->text_sel_end);
1582 }
1583 }
1585 static void
1586 sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see)
1587 {
1588 GdkRectangle im_cursor = { 0, 0, 1, 1 };
1590 // due to interruptible display, tc may already be destroyed during a display update before
1591 // the cursor update (can't do both atomically, alas)
1592 if (!tc->desktop) return;
1594 if (tc->text) {
1595 Geom::Point p0, p1;
1596 sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1597 Geom::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1598 Geom::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1600 // scroll to show cursor
1601 if (scroll_to_see) {
1602 Geom::Point const center = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().midpoint();
1603 if (Geom::L2(d0 - center) > Geom::L2(d1 - center))
1604 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1605 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d0, 1.0);
1606 else
1607 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d1, 1.0);
1608 }
1610 sp_canvas_item_show(tc->cursor);
1611 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1613 /* fixme: ... need another transformation to get canvas widget coordinate space? */
1614 im_cursor.x = (int) floor(d0[Geom::X]);
1615 im_cursor.y = (int) floor(d0[Geom::Y]);
1616 im_cursor.width = (int) floor(d1[Geom::X]) - im_cursor.x;
1617 im_cursor.height = (int) floor(d1[Geom::Y]) - im_cursor.y;
1619 tc->show = TRUE;
1620 tc->phase = 1;
1622 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1623 int const nChars = layout->iteratorToCharIndex(layout->end());
1624 char const *trunc = "";
1625 bool truncated = false;
1626 if (layout->inputTruncated()) {
1627 truncated = true;
1628 trunc = _(" [truncated]");
1629 }
1630 if (SP_IS_FLOWTEXT(tc->text)) {
1631 SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1632 if (frame) {
1633 if (truncated) {
1634 SP_CTRLRECT(tc->frame)->setColor(0xff0000ff, false, 0);
1635 } else {
1636 SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0);
1637 }
1638 sp_canvas_item_show(tc->frame);
1639 Geom::OptRect frame_bbox = sp_item_bbox_desktop(frame);
1640 if (frame_bbox) {
1641 SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
1642 }
1643 }
1645 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);
1646 } else {
1647 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);
1648 }
1650 } else {
1651 sp_canvas_item_hide(tc->cursor);
1652 sp_canvas_item_hide(tc->frame);
1653 tc->show = FALSE;
1654 if (!tc->nascent_object) {
1655 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
1656 }
1657 }
1659 if (tc->imc) {
1660 gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1661 }
1662 SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1663 }
1665 static void sp_text_context_update_text_selection(SPTextContext *tc)
1666 {
1667 // due to interruptible display, tc may already be destroyed during a display update before
1668 // the selection update (can't do both atomically, alas)
1669 if (!tc->desktop) return;
1671 for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1672 sp_canvas_item_hide(*it);
1673 gtk_object_destroy(*it);
1674 }
1675 tc->text_selection_quads.clear();
1677 std::vector<Geom::Point> quads;
1678 if (tc->text != NULL)
1679 quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1680 for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1681 SPCanvasItem *quad_canvasitem;
1682 quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1683 // FIXME: make the color settable in prefs
1684 // for now, use semitrasparent blue, as cairo cannot do inversion :(
1685 sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x00777777);
1686 sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1687 sp_canvas_item_show(quad_canvasitem);
1688 tc->text_selection_quads.push_back(quad_canvasitem);
1689 }
1690 }
1692 static gint
1693 sp_text_context_timeout(SPTextContext *tc)
1694 {
1695 if (tc->show) {
1696 sp_canvas_item_show(tc->cursor);
1697 if (tc->phase) {
1698 tc->phase = 0;
1699 sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
1700 } else {
1701 tc->phase = 1;
1702 sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0xffffffff);
1703 }
1704 }
1706 return TRUE;
1707 }
1709 static void
1710 sp_text_context_forget_text(SPTextContext *tc)
1711 {
1712 if (! tc->text) return;
1713 SPItem *ti = tc->text;
1714 (void)ti;
1715 /* We have to set it to zero,
1716 * or selection changed signal messes everything up */
1717 tc->text = NULL;
1719 /* FIXME: this automatic deletion when nothing is inputted crashes the XML edittor and also crashes when duplicating an empty flowtext.
1720 So don't create an empty flowtext in the first place? Create it when first character is typed.
1721 */
1722 /*
1723 if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1724 Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1725 // the repr may already have been unparented
1726 // if we were called e.g. as the result of
1727 // an undo or the element being removed from
1728 // the XML editor
1729 if ( text_repr && sp_repr_parent(text_repr) ) {
1730 sp_repr_unparent(text_repr);
1731 sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1732 _("Remove empty text"));
1733 }
1734 }
1735 */
1736 }
1738 gint
1739 sptc_focus_in(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1740 {
1741 gtk_im_context_focus_in(tc->imc);
1742 return FALSE;
1743 }
1745 gint
1746 sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1747 {
1748 gtk_im_context_focus_out(tc->imc);
1749 return FALSE;
1750 }
1752 static void
1753 sptc_commit(GtkIMContext */*imc*/, gchar *string, SPTextContext *tc)
1754 {
1755 if (!tc->text) {
1756 sp_text_context_setup_text(tc);
1757 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1758 }
1760 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1761 sp_text_context_update_cursor(tc);
1762 sp_text_context_update_text_selection(tc);
1764 sp_document_done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT,
1765 _("Type text"));
1766 }
1768 void
1769 sp_text_context_place_cursor (SPTextContext *tc, SPObject *text, Inkscape::Text::Layout::iterator where)
1770 {
1771 SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text);
1772 tc->text_sel_start = tc->text_sel_end = where;
1773 sp_text_context_update_cursor(tc);
1774 sp_text_context_update_text_selection(tc);
1775 }
1777 void
1778 sp_text_context_place_cursor_at (SPTextContext *tc, SPObject *text, Geom::Point const p)
1779 {
1780 SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text);
1781 sp_text_context_place_cursor (tc, text, sp_te_get_position_by_coords(tc->text, p));
1782 }
1784 Inkscape::Text::Layout::iterator *sp_text_context_get_cursor_position(SPTextContext *tc, SPObject *text)
1785 {
1786 if (text != tc->text)
1787 return NULL;
1788 return &(tc->text_sel_end);
1789 }
1792 /*
1793 Local Variables:
1794 mode:c++
1795 c-file-style:"stroustrup"
1796 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1797 indent-tabs-mode:nil
1798 fill-column:99
1799 End:
1800 */
1801 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :