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 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
916 sp_text_context_setup_text(tc);
917 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
918 }
919 tc->text_sel_start = tc->text_sel_end
920 = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, SP_TE_DELETE_OTHER);
922 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
923 sp_text_context_update_cursor(tc);
924 sp_text_context_update_text_selection(tc);
925 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
926 _("New line"));
927 return TRUE;
928 case GDK_BackSpace:
929 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
930 sp_te_deletion_type deleteType = SP_TE_DELETE_OTHER;
932 if (tc->text_sel_start == tc->text_sel_end) {
933 tc->text_sel_start.prevCursorPosition();
934 deleteType = SP_TE_DELETE_SINGLE_BACKSPACE;
935 }
937 tc->text_sel_start = tc->text_sel_end
938 = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, deleteType);
940 sp_text_context_update_cursor(tc);
941 sp_text_context_update_text_selection(tc);
942 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
943 _("Backspace"));
944 }
945 return TRUE;
946 case GDK_Delete:
947 case GDK_KP_Delete:
948 if (tc->text) {
949 sp_te_deletion_type deleteType = SP_TE_DELETE_OTHER;
951 if (tc->text_sel_start == tc->text_sel_end) {
952 tc->text_sel_end.nextCursorPosition();
953 deleteType = SP_TE_SINGLE_DELETE;
954 }
956 tc->text_sel_start = tc->text_sel_end
957 = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, deleteType);
959 sp_text_context_update_cursor(tc);
960 sp_text_context_update_text_selection(tc);
961 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
962 _("Delete"));
963 }
964 return TRUE;
965 case GDK_Left:
966 case GDK_KP_Left:
967 case GDK_KP_4:
968 if (tc->text) {
969 if (MOD__ALT) {
970 if (MOD__SHIFT)
971 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-10, 0));
972 else
973 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-1, 0));
974 sp_text_context_update_cursor(tc);
975 sp_text_context_update_text_selection(tc);
976 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:left", SP_VERB_CONTEXT_TEXT,
977 _("Kern to the left"));
978 } else {
979 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorLeftWithControl
980 : &Inkscape::Text::Layout::iterator::cursorLeft;
981 break;
982 }
983 }
984 return TRUE;
985 case GDK_Right:
986 case GDK_KP_Right:
987 case GDK_KP_6:
988 if (tc->text) {
989 if (MOD__ALT) {
990 if (MOD__SHIFT)
991 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(10, 0));
992 else
993 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(1, 0));
994 sp_text_context_update_cursor(tc);
995 sp_text_context_update_text_selection(tc);
996 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:right", SP_VERB_CONTEXT_TEXT,
997 _("Kern to the right"));
998 } else {
999 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorRightWithControl
1000 : &Inkscape::Text::Layout::iterator::cursorRight;
1001 break;
1002 }
1003 }
1004 return TRUE;
1005 case GDK_Up:
1006 case GDK_KP_Up:
1007 case GDK_KP_8:
1008 if (tc->text) {
1009 if (MOD__ALT) {
1010 if (MOD__SHIFT)
1011 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -10));
1012 else
1013 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -1));
1014 sp_text_context_update_cursor(tc);
1015 sp_text_context_update_text_selection(tc);
1016 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:up", SP_VERB_CONTEXT_TEXT,
1017 _("Kern up"));
1019 } else {
1020 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorUpWithControl
1021 : &Inkscape::Text::Layout::iterator::cursorUp;
1022 break;
1023 }
1024 }
1025 return TRUE;
1026 case GDK_Down:
1027 case GDK_KP_Down:
1028 case GDK_KP_2:
1029 if (tc->text) {
1030 if (MOD__ALT) {
1031 if (MOD__SHIFT)
1032 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 10));
1033 else
1034 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 1));
1035 sp_text_context_update_cursor(tc);
1036 sp_text_context_update_text_selection(tc);
1037 sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:down", SP_VERB_CONTEXT_TEXT,
1038 _("Kern down"));
1040 } else {
1041 cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorDownWithControl
1042 : &Inkscape::Text::Layout::iterator::cursorDown;
1043 break;
1044 }
1045 }
1046 return TRUE;
1047 case GDK_Home:
1048 case GDK_KP_Home:
1049 if (tc->text) {
1050 if (MOD__CTRL)
1051 cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfShape;
1052 else
1053 cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfLine;
1054 break;
1055 }
1056 return TRUE;
1057 case GDK_End:
1058 case GDK_KP_End:
1059 if (tc->text) {
1060 if (MOD__CTRL)
1061 cursor_movement_operator = &Inkscape::Text::Layout::iterator::nextStartOfShape;
1062 else
1063 cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisEndOfLine;
1064 break;
1065 }
1066 return TRUE;
1067 case GDK_Escape:
1068 if (tc->creating) {
1069 tc->creating = 0;
1070 if (tc->grabbed) {
1071 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1072 tc->grabbed = NULL;
1073 }
1074 Inkscape::Rubberband::get()->stop();
1075 } else {
1076 sp_desktop_selection(ec->desktop)->clear();
1077 }
1078 tc->nascent_object = FALSE;
1079 return TRUE;
1080 case GDK_bracketleft:
1081 if (tc->text) {
1082 if (MOD__ALT || MOD__CTRL) {
1083 if (MOD__ALT) {
1084 if (MOD__SHIFT) {
1085 // FIXME: alt+shift+[] does not work, don't know why
1086 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1087 } else {
1088 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1089 }
1090 } else {
1091 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -90);
1092 }
1093 sp_document_maybe_done(sp_desktop_document(ec->desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT,
1094 _("Rotate counterclockwise"));
1095 sp_text_context_update_cursor(tc);
1096 sp_text_context_update_text_selection(tc);
1097 return TRUE;
1098 }
1099 }
1100 break;
1101 case GDK_bracketright:
1102 if (tc->text) {
1103 if (MOD__ALT || MOD__CTRL) {
1104 if (MOD__ALT) {
1105 if (MOD__SHIFT) {
1106 // FIXME: alt+shift+[] does not work, don't know why
1107 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1108 } else {
1109 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1110 }
1111 } else {
1112 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 90);
1113 }
1114 sp_document_maybe_done(sp_desktop_document(ec->desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT,
1115 _("Rotate clockwise"));
1116 sp_text_context_update_cursor(tc);
1117 sp_text_context_update_text_selection(tc);
1118 return TRUE;
1119 }
1120 }
1121 break;
1122 case GDK_less:
1123 case GDK_comma:
1124 if (tc->text) {
1125 if (MOD__ALT) {
1126 if (MOD__CTRL) {
1127 if (MOD__SHIFT)
1128 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1129 else
1130 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1131 sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT,
1132 _("Contract line spacing"));
1134 } else {
1135 if (MOD__SHIFT)
1136 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
1137 else
1138 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
1139 sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT,
1140 _("Contract letter spacing"));
1142 }
1143 sp_text_context_update_cursor(tc);
1144 sp_text_context_update_text_selection(tc);
1145 return TRUE;
1146 }
1147 }
1148 break;
1149 case GDK_greater:
1150 case GDK_period:
1151 if (tc->text) {
1152 if (MOD__ALT) {
1153 if (MOD__CTRL) {
1154 if (MOD__SHIFT)
1155 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1156 else
1157 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1158 sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT,
1159 _("Expand line spacing"));
1161 } else {
1162 if (MOD__SHIFT)
1163 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
1164 else
1165 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
1166 sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT,
1167 _("Expand letter spacing"));
1169 }
1170 sp_text_context_update_cursor(tc);
1171 sp_text_context_update_text_selection(tc);
1172 return TRUE;
1173 }
1174 }
1175 break;
1176 default:
1177 break;
1178 }
1180 if (cursor_movement_operator) {
1181 Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
1182 Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
1183 (tc->text_sel_end.*cursor_movement_operator)();
1184 if (!MOD__SHIFT)
1185 tc->text_sel_start = tc->text_sel_end;
1186 if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1187 sp_text_context_update_cursor(tc);
1188 sp_text_context_update_text_selection(tc);
1189 }
1190 return TRUE;
1191 }
1193 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1194 } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1195 // except up/down that are swallowed to prevent the zoom field from activation
1196 if ((group0_keyval == GDK_Up ||
1197 group0_keyval == GDK_Down ||
1198 group0_keyval == GDK_KP_Up ||
1199 group0_keyval == GDK_KP_Down )
1200 && !MOD__CTRL_ONLY) {
1201 return TRUE;
1202 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1203 if (tc->creating) {
1204 tc->creating = 0;
1205 if (tc->grabbed) {
1206 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1207 tc->grabbed = NULL;
1208 }
1209 Inkscape::Rubberband::get()->stop();
1210 }
1211 }
1212 }
1213 break;
1214 }
1216 case GDK_KEY_RELEASE:
1217 if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1218 return TRUE;
1219 }
1220 break;
1221 default:
1222 break;
1223 }
1225 // if nobody consumed it so far
1226 if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1227 return ((SPEventContextClass *) parent_class)->root_handler(ec, event); // send event to parent
1228 } else {
1229 return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1230 }
1231 }
1233 /**
1234 Attempts to paste system clipboard into the currently edited text, returns true on success
1235 */
1236 bool
1237 sp_text_paste_inline(SPEventContext *ec)
1238 {
1239 if (!SP_IS_TEXT_CONTEXT(ec))
1240 return false;
1242 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1244 if ((tc->text) || (tc->nascent_object)) {
1245 // there is an active text object in this context, or a new object was just created
1247 Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1248 Glib::ustring const text = refClipboard->wait_for_text();
1250 if (!text.empty()) {
1252 if (!tc->text) { // create text if none (i.e. if nascent_object)
1253 sp_text_context_setup_text(tc);
1254 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1255 }
1257 // using indices is slow in ustrings. Whatever.
1258 Glib::ustring::size_type begin = 0;
1259 for ( ; ; ) {
1260 Glib::ustring::size_type end = text.find('\n', begin);
1261 if (end == Glib::ustring::npos) {
1262 if (begin != text.length())
1263 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());
1264 break;
1265 }
1266 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());
1267 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1268 begin = end + 1;
1269 }
1270 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
1271 _("Paste text"));
1273 return true;
1274 }
1275 } // FIXME: else create and select a new object under cursor!
1277 return false;
1278 }
1280 /**
1281 Gets the raw characters that comprise the currently selected text, converting line
1282 breaks into lf characters.
1283 */
1284 Glib::ustring
1285 sp_text_get_selected_text(SPEventContext const *ec)
1286 {
1287 if (!SP_IS_TEXT_CONTEXT(ec))
1288 return "";
1289 SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1290 if (tc->text == NULL)
1291 return "";
1293 return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1294 }
1296 /**
1297 Deletes the currently selected characters. Returns false if there is no
1298 text selection currently.
1299 */
1300 bool sp_text_delete_selection(SPEventContext *ec)
1301 {
1302 if (!SP_IS_TEXT_CONTEXT(ec))
1303 return false;
1304 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1305 if (tc->text == NULL)
1306 return false;
1308 if (tc->text_sel_start == tc->text_sel_end)
1309 return false;
1310 tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, SP_TE_DELETE_OTHER);
1311 sp_text_context_update_cursor(tc);
1312 sp_text_context_update_text_selection(tc);
1313 return true;
1314 }
1316 /**
1317 * \param selection Should not be NULL.
1318 */
1319 static void
1320 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1321 {
1322 g_assert(selection != NULL);
1324 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1326 if (ec->shape_knot_holder) { // destroy knotholder
1327 sp_knot_holder_destroy(ec->shape_knot_holder);
1328 ec->shape_knot_holder = NULL;
1329 }
1331 if (ec->shape_repr) { // remove old listener
1332 sp_repr_remove_listener_by_data(ec->shape_repr, ec);
1333 Inkscape::GC::release(ec->shape_repr);
1334 ec->shape_repr = 0;
1335 }
1337 SPItem *item = selection->singleItem();
1338 if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1339 ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
1340 Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
1341 if (shape_repr) {
1342 ec->shape_repr = shape_repr;
1343 Inkscape::GC::anchor(shape_repr);
1344 sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
1345 }
1346 }
1348 if (tc->text && (item != tc->text)) {
1349 sp_text_context_forget_text(tc);
1350 }
1351 tc->text = NULL;
1353 if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1354 tc->text = item;
1355 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1356 if (layout)
1357 tc->text_sel_start = tc->text_sel_end = layout->end();
1358 } else {
1359 tc->text = NULL;
1360 }
1362 // we update cursor without scrolling, because this position may not be final;
1363 // item_handler moves cusros to the point of click immediately
1364 sp_text_context_update_cursor(tc, false);
1365 sp_text_context_update_text_selection(tc);
1366 }
1368 static void
1369 sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc)
1370 {
1371 sp_text_context_update_cursor(tc);
1372 sp_text_context_update_text_selection(tc);
1373 }
1375 static bool
1376 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1377 {
1378 if (tc->text == NULL)
1379 return false;
1380 if (tc->text_sel_start == tc->text_sel_end)
1381 return false; // will get picked up by the parent and applied to the whole text object
1383 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1384 sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1385 _("Set text style"));
1386 sp_text_context_update_cursor(tc);
1387 sp_text_context_update_text_selection(tc);
1389 return true;
1390 }
1392 static int
1393 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1394 {
1395 if (tc->text == NULL)
1396 return QUERY_STYLE_NOTHING;
1397 const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1398 if (layout == NULL)
1399 return QUERY_STYLE_NOTHING;
1400 sp_text_context_validate_cursor_iterators(tc);
1402 GSList *styles_list = NULL;
1404 Inkscape::Text::Layout::iterator begin_it, end_it;
1405 if (tc->text_sel_start < tc->text_sel_end) {
1406 begin_it = tc->text_sel_start;
1407 end_it = tc->text_sel_end;
1408 } else {
1409 begin_it = tc->text_sel_end;
1410 end_it = tc->text_sel_start;
1411 }
1412 if (begin_it == end_it)
1413 if (!begin_it.prevCharacter())
1414 end_it.nextCharacter();
1415 for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1416 SPObject const *pos_obj = 0;
1417 void *rawptr = 0;
1418 layout->getSourceOfCharacter(it, &rawptr);
1419 pos_obj = SP_OBJECT(rawptr);
1420 if (pos_obj == 0) continue;
1421 while (SP_IS_STRING(pos_obj) && SP_OBJECT_PARENT(pos_obj)) {
1422 pos_obj = SP_OBJECT_PARENT(pos_obj); // SPStrings don't have style
1423 }
1424 styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1425 }
1427 int result = sp_desktop_query_style_from_list (styles_list, style, property);
1429 g_slist_free(styles_list);
1430 return result;
1431 }
1433 static void
1434 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1435 {
1436 if (tc->text == NULL)
1437 return;
1438 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1439 if (layout) { // undo can change the text length without us knowing it
1440 layout->validateIterator(&tc->text_sel_start);
1441 layout->validateIterator(&tc->text_sel_end);
1442 }
1443 }
1445 static void
1446 sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see)
1447 {
1448 GdkRectangle im_cursor = { 0, 0, 1, 1 };
1450 // due to interruptible display, tc may already be destroyed during a display update before
1451 // the cursor update (can't do both atomically, alas)
1452 if (!tc->desktop) return;
1454 if (tc->text) {
1455 NR::Point p0, p1;
1456 sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1457 NR::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1458 NR::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1460 // scroll to show cursor
1461 if (scroll_to_see) {
1462 NR::Point const dm = (d0 + d1) / 2;
1463 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1464 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(&dm, 1.0);
1465 }
1467 sp_canvas_item_show(tc->cursor);
1468 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1470 /* fixme: ... need another transformation to get canvas widget coordinate space? */
1471 im_cursor.x = (int) floor(d0[NR::X]);
1472 im_cursor.y = (int) floor(d0[NR::Y]);
1473 im_cursor.width = (int) floor(d1[NR::X]) - im_cursor.x;
1474 im_cursor.height = (int) floor(d1[NR::Y]) - im_cursor.y;
1476 tc->show = TRUE;
1477 tc->phase = 1;
1479 if (SP_IS_FLOWTEXT(tc->text)) {
1480 SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1481 if (frame) {
1482 sp_canvas_item_show(tc->frame);
1483 NR::Maybe<NR::Rect> frame_bbox = sp_item_bbox_desktop(frame);
1484 if (frame_bbox) {
1485 SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
1486 }
1487 }
1488 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1489 } else {
1490 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1491 }
1493 } else {
1494 sp_canvas_item_hide(tc->cursor);
1495 sp_canvas_item_hide(tc->frame);
1496 tc->show = FALSE;
1497 if (!tc->nascent_object) {
1498 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
1499 }
1500 }
1502 if (tc->imc) {
1503 gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1504 }
1505 SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1506 }
1508 static void sp_text_context_update_text_selection(SPTextContext *tc)
1509 {
1510 // due to interruptible display, tc may already be destroyed during a display update before
1511 // the selection update (can't do both atomically, alas)
1512 if (!tc->desktop) return;
1514 for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1515 sp_canvas_item_hide(*it);
1516 gtk_object_destroy(*it);
1517 }
1518 tc->text_selection_quads.clear();
1520 std::vector<NR::Point> quads;
1521 if (tc->text != NULL)
1522 quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1523 for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1524 SPCanvasItem *quad_canvasitem;
1525 quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1526 sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x000000ff);
1527 sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1528 sp_canvas_item_show(quad_canvasitem);
1529 tc->text_selection_quads.push_back(quad_canvasitem);
1530 }
1531 }
1533 static gint
1534 sp_text_context_timeout(SPTextContext *tc)
1535 {
1536 if (tc->show) {
1537 if (tc->phase) {
1538 tc->phase = 0;
1539 sp_canvas_item_hide(tc->cursor);
1540 } else {
1541 tc->phase = 1;
1542 sp_canvas_item_show(tc->cursor);
1543 }
1544 }
1546 return TRUE;
1547 }
1549 static void
1550 sp_text_context_forget_text(SPTextContext *tc)
1551 {
1552 if (! tc->text) return;
1553 SPItem *ti = tc->text;
1554 /* We have to set it to zero,
1555 * or selection changed signal messes everything up */
1556 tc->text = NULL;
1557 if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1558 Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1559 // the repr may already have been unparented
1560 // if we were called e.g. as the result of
1561 // an undo or the element being removed from
1562 // the XML editor
1563 if ( text_repr && sp_repr_parent(text_repr) ) {
1564 sp_repr_unparent(text_repr);
1565 sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1566 _("Remove empty text"));
1567 }
1568 }
1569 }
1571 gint
1572 sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1573 {
1574 gtk_im_context_focus_in(tc->imc);
1575 return FALSE;
1576 }
1578 gint
1579 sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
1580 {
1581 gtk_im_context_focus_out(tc->imc);
1582 return FALSE;
1583 }
1585 static void
1586 sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc)
1587 {
1588 if (!tc->text) {
1589 sp_text_context_setup_text(tc);
1590 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1591 }
1593 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1594 sp_text_context_update_cursor(tc);
1595 sp_text_context_update_text_selection(tc);
1597 sp_document_done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT,
1598 _("Type text"));
1599 }
1602 /*
1603 Local Variables:
1604 mode:c++
1605 c-file-style:"stroustrup"
1606 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1607 indent-tabs-mode:nil
1608 fill-column:99
1609 End:
1610 */
1611 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :