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_desktop_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_desktop_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_desktop_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_desktop_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_desktop_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 }
262 }
264 tc->sel_changed_connection = sp_desktop_selection(desktop)->connectChanged(
265 sigc::bind(sigc::ptr_fun(&sp_text_context_selection_changed), tc)
266 );
267 tc->sel_modified_connection = sp_desktop_selection(desktop)->connectModified(
268 sigc::bind(sigc::ptr_fun(&sp_text_context_selection_modified), tc)
269 );
270 tc->style_set_connection = desktop->connectSetStyle(
271 sigc::bind(sigc::ptr_fun(&sp_text_context_style_set), tc)
272 );
273 tc->style_query_connection = desktop->connectQueryStyle(
274 sigc::bind(sigc::ptr_fun(&sp_text_context_style_query), tc)
275 );
277 sp_text_context_selection_changed(sp_desktop_selection(desktop), tc);
279 if (prefs_get_int_attribute("tools.text", "selcue", 0) != 0) {
280 ec->enableSelectionCue();
281 }
282 if (prefs_get_int_attribute("tools.text", "gradientdrag", 0) != 0) {
283 ec->enableGrDrag();
284 }
285 }
287 static void
288 sp_text_context_finish(SPEventContext *ec)
289 {
290 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
292 ec->enableGrDrag(false);
294 tc->style_set_connection.disconnect();
295 tc->style_query_connection.disconnect();
296 tc->sel_changed_connection.disconnect();
297 tc->sel_modified_connection.disconnect();
299 sp_text_context_forget_text(SP_TEXT_CONTEXT(ec));
301 if (tc->imc) {
302 g_object_unref(G_OBJECT(tc->imc));
303 tc->imc = NULL;
304 }
306 if (tc->timeout) {
307 gtk_timeout_remove(tc->timeout);
308 tc->timeout = 0;
309 }
311 if (tc->cursor) {
312 gtk_object_destroy(GTK_OBJECT(tc->cursor));
313 tc->cursor = NULL;
314 }
316 if (tc->indicator) {
317 gtk_object_destroy(GTK_OBJECT(tc->indicator));
318 tc->indicator = NULL;
319 }
321 if (tc->frame) {
322 gtk_object_destroy(GTK_OBJECT(tc->frame));
323 tc->frame = NULL;
324 }
326 for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ;
327 it != tc->text_selection_quads.end() ; ++it) {
328 sp_canvas_item_hide(*it);
329 gtk_object_destroy(*it);
330 }
331 tc->text_selection_quads.clear();
333 if (ec->desktop) {
334 sp_signal_disconnect_by_data(sp_desktop_canvas(ec->desktop), tc);
335 }
336 }
339 static gint
340 sp_text_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event)
341 {
342 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
343 SPDesktop *desktop = ec->desktop;
344 SPItem *item_ungrouped;
346 gint ret = FALSE;
348 sp_text_context_validate_cursor_iterators(tc);
350 switch (event->type) {
351 case GDK_BUTTON_PRESS:
352 if (event->button.button == 1) {
353 // find out clicked item, disregarding groups
354 item_ungrouped = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE);
355 if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
356 sp_desktop_selection(ec->desktop)->set(item_ungrouped);
357 if (tc->text) {
358 // find out click point in document coordinates
359 NR::Point p = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
360 // set the cursor closest to that point
361 tc->text_sel_start = tc->text_sel_end = sp_te_get_position_by_coords(tc->text, p);
362 // update display
363 sp_text_context_update_cursor(tc);
364 sp_text_context_update_text_selection(tc);
365 tc->dragging = 1;
366 }
367 ret = TRUE;
368 }
369 }
370 break;
371 case GDK_2BUTTON_PRESS:
372 if (event->button.button == 1 && tc->text) {
373 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
374 if (layout) {
375 if (!layout->isStartOfWord(tc->text_sel_start))
376 tc->text_sel_start.prevStartOfWord();
377 if (!layout->isEndOfWord(tc->text_sel_end))
378 tc->text_sel_end.nextEndOfWord();
379 sp_text_context_update_cursor(tc);
380 sp_text_context_update_text_selection(tc);
381 tc->dragging = 2;
382 ret = TRUE;
383 }
384 }
385 break;
386 case GDK_3BUTTON_PRESS:
387 if (event->button.button == 1 && tc->text) {
388 tc->text_sel_start.thisStartOfLine();
389 tc->text_sel_end.thisEndOfLine();
390 sp_text_context_update_cursor(tc);
391 sp_text_context_update_text_selection(tc);
392 tc->dragging = 3;
393 ret = TRUE;
394 }
395 break;
396 case GDK_BUTTON_RELEASE:
397 if (event->button.button == 1 && tc->dragging) {
398 tc->dragging = 0;
399 ret = TRUE;
400 }
401 break;
402 case GDK_MOTION_NOTIFY:
403 if (event->motion.state & GDK_BUTTON1_MASK && tc->dragging) {
404 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
405 if (!layout) break;
406 // find out click point in document coordinates
407 NR::Point p = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
408 // set the cursor closest to that point
409 Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(tc->text, p);
410 if (tc->dragging == 2) {
411 // double-click dragging: go by word
412 if (new_end < tc->text_sel_start) {
413 if (!layout->isStartOfWord(new_end))
414 new_end.prevStartOfWord();
415 } else
416 if (!layout->isEndOfWord(new_end))
417 new_end.nextEndOfWord();
418 } else if (tc->dragging == 3) {
419 // triple-click dragging: go by line
420 if (new_end < tc->text_sel_start)
421 new_end.thisStartOfLine();
422 else
423 new_end.thisEndOfLine();
424 }
425 // update display
426 if (tc->text_sel_end != new_end) {
427 tc->text_sel_end = new_end;
428 sp_text_context_update_cursor(tc);
429 sp_text_context_update_text_selection(tc);
430 }
431 ret = TRUE;
432 break;
433 }
434 // find out item under mouse, disregarding groups
435 item_ungrouped = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE);
436 if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
437 sp_canvas_item_show(tc->indicator);
438 SP_CTRLRECT(tc->indicator)->setRectangle(sp_item_bbox_desktop(item_ungrouped));
440 ec->cursor_shape = cursor_text_insert_xpm;
441 ec->hot_x = 7;
442 ec->hot_y = 10;
443 sp_event_context_update_cursor(ec);
444 sp_text_context_update_text_selection(tc);
446 if (SP_IS_TEXT (item_ungrouped)) {
447 desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
448 } else {
449 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."));
450 }
452 tc->over_text = true;
454 ret = TRUE;
455 }
456 break;
457 default:
458 break;
459 }
461 if (!ret) {
462 if (((SPEventContextClass *) parent_class)->item_handler)
463 ret = ((SPEventContextClass *) parent_class)->item_handler(ec, item, event);
464 }
466 return ret;
467 }
469 static void
470 sp_text_context_setup_text(SPTextContext *tc)
471 {
472 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
474 /* Create <text> */
475 Inkscape::XML::Node *rtext = sp_repr_new("svg:text");
476 rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
478 /* Set style */
479 sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "tools.text", true);
481 sp_repr_set_svg_double(rtext, "x", tc->pdoc[NR::X]);
482 sp_repr_set_svg_double(rtext, "y", tc->pdoc[NR::Y]);
484 /* Create <tspan> */
485 Inkscape::XML::Node *rtspan = sp_repr_new("svg:tspan");
486 rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
487 rtext->addChild(rtspan, NULL);
488 Inkscape::GC::release(rtspan);
490 /* Create TEXT */
491 Inkscape::XML::Node *rstring = sp_repr_new_text("");
492 rtspan->addChild(rstring, NULL);
493 Inkscape::GC::release(rstring);
494 SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
495 /* fixme: Is selection::changed really immediate? */
496 /* yes, it's immediate .. why does it matter? */
497 sp_desktop_selection(ec->desktop)->set(text_item);
498 Inkscape::GC::release(rtext);
499 text_item->transform = SP_ITEM(ec->desktop->currentRoot())->getRelativeTransform(ec->desktop->currentLayer());
500 text_item->updateRepr();
501 sp_document_done(sp_desktop_document(ec->desktop));
502 }
504 /**
505 * Insert the character indicated by tc.uni to replace the current selection,
506 * and reset tc.uni/tc.unipos to empty string.
507 *
508 * \pre tc.uni/tc.unipos non-empty.
509 */
510 static void
511 insert_uni_char(SPTextContext *const tc)
512 {
513 g_return_if_fail(tc->unipos
514 && tc->unipos < sizeof(tc->uni)
515 && tc->uni[tc->unipos] == '\0');
516 unsigned int uv;
517 sscanf(tc->uni, "%x", &uv);
518 tc->unipos = 0;
519 tc->uni[tc->unipos] = '\0';
521 if ( !g_unichar_isprint(static_cast<gunichar>(uv))
522 && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
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_desktop_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,
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_desktop_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_desktop_selection(desktop)->set(ft);
696 ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
697 sp_document_done(sp_desktop_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_x:
809 case GDK_X:
810 if (MOD__ALT_ONLY) {
811 desktop->setToolboxFocusTo ("altx-text");
812 return TRUE;
813 }
814 break;
815 case GDK_space:
816 if (MOD__CTRL_ONLY) {
817 /* No-break space */
818 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
819 sp_text_context_setup_text(tc);
820 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
821 }
822 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
823 sp_text_context_update_cursor(tc);
824 sp_text_context_update_text_selection(tc);
825 ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
826 sp_document_done(sp_desktop_document(ec->desktop));
827 return TRUE;
828 }
829 break;
830 case GDK_U:
831 case GDK_u:
832 if (MOD__CTRL_ONLY) {
833 if (tc->unimode) {
834 tc->unimode = false;
835 ec->defaultMessageContext()->clear();
836 } else {
837 tc->unimode = true;
838 tc->unipos = 0;
839 ec->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode: "));
840 }
841 if (tc->imc) {
842 gtk_im_context_reset(tc->imc);
843 }
844 return TRUE;
845 }
846 break;
847 case GDK_B:
848 case GDK_b:
849 if (MOD__CTRL_ONLY && tc->text) {
850 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
851 SPCSSAttr *css = sp_repr_css_attr_new();
852 if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
853 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
854 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
855 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
856 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
857 sp_repr_css_set_property(css, "font-weight", "bold");
858 else
859 sp_repr_css_set_property(css, "font-weight", "normal");
860 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
861 sp_repr_css_attr_unref(css);
862 sp_document_done(sp_desktop_document(ec->desktop));
863 sp_text_context_update_cursor(tc);
864 sp_text_context_update_text_selection(tc);
865 return TRUE;
866 }
867 break;
868 case GDK_I:
869 case GDK_i:
870 if (MOD__CTRL_ONLY && tc->text) {
871 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
872 SPCSSAttr *css = sp_repr_css_attr_new();
873 if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL)
874 sp_repr_css_set_property(css, "font-style", "italic");
875 else
876 sp_repr_css_set_property(css, "font-style", "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;
886 case GDK_A:
887 case GDK_a:
888 if (MOD__CTRL_ONLY && tc->text) {
889 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
890 if (layout) {
891 tc->text_sel_start = layout->begin();
892 tc->text_sel_end = layout->end();
893 sp_text_context_update_cursor(tc);
894 sp_text_context_update_text_selection(tc);
895 return TRUE;
896 }
897 }
898 break;
900 case GDK_Return:
901 case GDK_KP_Enter:
902 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
903 sp_text_context_setup_text(tc);
904 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
905 }
906 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
907 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
908 sp_text_context_update_cursor(tc);
909 sp_text_context_update_text_selection(tc);
910 sp_document_done(sp_desktop_document(ec->desktop));
911 return TRUE;
912 case GDK_BackSpace:
913 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
914 if (tc->text_sel_start == tc->text_sel_end)
915 tc->text_sel_start.prevCursorPosition();
916 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
917 sp_text_context_update_cursor(tc);
918 sp_text_context_update_text_selection(tc);
919 sp_document_done(sp_desktop_document(ec->desktop));
920 }
921 return TRUE;
922 case GDK_Delete:
923 case GDK_KP_Delete:
924 if (tc->text) {
925 if (tc->text_sel_start == tc->text_sel_end)
926 tc->text_sel_end.nextCursorPosition();
927 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
928 sp_text_context_update_cursor(tc);
929 sp_text_context_update_text_selection(tc);
930 sp_document_done(sp_desktop_document(ec->desktop));
931 }
932 return TRUE;
933 case GDK_Left:
934 case GDK_KP_Left:
935 case GDK_KP_4:
936 if (tc->text) {
937 if (MOD__ALT) {
938 if (MOD__SHIFT)
939 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-10, 0));
940 else
941 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-1, 0));
942 sp_text_context_update_cursor(tc);
943 sp_text_context_update_text_selection(tc);
944 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:left");
945 } else {
946 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorLeftWithControl
947 : &Inkscape::Text::Layout::iterator::cursorLeft;
948 break;
949 }
950 }
951 return TRUE;
952 case GDK_Right:
953 case GDK_KP_Right:
954 case GDK_KP_6:
955 if (tc->text) {
956 if (MOD__ALT) {
957 if (MOD__SHIFT)
958 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(10, 0));
959 else
960 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(1, 0));
961 sp_text_context_update_cursor(tc);
962 sp_text_context_update_text_selection(tc);
963 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:right");
964 } else {
965 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorRightWithControl
966 : &Inkscape::Text::Layout::iterator::cursorRight;
967 break;
968 }
969 }
970 return TRUE;
971 case GDK_Up:
972 case GDK_KP_Up:
973 case GDK_KP_8:
974 if (tc->text) {
975 if (MOD__ALT) {
976 if (MOD__SHIFT)
977 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -10));
978 else
979 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -1));
980 sp_text_context_update_cursor(tc);
981 sp_text_context_update_text_selection(tc);
982 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:up");
983 } else {
984 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorUpWithControl
985 : &Inkscape::Text::Layout::iterator::cursorUp;
986 break;
987 }
988 }
989 return TRUE;
990 case GDK_Down:
991 case GDK_KP_Down:
992 case GDK_KP_2:
993 if (tc->text) {
994 if (MOD__ALT) {
995 if (MOD__SHIFT)
996 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 10));
997 else
998 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 1));
999 sp_text_context_update_cursor(tc);
1000 sp_text_context_update_text_selection(tc);
1001 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:down");
1002 } else {
1003 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorDownWithControl
1004 : &Inkscape::Text::Layout::iterator::cursorDown;
1005 break;
1006 }
1007 }
1008 return TRUE;
1009 case GDK_Home:
1010 case GDK_KP_Home:
1011 if (tc->text) {
1012 if (MOD__CTRL)
1013 cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfShape;
1014 else
1015 cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfLine;
1016 break;
1017 }
1018 return TRUE;
1019 case GDK_End:
1020 case GDK_KP_End:
1021 if (tc->text) {
1022 if (MOD__CTRL)
1023 cursor_movement_operator = &Inkscape::Text::Layout::iterator::nextStartOfShape;
1024 else
1025 cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisEndOfLine;
1026 break;
1027 }
1028 return TRUE;
1029 case GDK_Escape:
1030 if (tc->creating) {
1031 tc->creating = 0;
1032 if (tc->grabbed) {
1033 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1034 tc->grabbed = NULL;
1035 }
1036 Inkscape::Rubberband::get()->stop();
1037 } else {
1038 sp_desktop_selection(ec->desktop)->clear();
1039 }
1040 tc->nascent_object = FALSE;
1041 return TRUE;
1042 case GDK_bracketleft:
1043 if (tc->text) {
1044 if (MOD__ALT || MOD__CTRL) {
1045 if (MOD__ALT) {
1046 if (MOD__SHIFT) {
1047 // FIXME: alt+shift+[] does not work, don't know why
1048 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1049 } else {
1050 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1051 }
1052 } else {
1053 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -90);
1054 }
1055 sp_document_maybe_done(sp_desktop_document(ec->desktop), "textrot:ccw");
1056 sp_text_context_update_cursor(tc);
1057 sp_text_context_update_text_selection(tc);
1058 return TRUE;
1059 }
1060 }
1061 break;
1062 case GDK_bracketright:
1063 if (tc->text) {
1064 if (MOD__ALT || MOD__CTRL) {
1065 if (MOD__ALT) {
1066 if (MOD__SHIFT) {
1067 // FIXME: alt+shift+[] does not work, don't know why
1068 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1069 } else {
1070 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1071 }
1072 } else {
1073 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 90);
1074 }
1075 sp_document_maybe_done(sp_desktop_document(ec->desktop), "textrot:cw");
1076 sp_text_context_update_cursor(tc);
1077 sp_text_context_update_text_selection(tc);
1078 return TRUE;
1079 }
1080 }
1081 break;
1082 case GDK_less:
1083 case GDK_comma:
1084 if (tc->text) {
1085 if (MOD__ALT) {
1086 if (MOD__CTRL) {
1087 if (MOD__SHIFT)
1088 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1089 else
1090 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1091 sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:dec");
1092 } else {
1093 if (MOD__SHIFT)
1094 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1095 else
1096 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1097 sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:dec");
1098 }
1099 sp_text_context_update_cursor(tc);
1100 sp_text_context_update_text_selection(tc);
1101 return TRUE;
1102 }
1103 }
1104 break;
1105 case GDK_greater:
1106 case GDK_period:
1107 if (tc->text) {
1108 if (MOD__ALT) {
1109 if (MOD__CTRL) {
1110 if (MOD__SHIFT)
1111 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1112 else
1113 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1114 sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:inc");
1115 } else {
1116 if (MOD__SHIFT)
1117 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1118 else
1119 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1120 sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:inc");
1121 }
1122 sp_text_context_update_cursor(tc);
1123 sp_text_context_update_text_selection(tc);
1124 return TRUE;
1125 }
1126 }
1127 break;
1128 default:
1129 break;
1130 }
1132 if (cursor_movement_operator) {
1133 Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
1134 Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
1135 (tc->text_sel_end.*cursor_movement_operator)();
1136 if (!MOD__SHIFT)
1137 tc->text_sel_start = tc->text_sel_end;
1138 if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1139 sp_text_context_update_cursor(tc);
1140 sp_text_context_update_text_selection(tc);
1141 }
1142 return TRUE;
1143 }
1145 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1146 } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1147 // except up/down that are swallowed to prevent the zoom field from activation
1148 if ((group0_keyval == GDK_Up ||
1149 group0_keyval == GDK_Down ||
1150 group0_keyval == GDK_KP_Up ||
1151 group0_keyval == GDK_KP_Down )
1152 && !MOD__CTRL_ONLY) {
1153 return TRUE;
1154 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1155 if (tc->creating) {
1156 tc->creating = 0;
1157 if (tc->grabbed) {
1158 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1159 tc->grabbed = NULL;
1160 }
1161 Inkscape::Rubberband::get()->stop();
1162 }
1163 }
1164 }
1165 break;
1166 }
1168 case GDK_KEY_RELEASE:
1169 if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1170 return TRUE;
1171 }
1172 break;
1173 default:
1174 break;
1175 }
1177 // if nobody consumed it so far
1178 if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1179 return ((SPEventContextClass *) parent_class)->root_handler(ec, event); // send event to parent
1180 } else {
1181 return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1182 }
1183 }
1185 /**
1186 Attempts to paste system clipboard into the currently edited text, returns true on success
1187 */
1188 bool
1189 sp_text_paste_inline(SPEventContext *ec)
1190 {
1191 if (!SP_IS_TEXT_CONTEXT(ec))
1192 return false;
1194 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1196 if ((tc->text) || (tc->nascent_object)) {
1197 // there is an active text object in this context, or a new object was just created
1199 Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1200 Glib::ustring const text = refClipboard->wait_for_text();
1202 if (!text.empty()) {
1204 if (!tc->text) { // create text if none (i.e. if nascent_object)
1205 sp_text_context_setup_text(tc);
1206 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1207 }
1209 // using indices is slow in ustrings. Whatever.
1210 Glib::ustring::size_type begin = 0;
1211 for ( ; ; ) {
1212 Glib::ustring::size_type end = text.find('\n', begin);
1213 if (end == Glib::ustring::npos) {
1214 if (begin != text.length())
1215 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());
1216 break;
1217 }
1218 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());
1219 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1220 begin = end + 1;
1221 }
1222 sp_document_done(sp_desktop_document(ec->desktop));
1224 return true;
1225 }
1226 } // FIXME: else create and select a new object under cursor!
1228 return false;
1229 }
1231 /**
1232 Gets the raw characters that comprise the currently selected text, converting line
1233 breaks into lf characters.
1234 */
1235 Glib::ustring
1236 sp_text_get_selected_text(SPEventContext const *ec)
1237 {
1238 if (!SP_IS_TEXT_CONTEXT(ec))
1239 return "";
1240 SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1241 if (tc->text == NULL)
1242 return "";
1244 return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1245 }
1247 /**
1248 Deletes the currently selected characters. Returns false if there is no
1249 text selection currently.
1250 */
1251 bool sp_text_delete_selection(SPEventContext *ec)
1252 {
1253 if (!SP_IS_TEXT_CONTEXT(ec))
1254 return false;
1255 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1256 if (tc->text == NULL)
1257 return false;
1259 if (tc->text_sel_start == tc->text_sel_end)
1260 return false;
1261 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
1262 sp_text_context_update_cursor(tc);
1263 sp_text_context_update_text_selection(tc);
1264 return true;
1265 }
1267 /**
1268 * \param selection Should not be NULL.
1269 */
1270 static void
1271 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1272 {
1273 g_assert(selection != NULL);
1275 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1277 if (ec->shape_knot_holder) { // destroy knotholder
1278 sp_knot_holder_destroy(ec->shape_knot_holder);
1279 ec->shape_knot_holder = NULL;
1280 }
1282 if (ec->shape_repr) { // remove old listener
1283 sp_repr_remove_listener_by_data(ec->shape_repr, ec);
1284 Inkscape::GC::release(ec->shape_repr);
1285 ec->shape_repr = 0;
1286 }
1288 SPItem *item = selection->singleItem();
1289 if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1290 ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
1291 Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
1292 if (shape_repr) {
1293 ec->shape_repr = shape_repr;
1294 Inkscape::GC::anchor(shape_repr);
1295 sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
1296 }
1297 }
1299 if (tc->text && (item != tc->text)) {
1300 sp_text_context_forget_text(tc);
1301 }
1302 tc->text = NULL;
1304 if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1305 tc->text = item;
1306 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1307 if (layout)
1308 tc->text_sel_start = tc->text_sel_end = layout->end();
1309 } else {
1310 tc->text = NULL;
1311 }
1313 // we update cursor without scrolling, because this position may not be final;
1314 // item_handler moves cusros to the point of click immediately
1315 sp_text_context_update_cursor(tc, false);
1316 sp_text_context_update_text_selection(tc);
1317 }
1319 static void
1320 sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc)
1321 {
1322 sp_text_context_update_cursor(tc);
1323 sp_text_context_update_text_selection(tc);
1324 }
1326 static bool
1327 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1328 {
1329 if (tc->text == NULL)
1330 return false;
1331 if (tc->text_sel_start == tc->text_sel_end)
1332 return false; // will get picked up by the parent and applied to the whole text object
1334 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1335 sp_document_done(sp_desktop_document(tc->desktop));
1336 sp_text_context_update_cursor(tc);
1337 sp_text_context_update_text_selection(tc);
1339 return true;
1340 }
1342 static int
1343 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1344 {
1345 if (tc->text == NULL)
1346 return QUERY_STYLE_NOTHING;
1347 const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1348 if (layout == NULL)
1349 return QUERY_STYLE_NOTHING;
1350 sp_text_context_validate_cursor_iterators(tc);
1352 GSList *styles_list = NULL;
1354 Inkscape::Text::Layout::iterator begin_it, end_it;
1355 if (tc->text_sel_start < tc->text_sel_end) {
1356 begin_it = tc->text_sel_start;
1357 end_it = tc->text_sel_end;
1358 } else {
1359 begin_it = tc->text_sel_end;
1360 end_it = tc->text_sel_start;
1361 }
1362 if (begin_it == end_it)
1363 if (!begin_it.prevCharacter())
1364 end_it.nextCharacter();
1365 for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1366 SPObject const *pos_obj = 0;
1367 void *rawptr = 0;
1368 layout->getSourceOfCharacter(it, &rawptr);
1369 pos_obj = SP_OBJECT(rawptr);
1370 if (pos_obj == 0) continue;
1371 while (SP_OBJECT_STYLE(pos_obj) == NULL && SP_OBJECT_PARENT(pos_obj))
1372 pos_obj = SP_OBJECT_PARENT(pos_obj); // SPStrings don't have style
1373 styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1374 }
1376 int result = sp_desktop_query_style_from_list (styles_list, style, property);
1378 g_slist_free(styles_list);
1379 return result;
1380 }
1382 static void
1383 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1384 {
1385 if (tc->text == NULL)
1386 return;
1387 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1388 if (layout) { // undo can change the text length without us knowing it
1389 layout->validateIterator(&tc->text_sel_start);
1390 layout->validateIterator(&tc->text_sel_end);
1391 }
1392 }
1394 static void
1395 sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see)
1396 {
1397 GdkRectangle im_cursor = { 0, 0, 1, 1 };
1399 if (tc->text) {
1400 NR::Point p0, p1;
1401 sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1402 NR::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1403 NR::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1405 // scroll to show cursor
1406 if (scroll_to_see) {
1407 NR::Point const dm = (d0 + d1) / 2;
1408 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1409 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(&dm, 1.0);
1410 }
1412 sp_canvas_item_show(tc->cursor);
1413 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1415 /* fixme: ... need another transformation to get canvas widget coordinate space? */
1416 im_cursor.x = (int) floor(d0[NR::X]);
1417 im_cursor.y = (int) floor(d0[NR::Y]);
1418 im_cursor.width = (int) floor(d1[NR::X]) - im_cursor.x;
1419 im_cursor.height = (int) floor(d1[NR::Y]) - im_cursor.y;
1421 tc->show = TRUE;
1422 tc->phase = 1;
1424 if (SP_IS_FLOWTEXT(tc->text)) {
1425 SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1426 if (frame) {
1427 sp_canvas_item_show(tc->frame);
1428 SP_CTRLRECT(tc->frame)->setRectangle(sp_item_bbox_desktop(frame));
1429 }
1430 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1431 } else {
1432 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1433 }
1435 } else {
1436 sp_canvas_item_hide(tc->cursor);
1437 sp_canvas_item_hide(tc->frame);
1438 tc->show = FALSE;
1439 if (!tc->nascent_object) {
1440 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
1441 }
1442 }
1444 if (tc->imc) {
1445 gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1446 }
1447 SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1448 }
1450 static void sp_text_context_update_text_selection(SPTextContext *tc)
1451 {
1452 for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1453 sp_canvas_item_hide(*it);
1454 gtk_object_destroy(*it);
1455 }
1456 tc->text_selection_quads.clear();
1458 std::vector<NR::Point> quads;
1459 if (tc->text != NULL)
1460 quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1461 for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1462 SPCanvasItem *quad_canvasitem;
1463 quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1464 sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x000000ff);
1465 sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1466 sp_canvas_item_show(quad_canvasitem);
1467 tc->text_selection_quads.push_back(quad_canvasitem);
1468 }
1469 }
1471 static gint
1472 sp_text_context_timeout(SPTextContext *tc)
1473 {
1474 if (tc->show) {
1475 if (tc->phase) {
1476 tc->phase = 0;
1477 sp_canvas_item_hide(tc->cursor);
1478 } else {
1479 tc->phase = 1;
1480 sp_canvas_item_show(tc->cursor);
1481 }
1482 }
1484 return TRUE;
1485 }
1487 static void
1488 sp_text_context_forget_text(SPTextContext *tc)
1489 {
1490 if (! tc->text) return;
1491 SPItem *ti = tc->text;
1492 /* We have to set it to zero,
1493 * or selection changed signal messes everything up */
1494 tc->text = NULL;
1495 if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1496 Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1497 // the repr may already have been unparented
1498 // if we were called e.g. as the result of
1499 // an undo or the element being removed from
1500 // the XML editor
1501 if ( text_repr && sp_repr_parent(text_repr) ) {
1502 sp_repr_unparent(text_repr);
1503 }
1504 }
1505 }
1507 gint
1508 sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1509 {
1510 gtk_im_context_focus_in(tc->imc);
1511 return FALSE;
1512 }
1514 gint
1515 sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1516 {
1517 gtk_im_context_focus_out(tc->imc);
1518 return FALSE;
1519 }
1521 static void
1522 sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc)
1523 {
1524 if (!tc->text) {
1525 sp_text_context_setup_text(tc);
1526 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1527 }
1529 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1530 sp_text_context_update_cursor(tc);
1531 sp_text_context_update_text_selection(tc);
1533 sp_document_done(SP_OBJECT_DOCUMENT(tc->text));
1534 }
1537 /*
1538 Local Variables:
1539 mode:c++
1540 c-file-style:"stroustrup"
1541 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1542 indent-tabs-mode:nil
1543 fill-column:99
1544 End:
1545 */
1546 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :