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