1 #define __SP_NODE_CONTEXT_C__
3 /*
4 * Node editing context
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 *
10 * This code is in public domain
11 */
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16 #include <cstring>
17 #include <string>
18 #include <gdk/gdkkeysyms.h>
19 #include "macros.h"
20 #include <glibmm/i18n.h>
21 #include "display/sp-canvas-util.h"
22 #include "object-edit.h"
23 #include "sp-path.h"
24 #include "path-chemistry.h"
25 #include "rubberband.h"
26 #include "desktop.h"
27 #include "desktop-handles.h"
28 #include "selection.h"
29 #include "pixmaps/cursor-node.xpm"
30 #include "message-context.h"
31 #include "node-context.h"
32 #include "pixmaps/cursor-node-d.xpm"
33 #include "prefs-utils.h"
34 #include "xml/node-event-vector.h"
35 #include "style.h"
36 #include "splivarot.h"
37 #include "shape-editor.h"
38 #include "live_effects/effect.h"
40 // needed for flash nodepath upon mouseover:
41 #include "display/canvas-bpath.h"
42 #include "display/curve.h"
44 static void sp_node_context_class_init(SPNodeContextClass *klass);
45 static void sp_node_context_init(SPNodeContext *node_context);
46 static void sp_node_context_dispose(GObject *object);
48 static void sp_node_context_setup(SPEventContext *ec);
49 static gint sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event);
50 static gint sp_node_context_item_handler(SPEventContext *event_context,
51 SPItem *item, GdkEvent *event);
53 static SPEventContextClass *parent_class;
55 GType
56 sp_node_context_get_type()
57 {
58 static GType type = 0;
59 if (!type) {
60 GTypeInfo info = {
61 sizeof(SPNodeContextClass),
62 NULL, NULL,
63 (GClassInitFunc) sp_node_context_class_init,
64 NULL, NULL,
65 sizeof(SPNodeContext),
66 4,
67 (GInstanceInitFunc) sp_node_context_init,
68 NULL, /* value_table */
69 };
70 type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPNodeContext", &info, (GTypeFlags)0);
71 }
72 return type;
73 }
75 static void
76 sp_node_context_class_init(SPNodeContextClass *klass)
77 {
78 GObjectClass *object_class = (GObjectClass *) klass;
79 SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
81 parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
83 object_class->dispose = sp_node_context_dispose;
85 event_context_class->setup = sp_node_context_setup;
86 event_context_class->root_handler = sp_node_context_root_handler;
87 event_context_class->item_handler = sp_node_context_item_handler;
88 }
90 static void
91 sp_node_context_init(SPNodeContext *node_context)
92 {
93 SPEventContext *event_context = SP_EVENT_CONTEXT(node_context);
95 event_context->cursor_shape = cursor_node_xpm;
96 event_context->hot_x = 1;
97 event_context->hot_y = 1;
99 node_context->leftalt = FALSE;
100 node_context->rightalt = FALSE;
101 node_context->leftctrl = FALSE;
102 node_context->rightctrl = FALSE;
104 new (&node_context->sel_changed_connection) sigc::connection();
106 node_context->flash_tempitem = NULL;
107 node_context->flashed_item = NULL;
108 node_context->remove_flash_counter = 0;
109 }
111 static void
112 sp_node_context_dispose(GObject *object)
113 {
114 SPNodeContext *nc = SP_NODE_CONTEXT(object);
115 SPEventContext *ec = SP_EVENT_CONTEXT(object);
117 ec->enableGrDrag(false);
119 if (nc->flash_permitem) {
120 // hack!!! (we add a temporary canvasitem with life time 1 ms)
121 nc->flash_tempitem = nc->event_context.desktop->add_temporary_canvasitem (nc->flash_permitem, 1);
122 nc->flash_permitem = NULL;
123 }
125 nc->sel_changed_connection.disconnect();
126 nc->sel_changed_connection.~connection();
128 delete nc->shape_editor;
130 if (nc->_node_message_context) {
131 delete nc->_node_message_context;
132 }
134 G_OBJECT_CLASS(parent_class)->dispose(object);
135 }
137 static void
138 sp_node_context_setup(SPEventContext *ec)
139 {
140 SPNodeContext *nc = SP_NODE_CONTEXT(ec);
142 if (((SPEventContextClass *) parent_class)->setup)
143 ((SPEventContextClass *) parent_class)->setup(ec);
145 nc->sel_changed_connection.disconnect();
146 nc->sel_changed_connection = sp_desktop_selection(ec->desktop)->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc));
148 Inkscape::Selection *selection = sp_desktop_selection(ec->desktop);
149 SPItem *item = selection->singleItem();
151 nc->shape_editor = new ShapeEditor(ec->desktop);
153 nc->rb_escaped = false;
155 nc->cursor_drag = false;
157 nc->added_node = false;
159 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
161 if (item) {
162 nc->shape_editor->set_item(item);
163 }
165 if (prefs_get_int_attribute("tools.nodes", "selcue", 0) != 0) {
166 ec->enableSelectionCue();
167 }
169 if (prefs_get_int_attribute("tools.nodes", "gradientdrag", 0) != 0) {
170 ec->enableGrDrag();
171 }
173 ec->desktop->emitToolSubselectionChanged(NULL); // sets the coord entry fields to inactive
175 nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
177 nc->shape_editor->update_statusbar();
178 }
180 static SPCanvasItem *
181 sp_node_context_generate_helperpath(SPDesktop *desktop, SPPath *path) {
182 // This should be put somewhere else under the name of "generate helperpath" or something. Because basically this is copied of code from nodepath...
183 SPCurve *curve_new = sp_path_get_curve_for_edit(path);
184 SPCurve *flash_curve = curve_new->copy();
185 flash_curve->transform(from_2geom(sp_item_i2d_affine(SP_ITEM(path))));
186 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
187 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
188 // unless we also flash the nodes...
189 guint32 color = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
190 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
191 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
192 sp_canvas_item_show(canvasitem);
193 flash_curve->unref();
194 return canvasitem;
195 }
197 static void
198 sp_node_context_flash_path(SPEventContext *event_context, SPItem *item, guint timeout) {
199 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
201 nc->remove_flash_counter = 3; // for some reason root_handler is called twice after each item_handler...
202 if (nc->flashed_item != item) {
203 // we entered a new item
204 nc->flashed_item = item;
205 SPDesktop *desktop = event_context->desktop;
206 if (nc->flash_tempitem) {
207 desktop->remove_temporary_canvasitem(nc->flash_tempitem);
208 nc->flash_tempitem = NULL;
209 }
211 if (SP_IS_PATH(item)) {
212 SPCanvasItem *canvasitem = sp_node_context_generate_helperpath(desktop, SP_PATH(item));
213 nc->flash_tempitem = desktop->add_temporary_canvasitem (canvasitem, timeout);
214 }
215 }
216 }
218 /**
219 \brief Callback that processes the "changed" signal on the selection;
220 destroys old and creates new nodepath and reassigns listeners to the new selected item's repr
221 */
222 void
223 sp_node_context_selection_changed(Inkscape::Selection *selection, gpointer data)
224 {
225 SPNodeContext *nc = SP_NODE_CONTEXT(data);
227 // TODO: update ShapeEditorsCollective instead
228 nc->shape_editor->unset_item();
229 SPItem *item = selection->singleItem();
230 nc->shape_editor->set_item(item);
232 if (nc->flash_permitem) {
233 // hack!!! (we add a temporary canvasitem with life time 1 ms)
234 nc->flash_tempitem = nc->event_context.desktop->add_temporary_canvasitem (nc->flash_permitem, 1);
235 nc->flash_permitem = NULL;
236 }
237 if (SP_IS_LPE_ITEM(item)) {
238 Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
239 if (lpe && lpe->pathFlashType() == Inkscape::LivePathEffect::PERMANENT_FLASH) {
240 if (SP_IS_PATH(item)) {
241 SPCanvasItem *canvasitem = sp_node_context_generate_helperpath(nc->event_context.desktop, SP_PATH(item));
242 nc->flash_permitem = canvasitem;
243 }
244 }
245 }
247 nc->shape_editor->update_statusbar();
248 }
250 void
251 sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event)
252 {
253 sp_event_show_modifier_tip
254 (event_context->defaultMessageContext(), event,
255 _("<b>Ctrl</b>: toggle node type, snap handle angle, move hor/vert; <b>Ctrl+Alt</b>: move along handles"),
256 _("<b>Shift</b>: toggle node selection, disable snapping, rotate both handles"),
257 _("<b>Alt</b>: lock handle length; <b>Ctrl+Alt</b>: move along handles"));
258 }
260 static gint
261 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
262 {
263 gint ret = FALSE;
265 if (prefs_get_int_attribute ("tools.nodes", "pathflash_enabled", 0) == 1) {
266 guint timeout = prefs_get_int_attribute("tools.nodes", "pathflash_timeout", 500);
267 if (SP_IS_LPE_ITEM(item)) {
268 Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
269 if (lpe) {
270 if (lpe->pathFlashType() == Inkscape::LivePathEffect::SUPPRESS_FLASH) {
271 // return if flash is suppressed or if we want a permanent "flash" (this is handled
272 // in sp_node_context_selection_changed()
273 return ret;
274 }
275 if (lpe->pathFlashType() == Inkscape::LivePathEffect::PERMANENT_FLASH) {
276 timeout = 0;
277 }
278 }
279 }
280 sp_node_context_flash_path(event_context, item, timeout);
281 }
283 if (((SPEventContextClass *) parent_class)->item_handler)
284 ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
286 return ret;
287 }
289 static gint
290 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
291 {
292 SPDesktop *desktop = event_context->desktop;
293 Inkscape::Selection *selection = sp_desktop_selection (desktop);
295 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
296 double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
297 event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
298 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
299 double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
301 if ( (nc->flash_tempitem) && (nc->remove_flash_counter <= 0) ) {
302 desktop->remove_temporary_canvasitem(nc->flash_tempitem);
303 nc->flash_tempitem = NULL;
304 nc->flashed_item = NULL; // also reset this one, so the next time the same object is hovered over it shows again the highlight
305 } else {
306 nc->remove_flash_counter--;
307 }
309 gint ret = FALSE;
310 switch (event->type) {
311 case GDK_BUTTON_PRESS:
312 if (event->button.button == 1 && !event_context->space_panning) {
313 // save drag origin
314 event_context->xp = (gint) event->button.x;
315 event_context->yp = (gint) event->button.y;
316 event_context->within_tolerance = true;
317 nc->shape_editor->cancel_hit();
319 if (!(event->button.state & GDK_SHIFT_MASK)) {
320 if (!nc->drag) {
321 if (nc->shape_editor->has_nodepath() && selection->single() /* && item_over */) {
322 // save drag origin
323 bool over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->button.x, event->button.y), true);
324 //only dragging curves
325 if (over_stroke) {
326 ret = TRUE;
327 break;
328 }
329 }
330 }
331 }
332 NR::Point const button_w(event->button.x,
333 event->button.y);
334 NR::Point const button_dt(desktop->w2d(button_w));
335 Inkscape::Rubberband::get()->start(desktop, button_dt);
336 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
337 desktop->updateNow();
338 ret = TRUE;
339 }
340 break;
341 case GDK_MOTION_NOTIFY:
342 if (event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
344 if ( event_context->within_tolerance
345 && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
346 && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
347 break; // do not drag if we're within tolerance from origin
348 }
350 // The path went away while dragging; throw away any further motion
351 // events until the mouse pointer is released.
353 if (nc->shape_editor->hits_curve() && !nc->shape_editor->has_nodepath()) {
354 break;
355 }
357 // Once the user has moved farther than tolerance from the original location
358 // (indicating they intend to move the object, not click), then always process the
359 // motion notify coordinates as given (no snapping back to origin)
360 event_context->within_tolerance = false;
362 // Once we determine what the user is doing (dragging either a node or the
363 // selection rubberband), make sure we continue to perform that operation
364 // until the mouse pointer is lifted.
365 if (nc->current_state == SP_NODE_CONTEXT_INACTIVE) {
366 if (nc->shape_editor->hits_curve() && nc->shape_editor->has_nodepath()) {
367 nc->current_state = SP_NODE_CONTEXT_NODE_DRAGGING;
368 } else {
369 nc->current_state = SP_NODE_CONTEXT_RUBBERBAND_DRAGGING;
370 }
371 }
373 switch (nc->current_state) {
374 case SP_NODE_CONTEXT_NODE_DRAGGING:
375 {
376 nc->shape_editor->curve_drag (event->motion.x, event->motion.y);
378 gobble_motion_events(GDK_BUTTON1_MASK);
379 break;
380 }
381 case SP_NODE_CONTEXT_RUBBERBAND_DRAGGING:
382 if (Inkscape::Rubberband::get()->is_started()) {
383 NR::Point const motion_w(event->motion.x,
384 event->motion.y);
385 NR::Point const motion_dt(desktop->w2d(motion_w));
386 Inkscape::Rubberband::get()->move(motion_dt);
387 }
388 break;
389 }
391 nc->drag = TRUE;
392 ret = TRUE;
393 } else {
394 if (!nc->shape_editor->has_nodepath() || selection->singleItem() == NULL) {
395 break;
396 }
398 bool over_stroke = false;
399 over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->motion.x, event->motion.y), false);
401 if (nc->cursor_drag && !over_stroke) {
402 event_context->cursor_shape = cursor_node_xpm;
403 event_context->hot_x = 1;
404 event_context->hot_y = 1;
405 sp_event_context_update_cursor(event_context);
406 nc->cursor_drag = false;
407 } else if (!nc->cursor_drag && over_stroke) {
408 event_context->cursor_shape = cursor_node_d_xpm;
409 event_context->hot_x = 1;
410 event_context->hot_y = 1;
411 sp_event_context_update_cursor(event_context);
412 nc->cursor_drag = true;
413 }
414 }
415 break;
417 case GDK_2BUTTON_PRESS:
418 case GDK_BUTTON_RELEASE:
419 if ( (event->button.button == 1) && (!nc->drag) && !event_context->space_panning) {
420 // find out clicked item, disregarding groups, honoring Alt
421 SPItem *item_clicked = sp_event_context_find_item (desktop,
422 NR::Point(event->button.x, event->button.y),
423 (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
425 event_context->xp = event_context->yp = 0;
427 bool over_stroke = false;
428 if (nc->shape_editor->has_nodepath()) {
429 over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->button.x, event->button.y), false);
430 }
432 if (item_clicked || over_stroke) {
433 if (over_stroke || nc->added_node) {
434 switch (event->type) {
435 case GDK_BUTTON_RELEASE:
436 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
437 //add a node
438 nc->shape_editor->add_node_near_point();
439 } else {
440 if (nc->added_node) { // we just received double click, ignore release
441 nc->added_node = false;
442 break;
443 }
444 //select the segment
445 if (event->button.state & GDK_SHIFT_MASK) {
446 nc->shape_editor->select_segment_near_point(true);
447 } else {
448 nc->shape_editor->select_segment_near_point(false);
449 }
450 desktop->updateNow();
451 }
452 break;
453 case GDK_2BUTTON_PRESS:
454 //add a node
455 nc->shape_editor->add_node_near_point();
456 nc->added_node = true;
457 break;
458 default:
459 break;
460 }
461 } else if (event->button.state & GDK_SHIFT_MASK) {
462 selection->toggle(item_clicked);
463 desktop->updateNow();
464 } else {
465 selection->set(item_clicked);
466 desktop->updateNow();
467 }
468 Inkscape::Rubberband::get()->stop();
469 ret = TRUE;
470 break;
471 }
472 }
473 if (event->type == GDK_BUTTON_RELEASE) {
474 event_context->xp = event_context->yp = 0;
475 if (event->button.button == 1) {
476 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
478 if (nc->shape_editor->hits_curve() && !event_context->within_tolerance) { //drag curve
479 nc->shape_editor->finish_drag();
480 } else if (b && !event_context->within_tolerance) { // drag to select
481 nc->shape_editor->select_rect(*b, event->button.state & GDK_SHIFT_MASK);
482 } else {
483 if (!(nc->rb_escaped)) { // unless something was cancelled
484 if (nc->shape_editor->has_selection())
485 nc->shape_editor->deselect();
486 else
487 sp_desktop_selection(desktop)->clear();
488 }
489 }
490 ret = TRUE;
491 Inkscape::Rubberband::get()->stop();
492 desktop->updateNow();
493 nc->rb_escaped = false;
494 nc->drag = FALSE;
495 nc->shape_editor->cancel_hit();
496 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
497 }
498 }
499 break;
500 case GDK_KEY_PRESS:
501 switch (get_group0_keyval(&event->key)) {
502 case GDK_Insert:
503 case GDK_KP_Insert:
504 // with any modifiers
505 nc->shape_editor->add_node();
506 ret = TRUE;
507 break;
508 case GDK_Delete:
509 case GDK_KP_Delete:
510 case GDK_BackSpace:
511 if (MOD__CTRL_ONLY) {
512 nc->shape_editor->delete_nodes();
513 } else {
514 nc->shape_editor->delete_nodes_preserving_shape();
515 }
516 ret = TRUE;
517 break;
518 case GDK_C:
519 case GDK_c:
520 if (MOD__SHIFT_ONLY) {
521 nc->shape_editor->set_node_type(Inkscape::NodePath::NODE_CUSP);
522 ret = TRUE;
523 }
524 break;
525 case GDK_S:
526 case GDK_s:
527 if (MOD__SHIFT_ONLY) {
528 nc->shape_editor->set_node_type(Inkscape::NodePath::NODE_SMOOTH);
529 ret = TRUE;
530 }
531 break;
532 case GDK_Y:
533 case GDK_y:
534 if (MOD__SHIFT_ONLY) {
535 nc->shape_editor->set_node_type(Inkscape::NodePath::NODE_SYMM);
536 ret = TRUE;
537 }
538 break;
539 case GDK_B:
540 case GDK_b:
541 if (MOD__SHIFT_ONLY) {
542 nc->shape_editor->break_at_nodes();
543 ret = TRUE;
544 }
545 break;
546 case GDK_J:
547 case GDK_j:
548 if (MOD__SHIFT_ONLY) {
549 nc->shape_editor->join_nodes();
550 ret = TRUE;
551 }
552 break;
553 case GDK_D:
554 case GDK_d:
555 if (MOD__SHIFT_ONLY) {
556 nc->shape_editor->duplicate_nodes();
557 ret = TRUE;
558 }
559 break;
560 case GDK_L:
561 case GDK_l:
562 if (MOD__SHIFT_ONLY) {
563 nc->shape_editor->set_type_of_segments(NR_LINETO);
564 ret = TRUE;
565 }
566 break;
567 case GDK_U:
568 case GDK_u:
569 if (MOD__SHIFT_ONLY) {
570 nc->shape_editor->set_type_of_segments(NR_CURVETO);
571 ret = TRUE;
572 }
573 break;
574 case GDK_R:
575 case GDK_r:
576 if (MOD__SHIFT_ONLY) {
577 // FIXME: add top panel button
578 sp_selected_path_reverse();
579 ret = TRUE;
580 }
581 break;
582 case GDK_x:
583 case GDK_X:
584 if (MOD__ALT_ONLY) {
585 desktop->setToolboxFocusTo ("altx-nodes");
586 ret = TRUE;
587 }
588 break;
589 case GDK_Left: // move selection left
590 case GDK_KP_Left:
591 case GDK_KP_4:
592 if (!MOD__CTRL) { // not ctrl
593 gint mul = 1 + gobble_key_events(
594 get_group0_keyval(&event->key), 0); // with any mask
595 if (MOD__ALT) { // alt
596 if (MOD__SHIFT) nc->shape_editor->move_nodes_screen(mul*-10, 0); // shift
597 else nc->shape_editor->move_nodes_screen(mul*-1, 0); // no shift
598 }
599 else { // no alt
600 if (MOD__SHIFT) nc->shape_editor->move_nodes(mul*-10*nudge, 0); // shift
601 else nc->shape_editor->move_nodes(mul*-nudge, 0); // no shift
602 }
603 ret = TRUE;
604 }
605 break;
606 case GDK_Up: // move selection up
607 case GDK_KP_Up:
608 case GDK_KP_8:
609 if (!MOD__CTRL) { // not ctrl
610 gint mul = 1 + gobble_key_events(
611 get_group0_keyval(&event->key), 0); // with any mask
612 if (MOD__ALT) { // alt
613 if (MOD__SHIFT) nc->shape_editor->move_nodes_screen(0, mul*10); // shift
614 else nc->shape_editor->move_nodes_screen(0, mul*1); // no shift
615 }
616 else { // no alt
617 if (MOD__SHIFT) nc->shape_editor->move_nodes(0, mul*10*nudge); // shift
618 else nc->shape_editor->move_nodes(0, mul*nudge); // no shift
619 }
620 ret = TRUE;
621 }
622 break;
623 case GDK_Right: // move selection right
624 case GDK_KP_Right:
625 case GDK_KP_6:
626 if (!MOD__CTRL) { // not ctrl
627 gint mul = 1 + gobble_key_events(
628 get_group0_keyval(&event->key), 0); // with any mask
629 if (MOD__ALT) { // alt
630 if (MOD__SHIFT) nc->shape_editor->move_nodes_screen(mul*10, 0); // shift
631 else nc->shape_editor->move_nodes_screen(mul*1, 0); // no shift
632 }
633 else { // no alt
634 if (MOD__SHIFT) nc->shape_editor->move_nodes(mul*10*nudge, 0); // shift
635 else nc->shape_editor->move_nodes(mul*nudge, 0); // no shift
636 }
637 ret = TRUE;
638 }
639 break;
640 case GDK_Down: // move selection down
641 case GDK_KP_Down:
642 case GDK_KP_2:
643 if (!MOD__CTRL) { // not ctrl
644 gint mul = 1 + gobble_key_events(
645 get_group0_keyval(&event->key), 0); // with any mask
646 if (MOD__ALT) { // alt
647 if (MOD__SHIFT) nc->shape_editor->move_nodes_screen(0, mul*-10); // shift
648 else nc->shape_editor->move_nodes_screen(0, mul*-1); // no shift
649 }
650 else { // no alt
651 if (MOD__SHIFT) nc->shape_editor->move_nodes(0, mul*-10*nudge); // shift
652 else nc->shape_editor->move_nodes(0, mul*-nudge); // no shift
653 }
654 ret = TRUE;
655 }
656 break;
657 case GDK_Escape:
658 {
659 NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
660 if (b) {
661 Inkscape::Rubberband::get()->stop();
662 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
663 nc->rb_escaped = true;
664 } else {
665 if (nc->shape_editor->has_selection()) {
666 nc->shape_editor->deselect();
667 } else {
668 sp_desktop_selection(desktop)->clear();
669 }
670 }
671 ret = TRUE;
672 break;
673 }
675 case GDK_bracketleft:
676 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
677 if (nc->leftctrl)
678 nc->shape_editor->rotate_nodes (M_PI/snaps, -1, false);
679 if (nc->rightctrl)
680 nc->shape_editor->rotate_nodes (M_PI/snaps, 1, false);
681 } else if ( MOD__ALT && !MOD__CTRL ) {
682 if (nc->leftalt && nc->rightalt)
683 nc->shape_editor->rotate_nodes (1, 0, true);
684 else {
685 if (nc->leftalt)
686 nc->shape_editor->rotate_nodes (1, -1, true);
687 if (nc->rightalt)
688 nc->shape_editor->rotate_nodes (1, 1, true);
689 }
690 } else if ( snaps != 0 ) {
691 nc->shape_editor->rotate_nodes (M_PI/snaps, 0, false);
692 }
693 ret = TRUE;
694 break;
695 case GDK_bracketright:
696 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
697 if (nc->leftctrl)
698 nc->shape_editor->rotate_nodes (-M_PI/snaps, -1, false);
699 if (nc->rightctrl)
700 nc->shape_editor->rotate_nodes (-M_PI/snaps, 1, false);
701 } else if ( MOD__ALT && !MOD__CTRL ) {
702 if (nc->leftalt && nc->rightalt)
703 nc->shape_editor->rotate_nodes (-1, 0, true);
704 else {
705 if (nc->leftalt)
706 nc->shape_editor->rotate_nodes (-1, -1, true);
707 if (nc->rightalt)
708 nc->shape_editor->rotate_nodes (-1, 1, true);
709 }
710 } else if ( snaps != 0 ) {
711 nc->shape_editor->rotate_nodes (-M_PI/snaps, 0, false);
712 }
713 ret = TRUE;
714 break;
715 case GDK_less:
716 case GDK_comma:
717 if (MOD__CTRL) {
718 if (nc->leftctrl)
719 nc->shape_editor->scale_nodes(-offset, -1);
720 if (nc->rightctrl)
721 nc->shape_editor->scale_nodes(-offset, 1);
722 } else if (MOD__ALT) {
723 if (nc->leftalt && nc->rightalt)
724 nc->shape_editor->scale_nodes_screen (-1, 0);
725 else {
726 if (nc->leftalt)
727 nc->shape_editor->scale_nodes_screen (-1, -1);
728 if (nc->rightalt)
729 nc->shape_editor->scale_nodes_screen (-1, 1);
730 }
731 } else {
732 nc->shape_editor->scale_nodes (-offset, 0);
733 }
734 ret = TRUE;
735 break;
736 case GDK_greater:
737 case GDK_period:
738 if (MOD__CTRL) {
739 if (nc->leftctrl)
740 nc->shape_editor->scale_nodes (offset, -1);
741 if (nc->rightctrl)
742 nc->shape_editor->scale_nodes (offset, 1);
743 } else if (MOD__ALT) {
744 if (nc->leftalt && nc->rightalt)
745 nc->shape_editor->scale_nodes_screen (1, 0);
746 else {
747 if (nc->leftalt)
748 nc->shape_editor->scale_nodes_screen (1, -1);
749 if (nc->rightalt)
750 nc->shape_editor->scale_nodes_screen (1, 1);
751 }
752 } else {
753 nc->shape_editor->scale_nodes (offset, 0);
754 }
755 ret = TRUE;
756 break;
758 case GDK_Alt_L:
759 nc->leftalt = TRUE;
760 sp_node_context_show_modifier_tip(event_context, event);
761 break;
762 case GDK_Alt_R:
763 nc->rightalt = TRUE;
764 sp_node_context_show_modifier_tip(event_context, event);
765 break;
766 case GDK_Control_L:
767 nc->leftctrl = TRUE;
768 sp_node_context_show_modifier_tip(event_context, event);
769 break;
770 case GDK_Control_R:
771 nc->rightctrl = TRUE;
772 sp_node_context_show_modifier_tip(event_context, event);
773 break;
774 case GDK_Shift_L:
775 case GDK_Shift_R:
776 case GDK_Meta_L:
777 case GDK_Meta_R:
778 sp_node_context_show_modifier_tip(event_context, event);
779 break;
780 default:
781 ret = node_key(event);
782 break;
783 }
784 break;
785 case GDK_KEY_RELEASE:
786 switch (get_group0_keyval(&event->key)) {
787 case GDK_Alt_L:
788 nc->leftalt = FALSE;
789 event_context->defaultMessageContext()->clear();
790 break;
791 case GDK_Alt_R:
792 nc->rightalt = FALSE;
793 event_context->defaultMessageContext()->clear();
794 break;
795 case GDK_Control_L:
796 nc->leftctrl = FALSE;
797 event_context->defaultMessageContext()->clear();
798 break;
799 case GDK_Control_R:
800 nc->rightctrl = FALSE;
801 event_context->defaultMessageContext()->clear();
802 break;
803 case GDK_Shift_L:
804 case GDK_Shift_R:
805 case GDK_Meta_L:
806 case GDK_Meta_R:
807 event_context->defaultMessageContext()->clear();
808 break;
809 }
810 break;
811 default:
812 break;
813 }
815 if (!ret) {
816 if (((SPEventContextClass *) parent_class)->root_handler)
817 ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
818 }
820 return ret;
821 }
824 /*
825 Local Variables:
826 mode:c++
827 c-file-style:"stroustrup"
828 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
829 indent-tabs-mode:nil
830 fill-column:99
831 End:
832 */
833 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :