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