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