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