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