ccdb6c79eec6331054034b16ba94c9774e16dd26
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 char const *newd = NULL, *newtypestr = NULL;
314 gboolean changed = FALSE;
316 g_assert(data);
317 SPNodeContext *nc = ((SPNodeContext *) data);
318 SPEventContext *ec = ((SPEventContext *) data);
319 g_assert(nc);
320 Inkscape::NodePath::Path *np = nc->nodepath;
321 SPKnotHolder *kh = ec->shape_knot_holder;
323 if (np) {
324 item = SP_ITEM(np->path);
325 if (!strcmp(name, "d")) {
326 newd = new_value;
327 changed = nodepath_repr_d_changed(np, new_value);
328 } else if (!strcmp(name, "sodipodi:nodetypes")) {
329 newtypestr = new_value;
330 changed = nodepath_repr_typestr_changed(np, new_value);
331 } else {
332 return;
333 // With paths, we only need to act if one of the path-affecting attributes has changed.
334 }
335 } else if (kh) {
336 item = SP_ITEM(kh->item);
337 changed = !(kh->local_change);
338 kh->local_change = FALSE;
339 }
340 if (np && changed) {
341 GList *saved = NULL;
342 SPDesktop *desktop = np->desktop;
343 g_assert(desktop);
344 Inkscape::Selection *selection = desktop->selection;
345 g_assert(selection);
347 saved = save_nodepath_selection(nc->nodepath);
348 sp_nodepath_update_from_item(nc, item);
349 if (nc->nodepath && saved) restore_nodepath_selection(nc->nodepath, saved);
351 } else if (kh && changed) {
352 sp_nodepath_update_from_item(nc, item);
353 }
355 sp_nodepath_update_statusbar(nc->nodepath);
356 }
358 void
359 sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event)
360 {
361 sp_event_show_modifier_tip
362 (event_context->defaultMessageContext(), event,
363 _("<b>Ctrl</b>: toggle node type, snap handle angle, move hor/vert; <b>Ctrl+Alt</b>: move along handles"),
364 _("<b>Shift</b>: toggle node selection, disable snapping, rotate both handles"),
365 _("<b>Alt</b>: lock handle length; <b>Ctrl+Alt</b>: move along handles"));
366 }
368 bool
369 sp_node_context_is_over_stroke (SPNodeContext *nc, SPItem *item, NR::Point event_p, bool remember)
370 {
371 SPDesktop *desktop = SP_EVENT_CONTEXT (nc)->desktop;
373 //Translate click point into proper coord system
374 nc->curvepoint_doc = desktop->w2d(event_p);
375 nc->curvepoint_doc *= sp_item_dt2i_affine(item);
376 nc->curvepoint_doc *= sp_item_i2doc_affine(item);
378 NR::Maybe<Path::cut_position> position = get_nearest_position_on_Path(nc->nodepath->livarot_path, nc->curvepoint_doc);
379 NR::Point nearest = get_point_on_Path(nc->nodepath->livarot_path, position.assume().piece, position.assume().t);
380 NR::Point delta = nearest - nc->curvepoint_doc;
382 delta = desktop->d2w(delta);
384 double stroke_tolerance =
385 (SP_OBJECT_STYLE (item)->stroke.type != SP_PAINT_TYPE_NONE?
386 desktop->current_zoom() *
387 SP_OBJECT_STYLE (item)->stroke_width.computed *
388 sp_item_i2d_affine (item).expansion() * 0.5
389 : 0.0)
390 + (double) SP_EVENT_CONTEXT(nc)->tolerance;
392 bool close = (NR::L2 (delta) < stroke_tolerance);
394 if (remember && close) {
395 nc->curvepoint_event[NR::X] = (gint) event_p [NR::X];
396 nc->curvepoint_event[NR::Y] = (gint) event_p [NR::Y];
397 nc->hit = true;
398 nc->grab_t = position.assume().t;
399 nc->grab_node = sp_nodepath_get_node_by_index(position.assume().piece);
400 }
402 return close;
403 }
406 static gint
407 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
408 {
409 gint ret = FALSE;
411 SPDesktop *desktop = event_context->desktop;
412 Inkscape::Selection *selection = SP_DT_SELECTION (desktop);
414 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
416 switch (event->type) {
417 case GDK_2BUTTON_PRESS:
418 case GDK_BUTTON_RELEASE:
419 if (event->button.button == 1) {
420 if (!nc->drag) {
422 // find out clicked item, disregarding groups, honoring Alt
423 SPItem *item_clicked = sp_event_context_find_item (desktop,
424 NR::Point(event->button.x, event->button.y),
425 (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
426 // find out if we're over the selected item, disregarding groups
427 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
428 NR::Point(event->button.x, event->button.y));
430 bool over_stroke = false;
431 if (item_over && nc->nodepath) {
432 over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), false);
433 }
435 if (over_stroke || nc->added_node) {
436 switch (event->type) {
437 case GDK_BUTTON_RELEASE:
438 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
439 //add a node
440 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
441 } else {
442 if (nc->added_node) { // we just received double click, ignore release
443 nc->added_node = false;
444 break;
445 }
446 //select the segment
447 if (event->button.state & GDK_SHIFT_MASK) {
448 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, true);
449 } else {
450 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
451 }
452 }
453 break;
454 case GDK_2BUTTON_PRESS:
455 //add a node
456 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
457 nc->added_node = true;
458 break;
459 default:
460 break;
461 }
462 } else if (event->button.state & GDK_SHIFT_MASK) {
463 selection->toggle(item_clicked);
464 } else {
465 selection->set(item_clicked);
466 }
468 ret = TRUE;
469 }
470 break;
471 }
472 break;
473 case GDK_BUTTON_PRESS:
474 if (event->button.button == 1 && !(event->button.state & GDK_SHIFT_MASK)) {
475 // save drag origin
476 event_context->xp = (gint) event->button.x;
477 event_context->yp = (gint) event->button.y;
478 event_context->within_tolerance = true;
479 nc->hit = false;
481 if (!nc->drag) {
482 // find out if we're over the selected item, disregarding groups
483 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
484 NR::Point(event->button.x, event->button.y));
486 if (nc->nodepath && selection->single() && item_over) {
488 // save drag origin
489 bool over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), true);
490 //only dragging curves
491 if (over_stroke) {
492 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
493 ret = TRUE;
494 } else {
495 break;
496 }
497 } else {
498 break;
499 }
501 ret = TRUE;
502 }
503 break;
504 }
505 break;
506 default:
507 break;
508 }
510 if (!ret) {
511 if (((SPEventContextClass *) parent_class)->item_handler)
512 ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
513 }
515 return ret;
516 }
518 static gint
519 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
520 {
521 SPDesktop *desktop = event_context->desktop;
522 Inkscape::Selection *selection = SP_DT_SELECTION (desktop);
524 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
525 double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
526 event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
527 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
528 double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
530 gint ret = FALSE;
532 switch (event->type) {
533 case GDK_BUTTON_PRESS:
534 if (event->button.button == 1) {
535 // save drag origin
536 event_context->xp = (gint) event->button.x;
537 event_context->yp = (gint) event->button.y;
538 event_context->within_tolerance = true;
539 nc->hit = false;
541 NR::Point const button_w(event->button.x,
542 event->button.y);
543 NR::Point const button_dt(desktop->w2d(button_w));
544 Inkscape::Rubberband::get()->start(desktop, button_dt);
545 ret = TRUE;
546 }
547 break;
548 case GDK_MOTION_NOTIFY:
549 if (event->motion.state & GDK_BUTTON1_MASK) {
551 if ( event_context->within_tolerance
552 && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
553 && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
554 break; // do not drag if we're within tolerance from origin
555 }
556 // Once the user has moved farther than tolerance from the original location
557 // (indicating they intend to move the object, not click), then always process the
558 // motion notify coordinates as given (no snapping back to origin)
559 event_context->within_tolerance = false;
561 if (nc->nodepath && nc->hit) {
562 NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
563 event->motion.y - nc->curvepoint_event[NR::Y]);
564 NR::Point const delta_dt(desktop->w2d(delta_w));
565 sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
566 nc->curvepoint_event[NR::X] = (gint) event->motion.x;
567 nc->curvepoint_event[NR::Y] = (gint) event->motion.y;
568 gobble_motion_events(GDK_BUTTON1_MASK);
569 } else {
570 NR::Point const motion_w(event->motion.x,
571 event->motion.y);
572 NR::Point const motion_dt(desktop->w2d(motion_w));
573 Inkscape::Rubberband::get()->move(motion_dt);
574 }
575 nc->drag = TRUE;
576 ret = TRUE;
577 } else {
578 if (!nc->nodepath || selection->singleItem() == NULL) {
579 break;
580 }
582 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
583 NR::Point(event->motion.x, event->motion.y));
584 bool over_stroke = false;
585 if (item_over && nc->nodepath) {
586 over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
587 }
589 if (nc->cursor_drag && !over_stroke) {
590 event_context->cursor_shape = cursor_node_xpm;
591 event_context->hot_x = 1;
592 event_context->hot_y = 1;
593 sp_event_context_update_cursor(event_context);
594 nc->cursor_drag = false;
595 } else if (!nc->cursor_drag && over_stroke) {
596 event_context->cursor_shape = cursor_node_d_xpm;
597 event_context->hot_x = 1;
598 event_context->hot_y = 1;
599 sp_event_context_update_cursor(event_context);
600 nc->cursor_drag = true;
601 }
602 }
603 break;
604 case GDK_BUTTON_RELEASE:
605 event_context->xp = event_context->yp = 0;
606 if (event->button.button == 1) {
608 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
610 if (nc->hit && !event_context->within_tolerance) { //drag curve
611 sp_nodepath_update_repr (nc->nodepath);
612 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
613 if (nc->nodepath) {
614 sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
615 }
616 } else {
617 if (!(nc->rb_escaped)) { // unless something was cancelled
618 if (nc->nodepath && nc->nodepath->selected)
619 sp_nodepath_deselect(nc->nodepath);
620 else
621 SP_DT_SELECTION(desktop)->clear();
622 }
623 }
624 ret = TRUE;
625 Inkscape::Rubberband::get()->stop();
626 nc->rb_escaped = false;
627 nc->drag = FALSE;
628 nc->hit = false;
629 break;
630 }
631 break;
632 case GDK_KEY_PRESS:
633 switch (get_group0_keyval(&event->key)) {
634 case GDK_Insert:
635 case GDK_KP_Insert:
636 // with any modifiers
637 sp_node_selected_add_node();
638 ret = TRUE;
639 break;
640 case GDK_Delete:
641 case GDK_KP_Delete:
642 case GDK_BackSpace:
643 // with any modifiers
644 sp_node_selected_delete();
645 ret = TRUE;
646 break;
647 case GDK_C:
648 case GDK_c:
649 if (MOD__SHIFT_ONLY) {
650 sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
651 ret = TRUE;
652 }
653 break;
654 case GDK_S:
655 case GDK_s:
656 if (MOD__SHIFT_ONLY) {
657 sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
658 ret = TRUE;
659 }
660 break;
661 case GDK_Y:
662 case GDK_y:
663 if (MOD__SHIFT_ONLY) {
664 sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
665 ret = TRUE;
666 }
667 break;
668 case GDK_B:
669 case GDK_b:
670 if (MOD__SHIFT_ONLY) {
671 sp_node_selected_break();
672 ret = TRUE;
673 }
674 break;
675 case GDK_J:
676 case GDK_j:
677 if (MOD__SHIFT_ONLY) {
678 sp_node_selected_join();
679 ret = TRUE;
680 }
681 break;
682 case GDK_D:
683 case GDK_d:
684 if (MOD__SHIFT_ONLY) {
685 sp_node_selected_duplicate();
686 ret = TRUE;
687 }
688 break;
689 case GDK_L:
690 case GDK_l:
691 if (MOD__SHIFT_ONLY) {
692 sp_node_selected_set_line_type(NR_LINETO);
693 ret = TRUE;
694 }
695 break;
696 case GDK_U:
697 case GDK_u:
698 if (MOD__SHIFT_ONLY) {
699 sp_node_selected_set_line_type(NR_CURVETO);
700 ret = TRUE;
701 }
702 break;
703 case GDK_R:
704 case GDK_r:
705 if (MOD__SHIFT_ONLY) {
706 // FIXME: add top panel button
707 sp_selected_path_reverse();
708 ret = TRUE;
709 }
710 break;
711 case GDK_Left: // move selection left
712 case GDK_KP_Left:
713 case GDK_KP_4:
714 if (!MOD__CTRL) { // not ctrl
715 if (MOD__ALT) { // alt
716 if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
717 else sp_node_selected_move_screen(-1, 0); // no shift
718 }
719 else { // no alt
720 if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
721 else sp_node_selected_move(-nudge, 0); // no shift
722 }
723 ret = TRUE;
724 }
725 break;
726 case GDK_Up: // move selection up
727 case GDK_KP_Up:
728 case GDK_KP_8:
729 if (!MOD__CTRL) { // not ctrl
730 if (MOD__ALT) { // alt
731 if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
732 else sp_node_selected_move_screen(0, 1); // no shift
733 }
734 else { // no alt
735 if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
736 else sp_node_selected_move(0, nudge); // no shift
737 }
738 ret = TRUE;
739 }
740 break;
741 case GDK_Right: // move selection right
742 case GDK_KP_Right:
743 case GDK_KP_6:
744 if (!MOD__CTRL) { // not ctrl
745 if (MOD__ALT) { // alt
746 if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
747 else sp_node_selected_move_screen(1, 0); // no shift
748 }
749 else { // no alt
750 if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
751 else sp_node_selected_move(nudge, 0); // no shift
752 }
753 ret = TRUE;
754 }
755 break;
756 case GDK_Down: // move selection down
757 case GDK_KP_Down:
758 case GDK_KP_2:
759 if (!MOD__CTRL) { // not ctrl
760 if (MOD__ALT) { // alt
761 if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
762 else sp_node_selected_move_screen(0, -1); // no shift
763 }
764 else { // no alt
765 if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
766 else sp_node_selected_move(0, -nudge); // no shift
767 }
768 ret = TRUE;
769 }
770 break;
771 case GDK_Tab: // Tab - cycle selection forward
772 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
773 sp_nodepath_select_next(nc->nodepath);
774 ret = TRUE;
775 }
776 break;
777 case GDK_ISO_Left_Tab: // Shift Tab - cycle selection backward
778 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
779 sp_nodepath_select_prev(nc->nodepath);
780 ret = TRUE;
781 }
782 break;
783 case GDK_Escape:
784 {
785 NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
786 if (b != NR::Nothing()) {
787 Inkscape::Rubberband::get()->stop();
788 nc->rb_escaped = true;
789 } else {
790 if (nc->nodepath && nc->nodepath->selected) {
791 sp_nodepath_deselect(nc->nodepath);
792 } else {
793 SP_DT_SELECTION(desktop)->clear();
794 }
795 }
796 ret = TRUE;
797 break;
798 }
800 case GDK_bracketleft:
801 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
802 if (nc->leftctrl)
803 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
804 if (nc->rightctrl)
805 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
806 } else if ( MOD__ALT && !MOD__CTRL ) {
807 if (nc->leftalt && nc->rightalt)
808 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
809 else {
810 if (nc->leftalt)
811 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
812 if (nc->rightalt)
813 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
814 }
815 } else if ( snaps != 0 ) {
816 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
817 }
818 ret = TRUE;
819 break;
820 case GDK_bracketright:
821 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
822 if (nc->leftctrl)
823 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
824 if (nc->rightctrl)
825 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
826 } else if ( MOD__ALT && !MOD__CTRL ) {
827 if (nc->leftalt && nc->rightalt)
828 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
829 else {
830 if (nc->leftalt)
831 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
832 if (nc->rightalt)
833 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
834 }
835 } else if ( snaps != 0 ) {
836 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
837 }
838 ret = TRUE;
839 break;
840 case GDK_less:
841 case GDK_comma:
842 if (MOD__CTRL) {
843 if (nc->leftctrl)
844 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
845 if (nc->rightctrl)
846 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
847 } else if (MOD__ALT) {
848 if (nc->leftalt && nc->rightalt)
849 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
850 else {
851 if (nc->leftalt)
852 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
853 if (nc->rightalt)
854 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
855 }
856 } else {
857 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
858 }
859 ret = TRUE;
860 break;
861 case GDK_greater:
862 case GDK_period:
863 if (MOD__CTRL) {
864 if (nc->leftctrl)
865 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
866 if (nc->rightctrl)
867 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
868 } else if (MOD__ALT) {
869 if (nc->leftalt && nc->rightalt)
870 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
871 else {
872 if (nc->leftalt)
873 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
874 if (nc->rightalt)
875 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
876 }
877 } else {
878 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
879 }
880 ret = TRUE;
881 break;
883 case GDK_Alt_L:
884 nc->leftalt = TRUE;
885 sp_node_context_show_modifier_tip(event_context, event);
886 break;
887 case GDK_Alt_R:
888 nc->rightalt = TRUE;
889 sp_node_context_show_modifier_tip(event_context, event);
890 break;
891 case GDK_Control_L:
892 nc->leftctrl = TRUE;
893 sp_node_context_show_modifier_tip(event_context, event);
894 break;
895 case GDK_Control_R:
896 nc->rightctrl = TRUE;
897 sp_node_context_show_modifier_tip(event_context, event);
898 break;
899 case GDK_Shift_L:
900 case GDK_Shift_R:
901 case GDK_Meta_L:
902 case GDK_Meta_R:
903 sp_node_context_show_modifier_tip(event_context, event);
904 break;
905 default:
906 ret = node_key(event);
907 break;
908 }
909 break;
910 case GDK_KEY_RELEASE:
911 switch (get_group0_keyval(&event->key)) {
912 case GDK_Alt_L:
913 nc->leftalt = FALSE;
914 event_context->defaultMessageContext()->clear();
915 break;
916 case GDK_Alt_R:
917 nc->rightalt = FALSE;
918 event_context->defaultMessageContext()->clear();
919 break;
920 case GDK_Control_L:
921 nc->leftctrl = FALSE;
922 event_context->defaultMessageContext()->clear();
923 break;
924 case GDK_Control_R:
925 nc->rightctrl = FALSE;
926 event_context->defaultMessageContext()->clear();
927 break;
928 case GDK_Shift_L:
929 case GDK_Shift_R:
930 case GDK_Meta_L:
931 case GDK_Meta_R:
932 event_context->defaultMessageContext()->clear();
933 break;
934 }
935 break;
936 default:
937 break;
938 }
940 if (!ret) {
941 if (((SPEventContextClass *) parent_class)->root_handler)
942 ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
943 }
945 return ret;
946 }
949 /*
950 Local Variables:
951 mode:c++
952 c-file-style:"stroustrup"
953 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
954 indent-tabs-mode:nil
955 fill-column:99
956 End:
957 */
958 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :