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