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, except stamping code,
11 * which is Copyright (C) Masatake Yamato 2002
12 */
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
17 #include <gdk/gdkkeysyms.h>
18 #include "macros.h"
19 #include <glibmm/i18n.h>
20 #include "display/sp-canvas-util.h"
21 #include "object-edit.h"
22 #include "sp-path.h"
23 #include "path-chemistry.h"
24 #include "rubberband.h"
25 #include "desktop.h"
26 #include "desktop-handles.h"
27 #include "selection.h"
28 #include "pixmaps/cursor-node.xpm"
29 #include "message-context.h"
30 #include "node-context.h"
31 #include "pixmaps/cursor-node-d.xpm"
32 #include "prefs-utils.h"
33 #include "xml/node-event-vector.h"
34 #include "style.h"
35 #include "splivarot.h"
37 static void sp_node_context_class_init(SPNodeContextClass *klass);
38 static void sp_node_context_init(SPNodeContext *node_context);
39 static void sp_node_context_dispose(GObject *object);
41 static void sp_node_context_setup(SPEventContext *ec);
42 static gint sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event);
43 static gint sp_node_context_item_handler(SPEventContext *event_context,
44 SPItem *item, GdkEvent *event);
46 static void nodepath_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
47 gchar const *old_value, gchar const *new_value,
48 bool is_interactive, gpointer data);
50 static Inkscape::XML::NodeEventVector nodepath_repr_events = {
51 NULL, /* child_added */
52 NULL, /* child_removed */
53 nodepath_event_attr_changed,
54 NULL, /* content_changed */
55 NULL /* order_changed */
56 };
58 static SPEventContextClass *parent_class;
60 GType
61 sp_node_context_get_type()
62 {
63 static GType type = 0;
64 if (!type) {
65 GTypeInfo info = {
66 sizeof(SPNodeContextClass),
67 NULL, NULL,
68 (GClassInitFunc) sp_node_context_class_init,
69 NULL, NULL,
70 sizeof(SPNodeContext),
71 4,
72 (GInstanceInitFunc) sp_node_context_init,
73 NULL, /* value_table */
74 };
75 type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPNodeContext", &info, (GTypeFlags)0);
76 }
77 return type;
78 }
80 static void
81 sp_node_context_class_init(SPNodeContextClass *klass)
82 {
83 GObjectClass *object_class = (GObjectClass *) klass;
84 SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
86 parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
88 object_class->dispose = sp_node_context_dispose;
90 event_context_class->setup = sp_node_context_setup;
91 event_context_class->root_handler = sp_node_context_root_handler;
92 event_context_class->item_handler = sp_node_context_item_handler;
93 }
95 static void
96 sp_node_context_init(SPNodeContext *node_context)
97 {
98 SPEventContext *event_context = SP_EVENT_CONTEXT(node_context);
100 event_context->cursor_shape = cursor_node_xpm;
101 event_context->hot_x = 1;
102 event_context->hot_y = 1;
104 node_context->leftalt = FALSE;
105 node_context->rightalt = FALSE;
106 node_context->leftctrl = FALSE;
107 node_context->rightctrl = FALSE;
109 new (&node_context->sel_changed_connection) sigc::connection();
110 }
112 static void
113 sp_node_context_dispose(GObject *object)
114 {
115 SPNodeContext *nc = SP_NODE_CONTEXT(object);
116 SPEventContext *ec = SP_EVENT_CONTEXT(object);
118 ec->enableGrDrag(false);
120 nc->sel_changed_connection.disconnect();
121 nc->sel_changed_connection.~connection();
123 Inkscape::XML::Node *repr = NULL;
124 if (nc->nodepath) {
125 repr = nc->nodepath->repr;
126 }
127 if (!repr && ec->shape_knot_holder) {
128 repr = ec->shape_knot_holder->repr;
129 }
131 if (repr) {
132 sp_repr_remove_listener_by_data(repr, ec);
133 Inkscape::GC::release(repr);
134 }
136 if (nc->nodepath) {
137 sp_nodepath_destroy(nc->nodepath);
138 nc->nodepath = NULL;
139 }
141 if (ec->shape_knot_holder) {
142 sp_knot_holder_destroy(ec->shape_knot_holder);
143 ec->shape_knot_holder = NULL;
144 }
146 if (nc->_node_message_context) {
147 delete nc->_node_message_context;
148 }
150 G_OBJECT_CLASS(parent_class)->dispose(object);
151 }
153 static void
154 sp_node_context_setup(SPEventContext *ec)
155 {
156 SPNodeContext *nc = SP_NODE_CONTEXT(ec);
158 if (((SPEventContextClass *) parent_class)->setup)
159 ((SPEventContextClass *) parent_class)->setup(ec);
161 nc->sel_changed_connection.disconnect();
162 nc->sel_changed_connection = sp_desktop_selection(ec->desktop)->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc));
164 Inkscape::Selection *selection = sp_desktop_selection(ec->desktop);
165 SPItem *item = selection->singleItem();
167 nc->nodepath = NULL;
168 ec->shape_knot_holder = NULL;
170 nc->rb_escaped = false;
172 nc->cursor_drag = false;
174 nc->added_node = false;
176 if (item) {
177 nc->nodepath = sp_nodepath_new(ec->desktop, item);
178 if ( nc->nodepath) {
179 //point pack to parent in case nodepath is deleted
180 nc->nodepath->nodeContext = nc;
181 }
182 ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
184 if (nc->nodepath || ec->shape_knot_holder) {
185 // setting listener
186 Inkscape::XML::Node *repr;
187 if (ec->shape_knot_holder)
188 repr = ec->shape_knot_holder->repr;
189 else
190 repr = SP_OBJECT_REPR(item);
191 if (repr) {
192 Inkscape::GC::anchor(repr);
193 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
194 }
195 }
196 }
198 if (prefs_get_int_attribute("tools.nodes", "selcue", 0) != 0) {
199 ec->enableSelectionCue();
200 }
202 if (prefs_get_int_attribute("tools.nodes", "gradientdrag", 0) != 0) {
203 ec->enableGrDrag();
204 }
206 nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
207 sp_nodepath_update_statusbar(nc->nodepath);
208 }
210 /**
211 \brief Callback that processes the "changed" signal on the selection;
212 destroys old and creates new nodepath and reassigns listeners to the new selected item's repr
213 */
214 void
215 sp_node_context_selection_changed(Inkscape::Selection *selection, gpointer data)
216 {
217 SPNodeContext *nc = SP_NODE_CONTEXT(data);
218 SPEventContext *ec = SP_EVENT_CONTEXT(nc);
220 Inkscape::XML::Node *old_repr = NULL;
222 if (nc->nodepath) {
223 old_repr = nc->nodepath->repr;
224 sp_nodepath_destroy(nc->nodepath);
225 nc->nodepath = NULL;
226 }
228 if (ec->shape_knot_holder) {
229 old_repr = ec->shape_knot_holder->repr;
230 sp_knot_holder_destroy(ec->shape_knot_holder);
231 }
233 if (old_repr) { // remove old listener
234 sp_repr_remove_listener_by_data(old_repr, ec);
235 Inkscape::GC::release(old_repr);
236 }
238 SPItem *item = selection->singleItem();
240 SPDesktop *desktop = selection->desktop();
241 nc->nodepath = NULL;
242 ec->shape_knot_holder = NULL;
243 if (item) {
244 nc->nodepath = sp_nodepath_new(desktop, item);
245 if (nc->nodepath) {
246 nc->nodepath->nodeContext = nc;
247 }
248 ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
250 if (nc->nodepath || ec->shape_knot_holder) {
251 // setting new listener
252 Inkscape::XML::Node *repr;
253 if (ec->shape_knot_holder)
254 repr = ec->shape_knot_holder->repr;
255 else
256 repr = SP_OBJECT_REPR(item);
257 if (repr) {
258 Inkscape::GC::anchor(repr);
259 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
260 }
261 }
262 }
263 sp_nodepath_update_statusbar(nc->nodepath);
264 }
266 /**
267 \brief Regenerates nodepath when the item's repr was change outside of node edit
268 (e.g. by undo, or xml editor, or edited in another view). The item is assumed to be the same
269 (otherwise sp_node_context_selection_changed() would have been called), so repr and listeners
270 are not changed.
271 */
272 void
273 sp_nodepath_update_from_item(SPNodeContext *nc, SPItem *item)
274 {
275 g_assert(nc);
276 SPEventContext *ec = ((SPEventContext *) nc);
278 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(SP_EVENT_CONTEXT(nc));
279 g_assert(desktop);
281 if (nc->nodepath) {
282 sp_nodepath_destroy(nc->nodepath);
283 nc->nodepath = NULL;
284 }
286 if (ec->shape_knot_holder) {
287 sp_knot_holder_destroy(ec->shape_knot_holder);
288 ec->shape_knot_holder = NULL;
289 }
291 Inkscape::Selection *selection = sp_desktop_selection(desktop);
292 item = selection->singleItem();
294 if (item) {
295 nc->nodepath = sp_nodepath_new(desktop, item);
296 if (nc->nodepath) {
297 nc->nodepath->nodeContext = nc;
298 }
299 ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
300 }
301 sp_nodepath_update_statusbar(nc->nodepath);
302 }
304 /**
305 \brief Callback that is fired whenever an attribute of the selected item (which we have in the nodepath) changes
306 */
307 static void
308 nodepath_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
309 gchar const *old_value, gchar const *new_value,
310 bool is_interactive, gpointer data)
311 {
312 SPItem *item = NULL;
313 gboolean changed = FALSE;
315 g_assert(data);
316 SPNodeContext *nc = ((SPNodeContext *) data);
317 SPEventContext *ec = ((SPEventContext *) data);
318 g_assert(nc);
319 Inkscape::NodePath::Path *np = nc->nodepath;
320 SPKnotHolder *kh = ec->shape_knot_holder;
322 if (np) {
323 item = SP_ITEM(np->path);
324 if (!strcmp(name, "d") || !strcmp(name, "sodipodi:nodetypes")) { // With paths, we only need to act if one of the path-affecting attributes has changed.
325 changed = (np->local_change == 0);
326 if (np->local_change > 0)
327 np->local_change--;
328 }
330 } else if (kh) {
331 item = SP_ITEM(kh->item);
332 changed = !(kh->local_change);
333 kh->local_change = FALSE;
334 }
336 if (np && changed) {
337 GList *saved = NULL;
338 SPDesktop *desktop = np->desktop;
339 g_assert(desktop);
340 Inkscape::Selection *selection = desktop->selection;
341 g_assert(selection);
343 saved = save_nodepath_selection(nc->nodepath);
344 sp_nodepath_update_from_item(nc, item);
345 if (nc->nodepath && saved) restore_nodepath_selection(nc->nodepath, saved);
347 } else if (kh && changed) {
348 sp_nodepath_update_from_item(nc, item);
349 }
351 sp_nodepath_update_statusbar(nc->nodepath);
352 }
354 void
355 sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event)
356 {
357 sp_event_show_modifier_tip
358 (event_context->defaultMessageContext(), event,
359 _("<b>Ctrl</b>: toggle node type, snap handle angle, move hor/vert; <b>Ctrl+Alt</b>: move along handles"),
360 _("<b>Shift</b>: toggle node selection, disable snapping, rotate both handles"),
361 _("<b>Alt</b>: lock handle length; <b>Ctrl+Alt</b>: move along handles"));
362 }
364 bool
365 sp_node_context_is_over_stroke (SPNodeContext *nc, SPItem *item, NR::Point event_p, bool remember)
366 {
367 SPDesktop *desktop = SP_EVENT_CONTEXT (nc)->desktop;
369 //Translate click point into proper coord system
370 nc->curvepoint_doc = desktop->w2d(event_p);
371 nc->curvepoint_doc *= sp_item_dt2i_affine(item);
372 nc->curvepoint_doc *= sp_item_i2doc_affine(item);
374 NR::Maybe<Path::cut_position> position = get_nearest_position_on_Path(nc->nodepath->livarot_path, nc->curvepoint_doc);
375 NR::Point nearest = get_point_on_Path(nc->nodepath->livarot_path, position.assume().piece, position.assume().t);
376 NR::Point delta = nearest - nc->curvepoint_doc;
378 delta = desktop->d2w(delta);
380 double stroke_tolerance =
381 (SP_OBJECT_STYLE (item)->stroke.type != SP_PAINT_TYPE_NONE?
382 desktop->current_zoom() *
383 SP_OBJECT_STYLE (item)->stroke_width.computed *
384 sp_item_i2d_affine (item).expansion() * 0.5
385 : 0.0)
386 + (double) SP_EVENT_CONTEXT(nc)->tolerance;
388 bool close = (NR::L2 (delta) < stroke_tolerance);
390 if (remember && close) {
391 nc->curvepoint_event[NR::X] = (gint) event_p [NR::X];
392 nc->curvepoint_event[NR::Y] = (gint) event_p [NR::Y];
393 nc->hit = true;
394 nc->grab_t = position.assume().t;
395 nc->grab_node = sp_nodepath_get_node_by_index(position.assume().piece);
396 }
398 return close;
399 }
402 static gint
403 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
404 {
405 gint ret = FALSE;
407 SPDesktop *desktop = event_context->desktop;
408 Inkscape::Selection *selection = sp_desktop_selection (desktop);
410 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
412 switch (event->type) {
413 case GDK_2BUTTON_PRESS:
414 case GDK_BUTTON_RELEASE:
415 if (event->button.button == 1) {
416 if (!nc->drag) {
418 // find out clicked item, disregarding groups, honoring Alt
419 SPItem *item_clicked = sp_event_context_find_item (desktop,
420 NR::Point(event->button.x, event->button.y),
421 (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
422 // find out if we're over the selected item, disregarding groups
423 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
424 NR::Point(event->button.x, event->button.y));
426 bool over_stroke = false;
427 if (item_over && nc->nodepath) {
428 over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), false);
429 }
431 if (over_stroke || nc->added_node) {
432 switch (event->type) {
433 case GDK_BUTTON_RELEASE:
434 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
435 //add a node
436 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
437 } else {
438 if (nc->added_node) { // we just received double click, ignore release
439 nc->added_node = false;
440 break;
441 }
442 //select the segment
443 if (event->button.state & GDK_SHIFT_MASK) {
444 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, true);
445 } else {
446 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
447 }
448 }
449 break;
450 case GDK_2BUTTON_PRESS:
451 //add a node
452 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
453 nc->added_node = true;
454 break;
455 default:
456 break;
457 }
458 } else if (event->button.state & GDK_SHIFT_MASK) {
459 selection->toggle(item_clicked);
460 } else {
461 selection->set(item_clicked);
462 }
464 ret = TRUE;
465 }
466 break;
467 }
468 break;
469 case GDK_BUTTON_PRESS:
470 if (event->button.button == 1 && !(event->button.state & GDK_SHIFT_MASK)) {
471 // save drag origin
472 event_context->xp = (gint) event->button.x;
473 event_context->yp = (gint) event->button.y;
474 event_context->within_tolerance = true;
475 nc->hit = false;
477 if (!nc->drag) {
478 // find out if we're over the selected item, disregarding groups
479 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
480 NR::Point(event->button.x, event->button.y));
482 if (nc->nodepath && selection->single() && item_over) {
484 // save drag origin
485 bool over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), true);
486 //only dragging curves
487 if (over_stroke) {
488 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
489 ret = TRUE;
490 } else {
491 break;
492 }
493 } else {
494 break;
495 }
497 ret = TRUE;
498 }
499 break;
500 }
501 break;
502 default:
503 break;
504 }
506 if (!ret) {
507 if (((SPEventContextClass *) parent_class)->item_handler)
508 ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
509 }
511 return ret;
512 }
514 static gint
515 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
516 {
517 SPDesktop *desktop = event_context->desktop;
518 Inkscape::Selection *selection = sp_desktop_selection (desktop);
520 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
521 double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
522 event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
523 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
524 double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
526 gint ret = FALSE;
528 switch (event->type) {
529 case GDK_BUTTON_PRESS:
530 if (event->button.button == 1) {
531 // save drag origin
532 event_context->xp = (gint) event->button.x;
533 event_context->yp = (gint) event->button.y;
534 event_context->within_tolerance = true;
535 nc->hit = false;
537 NR::Point const button_w(event->button.x,
538 event->button.y);
539 NR::Point const button_dt(desktop->w2d(button_w));
540 Inkscape::Rubberband::get()->start(desktop, button_dt);
541 ret = TRUE;
542 }
543 break;
544 case GDK_MOTION_NOTIFY:
545 if (event->motion.state & GDK_BUTTON1_MASK) {
547 if ( event_context->within_tolerance
548 && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
549 && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
550 break; // do not drag if we're within tolerance from origin
551 }
552 // Once the user has moved farther than tolerance from the original location
553 // (indicating they intend to move the object, not click), then always process the
554 // motion notify coordinates as given (no snapping back to origin)
555 event_context->within_tolerance = false;
557 if (nc->nodepath && nc->hit) {
558 NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
559 event->motion.y - nc->curvepoint_event[NR::Y]);
560 NR::Point const delta_dt(desktop->w2d(delta_w));
561 sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
562 nc->curvepoint_event[NR::X] = (gint) event->motion.x;
563 nc->curvepoint_event[NR::Y] = (gint) event->motion.y;
564 gobble_motion_events(GDK_BUTTON1_MASK);
565 } else {
566 NR::Point const motion_w(event->motion.x,
567 event->motion.y);
568 NR::Point const motion_dt(desktop->w2d(motion_w));
569 Inkscape::Rubberband::get()->move(motion_dt);
570 }
571 nc->drag = TRUE;
572 ret = TRUE;
573 } else {
574 if (!nc->nodepath || selection->singleItem() == NULL) {
575 break;
576 }
578 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
579 NR::Point(event->motion.x, event->motion.y));
580 bool over_stroke = false;
581 if (item_over && nc->nodepath) {
582 over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
583 }
585 if (nc->cursor_drag && !over_stroke) {
586 event_context->cursor_shape = cursor_node_xpm;
587 event_context->hot_x = 1;
588 event_context->hot_y = 1;
589 sp_event_context_update_cursor(event_context);
590 nc->cursor_drag = false;
591 } else if (!nc->cursor_drag && over_stroke) {
592 event_context->cursor_shape = cursor_node_d_xpm;
593 event_context->hot_x = 1;
594 event_context->hot_y = 1;
595 sp_event_context_update_cursor(event_context);
596 nc->cursor_drag = true;
597 }
598 }
599 break;
600 case GDK_BUTTON_RELEASE:
601 event_context->xp = event_context->yp = 0;
602 if (event->button.button == 1) {
604 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
606 if (nc->hit && !event_context->within_tolerance) { //drag curve
607 sp_nodepath_update_repr (nc->nodepath);
608 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
609 if (nc->nodepath) {
610 sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
611 }
612 } else {
613 if (!(nc->rb_escaped)) { // unless something was cancelled
614 if (nc->nodepath && nc->nodepath->selected)
615 sp_nodepath_deselect(nc->nodepath);
616 else
617 sp_desktop_selection(desktop)->clear();
618 }
619 }
620 ret = TRUE;
621 Inkscape::Rubberband::get()->stop();
622 nc->rb_escaped = false;
623 nc->drag = FALSE;
624 nc->hit = false;
625 break;
626 }
627 break;
628 case GDK_KEY_PRESS:
629 switch (get_group0_keyval(&event->key)) {
630 case GDK_Insert:
631 case GDK_KP_Insert:
632 // with any modifiers
633 sp_node_selected_add_node();
634 ret = TRUE;
635 break;
636 case GDK_Delete:
637 case GDK_KP_Delete:
638 case GDK_BackSpace:
639 if (MOD__CTRL_ONLY) {
640 sp_node_selected_delete();
641 } else {
642 if (nc->nodepath && nc->nodepath->selected) {
643 sp_node_delete_preserve(g_list_copy(nc->nodepath->selected));
644 }
645 }
646 ret = TRUE;
647 break;
648 case GDK_C:
649 case GDK_c:
650 if (MOD__SHIFT_ONLY) {
651 sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
652 ret = TRUE;
653 }
654 break;
655 case GDK_S:
656 case GDK_s:
657 if (MOD__SHIFT_ONLY) {
658 sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
659 ret = TRUE;
660 }
661 break;
662 case GDK_Y:
663 case GDK_y:
664 if (MOD__SHIFT_ONLY) {
665 sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
666 ret = TRUE;
667 }
668 break;
669 case GDK_B:
670 case GDK_b:
671 if (MOD__SHIFT_ONLY) {
672 sp_node_selected_break();
673 ret = TRUE;
674 }
675 break;
676 case GDK_J:
677 case GDK_j:
678 if (MOD__SHIFT_ONLY) {
679 sp_node_selected_join();
680 ret = TRUE;
681 }
682 break;
683 case GDK_D:
684 case GDK_d:
685 if (MOD__SHIFT_ONLY) {
686 sp_node_selected_duplicate();
687 ret = TRUE;
688 }
689 break;
690 case GDK_L:
691 case GDK_l:
692 if (MOD__SHIFT_ONLY) {
693 sp_node_selected_set_line_type(NR_LINETO);
694 ret = TRUE;
695 }
696 break;
697 case GDK_U:
698 case GDK_u:
699 if (MOD__SHIFT_ONLY) {
700 sp_node_selected_set_line_type(NR_CURVETO);
701 ret = TRUE;
702 }
703 break;
704 case GDK_R:
705 case GDK_r:
706 if (MOD__SHIFT_ONLY) {
707 // FIXME: add top panel button
708 sp_selected_path_reverse();
709 ret = TRUE;
710 }
711 break;
712 case GDK_Left: // move selection left
713 case GDK_KP_Left:
714 case GDK_KP_4:
715 if (!MOD__CTRL) { // not ctrl
716 if (MOD__ALT) { // alt
717 if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
718 else sp_node_selected_move_screen(-1, 0); // no shift
719 }
720 else { // no alt
721 if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
722 else sp_node_selected_move(-nudge, 0); // no shift
723 }
724 ret = TRUE;
725 }
726 break;
727 case GDK_Up: // move selection up
728 case GDK_KP_Up:
729 case GDK_KP_8:
730 if (!MOD__CTRL) { // not ctrl
731 if (MOD__ALT) { // alt
732 if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
733 else sp_node_selected_move_screen(0, 1); // no shift
734 }
735 else { // no alt
736 if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
737 else sp_node_selected_move(0, nudge); // no shift
738 }
739 ret = TRUE;
740 }
741 break;
742 case GDK_Right: // move selection right
743 case GDK_KP_Right:
744 case GDK_KP_6:
745 if (!MOD__CTRL) { // not ctrl
746 if (MOD__ALT) { // alt
747 if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
748 else sp_node_selected_move_screen(1, 0); // no shift
749 }
750 else { // no alt
751 if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
752 else sp_node_selected_move(nudge, 0); // no shift
753 }
754 ret = TRUE;
755 }
756 break;
757 case GDK_Down: // move selection down
758 case GDK_KP_Down:
759 case GDK_KP_2:
760 if (!MOD__CTRL) { // not ctrl
761 if (MOD__ALT) { // alt
762 if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
763 else sp_node_selected_move_screen(0, -1); // no shift
764 }
765 else { // no alt
766 if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
767 else sp_node_selected_move(0, -nudge); // no shift
768 }
769 ret = TRUE;
770 }
771 break;
772 case GDK_Tab: // Tab - cycle selection forward
773 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
774 sp_nodepath_select_next(nc->nodepath);
775 ret = TRUE;
776 }
777 break;
778 case GDK_ISO_Left_Tab: // Shift Tab - cycle selection backward
779 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
780 sp_nodepath_select_prev(nc->nodepath);
781 ret = TRUE;
782 }
783 break;
784 case GDK_Escape:
785 {
786 NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
787 if (b != NR::Nothing()) {
788 Inkscape::Rubberband::get()->stop();
789 nc->rb_escaped = true;
790 } else {
791 if (nc->nodepath && nc->nodepath->selected) {
792 sp_nodepath_deselect(nc->nodepath);
793 } else {
794 sp_desktop_selection(desktop)->clear();
795 }
796 }
797 ret = TRUE;
798 break;
799 }
801 case GDK_bracketleft:
802 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
803 if (nc->leftctrl)
804 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
805 if (nc->rightctrl)
806 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
807 } else if ( MOD__ALT && !MOD__CTRL ) {
808 if (nc->leftalt && nc->rightalt)
809 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
810 else {
811 if (nc->leftalt)
812 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
813 if (nc->rightalt)
814 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
815 }
816 } else if ( snaps != 0 ) {
817 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
818 }
819 ret = TRUE;
820 break;
821 case GDK_bracketright:
822 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
823 if (nc->leftctrl)
824 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
825 if (nc->rightctrl)
826 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
827 } else if ( MOD__ALT && !MOD__CTRL ) {
828 if (nc->leftalt && nc->rightalt)
829 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
830 else {
831 if (nc->leftalt)
832 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
833 if (nc->rightalt)
834 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
835 }
836 } else if ( snaps != 0 ) {
837 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
838 }
839 ret = TRUE;
840 break;
841 case GDK_less:
842 case GDK_comma:
843 if (MOD__CTRL) {
844 if (nc->leftctrl)
845 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
846 if (nc->rightctrl)
847 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
848 } else if (MOD__ALT) {
849 if (nc->leftalt && nc->rightalt)
850 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
851 else {
852 if (nc->leftalt)
853 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
854 if (nc->rightalt)
855 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
856 }
857 } else {
858 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
859 }
860 ret = TRUE;
861 break;
862 case GDK_greater:
863 case GDK_period:
864 if (MOD__CTRL) {
865 if (nc->leftctrl)
866 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
867 if (nc->rightctrl)
868 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
869 } else if (MOD__ALT) {
870 if (nc->leftalt && nc->rightalt)
871 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
872 else {
873 if (nc->leftalt)
874 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
875 if (nc->rightalt)
876 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
877 }
878 } else {
879 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
880 }
881 ret = TRUE;
882 break;
884 case GDK_Alt_L:
885 nc->leftalt = TRUE;
886 sp_node_context_show_modifier_tip(event_context, event);
887 break;
888 case GDK_Alt_R:
889 nc->rightalt = TRUE;
890 sp_node_context_show_modifier_tip(event_context, event);
891 break;
892 case GDK_Control_L:
893 nc->leftctrl = TRUE;
894 sp_node_context_show_modifier_tip(event_context, event);
895 break;
896 case GDK_Control_R:
897 nc->rightctrl = TRUE;
898 sp_node_context_show_modifier_tip(event_context, event);
899 break;
900 case GDK_Shift_L:
901 case GDK_Shift_R:
902 case GDK_Meta_L:
903 case GDK_Meta_R:
904 sp_node_context_show_modifier_tip(event_context, event);
905 break;
906 default:
907 ret = node_key(event);
908 break;
909 }
910 break;
911 case GDK_KEY_RELEASE:
912 switch (get_group0_keyval(&event->key)) {
913 case GDK_Alt_L:
914 nc->leftalt = FALSE;
915 event_context->defaultMessageContext()->clear();
916 break;
917 case GDK_Alt_R:
918 nc->rightalt = FALSE;
919 event_context->defaultMessageContext()->clear();
920 break;
921 case GDK_Control_L:
922 nc->leftctrl = FALSE;
923 event_context->defaultMessageContext()->clear();
924 break;
925 case GDK_Control_R:
926 nc->rightctrl = FALSE;
927 event_context->defaultMessageContext()->clear();
928 break;
929 case GDK_Shift_L:
930 case GDK_Shift_R:
931 case GDK_Meta_L:
932 case GDK_Meta_R:
933 event_context->defaultMessageContext()->clear();
934 break;
935 }
936 break;
937 default:
938 break;
939 }
941 if (!ret) {
942 if (((SPEventContextClass *) parent_class)->root_handler)
943 ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
944 }
946 return ret;
947 }
950 /*
951 Local Variables:
952 mode:c++
953 c-file-style:"stroustrup"
954 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
955 indent-tabs-mode:nil
956 fill-column:99
957 End:
958 */
959 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :