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