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.pixbuf"
43 #include "pixmaps/cursor-text-insert.xpm"
44 #include "pixmaps/cursor-text-insert.pixbuf"
45 #include <glibmm/i18n.h>
46 #include "object-edit.h"
47 #include "xml/repr.h"
48 #include "xml/node-event-vector.h"
49 #include "prefs-utils.h"
50 #include "rubberband.h"
51 #include "sp-metrics.h"
52 #include "context-fns.h"
54 #include "text-editing.h"
56 #include "text-context.h"
59 static void sp_text_context_class_init(SPTextContextClass *klass);
60 static void sp_text_context_init(SPTextContext *text_context);
61 static void sp_text_context_dispose(GObject *obj);
63 static void sp_text_context_setup(SPEventContext *ec);
64 static void sp_text_context_finish(SPEventContext *ec);
65 static gint sp_text_context_root_handler(SPEventContext *event_context, GdkEvent *event);
66 static gint sp_text_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
68 static void sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc);
69 static void sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc);
70 static bool sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc);
71 static int sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc);
73 static void sp_text_context_validate_cursor_iterators(SPTextContext *tc);
74 static void sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see = true);
75 static void sp_text_context_update_text_selection(SPTextContext *tc);
76 static gint sp_text_context_timeout(SPTextContext *tc);
77 static void sp_text_context_forget_text(SPTextContext *tc);
79 static gint sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc);
80 static gint sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc);
81 static void sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc);
83 static SPEventContextClass *parent_class;
85 GType
86 sp_text_context_get_type()
87 {
88 static GType type = 0;
89 if (!type) {
90 GTypeInfo info = {
91 sizeof(SPTextContextClass),
92 NULL, NULL,
93 (GClassInitFunc) sp_text_context_class_init,
94 NULL, NULL,
95 sizeof(SPTextContext),
96 4,
97 (GInstanceInitFunc) sp_text_context_init,
98 NULL, /* value_table */
99 };
100 type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPTextContext", &info, (GTypeFlags)0);
101 }
102 return type;
103 }
105 static void
106 sp_text_context_class_init(SPTextContextClass *klass)
107 {
108 GObjectClass *object_class=(GObjectClass *)klass;
109 SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
111 parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
113 object_class->dispose = sp_text_context_dispose;
115 event_context_class->setup = sp_text_context_setup;
116 event_context_class->finish = sp_text_context_finish;
117 event_context_class->root_handler = sp_text_context_root_handler;
118 event_context_class->item_handler = sp_text_context_item_handler;
119 }
121 static void
122 sp_text_context_init(SPTextContext *tc)
123 {
124 SPEventContext *event_context = SP_EVENT_CONTEXT(tc);
126 event_context->cursor_shape = cursor_text_xpm;
127 event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
128 -1,
129 cursor_text_pixbuf,
130 FALSE,
131 NULL);
132 event_context->hot_x = 7;
133 event_context->hot_y = 7;
135 event_context->xp = 0;
136 event_context->yp = 0;
137 event_context->tolerance = 0;
138 event_context->within_tolerance = false;
140 event_context->shape_repr = NULL;
141 event_context->shape_knot_holder = NULL;
143 tc->imc = NULL;
145 tc->text = NULL;
146 tc->pdoc = NR::Point(0, 0);
147 new (&tc->text_sel_start) Inkscape::Text::Layout::iterator();
148 new (&tc->text_sel_end) Inkscape::Text::Layout::iterator();
149 new (&tc->text_selection_quads) std::vector<SPCanvasItem*>();
151 tc->unimode = false;
153 tc->cursor = NULL;
154 tc->indicator = NULL;
155 tc->frame = NULL;
156 tc->grabbed = NULL;
157 tc->timeout = 0;
158 tc->show = FALSE;
159 tc->phase = 0;
160 tc->nascent_object = 0;
161 tc->over_text = 0;
162 tc->dragging = 0;
163 tc->creating = 0;
165 new (&tc->sel_changed_connection) sigc::connection();
166 new (&tc->sel_modified_connection) sigc::connection();
167 new (&tc->style_set_connection) sigc::connection();
168 new (&tc->style_query_connection) sigc::connection();
169 }
171 static void
172 sp_text_context_dispose(GObject *obj)
173 {
174 SPTextContext *tc = SP_TEXT_CONTEXT(obj);
175 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
176 tc->style_query_connection.~connection();
177 tc->style_set_connection.~connection();
178 tc->sel_changed_connection.~connection();
179 tc->sel_modified_connection.~connection();
180 tc->text_sel_end.~iterator();
181 tc->text_sel_start.~iterator();
182 tc->text_selection_quads.~vector();
183 if (G_OBJECT_CLASS(parent_class)->dispose) {
184 G_OBJECT_CLASS(parent_class)->dispose(obj);
185 }
186 if (tc->grabbed) {
187 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
188 tc->grabbed = NULL;
189 }
191 Inkscape::Rubberband::get()->stop();
193 if (ec->shape_knot_holder) {
194 sp_knot_holder_destroy(ec->shape_knot_holder);
195 ec->shape_knot_holder = NULL;
196 }
197 if (ec->shape_repr) { // remove old listener
198 sp_repr_remove_listener_by_data(ec->shape_repr, ec);
199 Inkscape::GC::release(ec->shape_repr);
200 ec->shape_repr = 0;
201 }
202 }
204 static Inkscape::XML::NodeEventVector ec_shape_repr_events = {
205 NULL, /* child_added */
206 NULL, /* child_removed */
207 ec_shape_event_attr_changed,
208 NULL, /* content_changed */
209 NULL /* order_changed */
210 };
212 static void
213 sp_text_context_setup(SPEventContext *ec)
214 {
215 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
216 SPDesktop *desktop = ec->desktop;
218 tc->cursor = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLLINE, NULL);
219 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), 100, 0, 100, 100);
220 sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
221 sp_canvas_item_hide(tc->cursor);
223 tc->indicator = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
224 SP_CTRLRECT(tc->indicator)->setRectangle(NR::Rect(NR::Point(0, 0), NR::Point(100, 100)));
225 SP_CTRLRECT(tc->indicator)->setColor(0x0000ff7f, false, 0);
226 sp_canvas_item_hide(tc->indicator);
228 tc->frame = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
229 SP_CTRLRECT(tc->frame)->setRectangle(NR::Rect(NR::Point(0, 0), NR::Point(100, 100)));
230 SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0);
231 sp_canvas_item_hide(tc->frame);
233 tc->timeout = gtk_timeout_add(250, (GtkFunction) sp_text_context_timeout, ec);
235 tc->imc = gtk_im_multicontext_new();
236 if (tc->imc) {
237 GtkWidget *canvas = GTK_WIDGET(sp_desktop_canvas(desktop));
239 /* im preedit handling is very broken in inkscape for
240 * multi-byte characters. See bug 1086769.
241 * We need to let the IM handle the preediting, and
242 * just take in the characters when they're finished being
243 * entered.
244 */
245 gtk_im_context_set_use_preedit(tc->imc, FALSE);
246 gtk_im_context_set_client_window(tc->imc, canvas->window);
248 g_signal_connect(G_OBJECT(canvas), "focus_in_event", G_CALLBACK(sptc_focus_in), tc);
249 g_signal_connect(G_OBJECT(canvas), "focus_out_event", G_CALLBACK(sptc_focus_out), tc);
250 g_signal_connect(G_OBJECT(tc->imc), "commit", G_CALLBACK(sptc_commit), tc);
252 if (GTK_WIDGET_HAS_FOCUS(canvas)) {
253 sptc_focus_in(canvas, NULL, tc);
254 }
255 }
257 if (((SPEventContextClass *) parent_class)->setup)
258 ((SPEventContextClass *) parent_class)->setup(ec);
260 SPItem *item = sp_desktop_selection(ec->desktop)->singleItem();
261 if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
262 ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
263 Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
264 if (shape_repr) {
265 ec->shape_repr = shape_repr;
266 Inkscape::GC::anchor(shape_repr);
267 sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
268 }
269 }
271 tc->sel_changed_connection = sp_desktop_selection(desktop)->connectChanged(
272 sigc::bind(sigc::ptr_fun(&sp_text_context_selection_changed), tc)
273 );
274 tc->sel_modified_connection = sp_desktop_selection(desktop)->connectModified(
275 sigc::bind(sigc::ptr_fun(&sp_text_context_selection_modified), tc)
276 );
277 tc->style_set_connection = desktop->connectSetStyle(
278 sigc::bind(sigc::ptr_fun(&sp_text_context_style_set), tc)
279 );
280 tc->style_query_connection = desktop->connectQueryStyle(
281 sigc::bind(sigc::ptr_fun(&sp_text_context_style_query), tc)
282 );
284 sp_text_context_selection_changed(sp_desktop_selection(desktop), tc);
286 if (prefs_get_int_attribute("tools.text", "selcue", 0) != 0) {
287 ec->enableSelectionCue();
288 }
289 if (prefs_get_int_attribute("tools.text", "gradientdrag", 0) != 0) {
290 ec->enableGrDrag();
291 }
292 }
294 static void
295 sp_text_context_finish(SPEventContext *ec)
296 {
297 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
299 ec->enableGrDrag(false);
301 tc->style_set_connection.disconnect();
302 tc->style_query_connection.disconnect();
303 tc->sel_changed_connection.disconnect();
304 tc->sel_modified_connection.disconnect();
306 sp_text_context_forget_text(SP_TEXT_CONTEXT(ec));
308 if (tc->imc) {
309 g_object_unref(G_OBJECT(tc->imc));
310 tc->imc = NULL;
311 }
313 if (tc->timeout) {
314 gtk_timeout_remove(tc->timeout);
315 tc->timeout = 0;
316 }
318 if (tc->cursor) {
319 gtk_object_destroy(GTK_OBJECT(tc->cursor));
320 tc->cursor = NULL;
321 }
323 if (tc->indicator) {
324 gtk_object_destroy(GTK_OBJECT(tc->indicator));
325 tc->indicator = NULL;
326 }
328 if (tc->frame) {
329 gtk_object_destroy(GTK_OBJECT(tc->frame));
330 tc->frame = NULL;
331 }
333 for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ;
334 it != tc->text_selection_quads.end() ; ++it) {
335 sp_canvas_item_hide(*it);
336 gtk_object_destroy(*it);
337 }
338 tc->text_selection_quads.clear();
340 if (ec->desktop) {
341 sp_signal_disconnect_by_data(sp_desktop_canvas(ec->desktop), tc);
342 }
343 }
346 static gint
347 sp_text_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event)
348 {
349 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
350 SPDesktop *desktop = ec->desktop;
351 SPItem *item_ungrouped;
353 gint ret = FALSE;
355 sp_text_context_validate_cursor_iterators(tc);
357 switch (event->type) {
358 case GDK_BUTTON_PRESS:
359 if (event->button.button == 1) {
360 // find out clicked item, disregarding groups
361 item_ungrouped = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE);
362 if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
363 sp_desktop_selection(ec->desktop)->set(item_ungrouped);
364 if (tc->text) {
365 // find out click point in document coordinates
366 NR::Point p = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
367 // set the cursor closest to that point
368 tc->text_sel_start = tc->text_sel_end = sp_te_get_position_by_coords(tc->text, p);
369 // update display
370 sp_text_context_update_cursor(tc);
371 sp_text_context_update_text_selection(tc);
372 tc->dragging = 1;
373 }
374 ret = TRUE;
375 }
376 }
377 break;
378 case GDK_2BUTTON_PRESS:
379 if (event->button.button == 1 && tc->text) {
380 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
381 if (layout) {
382 if (!layout->isStartOfWord(tc->text_sel_start))
383 tc->text_sel_start.prevStartOfWord();
384 if (!layout->isEndOfWord(tc->text_sel_end))
385 tc->text_sel_end.nextEndOfWord();
386 sp_text_context_update_cursor(tc);
387 sp_text_context_update_text_selection(tc);
388 tc->dragging = 2;
389 ret = TRUE;
390 }
391 }
392 break;
393 case GDK_3BUTTON_PRESS:
394 if (event->button.button == 1 && tc->text) {
395 tc->text_sel_start.thisStartOfLine();
396 tc->text_sel_end.thisEndOfLine();
397 sp_text_context_update_cursor(tc);
398 sp_text_context_update_text_selection(tc);
399 tc->dragging = 3;
400 ret = TRUE;
401 }
402 break;
403 case GDK_BUTTON_RELEASE:
404 if (event->button.button == 1 && tc->dragging) {
405 tc->dragging = 0;
406 ret = TRUE;
407 }
408 break;
409 case GDK_MOTION_NOTIFY:
410 if (event->motion.state & GDK_BUTTON1_MASK && tc->dragging) {
411 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
412 if (!layout) break;
413 // find out click point in document coordinates
414 NR::Point p = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
415 // set the cursor closest to that point
416 Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(tc->text, p);
417 if (tc->dragging == 2) {
418 // double-click dragging: go by word
419 if (new_end < tc->text_sel_start) {
420 if (!layout->isStartOfWord(new_end))
421 new_end.prevStartOfWord();
422 } else
423 if (!layout->isEndOfWord(new_end))
424 new_end.nextEndOfWord();
425 } else if (tc->dragging == 3) {
426 // triple-click dragging: go by line
427 if (new_end < tc->text_sel_start)
428 new_end.thisStartOfLine();
429 else
430 new_end.thisEndOfLine();
431 }
432 // update display
433 if (tc->text_sel_end != new_end) {
434 tc->text_sel_end = new_end;
435 sp_text_context_update_cursor(tc);
436 sp_text_context_update_text_selection(tc);
437 }
438 ret = TRUE;
439 break;
440 }
441 // find out item under mouse, disregarding groups
442 item_ungrouped = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE);
443 if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
444 sp_canvas_item_show(tc->indicator);
445 SP_CTRLRECT(tc->indicator)->setRectangle(sp_item_bbox_desktop(item_ungrouped));
447 ec->cursor_shape = cursor_text_insert_xpm;
448 ec->cursor_pixbuf = gdk_pixbuf_new_from_inline(
449 -1,
450 cursor_text_insert_pixbuf,
451 FALSE,
452 NULL);
453 ec->hot_x = 7;
454 ec->hot_y = 10;
455 sp_event_context_update_cursor(ec);
456 sp_text_context_update_text_selection(tc);
458 if (SP_IS_TEXT (item_ungrouped)) {
459 desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
460 } else {
461 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."));
462 }
464 tc->over_text = true;
466 ret = TRUE;
467 }
468 break;
469 default:
470 break;
471 }
473 if (!ret) {
474 if (((SPEventContextClass *) parent_class)->item_handler)
475 ret = ((SPEventContextClass *) parent_class)->item_handler(ec, item, event);
476 }
478 return ret;
479 }
481 static void
482 sp_text_context_setup_text(SPTextContext *tc)
483 {
484 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
486 /* Create <text> */
487 Inkscape::XML::Node *rtext = sp_repr_new("svg:text");
488 rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
490 /* Set style */
491 sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "tools.text", true);
493 sp_repr_set_svg_double(rtext, "x", tc->pdoc[NR::X]);
494 sp_repr_set_svg_double(rtext, "y", tc->pdoc[NR::Y]);
496 /* Create <tspan> */
497 Inkscape::XML::Node *rtspan = sp_repr_new("svg:tspan");
498 rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
499 rtext->addChild(rtspan, NULL);
500 Inkscape::GC::release(rtspan);
502 /* Create TEXT */
503 Inkscape::XML::Node *rstring = sp_repr_new_text("");
504 rtspan->addChild(rstring, NULL);
505 Inkscape::GC::release(rstring);
506 SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
507 /* fixme: Is selection::changed really immediate? */
508 /* yes, it's immediate .. why does it matter? */
509 sp_desktop_selection(ec->desktop)->set(text_item);
510 Inkscape::GC::release(rtext);
511 text_item->transform = SP_ITEM(ec->desktop->currentRoot())->getRelativeTransform(ec->desktop->currentLayer());
512 text_item->updateRepr();
513 sp_document_done(sp_desktop_document(ec->desktop));
514 }
516 /**
517 * Insert the character indicated by tc.uni to replace the current selection,
518 * and reset tc.uni/tc.unipos to empty string.
519 *
520 * \pre tc.uni/tc.unipos non-empty.
521 */
522 static void
523 insert_uni_char(SPTextContext *const tc)
524 {
525 g_return_if_fail(tc->unipos
526 && tc->unipos < sizeof(tc->uni)
527 && tc->uni[tc->unipos] == '\0');
528 unsigned int uv;
529 sscanf(tc->uni, "%x", &uv);
530 tc->unipos = 0;
531 tc->uni[tc->unipos] = '\0';
533 if ( !g_unichar_isprint(static_cast<gunichar>(uv))
534 && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
535 // This may be due to bad input, so it goes to statusbar.
536 tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
537 _("Non-printable character"));
538 } else {
539 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
540 sp_text_context_setup_text(tc);
541 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
542 }
544 gchar u[10];
545 guint const len = g_unichar_to_utf8(uv, u);
546 u[len] = '\0';
548 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
549 sp_text_context_update_cursor(tc);
550 sp_text_context_update_text_selection(tc);
551 sp_document_done(sp_desktop_document(tc->desktop));
552 }
553 }
555 static void
556 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
557 {
558 unsigned int uv;
559 sscanf(hex, "%x", &uv);
560 if (!g_unichar_isprint((gunichar) uv)) {
561 uv = 0xfffd;
562 }
563 guint const len = g_unichar_to_utf8(uv, utf8);
564 utf8[len] = '\0';
565 }
567 static void
568 show_curr_uni_char(SPTextContext *const tc)
569 {
570 g_return_if_fail(tc->unipos < sizeof(tc->uni)
571 && tc->uni[tc->unipos] == '\0');
572 if (tc->unipos) {
573 char utf8[10];
574 hex_to_printable_utf8_buf(tc->uni, utf8);
576 /* Status bar messages are in pango markup, so we need xml escaping. */
577 if (utf8[1] == '\0') {
578 switch(utf8[0]) {
579 case '<': strcpy(utf8, "<"); break;
580 case '>': strcpy(utf8, ">"); break;
581 case '&': strcpy(utf8, "&"); break;
582 default: break;
583 }
584 }
585 tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
586 _("Unicode: %s: %s"), tc->uni, utf8);
587 } else {
588 tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode: "));
589 }
590 }
592 static gint
593 sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
594 {
595 SPTextContext *const tc = SP_TEXT_CONTEXT(ec);
597 SPDesktop *desktop = ec->desktop;
599 sp_canvas_item_hide(tc->indicator);
601 sp_text_context_validate_cursor_iterators(tc);
603 ec->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100);
605 switch (event->type) {
606 case GDK_BUTTON_PRESS:
607 if (event->button.button == 1) {
609 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(ec);
611 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
612 return TRUE;
613 }
615 // save drag origin
616 ec->xp = (gint) event->button.x;
617 ec->yp = (gint) event->button.y;
618 ec->within_tolerance = true;
620 NR::Point const button_pt(event->button.x, event->button.y);
621 tc->p0 = desktop->w2d(button_pt);
622 Inkscape::Rubberband::get()->start(desktop, tc->p0);
623 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
624 GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
625 GDK_POINTER_MOTION_MASK,
626 NULL, event->button.time);
627 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
628 tc->creating = 1;
630 /* Processed */
631 return TRUE;
632 }
633 break;
634 case GDK_MOTION_NOTIFY:
635 if (tc->over_text) {
636 tc->over_text = 0;
637 // update cursor and statusbar: we are not over a text object now
638 ec->cursor_shape = cursor_text_xpm;
639 ec->cursor_pixbuf = gdk_pixbuf_new_from_inline(
640 -1,
641 cursor_text_pixbuf,
642 FALSE,
643 NULL);
644 ec->hot_x = 7;
645 ec->hot_y = 7;
646 sp_event_context_update_cursor(ec);
647 desktop->event_context->defaultMessageContext()->clear();
648 }
650 if (tc->creating && event->motion.state & GDK_BUTTON1_MASK) {
651 if ( ec->within_tolerance
652 && ( abs( (gint) event->motion.x - ec->xp ) < ec->tolerance )
653 && ( abs( (gint) event->motion.y - ec->yp ) < ec->tolerance ) ) {
654 break; // do not drag if we're within tolerance from origin
655 }
656 // Once the user has moved farther than tolerance from the original location
657 // (indicating they intend to draw, not click), then always process the
658 // motion notify coordinates as given (no snapping back to origin)
659 ec->within_tolerance = false;
661 NR::Point const motion_pt(event->motion.x, event->motion.y);
662 NR::Point const p = desktop->w2d(motion_pt);
664 Inkscape::Rubberband::get()->move(p);
665 gobble_motion_events(GDK_BUTTON1_MASK);
667 // status text
668 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::X]), desktop->namedview->getDefaultMetric());
669 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::Y]), desktop->namedview->getDefaultMetric());
670 ec->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("<b>Flowed text frame</b>: %s × %s"), xs->str, ys->str);
671 g_string_free(xs, FALSE);
672 g_string_free(ys, FALSE);
674 }
675 break;
676 case GDK_BUTTON_RELEASE:
677 if (event->button.button == 1) {
679 if (tc->grabbed) {
680 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
681 tc->grabbed = NULL;
682 }
684 Inkscape::Rubberband::get()->stop();
686 if (tc->creating && ec->within_tolerance) {
687 /* Button 1, set X & Y & new item */
688 sp_desktop_selection(desktop)->clear();
689 NR::Point dtp = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
690 tc->pdoc = sp_desktop_dt2root_xy_point(ec->desktop, dtp);
692 tc->show = TRUE;
693 tc->phase = 1;
694 tc->nascent_object = 1; // new object was just created
696 /* Cursor */
697 sp_canvas_item_show(tc->cursor);
698 // Cursor height is defined by the new text object's font size; it needs to be set
699 // articifically here, for the text object does not exist yet:
700 double cursor_height = sp_desktop_get_font_size_tool(ec->desktop);
701 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), dtp, dtp + NR::Point(0, cursor_height));
702 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
704 ec->within_tolerance = false;
705 } else if (tc->creating) {
706 NR::Point const button_pt(event->button.x, event->button.y);
707 NR::Point p1 = desktop->w2d(button_pt);
708 double cursor_height = sp_desktop_get_font_size_tool(ec->desktop);
709 if (fabs(p1[NR::Y] - tc->p0[NR::Y]) > cursor_height) {
710 // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
711 SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
712 sp_desktop_selection(desktop)->set(ft);
713 ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
714 sp_document_done(sp_desktop_document(desktop));
715 } else {
716 ec->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
717 }
718 }
719 tc->creating = false;
720 return TRUE;
721 }
722 break;
723 case GDK_KEY_PRESS: {
724 guint const group0_keyval = get_group0_keyval(&event->key);
726 if (group0_keyval == GDK_KP_Add ||
727 group0_keyval == GDK_KP_Subtract) {
728 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
729 break; // otherwise pass on keypad +/- so they can zoom
730 }
732 if ((tc->text) || (tc->nascent_object)) {
733 // there is an active text object in this context, or a new object was just created
735 if (tc->unimode || !tc->imc
736 || (MOD__CTRL && MOD__SHIFT) // input methods tend to steal this for unimode,
737 // but we have our own so make sure they don't swallow it
738 || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
739 //IM did not consume the key, or we're in unimode
741 if (!MOD__CTRL_ONLY && tc->unimode) {
742 /* TODO: ISO 14755 (section 3 Definitions) says that we should also
743 accept the first 6 characters of alphabets other than the latin
744 alphabet "if the Latin alphabet is not used". The below is also
745 reasonable (viz. hope that the user's keyboard includes latin
746 characters and force latin interpretation -- just as we do for our
747 keyboard shortcuts), but differs from the ISO 14755
748 recommendation. */
749 switch (group0_keyval) {
750 case GDK_space:
751 case GDK_KP_Space: {
752 if (tc->unipos) {
753 insert_uni_char(tc);
754 }
755 /* Stay in unimode. */
756 show_curr_uni_char(tc);
757 return TRUE;
758 }
760 case GDK_BackSpace: {
761 g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
762 if (tc->unipos) {
763 tc->uni[--tc->unipos] = '\0';
764 }
765 show_curr_uni_char(tc);
766 return TRUE;
767 }
769 case GDK_Return:
770 case GDK_KP_Enter: {
771 if (tc->unipos) {
772 insert_uni_char(tc);
773 }
774 /* Exit unimode. */
775 tc->unimode = false;
776 ec->defaultMessageContext()->clear();
777 return TRUE;
778 }
780 case GDK_Escape: {
781 // Cancel unimode.
782 tc->unimode = false;
783 gtk_im_context_reset(tc->imc);
784 ec->defaultMessageContext()->clear();
785 return TRUE;
786 }
788 case GDK_Shift_L:
789 case GDK_Shift_R:
790 break;
792 default: {
793 if (g_ascii_isxdigit(group0_keyval)) {
794 g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
795 tc->uni[tc->unipos++] = group0_keyval;
796 tc->uni[tc->unipos] = '\0';
797 if (tc->unipos == 8) {
798 /* This behaviour is partly to allow us to continue to
799 use a fixed-length buffer for tc->uni. Reason for
800 choosing the number 8 is that it's the length of
801 ``canonical form'' mentioned in the ISO 14755 spec.
802 An advantage over choosing 6 is that it allows using
803 backspace for typos & misremembering when entering a
804 6-digit number. */
805 insert_uni_char(tc);
806 }
807 show_curr_uni_char(tc);
808 return TRUE;
809 } else {
810 /* The intent is to ignore but consume characters that could be
811 typos for hex digits. Gtk seems to ignore & consume all
812 non-hex-digits, and we do similar here. Though note that some
813 shortcuts (like keypad +/- for zoom) get processed before
814 reaching this code. */
815 return TRUE;
816 }
817 }
818 }
819 }
821 bool (Inkscape::Text::Layout::iterator::*cursor_movement_operator)() = NULL;
823 /* Neither unimode nor IM consumed key; process text tool shortcuts */
824 switch (group0_keyval) {
825 case GDK_x:
826 case GDK_X:
827 if (MOD__ALT_ONLY) {
828 desktop->setToolboxFocusTo ("altx-text");
829 return TRUE;
830 }
831 break;
832 case GDK_space:
833 if (MOD__CTRL_ONLY) {
834 /* No-break space */
835 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
836 sp_text_context_setup_text(tc);
837 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
838 }
839 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
840 sp_text_context_update_cursor(tc);
841 sp_text_context_update_text_selection(tc);
842 ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
843 sp_document_done(sp_desktop_document(ec->desktop));
844 return TRUE;
845 }
846 break;
847 case GDK_U:
848 case GDK_u:
849 if (MOD__CTRL_ONLY) {
850 if (tc->unimode) {
851 tc->unimode = false;
852 ec->defaultMessageContext()->clear();
853 } else {
854 tc->unimode = true;
855 tc->unipos = 0;
856 ec->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode: "));
857 }
858 if (tc->imc) {
859 gtk_im_context_reset(tc->imc);
860 }
861 return TRUE;
862 }
863 break;
864 case GDK_B:
865 case GDK_b:
866 if (MOD__CTRL_ONLY && tc->text) {
867 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
868 SPCSSAttr *css = sp_repr_css_attr_new();
869 if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
870 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
871 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
872 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
873 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
874 sp_repr_css_set_property(css, "font-weight", "bold");
875 else
876 sp_repr_css_set_property(css, "font-weight", "normal");
877 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
878 sp_repr_css_attr_unref(css);
879 sp_document_done(sp_desktop_document(ec->desktop));
880 sp_text_context_update_cursor(tc);
881 sp_text_context_update_text_selection(tc);
882 return TRUE;
883 }
884 break;
885 case GDK_I:
886 case GDK_i:
887 if (MOD__CTRL_ONLY && tc->text) {
888 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
889 SPCSSAttr *css = sp_repr_css_attr_new();
890 if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL)
891 sp_repr_css_set_property(css, "font-style", "italic");
892 else
893 sp_repr_css_set_property(css, "font-style", "normal");
894 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
895 sp_repr_css_attr_unref(css);
896 sp_document_done(sp_desktop_document(ec->desktop));
897 sp_text_context_update_cursor(tc);
898 sp_text_context_update_text_selection(tc);
899 return TRUE;
900 }
901 break;
903 case GDK_A:
904 case GDK_a:
905 if (MOD__CTRL_ONLY && tc->text) {
906 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
907 if (layout) {
908 tc->text_sel_start = layout->begin();
909 tc->text_sel_end = layout->end();
910 sp_text_context_update_cursor(tc);
911 sp_text_context_update_text_selection(tc);
912 return TRUE;
913 }
914 }
915 break;
917 case GDK_Return:
918 case GDK_KP_Enter:
919 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
920 sp_text_context_setup_text(tc);
921 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
922 }
923 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
924 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
925 sp_text_context_update_cursor(tc);
926 sp_text_context_update_text_selection(tc);
927 sp_document_done(sp_desktop_document(ec->desktop));
928 return TRUE;
929 case GDK_BackSpace:
930 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
931 if (tc->text_sel_start == tc->text_sel_end)
932 tc->text_sel_start.prevCursorPosition();
933 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
934 sp_text_context_update_cursor(tc);
935 sp_text_context_update_text_selection(tc);
936 sp_document_done(sp_desktop_document(ec->desktop));
937 }
938 return TRUE;
939 case GDK_Delete:
940 case GDK_KP_Delete:
941 if (tc->text) {
942 if (tc->text_sel_start == tc->text_sel_end)
943 tc->text_sel_end.nextCursorPosition();
944 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
945 sp_text_context_update_cursor(tc);
946 sp_text_context_update_text_selection(tc);
947 sp_document_done(sp_desktop_document(ec->desktop));
948 }
949 return TRUE;
950 case GDK_Left:
951 case GDK_KP_Left:
952 case GDK_KP_4:
953 if (tc->text) {
954 if (MOD__ALT) {
955 if (MOD__SHIFT)
956 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-10, 0));
957 else
958 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-1, 0));
959 sp_text_context_update_cursor(tc);
960 sp_text_context_update_text_selection(tc);
961 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:left");
962 } else {
963 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorLeftWithControl
964 : &Inkscape::Text::Layout::iterator::cursorLeft;
965 break;
966 }
967 }
968 return TRUE;
969 case GDK_Right:
970 case GDK_KP_Right:
971 case GDK_KP_6:
972 if (tc->text) {
973 if (MOD__ALT) {
974 if (MOD__SHIFT)
975 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(10, 0));
976 else
977 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(1, 0));
978 sp_text_context_update_cursor(tc);
979 sp_text_context_update_text_selection(tc);
980 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:right");
981 } else {
982 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorRightWithControl
983 : &Inkscape::Text::Layout::iterator::cursorRight;
984 break;
985 }
986 }
987 return TRUE;
988 case GDK_Up:
989 case GDK_KP_Up:
990 case GDK_KP_8:
991 if (tc->text) {
992 if (MOD__ALT) {
993 if (MOD__SHIFT)
994 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -10));
995 else
996 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -1));
997 sp_text_context_update_cursor(tc);
998 sp_text_context_update_text_selection(tc);
999 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:up");
1000 } else {
1001 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorUpWithControl
1002 : &Inkscape::Text::Layout::iterator::cursorUp;
1003 break;
1004 }
1005 }
1006 return TRUE;
1007 case GDK_Down:
1008 case GDK_KP_Down:
1009 case GDK_KP_2:
1010 if (tc->text) {
1011 if (MOD__ALT) {
1012 if (MOD__SHIFT)
1013 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 10));
1014 else
1015 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 1));
1016 sp_text_context_update_cursor(tc);
1017 sp_text_context_update_text_selection(tc);
1018 sp_document_maybe_done(sp_desktop_document(ec->desktop), "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");
1073 sp_text_context_update_cursor(tc);
1074 sp_text_context_update_text_selection(tc);
1075 return TRUE;
1076 }
1077 }
1078 break;
1079 case GDK_bracketright:
1080 if (tc->text) {
1081 if (MOD__ALT || MOD__CTRL) {
1082 if (MOD__ALT) {
1083 if (MOD__SHIFT) {
1084 // FIXME: alt+shift+[] does not work, don't know why
1085 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1086 } else {
1087 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1088 }
1089 } else {
1090 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 90);
1091 }
1092 sp_document_maybe_done(sp_desktop_document(ec->desktop), "textrot:cw");
1093 sp_text_context_update_cursor(tc);
1094 sp_text_context_update_text_selection(tc);
1095 return TRUE;
1096 }
1097 }
1098 break;
1099 case GDK_less:
1100 case GDK_comma:
1101 if (tc->text) {
1102 if (MOD__ALT) {
1103 if (MOD__CTRL) {
1104 if (MOD__SHIFT)
1105 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1106 else
1107 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1108 sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:dec");
1109 } else {
1110 if (MOD__SHIFT)
1111 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1112 else
1113 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1114 sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:dec");
1115 }
1116 sp_text_context_update_cursor(tc);
1117 sp_text_context_update_text_selection(tc);
1118 return TRUE;
1119 }
1120 }
1121 break;
1122 case GDK_greater:
1123 case GDK_period:
1124 if (tc->text) {
1125 if (MOD__ALT) {
1126 if (MOD__CTRL) {
1127 if (MOD__SHIFT)
1128 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1129 else
1130 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1131 sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:inc");
1132 } else {
1133 if (MOD__SHIFT)
1134 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1135 else
1136 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1137 sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:inc");
1138 }
1139 sp_text_context_update_cursor(tc);
1140 sp_text_context_update_text_selection(tc);
1141 return TRUE;
1142 }
1143 }
1144 break;
1145 default:
1146 break;
1147 }
1149 if (cursor_movement_operator) {
1150 Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
1151 Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
1152 (tc->text_sel_end.*cursor_movement_operator)();
1153 if (!MOD__SHIFT)
1154 tc->text_sel_start = tc->text_sel_end;
1155 if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1156 sp_text_context_update_cursor(tc);
1157 sp_text_context_update_text_selection(tc);
1158 }
1159 return TRUE;
1160 }
1162 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1163 } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1164 // except up/down that are swallowed to prevent the zoom field from activation
1165 if ((group0_keyval == GDK_Up ||
1166 group0_keyval == GDK_Down ||
1167 group0_keyval == GDK_KP_Up ||
1168 group0_keyval == GDK_KP_Down )
1169 && !MOD__CTRL_ONLY) {
1170 return TRUE;
1171 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1172 if (tc->creating) {
1173 tc->creating = 0;
1174 if (tc->grabbed) {
1175 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1176 tc->grabbed = NULL;
1177 }
1178 Inkscape::Rubberband::get()->stop();
1179 }
1180 }
1181 }
1182 break;
1183 }
1185 case GDK_KEY_RELEASE:
1186 if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1187 return TRUE;
1188 }
1189 break;
1190 default:
1191 break;
1192 }
1194 // if nobody consumed it so far
1195 if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1196 return ((SPEventContextClass *) parent_class)->root_handler(ec, event); // send event to parent
1197 } else {
1198 return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1199 }
1200 }
1202 /**
1203 Attempts to paste system clipboard into the currently edited text, returns true on success
1204 */
1205 bool
1206 sp_text_paste_inline(SPEventContext *ec)
1207 {
1208 if (!SP_IS_TEXT_CONTEXT(ec))
1209 return false;
1211 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1213 if ((tc->text) || (tc->nascent_object)) {
1214 // there is an active text object in this context, or a new object was just created
1216 Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1217 Glib::ustring const text = refClipboard->wait_for_text();
1219 if (!text.empty()) {
1221 if (!tc->text) { // create text if none (i.e. if nascent_object)
1222 sp_text_context_setup_text(tc);
1223 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1224 }
1226 // using indices is slow in ustrings. Whatever.
1227 Glib::ustring::size_type begin = 0;
1228 for ( ; ; ) {
1229 Glib::ustring::size_type end = text.find('\n', begin);
1230 if (end == Glib::ustring::npos) {
1231 if (begin != text.length())
1232 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());
1233 break;
1234 }
1235 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());
1236 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1237 begin = end + 1;
1238 }
1239 sp_document_done(sp_desktop_document(ec->desktop));
1241 return true;
1242 }
1243 } // FIXME: else create and select a new object under cursor!
1245 return false;
1246 }
1248 /**
1249 Gets the raw characters that comprise the currently selected text, converting line
1250 breaks into lf characters.
1251 */
1252 Glib::ustring
1253 sp_text_get_selected_text(SPEventContext const *ec)
1254 {
1255 if (!SP_IS_TEXT_CONTEXT(ec))
1256 return "";
1257 SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1258 if (tc->text == NULL)
1259 return "";
1261 return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1262 }
1264 /**
1265 Deletes the currently selected characters. Returns false if there is no
1266 text selection currently.
1267 */
1268 bool sp_text_delete_selection(SPEventContext *ec)
1269 {
1270 if (!SP_IS_TEXT_CONTEXT(ec))
1271 return false;
1272 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1273 if (tc->text == NULL)
1274 return false;
1276 if (tc->text_sel_start == tc->text_sel_end)
1277 return false;
1278 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
1279 sp_text_context_update_cursor(tc);
1280 sp_text_context_update_text_selection(tc);
1281 return true;
1282 }
1284 /**
1285 * \param selection Should not be NULL.
1286 */
1287 static void
1288 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1289 {
1290 g_assert(selection != NULL);
1292 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1294 if (ec->shape_knot_holder) { // destroy knotholder
1295 sp_knot_holder_destroy(ec->shape_knot_holder);
1296 ec->shape_knot_holder = NULL;
1297 }
1299 if (ec->shape_repr) { // remove old listener
1300 sp_repr_remove_listener_by_data(ec->shape_repr, ec);
1301 Inkscape::GC::release(ec->shape_repr);
1302 ec->shape_repr = 0;
1303 }
1305 SPItem *item = selection->singleItem();
1306 if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1307 ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
1308 Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
1309 if (shape_repr) {
1310 ec->shape_repr = shape_repr;
1311 Inkscape::GC::anchor(shape_repr);
1312 sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
1313 }
1314 }
1316 if (tc->text && (item != tc->text)) {
1317 sp_text_context_forget_text(tc);
1318 }
1319 tc->text = NULL;
1321 if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1322 tc->text = item;
1323 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1324 if (layout)
1325 tc->text_sel_start = tc->text_sel_end = layout->end();
1326 } else {
1327 tc->text = NULL;
1328 }
1330 // we update cursor without scrolling, because this position may not be final;
1331 // item_handler moves cusros to the point of click immediately
1332 sp_text_context_update_cursor(tc, false);
1333 sp_text_context_update_text_selection(tc);
1334 }
1336 static void
1337 sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc)
1338 {
1339 sp_text_context_update_cursor(tc);
1340 sp_text_context_update_text_selection(tc);
1341 }
1343 static bool
1344 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1345 {
1346 if (tc->text == NULL)
1347 return false;
1348 if (tc->text_sel_start == tc->text_sel_end)
1349 return false; // will get picked up by the parent and applied to the whole text object
1351 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1352 sp_document_done(sp_desktop_document(tc->desktop));
1353 sp_text_context_update_cursor(tc);
1354 sp_text_context_update_text_selection(tc);
1356 return true;
1357 }
1359 static int
1360 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1361 {
1362 if (tc->text == NULL)
1363 return QUERY_STYLE_NOTHING;
1364 const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1365 if (layout == NULL)
1366 return QUERY_STYLE_NOTHING;
1367 sp_text_context_validate_cursor_iterators(tc);
1369 GSList *styles_list = NULL;
1371 Inkscape::Text::Layout::iterator begin_it, end_it;
1372 if (tc->text_sel_start < tc->text_sel_end) {
1373 begin_it = tc->text_sel_start;
1374 end_it = tc->text_sel_end;
1375 } else {
1376 begin_it = tc->text_sel_end;
1377 end_it = tc->text_sel_start;
1378 }
1379 if (begin_it == end_it)
1380 if (!begin_it.prevCharacter())
1381 end_it.nextCharacter();
1382 for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1383 SPObject const *pos_obj = 0;
1384 void *rawptr = 0;
1385 layout->getSourceOfCharacter(it, &rawptr);
1386 pos_obj = SP_OBJECT(rawptr);
1387 if (pos_obj == 0) continue;
1388 while (SP_OBJECT_STYLE(pos_obj) == NULL && SP_OBJECT_PARENT(pos_obj))
1389 pos_obj = SP_OBJECT_PARENT(pos_obj); // SPStrings don't have style
1390 styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1391 }
1393 int result = sp_desktop_query_style_from_list (styles_list, style, property);
1395 g_slist_free(styles_list);
1396 return result;
1397 }
1399 static void
1400 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1401 {
1402 if (tc->text == NULL)
1403 return;
1404 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1405 if (layout) { // undo can change the text length without us knowing it
1406 layout->validateIterator(&tc->text_sel_start);
1407 layout->validateIterator(&tc->text_sel_end);
1408 }
1409 }
1411 static void
1412 sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see)
1413 {
1414 GdkRectangle im_cursor = { 0, 0, 1, 1 };
1416 if (tc->text) {
1417 NR::Point p0, p1;
1418 sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1419 NR::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1420 NR::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1422 // scroll to show cursor
1423 if (scroll_to_see) {
1424 NR::Point const dm = (d0 + d1) / 2;
1425 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1426 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(&dm, 1.0);
1427 }
1429 sp_canvas_item_show(tc->cursor);
1430 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1432 /* fixme: ... need another transformation to get canvas widget coordinate space? */
1433 im_cursor.x = (int) floor(d0[NR::X]);
1434 im_cursor.y = (int) floor(d0[NR::Y]);
1435 im_cursor.width = (int) floor(d1[NR::X]) - im_cursor.x;
1436 im_cursor.height = (int) floor(d1[NR::Y]) - im_cursor.y;
1438 tc->show = TRUE;
1439 tc->phase = 1;
1441 if (SP_IS_FLOWTEXT(tc->text)) {
1442 SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1443 if (frame) {
1444 sp_canvas_item_show(tc->frame);
1445 SP_CTRLRECT(tc->frame)->setRectangle(sp_item_bbox_desktop(frame));
1446 }
1447 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1448 } else {
1449 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1450 }
1452 } else {
1453 sp_canvas_item_hide(tc->cursor);
1454 sp_canvas_item_hide(tc->frame);
1455 tc->show = FALSE;
1456 if (!tc->nascent_object) {
1457 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
1458 }
1459 }
1461 if (tc->imc) {
1462 gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1463 }
1464 SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1465 }
1467 static void sp_text_context_update_text_selection(SPTextContext *tc)
1468 {
1469 for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1470 sp_canvas_item_hide(*it);
1471 gtk_object_destroy(*it);
1472 }
1473 tc->text_selection_quads.clear();
1475 std::vector<NR::Point> quads;
1476 if (tc->text != NULL)
1477 quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1478 for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1479 SPCanvasItem *quad_canvasitem;
1480 quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1481 sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x000000ff);
1482 sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1483 sp_canvas_item_show(quad_canvasitem);
1484 tc->text_selection_quads.push_back(quad_canvasitem);
1485 }
1486 }
1488 static gint
1489 sp_text_context_timeout(SPTextContext *tc)
1490 {
1491 if (tc->show) {
1492 if (tc->phase) {
1493 tc->phase = 0;
1494 sp_canvas_item_hide(tc->cursor);
1495 } else {
1496 tc->phase = 1;
1497 sp_canvas_item_show(tc->cursor);
1498 }
1499 }
1501 return TRUE;
1502 }
1504 static void
1505 sp_text_context_forget_text(SPTextContext *tc)
1506 {
1507 if (! tc->text) return;
1508 SPItem *ti = tc->text;
1509 /* We have to set it to zero,
1510 * or selection changed signal messes everything up */
1511 tc->text = NULL;
1512 if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1513 Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1514 // the repr may already have been unparented
1515 // if we were called e.g. as the result of
1516 // an undo or the element being removed from
1517 // the XML editor
1518 if ( text_repr && sp_repr_parent(text_repr) ) {
1519 sp_repr_unparent(text_repr);
1520 }
1521 }
1522 }
1524 gint
1525 sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1526 {
1527 gtk_im_context_focus_in(tc->imc);
1528 return FALSE;
1529 }
1531 gint
1532 sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1533 {
1534 gtk_im_context_focus_out(tc->imc);
1535 return FALSE;
1536 }
1538 static void
1539 sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc)
1540 {
1541 if (!tc->text) {
1542 sp_text_context_setup_text(tc);
1543 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1544 }
1546 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1547 sp_text_context_update_cursor(tc);
1548 sp_text_context_update_text_selection(tc);
1550 sp_document_done(SP_OBJECT_DOCUMENT(tc->text));
1551 }
1554 /*
1555 Local Variables:
1556 mode:c++
1557 c-file-style:"stroustrup"
1558 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1559 indent-tabs-mode:nil
1560 fill-column:99
1561 End:
1562 */
1563 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :