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