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 }
357 ret = TRUE;
358 }
359 }
360 break;
361 case GDK_2BUTTON_PRESS:
362 if (event->button.button == 1 && tc->text) {
363 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
364 if (layout) {
365 if (!layout->isStartOfWord(tc->text_sel_start))
366 tc->text_sel_start.prevStartOfWord();
367 if (!layout->isEndOfWord(tc->text_sel_end))
368 tc->text_sel_end.nextEndOfWord();
369 sp_text_context_update_cursor(tc);
370 sp_text_context_update_text_selection(tc);
371 tc->dragging = 2;
372 ret = TRUE;
373 }
374 }
375 break;
376 case GDK_3BUTTON_PRESS:
377 if (event->button.button == 1 && tc->text) {
378 tc->text_sel_start.thisStartOfLine();
379 tc->text_sel_end.thisEndOfLine();
380 sp_text_context_update_cursor(tc);
381 sp_text_context_update_text_selection(tc);
382 tc->dragging = 3;
383 ret = TRUE;
384 }
385 break;
386 case GDK_BUTTON_RELEASE:
387 if (event->button.button == 1 && tc->dragging && !event_context->space_panning) {
388 tc->dragging = 0;
389 ret = TRUE;
390 }
391 break;
392 case GDK_MOTION_NOTIFY:
393 if (event->motion.state & GDK_BUTTON1_MASK && tc->dragging && !event_context->space_panning) {
394 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
395 if (!layout) break;
396 // find out click point in document coordinates
397 Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
398 // set the cursor closest to that point
399 Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(tc->text, p);
400 if (tc->dragging == 2) {
401 // double-click dragging: go by word
402 if (new_end < tc->text_sel_start) {
403 if (!layout->isStartOfWord(new_end))
404 new_end.prevStartOfWord();
405 } else
406 if (!layout->isEndOfWord(new_end))
407 new_end.nextEndOfWord();
408 } else if (tc->dragging == 3) {
409 // triple-click dragging: go by line
410 if (new_end < tc->text_sel_start)
411 new_end.thisStartOfLine();
412 else
413 new_end.thisEndOfLine();
414 }
415 // update display
416 if (tc->text_sel_end != new_end) {
417 tc->text_sel_end = new_end;
418 sp_text_context_update_cursor(tc);
419 sp_text_context_update_text_selection(tc);
420 }
421 gobble_motion_events(GDK_BUTTON1_MASK);
422 ret = TRUE;
423 break;
424 }
425 // find out item under mouse, disregarding groups
426 item_ungrouped = desktop->item_at_point(Geom::Point(event->button.x, event->button.y), TRUE);
427 if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
428 sp_canvas_item_show(tc->indicator);
429 Geom::OptRect ibbox = sp_item_bbox_desktop(item_ungrouped);
430 if (ibbox) {
431 SP_CTRLRECT(tc->indicator)->setRectangle(*ibbox);
432 }
434 event_context->cursor_shape = cursor_text_insert_xpm;
435 event_context->hot_x = 7;
436 event_context->hot_y = 10;
437 sp_event_context_update_cursor(event_context);
438 sp_text_context_update_text_selection(tc);
440 if (SP_IS_TEXT (item_ungrouped)) {
441 desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
442 } else {
443 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."));
444 }
446 tc->over_text = true;
448 ret = TRUE;
449 }
450 break;
451 default:
452 break;
453 }
455 if (!ret) {
456 if (((SPEventContextClass *) parent_class)->item_handler)
457 ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
458 }
460 return ret;
461 }
463 static void
464 sp_text_context_setup_text(SPTextContext *tc)
465 {
466 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
468 /* Create <text> */
469 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_EVENT_CONTEXT_DESKTOP(ec)->doc());
470 Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
471 rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
473 /* Set style */
474 sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "/tools/text", true);
476 sp_repr_set_svg_double(rtext, "x", tc->pdoc[Geom::X]);
477 sp_repr_set_svg_double(rtext, "y", tc->pdoc[Geom::Y]);
479 /* Create <tspan> */
480 Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
481 rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
482 rtext->addChild(rtspan, NULL);
483 Inkscape::GC::release(rtspan);
485 /* Create TEXT */
486 Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
487 rtspan->addChild(rstring, NULL);
488 Inkscape::GC::release(rstring);
489 SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
490 /* fixme: Is selection::changed really immediate? */
491 /* yes, it's immediate .. why does it matter? */
492 sp_desktop_selection(ec->desktop)->set(text_item);
493 Inkscape::GC::release(rtext);
494 text_item->transform = sp_item_i2doc_affine(SP_ITEM(ec->desktop->currentLayer())).inverse();
496 text_item->updateRepr();
497 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
498 _("Create text"));
499 }
501 /**
502 * Insert the character indicated by tc.uni to replace the current selection,
503 * and reset tc.uni/tc.unipos to empty string.
504 *
505 * \pre tc.uni/tc.unipos non-empty.
506 */
507 static void
508 insert_uni_char(SPTextContext *const tc)
509 {
510 g_return_if_fail(tc->unipos
511 && tc->unipos < sizeof(tc->uni)
512 && tc->uni[tc->unipos] == '\0');
513 unsigned int uv;
514 sscanf(tc->uni, "%x", &uv);
515 tc->unipos = 0;
516 tc->uni[tc->unipos] = '\0';
518 if ( !g_unichar_isprint(static_cast<gunichar>(uv))
519 && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
520 // This may be due to bad input, so it goes to statusbar.
521 tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
522 _("Non-printable character"));
523 } else {
524 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
525 sp_text_context_setup_text(tc);
526 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
527 }
529 gchar u[10];
530 guint const len = g_unichar_to_utf8(uv, u);
531 u[len] = '\0';
533 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
534 sp_text_context_update_cursor(tc);
535 sp_text_context_update_text_selection(tc);
536 sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM,
537 _("Insert Unicode character"));
538 }
539 }
541 static void
542 hex_to_printable_utf8_buf(char const *const hex, char *utf8)
543 {
544 unsigned int uv;
545 sscanf(hex, "%x", &uv);
546 if (!g_unichar_isprint((gunichar) uv)) {
547 uv = 0xfffd;
548 }
549 guint const len = g_unichar_to_utf8(uv, utf8);
550 utf8[len] = '\0';
551 }
553 static void
554 show_curr_uni_char(SPTextContext *const tc)
555 {
556 g_return_if_fail(tc->unipos < sizeof(tc->uni)
557 && tc->uni[tc->unipos] == '\0');
558 if (tc->unipos) {
559 char utf8[10];
560 hex_to_printable_utf8_buf(tc->uni, utf8);
562 /* Status bar messages are in pango markup, so we need xml escaping. */
563 if (utf8[1] == '\0') {
564 switch(utf8[0]) {
565 case '<': strcpy(utf8, "<"); break;
566 case '>': strcpy(utf8, ">"); break;
567 case '&': strcpy(utf8, "&"); break;
568 default: break;
569 }
570 }
571 tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
572 _("Unicode (<b>Enter</b> to finish): %s: %s"), tc->uni, utf8);
573 } else {
574 tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
575 }
576 }
578 static gint
579 sp_text_context_root_handler(SPEventContext *const event_context, GdkEvent *const event)
580 {
581 SPTextContext *const tc = SP_TEXT_CONTEXT(event_context);
583 SPDesktop *desktop = event_context->desktop;
585 sp_canvas_item_hide(tc->indicator);
587 sp_text_context_validate_cursor_iterators(tc);
589 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
590 event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
592 switch (event->type) {
593 case GDK_BUTTON_PRESS:
594 if (event->button.button == 1 && !event_context->space_panning) {
596 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
597 return TRUE;
598 }
600 // save drag origin
601 event_context->xp = (gint) event->button.x;
602 event_context->yp = (gint) event->button.y;
603 event_context->within_tolerance = true;
605 Geom::Point const button_pt(event->button.x, event->button.y);
606 tc->p0 = desktop->w2d(button_pt);
607 Inkscape::Rubberband::get(desktop)->start(desktop, tc->p0);
608 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
609 GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
610 NULL, event->button.time);
611 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
612 tc->creating = 1;
614 /* Processed */
615 return TRUE;
616 }
617 break;
618 case GDK_MOTION_NOTIFY:
619 if (tc->over_text) {
620 tc->over_text = 0;
621 // update cursor and statusbar: we are not over a text object now
622 event_context->cursor_shape = cursor_text_xpm;
623 event_context->hot_x = 7;
624 event_context->hot_y = 7;
625 sp_event_context_update_cursor(event_context);
626 desktop->event_context->defaultMessageContext()->clear();
627 }
629 if (tc->creating && event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
630 if ( event_context->within_tolerance
631 && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
632 && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
633 break; // do not drag if we're within tolerance from origin
634 }
635 // Once the user has moved farther than tolerance from the original location
636 // (indicating they intend to draw, not click), then always process the
637 // motion notify coordinates as given (no snapping back to origin)
638 event_context->within_tolerance = false;
640 Geom::Point const motion_pt(event->motion.x, event->motion.y);
641 Geom::Point const p = desktop->w2d(motion_pt);
643 Inkscape::Rubberband::get(desktop)->move(p);
644 gobble_motion_events(GDK_BUTTON1_MASK);
646 // status text
647 GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::X]), desktop->namedview->getDefaultMetric());
648 GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::Y]), desktop->namedview->getDefaultMetric());
649 event_context->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Flowed text frame</b>: %s × %s"), xs->str, ys->str);
650 g_string_free(xs, FALSE);
651 g_string_free(ys, FALSE);
653 }
654 break;
655 case GDK_BUTTON_RELEASE:
656 if (event->button.button == 1 && !event_context->space_panning) {
658 if (tc->grabbed) {
659 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
660 tc->grabbed = NULL;
661 }
663 Inkscape::Rubberband::get(desktop)->stop();
665 if (tc->creating && event_context->within_tolerance) {
666 /* Button 1, set X & Y & new item */
667 sp_desktop_selection(desktop)->clear();
668 Geom::Point dtp = desktop->w2d(Geom::Point(event->button.x, event->button.y));
669 tc->pdoc = sp_desktop_dt2doc_xy_point(desktop, dtp);
671 tc->show = TRUE;
672 tc->phase = 1;
673 tc->nascent_object = 1; // new object was just created
675 /* Cursor */
676 sp_canvas_item_show(tc->cursor);
677 // Cursor height is defined by the new text object's font size; it needs to be set
678 // articifically here, for the text object does not exist yet:
679 double cursor_height = sp_desktop_get_font_size_tool(desktop);
680 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), dtp, dtp + Geom::Point(0, cursor_height));
681 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
683 event_context->within_tolerance = false;
684 } else if (tc->creating) {
685 Geom::Point const button_pt(event->button.x, event->button.y);
686 Geom::Point p1 = desktop->w2d(button_pt);
687 double cursor_height = sp_desktop_get_font_size_tool(desktop);
688 if (fabs(p1[Geom::Y] - tc->p0[Geom::Y]) > cursor_height) {
689 // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
690 SPItem *ft = create_flowtext_with_internal_frame (desktop, tc->p0, p1);
691 /* Set style */
692 sp_desktop_apply_style_tool(desktop, SP_OBJECT_REPR(ft), "/tools/text", true);
693 sp_desktop_selection(desktop)->set(ft);
694 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
695 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
696 _("Create flowed text"));
697 } else {
698 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
699 }
700 }
701 tc->creating = false;
702 return TRUE;
703 }
704 break;
705 case GDK_KEY_PRESS: {
706 guint const group0_keyval = get_group0_keyval(&event->key);
708 if (group0_keyval == GDK_KP_Add ||
709 group0_keyval == GDK_KP_Subtract) {
710 if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
711 break; // otherwise pass on keypad +/- so they can zoom
712 }
714 if ((tc->text) || (tc->nascent_object)) {
715 // there is an active text object in this context, or a new object was just created
717 if (tc->unimode || !tc->imc
718 || (MOD__CTRL && MOD__SHIFT) // input methods tend to steal this for unimode,
719 // but we have our own so make sure they don't swallow it
720 || !gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
721 //IM did not consume the key, or we're in unimode
723 if (!MOD__CTRL_ONLY && tc->unimode) {
724 /* TODO: ISO 14755 (section 3 Definitions) says that we should also
725 accept the first 6 characters of alphabets other than the latin
726 alphabet "if the Latin alphabet is not used". The below is also
727 reasonable (viz. hope that the user's keyboard includes latin
728 characters and force latin interpretation -- just as we do for our
729 keyboard shortcuts), but differs from the ISO 14755
730 recommendation. */
731 switch (group0_keyval) {
732 case GDK_space:
733 case GDK_KP_Space: {
734 if (tc->unipos) {
735 insert_uni_char(tc);
736 }
737 /* Stay in unimode. */
738 show_curr_uni_char(tc);
739 return TRUE;
740 }
742 case GDK_BackSpace: {
743 g_return_val_if_fail(tc->unipos < sizeof(tc->uni), TRUE);
744 if (tc->unipos) {
745 tc->uni[--tc->unipos] = '\0';
746 }
747 show_curr_uni_char(tc);
748 return TRUE;
749 }
751 case GDK_Return:
752 case GDK_KP_Enter: {
753 if (tc->unipos) {
754 insert_uni_char(tc);
755 }
756 /* Exit unimode. */
757 tc->unimode = false;
758 event_context->defaultMessageContext()->clear();
759 return TRUE;
760 }
762 case GDK_Escape: {
763 // Cancel unimode.
764 tc->unimode = false;
765 gtk_im_context_reset(tc->imc);
766 event_context->defaultMessageContext()->clear();
767 return TRUE;
768 }
770 case GDK_Shift_L:
771 case GDK_Shift_R:
772 break;
774 default: {
775 if (g_ascii_isxdigit(group0_keyval)) {
776 g_return_val_if_fail(tc->unipos < sizeof(tc->uni) - 1, TRUE);
777 tc->uni[tc->unipos++] = group0_keyval;
778 tc->uni[tc->unipos] = '\0';
779 if (tc->unipos == 8) {
780 /* This behaviour is partly to allow us to continue to
781 use a fixed-length buffer for tc->uni. Reason for
782 choosing the number 8 is that it's the length of
783 ``canonical form'' mentioned in the ISO 14755 spec.
784 An advantage over choosing 6 is that it allows using
785 backspace for typos & misremembering when entering a
786 6-digit number. */
787 insert_uni_char(tc);
788 }
789 show_curr_uni_char(tc);
790 return TRUE;
791 } else {
792 /* The intent is to ignore but consume characters that could be
793 typos for hex digits. Gtk seems to ignore & consume all
794 non-hex-digits, and we do similar here. Though note that some
795 shortcuts (like keypad +/- for zoom) get processed before
796 reaching this code. */
797 return TRUE;
798 }
799 }
800 }
801 }
803 Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
804 Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
805 bool cursor_moved = false;
806 int screenlines = 1;
807 if (tc->text) {
808 double spacing = sp_te_get_average_linespacing(tc->text);
809 Geom::Rect const d = desktop->get_display_area();
810 screenlines = (int) floor(fabs(d.min()[Geom::Y] - d.max()[Geom::Y])/spacing) - 1;
811 if (screenlines <= 0)
812 screenlines = 1;
813 }
815 /* Neither unimode nor IM consumed key; process text tool shortcuts */
816 switch (group0_keyval) {
817 case GDK_x:
818 case GDK_X:
819 if (MOD__ALT_ONLY) {
820 desktop->setToolboxFocusTo ("altx-text");
821 return TRUE;
822 }
823 break;
824 case GDK_space:
825 if (MOD__CTRL_ONLY) {
826 /* No-break space */
827 if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
828 sp_text_context_setup_text(tc);
829 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
830 }
831 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
832 sp_text_context_update_cursor(tc);
833 sp_text_context_update_text_selection(tc);
834 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
835 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
836 _("Insert no-break space"));
837 return TRUE;
838 }
839 break;
840 case GDK_U:
841 case GDK_u:
842 if (MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT)) {
843 if (tc->unimode) {
844 tc->unimode = false;
845 event_context->defaultMessageContext()->clear();
846 } else {
847 tc->unimode = true;
848 tc->unipos = 0;
849 event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
850 }
851 if (tc->imc) {
852 gtk_im_context_reset(tc->imc);
853 }
854 return TRUE;
855 }
856 break;
857 case GDK_B:
858 case GDK_b:
859 if (MOD__CTRL_ONLY && tc->text) {
860 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
861 SPCSSAttr *css = sp_repr_css_attr_new();
862 if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
863 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
864 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
865 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
866 || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
867 sp_repr_css_set_property(css, "font-weight", "bold");
868 else
869 sp_repr_css_set_property(css, "font-weight", "normal");
870 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
871 sp_repr_css_attr_unref(css);
872 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
873 _("Make bold"));
874 sp_text_context_update_cursor(tc);
875 sp_text_context_update_text_selection(tc);
876 return TRUE;
877 }
878 break;
879 case GDK_I:
880 case GDK_i:
881 if (MOD__CTRL_ONLY && tc->text) {
882 SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->text_sel_end));
883 SPCSSAttr *css = sp_repr_css_attr_new();
884 if (style->font_style.computed == SP_CSS_FONT_STYLE_NORMAL)
885 sp_repr_css_set_property(css, "font-style", "italic");
886 else
887 sp_repr_css_set_property(css, "font-style", "normal");
888 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
889 sp_repr_css_attr_unref(css);
890 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
891 _("Make italic"));
892 sp_text_context_update_cursor(tc);
893 sp_text_context_update_text_selection(tc);
894 return TRUE;
895 }
896 break;
898 case GDK_A:
899 case GDK_a:
900 if (MOD__CTRL_ONLY && tc->text) {
901 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
902 if (layout) {
903 tc->text_sel_start = layout->begin();
904 tc->text_sel_end = layout->end();
905 sp_text_context_update_cursor(tc);
906 sp_text_context_update_text_selection(tc);
907 return TRUE;
908 }
909 }
910 break;
912 case GDK_Return:
913 case GDK_KP_Enter:
914 {
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 }
920 iterator_pair enter_pair;
921 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, enter_pair);
922 (void)success; // TODO cleanup
923 tc->text_sel_start = tc->text_sel_end = enter_pair.first;
925 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
927 sp_text_context_update_cursor(tc);
928 sp_text_context_update_text_selection(tc);
929 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
930 _("New line"));
931 return TRUE;
932 }
933 case GDK_BackSpace:
934 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
936 bool noSelection = false;
938 if (tc->text_sel_start == tc->text_sel_end) {
939 tc->text_sel_start.prevCursorPosition();
940 noSelection = true;
941 }
943 iterator_pair bspace_pair;
944 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, bspace_pair);
946 if (noSelection) {
947 if (success) {
948 tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
949 } else { // nothing deleted
950 tc->text_sel_start = tc->text_sel_end = bspace_pair.second;
951 }
952 } else {
953 if (success) {
954 tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
955 } else { // nothing deleted
956 tc->text_sel_start = bspace_pair.first;
957 tc->text_sel_end = bspace_pair.second;
958 }
959 }
961 sp_text_context_update_cursor(tc);
962 sp_text_context_update_text_selection(tc);
963 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
964 _("Backspace"));
965 }
966 return TRUE;
967 case GDK_Delete:
968 case GDK_KP_Delete:
969 if (tc->text) {
970 bool noSelection = false;
972 if (tc->text_sel_start == tc->text_sel_end) {
973 tc->text_sel_end.nextCursorPosition();
974 noSelection = true;
975 }
977 iterator_pair del_pair;
978 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, del_pair);
980 if (noSelection) {
981 tc->text_sel_start = tc->text_sel_end = del_pair.first;
982 } else {
983 if (success) {
984 tc->text_sel_start = tc->text_sel_end = del_pair.first;
985 } else { // nothing deleted
986 tc->text_sel_start = del_pair.first;
987 tc->text_sel_end = del_pair.second;
988 }
989 }
992 sp_text_context_update_cursor(tc);
993 sp_text_context_update_text_selection(tc);
994 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
995 _("Delete"));
996 }
997 return TRUE;
998 case GDK_Left:
999 case GDK_KP_Left:
1000 case GDK_KP_4:
1001 if (tc->text) {
1002 if (MOD__ALT) {
1003 gint mul = 1 + gobble_key_events(
1004 get_group0_keyval(&event->key), 0); // with any mask
1005 if (MOD__SHIFT)
1006 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-10, 0));
1007 else
1008 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-1, 0));
1009 sp_text_context_update_cursor(tc);
1010 sp_text_context_update_text_selection(tc);
1011 sp_document_maybe_done(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT,
1012 _("Kern to the left"));
1013 } else {
1014 if (MOD__CTRL)
1015 tc->text_sel_end.cursorLeftWithControl();
1016 else
1017 tc->text_sel_end.cursorLeft();
1018 cursor_moved = true;
1019 break;
1020 }
1021 }
1022 return TRUE;
1023 case GDK_Right:
1024 case GDK_KP_Right:
1025 case GDK_KP_6:
1026 if (tc->text) {
1027 if (MOD__ALT) {
1028 gint mul = 1 + gobble_key_events(
1029 get_group0_keyval(&event->key), 0); // with any mask
1030 if (MOD__SHIFT)
1031 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*10, 0));
1032 else
1033 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*1, 0));
1034 sp_text_context_update_cursor(tc);
1035 sp_text_context_update_text_selection(tc);
1036 sp_document_maybe_done(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT,
1037 _("Kern to the right"));
1038 } else {
1039 if (MOD__CTRL)
1040 tc->text_sel_end.cursorRightWithControl();
1041 else
1042 tc->text_sel_end.cursorRight();
1043 cursor_moved = true;
1044 break;
1045 }
1046 }
1047 return TRUE;
1048 case GDK_Up:
1049 case GDK_KP_Up:
1050 case GDK_KP_8:
1051 if (tc->text) {
1052 if (MOD__ALT) {
1053 gint mul = 1 + gobble_key_events(
1054 get_group0_keyval(&event->key), 0); // with any mask
1055 if (MOD__SHIFT)
1056 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-10));
1057 else
1058 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-1));
1059 sp_text_context_update_cursor(tc);
1060 sp_text_context_update_text_selection(tc);
1061 sp_document_maybe_done(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT,
1062 _("Kern up"));
1064 } else {
1065 if (MOD__CTRL)
1066 tc->text_sel_end.cursorUpWithControl();
1067 else
1068 tc->text_sel_end.cursorUp();
1069 cursor_moved = true;
1070 break;
1071 }
1072 }
1073 return TRUE;
1074 case GDK_Down:
1075 case GDK_KP_Down:
1076 case GDK_KP_2:
1077 if (tc->text) {
1078 if (MOD__ALT) {
1079 gint mul = 1 + gobble_key_events(
1080 get_group0_keyval(&event->key), 0); // with any mask
1081 if (MOD__SHIFT)
1082 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*10));
1083 else
1084 sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*1));
1085 sp_text_context_update_cursor(tc);
1086 sp_text_context_update_text_selection(tc);
1087 sp_document_maybe_done(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT,
1088 _("Kern down"));
1090 } else {
1091 if (MOD__CTRL)
1092 tc->text_sel_end.cursorDownWithControl();
1093 else
1094 tc->text_sel_end.cursorDown();
1095 cursor_moved = true;
1096 break;
1097 }
1098 }
1099 return TRUE;
1100 case GDK_Home:
1101 case GDK_KP_Home:
1102 if (tc->text) {
1103 if (MOD__CTRL)
1104 tc->text_sel_end.thisStartOfShape();
1105 else
1106 tc->text_sel_end.thisStartOfLine();
1107 cursor_moved = true;
1108 break;
1109 }
1110 return TRUE;
1111 case GDK_End:
1112 case GDK_KP_End:
1113 if (tc->text) {
1114 if (MOD__CTRL)
1115 tc->text_sel_end.nextStartOfShape();
1116 else
1117 tc->text_sel_end.thisEndOfLine();
1118 cursor_moved = true;
1119 break;
1120 }
1121 return TRUE;
1122 case GDK_Page_Down:
1123 case GDK_KP_Page_Down:
1124 if (tc->text) {
1125 tc->text_sel_end.cursorDown(screenlines);
1126 cursor_moved = true;
1127 break;
1128 }
1129 return TRUE;
1130 case GDK_Page_Up:
1131 case GDK_KP_Page_Up:
1132 if (tc->text) {
1133 tc->text_sel_end.cursorUp(screenlines);
1134 cursor_moved = true;
1135 break;
1136 }
1137 return TRUE;
1138 case GDK_Escape:
1139 if (tc->creating) {
1140 tc->creating = 0;
1141 if (tc->grabbed) {
1142 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1143 tc->grabbed = NULL;
1144 }
1145 Inkscape::Rubberband::get(desktop)->stop();
1146 } else {
1147 sp_desktop_selection(desktop)->clear();
1148 }
1149 tc->nascent_object = FALSE;
1150 return TRUE;
1151 case GDK_bracketleft:
1152 if (tc->text) {
1153 if (MOD__ALT || MOD__CTRL) {
1154 if (MOD__ALT) {
1155 if (MOD__SHIFT) {
1156 // FIXME: alt+shift+[] does not work, don't know why
1157 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1158 } else {
1159 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1160 }
1161 } else {
1162 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -90);
1163 }
1164 sp_document_maybe_done(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT,
1165 _("Rotate counterclockwise"));
1166 sp_text_context_update_cursor(tc);
1167 sp_text_context_update_text_selection(tc);
1168 return TRUE;
1169 }
1170 }
1171 break;
1172 case GDK_bracketright:
1173 if (tc->text) {
1174 if (MOD__ALT || MOD__CTRL) {
1175 if (MOD__ALT) {
1176 if (MOD__SHIFT) {
1177 // FIXME: alt+shift+[] does not work, don't know why
1178 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1179 } else {
1180 sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1181 }
1182 } else {
1183 sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 90);
1184 }
1185 sp_document_maybe_done(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT,
1186 _("Rotate clockwise"));
1187 sp_text_context_update_cursor(tc);
1188 sp_text_context_update_text_selection(tc);
1189 return TRUE;
1190 }
1191 }
1192 break;
1193 case GDK_less:
1194 case GDK_comma:
1195 if (tc->text) {
1196 if (MOD__ALT) {
1197 if (MOD__CTRL) {
1198 if (MOD__SHIFT)
1199 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1200 else
1201 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1202 sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT,
1203 _("Contract line spacing"));
1205 } else {
1206 if (MOD__SHIFT)
1207 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
1208 else
1209 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
1210 sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT,
1211 _("Contract letter spacing"));
1213 }
1214 sp_text_context_update_cursor(tc);
1215 sp_text_context_update_text_selection(tc);
1216 return TRUE;
1217 }
1218 }
1219 break;
1220 case GDK_greater:
1221 case GDK_period:
1222 if (tc->text) {
1223 if (MOD__ALT) {
1224 if (MOD__CTRL) {
1225 if (MOD__SHIFT)
1226 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1227 else
1228 sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1229 sp_document_maybe_done(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT,
1230 _("Expand line spacing"));
1232 } else {
1233 if (MOD__SHIFT)
1234 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
1235 else
1236 sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
1237 sp_document_maybe_done(sp_desktop_document(desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT,
1238 _("Expand letter spacing"));
1240 }
1241 sp_text_context_update_cursor(tc);
1242 sp_text_context_update_text_selection(tc);
1243 return TRUE;
1244 }
1245 }
1246 break;
1247 default:
1248 break;
1249 }
1251 if (cursor_moved) {
1252 if (!MOD__SHIFT)
1253 tc->text_sel_start = tc->text_sel_end;
1254 if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
1255 sp_text_context_update_cursor(tc);
1256 sp_text_context_update_text_selection(tc);
1257 }
1258 return TRUE;
1259 }
1261 } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
1262 } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1263 // except up/down that are swallowed to prevent the zoom field from activation
1264 if ((group0_keyval == GDK_Up ||
1265 group0_keyval == GDK_Down ||
1266 group0_keyval == GDK_KP_Up ||
1267 group0_keyval == GDK_KP_Down )
1268 && !MOD__CTRL_ONLY) {
1269 return TRUE;
1270 } else if (group0_keyval == GDK_Escape) { // cancel rubberband
1271 if (tc->creating) {
1272 tc->creating = 0;
1273 if (tc->grabbed) {
1274 sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
1275 tc->grabbed = NULL;
1276 }
1277 Inkscape::Rubberband::get(desktop)->stop();
1278 }
1279 }
1280 }
1281 break;
1282 }
1284 case GDK_KEY_RELEASE:
1285 if (!tc->unimode && tc->imc && gtk_im_context_filter_keypress(tc->imc, (GdkEventKey*) event)) {
1286 return TRUE;
1287 }
1288 break;
1289 default:
1290 break;
1291 }
1293 // if nobody consumed it so far
1294 if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
1295 return ((SPEventContextClass *) parent_class)->root_handler(event_context, event); // send event to parent
1296 } else {
1297 return FALSE; // return "I did nothing" value so that global shortcuts can be activated
1298 }
1299 }
1301 /**
1302 Attempts to paste system clipboard into the currently edited text, returns true on success
1303 */
1304 bool
1305 sp_text_paste_inline(SPEventContext *ec)
1306 {
1307 if (!SP_IS_TEXT_CONTEXT(ec))
1308 return false;
1310 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1312 if ((tc->text) || (tc->nascent_object)) {
1313 // there is an active text object in this context, or a new object was just created
1315 Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
1316 Glib::ustring const clip_text = refClipboard->wait_for_text();
1318 if (!clip_text.empty()) {
1319 // Fix for 244940
1320 // The XML standard defines the following as valid characters
1321 // (Extensible Markup Language (XML) 1.0 (Fourth Edition) paragraph 2.2)
1322 // char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
1323 // Since what comes in off the paste buffer will go right into XML, clean
1324 // the text here.
1325 Glib::ustring text(clip_text);
1326 Glib::ustring::iterator itr = text.begin();
1327 gunichar paste_string_uchar;
1329 while(itr != text.end())
1330 {
1331 paste_string_uchar = *itr;
1333 // Make sure we don't have a control character. We should really check
1334 // for the whole range above... Add the rest of the invalid cases from
1335 // above if we find additional issues
1336 if(paste_string_uchar >= 0x00000020 ||
1337 paste_string_uchar == 0x00000009 ||
1338 paste_string_uchar == 0x0000000A ||
1339 paste_string_uchar == 0x0000000D) {
1340 itr++;
1341 } else {
1342 itr = text.erase(itr);
1343 }
1344 }
1346 if (!tc->text) { // create text if none (i.e. if nascent_object)
1347 sp_text_context_setup_text(tc);
1348 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1349 }
1351 // using indices is slow in ustrings. Whatever.
1352 Glib::ustring::size_type begin = 0;
1353 for ( ; ; ) {
1354 Glib::ustring::size_type end = text.find('\n', begin);
1355 if (end == Glib::ustring::npos) {
1356 if (begin != text.length())
1357 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());
1358 break;
1359 }
1360 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());
1361 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
1362 begin = end + 1;
1363 }
1364 sp_document_done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
1365 _("Paste text"));
1367 return true;
1368 }
1369 } // FIXME: else create and select a new object under cursor!
1371 return false;
1372 }
1374 /**
1375 Gets the raw characters that comprise the currently selected text, converting line
1376 breaks into lf characters.
1377 */
1378 Glib::ustring
1379 sp_text_get_selected_text(SPEventContext const *ec)
1380 {
1381 if (!SP_IS_TEXT_CONTEXT(ec))
1382 return "";
1383 SPTextContext const *tc = SP_TEXT_CONTEXT(ec);
1384 if (tc->text == NULL)
1385 return "";
1387 return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
1388 }
1390 /**
1391 Deletes the currently selected characters. Returns false if there is no
1392 text selection currently.
1393 */
1394 bool sp_text_delete_selection(SPEventContext *ec)
1395 {
1396 if (!SP_IS_TEXT_CONTEXT(ec))
1397 return false;
1398 SPTextContext *tc = SP_TEXT_CONTEXT(ec);
1399 if (tc->text == NULL)
1400 return false;
1402 if (tc->text_sel_start == tc->text_sel_end)
1403 return false;
1405 iterator_pair pair;
1406 bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair);
1409 if (success) {
1410 tc->text_sel_start = tc->text_sel_end = pair.first;
1411 } else { // nothing deleted
1412 tc->text_sel_start = pair.first;
1413 tc->text_sel_end = pair.second;
1414 }
1416 sp_text_context_update_cursor(tc);
1417 sp_text_context_update_text_selection(tc);
1419 return true;
1420 }
1422 /**
1423 * \param selection Should not be NULL.
1424 */
1425 static void
1426 sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext *tc)
1427 {
1428 g_assert(selection != NULL);
1430 SPEventContext *ec = SP_EVENT_CONTEXT(tc);
1432 ec->shape_editor->unset_item(SH_KNOTHOLDER);
1433 SPItem *item = selection->singleItem();
1434 if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
1435 ec->shape_editor->set_item(item, SH_KNOTHOLDER);
1436 }
1438 if (tc->text && (item != tc->text)) {
1439 sp_text_context_forget_text(tc);
1440 }
1441 tc->text = NULL;
1443 if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1444 tc->text = item;
1445 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1446 if (layout)
1447 tc->text_sel_start = tc->text_sel_end = layout->end();
1448 } else {
1449 tc->text = NULL;
1450 }
1452 // we update cursor without scrolling, because this position may not be final;
1453 // item_handler moves cusros to the point of click immediately
1454 sp_text_context_update_cursor(tc, false);
1455 sp_text_context_update_text_selection(tc);
1456 }
1458 static void
1459 sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, SPTextContext *tc)
1460 {
1461 sp_text_context_update_cursor(tc);
1462 sp_text_context_update_text_selection(tc);
1463 }
1465 static bool
1466 sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
1467 {
1468 if (tc->text == NULL)
1469 return false;
1470 if (tc->text_sel_start == tc->text_sel_end)
1471 return false; // will get picked up by the parent and applied to the whole text object
1473 sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
1474 sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1475 _("Set text style"));
1476 sp_text_context_update_cursor(tc);
1477 sp_text_context_update_text_selection(tc);
1479 return true;
1480 }
1482 static int
1483 sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
1484 {
1485 if (tc->text == NULL)
1486 return QUERY_STYLE_NOTHING;
1487 const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
1488 if (layout == NULL)
1489 return QUERY_STYLE_NOTHING;
1490 sp_text_context_validate_cursor_iterators(tc);
1492 GSList *styles_list = NULL;
1494 Inkscape::Text::Layout::iterator begin_it, end_it;
1495 if (tc->text_sel_start < tc->text_sel_end) {
1496 begin_it = tc->text_sel_start;
1497 end_it = tc->text_sel_end;
1498 } else {
1499 begin_it = tc->text_sel_end;
1500 end_it = tc->text_sel_start;
1501 }
1502 if (begin_it == end_it)
1503 if (!begin_it.prevCharacter())
1504 end_it.nextCharacter();
1505 for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
1506 SPObject const *pos_obj = 0;
1507 void *rawptr = 0;
1508 layout->getSourceOfCharacter(it, &rawptr);
1509 if (!rawptr || !SP_IS_OBJECT(rawptr))
1510 continue;
1511 pos_obj = SP_OBJECT(rawptr);
1512 while (SP_IS_STRING(pos_obj) && SP_OBJECT_PARENT(pos_obj)) {
1513 pos_obj = SP_OBJECT_PARENT(pos_obj); // SPStrings don't have style
1514 }
1515 styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
1516 }
1518 int result = sp_desktop_query_style_from_list (styles_list, style, property);
1520 g_slist_free(styles_list);
1521 return result;
1522 }
1524 static void
1525 sp_text_context_validate_cursor_iterators(SPTextContext *tc)
1526 {
1527 if (tc->text == NULL)
1528 return;
1529 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
1530 if (layout) { // undo can change the text length without us knowing it
1531 layout->validateIterator(&tc->text_sel_start);
1532 layout->validateIterator(&tc->text_sel_end);
1533 }
1534 }
1536 static void
1537 sp_text_context_update_cursor(SPTextContext *tc, bool scroll_to_see)
1538 {
1539 GdkRectangle im_cursor = { 0, 0, 1, 1 };
1541 // due to interruptible display, tc may already be destroyed during a display update before
1542 // the cursor update (can't do both atomically, alas)
1543 if (!tc->desktop) return;
1545 if (tc->text) {
1546 Geom::Point p0, p1;
1547 sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
1548 Geom::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
1549 Geom::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
1551 // scroll to show cursor
1552 if (scroll_to_see) {
1553 Geom::Point const center = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().midpoint();
1554 if (Geom::L2(d0 - center) > Geom::L2(d1 - center))
1555 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1556 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d0, 1.0);
1557 else
1558 SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d1, 1.0);
1559 }
1561 sp_canvas_item_show(tc->cursor);
1562 sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
1564 /* fixme: ... need another transformation to get canvas widget coordinate space? */
1565 im_cursor.x = (int) floor(d0[Geom::X]);
1566 im_cursor.y = (int) floor(d0[Geom::Y]);
1567 im_cursor.width = (int) floor(d1[Geom::X]) - im_cursor.x;
1568 im_cursor.height = (int) floor(d1[Geom::Y]) - im_cursor.y;
1570 tc->show = TRUE;
1571 tc->phase = 1;
1573 if (SP_IS_FLOWTEXT(tc->text)) {
1574 SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
1575 if (frame) {
1576 sp_canvas_item_show(tc->frame);
1577 Geom::OptRect frame_bbox = sp_item_bbox_desktop(frame);
1578 if (frame_bbox) {
1579 SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
1580 }
1581 }
1582 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
1583 } else {
1584 SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
1585 }
1587 } else {
1588 sp_canvas_item_hide(tc->cursor);
1589 sp_canvas_item_hide(tc->frame);
1590 tc->show = FALSE;
1591 if (!tc->nascent_object) {
1592 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
1593 }
1594 }
1596 if (tc->imc) {
1597 gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
1598 }
1599 SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
1600 }
1602 static void sp_text_context_update_text_selection(SPTextContext *tc)
1603 {
1604 // due to interruptible display, tc may already be destroyed during a display update before
1605 // the selection update (can't do both atomically, alas)
1606 if (!tc->desktop) return;
1608 for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
1609 sp_canvas_item_hide(*it);
1610 gtk_object_destroy(*it);
1611 }
1612 tc->text_selection_quads.clear();
1614 std::vector<Geom::Point> quads;
1615 if (tc->text != NULL)
1616 quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, sp_item_i2d_affine(tc->text));
1617 for (unsigned i = 0 ; i < quads.size() ; i += 4) {
1618 SPCanvasItem *quad_canvasitem;
1619 quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
1620 // FIXME: make the color settable in prefs
1621 // for now, use semitrasparent blue, as cairo cannot do inversion :(
1622 sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x00777777);
1623 sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1624 sp_canvas_item_show(quad_canvasitem);
1625 tc->text_selection_quads.push_back(quad_canvasitem);
1626 }
1627 }
1629 static gint
1630 sp_text_context_timeout(SPTextContext *tc)
1631 {
1632 if (tc->show) {
1633 sp_canvas_item_show(tc->cursor);
1634 if (tc->phase) {
1635 tc->phase = 0;
1636 sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
1637 } else {
1638 tc->phase = 1;
1639 sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0xffffffff);
1640 }
1641 }
1643 return TRUE;
1644 }
1646 static void
1647 sp_text_context_forget_text(SPTextContext *tc)
1648 {
1649 if (! tc->text) return;
1650 SPItem *ti = tc->text;
1651 (void)ti;
1652 /* We have to set it to zero,
1653 * or selection changed signal messes everything up */
1654 tc->text = NULL;
1656 /* FIXME: this automatic deletion when nothing is inputted crashes the XML edittor and also crashes when duplicating an empty flowtext.
1657 So don't create an empty flowtext in the first place? Create it when first character is typed.
1658 */
1659 /*
1660 if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
1661 Inkscape::XML::Node *text_repr=SP_OBJECT_REPR(ti);
1662 // the repr may already have been unparented
1663 // if we were called e.g. as the result of
1664 // an undo or the element being removed from
1665 // the XML editor
1666 if ( text_repr && sp_repr_parent(text_repr) ) {
1667 sp_repr_unparent(text_repr);
1668 sp_document_done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
1669 _("Remove empty text"));
1670 }
1671 }
1672 */
1673 }
1675 gint
1676 sptc_focus_in(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1677 {
1678 gtk_im_context_focus_in(tc->imc);
1679 return FALSE;
1680 }
1682 gint
1683 sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
1684 {
1685 gtk_im_context_focus_out(tc->imc);
1686 return FALSE;
1687 }
1689 static void
1690 sptc_commit(GtkIMContext */*imc*/, gchar *string, SPTextContext *tc)
1691 {
1692 if (!tc->text) {
1693 sp_text_context_setup_text(tc);
1694 tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
1695 }
1697 tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
1698 sp_text_context_update_cursor(tc);
1699 sp_text_context_update_text_selection(tc);
1701 sp_document_done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT,
1702 _("Type text"));
1703 }
1706 /*
1707 Local Variables:
1708 mode:c++
1709 c-file-style:"stroustrup"
1710 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1711 indent-tabs-mode:nil
1712 fill-column:99
1713 End:
1714 */
1715 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :