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 "preferences.h"
48 #include "rubberband.h"
49 #include "sp-metrics.h"
50 #include "context-fns.h"
51 #include "verbs.h"
52 #include "shape-editor.h"
54 #include "text-editing.h"
56 #include "text-context.h"
59 static void sp_text_context_class_init(SPTextContextClass *klass);
60 static void sp_text_context_init(SPTextContext *text_context);
61 static void sp_text_context_dispose(GObject *obj);
63 static void sp_text_context_setup(SPEventContext *ec);
64 static void sp_text_context_finish(SPEventContext *ec);
65 static gint sp_text_context_root_handler(SPEventContext *event_context, GdkEvent *event);
66 static gint sp_text_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
68 static void sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc);
69 static void sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc);
70 static bool sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc);
71 static int sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc);
73 static void sp_text_context_validate_cursor_iterators(SPTextContext *tc);
74 static void sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see = true);
75 static void sp_text_context_update_text_selection(SPTextContext *tc);
76 static gint sp_text_context_timeout(SPTextContext *tc);
77 static void sp_text_context_forget_text(SPTextContext *tc);
79 static gint sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc);
80 static gint sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc);
81 static void sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc);
83 static SPEventContextClass *parent_class;
85 GType
86 sp_text_context_get_type()
87 {
88 static GType type = 0;
89 if (!type) {
90 GTypeInfo info = {
91 sizeof(SPTextContextClass),
92 NULL, NULL,
93 (GClassInitFunc) sp_text_context_class_init,
94 NULL, NULL,
95 sizeof(SPTextContext),
96 4,
97 (GInstanceInitFunc) sp_text_context_init,
98 NULL, /* value_table */
99 };
100 type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPTextContext", &info, (GTypeFlags)0);
101 }
102 return type;
103 }
105 static void
106 sp_text_context_class_init(SPTextContextClass *klass)
107 {
108 GObjectClass *object_class=(GObjectClass *)klass;
109 SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
111 parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
113 object_class->dispose = sp_text_context_dispose;
115 event_context_class->setup = sp_text_context_setup;
116 event_context_class->finish = sp_text_context_finish;
117 event_context_class->root_handler = sp_text_context_root_handler;
118 event_context_class->item_handler = sp_text_context_item_handler;
119 }
121 static void
122 sp_text_context_init(SPTextContext *tc)
123 {
124 SPEventContext *event_context = SP_EVENT_CONTEXT(tc);
126 event_context->cursor_shape = cursor_text_xpm;
127 event_context->hot_x = 7;
128 event_context->hot_y = 7;
130 event_context->xp = 0;
131 event_context->yp = 0;
132 event_context->tolerance = 0;
133 event_context->within_tolerance = false;
135 tc->imc = NULL;
137 tc->text = NULL;
138 tc->pdoc = Geom::Point(0, 0);
139 new (&tc->text_sel_start) Inkscape::Text::Layout::iterator();
140 new (&tc->text_sel_end) Inkscape::Text::Layout::iterator();
141 new (&tc->text_selection_quads) std::vector<SPCanvasItem*>();
143 tc->unimode = false;
145 tc->cursor = NULL;
146 tc->indicator = NULL;
147 tc->frame = NULL;
148 tc->grabbed = NULL;
149 tc->timeout = 0;
150 tc->show = FALSE;
151 tc->phase = 0;
152 tc->nascent_object = 0;
153 tc->over_text = 0;
154 tc->dragging = 0;
155 tc->creating = 0;
157 new (&tc->sel_changed_connection) sigc::connection();
158 new (&tc->sel_modified_connection) sigc::connection();
159 new (&tc->style_set_connection) sigc::connection();
160 new (&tc->style_query_connection) sigc::connection();
161 }
163 static void
164 sp_text_context_dispose(GObject *obj)
165 {
166 SPTextContext *tc = SP_TEXT_CONTEXT(obj);
167 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
168 tc->style_query_connection.~connection();
169 tc->style_set_connection.~connection();
170 tc->sel_changed_connection.~connection();
171 tc->sel_modified_connection.~connection();
173 delete ec->shape_editor;
174 ec->shape_editor = NULL;
176 tc->text_sel_end.~iterator();
177 tc->text_sel_start.~iterator();
178 tc->text_selection_quads.~vector();
179 if (G_OBJECT_CLASS(parent_class)->dispose) {
180 G_OBJECT_CLASS(parent_class)->dispose(obj);
181 }
182 if (tc->grabbed) {
183 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
184 tc->grabbed = NULL;
185 }
187 Inkscape::Rubberband::get(ec->desktop)->stop();
188 }
190 static void
191 sp_text_context_setup(SPEventContext *ec)
192 {
193 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
194 SPDesktop *desktop = ec->desktop;
195 GtkSettings* settings = gtk_settings_get_default();
196 gint timeout = 0;
197 g_object_get( settings, "gtk-cursor-blink-time", &timeout, NULL );
198 if (timeout < 0) {
199 timeout = 200;
200 } else {
201 timeout /= 2;
202 }
204 tc->cursor = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLLINE, NULL);
205 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), 100, 0, 100, 100);
206 sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
207 sp_canvas_item_hide(tc->cursor);
209 tc->indicator = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
210 SP_CTRLRECT(tc->indicator)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100)));
211 SP_CTRLRECT(tc->indicator)->setColor(0x0000ff7f, false, 0);
212 sp_canvas_item_hide(tc->indicator);
214 tc->frame = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
215 SP_CTRLRECT(tc->frame)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100)));
216 SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0);
217 sp_canvas_item_hide(tc->frame);
219 tc->timeout = gtk_timeout_add(timeout, (GtkFunction) sp_text_context_timeout, ec);
221 tc->imc = gtk_im_multicontext_new();
222 if (tc->imc) {
223 GtkWidget *canvas = GTK_WIDGET(sp_desktop_canvas(desktop));
225 /* im preedit handling is very broken in inkscape for
226 * multi-byte characters. See bug 1086769.
227 * We need to let the IM handle the preediting, and
228 * just take in the characters when they're finished being
229 * entered.
230 */
231 gtk_im_context_set_use_preedit(tc->imc, FALSE);
232 gtk_im_context_set_client_window(tc->imc, canvas->window);
234 g_signal_connect(G_OBJECT(canvas), "focus_in_event", G_CALLBACK(sptc_focus_in), tc);
235 g_signal_connect(G_OBJECT(canvas), "focus_out_event", G_CALLBACK(sptc_focus_out), tc);
236 g_signal_connect(G_OBJECT(tc->imc), "commit", G_CALLBACK(sptc_commit), tc);
238 if (GTK_WIDGET_HAS_FOCUS(canvas)) {
239 sptc_focus_in(canvas, NULL, tc);
240 }
241 }
243 if (((SPEventContextClass *) parent_class)->setup)
244 ((SPEventContextClass *) parent_class)->setup(ec);
246 ec->shape_editor = new ShapeEditor(ec->desktop);
248 SPItem *item = sp_desktop_selection(ec->desktop)->singleItem();
249 if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
250 ec->shape_editor->set_item(item, SH_KNOTHOLDER);
251 }
253 tc->sel_changed_connection = sp_desktop_selection(desktop)->connectChanged(
254 sigc::bind(sigc::ptr_fun(&sp_text_context_selection_changed), tc)
255 );
256 tc->sel_modified_connection = sp_desktop_selection(desktop)->connectModified(
257 sigc::bind(sigc::ptr_fun(&sp_text_context_selection_modified), tc)
258 );
259 tc->style_set_connection = desktop->connectSetStyle(
260 sigc::bind(sigc::ptr_fun(&sp_text_context_style_set), tc)
261 );
262 tc->style_query_connection = desktop->connectQueryStyle(
263 sigc::bind(sigc::ptr_fun(&sp_text_context_style_query), tc)
264 );
266 sp_text_context_selection_changed(sp_desktop_selection(desktop), tc);
268 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
269 if (prefs->getBool("/tools/text/selcue")) {
270 ec->enableSelectionCue();
271 }
272 if (prefs->getBool("/tools/text/gradientdrag")) {
273 ec->enableGrDrag();
274 }
275 }
277 static void
278 sp_text_context_finish(SPEventContext *ec)
279 {
280 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
282 if (ec->desktop) {
283 sp_signal_disconnect_by_data(sp_desktop_canvas(ec->desktop), tc);
284 }
286 ec->enableGrDrag(false);
288 tc->style_set_connection.disconnect();
289 tc->style_query_connection.disconnect();
290 tc->sel_changed_connection.disconnect();
291 tc->sel_modified_connection.disconnect();
293 sp_text_context_forget_text(SP_TEXT_CONTEXT(ec));
295 if (tc->imc) {
296 g_object_unref(G_OBJECT(tc->imc));
297 tc->imc = NULL;
298 }
300 if (tc->timeout) {
301 gtk_timeout_remove(tc->timeout);
302 tc->timeout = 0;
303 }
305 if (tc->cursor) {
306 gtk_object_destroy(GTK_OBJECT(tc->cursor));
307 tc->cursor = NULL;
308 }
310 if (tc->indicator) {
311 gtk_object_destroy(GTK_OBJECT(tc->indicator));
312 tc->indicator = NULL;
313 }
315 if (tc->frame) {
316 gtk_object_destroy(GTK_OBJECT(tc->frame));
317 tc->frame = NULL;
318 }
320 for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ;
321 it != tc->text_selection_quads.end() ; ++it) {
322 sp_canvas_item_hide(*it);
323 gtk_object_destroy(*it);
324 }
325 tc->text_selection_quads.clear();
326 }
329 static gint
330 sp_text_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
331 {
332 SPTextContext *tc = SP_TEXT_CONTEXT(event_context);
333 SPDesktop *desktop = event_context->desktop;
334 SPItem *item_ungrouped;
336 gint ret = FALSE;
338 sp_text_context_validate_cursor_iterators(tc);
340 switch (event->type) {
341 case GDK_BUTTON_PRESS:
342 if (event->button.button == 1 && !event_context->space_panning) {
343 // find out clicked item, disregarding groups
344 item_ungrouped = desktop->item_at_point(Geom::Point(event->button.x, event->button.y), TRUE);
345 if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
346 sp_desktop_selection(desktop)->set(item_ungrouped);
347 if (tc->text) {
348 // find out click point in document coordinates
349 Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
350 // set the cursor closest to that point
351 tc->text_sel_start = tc->text_sel_end = sp_te_get_position_by_coords(tc->text, p);
352 // update display
353 sp_text_context_update_cursor(tc);
354 sp_text_context_update_text_selection(tc);
355 tc->dragging = 1;
356 sp_canvas_set_snap_delay_active(desktop->canvas, true);
357 }
358 ret = TRUE;
359 }
360 }
361 break;
362 case GDK_2BUTTON_PRESS:
363 if (event->button.button == 1 && tc->text) {
364 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
365 if (layout) {
366 if (!layout->isStartOfWord(tc->text_sel_start))
367 tc->text_sel_start.prevStartOfWord();
368 if (!layout->isEndOfWord(tc->text_sel_end))
369 tc->text_sel_end.nextEndOfWord();
370 sp_text_context_update_cursor(tc);
371 sp_text_context_update_text_selection(tc);
372 tc->dragging = 2;
373 sp_canvas_set_snap_delay_active(desktop->canvas, true);
374 ret = TRUE;
375 }
376 }
377 break;
378 case GDK_3BUTTON_PRESS:
379 if (event->button.button == 1 && tc->text) {
380 tc->text_sel_start.thisStartOfLine();
381 tc->text_sel_end.thisEndOfLine();
382 sp_text_context_update_cursor(tc);
383 sp_text_context_update_text_selection(tc);
384 tc->dragging = 3;
385 sp_canvas_set_snap_delay_active(desktop->canvas, true);
386 ret = TRUE;
387 }
388 break;
389 case GDK_BUTTON_RELEASE:
390 if (event->button.button == 1 && tc->dragging && !event_context->space_panning) {
391 tc->dragging = 0;
392 sp_canvas_set_snap_delay_active(desktop->canvas, false);
393 ret = TRUE;
394 }
395 break;
396 case GDK_MOTION_NOTIFY:
397 if (event->motion.state & GDK_BUTTON1_MASK && tc->dragging && !event_context->space_panning) {
398 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
399 if (!layout) break;
400 // find out click point in document coordinates
401 Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
402 // set the cursor closest to that point
403 Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(tc->text, p);
404 if (tc->dragging == 2) {
405 // double-click dragging: go by word
406 if (new_end < tc->text_sel_start) {
407 if (!layout->isStartOfWord(new_end))
408 new_end.prevStartOfWord();
409 } else
410 if (!layout->isEndOfWord(new_end))
411 new_end.nextEndOfWord();
412 } else if (tc->dragging == 3) {
413 // triple-click dragging: go by line
414 if (new_end < tc->text_sel_start)
415 new_end.thisStartOfLine();
416 else
417 new_end.thisEndOfLine();
418 }
419 // update display
420 if (tc->text_sel_end != new_end) {
421 tc->text_sel_end = new_end;
422 sp_text_context_update_cursor(tc);
423 sp_text_context_update_text_selection(tc);
424 }
425 gobble_motion_events(GDK_BUTTON1_MASK);
426 ret = TRUE;
427 break;
428 }
429 // find out item under mouse, disregarding groups
430 item_ungrouped = desktop->item_at_point(Geom::Point(event->button.x, event->button.y), TRUE);
431 if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
432 sp_canvas_item_show(tc->indicator);
433 Geom::OptRect ibbox = sp_item_bbox_desktop(item_ungrouped);
434 if (ibbox) {
435 SP_CTRLRECT(tc->indicator)->setRectangle(*ibbox);
436 }
438 event_context->cursor_shape = cursor_text_insert_xpm;
439 event_context->hot_x = 7;
440 event_context->hot_y = 10;
441 sp_event_context_update_cursor(event_context);
442 sp_text_context_update_text_selection(tc);
444 if (SP_IS_TEXT (item_ungrouped)) {
445 desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
446 } else {
447 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."));
448 }
450 tc->over_text = true;
452 ret = TRUE;
453 }
454 break;
455 default:
456 break;
457 }
459 if (!ret) {
460 if (((SPEventContextClass *) parent_class)->item_handler)
461 ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
462 }
464 return ret;
465 }
467 static void
468 sp_text_context_setup_text(SPTextContext *tc)
469 {
470 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
472 /* Create <text> */
473 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_EVENT_CONTEXT_DESKTOP(ec)->doc());
474 Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
475 rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
477 /* Set style */
478 sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "/tools/text", true);
480 sp_repr_set_svg_double(rtext, "x", tc->pdoc[Geom::X]);
481 sp_repr_set_svg_double(rtext, "y", tc->pdoc[Geom::Y]);
483 /* Create <tspan> */
484 Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
485 rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
486 rtext->addChild(rtspan, NULL);
487 Inkscape::GC::release(rtspan);
489 /* Create TEXT */
490 Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
491 rtspan->addChild(rstring, NULL);
492 Inkscape::GC::release(rstring);
493 SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
494 /* fixme: Is selection::changed really immediate? */
495 /* yes, it's immediate .. why does it matter? */
496 sp_desktop_selection(ec->desktop)->set(text_item);
497 Inkscape::GC::release(rtext);
498 text_item->transform = sp_item_i2doc_affine(SP_ITEM(ec->desktop->currentLayer())).inverse();
500 text_item->updateRepr();
501 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
502 _("Create text"));
503 }
505 /**
506 * Insert the character indicated by tc.uni to replace the current selection,
507 * and reset tc.uni/tc.unipos to empty string.
508 *
509 * \pre tc.uni/tc.unipos non-empty.
510 */
511 static void
512 insert_uni_char(SPTextContext *const tc)
513 {
514 g_return_if_fail(tc->unipos
515 && tc->unipos < sizeof(tc->uni)
516 && tc->uni[tc->unipos] == '\0');
517 unsigned int uv;
518 sscanf(tc->uni, "%x", &uv);
519 tc->unipos = 0;
520 tc->uni[tc->unipos] = '\0';
522 if ( !g_unichar_isprint(static_cast<gunichar>(uv))
523 && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
524 // This may be due to bad input, so it goes to statusbar.
525 tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
526 _("Non-printable character"));
527 } else {
528 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
529 sp_text_context_setup_text(tc);
530 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
531 }
533 gchar u[10];
534 guint const len = g_unichar_to_utf8(uv, u);
535 u[len] = '\0';
537 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
538 sp_text_context_update_cursor(tc);
539 sp_text_context_update_text_selection(tc);
540 sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM,
541 _("Insert Unicode character"));
542 }
543 }
545 static void
546 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
547 {
548 unsigned int uv;
549 sscanf(hex, "%x", &uv);
550 if (!g_unichar_isprint((gunichar) uv)) {
551 uv = 0xfffd;
552 }
553 guint const len = g_unichar_to_utf8(uv, utf8);
554 utf8[len] = '\0';
555 }
557 static void
558 show_curr_uni_char(SPTextContext *const tc)
559 {
560 g_return_if_fail(tc->unipos < sizeof(tc->uni)
561 && tc->uni[tc->unipos] == '\0');
562 if (tc->unipos) {
563 char utf8[10];
564 hex_to_printable_utf8_buf(tc->uni, utf8);
566 /* Status bar messages are in pango markup, so we need xml escaping. */
567 if (utf8[1] == '\0') {
568 switch(utf8[0]) {
569 case '<': strcpy(utf8, "<"); break;
570 case '>': strcpy(utf8, ">"); break;
571 case '&': strcpy(utf8, "&"); break;
572 default: break;
573 }
574 }
575 tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
576 _("Unicode (<b>Enter</b> to finish): %s: %s"), tc->uni, utf8);
577 } else {
578 tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
579 }
580 }
582 static gint
583 sp_text_context_root_handler(SPEventContext *const event_context, GdkEvent *const event)
584 {
585 SPTextContext *const tc = SP_TEXT_CONTEXT(event_context);
587 SPDesktop *desktop = event_context->desktop;
589 sp_canvas_item_hide(tc->indicator);
591 sp_text_context_validate_cursor_iterators(tc);
593 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
594 event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
596 switch (event->type) {
597 case GDK_BUTTON_PRESS:
598 if (event->button.button == 1 && !event_context->space_panning) {
600 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
601 return TRUE;
602 }
604 // save drag origin
605 event_context->xp = (gint) event->button.x;
606 event_context->yp = (gint) event->button.y;
607 event_context->within_tolerance = true;
609 Geom::Point const button_pt(event->button.x, event->button.y);
610 tc->p0 = desktop->w2d(button_pt);
611 Inkscape::Rubberband::get(desktop)->start(desktop, tc->p0);
612 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
613 GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
614 NULL, event->button.time);
615 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
616 tc->creating = 1;
618 /* Processed */
619 return TRUE;
620 }
621 break;
622 case GDK_MOTION_NOTIFY:
623 if (tc->over_text) {
624 tc->over_text = 0;
625 // update cursor and statusbar: we are not over a text object now
626 event_context->cursor_shape = cursor_text_xpm;
627 event_context->hot_x = 7;
628 event_context->hot_y = 7;
629 sp_event_context_update_cursor(event_context);
630 desktop->event_context->defaultMessageContext()->clear();
631 }
633 if (tc->creating && event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
634 if ( event_context->within_tolerance
635 && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
636 && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
637 break; // do not drag if we're within tolerance from origin
638 }
639 // Once the user has moved farther than tolerance from the original location
640 // (indicating they intend to draw, not click), then always process the
641 // motion notify coordinates as given (no snapping back to origin)
642 event_context->within_tolerance = false;
644 Geom::Point const motion_pt(event->motion.x, event->motion.y);
645 Geom::Point const p = desktop->w2d(motion_pt);
647 Inkscape::Rubberband::get(desktop)->move(p);
648 gobble_motion_events(GDK_BUTTON1_MASK);
650 // status text
651 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::X]), desktop->namedview->getDefaultMetric());
652 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::Y]), desktop->namedview->getDefaultMetric());
653 event_context->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Flowed text frame</b>: %s × %s"), xs->str, ys->str);
654 g_string_free(xs, FALSE);
655 g_string_free(ys, FALSE);
657 }
658 break;
659 case GDK_BUTTON_RELEASE:
660 if (event->button.button == 1 && !event_context->space_panning) {
662 if (tc->grabbed) {
663 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
664 tc->grabbed = NULL;
665 }
667 Inkscape::Rubberband::get(desktop)->stop();
669 if (tc->creating && event_context->within_tolerance) {
670 /* Button 1, set X & Y & new item */
671 sp_desktop_selection(desktop)->clear();
672 Geom::Point dtp = desktop->w2d(Geom::Point(event->button.x, event->button.y));
673 tc->pdoc = sp_desktop_dt2doc_xy_point(desktop, dtp);
675 tc->show = TRUE;
676 tc->phase = 1;
677 tc->nascent_object = 1; // new object was just created
679 /* Cursor */
680 sp_canvas_item_show(tc->cursor);
681 // Cursor height is defined by the new text object's font size; it needs to be set
682 // articifically here, for the text object does not exist yet:
683 double cursor_height = sp_desktop_get_font_size_tool(desktop);
684 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), dtp, dtp + Geom::Point(0, cursor_height));
685 event_context->_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
687 event_context->within_tolerance = false;
688 } else if (tc->creating) {
689 Geom::Point const button_pt(event->button.x, event->button.y);
690 Geom::Point p1 = desktop->w2d(button_pt);
691 double cursor_height = sp_desktop_get_font_size_tool(desktop);
692 if (fabs(p1[Geom::Y] - tc->p0[Geom::Y]) > cursor_height) {
693 // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
694 SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
695 /* Set style */
696 sp_desktop_apply_style_tool(desktop, SP_OBJECT_REPR(ft), "/tools/text", true);
697 sp_desktop_selection(desktop)->set(ft);
698 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
699 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
700 _("Create flowed text"));
701 } else {
702 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
703 }
704 }
705 tc->creating = false;
706 return TRUE;
707 }
708 break;
709 case GDK_KEY_PRESS: {
710 guint const group0_keyval = get_group0_keyval(&event->key);
712 if (group0_keyval == GDK_KP_Add ||
713 group0_keyval == GDK_KP_Subtract) {
714 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
715 break; // otherwise pass on keypad +/- so they can zoom
716 }
718 if ((tc->text) || (tc->nascent_object)) {
719 // there is an active text object in this context, or a new object was just created
721 if (tc->unimode || !tc->imc
722 || (MOD__CTRL && MOD__SHIFT) // input methods tend to steal this for unimode,
723 // but we have our own so make sure they don't swallow it
724 || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
725 //IM did not consume the key, or we're in unimode
727 if (!MOD__CTRL_ONLY && tc->unimode) {
728 /* TODO: ISO 14755 (section 3 Definitions) says that we should also
729 accept the first 6 characters of alphabets other than the latin
730 alphabet "if the Latin alphabet is not used". The below is also
731 reasonable (viz. hope that the user's keyboard includes latin
732 characters and force latin interpretation -- just as we do for our
733 keyboard shortcuts), but differs from the ISO 14755
734 recommendation. */
735 switch (group0_keyval) {
736 case GDK_space:
737 case GDK_KP_Space: {
738 if (tc->unipos) {
739 insert_uni_char(tc);
740 }
741 /* Stay in unimode. */
742 show_curr_uni_char(tc);
743 return TRUE;
744 }
746 case GDK_BackSpace: {
747 g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
748 if (tc->unipos) {
749 tc->uni[--tc->unipos] = '\0';
750 }
751 show_curr_uni_char(tc);
752 return TRUE;
753 }
755 case GDK_Return:
756 case GDK_KP_Enter: {
757 if (tc->unipos) {
758 insert_uni_char(tc);
759 }
760 /* Exit unimode. */
761 tc->unimode = false;
762 event_context->defaultMessageContext()->clear();
763 return TRUE;
764 }
766 case GDK_Escape: {
767 // Cancel unimode.
768 tc->unimode = false;
769 gtk_im_context_reset(tc->imc);
770 event_context->defaultMessageContext()->clear();
771 return TRUE;
772 }
774 case GDK_Shift_L:
775 case GDK_Shift_R:
776 break;
778 default: {
779 if (g_ascii_isxdigit(group0_keyval)) {
780 g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
781 tc->uni[tc->unipos++] = group0_keyval;
782 tc->uni[tc->unipos] = '\0';
783 if (tc->unipos == 8) {
784 /* This behaviour is partly to allow us to continue to
785 use a fixed-length buffer for tc->uni. Reason for
786 choosing the number 8 is that it's the length of
787 ``canonical form'' mentioned in the ISO 14755 spec.
788 An advantage over choosing 6 is that it allows using
789 backspace for typos & misremembering when entering a
790 6-digit number. */
791 insert_uni_char(tc);
792 }
793 show_curr_uni_char(tc);
794 return TRUE;
795 } else {
796 /* The intent is to ignore but consume characters that could be
797 typos for hex digits. Gtk seems to ignore & consume all
798 non-hex-digits, and we do similar here. Though note that some
799 shortcuts (like keypad +/- for zoom) get processed before
800 reaching this code. */
801 return TRUE;
802 }
803 }
804 }
805 }
807 Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
808 Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
809 bool cursor_moved = false;
810 int screenlines = 1;
811 if (tc->text) {
812 double spacing = sp_te_get_average_linespacing(tc->text);
813 Geom::Rect const d = desktop->get_display_area();
814 screenlines = (int) floor(fabs(d.min()[Geom::Y] - d.max()[Geom::Y])/spacing) - 1;
815 if (screenlines <= 0)
816 screenlines = 1;
817 }
819 /* Neither unimode nor IM consumed key; process text tool shortcuts */
820 switch (group0_keyval) {
821 case GDK_x:
822 case GDK_X:
823 if (MOD__ALT_ONLY) {
824 desktop->setToolboxFocusTo ("altx-text");
825 return TRUE;
826 }
827 break;
828 case GDK_space:
829 if (MOD__CTRL_ONLY) {
830 /* No-break space */
831 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
832 sp_text_context_setup_text(tc);
833 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
834 }
835 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
836 sp_text_context_update_cursor(tc);
837 sp_text_context_update_text_selection(tc);
838 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
839 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
840 _("Insert no-break space"));
841 return TRUE;
842 }
843 break;
844 case GDK_U:
845 case GDK_u:
846 if (MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT)) {
847 if (tc->unimode) {
848 tc->unimode = false;
849 event_context->defaultMessageContext()->clear();
850 } else {
851 tc->unimode = true;
852 tc->unipos = 0;
853 event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
854 }
855 if (tc->imc) {
856 gtk_im_context_reset(tc->imc);
857 }
858 return TRUE;
859 }
860 break;
861 case GDK_B:
862 case GDK_b:
863 if (MOD__CTRL_ONLY && tc->text) {
864 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
865 SPCSSAttr *css = sp_repr_css_attr_new();
866 if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
867 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
868 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
869 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
870 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
871 sp_repr_css_set_property(css, "font-weight", "bold");
872 else
873 sp_repr_css_set_property(css, "font-weight", "normal");
874 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
875 sp_repr_css_attr_unref(css);
876 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
877 _("Make bold"));
878 sp_text_context_update_cursor(tc);
879 sp_text_context_update_text_selection(tc);
880 return TRUE;
881 }
882 break;
883 case GDK_I:
884 case GDK_i:
885 if (MOD__CTRL_ONLY && tc->text) {
886 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
887 SPCSSAttr *css = sp_repr_css_attr_new();
888 if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL)
889 sp_repr_css_set_property(css, "font-style", "italic");
890 else
891 sp_repr_css_set_property(css, "font-style", "normal");
892 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
893 sp_repr_css_attr_unref(css);
894 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
895 _("Make italic"));
896 sp_text_context_update_cursor(tc);
897 sp_text_context_update_text_selection(tc);
898 return TRUE;
899 }
900 break;
902 case GDK_A:
903 case GDK_a:
904 if (MOD__CTRL_ONLY && tc->text) {
905 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
906 if (layout) {
907 tc->text_sel_start = layout->begin();
908 tc->text_sel_end = layout->end();
909 sp_text_context_update_cursor(tc);
910 sp_text_context_update_text_selection(tc);
911 return TRUE;
912 }
913 }
914 break;
916 case GDK_Return:
917 case GDK_KP_Enter:
918 {
919 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
920 sp_text_context_setup_text(tc);
921 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
922 }
924 iterator_pair enter_pair;
925 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, enter_pair);
926 (void)success; // TODO cleanup
927 tc->text_sel_start = tc->text_sel_end = enter_pair.first;
929 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
931 sp_text_context_update_cursor(tc);
932 sp_text_context_update_text_selection(tc);
933 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
934 _("New line"));
935 return TRUE;
936 }
937 case GDK_BackSpace:
938 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
940 bool noSelection = false;
942 if (tc->text_sel_start == tc->text_sel_end) {
943 tc->text_sel_start.prevCursorPosition();
944 noSelection = true;
945 }
947 iterator_pair bspace_pair;
948 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, bspace_pair);
950 if (noSelection) {
951 if (success) {
952 tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
953 } else { // nothing deleted
954 tc->text_sel_start = tc->text_sel_end = bspace_pair.second;
955 }
956 } else {
957 if (success) {
958 tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
959 } else { // nothing deleted
960 tc->text_sel_start = bspace_pair.first;
961 tc->text_sel_end = bspace_pair.second;
962 }
963 }
965 sp_text_context_update_cursor(tc);
966 sp_text_context_update_text_selection(tc);
967 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
968 _("Backspace"));
969 }
970 return TRUE;
971 case GDK_Delete:
972 case GDK_KP_Delete:
973 if (tc->text) {
974 bool noSelection = false;
976 if (tc->text_sel_start == tc->text_sel_end) {
977 tc->text_sel_end.nextCursorPosition();
978 noSelection = true;
979 }
981 iterator_pair del_pair;
982 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, del_pair);
984 if (noSelection) {
985 tc->text_sel_start = tc->text_sel_end = del_pair.first;
986 } else {
987 if (success) {
988 tc->text_sel_start = tc->text_sel_end = del_pair.first;
989 } else { // nothing deleted
990 tc->text_sel_start = del_pair.first;
991 tc->text_sel_end = del_pair.second;
992 }
993 }
996 sp_text_context_update_cursor(tc);
997 sp_text_context_update_text_selection(tc);
998 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
999 _("Delete"));
1000 }
1001 return TRUE;
1002 case GDK_Left:
1003 case GDK_KP_Left:
1004 case GDK_KP_4:
1005 if (tc->text) {
1006 if (MOD__ALT) {
1007 gint mul = 1 + gobble_key_events(
1008 get_group0_keyval(&event->key), 0); // with any mask
1009 if (MOD__SHIFT)
1010 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-10, 0));
1011 else
1012 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-1, 0));
1013 sp_text_context_update_cursor(tc);
1014 sp_text_context_update_text_selection(tc);
1015 sp_document_maybe_done(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT,
1016 _("Kern to the left"));
1017 } else {
1018 if (MOD__CTRL)
1019 tc->text_sel_end.cursorLeftWithControl();
1020 else
1021 tc->text_sel_end.cursorLeft();
1022 cursor_moved = true;
1023 break;
1024 }
1025 }
1026 return TRUE;
1027 case GDK_Right:
1028 case GDK_KP_Right:
1029 case GDK_KP_6:
1030 if (tc->text) {
1031 if (MOD__ALT) {
1032 gint mul = 1 + gobble_key_events(
1033 get_group0_keyval(&event->key), 0); // with any mask
1034 if (MOD__SHIFT)
1035 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*10, 0));
1036 else
1037 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*1, 0));
1038 sp_text_context_update_cursor(tc);
1039 sp_text_context_update_text_selection(tc);
1040 sp_document_maybe_done(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT,
1041 _("Kern to the right"));
1042 } else {
1043 if (MOD__CTRL)
1044 tc->text_sel_end.cursorRightWithControl();
1045 else
1046 tc->text_sel_end.cursorRight();
1047 cursor_moved = true;
1048 break;
1049 }
1050 }
1051 return TRUE;
1052 case GDK_Up:
1053 case GDK_KP_Up:
1054 case GDK_KP_8:
1055 if (tc->text) {
1056 if (MOD__ALT) {
1057 gint mul = 1 + gobble_key_events(
1058 get_group0_keyval(&event->key), 0); // with any mask
1059 if (MOD__SHIFT)
1060 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-10));
1061 else
1062 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-1));
1063 sp_text_context_update_cursor(tc);
1064 sp_text_context_update_text_selection(tc);
1065 sp_document_maybe_done(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT,
1066 _("Kern up"));
1068 } else {
1069 if (MOD__CTRL)
1070 tc->text_sel_end.cursorUpWithControl();
1071 else
1072 tc->text_sel_end.cursorUp();
1073 cursor_moved = true;
1074 break;
1075 }
1076 }
1077 return TRUE;
1078 case GDK_Down:
1079 case GDK_KP_Down:
1080 case GDK_KP_2:
1081 if (tc->text) {
1082 if (MOD__ALT) {
1083 gint mul = 1 + gobble_key_events(
1084 get_group0_keyval(&event->key), 0); // with any mask
1085 if (MOD__SHIFT)
1086 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*10));
1087 else
1088 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*1));
1089 sp_text_context_update_cursor(tc);
1090 sp_text_context_update_text_selection(tc);
1091 sp_document_maybe_done(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT,
1092 _("Kern down"));
1094 } else {
1095 if (MOD__CTRL)
1096 tc->text_sel_end.cursorDownWithControl();
1097 else
1098 tc->text_sel_end.cursorDown();
1099 cursor_moved = true;
1100 break;
1101 }
1102 }
1103 return TRUE;
1104 case GDK_Home:
1105 case GDK_KP_Home:
1106 if (tc->text) {
1107 if (MOD__CTRL)
1108 tc->text_sel_end.thisStartOfShape();
1109 else
1110 tc->text_sel_end.thisStartOfLine();
1111 cursor_moved = true;
1112 break;
1113 }
1114 return TRUE;
1115 case GDK_End:
1116 case GDK_KP_End:
1117 if (tc->text) {
1118 if (MOD__CTRL)
1119 tc->text_sel_end.nextStartOfShape();
1120 else
1121 tc->text_sel_end.thisEndOfLine();
1122 cursor_moved = true;
1123 break;
1124 }
1125 return TRUE;
1126 case GDK_Page_Down:
1127 case GDK_KP_Page_Down:
1128 if (tc->text) {
1129 tc->text_sel_end.cursorDown(screenlines);
1130 cursor_moved = true;
1131 break;
1132 }
1133 return TRUE;
1134 case GDK_Page_Up:
1135 case GDK_KP_Page_Up:
1136 if (tc->text) {
1137 tc->text_sel_end.cursorUp(screenlines);
1138 cursor_moved = true;
1139 break;
1140 }
1141 return TRUE;
1142 case GDK_Escape:
1143 if (tc->creating) {
1144 tc->creating = 0;
1145 if (tc->grabbed) {
1146 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1147 tc->grabbed = NULL;
1148 }
1149 Inkscape::Rubberband::get(desktop)->stop();
1150 } else {
1151 sp_desktop_selection(desktop)->clear();
1152 }
1153 tc->nascent_object = FALSE;
1154 return TRUE;
1155 case GDK_bracketleft:
1156 if (tc->text) {
1157 if (MOD__ALT || MOD__CTRL) {
1158 if (MOD__ALT) {
1159 if (MOD__SHIFT) {
1160 // FIXME: alt+shift+[] does not work, don't know why
1161 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1162 } else {
1163 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1164 }
1165 } else {
1166 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -90);
1167 }
1168 sp_document_maybe_done(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT,
1169 _("Rotate counterclockwise"));
1170 sp_text_context_update_cursor(tc);
1171 sp_text_context_update_text_selection(tc);
1172 return TRUE;
1173 }
1174 }
1175 break;
1176 case GDK_bracketright:
1177 if (tc->text) {
1178 if (MOD__ALT || MOD__CTRL) {
1179 if (MOD__ALT) {
1180 if (MOD__SHIFT) {
1181 // FIXME: alt+shift+[] does not work, don't know why
1182 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1183 } else {
1184 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1185 }
1186 } else {
1187 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 90);
1188 }
1189 sp_document_maybe_done(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT,
1190 _("Rotate clockwise"));
1191 sp_text_context_update_cursor(tc);
1192 sp_text_context_update_text_selection(tc);
1193 return TRUE;
1194 }
1195 }
1196 break;
1197 case GDK_less:
1198 case GDK_comma:
1199 if (tc->text) {
1200 if (MOD__ALT) {
1201 if (MOD__CTRL) {
1202 if (MOD__SHIFT)
1203 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1204 else
1205 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1206 sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT,
1207 _("Contract line spacing"));
1209 } else {
1210 if (MOD__SHIFT)
1211 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1212 else
1213 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1214 sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT,
1215 _("Contract letter spacing"));
1217 }
1218 sp_text_context_update_cursor(tc);
1219 sp_text_context_update_text_selection(tc);
1220 return TRUE;
1221 }
1222 }
1223 break;
1224 case GDK_greater:
1225 case GDK_period:
1226 if (tc->text) {
1227 if (MOD__ALT) {
1228 if (MOD__CTRL) {
1229 if (MOD__SHIFT)
1230 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1231 else
1232 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1233 sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT,
1234 _("Expand line spacing"));
1236 } else {
1237 if (MOD__SHIFT)
1238 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1239 else
1240 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1241 sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT,
1242 _("Expand letter spacing"));
1244 }
1245 sp_text_context_update_cursor(tc);
1246 sp_text_context_update_text_selection(tc);
1247 return TRUE;
1248 }
1249 }
1250 break;
1251 default:
1252 break;
1253 }
1255 if (cursor_moved) {
1256 if (!MOD__SHIFT)
1257 tc->text_sel_start = tc->text_sel_end;
1258 if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1259 sp_text_context_update_cursor(tc);
1260 sp_text_context_update_text_selection(tc);
1261 }
1262 return TRUE;
1263 }
1265 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1266 } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1267 // except up/down that are swallowed to prevent the zoom field from activation
1268 if ((group0_keyval == GDK_Up ||
1269 group0_keyval == GDK_Down ||
1270 group0_keyval == GDK_KP_Up ||
1271 group0_keyval == GDK_KP_Down )
1272 && !MOD__CTRL_ONLY) {
1273 return TRUE;
1274 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1275 if (tc->creating) {
1276 tc->creating = 0;
1277 if (tc->grabbed) {
1278 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1279 tc->grabbed = NULL;
1280 }
1281 Inkscape::Rubberband::get(desktop)->stop();
1282 }
1283 }
1284 }
1285 break;
1286 }
1288 case GDK_KEY_RELEASE:
1289 if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1290 return TRUE;
1291 }
1292 break;
1293 default:
1294 break;
1295 }
1297 // if nobody consumed it so far
1298 if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1299 return ((SPEventContextClass *) parent_class)->root_handler(event_context, event); // send event to parent
1300 } else {
1301 return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1302 }
1303 }
1305 /**
1306 Attempts to paste system clipboard into the currently edited text, returns true on success
1307 */
1308 bool
1309 sp_text_paste_inline(SPEventContext *ec)
1310 {
1311 if (!SP_IS_TEXT_CONTEXT(ec))
1312 return false;
1314 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1316 if ((tc->text) || (tc->nascent_object)) {
1317 // there is an active text object in this context, or a new object was just created
1319 Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1320 Glib::ustring const clip_text = refClipboard->wait_for_text();
1322 if (!clip_text.empty()) {
1323 // Fix for 244940
1324 // The XML standard defines the following as valid characters
1325 // (Extensible Markup Language (XML) 1.0 (Fourth Edition) paragraph 2.2)
1326 // char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
1327 // Since what comes in off the paste buffer will go right into XML, clean
1328 // the text here.
1329 Glib::ustring text(clip_text);
1330 Glib::ustring::iterator itr = text.begin();
1331 gunichar paste_string_uchar;
1333 while(itr != text.end())
1334 {
1335 paste_string_uchar = *itr;
1337 // Make sure we don't have a control character. We should really check
1338 // for the whole range above... Add the rest of the invalid cases from
1339 // above if we find additional issues
1340 if(paste_string_uchar >= 0x00000020 ||
1341 paste_string_uchar == 0x00000009 ||
1342 paste_string_uchar == 0x0000000A ||
1343 paste_string_uchar == 0x0000000D) {
1344 itr++;
1345 } else {
1346 itr = text.erase(itr);
1347 }
1348 }
1350 if (!tc->text) { // create text if none (i.e. if nascent_object)
1351 sp_text_context_setup_text(tc);
1352 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1353 }
1355 // using indices is slow in ustrings. Whatever.
1356 Glib::ustring::size_type begin = 0;
1357 for ( ; ; ) {
1358 Glib::ustring::size_type end = text.find('\n', begin);
1359 if (end == Glib::ustring::npos) {
1360 if (begin != text.length())
1361 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());
1362 break;
1363 }
1364 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());
1365 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1366 begin = end + 1;
1367 }
1368 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
1369 _("Paste text"));
1371 return true;
1372 }
1373 } // FIXME: else create and select a new object under cursor!
1375 return false;
1376 }
1378 /**
1379 Gets the raw characters that comprise the currently selected text, converting line
1380 breaks into lf characters.
1381 */
1382 Glib::ustring
1383 sp_text_get_selected_text(SPEventContext const *ec)
1384 {
1385 if (!SP_IS_TEXT_CONTEXT(ec))
1386 return "";
1387 SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1388 if (tc->text == NULL)
1389 return "";
1391 return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1392 }
1394 /**
1395 Deletes the currently selected characters. Returns false if there is no
1396 text selection currently.
1397 */
1398 bool sp_text_delete_selection(SPEventContext *ec)
1399 {
1400 if (!SP_IS_TEXT_CONTEXT(ec))
1401 return false;
1402 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1403 if (tc->text == NULL)
1404 return false;
1406 if (tc->text_sel_start == tc->text_sel_end)
1407 return false;
1409 iterator_pair pair;
1410 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair);
1413 if (success) {
1414 tc->text_sel_start = tc->text_sel_end = pair.first;
1415 } else { // nothing deleted
1416 tc->text_sel_start = pair.first;
1417 tc->text_sel_end = pair.second;
1418 }
1420 sp_text_context_update_cursor(tc);
1421 sp_text_context_update_text_selection(tc);
1423 return true;
1424 }
1426 /**
1427 * \param selection Should not be NULL.
1428 */
1429 static void
1430 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1431 {
1432 g_assert(selection != NULL);
1434 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1436 ec->shape_editor->unset_item(SH_KNOTHOLDER);
1437 SPItem *item = selection->singleItem();
1438 if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1439 ec->shape_editor->set_item(item, SH_KNOTHOLDER);
1440 }
1442 if (tc->text && (item != tc->text)) {
1443 sp_text_context_forget_text(tc);
1444 }
1445 tc->text = NULL;
1447 if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1448 tc->text = item;
1449 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1450 if (layout)
1451 tc->text_sel_start = tc->text_sel_end = layout->end();
1452 } else {
1453 tc->text = NULL;
1454 }
1456 // we update cursor without scrolling, because this position may not be final;
1457 // item_handler moves cusros to the point of click immediately
1458 sp_text_context_update_cursor(tc, false);
1459 sp_text_context_update_text_selection(tc);
1460 }
1462 static void
1463 sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, SPTextContext *tc)
1464 {
1465 sp_text_context_update_cursor(tc);
1466 sp_text_context_update_text_selection(tc);
1467 }
1469 static bool
1470 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1471 {
1472 if (tc->text == NULL)
1473 return false;
1474 if (tc->text_sel_start == tc->text_sel_end)
1475 return false; // will get picked up by the parent and applied to the whole text object
1477 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1478 sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1479 _("Set text style"));
1480 sp_text_context_update_cursor(tc);
1481 sp_text_context_update_text_selection(tc);
1483 return true;
1484 }
1486 static int
1487 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1488 {
1489 if (tc->text == NULL)
1490 return QUERY_STYLE_NOTHING;
1491 const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1492 if (layout == NULL)
1493 return QUERY_STYLE_NOTHING;
1494 sp_text_context_validate_cursor_iterators(tc);
1496 GSList *styles_list = NULL;
1498 Inkscape::Text::Layout::iterator begin_it, end_it;
1499 if (tc->text_sel_start < tc->text_sel_end) {
1500 begin_it = tc->text_sel_start;
1501 end_it = tc->text_sel_end;
1502 } else {
1503 begin_it = tc->text_sel_end;
1504 end_it = tc->text_sel_start;
1505 }
1506 if (begin_it == end_it)
1507 if (!begin_it.prevCharacter())
1508 end_it.nextCharacter();
1509 for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1510 SPObject const *pos_obj = 0;
1511 void *rawptr = 0;
1512 layout->getSourceOfCharacter(it, &rawptr);
1513 if (!rawptr || !SP_IS_OBJECT(rawptr))
1514 continue;
1515 pos_obj = SP_OBJECT(rawptr);
1516 while (SP_IS_STRING(pos_obj) && SP_OBJECT_PARENT(pos_obj)) {
1517 pos_obj = SP_OBJECT_PARENT(pos_obj); // SPStrings don't have style
1518 }
1519 styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1520 }
1522 int result = sp_desktop_query_style_from_list (styles_list, style, property);
1524 g_slist_free(styles_list);
1525 return result;
1526 }
1528 static void
1529 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1530 {
1531 if (tc->text == NULL)
1532 return;
1533 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1534 if (layout) { // undo can change the text length without us knowing it
1535 layout->validateIterator(&tc->text_sel_start);
1536 layout->validateIterator(&tc->text_sel_end);
1537 }
1538 }
1540 static void
1541 sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see)
1542 {
1543 GdkRectangle im_cursor = { 0, 0, 1, 1 };
1545 // due to interruptible display, tc may already be destroyed during a display update before
1546 // the cursor update (can't do both atomically, alas)
1547 if (!tc->desktop) return;
1549 if (tc->text) {
1550 Geom::Point p0, p1;
1551 sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1552 Geom::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1553 Geom::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1555 // scroll to show cursor
1556 if (scroll_to_see) {
1557 Geom::Point const center = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().midpoint();
1558 if (Geom::L2(d0 - center) > Geom::L2(d1 - center))
1559 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1560 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d0, 1.0);
1561 else
1562 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d1, 1.0);
1563 }
1565 sp_canvas_item_show(tc->cursor);
1566 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1568 /* fixme: ... need another transformation to get canvas widget coordinate space? */
1569 im_cursor.x = (int) floor(d0[Geom::X]);
1570 im_cursor.y = (int) floor(d0[Geom::Y]);
1571 im_cursor.width = (int) floor(d1[Geom::X]) - im_cursor.x;
1572 im_cursor.height = (int) floor(d1[Geom::Y]) - im_cursor.y;
1574 tc->show = TRUE;
1575 tc->phase = 1;
1577 if (SP_IS_FLOWTEXT(tc->text)) {
1578 SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1579 if (frame) {
1580 sp_canvas_item_show(tc->frame);
1581 Geom::OptRect frame_bbox = sp_item_bbox_desktop(frame);
1582 if (frame_bbox) {
1583 SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
1584 }
1585 }
1586 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1587 } else {
1588 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1589 }
1591 } else {
1592 sp_canvas_item_hide(tc->cursor);
1593 sp_canvas_item_hide(tc->frame);
1594 tc->show = FALSE;
1595 if (!tc->nascent_object) {
1596 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
1597 }
1598 }
1600 if (tc->imc) {
1601 gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1602 }
1603 SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1604 }
1606 static void sp_text_context_update_text_selection(SPTextContext *tc)
1607 {
1608 // due to interruptible display, tc may already be destroyed during a display update before
1609 // the selection update (can't do both atomically, alas)
1610 if (!tc->desktop) return;
1612 for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1613 sp_canvas_item_hide(*it);
1614 gtk_object_destroy(*it);
1615 }
1616 tc->text_selection_quads.clear();
1618 std::vector<Geom::Point> quads;
1619 if (tc->text != NULL)
1620 quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1621 for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1622 SPCanvasItem *quad_canvasitem;
1623 quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1624 // FIXME: make the color settable in prefs
1625 // for now, use semitrasparent blue, as cairo cannot do inversion :(
1626 sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x00777777);
1627 sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1628 sp_canvas_item_show(quad_canvasitem);
1629 tc->text_selection_quads.push_back(quad_canvasitem);
1630 }
1631 }
1633 static gint
1634 sp_text_context_timeout(SPTextContext *tc)
1635 {
1636 if (tc->show) {
1637 sp_canvas_item_show(tc->cursor);
1638 if (tc->phase) {
1639 tc->phase = 0;
1640 sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
1641 } else {
1642 tc->phase = 1;
1643 sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0xffffffff);
1644 }
1645 }
1647 return TRUE;
1648 }
1650 static void
1651 sp_text_context_forget_text(SPTextContext *tc)
1652 {
1653 if (! tc->text) return;
1654 SPItem *ti = tc->text;
1655 (void)ti;
1656 /* We have to set it to zero,
1657 * or selection changed signal messes everything up */
1658 tc->text = NULL;
1660 /* FIXME: this automatic deletion when nothing is inputted crashes the XML edittor and also crashes when duplicating an empty flowtext.
1661 So don't create an empty flowtext in the first place? Create it when first character is typed.
1662 */
1663 /*
1664 if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1665 Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1666 // the repr may already have been unparented
1667 // if we were called e.g. as the result of
1668 // an undo or the element being removed from
1669 // the XML editor
1670 if ( text_repr && sp_repr_parent(text_repr) ) {
1671 sp_repr_unparent(text_repr);
1672 sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1673 _("Remove empty text"));
1674 }
1675 }
1676 */
1677 }
1679 gint
1680 sptc_focus_in(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1681 {
1682 gtk_im_context_focus_in(tc->imc);
1683 return FALSE;
1684 }
1686 gint
1687 sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1688 {
1689 gtk_im_context_focus_out(tc->imc);
1690 return FALSE;
1691 }
1693 static void
1694 sptc_commit(GtkIMContext */*imc*/, gchar *string, SPTextContext *tc)
1695 {
1696 if (!tc->text) {
1697 sp_text_context_setup_text(tc);
1698 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1699 }
1701 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1702 sp_text_context_update_cursor(tc);
1703 sp_text_context_update_text_selection(tc);
1705 sp_document_done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT,
1706 _("Type text"));
1707 }
1710 /*
1711 Local Variables:
1712 mode:c++
1713 c-file-style:"stroustrup"
1714 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1715 indent-tabs-mode:nil
1716 fill-column:99
1717 End:
1718 */
1719 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :