1e01b1af9ac59e00602c3087623db0455cd4e89e
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_DT_SELECTION(ec->desktop)->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc));
164 Inkscape::Selection *selection = SP_DT_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_DT_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_DT_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_DT_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_DT_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 // with any modifiers
640 sp_node_selected_delete();
641 ret = TRUE;
642 break;
643 case GDK_C:
644 case GDK_c:
645 if (MOD__SHIFT_ONLY) {
646 sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
647 ret = TRUE;
648 }
649 break;
650 case GDK_S:
651 case GDK_s:
652 if (MOD__SHIFT_ONLY) {
653 sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
654 ret = TRUE;
655 }
656 break;
657 case GDK_Y:
658 case GDK_y:
659 if (MOD__SHIFT_ONLY) {
660 sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
661 ret = TRUE;
662 }
663 break;
664 case GDK_B:
665 case GDK_b:
666 if (MOD__SHIFT_ONLY) {
667 sp_node_selected_break();
668 ret = TRUE;
669 }
670 break;
671 case GDK_J:
672 case GDK_j:
673 if (MOD__SHIFT_ONLY) {
674 sp_node_selected_join();
675 ret = TRUE;
676 }
677 break;
678 case GDK_D:
679 case GDK_d:
680 if (MOD__SHIFT_ONLY) {
681 sp_node_selected_duplicate();
682 ret = TRUE;
683 }
684 break;
685 case GDK_L:
686 case GDK_l:
687 if (MOD__SHIFT_ONLY) {
688 sp_node_selected_set_line_type(NR_LINETO);
689 ret = TRUE;
690 }
691 break;
692 case GDK_U:
693 case GDK_u:
694 if (MOD__SHIFT_ONLY) {
695 sp_node_selected_set_line_type(NR_CURVETO);
696 ret = TRUE;
697 }
698 break;
699 case GDK_R:
700 case GDK_r:
701 if (MOD__SHIFT_ONLY) {
702 // FIXME: add top panel button
703 sp_selected_path_reverse();
704 ret = TRUE;
705 }
706 break;
707 case GDK_Left: // move selection left
708 case GDK_KP_Left:
709 case GDK_KP_4:
710 if (!MOD__CTRL) { // not ctrl
711 if (MOD__ALT) { // alt
712 if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
713 else sp_node_selected_move_screen(-1, 0); // no shift
714 }
715 else { // no alt
716 if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
717 else sp_node_selected_move(-nudge, 0); // no shift
718 }
719 ret = TRUE;
720 }
721 break;
722 case GDK_Up: // move selection up
723 case GDK_KP_Up:
724 case GDK_KP_8:
725 if (!MOD__CTRL) { // not ctrl
726 if (MOD__ALT) { // alt
727 if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
728 else sp_node_selected_move_screen(0, 1); // no shift
729 }
730 else { // no alt
731 if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
732 else sp_node_selected_move(0, nudge); // no shift
733 }
734 ret = TRUE;
735 }
736 break;
737 case GDK_Right: // move selection right
738 case GDK_KP_Right:
739 case GDK_KP_6:
740 if (!MOD__CTRL) { // not ctrl
741 if (MOD__ALT) { // alt
742 if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
743 else sp_node_selected_move_screen(1, 0); // no shift
744 }
745 else { // no alt
746 if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
747 else sp_node_selected_move(nudge, 0); // no shift
748 }
749 ret = TRUE;
750 }
751 break;
752 case GDK_Down: // move selection down
753 case GDK_KP_Down:
754 case GDK_KP_2:
755 if (!MOD__CTRL) { // not ctrl
756 if (MOD__ALT) { // alt
757 if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
758 else sp_node_selected_move_screen(0, -1); // no shift
759 }
760 else { // no alt
761 if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
762 else sp_node_selected_move(0, -nudge); // no shift
763 }
764 ret = TRUE;
765 }
766 break;
767 case GDK_Tab: // Tab - cycle selection forward
768 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
769 sp_nodepath_select_next(nc->nodepath);
770 ret = TRUE;
771 }
772 break;
773 case GDK_ISO_Left_Tab: // Shift Tab - cycle selection backward
774 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
775 sp_nodepath_select_prev(nc->nodepath);
776 ret = TRUE;
777 }
778 break;
779 case GDK_Escape:
780 {
781 NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
782 if (b != NR::Nothing()) {
783 Inkscape::Rubberband::get()->stop();
784 nc->rb_escaped = true;
785 } else {
786 if (nc->nodepath && nc->nodepath->selected) {
787 sp_nodepath_deselect(nc->nodepath);
788 } else {
789 SP_DT_SELECTION(desktop)->clear();
790 }
791 }
792 ret = TRUE;
793 break;
794 }
796 case GDK_bracketleft:
797 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
798 if (nc->leftctrl)
799 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
800 if (nc->rightctrl)
801 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
802 } else if ( MOD__ALT && !MOD__CTRL ) {
803 if (nc->leftalt && nc->rightalt)
804 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
805 else {
806 if (nc->leftalt)
807 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
808 if (nc->rightalt)
809 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
810 }
811 } else if ( snaps != 0 ) {
812 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
813 }
814 ret = TRUE;
815 break;
816 case GDK_bracketright:
817 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
818 if (nc->leftctrl)
819 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
820 if (nc->rightctrl)
821 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
822 } else if ( MOD__ALT && !MOD__CTRL ) {
823 if (nc->leftalt && nc->rightalt)
824 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
825 else {
826 if (nc->leftalt)
827 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
828 if (nc->rightalt)
829 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
830 }
831 } else if ( snaps != 0 ) {
832 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
833 }
834 ret = TRUE;
835 break;
836 case GDK_less:
837 case GDK_comma:
838 if (MOD__CTRL) {
839 if (nc->leftctrl)
840 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
841 if (nc->rightctrl)
842 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
843 } else if (MOD__ALT) {
844 if (nc->leftalt && nc->rightalt)
845 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
846 else {
847 if (nc->leftalt)
848 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
849 if (nc->rightalt)
850 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
851 }
852 } else {
853 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
854 }
855 ret = TRUE;
856 break;
857 case GDK_greater:
858 case GDK_period:
859 if (MOD__CTRL) {
860 if (nc->leftctrl)
861 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
862 if (nc->rightctrl)
863 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
864 } else if (MOD__ALT) {
865 if (nc->leftalt && nc->rightalt)
866 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
867 else {
868 if (nc->leftalt)
869 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
870 if (nc->rightalt)
871 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
872 }
873 } else {
874 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
875 }
876 ret = TRUE;
877 break;
879 case GDK_Alt_L:
880 nc->leftalt = TRUE;
881 sp_node_context_show_modifier_tip(event_context, event);
882 break;
883 case GDK_Alt_R:
884 nc->rightalt = TRUE;
885 sp_node_context_show_modifier_tip(event_context, event);
886 break;
887 case GDK_Control_L:
888 nc->leftctrl = TRUE;
889 sp_node_context_show_modifier_tip(event_context, event);
890 break;
891 case GDK_Control_R:
892 nc->rightctrl = TRUE;
893 sp_node_context_show_modifier_tip(event_context, event);
894 break;
895 case GDK_Shift_L:
896 case GDK_Shift_R:
897 case GDK_Meta_L:
898 case GDK_Meta_R:
899 sp_node_context_show_modifier_tip(event_context, event);
900 break;
901 default:
902 ret = node_key(event);
903 break;
904 }
905 break;
906 case GDK_KEY_RELEASE:
907 switch (get_group0_keyval(&event->key)) {
908 case GDK_Alt_L:
909 nc->leftalt = FALSE;
910 event_context->defaultMessageContext()->clear();
911 break;
912 case GDK_Alt_R:
913 nc->rightalt = FALSE;
914 event_context->defaultMessageContext()->clear();
915 break;
916 case GDK_Control_L:
917 nc->leftctrl = FALSE;
918 event_context->defaultMessageContext()->clear();
919 break;
920 case GDK_Control_R:
921 nc->rightctrl = FALSE;
922 event_context->defaultMessageContext()->clear();
923 break;
924 case GDK_Shift_L:
925 case GDK_Shift_R:
926 case GDK_Meta_L:
927 case GDK_Meta_R:
928 event_context->defaultMessageContext()->clear();
929 break;
930 }
931 break;
932 default:
933 break;
934 }
936 if (!ret) {
937 if (((SPEventContextClass *) parent_class)->root_handler)
938 ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
939 }
941 return ret;
942 }
945 /*
946 Local Variables:
947 mode:c++
948 c-file-style:"stroustrup"
949 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
950 indent-tabs-mode:nil
951 fill-column:99
952 End:
953 */
954 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :