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