e277ff58492713224ddd39720c3fab090d9bc3d1
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 boost::optional<Geom::Rect> 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(ec->desktop->currentRoot())->getRelativeTransform(ec->desktop->currentLayer());
515 text_item->updateRepr();
516 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
517 _("Create text"));
518 }
520 /**
521 * Insert the character indicated by tc.uni to replace the current selection,
522 * and reset tc.uni/tc.unipos to empty string.
523 *
524 * \pre tc.uni/tc.unipos non-empty.
525 */
526 static void
527 insert_uni_char(SPTextContext *const tc)
528 {
529 g_return_if_fail(tc->unipos
530 && tc->unipos < sizeof(tc->uni)
531 && tc->uni[tc->unipos] == '\0');
532 unsigned int uv;
533 sscanf(tc->uni, "%x", &uv);
534 tc->unipos = 0;
535 tc->uni[tc->unipos] = '\0';
537 if ( !g_unichar_isprint(static_cast<gunichar>(uv))
538 && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
539 // This may be due to bad input, so it goes to statusbar.
540 tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
541 _("Non-printable character"));
542 } else {
543 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
544 sp_text_context_setup_text(tc);
545 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
546 }
548 gchar u[10];
549 guint const len = g_unichar_to_utf8(uv, u);
550 u[len] = '\0';
552 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
553 sp_text_context_update_cursor(tc);
554 sp_text_context_update_text_selection(tc);
555 sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM,
556 _("Insert Unicode character"));
557 }
558 }
560 static void
561 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
562 {
563 unsigned int uv;
564 sscanf(hex, "%x", &uv);
565 if (!g_unichar_isprint((gunichar) uv)) {
566 uv = 0xfffd;
567 }
568 guint const len = g_unichar_to_utf8(uv, utf8);
569 utf8[len] = '\0';
570 }
572 static void
573 show_curr_uni_char(SPTextContext *const tc)
574 {
575 g_return_if_fail(tc->unipos < sizeof(tc->uni)
576 && tc->uni[tc->unipos] == '\0');
577 if (tc->unipos) {
578 char utf8[10];
579 hex_to_printable_utf8_buf(tc->uni, utf8);
581 /* Status bar messages are in pango markup, so we need xml escaping. */
582 if (utf8[1] == '\0') {
583 switch(utf8[0]) {
584 case '<': strcpy(utf8, "<"); break;
585 case '>': strcpy(utf8, ">"); break;
586 case '&': strcpy(utf8, "&"); break;
587 default: break;
588 }
589 }
590 tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
591 _("Unicode (<b>Enter</b> to finish): %s: %s"), tc->uni, utf8);
592 } else {
593 tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
594 }
595 }
597 static gint
598 sp_text_context_root_handler(SPEventContext *const event_context, GdkEvent *const event)
599 {
600 SPTextContext *const tc = SP_TEXT_CONTEXT(event_context);
602 SPDesktop *desktop = event_context->desktop;
604 sp_canvas_item_hide(tc->indicator);
606 sp_text_context_validate_cursor_iterators(tc);
608 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
609 event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
611 switch (event->type) {
612 case GDK_BUTTON_PRESS:
613 if (event->button.button == 1 && !event_context->space_panning) {
615 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
616 return TRUE;
617 }
619 // save drag origin
620 event_context->xp = (gint) event->button.x;
621 event_context->yp = (gint) event->button.y;
622 event_context->within_tolerance = true;
624 Geom::Point const button_pt(event->button.x, event->button.y);
625 tc->p0 = desktop->w2d(button_pt);
626 Inkscape::Rubberband::get(desktop)->start(desktop, tc->p0);
627 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
628 GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
629 NULL, event->button.time);
630 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
631 tc->creating = 1;
633 /* Processed */
634 return TRUE;
635 }
636 break;
637 case GDK_MOTION_NOTIFY:
638 if (tc->over_text) {
639 tc->over_text = 0;
640 // update cursor and statusbar: we are not over a text object now
641 event_context->cursor_shape = cursor_text_xpm;
642 event_context->hot_x = 7;
643 event_context->hot_y = 7;
644 sp_event_context_update_cursor(event_context);
645 desktop->event_context->defaultMessageContext()->clear();
646 }
648 if (tc->creating && event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
649 if ( event_context->within_tolerance
650 && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
651 && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
652 break; // do not drag if we're within tolerance from origin
653 }
654 // Once the user has moved farther than tolerance from the original location
655 // (indicating they intend to draw, not click), then always process the
656 // motion notify coordinates as given (no snapping back to origin)
657 event_context->within_tolerance = false;
659 Geom::Point const motion_pt(event->motion.x, event->motion.y);
660 Geom::Point const p = desktop->w2d(motion_pt);
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 }
673 break;
674 case GDK_BUTTON_RELEASE:
675 if (event->button.button == 1 && !event_context->space_panning) {
677 if (tc->grabbed) {
678 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
679 tc->grabbed = NULL;
680 }
682 Inkscape::Rubberband::get(desktop)->stop();
684 if (tc->creating && event_context->within_tolerance) {
685 /* Button 1, set X & Y & new item */
686 sp_desktop_selection(desktop)->clear();
687 Geom::Point dtp = desktop->w2d(Geom::Point(event->button.x, event->button.y));
688 tc->pdoc = sp_desktop_dt2root_xy_point(desktop, dtp);
690 tc->show = TRUE;
691 tc->phase = 1;
692 tc->nascent_object = 1; // new object was just created
694 /* Cursor */
695 sp_canvas_item_show(tc->cursor);
696 // Cursor height is defined by the new text object's font size; it needs to be set
697 // articifically here, for the text object does not exist yet:
698 double cursor_height = sp_desktop_get_font_size_tool(desktop);
699 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), dtp, dtp + Geom::Point(0, cursor_height));
700 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
702 event_context->within_tolerance = false;
703 } else if (tc->creating) {
704 Geom::Point const button_pt(event->button.x, event->button.y);
705 Geom::Point p1 = desktop->w2d(button_pt);
706 double cursor_height = sp_desktop_get_font_size_tool(desktop);
707 if (fabs(p1[Geom::Y] - tc->p0[Geom::Y]) > cursor_height) {
708 // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
709 SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
710 /* Set style */
711 sp_desktop_apply_style_tool(desktop, SP_OBJECT_REPR(ft), "/tools/text", true);
712 sp_desktop_selection(desktop)->set(ft);
713 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
714 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
715 _("Create flowed text"));
716 } else {
717 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
718 }
719 }
720 tc->creating = false;
721 return TRUE;
722 }
723 break;
724 case GDK_KEY_PRESS: {
725 guint const group0_keyval = get_group0_keyval(&event->key);
727 if (group0_keyval == GDK_KP_Add ||
728 group0_keyval == GDK_KP_Subtract) {
729 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
730 break; // otherwise pass on keypad +/- so they can zoom
731 }
733 if ((tc->text) || (tc->nascent_object)) {
734 // there is an active text object in this context, or a new object was just created
736 if (tc->unimode || !tc->imc
737 || (MOD__CTRL && MOD__SHIFT) // input methods tend to steal this for unimode,
738 // but we have our own so make sure they don't swallow it
739 || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
740 //IM did not consume the key, or we're in unimode
742 if (!MOD__CTRL_ONLY && tc->unimode) {
743 /* TODO: ISO 14755 (section 3 Definitions) says that we should also
744 accept the first 6 characters of alphabets other than the latin
745 alphabet "if the Latin alphabet is not used". The below is also
746 reasonable (viz. hope that the user's keyboard includes latin
747 characters and force latin interpretation -- just as we do for our
748 keyboard shortcuts), but differs from the ISO 14755
749 recommendation. */
750 switch (group0_keyval) {
751 case GDK_space:
752 case GDK_KP_Space: {
753 if (tc->unipos) {
754 insert_uni_char(tc);
755 }
756 /* Stay in unimode. */
757 show_curr_uni_char(tc);
758 return TRUE;
759 }
761 case GDK_BackSpace: {
762 g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
763 if (tc->unipos) {
764 tc->uni[--tc->unipos] = '\0';
765 }
766 show_curr_uni_char(tc);
767 return TRUE;
768 }
770 case GDK_Return:
771 case GDK_KP_Enter: {
772 if (tc->unipos) {
773 insert_uni_char(tc);
774 }
775 /* Exit unimode. */
776 tc->unimode = false;
777 event_context->defaultMessageContext()->clear();
778 return TRUE;
779 }
781 case GDK_Escape: {
782 // Cancel unimode.
783 tc->unimode = false;
784 gtk_im_context_reset(tc->imc);
785 event_context->defaultMessageContext()->clear();
786 return TRUE;
787 }
789 case GDK_Shift_L:
790 case GDK_Shift_R:
791 break;
793 default: {
794 if (g_ascii_isxdigit(group0_keyval)) {
795 g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
796 tc->uni[tc->unipos++] = group0_keyval;
797 tc->uni[tc->unipos] = '\0';
798 if (tc->unipos == 8) {
799 /* This behaviour is partly to allow us to continue to
800 use a fixed-length buffer for tc->uni. Reason for
801 choosing the number 8 is that it's the length of
802 ``canonical form'' mentioned in the ISO 14755 spec.
803 An advantage over choosing 6 is that it allows using
804 backspace for typos & misremembering when entering a
805 6-digit number. */
806 insert_uni_char(tc);
807 }
808 show_curr_uni_char(tc);
809 return TRUE;
810 } else {
811 /* The intent is to ignore but consume characters that could be
812 typos for hex digits. Gtk seems to ignore & consume all
813 non-hex-digits, and we do similar here. Though note that some
814 shortcuts (like keypad +/- for zoom) get processed before
815 reaching this code. */
816 return TRUE;
817 }
818 }
819 }
820 }
822 Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
823 Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
824 bool cursor_moved = false;
825 int screenlines = 1;
826 if (tc->text) {
827 double spacing = sp_te_get_average_linespacing(tc->text);
828 Geom::Rect const d = desktop->get_display_area();
829 screenlines = (int) floor(fabs(d.min()[Geom::Y] - d.max()[Geom::Y])/spacing) - 1;
830 if (screenlines <= 0)
831 screenlines = 1;
832 }
834 /* Neither unimode nor IM consumed key; process text tool shortcuts */
835 switch (group0_keyval) {
836 case GDK_x:
837 case GDK_X:
838 if (MOD__ALT_ONLY) {
839 desktop->setToolboxFocusTo ("altx-text");
840 return TRUE;
841 }
842 break;
843 case GDK_space:
844 if (MOD__CTRL_ONLY) {
845 /* No-break space */
846 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
847 sp_text_context_setup_text(tc);
848 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
849 }
850 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
851 sp_text_context_update_cursor(tc);
852 sp_text_context_update_text_selection(tc);
853 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
854 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
855 _("Insert no-break space"));
856 return TRUE;
857 }
858 break;
859 case GDK_U:
860 case GDK_u:
861 if (MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT)) {
862 if (tc->unimode) {
863 tc->unimode = false;
864 event_context->defaultMessageContext()->clear();
865 } else {
866 tc->unimode = true;
867 tc->unipos = 0;
868 event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
869 }
870 if (tc->imc) {
871 gtk_im_context_reset(tc->imc);
872 }
873 return TRUE;
874 }
875 break;
876 case GDK_B:
877 case GDK_b:
878 if (MOD__CTRL_ONLY && tc->text) {
879 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
880 SPCSSAttr *css = sp_repr_css_attr_new();
881 if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
882 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
883 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
884 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
885 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
886 sp_repr_css_set_property(css, "font-weight", "bold");
887 else
888 sp_repr_css_set_property(css, "font-weight", "normal");
889 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
890 sp_repr_css_attr_unref(css);
891 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
892 _("Make bold"));
893 sp_text_context_update_cursor(tc);
894 sp_text_context_update_text_selection(tc);
895 return TRUE;
896 }
897 break;
898 case GDK_I:
899 case GDK_i:
900 if (MOD__CTRL_ONLY && tc->text) {
901 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
902 SPCSSAttr *css = sp_repr_css_attr_new();
903 if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL)
904 sp_repr_css_set_property(css, "font-style", "italic");
905 else
906 sp_repr_css_set_property(css, "font-style", "normal");
907 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
908 sp_repr_css_attr_unref(css);
909 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
910 _("Make italic"));
911 sp_text_context_update_cursor(tc);
912 sp_text_context_update_text_selection(tc);
913 return TRUE;
914 }
915 break;
917 case GDK_A:
918 case GDK_a:
919 if (MOD__CTRL_ONLY && tc->text) {
920 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
921 if (layout) {
922 tc->text_sel_start = layout->begin();
923 tc->text_sel_end = layout->end();
924 sp_text_context_update_cursor(tc);
925 sp_text_context_update_text_selection(tc);
926 return TRUE;
927 }
928 }
929 break;
931 case GDK_Return:
932 case GDK_KP_Enter:
933 {
934 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
935 sp_text_context_setup_text(tc);
936 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
937 }
939 iterator_pair enter_pair;
940 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, enter_pair);
941 (void)success; // TODO cleanup
942 tc->text_sel_start = tc->text_sel_end = enter_pair.first;
944 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
946 sp_text_context_update_cursor(tc);
947 sp_text_context_update_text_selection(tc);
948 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
949 _("New line"));
950 return TRUE;
951 }
952 case GDK_BackSpace:
953 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
955 bool noSelection = false;
957 if (tc->text_sel_start == tc->text_sel_end) {
958 tc->text_sel_start.prevCursorPosition();
959 noSelection = true;
960 }
962 iterator_pair bspace_pair;
963 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, bspace_pair);
965 if (noSelection) {
966 if (success) {
967 tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
968 } else { // nothing deleted
969 tc->text_sel_start = tc->text_sel_end = bspace_pair.second;
970 }
971 } else {
972 if (success) {
973 tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
974 } else { // nothing deleted
975 tc->text_sel_start = bspace_pair.first;
976 tc->text_sel_end = bspace_pair.second;
977 }
978 }
980 sp_text_context_update_cursor(tc);
981 sp_text_context_update_text_selection(tc);
982 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
983 _("Backspace"));
984 }
985 return TRUE;
986 case GDK_Delete:
987 case GDK_KP_Delete:
988 if (tc->text) {
989 bool noSelection = false;
991 if (tc->text_sel_start == tc->text_sel_end) {
992 tc->text_sel_end.nextCursorPosition();
993 noSelection = true;
994 }
996 iterator_pair del_pair;
997 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, del_pair);
999 if (noSelection) {
1000 tc->text_sel_start = tc->text_sel_end = del_pair.first;
1001 } else {
1002 if (success) {
1003 tc->text_sel_start = tc->text_sel_end = del_pair.first;
1004 } else { // nothing deleted
1005 tc->text_sel_start = del_pair.first;
1006 tc->text_sel_end = del_pair.second;
1007 }
1008 }
1011 sp_text_context_update_cursor(tc);
1012 sp_text_context_update_text_selection(tc);
1013 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
1014 _("Delete"));
1015 }
1016 return TRUE;
1017 case GDK_Left:
1018 case GDK_KP_Left:
1019 case GDK_KP_4:
1020 if (tc->text) {
1021 if (MOD__ALT) {
1022 gint mul = 1 + gobble_key_events(
1023 get_group0_keyval(&event->key), 0); // with any mask
1024 if (MOD__SHIFT)
1025 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-10, 0));
1026 else
1027 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-1, 0));
1028 sp_text_context_update_cursor(tc);
1029 sp_text_context_update_text_selection(tc);
1030 sp_document_maybe_done(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT,
1031 _("Kern to the left"));
1032 } else {
1033 if (MOD__CTRL)
1034 tc->text_sel_end.cursorLeftWithControl();
1035 else
1036 tc->text_sel_end.cursorLeft();
1037 cursor_moved = true;
1038 break;
1039 }
1040 }
1041 return TRUE;
1042 case GDK_Right:
1043 case GDK_KP_Right:
1044 case GDK_KP_6:
1045 if (tc->text) {
1046 if (MOD__ALT) {
1047 gint mul = 1 + gobble_key_events(
1048 get_group0_keyval(&event->key), 0); // with any mask
1049 if (MOD__SHIFT)
1050 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*10, 0));
1051 else
1052 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*1, 0));
1053 sp_text_context_update_cursor(tc);
1054 sp_text_context_update_text_selection(tc);
1055 sp_document_maybe_done(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT,
1056 _("Kern to the right"));
1057 } else {
1058 if (MOD__CTRL)
1059 tc->text_sel_end.cursorRightWithControl();
1060 else
1061 tc->text_sel_end.cursorRight();
1062 cursor_moved = true;
1063 break;
1064 }
1065 }
1066 return TRUE;
1067 case GDK_Up:
1068 case GDK_KP_Up:
1069 case GDK_KP_8:
1070 if (tc->text) {
1071 if (MOD__ALT) {
1072 gint mul = 1 + gobble_key_events(
1073 get_group0_keyval(&event->key), 0); // with any mask
1074 if (MOD__SHIFT)
1075 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-10));
1076 else
1077 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-1));
1078 sp_text_context_update_cursor(tc);
1079 sp_text_context_update_text_selection(tc);
1080 sp_document_maybe_done(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT,
1081 _("Kern up"));
1083 } else {
1084 if (MOD__CTRL)
1085 tc->text_sel_end.cursorUpWithControl();
1086 else
1087 tc->text_sel_end.cursorUp();
1088 cursor_moved = true;
1089 break;
1090 }
1091 }
1092 return TRUE;
1093 case GDK_Down:
1094 case GDK_KP_Down:
1095 case GDK_KP_2:
1096 if (tc->text) {
1097 if (MOD__ALT) {
1098 gint mul = 1 + gobble_key_events(
1099 get_group0_keyval(&event->key), 0); // with any mask
1100 if (MOD__SHIFT)
1101 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*10));
1102 else
1103 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*1));
1104 sp_text_context_update_cursor(tc);
1105 sp_text_context_update_text_selection(tc);
1106 sp_document_maybe_done(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT,
1107 _("Kern down"));
1109 } else {
1110 if (MOD__CTRL)
1111 tc->text_sel_end.cursorDownWithControl();
1112 else
1113 tc->text_sel_end.cursorDown();
1114 cursor_moved = true;
1115 break;
1116 }
1117 }
1118 return TRUE;
1119 case GDK_Home:
1120 case GDK_KP_Home:
1121 if (tc->text) {
1122 if (MOD__CTRL)
1123 tc->text_sel_end.thisStartOfShape();
1124 else
1125 tc->text_sel_end.thisStartOfLine();
1126 cursor_moved = true;
1127 break;
1128 }
1129 return TRUE;
1130 case GDK_End:
1131 case GDK_KP_End:
1132 if (tc->text) {
1133 if (MOD__CTRL)
1134 tc->text_sel_end.nextStartOfShape();
1135 else
1136 tc->text_sel_end.thisEndOfLine();
1137 cursor_moved = true;
1138 break;
1139 }
1140 return TRUE;
1141 case GDK_Page_Down:
1142 case GDK_KP_Page_Down:
1143 if (tc->text) {
1144 tc->text_sel_end.cursorDown(screenlines);
1145 cursor_moved = true;
1146 break;
1147 }
1148 return TRUE;
1149 case GDK_Page_Up:
1150 case GDK_KP_Page_Up:
1151 if (tc->text) {
1152 tc->text_sel_end.cursorUp(screenlines);
1153 cursor_moved = true;
1154 break;
1155 }
1156 return TRUE;
1157 case GDK_Escape:
1158 if (tc->creating) {
1159 tc->creating = 0;
1160 if (tc->grabbed) {
1161 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1162 tc->grabbed = NULL;
1163 }
1164 Inkscape::Rubberband::get(desktop)->stop();
1165 } else {
1166 sp_desktop_selection(desktop)->clear();
1167 }
1168 tc->nascent_object = FALSE;
1169 return TRUE;
1170 case GDK_bracketleft:
1171 if (tc->text) {
1172 if (MOD__ALT || MOD__CTRL) {
1173 if (MOD__ALT) {
1174 if (MOD__SHIFT) {
1175 // FIXME: alt+shift+[] does not work, don't know why
1176 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1177 } else {
1178 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1179 }
1180 } else {
1181 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -90);
1182 }
1183 sp_document_maybe_done(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT,
1184 _("Rotate counterclockwise"));
1185 sp_text_context_update_cursor(tc);
1186 sp_text_context_update_text_selection(tc);
1187 return TRUE;
1188 }
1189 }
1190 break;
1191 case GDK_bracketright:
1192 if (tc->text) {
1193 if (MOD__ALT || MOD__CTRL) {
1194 if (MOD__ALT) {
1195 if (MOD__SHIFT) {
1196 // FIXME: alt+shift+[] does not work, don't know why
1197 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1198 } else {
1199 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1200 }
1201 } else {
1202 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 90);
1203 }
1204 sp_document_maybe_done(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT,
1205 _("Rotate clockwise"));
1206 sp_text_context_update_cursor(tc);
1207 sp_text_context_update_text_selection(tc);
1208 return TRUE;
1209 }
1210 }
1211 break;
1212 case GDK_less:
1213 case GDK_comma:
1214 if (tc->text) {
1215 if (MOD__ALT) {
1216 if (MOD__CTRL) {
1217 if (MOD__SHIFT)
1218 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1219 else
1220 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1221 sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT,
1222 _("Contract line spacing"));
1224 } else {
1225 if (MOD__SHIFT)
1226 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1227 else
1228 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1229 sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT,
1230 _("Contract letter spacing"));
1232 }
1233 sp_text_context_update_cursor(tc);
1234 sp_text_context_update_text_selection(tc);
1235 return TRUE;
1236 }
1237 }
1238 break;
1239 case GDK_greater:
1240 case GDK_period:
1241 if (tc->text) {
1242 if (MOD__ALT) {
1243 if (MOD__CTRL) {
1244 if (MOD__SHIFT)
1245 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1246 else
1247 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1248 sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT,
1249 _("Expand line spacing"));
1251 } else {
1252 if (MOD__SHIFT)
1253 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1254 else
1255 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1256 sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT,
1257 _("Expand letter spacing"));
1259 }
1260 sp_text_context_update_cursor(tc);
1261 sp_text_context_update_text_selection(tc);
1262 return TRUE;
1263 }
1264 }
1265 break;
1266 default:
1267 break;
1268 }
1270 if (cursor_moved) {
1271 if (!MOD__SHIFT)
1272 tc->text_sel_start = tc->text_sel_end;
1273 if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1274 sp_text_context_update_cursor(tc);
1275 sp_text_context_update_text_selection(tc);
1276 }
1277 return TRUE;
1278 }
1280 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1281 } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1282 // except up/down that are swallowed to prevent the zoom field from activation
1283 if ((group0_keyval == GDK_Up ||
1284 group0_keyval == GDK_Down ||
1285 group0_keyval == GDK_KP_Up ||
1286 group0_keyval == GDK_KP_Down )
1287 && !MOD__CTRL_ONLY) {
1288 return TRUE;
1289 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1290 if (tc->creating) {
1291 tc->creating = 0;
1292 if (tc->grabbed) {
1293 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1294 tc->grabbed = NULL;
1295 }
1296 Inkscape::Rubberband::get(desktop)->stop();
1297 }
1298 }
1299 }
1300 break;
1301 }
1303 case GDK_KEY_RELEASE:
1304 if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1305 return TRUE;
1306 }
1307 break;
1308 default:
1309 break;
1310 }
1312 // if nobody consumed it so far
1313 if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1314 return ((SPEventContextClass *) parent_class)->root_handler(event_context, event); // send event to parent
1315 } else {
1316 return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1317 }
1318 }
1320 /**
1321 Attempts to paste system clipboard into the currently edited text, returns true on success
1322 */
1323 bool
1324 sp_text_paste_inline(SPEventContext *ec)
1325 {
1326 if (!SP_IS_TEXT_CONTEXT(ec))
1327 return false;
1329 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1331 if ((tc->text) || (tc->nascent_object)) {
1332 // there is an active text object in this context, or a new object was just created
1334 Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1335 Glib::ustring const clip_text = refClipboard->wait_for_text();
1337 if (!clip_text.empty()) {
1338 // Fix for 244940
1339 // The XML standard defines the following as valid characters
1340 // (Extensible Markup Language (XML) 1.0 (Fourth Edition) paragraph 2.2)
1341 // char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
1342 // Since what comes in off the paste buffer will go right into XML, clean
1343 // the text here.
1344 Glib::ustring text(clip_text);
1345 Glib::ustring::iterator itr = text.begin();
1346 gunichar paste_string_uchar;
1348 while(itr != text.end())
1349 {
1350 paste_string_uchar = *itr;
1352 // Make sure we don't have a control character. We should really check
1353 // for the whole range above... Add the rest of the invalid cases from
1354 // above if we find additional issues
1355 if(paste_string_uchar >= 0x00000020 ||
1356 paste_string_uchar == 0x00000009 ||
1357 paste_string_uchar == 0x0000000A ||
1358 paste_string_uchar == 0x0000000D) {
1359 itr++;
1360 } else {
1361 itr = text.erase(itr);
1362 }
1363 }
1365 if (!tc->text) { // create text if none (i.e. if nascent_object)
1366 sp_text_context_setup_text(tc);
1367 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1368 }
1370 // using indices is slow in ustrings. Whatever.
1371 Glib::ustring::size_type begin = 0;
1372 for ( ; ; ) {
1373 Glib::ustring::size_type end = text.find('\n', begin);
1374 if (end == Glib::ustring::npos) {
1375 if (begin != text.length())
1376 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());
1377 break;
1378 }
1379 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());
1380 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1381 begin = end + 1;
1382 }
1383 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
1384 _("Paste text"));
1386 return true;
1387 }
1388 } // FIXME: else create and select a new object under cursor!
1390 return false;
1391 }
1393 /**
1394 Gets the raw characters that comprise the currently selected text, converting line
1395 breaks into lf characters.
1396 */
1397 Glib::ustring
1398 sp_text_get_selected_text(SPEventContext const *ec)
1399 {
1400 if (!SP_IS_TEXT_CONTEXT(ec))
1401 return "";
1402 SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1403 if (tc->text == NULL)
1404 return "";
1406 return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1407 }
1409 /**
1410 Deletes the currently selected characters. Returns false if there is no
1411 text selection currently.
1412 */
1413 bool sp_text_delete_selection(SPEventContext *ec)
1414 {
1415 if (!SP_IS_TEXT_CONTEXT(ec))
1416 return false;
1417 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1418 if (tc->text == NULL)
1419 return false;
1421 if (tc->text_sel_start == tc->text_sel_end)
1422 return false;
1424 iterator_pair pair;
1425 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair);
1428 if (success) {
1429 tc->text_sel_start = tc->text_sel_end = pair.first;
1430 } else { // nothing deleted
1431 tc->text_sel_start = pair.first;
1432 tc->text_sel_end = pair.second;
1433 }
1435 sp_text_context_update_cursor(tc);
1436 sp_text_context_update_text_selection(tc);
1438 return true;
1439 }
1441 /**
1442 * \param selection Should not be NULL.
1443 */
1444 static void
1445 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1446 {
1447 g_assert(selection != NULL);
1449 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1451 if (ec->shape_knot_holder) { // destroy knotholder
1452 delete ec->shape_knot_holder;
1453 ec->shape_knot_holder = NULL;
1454 }
1456 if (ec->shape_repr) { // remove old listener
1457 sp_repr_remove_listener_by_data(ec->shape_repr, ec);
1458 Inkscape::GC::release(ec->shape_repr);
1459 ec->shape_repr = 0;
1460 }
1462 SPItem *item = selection->singleItem();
1463 if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1464 ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
1465 Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
1466 if (shape_repr) {
1467 ec->shape_repr = shape_repr;
1468 Inkscape::GC::anchor(shape_repr);
1469 sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
1470 }
1471 }
1473 if (tc->text && (item != tc->text)) {
1474 sp_text_context_forget_text(tc);
1475 }
1476 tc->text = NULL;
1478 if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1479 tc->text = item;
1480 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1481 if (layout)
1482 tc->text_sel_start = tc->text_sel_end = layout->end();
1483 } else {
1484 tc->text = NULL;
1485 }
1487 // we update cursor without scrolling, because this position may not be final;
1488 // item_handler moves cusros to the point of click immediately
1489 sp_text_context_update_cursor(tc, false);
1490 sp_text_context_update_text_selection(tc);
1491 }
1493 static void
1494 sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, SPTextContext *tc)
1495 {
1496 sp_text_context_update_cursor(tc);
1497 sp_text_context_update_text_selection(tc);
1498 }
1500 static bool
1501 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1502 {
1503 if (tc->text == NULL)
1504 return false;
1505 if (tc->text_sel_start == tc->text_sel_end)
1506 return false; // will get picked up by the parent and applied to the whole text object
1508 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1509 sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1510 _("Set text style"));
1511 sp_text_context_update_cursor(tc);
1512 sp_text_context_update_text_selection(tc);
1514 return true;
1515 }
1517 static int
1518 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1519 {
1520 if (tc->text == NULL)
1521 return QUERY_STYLE_NOTHING;
1522 const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1523 if (layout == NULL)
1524 return QUERY_STYLE_NOTHING;
1525 sp_text_context_validate_cursor_iterators(tc);
1527 GSList *styles_list = NULL;
1529 Inkscape::Text::Layout::iterator begin_it, end_it;
1530 if (tc->text_sel_start < tc->text_sel_end) {
1531 begin_it = tc->text_sel_start;
1532 end_it = tc->text_sel_end;
1533 } else {
1534 begin_it = tc->text_sel_end;
1535 end_it = tc->text_sel_start;
1536 }
1537 if (begin_it == end_it)
1538 if (!begin_it.prevCharacter())
1539 end_it.nextCharacter();
1540 for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1541 SPObject const *pos_obj = 0;
1542 void *rawptr = 0;
1543 layout->getSourceOfCharacter(it, &rawptr);
1544 if (!rawptr || !SP_IS_OBJECT(rawptr))
1545 continue;
1546 pos_obj = SP_OBJECT(rawptr);
1547 while (SP_IS_STRING(pos_obj) && SP_OBJECT_PARENT(pos_obj)) {
1548 pos_obj = SP_OBJECT_PARENT(pos_obj); // SPStrings don't have style
1549 }
1550 styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1551 }
1553 int result = sp_desktop_query_style_from_list (styles_list, style, property);
1555 g_slist_free(styles_list);
1556 return result;
1557 }
1559 static void
1560 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1561 {
1562 if (tc->text == NULL)
1563 return;
1564 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1565 if (layout) { // undo can change the text length without us knowing it
1566 layout->validateIterator(&tc->text_sel_start);
1567 layout->validateIterator(&tc->text_sel_end);
1568 }
1569 }
1571 static void
1572 sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see)
1573 {
1574 GdkRectangle im_cursor = { 0, 0, 1, 1 };
1576 // due to interruptible display, tc may already be destroyed during a display update before
1577 // the cursor update (can't do both atomically, alas)
1578 if (!tc->desktop) return;
1580 if (tc->text) {
1581 Geom::Point p0, p1;
1582 sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1583 Geom::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1584 Geom::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1586 // scroll to show cursor
1587 if (scroll_to_see) {
1588 Geom::Point const center = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().midpoint();
1589 if (Geom::L2(d0 - center) > Geom::L2(d1 - center))
1590 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1591 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d0, 1.0);
1592 else
1593 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d1, 1.0);
1594 }
1596 sp_canvas_item_show(tc->cursor);
1597 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1599 /* fixme: ... need another transformation to get canvas widget coordinate space? */
1600 im_cursor.x = (int) floor(d0[Geom::X]);
1601 im_cursor.y = (int) floor(d0[Geom::Y]);
1602 im_cursor.width = (int) floor(d1[Geom::X]) - im_cursor.x;
1603 im_cursor.height = (int) floor(d1[Geom::Y]) - im_cursor.y;
1605 tc->show = TRUE;
1606 tc->phase = 1;
1608 if (SP_IS_FLOWTEXT(tc->text)) {
1609 SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1610 if (frame) {
1611 sp_canvas_item_show(tc->frame);
1612 boost::optional<Geom::Rect> frame_bbox = sp_item_bbox_desktop(frame);
1613 if (frame_bbox) {
1614 SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
1615 }
1616 }
1617 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1618 } else {
1619 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1620 }
1622 } else {
1623 sp_canvas_item_hide(tc->cursor);
1624 sp_canvas_item_hide(tc->frame);
1625 tc->show = FALSE;
1626 if (!tc->nascent_object) {
1627 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
1628 }
1629 }
1631 if (tc->imc) {
1632 gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1633 }
1634 SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1635 }
1637 static void sp_text_context_update_text_selection(SPTextContext *tc)
1638 {
1639 // due to interruptible display, tc may already be destroyed during a display update before
1640 // the selection update (can't do both atomically, alas)
1641 if (!tc->desktop) return;
1643 for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1644 sp_canvas_item_hide(*it);
1645 gtk_object_destroy(*it);
1646 }
1647 tc->text_selection_quads.clear();
1649 std::vector<Geom::Point> quads;
1650 if (tc->text != NULL)
1651 quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1652 for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1653 SPCanvasItem *quad_canvasitem;
1654 quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1655 // FIXME: make the color settable in prefs
1656 // for now, use semitrasparent blue, as cairo cannot do inversion :(
1657 sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x00777777);
1658 sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1659 sp_canvas_item_show(quad_canvasitem);
1660 tc->text_selection_quads.push_back(quad_canvasitem);
1661 }
1662 }
1664 static gint
1665 sp_text_context_timeout(SPTextContext *tc)
1666 {
1667 if (tc->show) {
1668 sp_canvas_item_show(tc->cursor);
1669 if (tc->phase) {
1670 tc->phase = 0;
1671 sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0xffffffff);
1672 } else {
1673 tc->phase = 1;
1674 sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
1675 }
1676 }
1678 return TRUE;
1679 }
1681 static void
1682 sp_text_context_forget_text(SPTextContext *tc)
1683 {
1684 if (! tc->text) return;
1685 SPItem *ti = tc->text;
1686 (void)ti;
1687 /* We have to set it to zero,
1688 * or selection changed signal messes everything up */
1689 tc->text = NULL;
1691 /* FIXME: this automatic deletion when nothing is inputted crashes the XML edittor and also crashes when duplicating an empty flowtext.
1692 So don't create an empty flowtext in the first place? Create it when first character is typed.
1693 */
1694 /*
1695 if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1696 Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1697 // the repr may already have been unparented
1698 // if we were called e.g. as the result of
1699 // an undo or the element being removed from
1700 // the XML editor
1701 if ( text_repr && sp_repr_parent(text_repr) ) {
1702 sp_repr_unparent(text_repr);
1703 sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1704 _("Remove empty text"));
1705 }
1706 }
1707 */
1708 }
1710 gint
1711 sptc_focus_in(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1712 {
1713 gtk_im_context_focus_in(tc->imc);
1714 return FALSE;
1715 }
1717 gint
1718 sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1719 {
1720 gtk_im_context_focus_out(tc->imc);
1721 return FALSE;
1722 }
1724 static void
1725 sptc_commit(GtkIMContext */*imc*/, gchar *string, SPTextContext *tc)
1726 {
1727 if (!tc->text) {
1728 sp_text_context_setup_text(tc);
1729 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1730 }
1732 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1733 sp_text_context_update_cursor(tc);
1734 sp_text_context_update_text_selection(tc);
1736 sp_document_done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT,
1737 _("Type text"));
1738 }
1741 /*
1742 Local Variables:
1743 mode:c++
1744 c-file-style:"stroustrup"
1745 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1746 indent-tabs-mode:nil
1747 fill-column:99
1748 End:
1749 */
1750 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :