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()->stop();
187 if (ec->shape_knot_holder) {
188 sp_knot_holder_destroy(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(250, (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 *ec, SPItem *item, GdkEvent *event)
342 {
343 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
344 SPDesktop *desktop = ec->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) {
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(ec->desktop)->set(item_ungrouped);
358 if (tc->text) {
359 // find out click point in document coordinates
360 NR::Point p = ec->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) {
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) {
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 = ec->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 ret = TRUE;
433 break;
434 }
435 // find out item under mouse, disregarding groups
436 item_ungrouped = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE);
437 if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
438 sp_canvas_item_show(tc->indicator);
439 SP_CTRLRECT(tc->indicator)->setRectangle(sp_item_bbox_desktop(item_ungrouped));
441 ec->cursor_shape = cursor_text_insert_xpm;
442 ec->hot_x = 7;
443 ec->hot_y = 10;
444 sp_event_context_update_cursor(ec);
445 sp_text_context_update_text_selection(tc);
447 if (SP_IS_TEXT (item_ungrouped)) {
448 desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
449 } else {
450 desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the flowed text, <b>drag</b> to select part of the text."));
451 }
453 tc->over_text = true;
455 ret = TRUE;
456 }
457 break;
458 default:
459 break;
460 }
462 if (!ret) {
463 if (((SPEventContextClass *) parent_class)->item_handler)
464 ret = ((SPEventContextClass *) parent_class)->item_handler(ec, item, event);
465 }
467 return ret;
468 }
470 static void
471 sp_text_context_setup_text(SPTextContext *tc)
472 {
473 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
475 /* Create <text> */
476 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_EVENT_CONTEXT_DESKTOP(ec)->doc());
477 Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
478 rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
480 /* Set style */
481 sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "tools.text", true);
483 sp_repr_set_svg_double(rtext, "x", tc->pdoc[NR::X]);
484 sp_repr_set_svg_double(rtext, "y", tc->pdoc[NR::Y]);
486 /* Create <tspan> */
487 Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
488 rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
489 rtext->addChild(rtspan, NULL);
490 Inkscape::GC::release(rtspan);
492 /* Create TEXT */
493 Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
494 rtspan->addChild(rstring, NULL);
495 Inkscape::GC::release(rstring);
496 SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
497 /* fixme: Is selection::changed really immediate? */
498 /* yes, it's immediate .. why does it matter? */
499 sp_desktop_selection(ec->desktop)->set(text_item);
500 Inkscape::GC::release(rtext);
501 text_item->transform = SP_ITEM(ec->desktop->currentRoot())->getRelativeTransform(ec->desktop->currentLayer());
502 text_item->updateRepr();
503 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
504 _("Create text"));
505 }
507 /**
508 * Insert the character indicated by tc.uni to replace the current selection,
509 * and reset tc.uni/tc.unipos to empty string.
510 *
511 * \pre tc.uni/tc.unipos non-empty.
512 */
513 static void
514 insert_uni_char(SPTextContext *const tc)
515 {
516 g_return_if_fail(tc->unipos
517 && tc->unipos < sizeof(tc->uni)
518 && tc->uni[tc->unipos] == '\0');
519 unsigned int uv;
520 sscanf(tc->uni, "%x", &uv);
521 tc->unipos = 0;
522 tc->uni[tc->unipos] = '\0';
524 if ( !g_unichar_isprint(static_cast<gunichar>(uv))
525 && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
526 // This may be due to bad input, so it goes to statusbar.
527 tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
528 _("Non-printable character"));
529 } else {
530 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
531 sp_text_context_setup_text(tc);
532 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
533 }
535 gchar u[10];
536 guint const len = g_unichar_to_utf8(uv, u);
537 u[len] = '\0';
539 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
540 sp_text_context_update_cursor(tc);
541 sp_text_context_update_text_selection(tc);
542 sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM,
543 _("Insert Unicode character"));
544 }
545 }
547 static void
548 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
549 {
550 unsigned int uv;
551 sscanf(hex, "%x", &uv);
552 if (!g_unichar_isprint((gunichar) uv)) {
553 uv = 0xfffd;
554 }
555 guint const len = g_unichar_to_utf8(uv, utf8);
556 utf8[len] = '\0';
557 }
559 static void
560 show_curr_uni_char(SPTextContext *const tc)
561 {
562 g_return_if_fail(tc->unipos < sizeof(tc->uni)
563 && tc->uni[tc->unipos] == '\0');
564 if (tc->unipos) {
565 char utf8[10];
566 hex_to_printable_utf8_buf(tc->uni, utf8);
568 /* Status bar messages are in pango markup, so we need xml escaping. */
569 if (utf8[1] == '\0') {
570 switch(utf8[0]) {
571 case '<': strcpy(utf8, "<"); break;
572 case '>': strcpy(utf8, ">"); break;
573 case '&': strcpy(utf8, "&"); break;
574 default: break;
575 }
576 }
577 tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
578 _("Unicode (<b>Enter</b> to finish): %s: %s"), tc->uni, utf8);
579 } else {
580 tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
581 }
582 }
584 static gint
585 sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
586 {
587 SPTextContext *const tc = SP_TEXT_CONTEXT(ec);
589 SPDesktop *desktop = ec->desktop;
591 sp_canvas_item_hide(tc->indicator);
593 sp_text_context_validate_cursor_iterators(tc);
595 ec->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100);
597 switch (event->type) {
598 case GDK_BUTTON_PRESS:
599 if (event->button.button == 1) {
601 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(ec);
603 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
604 return TRUE;
605 }
607 // save drag origin
608 ec->xp = (gint) event->button.x;
609 ec->yp = (gint) event->button.y;
610 ec->within_tolerance = true;
612 NR::Point const button_pt(event->button.x, event->button.y);
613 tc->p0 = desktop->w2d(button_pt);
614 Inkscape::Rubberband::get()->start(desktop, tc->p0);
615 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
616 GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
617 GDK_POINTER_MOTION_MASK,
618 NULL, event->button.time);
619 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
620 tc->creating = 1;
622 /* Processed */
623 return TRUE;
624 }
625 break;
626 case GDK_MOTION_NOTIFY:
627 if (tc->over_text) {
628 tc->over_text = 0;
629 // update cursor and statusbar: we are not over a text object now
630 ec->cursor_shape = cursor_text_xpm;
631 ec->hot_x = 7;
632 ec->hot_y = 7;
633 sp_event_context_update_cursor(ec);
634 desktop->event_context->defaultMessageContext()->clear();
635 }
637 if (tc->creating && event->motion.state & GDK_BUTTON1_MASK) {
638 if ( ec->within_tolerance
639 && ( abs( (gint) event->motion.x - ec->xp ) < ec->tolerance )
640 && ( abs( (gint) event->motion.y - ec->yp ) < ec->tolerance ) ) {
641 break; // do not drag if we're within tolerance from origin
642 }
643 // Once the user has moved farther than tolerance from the original location
644 // (indicating they intend to draw, not click), then always process the
645 // motion notify coordinates as given (no snapping back to origin)
646 ec->within_tolerance = false;
648 NR::Point const motion_pt(event->motion.x, event->motion.y);
649 NR::Point const p = desktop->w2d(motion_pt);
651 Inkscape::Rubberband::get()->move(p);
652 gobble_motion_events(GDK_BUTTON1_MASK);
654 // status text
655 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::X]), desktop->namedview->getDefaultMetric());
656 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::Y]), desktop->namedview->getDefaultMetric());
657 ec->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("<b>Flowed text frame</b>: %s × %s"), xs->str, ys->str);
658 g_string_free(xs, FALSE);
659 g_string_free(ys, FALSE);
661 }
662 break;
663 case GDK_BUTTON_RELEASE:
664 if (event->button.button == 1) {
666 if (tc->grabbed) {
667 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
668 tc->grabbed = NULL;
669 }
671 Inkscape::Rubberband::get()->stop();
673 if (tc->creating && ec->within_tolerance) {
674 /* Button 1, set X & Y & new item */
675 sp_desktop_selection(desktop)->clear();
676 NR::Point dtp = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
677 tc->pdoc = sp_desktop_dt2root_xy_point(ec->desktop, dtp);
679 tc->show = TRUE;
680 tc->phase = 1;
681 tc->nascent_object = 1; // new object was just created
683 /* Cursor */
684 sp_canvas_item_show(tc->cursor);
685 // Cursor height is defined by the new text object's font size; it needs to be set
686 // articifically here, for the text object does not exist yet:
687 double cursor_height = sp_desktop_get_font_size_tool(ec->desktop);
688 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), dtp, dtp + NR::Point(0, cursor_height));
689 ec->_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
691 ec->within_tolerance = false;
692 } else if (tc->creating) {
693 NR::Point const button_pt(event->button.x, event->button.y);
694 NR::Point p1 = desktop->w2d(button_pt);
695 double cursor_height = sp_desktop_get_font_size_tool(ec->desktop);
696 if (fabs(p1[NR::Y] - tc->p0[NR::Y]) > cursor_height) {
697 // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
698 SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
699 sp_desktop_selection(desktop)->set(ft);
700 ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
701 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
702 _("Create flowed text"));
703 } else {
704 ec->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
705 }
706 }
707 tc->creating = false;
708 return TRUE;
709 }
710 break;
711 case GDK_KEY_PRESS: {
712 guint const group0_keyval = get_group0_keyval(&event->key);
714 if (group0_keyval == GDK_KP_Add ||
715 group0_keyval == GDK_KP_Subtract) {
716 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
717 break; // otherwise pass on keypad +/- so they can zoom
718 }
720 if ((tc->text) || (tc->nascent_object)) {
721 // there is an active text object in this context, or a new object was just created
723 if (tc->unimode || !tc->imc
724 || (MOD__CTRL && MOD__SHIFT) // input methods tend to steal this for unimode,
725 // but we have our own so make sure they don't swallow it
726 || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
727 //IM did not consume the key, or we're in unimode
729 if (!MOD__CTRL_ONLY && tc->unimode) {
730 /* TODO: ISO 14755 (section 3 Definitions) says that we should also
731 accept the first 6 characters of alphabets other than the latin
732 alphabet "if the Latin alphabet is not used". The below is also
733 reasonable (viz. hope that the user's keyboard includes latin
734 characters and force latin interpretation -- just as we do for our
735 keyboard shortcuts), but differs from the ISO 14755
736 recommendation. */
737 switch (group0_keyval) {
738 case GDK_space:
739 case GDK_KP_Space: {
740 if (tc->unipos) {
741 insert_uni_char(tc);
742 }
743 /* Stay in unimode. */
744 show_curr_uni_char(tc);
745 return TRUE;
746 }
748 case GDK_BackSpace: {
749 g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
750 if (tc->unipos) {
751 tc->uni[--tc->unipos] = '\0';
752 }
753 show_curr_uni_char(tc);
754 return TRUE;
755 }
757 case GDK_Return:
758 case GDK_KP_Enter: {
759 if (tc->unipos) {
760 insert_uni_char(tc);
761 }
762 /* Exit unimode. */
763 tc->unimode = false;
764 ec->defaultMessageContext()->clear();
765 return TRUE;
766 }
768 case GDK_Escape: {
769 // Cancel unimode.
770 tc->unimode = false;
771 gtk_im_context_reset(tc->imc);
772 ec->defaultMessageContext()->clear();
773 return TRUE;
774 }
776 case GDK_Shift_L:
777 case GDK_Shift_R:
778 break;
780 default: {
781 if (g_ascii_isxdigit(group0_keyval)) {
782 g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
783 tc->uni[tc->unipos++] = group0_keyval;
784 tc->uni[tc->unipos] = '\0';
785 if (tc->unipos == 8) {
786 /* This behaviour is partly to allow us to continue to
787 use a fixed-length buffer for tc->uni. Reason for
788 choosing the number 8 is that it's the length of
789 ``canonical form'' mentioned in the ISO 14755 spec.
790 An advantage over choosing 6 is that it allows using
791 backspace for typos & misremembering when entering a
792 6-digit number. */
793 insert_uni_char(tc);
794 }
795 show_curr_uni_char(tc);
796 return TRUE;
797 } else {
798 /* The intent is to ignore but consume characters that could be
799 typos for hex digits. Gtk seems to ignore & consume all
800 non-hex-digits, and we do similar here. Though note that some
801 shortcuts (like keypad +/- for zoom) get processed before
802 reaching this code. */
803 return TRUE;
804 }
805 }
806 }
807 }
809 bool (Inkscape::Text::Layout::iterator::*cursor_movement_operator)() = NULL;
811 /* Neither unimode nor IM consumed key; process text tool shortcuts */
812 switch (group0_keyval) {
813 case GDK_x:
814 case GDK_X:
815 if (MOD__ALT_ONLY) {
816 desktop->setToolboxFocusTo ("altx-text");
817 return TRUE;
818 }
819 break;
820 case GDK_space:
821 if (MOD__CTRL_ONLY) {
822 /* No-break space */
823 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
824 sp_text_context_setup_text(tc);
825 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
826 }
827 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
828 sp_text_context_update_cursor(tc);
829 sp_text_context_update_text_selection(tc);
830 ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
831 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
832 _("Insert no-break space"));
833 return TRUE;
834 }
835 break;
836 case GDK_U:
837 case GDK_u:
838 if (MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT)) {
839 if (tc->unimode) {
840 tc->unimode = false;
841 ec->defaultMessageContext()->clear();
842 } else {
843 tc->unimode = true;
844 tc->unipos = 0;
845 ec->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
846 }
847 if (tc->imc) {
848 gtk_im_context_reset(tc->imc);
849 }
850 return TRUE;
851 }
852 break;
853 case GDK_B:
854 case GDK_b:
855 if (MOD__CTRL_ONLY && tc->text) {
856 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
857 SPCSSAttr *css = sp_repr_css_attr_new();
858 if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
859 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
860 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
861 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
862 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
863 sp_repr_css_set_property(css, "font-weight", "bold");
864 else
865 sp_repr_css_set_property(css, "font-weight", "normal");
866 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
867 sp_repr_css_attr_unref(css);
868 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
869 _("Make bold"));
870 sp_text_context_update_cursor(tc);
871 sp_text_context_update_text_selection(tc);
872 return TRUE;
873 }
874 break;
875 case GDK_I:
876 case GDK_i:
877 if (MOD__CTRL_ONLY && tc->text) {
878 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
879 SPCSSAttr *css = sp_repr_css_attr_new();
880 if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL)
881 sp_repr_css_set_property(css, "font-style", "italic");
882 else
883 sp_repr_css_set_property(css, "font-style", "normal");
884 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
885 sp_repr_css_attr_unref(css);
886 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
887 _("Make italic"));
888 sp_text_context_update_cursor(tc);
889 sp_text_context_update_text_selection(tc);
890 return TRUE;
891 }
892 break;
894 case GDK_A:
895 case GDK_a:
896 if (MOD__CTRL_ONLY && tc->text) {
897 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
898 if (layout) {
899 tc->text_sel_start = layout->begin();
900 tc->text_sel_end = layout->end();
901 sp_text_context_update_cursor(tc);
902 sp_text_context_update_text_selection(tc);
903 return TRUE;
904 }
905 }
906 break;
908 case GDK_Return:
909 case GDK_KP_Enter:
910 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
911 sp_text_context_setup_text(tc);
912 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
913 }
914 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
915 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
916 sp_text_context_update_cursor(tc);
917 sp_text_context_update_text_selection(tc);
918 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
919 _("New line"));
920 return TRUE;
921 case GDK_BackSpace:
922 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
923 if (tc->text_sel_start == tc->text_sel_end)
924 tc->text_sel_start.prevCursorPosition();
925 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
926 sp_text_context_update_cursor(tc);
927 sp_text_context_update_text_selection(tc);
928 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
929 _("Backspace"));
930 }
931 return TRUE;
932 case GDK_Delete:
933 case GDK_KP_Delete:
934 if (tc->text) {
935 if (tc->text_sel_start == tc->text_sel_end)
936 tc->text_sel_end.nextCursorPosition();
937 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
938 sp_text_context_update_cursor(tc);
939 sp_text_context_update_text_selection(tc);
940 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
941 _("Delete"));
942 }
943 return TRUE;
944 case GDK_Left:
945 case GDK_KP_Left:
946 case GDK_KP_4:
947 if (tc->text) {
948 if (MOD__ALT) {
949 if (MOD__SHIFT)
950 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-10, 0));
951 else
952 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-1, 0));
953 sp_text_context_update_cursor(tc);
954 sp_text_context_update_text_selection(tc);
955 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:left", SP_VERB_CONTEXT_TEXT,
956 _("Kern to the left"));
957 } else {
958 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorLeftWithControl
959 : &Inkscape::Text::Layout::iterator::cursorLeft;
960 break;
961 }
962 }
963 return TRUE;
964 case GDK_Right:
965 case GDK_KP_Right:
966 case GDK_KP_6:
967 if (tc->text) {
968 if (MOD__ALT) {
969 if (MOD__SHIFT)
970 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(10, 0));
971 else
972 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(1, 0));
973 sp_text_context_update_cursor(tc);
974 sp_text_context_update_text_selection(tc);
975 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:right", SP_VERB_CONTEXT_TEXT,
976 _("Kern to the right"));
977 } else {
978 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorRightWithControl
979 : &Inkscape::Text::Layout::iterator::cursorRight;
980 break;
981 }
982 }
983 return TRUE;
984 case GDK_Up:
985 case GDK_KP_Up:
986 case GDK_KP_8:
987 if (tc->text) {
988 if (MOD__ALT) {
989 if (MOD__SHIFT)
990 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -10));
991 else
992 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -1));
993 sp_text_context_update_cursor(tc);
994 sp_text_context_update_text_selection(tc);
995 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:up", SP_VERB_CONTEXT_TEXT,
996 _("Kern up"));
998 } else {
999 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorUpWithControl
1000 : &Inkscape::Text::Layout::iterator::cursorUp;
1001 break;
1002 }
1003 }
1004 return TRUE;
1005 case GDK_Down:
1006 case GDK_KP_Down:
1007 case GDK_KP_2:
1008 if (tc->text) {
1009 if (MOD__ALT) {
1010 if (MOD__SHIFT)
1011 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 10));
1012 else
1013 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 1));
1014 sp_text_context_update_cursor(tc);
1015 sp_text_context_update_text_selection(tc);
1016 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:down", SP_VERB_CONTEXT_TEXT,
1017 _("Kern down"));
1019 } else {
1020 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorDownWithControl
1021 : &Inkscape::Text::Layout::iterator::cursorDown;
1022 break;
1023 }
1024 }
1025 return TRUE;
1026 case GDK_Home:
1027 case GDK_KP_Home:
1028 if (tc->text) {
1029 if (MOD__CTRL)
1030 cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfShape;
1031 else
1032 cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfLine;
1033 break;
1034 }
1035 return TRUE;
1036 case GDK_End:
1037 case GDK_KP_End:
1038 if (tc->text) {
1039 if (MOD__CTRL)
1040 cursor_movement_operator = &Inkscape::Text::Layout::iterator::nextStartOfShape;
1041 else
1042 cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisEndOfLine;
1043 break;
1044 }
1045 return TRUE;
1046 case GDK_Escape:
1047 if (tc->creating) {
1048 tc->creating = 0;
1049 if (tc->grabbed) {
1050 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1051 tc->grabbed = NULL;
1052 }
1053 Inkscape::Rubberband::get()->stop();
1054 } else {
1055 sp_desktop_selection(ec->desktop)->clear();
1056 }
1057 tc->nascent_object = FALSE;
1058 return TRUE;
1059 case GDK_bracketleft:
1060 if (tc->text) {
1061 if (MOD__ALT || MOD__CTRL) {
1062 if (MOD__ALT) {
1063 if (MOD__SHIFT) {
1064 // FIXME: alt+shift+[] does not work, don't know why
1065 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1066 } else {
1067 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1068 }
1069 } else {
1070 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -90);
1071 }
1072 sp_document_maybe_done(sp_desktop_document(ec->desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT,
1073 _("Rotate counterclockwise"));
1074 sp_text_context_update_cursor(tc);
1075 sp_text_context_update_text_selection(tc);
1076 return TRUE;
1077 }
1078 }
1079 break;
1080 case GDK_bracketright:
1081 if (tc->text) {
1082 if (MOD__ALT || MOD__CTRL) {
1083 if (MOD__ALT) {
1084 if (MOD__SHIFT) {
1085 // FIXME: alt+shift+[] does not work, don't know why
1086 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1087 } else {
1088 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1089 }
1090 } else {
1091 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 90);
1092 }
1093 sp_document_maybe_done(sp_desktop_document(ec->desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT,
1094 _("Rotate clockwise"));
1095 sp_text_context_update_cursor(tc);
1096 sp_text_context_update_text_selection(tc);
1097 return TRUE;
1098 }
1099 }
1100 break;
1101 case GDK_less:
1102 case GDK_comma:
1103 if (tc->text) {
1104 if (MOD__ALT) {
1105 if (MOD__CTRL) {
1106 if (MOD__SHIFT)
1107 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1108 else
1109 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1110 sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT,
1111 _("Contract line spacing"));
1113 } else {
1114 if (MOD__SHIFT)
1115 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1116 else
1117 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1118 sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT,
1119 _("Contract letter spacing"));
1121 }
1122 sp_text_context_update_cursor(tc);
1123 sp_text_context_update_text_selection(tc);
1124 return TRUE;
1125 }
1126 }
1127 break;
1128 case GDK_greater:
1129 case GDK_period:
1130 if (tc->text) {
1131 if (MOD__ALT) {
1132 if (MOD__CTRL) {
1133 if (MOD__SHIFT)
1134 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1135 else
1136 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1137 sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT,
1138 _("Expand line spacing"));
1140 } else {
1141 if (MOD__SHIFT)
1142 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1143 else
1144 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1145 sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT,
1146 _("Expand letter spacing"));
1148 }
1149 sp_text_context_update_cursor(tc);
1150 sp_text_context_update_text_selection(tc);
1151 return TRUE;
1152 }
1153 }
1154 break;
1155 default:
1156 break;
1157 }
1159 if (cursor_movement_operator) {
1160 Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
1161 Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
1162 (tc->text_sel_end.*cursor_movement_operator)();
1163 if (!MOD__SHIFT)
1164 tc->text_sel_start = tc->text_sel_end;
1165 if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1166 sp_text_context_update_cursor(tc);
1167 sp_text_context_update_text_selection(tc);
1168 }
1169 return TRUE;
1170 }
1172 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1173 } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1174 // except up/down that are swallowed to prevent the zoom field from activation
1175 if ((group0_keyval == GDK_Up ||
1176 group0_keyval == GDK_Down ||
1177 group0_keyval == GDK_KP_Up ||
1178 group0_keyval == GDK_KP_Down )
1179 && !MOD__CTRL_ONLY) {
1180 return TRUE;
1181 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1182 if (tc->creating) {
1183 tc->creating = 0;
1184 if (tc->grabbed) {
1185 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1186 tc->grabbed = NULL;
1187 }
1188 Inkscape::Rubberband::get()->stop();
1189 }
1190 }
1191 }
1192 break;
1193 }
1195 case GDK_KEY_RELEASE:
1196 if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1197 return TRUE;
1198 }
1199 break;
1200 default:
1201 break;
1202 }
1204 // if nobody consumed it so far
1205 if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1206 return ((SPEventContextClass *) parent_class)->root_handler(ec, event); // send event to parent
1207 } else {
1208 return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1209 }
1210 }
1212 /**
1213 Attempts to paste system clipboard into the currently edited text, returns true on success
1214 */
1215 bool
1216 sp_text_paste_inline(SPEventContext *ec)
1217 {
1218 if (!SP_IS_TEXT_CONTEXT(ec))
1219 return false;
1221 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1223 if ((tc->text) || (tc->nascent_object)) {
1224 // there is an active text object in this context, or a new object was just created
1226 Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1227 Glib::ustring const text = refClipboard->wait_for_text();
1229 if (!text.empty()) {
1231 if (!tc->text) { // create text if none (i.e. if nascent_object)
1232 sp_text_context_setup_text(tc);
1233 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1234 }
1236 // using indices is slow in ustrings. Whatever.
1237 Glib::ustring::size_type begin = 0;
1238 for ( ; ; ) {
1239 Glib::ustring::size_type end = text.find('\n', begin);
1240 if (end == Glib::ustring::npos) {
1241 if (begin != text.length())
1242 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());
1243 break;
1244 }
1245 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());
1246 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1247 begin = end + 1;
1248 }
1249 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
1250 _("Paste text"));
1252 return true;
1253 }
1254 } // FIXME: else create and select a new object under cursor!
1256 return false;
1257 }
1259 /**
1260 Gets the raw characters that comprise the currently selected text, converting line
1261 breaks into lf characters.
1262 */
1263 Glib::ustring
1264 sp_text_get_selected_text(SPEventContext const *ec)
1265 {
1266 if (!SP_IS_TEXT_CONTEXT(ec))
1267 return "";
1268 SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1269 if (tc->text == NULL)
1270 return "";
1272 return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1273 }
1275 /**
1276 Deletes the currently selected characters. Returns false if there is no
1277 text selection currently.
1278 */
1279 bool sp_text_delete_selection(SPEventContext *ec)
1280 {
1281 if (!SP_IS_TEXT_CONTEXT(ec))
1282 return false;
1283 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1284 if (tc->text == NULL)
1285 return false;
1287 if (tc->text_sel_start == tc->text_sel_end)
1288 return false;
1289 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
1290 sp_text_context_update_cursor(tc);
1291 sp_text_context_update_text_selection(tc);
1292 return true;
1293 }
1295 /**
1296 * \param selection Should not be NULL.
1297 */
1298 static void
1299 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1300 {
1301 g_assert(selection != NULL);
1303 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1305 if (ec->shape_knot_holder) { // destroy knotholder
1306 sp_knot_holder_destroy(ec->shape_knot_holder);
1307 ec->shape_knot_holder = NULL;
1308 }
1310 if (ec->shape_repr) { // remove old listener
1311 sp_repr_remove_listener_by_data(ec->shape_repr, ec);
1312 Inkscape::GC::release(ec->shape_repr);
1313 ec->shape_repr = 0;
1314 }
1316 SPItem *item = selection->singleItem();
1317 if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1318 ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
1319 Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
1320 if (shape_repr) {
1321 ec->shape_repr = shape_repr;
1322 Inkscape::GC::anchor(shape_repr);
1323 sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
1324 }
1325 }
1327 if (tc->text && (item != tc->text)) {
1328 sp_text_context_forget_text(tc);
1329 }
1330 tc->text = NULL;
1332 if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1333 tc->text = item;
1334 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1335 if (layout)
1336 tc->text_sel_start = tc->text_sel_end = layout->end();
1337 } else {
1338 tc->text = NULL;
1339 }
1341 // we update cursor without scrolling, because this position may not be final;
1342 // item_handler moves cusros to the point of click immediately
1343 sp_text_context_update_cursor(tc, false);
1344 sp_text_context_update_text_selection(tc);
1345 }
1347 static void
1348 sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc)
1349 {
1350 sp_text_context_update_cursor(tc);
1351 sp_text_context_update_text_selection(tc);
1352 }
1354 static bool
1355 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1356 {
1357 if (tc->text == NULL)
1358 return false;
1359 if (tc->text_sel_start == tc->text_sel_end)
1360 return false; // will get picked up by the parent and applied to the whole text object
1362 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1363 sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1364 _("Set text style"));
1365 sp_text_context_update_cursor(tc);
1366 sp_text_context_update_text_selection(tc);
1368 return true;
1369 }
1371 static int
1372 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1373 {
1374 if (tc->text == NULL)
1375 return QUERY_STYLE_NOTHING;
1376 const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1377 if (layout == NULL)
1378 return QUERY_STYLE_NOTHING;
1379 sp_text_context_validate_cursor_iterators(tc);
1381 GSList *styles_list = NULL;
1383 Inkscape::Text::Layout::iterator begin_it, end_it;
1384 if (tc->text_sel_start < tc->text_sel_end) {
1385 begin_it = tc->text_sel_start;
1386 end_it = tc->text_sel_end;
1387 } else {
1388 begin_it = tc->text_sel_end;
1389 end_it = tc->text_sel_start;
1390 }
1391 if (begin_it == end_it)
1392 if (!begin_it.prevCharacter())
1393 end_it.nextCharacter();
1394 for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1395 SPObject const *pos_obj = 0;
1396 void *rawptr = 0;
1397 layout->getSourceOfCharacter(it, &rawptr);
1398 pos_obj = SP_OBJECT(rawptr);
1399 if (pos_obj == 0) continue;
1400 while (SP_OBJECT_STYLE(pos_obj) == NULL && SP_OBJECT_PARENT(pos_obj))
1401 pos_obj = SP_OBJECT_PARENT(pos_obj); // SPStrings don't have style
1402 styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1403 }
1405 int result = sp_desktop_query_style_from_list (styles_list, style, property);
1407 g_slist_free(styles_list);
1408 return result;
1409 }
1411 static void
1412 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1413 {
1414 if (tc->text == NULL)
1415 return;
1416 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1417 if (layout) { // undo can change the text length without us knowing it
1418 layout->validateIterator(&tc->text_sel_start);
1419 layout->validateIterator(&tc->text_sel_end);
1420 }
1421 }
1423 static void
1424 sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see)
1425 {
1426 GdkRectangle im_cursor = { 0, 0, 1, 1 };
1428 // due to interruptible display, tc may already be destroyed during a display update before
1429 // the cursor update (can't do both atomically, alas)
1430 if (!tc->desktop) return;
1432 if (tc->text) {
1433 NR::Point p0, p1;
1434 sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1435 NR::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1436 NR::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1438 // scroll to show cursor
1439 if (scroll_to_see) {
1440 NR::Point const dm = (d0 + d1) / 2;
1441 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1442 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(&dm, 1.0);
1443 }
1445 sp_canvas_item_show(tc->cursor);
1446 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1448 /* fixme: ... need another transformation to get canvas widget coordinate space? */
1449 im_cursor.x = (int) floor(d0[NR::X]);
1450 im_cursor.y = (int) floor(d0[NR::Y]);
1451 im_cursor.width = (int) floor(d1[NR::X]) - im_cursor.x;
1452 im_cursor.height = (int) floor(d1[NR::Y]) - im_cursor.y;
1454 tc->show = TRUE;
1455 tc->phase = 1;
1457 if (SP_IS_FLOWTEXT(tc->text)) {
1458 SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1459 if (frame) {
1460 sp_canvas_item_show(tc->frame);
1461 SP_CTRLRECT(tc->frame)->setRectangle(sp_item_bbox_desktop(frame));
1462 }
1463 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1464 } else {
1465 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1466 }
1468 } else {
1469 sp_canvas_item_hide(tc->cursor);
1470 sp_canvas_item_hide(tc->frame);
1471 tc->show = FALSE;
1472 if (!tc->nascent_object) {
1473 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
1474 }
1475 }
1477 if (tc->imc) {
1478 gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1479 }
1480 SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1481 }
1483 static void sp_text_context_update_text_selection(SPTextContext *tc)
1484 {
1485 // due to interruptible display, tc may already be destroyed during a display update before
1486 // the selection update (can't do both atomically, alas)
1487 if (!tc->desktop) return;
1489 for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1490 sp_canvas_item_hide(*it);
1491 gtk_object_destroy(*it);
1492 }
1493 tc->text_selection_quads.clear();
1495 std::vector<NR::Point> quads;
1496 if (tc->text != NULL)
1497 quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1498 for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1499 SPCanvasItem *quad_canvasitem;
1500 quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1501 sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x000000ff);
1502 sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1503 sp_canvas_item_show(quad_canvasitem);
1504 tc->text_selection_quads.push_back(quad_canvasitem);
1505 }
1506 }
1508 static gint
1509 sp_text_context_timeout(SPTextContext *tc)
1510 {
1511 if (tc->show) {
1512 if (tc->phase) {
1513 tc->phase = 0;
1514 sp_canvas_item_hide(tc->cursor);
1515 } else {
1516 tc->phase = 1;
1517 sp_canvas_item_show(tc->cursor);
1518 }
1519 }
1521 return TRUE;
1522 }
1524 static void
1525 sp_text_context_forget_text(SPTextContext *tc)
1526 {
1527 if (! tc->text) return;
1528 SPItem *ti = tc->text;
1529 /* We have to set it to zero,
1530 * or selection changed signal messes everything up */
1531 tc->text = NULL;
1532 if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1533 Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1534 // the repr may already have been unparented
1535 // if we were called e.g. as the result of
1536 // an undo or the element being removed from
1537 // the XML editor
1538 if ( text_repr && sp_repr_parent(text_repr) ) {
1539 sp_repr_unparent(text_repr);
1540 }
1541 }
1542 }
1544 gint
1545 sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1546 {
1547 gtk_im_context_focus_in(tc->imc);
1548 return FALSE;
1549 }
1551 gint
1552 sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1553 {
1554 gtk_im_context_focus_out(tc->imc);
1555 return FALSE;
1556 }
1558 static void
1559 sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc)
1560 {
1561 if (!tc->text) {
1562 sp_text_context_setup_text(tc);
1563 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1564 }
1566 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1567 sp_text_context_update_cursor(tc);
1568 sp_text_context_update_text_selection(tc);
1570 sp_document_done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT,
1571 _("Type text"));
1572 }
1575 /*
1576 Local Variables:
1577 mode:c++
1578 c-file-style:"stroustrup"
1579 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1580 indent-tabs-mode:nil
1581 fill-column:99
1582 End:
1583 */
1584 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :