a181f4f6fb22d70f63128da9fe536da50ce68dd3
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"
52 #include "text-editing.h"
54 #include "text-context.h"
57 static void sp_text_context_class_init(SPTextContextClass *klass);
58 static void sp_text_context_init(SPTextContext *text_context);
59 static void sp_text_context_dispose(GObject *obj);
61 static void sp_text_context_setup(SPEventContext *ec);
62 static void sp_text_context_finish(SPEventContext *ec);
63 static gint sp_text_context_root_handler(SPEventContext *event_context, GdkEvent *event);
64 static gint sp_text_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
66 static void sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc);
67 static void sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc);
68 static bool sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc);
69 static int sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc);
71 static void sp_text_context_validate_cursor_iterators(SPTextContext *tc);
72 static void sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see = true);
73 static void sp_text_context_update_text_selection(SPTextContext *tc);
74 static gint sp_text_context_timeout(SPTextContext *tc);
75 static void sp_text_context_forget_text(SPTextContext *tc);
77 static gint sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc);
78 static gint sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc);
79 static void sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc);
81 static SPEventContextClass *parent_class;
83 GType
84 sp_text_context_get_type()
85 {
86 static GType type = 0;
87 if (!type) {
88 GTypeInfo info = {
89 sizeof(SPTextContextClass),
90 NULL, NULL,
91 (GClassInitFunc) sp_text_context_class_init,
92 NULL, NULL,
93 sizeof(SPTextContext),
94 4,
95 (GInstanceInitFunc) sp_text_context_init,
96 NULL, /* value_table */
97 };
98 type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPTextContext", &info, (GTypeFlags)0);
99 }
100 return type;
101 }
103 static void
104 sp_text_context_class_init(SPTextContextClass *klass)
105 {
106 GObjectClass *object_class=(GObjectClass *)klass;
107 SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
109 parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
111 object_class->dispose = sp_text_context_dispose;
113 event_context_class->setup = sp_text_context_setup;
114 event_context_class->finish = sp_text_context_finish;
115 event_context_class->root_handler = sp_text_context_root_handler;
116 event_context_class->item_handler = sp_text_context_item_handler;
117 }
119 static void
120 sp_text_context_init(SPTextContext *tc)
121 {
122 SPEventContext *event_context = SP_EVENT_CONTEXT(tc);
124 event_context->cursor_shape = cursor_text_xpm;
125 event_context->hot_x = 7;
126 event_context->hot_y = 7;
128 event_context->xp = 0;
129 event_context->yp = 0;
130 event_context->tolerance = 0;
131 event_context->within_tolerance = false;
133 event_context->shape_repr = NULL;
134 event_context->shape_knot_holder = NULL;
136 tc->imc = NULL;
138 tc->text = NULL;
139 tc->pdoc = NR::Point(0, 0);
140 new (&tc->text_sel_start) Inkscape::Text::Layout::iterator();
141 new (&tc->text_sel_end) Inkscape::Text::Layout::iterator();
142 new (&tc->text_selection_quads) std::vector<SPCanvasItem*>();
144 tc->unimode = false;
146 tc->cursor = NULL;
147 tc->indicator = NULL;
148 tc->frame = NULL;
149 tc->grabbed = NULL;
150 tc->timeout = 0;
151 tc->show = FALSE;
152 tc->phase = 0;
153 tc->nascent_object = 0;
154 tc->over_text = 0;
155 tc->dragging = 0;
156 tc->creating = 0;
158 new (&tc->sel_changed_connection) sigc::connection();
159 new (&tc->sel_modified_connection) sigc::connection();
160 new (&tc->style_set_connection) sigc::connection();
161 new (&tc->style_query_connection) sigc::connection();
162 }
164 static void
165 sp_text_context_dispose(GObject *obj)
166 {
167 SPTextContext *tc = SP_TEXT_CONTEXT(obj);
168 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
169 tc->style_query_connection.~connection();
170 tc->style_set_connection.~connection();
171 tc->sel_changed_connection.~connection();
172 tc->sel_modified_connection.~connection();
173 tc->text_sel_end.~iterator();
174 tc->text_sel_start.~iterator();
175 tc->text_selection_quads.~vector();
176 if (G_OBJECT_CLASS(parent_class)->dispose) {
177 G_OBJECT_CLASS(parent_class)->dispose(obj);
178 }
179 if (tc->grabbed) {
180 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
181 tc->grabbed = NULL;
182 }
184 Inkscape::Rubberband::get()->stop();
186 if (ec->shape_knot_holder) {
187 sp_knot_holder_destroy(ec->shape_knot_holder);
188 ec->shape_knot_holder = NULL;
189 }
190 if (ec->shape_repr) { // remove old listener
191 sp_repr_remove_listener_by_data(ec->shape_repr, ec);
192 Inkscape::GC::release(ec->shape_repr);
193 ec->shape_repr = 0;
194 }
195 }
197 static Inkscape::XML::NodeEventVector ec_shape_repr_events = {
198 NULL, /* child_added */
199 NULL, /* child_removed */
200 ec_shape_event_attr_changed,
201 NULL, /* content_changed */
202 NULL /* order_changed */
203 };
205 static void
206 sp_text_context_setup(SPEventContext *ec)
207 {
208 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
209 SPDesktop *desktop = ec->desktop;
211 tc->cursor = sp_canvas_item_new(SP_DT_CONTROLS(desktop), SP_TYPE_CTRLLINE, NULL);
212 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), 100, 0, 100, 100);
213 sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
214 sp_canvas_item_hide(tc->cursor);
216 tc->indicator = sp_canvas_item_new(SP_DT_CONTROLS(desktop), SP_TYPE_CTRLRECT, NULL);
217 SP_CTRLRECT(tc->indicator)->setRectangle(NR::Rect(NR::Point(0, 0), NR::Point(100, 100)));
218 SP_CTRLRECT(tc->indicator)->setColor(0x0000ff7f, false, 0);
219 sp_canvas_item_hide(tc->indicator);
221 tc->frame = sp_canvas_item_new(SP_DT_CONTROLS(desktop), SP_TYPE_CTRLRECT, NULL);
222 SP_CTRLRECT(tc->frame)->setRectangle(NR::Rect(NR::Point(0, 0), NR::Point(100, 100)));
223 SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0);
224 sp_canvas_item_hide(tc->frame);
226 tc->timeout = gtk_timeout_add(250, (GtkFunction) sp_text_context_timeout, ec);
228 tc->imc = gtk_im_multicontext_new();
229 if (tc->imc) {
230 GtkWidget *canvas = GTK_WIDGET(SP_DT_CANVAS(desktop));
232 /* im preedit handling is very broken in inkscape for
233 * multi-byte characters. See bug 1086769.
234 * We need to let the IM handle the preediting, and
235 * just take in the characters when they're finished being
236 * entered.
237 */
238 gtk_im_context_set_use_preedit(tc->imc, FALSE);
239 gtk_im_context_set_client_window(tc->imc, canvas->window);
241 g_signal_connect(G_OBJECT(canvas), "focus_in_event", G_CALLBACK(sptc_focus_in), tc);
242 g_signal_connect(G_OBJECT(canvas), "focus_out_event", G_CALLBACK(sptc_focus_out), tc);
243 g_signal_connect(G_OBJECT(tc->imc), "commit", G_CALLBACK(sptc_commit), tc);
245 if (GTK_WIDGET_HAS_FOCUS(canvas)) {
246 sptc_focus_in(canvas, NULL, tc);
247 }
248 }
250 if (((SPEventContextClass *) parent_class)->setup)
251 ((SPEventContextClass *) parent_class)->setup(ec);
253 SPItem *item = SP_DT_SELECTION(ec->desktop)->singleItem();
254 if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
255 ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
256 Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
257 if (shape_repr) {
258 ec->shape_repr = shape_repr;
259 Inkscape::GC::anchor(shape_repr);
260 sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
261 sp_repr_synthesize_events(shape_repr, &ec_shape_repr_events, ec);
262 }
263 }
265 tc->sel_changed_connection = SP_DT_SELECTION(desktop)->connectChanged(
266 sigc::bind(sigc::ptr_fun(&sp_text_context_selection_changed), tc)
267 );
268 tc->sel_modified_connection = SP_DT_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_DT_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 ec->enableGrDrag(false);
295 tc->style_set_connection.disconnect();
296 tc->style_query_connection.disconnect();
297 tc->sel_changed_connection.disconnect();
298 tc->sel_modified_connection.disconnect();
300 sp_text_context_forget_text(SP_TEXT_CONTEXT(ec));
302 if (tc->imc) {
303 g_object_unref(G_OBJECT(tc->imc));
304 tc->imc = NULL;
305 }
307 if (tc->timeout) {
308 gtk_timeout_remove(tc->timeout);
309 tc->timeout = 0;
310 }
312 if (tc->cursor) {
313 gtk_object_destroy(GTK_OBJECT(tc->cursor));
314 tc->cursor = NULL;
315 }
317 if (tc->indicator) {
318 gtk_object_destroy(GTK_OBJECT(tc->indicator));
319 tc->indicator = NULL;
320 }
322 if (tc->frame) {
323 gtk_object_destroy(GTK_OBJECT(tc->frame));
324 tc->frame = NULL;
325 }
327 for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ;
328 it != tc->text_selection_quads.end() ; ++it) {
329 sp_canvas_item_hide(*it);
330 gtk_object_destroy(*it);
331 }
332 tc->text_selection_quads.clear();
334 if (ec->desktop) {
335 sp_signal_disconnect_by_data(SP_DT_CANVAS(ec->desktop), tc);
336 }
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_DT_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::Node *rtext = sp_repr_new("svg:text");
477 rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
479 /* Set style */
480 sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "tools.text", true);
482 sp_repr_set_svg_double(rtext, "x", tc->pdoc[NR::X]);
483 sp_repr_set_svg_double(rtext, "y", tc->pdoc[NR::Y]);
485 /* Create <tspan> */
486 Inkscape::XML::Node *rtspan = sp_repr_new("svg:tspan");
487 rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
488 rtext->addChild(rtspan, NULL);
489 Inkscape::GC::release(rtspan);
491 /* Create TEXT */
492 Inkscape::XML::Node *rstring = sp_repr_new_text("");
493 rtspan->addChild(rstring, NULL);
494 Inkscape::GC::release(rstring);
495 SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
496 /* fixme: Is selection::changed really immediate? */
497 /* yes, it's immediate .. why does it matter? */
498 SP_DT_SELECTION(ec->desktop)->set(text_item);
499 Inkscape::GC::release(rtext);
500 text_item->transform = SP_ITEM(ec->desktop->currentRoot())->getRelativeTransform(ec->desktop->currentLayer());
501 text_item->updateRepr();
502 sp_document_done(SP_DT_DOCUMENT(ec->desktop));
503 }
505 /**
506 * Insert the character indicated by tc.uni to replace the current selection,
507 * and reset tc.uni/tc.unipos to empty string.
508 *
509 * \pre tc.uni/tc.unipos non-empty.
510 */
511 static void
512 insert_uni_char(SPTextContext *const tc)
513 {
514 g_return_if_fail(tc->unipos
515 && tc->unipos < sizeof(tc->uni)
516 && tc->uni[tc->unipos] == '\0');
517 unsigned int uv;
518 sscanf(tc->uni, "%x", &uv);
519 tc->unipos = 0;
520 tc->uni[tc->unipos] = '\0';
522 if (!g_unichar_isprint((gunichar) uv)) {
523 // This may be due to bad input, so it goes to statusbar.
524 tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
525 _("Non-printable character"));
526 } else {
527 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
528 sp_text_context_setup_text(tc);
529 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
530 }
532 gchar u[10];
533 guint const len = g_unichar_to_utf8(uv, u);
534 u[len] = '\0';
536 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
537 sp_text_context_update_cursor(tc);
538 sp_text_context_update_text_selection(tc);
539 sp_document_done(SP_DT_DOCUMENT(tc->desktop));
540 }
541 }
543 static void
544 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
545 {
546 unsigned int uv;
547 sscanf(hex, "%x", &uv);
548 if (!g_unichar_isprint((gunichar) uv)) {
549 uv = 0xfffd;
550 }
551 guint const len = g_unichar_to_utf8(uv, utf8);
552 utf8[len] = '\0';
553 }
555 static void
556 show_curr_uni_char(SPTextContext *const tc)
557 {
558 g_return_if_fail(tc->unipos < sizeof(tc->uni)
559 && tc->uni[tc->unipos] == '\0');
560 if (tc->unipos) {
561 char utf8[10];
562 hex_to_printable_utf8_buf(tc->uni, utf8);
564 /* Status bar messages are in pango markup, so we need xml escaping. */
565 if (utf8[1] == '\0') {
566 switch(utf8[0]) {
567 case '<': strcpy(utf8, "<"); break;
568 case '>': strcpy(utf8, ">"); break;
569 case '&': strcpy(utf8, "&"); break;
570 default: break;
571 }
572 }
573 tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
574 _("Unicode: %s: %s"), tc->uni, utf8);
575 } else {
576 tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode: "));
577 }
578 }
580 static gint
581 sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
582 {
583 SPTextContext *const tc = SP_TEXT_CONTEXT(ec);
585 SPDesktop *desktop = ec->desktop;
587 sp_canvas_item_hide(tc->indicator);
589 sp_text_context_validate_cursor_iterators(tc);
591 ec->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100);
593 switch (event->type) {
594 case GDK_BUTTON_PRESS:
595 if (event->button.button == 1) {
597 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(ec);
599 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
600 return TRUE;
601 }
603 // save drag origin
604 ec->xp = (gint) event->button.x;
605 ec->yp = (gint) event->button.y;
606 ec->within_tolerance = true;
608 NR::Point const button_pt(event->button.x, event->button.y);
609 tc->p0 = desktop->w2d(button_pt);
610 Inkscape::Rubberband::get()->start(desktop, tc->p0);
611 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
612 GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
613 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
614 NULL, event->button.time);
615 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
616 tc->creating = 1;
618 /* Processed */
619 return TRUE;
620 }
621 break;
622 case GDK_MOTION_NOTIFY:
623 if (tc->over_text) {
624 tc->over_text = 0;
625 // update cursor and statusbar: we are not over a text object now
626 ec->cursor_shape = cursor_text_xpm;
627 ec->hot_x = 7;
628 ec->hot_y = 7;
629 sp_event_context_update_cursor(ec);
630 desktop->event_context->defaultMessageContext()->clear();
631 }
633 if (tc->creating && event->motion.state & GDK_BUTTON1_MASK) {
634 if ( ec->within_tolerance
635 && ( abs( (gint) event->motion.x - ec->xp ) < ec->tolerance )
636 && ( abs( (gint) event->motion.y - ec->yp ) < ec->tolerance ) ) {
637 break; // do not drag if we're within tolerance from origin
638 }
639 // Once the user has moved farther than tolerance from the original location
640 // (indicating they intend to draw, not click), then always process the
641 // motion notify coordinates as given (no snapping back to origin)
642 ec->within_tolerance = false;
644 NR::Point const motion_pt(event->motion.x, event->motion.y);
645 NR::Point const p = desktop->w2d(motion_pt);
647 Inkscape::Rubberband::get()->move(p);
648 gobble_motion_events(GDK_BUTTON1_MASK);
650 // status text
651 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::X]), desktop->namedview->getDefaultMetric());
652 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::Y]), desktop->namedview->getDefaultMetric());
653 ec->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("<b>Flowed text frame</b>: %s × %s"), xs->str, ys->str);
654 g_string_free(xs, FALSE);
655 g_string_free(ys, FALSE);
657 }
658 break;
659 case GDK_BUTTON_RELEASE:
660 if (event->button.button == 1) {
662 if (tc->grabbed) {
663 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
664 tc->grabbed = NULL;
665 }
667 Inkscape::Rubberband::get()->stop();
669 if (tc->creating && ec->within_tolerance) {
670 /* Button 1, set X & Y & new item */
671 SP_DT_SELECTION(desktop)->clear();
672 NR::Point dtp = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
673 tc->pdoc = sp_desktop_dt2root_xy_point(ec->desktop, dtp);
675 tc->show = TRUE;
676 tc->phase = 1;
677 tc->nascent_object = 1; // new object was just created
679 /* Cursor */
680 sp_canvas_item_show(tc->cursor);
681 // Cursor height is defined by the new text object's font size; it needs to be set
682 // articifically here, for the text object does not exist yet:
683 double cursor_height = sp_desktop_get_font_size_tool(ec->desktop);
684 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), dtp, dtp + NR::Point(0, cursor_height));
685 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
687 ec->within_tolerance = false;
688 } else if (tc->creating) {
689 NR::Point const button_pt(event->button.x, event->button.y);
690 NR::Point p1 = desktop->w2d(button_pt);
691 double cursor_height = sp_desktop_get_font_size_tool(ec->desktop);
692 if (fabs(p1[NR::Y] - tc->p0[NR::Y]) > cursor_height) {
693 // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
694 SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
695 SP_DT_SELECTION(desktop)->set(ft);
696 ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
697 sp_document_done(SP_DT_DOCUMENT(desktop));
698 } else {
699 ec->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
700 }
701 }
702 tc->creating = false;
703 return TRUE;
704 }
705 break;
706 case GDK_KEY_PRESS: {
707 guint const group0_keyval = get_group0_keyval(&event->key);
709 if (group0_keyval == GDK_KP_Add ||
710 group0_keyval == GDK_KP_Subtract) {
711 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
712 break; // otherwise pass on keypad +/- so they can zoom
713 }
715 if ((tc->text) || (tc->nascent_object)) {
716 // there is an active text object in this context, or a new object was just created
718 if (tc->unimode || !tc->imc
719 || (MOD__CTRL && MOD__SHIFT) // input methods tend to steal this for unimode,
720 // but we have our own so make sure they don't swallow it
721 || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
722 //IM did not consume the key, or we're in unimode
724 if (!MOD__CTRL_ONLY && tc->unimode) {
725 /* TODO: ISO 14755 (section 3 Definitions) says that we should also
726 accept the first 6 characters of alphabets other than the latin
727 alphabet "if the Latin alphabet is not used". The below is also
728 reasonable (viz. hope that the user's keyboard includes latin
729 characters and force latin interpretation -- just as we do for our
730 keyboard shortcuts), but differs from the ISO 14755
731 recommendation. */
732 switch (group0_keyval) {
733 case GDK_space:
734 case GDK_KP_Space: {
735 if (tc->unipos) {
736 insert_uni_char(tc);
737 }
738 /* Stay in unimode. */
739 show_curr_uni_char(tc);
740 return TRUE;
741 }
743 case GDK_BackSpace: {
744 g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
745 if (tc->unipos) {
746 tc->uni[--tc->unipos] = '\0';
747 }
748 show_curr_uni_char(tc);
749 return TRUE;
750 }
752 case GDK_Return:
753 case GDK_KP_Enter: {
754 if (tc->unipos) {
755 insert_uni_char(tc);
756 }
757 /* Exit unimode. */
758 tc->unimode = false;
759 ec->defaultMessageContext()->clear();
760 return TRUE;
761 }
763 case GDK_Escape: {
764 // Cancel unimode.
765 tc->unimode = false;
766 gtk_im_context_reset(tc->imc);
767 ec->defaultMessageContext()->clear();
768 return TRUE;
769 }
771 case GDK_Shift_L:
772 case GDK_Shift_R:
773 break;
775 default: {
776 if (g_ascii_isxdigit(group0_keyval)) {
777 g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
778 tc->uni[tc->unipos++] = group0_keyval;
779 tc->uni[tc->unipos] = '\0';
780 if (tc->unipos == 8) {
781 /* This behaviour is partly to allow us to continue to
782 use a fixed-length buffer for tc->uni. Reason for
783 choosing the number 8 is that it's the length of
784 ``canonical form'' mentioned in the ISO 14755 spec.
785 An advantage over choosing 6 is that it allows using
786 backspace for typos & misremembering when entering a
787 6-digit number. */
788 insert_uni_char(tc);
789 }
790 show_curr_uni_char(tc);
791 return TRUE;
792 } else {
793 /* The intent is to ignore but consume characters that could be
794 typos for hex digits. Gtk seems to ignore & consume all
795 non-hex-digits, and we do similar here. Though note that some
796 shortcuts (like keypad +/- for zoom) get processed before
797 reaching this code. */
798 return TRUE;
799 }
800 }
801 }
802 }
804 bool (Inkscape::Text::Layout::iterator::*cursor_movement_operator)() = NULL;
806 /* Neither unimode nor IM consumed key; process text tool shortcuts */
807 switch (group0_keyval) {
808 case GDK_space:
809 if (MOD__CTRL_ONLY) {
810 /* No-break space */
811 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
812 sp_text_context_setup_text(tc);
813 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
814 }
815 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
816 sp_text_context_update_cursor(tc);
817 sp_text_context_update_text_selection(tc);
818 ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
819 sp_document_done(SP_DT_DOCUMENT(ec->desktop));
820 return TRUE;
821 }
822 break;
823 case GDK_U:
824 case GDK_u:
825 if (MOD__CTRL_ONLY) {
826 if (tc->unimode) {
827 tc->unimode = false;
828 ec->defaultMessageContext()->clear();
829 } else {
830 tc->unimode = true;
831 tc->unipos = 0;
832 ec->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode: "));
833 }
834 if (tc->imc) {
835 gtk_im_context_reset(tc->imc);
836 }
837 return TRUE;
838 }
839 break;
840 case GDK_B:
841 case GDK_b:
842 if (MOD__CTRL_ONLY && tc->text) {
843 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
844 SPCSSAttr *css = sp_repr_css_attr_new();
845 if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
846 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
847 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
848 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
849 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
850 sp_repr_css_set_property(css, "font-weight", "bold");
851 else
852 sp_repr_css_set_property(css, "font-weight", "normal");
853 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
854 sp_repr_css_attr_unref(css);
855 sp_document_done(SP_DT_DOCUMENT(ec->desktop));
856 sp_text_context_update_cursor(tc);
857 sp_text_context_update_text_selection(tc);
858 return TRUE;
859 }
860 break;
861 case GDK_I:
862 case GDK_i:
863 if (MOD__CTRL_ONLY && tc->text) {
864 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
865 SPCSSAttr *css = sp_repr_css_attr_new();
866 if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL)
867 sp_repr_css_set_property(css, "font-style", "italic");
868 else
869 sp_repr_css_set_property(css, "font-style", "normal");
870 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
871 sp_repr_css_attr_unref(css);
872 sp_document_done(SP_DT_DOCUMENT(ec->desktop));
873 sp_text_context_update_cursor(tc);
874 sp_text_context_update_text_selection(tc);
875 return TRUE;
876 }
877 break;
879 case GDK_A:
880 case GDK_a:
881 if (MOD__CTRL_ONLY && tc->text) {
882 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
883 if (layout) {
884 tc->text_sel_start = layout->begin();
885 tc->text_sel_end = layout->end();
886 sp_text_context_update_cursor(tc);
887 sp_text_context_update_text_selection(tc);
888 return TRUE;
889 }
890 }
891 break;
893 case GDK_Return:
894 case GDK_KP_Enter:
895 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
896 sp_text_context_setup_text(tc);
897 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
898 }
899 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
900 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
901 sp_text_context_update_cursor(tc);
902 sp_text_context_update_text_selection(tc);
903 sp_document_done(SP_DT_DOCUMENT(ec->desktop));
904 return TRUE;
905 case GDK_BackSpace:
906 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
907 if (tc->text_sel_start == tc->text_sel_end)
908 tc->text_sel_start.prevCursorPosition();
909 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
910 sp_text_context_update_cursor(tc);
911 sp_text_context_update_text_selection(tc);
912 sp_document_done(SP_DT_DOCUMENT(ec->desktop));
913 }
914 return TRUE;
915 case GDK_Delete:
916 case GDK_KP_Delete:
917 if (tc->text) {
918 if (tc->text_sel_start == tc->text_sel_end)
919 tc->text_sel_end.nextCursorPosition();
920 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
921 sp_text_context_update_cursor(tc);
922 sp_text_context_update_text_selection(tc);
923 sp_document_done(SP_DT_DOCUMENT(ec->desktop));
924 }
925 return TRUE;
926 case GDK_Left:
927 case GDK_KP_Left:
928 case GDK_KP_4:
929 if (tc->text) {
930 if (MOD__ALT) {
931 if (MOD__SHIFT)
932 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-10, 0));
933 else
934 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-1, 0));
935 sp_text_context_update_cursor(tc);
936 sp_text_context_update_text_selection(tc);
937 sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "kern:left");
938 } else {
939 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorLeftWithControl
940 : &Inkscape::Text::Layout::iterator::cursorLeft;
941 break;
942 }
943 }
944 return TRUE;
945 case GDK_Right:
946 case GDK_KP_Right:
947 case GDK_KP_6:
948 if (tc->text) {
949 if (MOD__ALT) {
950 if (MOD__SHIFT)
951 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(10, 0));
952 else
953 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(1, 0));
954 sp_text_context_update_cursor(tc);
955 sp_text_context_update_text_selection(tc);
956 sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "kern:right");
957 } else {
958 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorRightWithControl
959 : &Inkscape::Text::Layout::iterator::cursorRight;
960 break;
961 }
962 }
963 return TRUE;
964 case GDK_Up:
965 case GDK_KP_Up:
966 case GDK_KP_8:
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(0, -10));
971 else
972 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -1));
973 sp_text_context_update_cursor(tc);
974 sp_text_context_update_text_selection(tc);
975 sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "kern:up");
976 } else {
977 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorUpWithControl
978 : &Inkscape::Text::Layout::iterator::cursorUp;
979 break;
980 }
981 }
982 return TRUE;
983 case GDK_Down:
984 case GDK_KP_Down:
985 case GDK_KP_2:
986 if (tc->text) {
987 if (MOD__ALT) {
988 if (MOD__SHIFT)
989 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 10));
990 else
991 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 1));
992 sp_text_context_update_cursor(tc);
993 sp_text_context_update_text_selection(tc);
994 sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "kern:down");
995 } else {
996 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorDownWithControl
997 : &Inkscape::Text::Layout::iterator::cursorDown;
998 break;
999 }
1000 }
1001 return TRUE;
1002 case GDK_Home:
1003 case GDK_KP_Home:
1004 if (tc->text) {
1005 if (MOD__CTRL)
1006 cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfShape;
1007 else
1008 cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfLine;
1009 break;
1010 }
1011 return TRUE;
1012 case GDK_End:
1013 case GDK_KP_End:
1014 if (tc->text) {
1015 if (MOD__CTRL)
1016 cursor_movement_operator = &Inkscape::Text::Layout::iterator::nextStartOfShape;
1017 else
1018 cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisEndOfLine;
1019 break;
1020 }
1021 return TRUE;
1022 case GDK_Escape:
1023 if (tc->creating) {
1024 tc->creating = 0;
1025 if (tc->grabbed) {
1026 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1027 tc->grabbed = NULL;
1028 }
1029 Inkscape::Rubberband::get()->stop();
1030 } else {
1031 SP_DT_SELECTION(ec->desktop)->clear();
1032 }
1033 return TRUE;
1034 case GDK_bracketleft:
1035 if (tc->text) {
1036 if (MOD__ALT || MOD__CTRL) {
1037 if (MOD__ALT) {
1038 if (MOD__SHIFT) {
1039 // FIXME: alt+shift+[] does not work, don't know why
1040 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1041 } else {
1042 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1043 }
1044 } else {
1045 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -90);
1046 }
1047 sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "textrot:ccw");
1048 sp_text_context_update_cursor(tc);
1049 sp_text_context_update_text_selection(tc);
1050 return TRUE;
1051 }
1052 }
1053 break;
1054 case GDK_bracketright:
1055 if (tc->text) {
1056 if (MOD__ALT || MOD__CTRL) {
1057 if (MOD__ALT) {
1058 if (MOD__SHIFT) {
1059 // FIXME: alt+shift+[] does not work, don't know why
1060 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1061 } else {
1062 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1063 }
1064 } else {
1065 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 90);
1066 }
1067 sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "textrot:cw");
1068 sp_text_context_update_cursor(tc);
1069 sp_text_context_update_text_selection(tc);
1070 return TRUE;
1071 }
1072 }
1073 break;
1074 case GDK_less:
1075 case GDK_comma:
1076 if (tc->text) {
1077 if (MOD__ALT) {
1078 if (MOD__CTRL) {
1079 if (MOD__SHIFT)
1080 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1081 else
1082 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1083 sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "linespacing:dec");
1084 } else {
1085 if (MOD__SHIFT)
1086 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1087 else
1088 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1089 sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "letterspacing:dec");
1090 }
1091 sp_text_context_update_cursor(tc);
1092 sp_text_context_update_text_selection(tc);
1093 return TRUE;
1094 }
1095 }
1096 break;
1097 case GDK_greater:
1098 case GDK_period:
1099 if (tc->text) {
1100 if (MOD__ALT) {
1101 if (MOD__CTRL) {
1102 if (MOD__SHIFT)
1103 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1104 else
1105 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1106 sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "linespacing:inc");
1107 } else {
1108 if (MOD__SHIFT)
1109 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1110 else
1111 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1112 sp_document_maybe_done(SP_DT_DOCUMENT(ec->desktop), "letterspacing:inc");
1113 }
1114 sp_text_context_update_cursor(tc);
1115 sp_text_context_update_text_selection(tc);
1116 return TRUE;
1117 }
1118 }
1119 break;
1120 default:
1121 break;
1122 }
1124 if (cursor_movement_operator) {
1125 Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
1126 Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
1127 (tc->text_sel_end.*cursor_movement_operator)();
1128 if (!MOD__SHIFT)
1129 tc->text_sel_start = tc->text_sel_end;
1130 if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1131 sp_text_context_update_cursor(tc);
1132 sp_text_context_update_text_selection(tc);
1133 }
1134 return TRUE;
1135 }
1137 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1138 } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1139 // except up/down that are swallowed to prevent the zoom field from activation
1140 if ((group0_keyval == GDK_Up ||
1141 group0_keyval == GDK_Down ||
1142 group0_keyval == GDK_KP_Up ||
1143 group0_keyval == GDK_KP_Down )
1144 && !MOD__CTRL_ONLY) {
1145 return TRUE;
1146 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1147 if (tc->creating) {
1148 tc->creating = 0;
1149 if (tc->grabbed) {
1150 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1151 tc->grabbed = NULL;
1152 }
1153 Inkscape::Rubberband::get()->stop();
1154 }
1155 }
1156 }
1157 break;
1158 }
1160 case GDK_KEY_RELEASE:
1161 if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1162 return TRUE;
1163 }
1164 break;
1165 default:
1166 break;
1167 }
1169 // if nobody consumed it so far
1170 if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1171 return ((SPEventContextClass *) parent_class)->root_handler(ec, event); // send event to parent
1172 } else {
1173 return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1174 }
1175 }
1177 /**
1178 Attempts to paste system clipboard into the currently edited text, returns true on success
1179 */
1180 bool
1181 sp_text_paste_inline(SPEventContext *ec)
1182 {
1183 if (!SP_IS_TEXT_CONTEXT(ec))
1184 return false;
1186 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1188 if ((tc->text) || (tc->nascent_object)) {
1189 // there is an active text object in this context, or a new object was just created
1191 Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1192 Glib::ustring const text = refClipboard->wait_for_text();
1194 if (!text.empty()) {
1196 if (!tc->text) { // create text if none (i.e. if nascent_object)
1197 sp_text_context_setup_text(tc);
1198 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1199 }
1201 // using indices is slow in ustrings. Whatever.
1202 Glib::ustring::size_type begin = 0;
1203 for ( ; ; ) {
1204 Glib::ustring::size_type end = text.find('\n', begin);
1205 if (end == Glib::ustring::npos) {
1206 if (begin != text.length())
1207 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());
1208 break;
1209 }
1210 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());
1211 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1212 begin = end + 1;
1213 }
1214 sp_document_done(SP_DT_DOCUMENT(ec->desktop));
1216 return true;
1217 }
1218 } // FIXME: else create and select a new object under cursor!
1220 return false;
1221 }
1223 /**
1224 Gets the raw characters that comprise the currently selected text, converting line
1225 breaks into lf characters.
1226 */
1227 Glib::ustring
1228 sp_text_get_selected_text(SPEventContext const *ec)
1229 {
1230 if (!SP_IS_TEXT_CONTEXT(ec))
1231 return "";
1232 SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1233 if (tc->text == NULL)
1234 return "";
1236 return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1237 }
1239 /**
1240 Deletes the currently selected characters. Returns false if there is no
1241 text selection currently.
1242 */
1243 bool sp_text_delete_selection(SPEventContext *ec)
1244 {
1245 if (!SP_IS_TEXT_CONTEXT(ec))
1246 return false;
1247 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1248 if (tc->text == NULL)
1249 return false;
1251 if (tc->text_sel_start == tc->text_sel_end)
1252 return false;
1253 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
1254 sp_text_context_update_cursor(tc);
1255 sp_text_context_update_text_selection(tc);
1256 return true;
1257 }
1259 /**
1260 * \param selection Should not be NULL.
1261 */
1262 static void
1263 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1264 {
1265 g_assert(selection != NULL);
1267 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1269 if (ec->shape_knot_holder) { // destroy knotholder
1270 sp_knot_holder_destroy(ec->shape_knot_holder);
1271 ec->shape_knot_holder = NULL;
1272 }
1274 if (ec->shape_repr) { // remove old listener
1275 sp_repr_remove_listener_by_data(ec->shape_repr, ec);
1276 Inkscape::GC::release(ec->shape_repr);
1277 ec->shape_repr = 0;
1278 }
1280 SPItem *item = selection->singleItem();
1281 if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1282 ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
1283 Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
1284 if (shape_repr) {
1285 ec->shape_repr = shape_repr;
1286 Inkscape::GC::anchor(shape_repr);
1287 sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
1288 sp_repr_synthesize_events(shape_repr, &ec_shape_repr_events, ec);
1289 }
1290 }
1292 if (tc->text && (item != tc->text)) {
1293 sp_text_context_forget_text(tc);
1294 }
1295 tc->text = NULL;
1297 if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1298 tc->text = item;
1299 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1300 if (layout)
1301 tc->text_sel_start = tc->text_sel_end = layout->end();
1302 } else {
1303 tc->text = NULL;
1304 }
1306 // we update cursor without scrolling, because this position may not be final;
1307 // item_handler moves cusros to the point of click immediately
1308 sp_text_context_update_cursor(tc, false);
1309 sp_text_context_update_text_selection(tc);
1310 }
1312 static void
1313 sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc)
1314 {
1315 sp_text_context_update_cursor(tc);
1316 sp_text_context_update_text_selection(tc);
1317 }
1319 static bool
1320 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1321 {
1322 if (tc->text == NULL)
1323 return false;
1324 if (tc->text_sel_start == tc->text_sel_end)
1325 return false; // will get picked up by the parent and applied to the whole text object
1327 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1328 sp_document_done(SP_DT_DOCUMENT(tc->desktop));
1329 sp_text_context_update_cursor(tc);
1330 sp_text_context_update_text_selection(tc);
1332 return true;
1333 }
1335 static int
1336 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1337 {
1338 if (tc->text == NULL)
1339 return QUERY_STYLE_NOTHING;
1340 const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1341 if (layout == NULL)
1342 return QUERY_STYLE_NOTHING;
1343 sp_text_context_validate_cursor_iterators(tc);
1345 GSList *styles_list = NULL;
1347 Inkscape::Text::Layout::iterator begin_it, end_it;
1348 if (tc->text_sel_start < tc->text_sel_end) {
1349 begin_it = tc->text_sel_start;
1350 end_it = tc->text_sel_end;
1351 } else {
1352 begin_it = tc->text_sel_end;
1353 end_it = tc->text_sel_start;
1354 }
1355 if (begin_it == end_it)
1356 if (!begin_it.prevCharacter())
1357 end_it.nextCharacter();
1358 for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1359 SPObject const *pos_obj = NULL;
1360 layout->getSourceOfCharacter(it, (void**)&pos_obj);
1361 if (pos_obj == NULL) continue;
1362 while (SP_OBJECT_STYLE(pos_obj) == NULL && SP_OBJECT_PARENT(pos_obj))
1363 pos_obj = SP_OBJECT_PARENT(pos_obj); // SPStrings don't have style
1364 styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1365 }
1367 int result = sp_desktop_query_style_from_list (styles_list, style, property);
1369 g_slist_free(styles_list);
1370 return result;
1371 }
1373 static void
1374 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1375 {
1376 if (tc->text == NULL)
1377 return;
1378 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1379 if (layout) { // undo can change the text length without us knowing it
1380 layout->validateIterator(&tc->text_sel_start);
1381 layout->validateIterator(&tc->text_sel_end);
1382 }
1383 }
1385 static void
1386 sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see)
1387 {
1388 GdkRectangle im_cursor = { 0, 0, 1, 1 };
1390 if (tc->text) {
1391 NR::Point p0, p1;
1392 sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1393 NR::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1394 NR::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1396 // scroll to show cursor
1397 if (scroll_to_see) {
1398 NR::Point const dm = (d0 + d1) / 2;
1399 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1400 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(&dm, 1.0);
1401 }
1403 sp_canvas_item_show(tc->cursor);
1404 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1406 /* fixme: ... need another transformation to get canvas widget coordinate space? */
1407 im_cursor.x = (int) floor(d0[NR::X]);
1408 im_cursor.y = (int) floor(d0[NR::Y]);
1409 im_cursor.width = (int) floor(d1[NR::X]) - im_cursor.x;
1410 im_cursor.height = (int) floor(d1[NR::Y]) - im_cursor.y;
1412 tc->show = TRUE;
1413 tc->phase = 1;
1415 if (SP_IS_FLOWTEXT(tc->text)) {
1416 SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1417 if (frame) {
1418 sp_canvas_item_show(tc->frame);
1419 SP_CTRLRECT(tc->frame)->setRectangle(sp_item_bbox_desktop(frame));
1420 }
1421 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1422 } else {
1423 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1424 }
1426 } else {
1427 sp_canvas_item_hide(tc->cursor);
1428 sp_canvas_item_hide(tc->frame);
1429 tc->show = FALSE;
1430 if (!tc->nascent_object) {
1431 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
1432 }
1433 }
1435 if (tc->imc) {
1436 gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1437 }
1438 SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1439 }
1441 static void sp_text_context_update_text_selection(SPTextContext *tc)
1442 {
1443 for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1444 sp_canvas_item_hide(*it);
1445 gtk_object_destroy(*it);
1446 }
1447 tc->text_selection_quads.clear();
1449 std::vector<NR::Point> quads;
1450 if (tc->text != NULL)
1451 quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1452 for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1453 SPCanvasItem *quad_canvasitem;
1454 quad_canvasitem = sp_canvas_item_new(SP_DT_CONTROLS(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1455 sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x000000ff);
1456 sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1457 sp_canvas_item_show(quad_canvasitem);
1458 tc->text_selection_quads.push_back(quad_canvasitem);
1459 }
1460 }
1462 static gint
1463 sp_text_context_timeout(SPTextContext *tc)
1464 {
1465 if (tc->show) {
1466 if (tc->phase) {
1467 tc->phase = 0;
1468 sp_canvas_item_hide(tc->cursor);
1469 } else {
1470 tc->phase = 1;
1471 sp_canvas_item_show(tc->cursor);
1472 }
1473 }
1475 return TRUE;
1476 }
1478 static void
1479 sp_text_context_forget_text(SPTextContext *tc)
1480 {
1481 if (! tc->text) return;
1482 SPItem *ti = tc->text;
1483 /* We have to set it to zero,
1484 * or selection changed signal messes everything up */
1485 tc->text = NULL;
1486 if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1487 Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1488 // the repr may already have been unparented
1489 // if we were called e.g. as the result of
1490 // an undo or the element being removed from
1491 // the XML editor
1492 if ( text_repr && sp_repr_parent(text_repr) ) {
1493 sp_repr_unparent(text_repr);
1494 }
1495 }
1496 }
1498 gint
1499 sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1500 {
1501 gtk_im_context_focus_in(tc->imc);
1502 return FALSE;
1503 }
1505 gint
1506 sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1507 {
1508 gtk_im_context_focus_out(tc->imc);
1509 return FALSE;
1510 }
1512 static void
1513 sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc)
1514 {
1515 if (!tc->text) {
1516 sp_text_context_setup_text(tc);
1517 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1518 }
1520 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1521 sp_text_context_update_cursor(tc);
1522 sp_text_context_update_text_selection(tc);
1524 sp_document_done(SP_OBJECT_DOCUMENT(tc->text));
1525 }
1528 /*
1529 Local Variables:
1530 mode:c++
1531 c-file-style:"stroustrup"
1532 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1533 indent-tabs-mode:nil
1534 fill-column:99
1535 End:
1536 */
1537 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :