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 /* Set style */
703 sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), SP_OBJECT_REPR(ft), "tools.text", true);
704 sp_desktop_selection(desktop)->set(ft);
705 ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
706 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
707 _("Create flowed text"));
708 } else {
709 ec->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
710 }
711 }
712 tc->creating = false;
713 return TRUE;
714 }
715 break;
716 case GDK_KEY_PRESS: {
717 guint const group0_keyval = get_group0_keyval(&event->key);
719 if (group0_keyval == GDK_KP_Add ||
720 group0_keyval == GDK_KP_Subtract) {
721 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
722 break; // otherwise pass on keypad +/- so they can zoom
723 }
725 if ((tc->text) || (tc->nascent_object)) {
726 // there is an active text object in this context, or a new object was just created
728 if (tc->unimode || !tc->imc
729 || (MOD__CTRL && MOD__SHIFT) // input methods tend to steal this for unimode,
730 // but we have our own so make sure they don't swallow it
731 || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
732 //IM did not consume the key, or we're in unimode
734 if (!MOD__CTRL_ONLY && tc->unimode) {
735 /* TODO: ISO 14755 (section 3 Definitions) says that we should also
736 accept the first 6 characters of alphabets other than the latin
737 alphabet "if the Latin alphabet is not used". The below is also
738 reasonable (viz. hope that the user's keyboard includes latin
739 characters and force latin interpretation -- just as we do for our
740 keyboard shortcuts), but differs from the ISO 14755
741 recommendation. */
742 switch (group0_keyval) {
743 case GDK_space:
744 case GDK_KP_Space: {
745 if (tc->unipos) {
746 insert_uni_char(tc);
747 }
748 /* Stay in unimode. */
749 show_curr_uni_char(tc);
750 return TRUE;
751 }
753 case GDK_BackSpace: {
754 g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
755 if (tc->unipos) {
756 tc->uni[--tc->unipos] = '\0';
757 }
758 show_curr_uni_char(tc);
759 return TRUE;
760 }
762 case GDK_Return:
763 case GDK_KP_Enter: {
764 if (tc->unipos) {
765 insert_uni_char(tc);
766 }
767 /* Exit unimode. */
768 tc->unimode = false;
769 ec->defaultMessageContext()->clear();
770 return TRUE;
771 }
773 case GDK_Escape: {
774 // Cancel unimode.
775 tc->unimode = false;
776 gtk_im_context_reset(tc->imc);
777 ec->defaultMessageContext()->clear();
778 return TRUE;
779 }
781 case GDK_Shift_L:
782 case GDK_Shift_R:
783 break;
785 default: {
786 if (g_ascii_isxdigit(group0_keyval)) {
787 g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
788 tc->uni[tc->unipos++] = group0_keyval;
789 tc->uni[tc->unipos] = '\0';
790 if (tc->unipos == 8) {
791 /* This behaviour is partly to allow us to continue to
792 use a fixed-length buffer for tc->uni. Reason for
793 choosing the number 8 is that it's the length of
794 ``canonical form'' mentioned in the ISO 14755 spec.
795 An advantage over choosing 6 is that it allows using
796 backspace for typos & misremembering when entering a
797 6-digit number. */
798 insert_uni_char(tc);
799 }
800 show_curr_uni_char(tc);
801 return TRUE;
802 } else {
803 /* The intent is to ignore but consume characters that could be
804 typos for hex digits. Gtk seems to ignore & consume all
805 non-hex-digits, and we do similar here. Though note that some
806 shortcuts (like keypad +/- for zoom) get processed before
807 reaching this code. */
808 return TRUE;
809 }
810 }
811 }
812 }
814 bool (Inkscape::Text::Layout::iterator::*cursor_movement_operator)() = NULL;
816 /* Neither unimode nor IM consumed key; process text tool shortcuts */
817 switch (group0_keyval) {
818 case GDK_x:
819 case GDK_X:
820 if (MOD__ALT_ONLY) {
821 desktop->setToolboxFocusTo ("altx-text");
822 return TRUE;
823 }
824 break;
825 case GDK_space:
826 if (MOD__CTRL_ONLY) {
827 /* No-break space */
828 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
829 sp_text_context_setup_text(tc);
830 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
831 }
832 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
833 sp_text_context_update_cursor(tc);
834 sp_text_context_update_text_selection(tc);
835 ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
836 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
837 _("Insert no-break space"));
838 return TRUE;
839 }
840 break;
841 case GDK_U:
842 case GDK_u:
843 if (MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT)) {
844 if (tc->unimode) {
845 tc->unimode = false;
846 ec->defaultMessageContext()->clear();
847 } else {
848 tc->unimode = true;
849 tc->unipos = 0;
850 ec->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
851 }
852 if (tc->imc) {
853 gtk_im_context_reset(tc->imc);
854 }
855 return TRUE;
856 }
857 break;
858 case GDK_B:
859 case GDK_b:
860 if (MOD__CTRL_ONLY && tc->text) {
861 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
862 SPCSSAttr *css = sp_repr_css_attr_new();
863 if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
864 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
865 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
866 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
867 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
868 sp_repr_css_set_property(css, "font-weight", "bold");
869 else
870 sp_repr_css_set_property(css, "font-weight", "normal");
871 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
872 sp_repr_css_attr_unref(css);
873 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
874 _("Make bold"));
875 sp_text_context_update_cursor(tc);
876 sp_text_context_update_text_selection(tc);
877 return TRUE;
878 }
879 break;
880 case GDK_I:
881 case GDK_i:
882 if (MOD__CTRL_ONLY && tc->text) {
883 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
884 SPCSSAttr *css = sp_repr_css_attr_new();
885 if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL)
886 sp_repr_css_set_property(css, "font-style", "italic");
887 else
888 sp_repr_css_set_property(css, "font-style", "normal");
889 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
890 sp_repr_css_attr_unref(css);
891 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
892 _("Make italic"));
893 sp_text_context_update_cursor(tc);
894 sp_text_context_update_text_selection(tc);
895 return TRUE;
896 }
897 break;
899 case GDK_A:
900 case GDK_a:
901 if (MOD__CTRL_ONLY && tc->text) {
902 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
903 if (layout) {
904 tc->text_sel_start = layout->begin();
905 tc->text_sel_end = layout->end();
906 sp_text_context_update_cursor(tc);
907 sp_text_context_update_text_selection(tc);
908 return TRUE;
909 }
910 }
911 break;
913 case GDK_Return:
914 case GDK_KP_Enter:
915 {
916 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
917 sp_text_context_setup_text(tc);
918 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
919 }
921 iterator_pair enter_pair;
922 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, enter_pair);
923 tc->text_sel_start = tc->text_sel_end = enter_pair.first;
925 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
927 sp_text_context_update_cursor(tc);
928 sp_text_context_update_text_selection(tc);
929 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
930 _("New line"));
931 return TRUE;
932 }
933 case GDK_BackSpace:
934 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
936 bool noSelection = false;
938 if (tc->text_sel_start == tc->text_sel_end) {
939 tc->text_sel_start.prevCursorPosition();
940 noSelection = true;
941 }
943 iterator_pair bspace_pair;
944 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, bspace_pair);
946 if (noSelection) {
947 if (success) {
948 tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
949 } else { // nothing deleted
950 tc->text_sel_start = tc->text_sel_end = bspace_pair.second;
951 }
952 } else {
953 if (success) {
954 tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
955 } else { // nothing deleted
956 tc->text_sel_start = bspace_pair.first;
957 tc->text_sel_end = bspace_pair.second;
958 }
959 }
961 sp_text_context_update_cursor(tc);
962 sp_text_context_update_text_selection(tc);
963 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
964 _("Backspace"));
965 }
966 return TRUE;
967 case GDK_Delete:
968 case GDK_KP_Delete:
969 if (tc->text) {
970 bool noSelection = false;
972 if (tc->text_sel_start == tc->text_sel_end) {
973 tc->text_sel_end.nextCursorPosition();
974 noSelection = true;
975 }
977 iterator_pair del_pair;
978 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, del_pair);
980 if (noSelection) {
981 tc->text_sel_start = tc->text_sel_end = del_pair.first;
982 } else {
983 if (success) {
984 tc->text_sel_start = tc->text_sel_end = del_pair.first;
985 } else { // nothing deleted
986 tc->text_sel_start = del_pair.first;
987 tc->text_sel_end = del_pair.second;
988 }
989 }
992 sp_text_context_update_cursor(tc);
993 sp_text_context_update_text_selection(tc);
994 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
995 _("Delete"));
996 }
997 return TRUE;
998 case GDK_Left:
999 case GDK_KP_Left:
1000 case GDK_KP_4:
1001 if (tc->text) {
1002 if (MOD__ALT) {
1003 if (MOD__SHIFT)
1004 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-10, 0));
1005 else
1006 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-1, 0));
1007 sp_text_context_update_cursor(tc);
1008 sp_text_context_update_text_selection(tc);
1009 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:left", SP_VERB_CONTEXT_TEXT,
1010 _("Kern to the left"));
1011 } else {
1012 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorLeftWithControl
1013 : &Inkscape::Text::Layout::iterator::cursorLeft;
1014 break;
1015 }
1016 }
1017 return TRUE;
1018 case GDK_Right:
1019 case GDK_KP_Right:
1020 case GDK_KP_6:
1021 if (tc->text) {
1022 if (MOD__ALT) {
1023 if (MOD__SHIFT)
1024 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(10, 0));
1025 else
1026 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(1, 0));
1027 sp_text_context_update_cursor(tc);
1028 sp_text_context_update_text_selection(tc);
1029 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:right", SP_VERB_CONTEXT_TEXT,
1030 _("Kern to the right"));
1031 } else {
1032 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorRightWithControl
1033 : &Inkscape::Text::Layout::iterator::cursorRight;
1034 break;
1035 }
1036 }
1037 return TRUE;
1038 case GDK_Up:
1039 case GDK_KP_Up:
1040 case GDK_KP_8:
1041 if (tc->text) {
1042 if (MOD__ALT) {
1043 if (MOD__SHIFT)
1044 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -10));
1045 else
1046 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -1));
1047 sp_text_context_update_cursor(tc);
1048 sp_text_context_update_text_selection(tc);
1049 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:up", SP_VERB_CONTEXT_TEXT,
1050 _("Kern up"));
1052 } else {
1053 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorUpWithControl
1054 : &Inkscape::Text::Layout::iterator::cursorUp;
1055 break;
1056 }
1057 }
1058 return TRUE;
1059 case GDK_Down:
1060 case GDK_KP_Down:
1061 case GDK_KP_2:
1062 if (tc->text) {
1063 if (MOD__ALT) {
1064 if (MOD__SHIFT)
1065 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 10));
1066 else
1067 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 1));
1068 sp_text_context_update_cursor(tc);
1069 sp_text_context_update_text_selection(tc);
1070 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:down", SP_VERB_CONTEXT_TEXT,
1071 _("Kern down"));
1073 } else {
1074 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorDownWithControl
1075 : &Inkscape::Text::Layout::iterator::cursorDown;
1076 break;
1077 }
1078 }
1079 return TRUE;
1080 case GDK_Home:
1081 case GDK_KP_Home:
1082 if (tc->text) {
1083 if (MOD__CTRL)
1084 cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfShape;
1085 else
1086 cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfLine;
1087 break;
1088 }
1089 return TRUE;
1090 case GDK_End:
1091 case GDK_KP_End:
1092 if (tc->text) {
1093 if (MOD__CTRL)
1094 cursor_movement_operator = &Inkscape::Text::Layout::iterator::nextStartOfShape;
1095 else
1096 cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisEndOfLine;
1097 break;
1098 }
1099 return TRUE;
1100 case GDK_Escape:
1101 if (tc->creating) {
1102 tc->creating = 0;
1103 if (tc->grabbed) {
1104 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1105 tc->grabbed = NULL;
1106 }
1107 Inkscape::Rubberband::get()->stop();
1108 } else {
1109 sp_desktop_selection(ec->desktop)->clear();
1110 }
1111 tc->nascent_object = FALSE;
1112 return TRUE;
1113 case GDK_bracketleft:
1114 if (tc->text) {
1115 if (MOD__ALT || MOD__CTRL) {
1116 if (MOD__ALT) {
1117 if (MOD__SHIFT) {
1118 // FIXME: alt+shift+[] does not work, don't know why
1119 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1120 } else {
1121 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1122 }
1123 } else {
1124 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -90);
1125 }
1126 sp_document_maybe_done(sp_desktop_document(ec->desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT,
1127 _("Rotate counterclockwise"));
1128 sp_text_context_update_cursor(tc);
1129 sp_text_context_update_text_selection(tc);
1130 return TRUE;
1131 }
1132 }
1133 break;
1134 case GDK_bracketright:
1135 if (tc->text) {
1136 if (MOD__ALT || MOD__CTRL) {
1137 if (MOD__ALT) {
1138 if (MOD__SHIFT) {
1139 // FIXME: alt+shift+[] does not work, don't know why
1140 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1141 } else {
1142 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1143 }
1144 } else {
1145 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 90);
1146 }
1147 sp_document_maybe_done(sp_desktop_document(ec->desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT,
1148 _("Rotate clockwise"));
1149 sp_text_context_update_cursor(tc);
1150 sp_text_context_update_text_selection(tc);
1151 return TRUE;
1152 }
1153 }
1154 break;
1155 case GDK_less:
1156 case GDK_comma:
1157 if (tc->text) {
1158 if (MOD__ALT) {
1159 if (MOD__CTRL) {
1160 if (MOD__SHIFT)
1161 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1162 else
1163 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1164 sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT,
1165 _("Contract line spacing"));
1167 } else {
1168 if (MOD__SHIFT)
1169 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1170 else
1171 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1172 sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT,
1173 _("Contract letter spacing"));
1175 }
1176 sp_text_context_update_cursor(tc);
1177 sp_text_context_update_text_selection(tc);
1178 return TRUE;
1179 }
1180 }
1181 break;
1182 case GDK_greater:
1183 case GDK_period:
1184 if (tc->text) {
1185 if (MOD__ALT) {
1186 if (MOD__CTRL) {
1187 if (MOD__SHIFT)
1188 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1189 else
1190 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1191 sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT,
1192 _("Expand line spacing"));
1194 } else {
1195 if (MOD__SHIFT)
1196 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1197 else
1198 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1199 sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT,
1200 _("Expand letter spacing"));
1202 }
1203 sp_text_context_update_cursor(tc);
1204 sp_text_context_update_text_selection(tc);
1205 return TRUE;
1206 }
1207 }
1208 break;
1209 default:
1210 break;
1211 }
1213 if (cursor_movement_operator) {
1214 Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
1215 Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
1216 (tc->text_sel_end.*cursor_movement_operator)();
1217 if (!MOD__SHIFT)
1218 tc->text_sel_start = tc->text_sel_end;
1219 if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1220 sp_text_context_update_cursor(tc);
1221 sp_text_context_update_text_selection(tc);
1222 }
1223 return TRUE;
1224 }
1226 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1227 } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1228 // except up/down that are swallowed to prevent the zoom field from activation
1229 if ((group0_keyval == GDK_Up ||
1230 group0_keyval == GDK_Down ||
1231 group0_keyval == GDK_KP_Up ||
1232 group0_keyval == GDK_KP_Down )
1233 && !MOD__CTRL_ONLY) {
1234 return TRUE;
1235 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1236 if (tc->creating) {
1237 tc->creating = 0;
1238 if (tc->grabbed) {
1239 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1240 tc->grabbed = NULL;
1241 }
1242 Inkscape::Rubberband::get()->stop();
1243 }
1244 }
1245 }
1246 break;
1247 }
1249 case GDK_KEY_RELEASE:
1250 if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1251 return TRUE;
1252 }
1253 break;
1254 default:
1255 break;
1256 }
1258 // if nobody consumed it so far
1259 if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1260 return ((SPEventContextClass *) parent_class)->root_handler(ec, event); // send event to parent
1261 } else {
1262 return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1263 }
1264 }
1266 /**
1267 Attempts to paste system clipboard into the currently edited text, returns true on success
1268 */
1269 bool
1270 sp_text_paste_inline(SPEventContext *ec)
1271 {
1272 if (!SP_IS_TEXT_CONTEXT(ec))
1273 return false;
1275 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1277 if ((tc->text) || (tc->nascent_object)) {
1278 // there is an active text object in this context, or a new object was just created
1280 Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1281 Glib::ustring const text = refClipboard->wait_for_text();
1283 if (!text.empty()) {
1285 if (!tc->text) { // create text if none (i.e. if nascent_object)
1286 sp_text_context_setup_text(tc);
1287 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1288 }
1290 // using indices is slow in ustrings. Whatever.
1291 Glib::ustring::size_type begin = 0;
1292 for ( ; ; ) {
1293 Glib::ustring::size_type end = text.find('\n', begin);
1294 if (end == Glib::ustring::npos) {
1295 if (begin != text.length())
1296 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());
1297 break;
1298 }
1299 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());
1300 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1301 begin = end + 1;
1302 }
1303 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
1304 _("Paste text"));
1306 return true;
1307 }
1308 } // FIXME: else create and select a new object under cursor!
1310 return false;
1311 }
1313 /**
1314 Gets the raw characters that comprise the currently selected text, converting line
1315 breaks into lf characters.
1316 */
1317 Glib::ustring
1318 sp_text_get_selected_text(SPEventContext const *ec)
1319 {
1320 if (!SP_IS_TEXT_CONTEXT(ec))
1321 return "";
1322 SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1323 if (tc->text == NULL)
1324 return "";
1326 return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1327 }
1329 /**
1330 Deletes the currently selected characters. Returns false if there is no
1331 text selection currently.
1332 */
1333 bool sp_text_delete_selection(SPEventContext *ec)
1334 {
1335 if (!SP_IS_TEXT_CONTEXT(ec))
1336 return false;
1337 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1338 if (tc->text == NULL)
1339 return false;
1341 if (tc->text_sel_start == tc->text_sel_end)
1342 return false;
1344 iterator_pair pair;
1345 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair);
1348 if (success) {
1349 tc->text_sel_start = tc->text_sel_end = pair.first;
1350 } else { // nothing deleted
1351 tc->text_sel_start = pair.first;
1352 tc->text_sel_end = pair.second;
1353 }
1355 sp_text_context_update_cursor(tc);
1356 sp_text_context_update_text_selection(tc);
1358 return true;
1359 }
1361 /**
1362 * \param selection Should not be NULL.
1363 */
1364 static void
1365 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1366 {
1367 g_assert(selection != NULL);
1369 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1371 if (ec->shape_knot_holder) { // destroy knotholder
1372 sp_knot_holder_destroy(ec->shape_knot_holder);
1373 ec->shape_knot_holder = NULL;
1374 }
1376 if (ec->shape_repr) { // remove old listener
1377 sp_repr_remove_listener_by_data(ec->shape_repr, ec);
1378 Inkscape::GC::release(ec->shape_repr);
1379 ec->shape_repr = 0;
1380 }
1382 SPItem *item = selection->singleItem();
1383 if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1384 ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
1385 Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
1386 if (shape_repr) {
1387 ec->shape_repr = shape_repr;
1388 Inkscape::GC::anchor(shape_repr);
1389 sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
1390 }
1391 }
1393 if (tc->text && (item != tc->text)) {
1394 sp_text_context_forget_text(tc);
1395 }
1396 tc->text = NULL;
1398 if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1399 tc->text = item;
1400 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1401 if (layout)
1402 tc->text_sel_start = tc->text_sel_end = layout->end();
1403 } else {
1404 tc->text = NULL;
1405 }
1407 // we update cursor without scrolling, because this position may not be final;
1408 // item_handler moves cusros to the point of click immediately
1409 sp_text_context_update_cursor(tc, false);
1410 sp_text_context_update_text_selection(tc);
1411 }
1413 static void
1414 sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc)
1415 {
1416 sp_text_context_update_cursor(tc);
1417 sp_text_context_update_text_selection(tc);
1418 }
1420 static bool
1421 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1422 {
1423 if (tc->text == NULL)
1424 return false;
1425 if (tc->text_sel_start == tc->text_sel_end)
1426 return false; // will get picked up by the parent and applied to the whole text object
1428 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1429 sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1430 _("Set text style"));
1431 sp_text_context_update_cursor(tc);
1432 sp_text_context_update_text_selection(tc);
1434 return true;
1435 }
1437 static int
1438 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1439 {
1440 if (tc->text == NULL)
1441 return QUERY_STYLE_NOTHING;
1442 const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1443 if (layout == NULL)
1444 return QUERY_STYLE_NOTHING;
1445 sp_text_context_validate_cursor_iterators(tc);
1447 GSList *styles_list = NULL;
1449 Inkscape::Text::Layout::iterator begin_it, end_it;
1450 if (tc->text_sel_start < tc->text_sel_end) {
1451 begin_it = tc->text_sel_start;
1452 end_it = tc->text_sel_end;
1453 } else {
1454 begin_it = tc->text_sel_end;
1455 end_it = tc->text_sel_start;
1456 }
1457 if (begin_it == end_it)
1458 if (!begin_it.prevCharacter())
1459 end_it.nextCharacter();
1460 for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1461 SPObject const *pos_obj = 0;
1462 void *rawptr = 0;
1463 layout->getSourceOfCharacter(it, &rawptr);
1464 pos_obj = SP_OBJECT(rawptr);
1465 if (pos_obj == 0) continue;
1466 while (SP_IS_STRING(pos_obj) && SP_OBJECT_PARENT(pos_obj)) {
1467 pos_obj = SP_OBJECT_PARENT(pos_obj); // SPStrings don't have style
1468 }
1469 styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1470 }
1472 int result = sp_desktop_query_style_from_list (styles_list, style, property);
1474 g_slist_free(styles_list);
1475 return result;
1476 }
1478 static void
1479 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1480 {
1481 if (tc->text == NULL)
1482 return;
1483 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1484 if (layout) { // undo can change the text length without us knowing it
1485 layout->validateIterator(&tc->text_sel_start);
1486 layout->validateIterator(&tc->text_sel_end);
1487 }
1488 }
1490 static void
1491 sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see)
1492 {
1493 GdkRectangle im_cursor = { 0, 0, 1, 1 };
1495 // due to interruptible display, tc may already be destroyed during a display update before
1496 // the cursor update (can't do both atomically, alas)
1497 if (!tc->desktop) return;
1499 if (tc->text) {
1500 NR::Point p0, p1;
1501 sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1502 NR::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1503 NR::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1505 // scroll to show cursor
1506 if (scroll_to_see) {
1507 NR::Point const dm = (d0 + d1) / 2;
1508 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1509 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(&dm, 1.0);
1510 }
1512 sp_canvas_item_show(tc->cursor);
1513 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1515 /* fixme: ... need another transformation to get canvas widget coordinate space? */
1516 im_cursor.x = (int) floor(d0[NR::X]);
1517 im_cursor.y = (int) floor(d0[NR::Y]);
1518 im_cursor.width = (int) floor(d1[NR::X]) - im_cursor.x;
1519 im_cursor.height = (int) floor(d1[NR::Y]) - im_cursor.y;
1521 tc->show = TRUE;
1522 tc->phase = 1;
1524 if (SP_IS_FLOWTEXT(tc->text)) {
1525 SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1526 if (frame) {
1527 sp_canvas_item_show(tc->frame);
1528 NR::Maybe<NR::Rect> frame_bbox = sp_item_bbox_desktop(frame);
1529 if (frame_bbox) {
1530 SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
1531 }
1532 }
1533 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1534 } else {
1535 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1536 }
1538 } else {
1539 sp_canvas_item_hide(tc->cursor);
1540 sp_canvas_item_hide(tc->frame);
1541 tc->show = FALSE;
1542 if (!tc->nascent_object) {
1543 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
1544 }
1545 }
1547 if (tc->imc) {
1548 gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1549 }
1550 SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1551 }
1553 static void sp_text_context_update_text_selection(SPTextContext *tc)
1554 {
1555 // due to interruptible display, tc may already be destroyed during a display update before
1556 // the selection update (can't do both atomically, alas)
1557 if (!tc->desktop) return;
1559 for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1560 sp_canvas_item_hide(*it);
1561 gtk_object_destroy(*it);
1562 }
1563 tc->text_selection_quads.clear();
1565 std::vector<NR::Point> quads;
1566 if (tc->text != NULL)
1567 quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1568 for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1569 SPCanvasItem *quad_canvasitem;
1570 quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1571 sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x000000ff);
1572 sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1573 sp_canvas_item_show(quad_canvasitem);
1574 tc->text_selection_quads.push_back(quad_canvasitem);
1575 }
1576 }
1578 static gint
1579 sp_text_context_timeout(SPTextContext *tc)
1580 {
1581 if (tc->show) {
1582 if (tc->phase) {
1583 tc->phase = 0;
1584 sp_canvas_item_hide(tc->cursor);
1585 } else {
1586 tc->phase = 1;
1587 sp_canvas_item_show(tc->cursor);
1588 }
1589 }
1591 return TRUE;
1592 }
1594 static void
1595 sp_text_context_forget_text(SPTextContext *tc)
1596 {
1597 if (! tc->text) return;
1598 SPItem *ti = tc->text;
1599 /* We have to set it to zero,
1600 * or selection changed signal messes everything up */
1601 tc->text = NULL;
1602 if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1603 Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1604 // the repr may already have been unparented
1605 // if we were called e.g. as the result of
1606 // an undo or the element being removed from
1607 // the XML editor
1608 if ( text_repr && sp_repr_parent(text_repr) ) {
1609 sp_repr_unparent(text_repr);
1610 sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1611 _("Remove empty text"));
1612 }
1613 }
1614 }
1616 gint
1617 sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1618 {
1619 gtk_im_context_focus_in(tc->imc);
1620 return FALSE;
1621 }
1623 gint
1624 sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1625 {
1626 gtk_im_context_focus_out(tc->imc);
1627 return FALSE;
1628 }
1630 static void
1631 sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc)
1632 {
1633 if (!tc->text) {
1634 sp_text_context_setup_text(tc);
1635 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1636 }
1638 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1639 sp_text_context_update_cursor(tc);
1640 sp_text_context_update_text_selection(tc);
1642 sp_document_done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT,
1643 _("Type text"));
1644 }
1647 /*
1648 Local Variables:
1649 mode:c++
1650 c-file-style:"stroustrup"
1651 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1652 indent-tabs-mode:nil
1653 fill-column:99
1654 End:
1655 */
1656 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :