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