1 #define __SP_NODE_CONTEXT_C__
3 /*
4 * Node editing context
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 *
10 * This code is in public domain, except stamping code,
11 * which is Copyright (C) Masatake Yamato 2002
12 */
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
17 #include <gdk/gdkkeysyms.h>
18 #include "macros.h"
19 #include <glibmm/i18n.h>
20 #include "display/sp-canvas-util.h"
21 #include "object-edit.h"
22 #include "sp-path.h"
23 #include "path-chemistry.h"
24 #include "rubberband.h"
25 #include "desktop.h"
26 #include "desktop-handles.h"
27 #include "selection.h"
28 #include "pixmaps/cursor-node.xpm"
29 #include "message-context.h"
30 #include "node-context.h"
31 #include "pixmaps/cursor-node-d.xpm"
32 #include "prefs-utils.h"
33 #include "xml/node-event-vector.h"
34 #include "style.h"
35 #include "splivarot.h"
37 static void sp_node_context_class_init(SPNodeContextClass *klass);
38 static void sp_node_context_init(SPNodeContext *node_context);
39 static void sp_node_context_dispose(GObject *object);
41 static void sp_node_context_setup(SPEventContext *ec);
42 static gint sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event);
43 static gint sp_node_context_item_handler(SPEventContext *event_context,
44 SPItem *item, GdkEvent *event);
46 static void nodepath_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
47 gchar const *old_value, gchar const *new_value,
48 bool is_interactive, gpointer data);
50 static Inkscape::XML::NodeEventVector nodepath_repr_events = {
51 NULL, /* child_added */
52 NULL, /* child_removed */
53 nodepath_event_attr_changed,
54 NULL, /* content_changed */
55 NULL /* order_changed */
56 };
58 static SPEventContextClass *parent_class;
60 GType
61 sp_node_context_get_type()
62 {
63 static GType type = 0;
64 if (!type) {
65 GTypeInfo info = {
66 sizeof(SPNodeContextClass),
67 NULL, NULL,
68 (GClassInitFunc) sp_node_context_class_init,
69 NULL, NULL,
70 sizeof(SPNodeContext),
71 4,
72 (GInstanceInitFunc) sp_node_context_init,
73 NULL, /* value_table */
74 };
75 type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPNodeContext", &info, (GTypeFlags)0);
76 }
77 return type;
78 }
80 static void
81 sp_node_context_class_init(SPNodeContextClass *klass)
82 {
83 GObjectClass *object_class = (GObjectClass *) klass;
84 SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
86 parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
88 object_class->dispose = sp_node_context_dispose;
90 event_context_class->setup = sp_node_context_setup;
91 event_context_class->root_handler = sp_node_context_root_handler;
92 event_context_class->item_handler = sp_node_context_item_handler;
93 }
95 static void
96 sp_node_context_init(SPNodeContext *node_context)
97 {
98 SPEventContext *event_context = SP_EVENT_CONTEXT(node_context);
100 event_context->cursor_shape = cursor_node_xpm;
101 event_context->hot_x = 1;
102 event_context->hot_y = 1;
104 node_context->leftalt = FALSE;
105 node_context->rightalt = FALSE;
106 node_context->leftctrl = FALSE;
107 node_context->rightctrl = FALSE;
109 new (&node_context->sel_changed_connection) sigc::connection();
110 }
112 static void
113 sp_node_context_dispose(GObject *object)
114 {
115 SPNodeContext *nc = SP_NODE_CONTEXT(object);
116 SPEventContext *ec = SP_EVENT_CONTEXT(object);
118 ec->enableGrDrag(false);
120 nc->sel_changed_connection.disconnect();
121 nc->sel_changed_connection.~connection();
123 Inkscape::XML::Node *repr = NULL;
124 if (nc->nodepath) {
125 repr = nc->nodepath->repr;
126 }
127 if (!repr && ec->shape_knot_holder) {
128 repr = ec->shape_knot_holder->repr;
129 }
131 if (repr) {
132 sp_repr_remove_listener_by_data(repr, ec);
133 Inkscape::GC::release(repr);
134 }
136 if (nc->nodepath) {
137 sp_nodepath_destroy(nc->nodepath);
138 nc->nodepath = NULL;
139 }
141 if (ec->shape_knot_holder) {
142 sp_knot_holder_destroy(ec->shape_knot_holder);
143 ec->shape_knot_holder = NULL;
144 }
146 if (nc->_node_message_context) {
147 delete nc->_node_message_context;
148 }
150 G_OBJECT_CLASS(parent_class)->dispose(object);
151 }
153 static void
154 sp_node_context_setup(SPEventContext *ec)
155 {
156 SPNodeContext *nc = SP_NODE_CONTEXT(ec);
158 if (((SPEventContextClass *) parent_class)->setup)
159 ((SPEventContextClass *) parent_class)->setup(ec);
161 nc->sel_changed_connection.disconnect();
162 nc->sel_changed_connection = sp_desktop_selection(ec->desktop)->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc));
164 Inkscape::Selection *selection = sp_desktop_selection(ec->desktop);
165 SPItem *item = selection->singleItem();
167 nc->nodepath = NULL;
168 ec->shape_knot_holder = NULL;
170 nc->rb_escaped = false;
172 nc->cursor_drag = false;
174 nc->added_node = false;
176 if (item) {
177 nc->nodepath = sp_nodepath_new(ec->desktop, item, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
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, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
245 if (nc->nodepath) {
246 nc->nodepath->nodeContext = nc;
247 }
248 ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
250 if (nc->nodepath || ec->shape_knot_holder) {
251 // setting new listener
252 Inkscape::XML::Node *repr;
253 if (ec->shape_knot_holder)
254 repr = ec->shape_knot_holder->repr;
255 else
256 repr = SP_OBJECT_REPR(item);
257 if (repr) {
258 Inkscape::GC::anchor(repr);
259 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
260 }
261 }
262 }
263 sp_nodepath_update_statusbar(nc->nodepath);
264 }
266 /**
267 \brief Regenerates nodepath when the item's repr was change outside of node edit
268 (e.g. by undo, or xml editor, or edited in another view). The item is assumed to be the same
269 (otherwise sp_node_context_selection_changed() would have been called), so repr and listeners
270 are not changed.
271 */
272 void
273 sp_nodepath_update_from_item(SPNodeContext *nc, SPItem *item)
274 {
275 g_assert(nc);
276 SPEventContext *ec = ((SPEventContext *) nc);
278 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(SP_EVENT_CONTEXT(nc));
279 g_assert(desktop);
281 if (nc->nodepath) {
282 sp_nodepath_destroy(nc->nodepath);
283 nc->nodepath = NULL;
284 }
286 if (ec->shape_knot_holder) {
287 sp_knot_holder_destroy(ec->shape_knot_holder);
288 ec->shape_knot_holder = NULL;
289 }
291 Inkscape::Selection *selection = sp_desktop_selection(desktop);
292 item = selection->singleItem();
294 if (item) {
295 nc->nodepath = sp_nodepath_new(desktop, item, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
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 sp_nodepath_ensure_livarot_path(nc->nodepath);
375 NR::Maybe<Path::cut_position> position = get_nearest_position_on_Path(nc->nodepath->livarot_path, nc->curvepoint_doc);
376 NR::Point nearest = get_point_on_Path(nc->nodepath->livarot_path, position.assume().piece, position.assume().t);
377 NR::Point delta = nearest - nc->curvepoint_doc;
379 delta = desktop->d2w(delta);
381 double stroke_tolerance =
382 (SP_OBJECT_STYLE (item)->stroke.type != SP_PAINT_TYPE_NONE?
383 desktop->current_zoom() *
384 SP_OBJECT_STYLE (item)->stroke_width.computed *
385 sp_item_i2d_affine (item).expansion() * 0.5
386 : 0.0)
387 + (double) SP_EVENT_CONTEXT(nc)->tolerance;
389 bool close = (NR::L2 (delta) < stroke_tolerance);
391 if (remember && close) {
392 nc->curvepoint_event[NR::X] = (gint) event_p [NR::X];
393 nc->curvepoint_event[NR::Y] = (gint) event_p [NR::Y];
394 nc->hit = true;
395 nc->grab_t = position.assume().t;
396 nc->grab_node = sp_nodepath_get_node_by_index(position.assume().piece);
397 }
399 return close;
400 }
403 static gint
404 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
405 {
406 gint ret = FALSE;
408 SPDesktop *desktop = event_context->desktop;
409 Inkscape::Selection *selection = sp_desktop_selection (desktop);
411 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
413 switch (event->type) {
414 case GDK_2BUTTON_PRESS:
415 case GDK_BUTTON_RELEASE:
416 if (event->button.button == 1) {
417 if (!nc->drag) {
419 // find out clicked item, disregarding groups, honoring Alt
420 SPItem *item_clicked = sp_event_context_find_item (desktop,
421 NR::Point(event->button.x, event->button.y),
422 (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
423 // find out if we're over the selected item, disregarding groups
424 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
425 NR::Point(event->button.x, event->button.y));
427 bool over_stroke = false;
428 if (item_over && nc->nodepath) {
429 over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), false);
430 }
432 if (over_stroke || nc->added_node) {
433 switch (event->type) {
434 case GDK_BUTTON_RELEASE:
435 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
436 //add a node
437 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
438 } else {
439 if (nc->added_node) { // we just received double click, ignore release
440 nc->added_node = false;
441 break;
442 }
443 //select the segment
444 if (event->button.state & GDK_SHIFT_MASK) {
445 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, true);
446 } else {
447 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
448 }
449 desktop->updateNow();
450 }
451 break;
452 case GDK_2BUTTON_PRESS:
453 //add a node
454 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
455 nc->added_node = true;
456 break;
457 default:
458 break;
459 }
460 } else if (event->button.state & GDK_SHIFT_MASK) {
461 selection->toggle(item_clicked);
462 desktop->updateNow();
463 } else {
464 selection->set(item_clicked);
465 desktop->updateNow();
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_desktop_selection (desktop);
524 // fixme: nc->nodepath can potentially become NULL after retrieving nc.
525 // A general method for handling this possibility should be created.
526 // For now, the number of checks for a NULL nc->nodepath have been
527 // increased, both here and in the called sp_nodepath_* functions.
529 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
530 double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
531 event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
532 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
533 double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
535 gint ret = FALSE;
537 switch (event->type) {
538 case GDK_BUTTON_PRESS:
539 if (event->button.button == 1) {
540 // save drag origin
541 event_context->xp = (gint) event->button.x;
542 event_context->yp = (gint) event->button.y;
543 event_context->within_tolerance = true;
544 nc->hit = false;
546 NR::Point const button_w(event->button.x,
547 event->button.y);
548 NR::Point const button_dt(desktop->w2d(button_w));
549 Inkscape::Rubberband::get()->start(desktop, button_dt);
550 desktop->updateNow();
551 ret = TRUE;
552 }
553 break;
554 case GDK_MOTION_NOTIFY:
555 if (event->motion.state & GDK_BUTTON1_MASK) {
557 if ( event_context->within_tolerance
558 && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
559 && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
560 break; // do not drag if we're within tolerance from origin
561 }
563 // The path went away while dragging; throw away any further motion
564 // events until the mouse pointer is released.
565 if (nc->hit && (nc->nodepath == NULL)) {
566 break;
567 }
569 // Once the user has moved farther than tolerance from the original location
570 // (indicating they intend to move the object, not click), then always process the
571 // motion notify coordinates as given (no snapping back to origin)
572 event_context->within_tolerance = false;
574 if (nc->nodepath && nc->hit) {
575 NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
576 event->motion.y - nc->curvepoint_event[NR::Y]);
577 NR::Point const delta_dt(desktop->w2d(delta_w));
578 sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
579 nc->curvepoint_event[NR::X] = (gint) event->motion.x;
580 nc->curvepoint_event[NR::Y] = (gint) event->motion.y;
581 gobble_motion_events(GDK_BUTTON1_MASK);
582 } else {
583 if (Inkscape::Rubberband::get()->is_started()) {
584 NR::Point const motion_w(event->motion.x,
585 event->motion.y);
586 NR::Point const motion_dt(desktop->w2d(motion_w));
587 Inkscape::Rubberband::get()->move(motion_dt);
588 }
589 }
590 nc->drag = TRUE;
591 ret = TRUE;
592 } else {
593 if (!nc->nodepath || selection->singleItem() == NULL) {
594 break;
595 }
597 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
598 NR::Point(event->motion.x, event->motion.y));
599 bool over_stroke = false;
600 if (item_over && nc->nodepath) {
601 over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
602 }
604 if (nc->cursor_drag && !over_stroke) {
605 event_context->cursor_shape = cursor_node_xpm;
606 event_context->hot_x = 1;
607 event_context->hot_y = 1;
608 sp_event_context_update_cursor(event_context);
609 nc->cursor_drag = false;
610 } else if (!nc->cursor_drag && over_stroke) {
611 event_context->cursor_shape = cursor_node_d_xpm;
612 event_context->hot_x = 1;
613 event_context->hot_y = 1;
614 sp_event_context_update_cursor(event_context);
615 nc->cursor_drag = true;
616 }
617 }
618 break;
619 case GDK_BUTTON_RELEASE:
620 event_context->xp = event_context->yp = 0;
621 if (event->button.button == 1) {
623 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
625 if (nc->hit && !event_context->within_tolerance) { //drag curve
626 if (nc->nodepath) {
627 sp_nodepath_update_repr (nc->nodepath, _("Drag curve"));
628 }
629 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
630 if (nc->nodepath) {
631 sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
632 }
633 } else {
634 if (!(nc->rb_escaped)) { // unless something was cancelled
635 if (nc->nodepath && nc->nodepath->selected)
636 sp_nodepath_deselect(nc->nodepath);
637 else
638 sp_desktop_selection(desktop)->clear();
639 }
640 }
641 ret = TRUE;
642 Inkscape::Rubberband::get()->stop();
643 desktop->updateNow();
644 nc->rb_escaped = false;
645 nc->drag = FALSE;
646 nc->hit = false;
647 break;
648 }
649 break;
650 case GDK_KEY_PRESS:
651 switch (get_group0_keyval(&event->key)) {
652 case GDK_Insert:
653 case GDK_KP_Insert:
654 // with any modifiers
655 sp_node_selected_add_node();
656 ret = TRUE;
657 break;
658 case GDK_Delete:
659 case GDK_KP_Delete:
660 case GDK_BackSpace:
661 if (MOD__CTRL_ONLY) {
662 sp_node_selected_delete();
663 } else {
664 if (nc->nodepath && nc->nodepath->selected) {
665 sp_node_delete_preserve(g_list_copy(nc->nodepath->selected));
666 }
667 }
668 ret = TRUE;
669 break;
670 case GDK_C:
671 case GDK_c:
672 if (MOD__SHIFT_ONLY) {
673 sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
674 ret = TRUE;
675 }
676 break;
677 case GDK_S:
678 case GDK_s:
679 if (MOD__SHIFT_ONLY) {
680 sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
681 ret = TRUE;
682 }
683 break;
684 case GDK_Y:
685 case GDK_y:
686 if (MOD__SHIFT_ONLY) {
687 sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
688 ret = TRUE;
689 }
690 break;
691 case GDK_B:
692 case GDK_b:
693 if (MOD__SHIFT_ONLY) {
694 sp_node_selected_break();
695 ret = TRUE;
696 }
697 break;
698 case GDK_J:
699 case GDK_j:
700 if (MOD__SHIFT_ONLY) {
701 sp_node_selected_join();
702 ret = TRUE;
703 }
704 break;
705 case GDK_D:
706 case GDK_d:
707 if (MOD__SHIFT_ONLY) {
708 sp_node_selected_duplicate();
709 ret = TRUE;
710 }
711 break;
712 case GDK_L:
713 case GDK_l:
714 if (MOD__SHIFT_ONLY) {
715 sp_node_selected_set_line_type(NR_LINETO);
716 ret = TRUE;
717 }
718 break;
719 case GDK_U:
720 case GDK_u:
721 if (MOD__SHIFT_ONLY) {
722 sp_node_selected_set_line_type(NR_CURVETO);
723 ret = TRUE;
724 }
725 break;
726 case GDK_R:
727 case GDK_r:
728 if (MOD__SHIFT_ONLY) {
729 // FIXME: add top panel button
730 sp_selected_path_reverse();
731 ret = TRUE;
732 }
733 break;
734 case GDK_Left: // move selection left
735 case GDK_KP_Left:
736 case GDK_KP_4:
737 if (!MOD__CTRL) { // not ctrl
738 if (MOD__ALT) { // alt
739 if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
740 else sp_node_selected_move_screen(-1, 0); // no shift
741 }
742 else { // no alt
743 if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
744 else sp_node_selected_move(-nudge, 0); // no shift
745 }
746 ret = TRUE;
747 }
748 break;
749 case GDK_Up: // move selection up
750 case GDK_KP_Up:
751 case GDK_KP_8:
752 if (!MOD__CTRL) { // not ctrl
753 if (MOD__ALT) { // alt
754 if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
755 else sp_node_selected_move_screen(0, 1); // no shift
756 }
757 else { // no alt
758 if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
759 else sp_node_selected_move(0, nudge); // no shift
760 }
761 ret = TRUE;
762 }
763 break;
764 case GDK_Right: // move selection right
765 case GDK_KP_Right:
766 case GDK_KP_6:
767 if (!MOD__CTRL) { // not ctrl
768 if (MOD__ALT) { // alt
769 if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
770 else sp_node_selected_move_screen(1, 0); // no shift
771 }
772 else { // no alt
773 if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
774 else sp_node_selected_move(nudge, 0); // no shift
775 }
776 ret = TRUE;
777 }
778 break;
779 case GDK_Down: // move selection down
780 case GDK_KP_Down:
781 case GDK_KP_2:
782 if (!MOD__CTRL) { // not ctrl
783 if (MOD__ALT) { // alt
784 if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
785 else sp_node_selected_move_screen(0, -1); // no shift
786 }
787 else { // no alt
788 if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
789 else sp_node_selected_move(0, -nudge); // no shift
790 }
791 ret = TRUE;
792 }
793 break;
794 case GDK_Tab: // Tab - cycle selection forward
795 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
796 sp_nodepath_select_next(nc->nodepath);
797 ret = TRUE;
798 }
799 break;
800 case GDK_ISO_Left_Tab: // Shift Tab - cycle selection backward
801 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
802 sp_nodepath_select_prev(nc->nodepath);
803 ret = TRUE;
804 }
805 break;
806 case GDK_Escape:
807 {
808 NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
809 if (b != NR::Nothing()) {
810 Inkscape::Rubberband::get()->stop();
811 nc->rb_escaped = true;
812 } else {
813 if (nc->nodepath && nc->nodepath->selected) {
814 sp_nodepath_deselect(nc->nodepath);
815 } else {
816 sp_desktop_selection(desktop)->clear();
817 }
818 }
819 ret = TRUE;
820 break;
821 }
823 case GDK_bracketleft:
824 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
825 if (nc->leftctrl)
826 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
827 if (nc->rightctrl)
828 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
829 } else if ( MOD__ALT && !MOD__CTRL ) {
830 if (nc->leftalt && nc->rightalt)
831 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
832 else {
833 if (nc->leftalt)
834 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
835 if (nc->rightalt)
836 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
837 }
838 } else if ( snaps != 0 ) {
839 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
840 }
841 ret = TRUE;
842 break;
843 case GDK_bracketright:
844 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
845 if (nc->leftctrl)
846 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
847 if (nc->rightctrl)
848 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
849 } else if ( MOD__ALT && !MOD__CTRL ) {
850 if (nc->leftalt && nc->rightalt)
851 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
852 else {
853 if (nc->leftalt)
854 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
855 if (nc->rightalt)
856 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
857 }
858 } else if ( snaps != 0 ) {
859 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
860 }
861 ret = TRUE;
862 break;
863 case GDK_less:
864 case GDK_comma:
865 if (MOD__CTRL) {
866 if (nc->leftctrl)
867 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
868 if (nc->rightctrl)
869 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
870 } else if (MOD__ALT) {
871 if (nc->leftalt && nc->rightalt)
872 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
873 else {
874 if (nc->leftalt)
875 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
876 if (nc->rightalt)
877 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
878 }
879 } else {
880 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
881 }
882 ret = TRUE;
883 break;
884 case GDK_greater:
885 case GDK_period:
886 if (MOD__CTRL) {
887 if (nc->leftctrl)
888 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
889 if (nc->rightctrl)
890 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
891 } else if (MOD__ALT) {
892 if (nc->leftalt && nc->rightalt)
893 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
894 else {
895 if (nc->leftalt)
896 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
897 if (nc->rightalt)
898 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
899 }
900 } else {
901 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
902 }
903 ret = TRUE;
904 break;
906 case GDK_Alt_L:
907 nc->leftalt = TRUE;
908 sp_node_context_show_modifier_tip(event_context, event);
909 break;
910 case GDK_Alt_R:
911 nc->rightalt = TRUE;
912 sp_node_context_show_modifier_tip(event_context, event);
913 break;
914 case GDK_Control_L:
915 nc->leftctrl = TRUE;
916 sp_node_context_show_modifier_tip(event_context, event);
917 break;
918 case GDK_Control_R:
919 nc->rightctrl = TRUE;
920 sp_node_context_show_modifier_tip(event_context, event);
921 break;
922 case GDK_Shift_L:
923 case GDK_Shift_R:
924 case GDK_Meta_L:
925 case GDK_Meta_R:
926 sp_node_context_show_modifier_tip(event_context, event);
927 break;
928 default:
929 ret = node_key(event);
930 break;
931 }
932 break;
933 case GDK_KEY_RELEASE:
934 switch (get_group0_keyval(&event->key)) {
935 case GDK_Alt_L:
936 nc->leftalt = FALSE;
937 event_context->defaultMessageContext()->clear();
938 break;
939 case GDK_Alt_R:
940 nc->rightalt = FALSE;
941 event_context->defaultMessageContext()->clear();
942 break;
943 case GDK_Control_L:
944 nc->leftctrl = FALSE;
945 event_context->defaultMessageContext()->clear();
946 break;
947 case GDK_Control_R:
948 nc->rightctrl = FALSE;
949 event_context->defaultMessageContext()->clear();
950 break;
951 case GDK_Shift_L:
952 case GDK_Shift_R:
953 case GDK_Meta_L:
954 case GDK_Meta_R:
955 event_context->defaultMessageContext()->clear();
956 break;
957 }
958 break;
959 default:
960 break;
961 }
963 if (!ret) {
964 if (((SPEventContextClass *) parent_class)->root_handler)
965 ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
966 }
968 return ret;
969 }
972 /*
973 Local Variables:
974 mode:c++
975 c-file-style:"stroustrup"
976 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
977 indent-tabs-mode:nil
978 fill-column:99
979 End:
980 */
981 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :