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 "pixmaps/cursor-node.pixbuf"
30 #include "message-context.h"
31 #include "node-context.h"
32 #include "pixmaps/cursor-node-d.xpm"
33 #include "pixmaps/cursor-node-d.pixbuf"
34 #include "prefs-utils.h"
35 #include "xml/node-event-vector.h"
36 #include "style.h"
37 #include "splivarot.h"
39 static void sp_node_context_class_init(SPNodeContextClass *klass);
40 static void sp_node_context_init(SPNodeContext *node_context);
41 static void sp_node_context_dispose(GObject *object);
43 static void sp_node_context_setup(SPEventContext *ec);
44 static gint sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event);
45 static gint sp_node_context_item_handler(SPEventContext *event_context,
46 SPItem *item, GdkEvent *event);
48 static void nodepath_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
49 gchar const *old_value, gchar const *new_value,
50 bool is_interactive, gpointer data);
52 static Inkscape::XML::NodeEventVector nodepath_repr_events = {
53 NULL, /* child_added */
54 NULL, /* child_removed */
55 nodepath_event_attr_changed,
56 NULL, /* content_changed */
57 NULL /* order_changed */
58 };
60 static SPEventContextClass *parent_class;
62 GType
63 sp_node_context_get_type()
64 {
65 static GType type = 0;
66 if (!type) {
67 GTypeInfo info = {
68 sizeof(SPNodeContextClass),
69 NULL, NULL,
70 (GClassInitFunc) sp_node_context_class_init,
71 NULL, NULL,
72 sizeof(SPNodeContext),
73 4,
74 (GInstanceInitFunc) sp_node_context_init,
75 NULL, /* value_table */
76 };
77 type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPNodeContext", &info, (GTypeFlags)0);
78 }
79 return type;
80 }
82 static void
83 sp_node_context_class_init(SPNodeContextClass *klass)
84 {
85 GObjectClass *object_class = (GObjectClass *) klass;
86 SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
88 parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
90 object_class->dispose = sp_node_context_dispose;
92 event_context_class->setup = sp_node_context_setup;
93 event_context_class->root_handler = sp_node_context_root_handler;
94 event_context_class->item_handler = sp_node_context_item_handler;
95 }
97 static void
98 sp_node_context_init(SPNodeContext *node_context)
99 {
100 SPEventContext *event_context = SP_EVENT_CONTEXT(node_context);
102 event_context->cursor_shape = cursor_node_xpm;
103 event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
104 -1,
105 cursor_node_pixbuf,
106 FALSE,
107 NULL);
108 event_context->hot_x = 1;
109 event_context->hot_y = 1;
111 node_context->leftalt = FALSE;
112 node_context->rightalt = FALSE;
113 node_context->leftctrl = FALSE;
114 node_context->rightctrl = FALSE;
116 new (&node_context->sel_changed_connection) sigc::connection();
117 }
119 static void
120 sp_node_context_dispose(GObject *object)
121 {
122 SPNodeContext *nc = SP_NODE_CONTEXT(object);
123 SPEventContext *ec = SP_EVENT_CONTEXT(object);
125 ec->enableGrDrag(false);
127 nc->sel_changed_connection.disconnect();
128 nc->sel_changed_connection.~connection();
130 Inkscape::XML::Node *repr = NULL;
131 if (nc->nodepath) {
132 repr = nc->nodepath->repr;
133 }
134 if (!repr && ec->shape_knot_holder) {
135 repr = ec->shape_knot_holder->repr;
136 }
138 if (repr) {
139 sp_repr_remove_listener_by_data(repr, ec);
140 Inkscape::GC::release(repr);
141 }
143 if (nc->nodepath) {
144 sp_nodepath_destroy(nc->nodepath);
145 nc->nodepath = NULL;
146 }
148 if (ec->shape_knot_holder) {
149 sp_knot_holder_destroy(ec->shape_knot_holder);
150 ec->shape_knot_holder = NULL;
151 }
153 if (nc->_node_message_context) {
154 delete nc->_node_message_context;
155 }
157 G_OBJECT_CLASS(parent_class)->dispose(object);
158 }
160 static void
161 sp_node_context_setup(SPEventContext *ec)
162 {
163 SPNodeContext *nc = SP_NODE_CONTEXT(ec);
165 if (((SPEventContextClass *) parent_class)->setup)
166 ((SPEventContextClass *) parent_class)->setup(ec);
168 nc->sel_changed_connection.disconnect();
169 nc->sel_changed_connection = sp_desktop_selection(ec->desktop)->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc));
171 Inkscape::Selection *selection = sp_desktop_selection(ec->desktop);
172 SPItem *item = selection->singleItem();
174 nc->nodepath = NULL;
175 ec->shape_knot_holder = NULL;
177 nc->rb_escaped = false;
179 nc->cursor_drag = false;
181 nc->added_node = false;
183 if (item) {
184 nc->nodepath = sp_nodepath_new(ec->desktop, item, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
185 if ( nc->nodepath) {
186 //point pack to parent in case nodepath is deleted
187 nc->nodepath->nodeContext = nc;
188 }
189 ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
191 if (nc->nodepath || ec->shape_knot_holder) {
192 // setting listener
193 Inkscape::XML::Node *repr;
194 if (ec->shape_knot_holder)
195 repr = ec->shape_knot_holder->repr;
196 else
197 repr = SP_OBJECT_REPR(item);
198 if (repr) {
199 Inkscape::GC::anchor(repr);
200 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
201 }
202 }
203 }
205 if (prefs_get_int_attribute("tools.nodes", "selcue", 0) != 0) {
206 ec->enableSelectionCue();
207 }
209 if (prefs_get_int_attribute("tools.nodes", "gradientdrag", 0) != 0) {
210 ec->enableGrDrag();
211 }
213 nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
214 sp_nodepath_update_statusbar(nc->nodepath);
215 }
217 /**
218 \brief Callback that processes the "changed" signal on the selection;
219 destroys old and creates new nodepath and reassigns listeners to the new selected item's repr
220 */
221 void
222 sp_node_context_selection_changed(Inkscape::Selection *selection, gpointer data)
223 {
224 SPNodeContext *nc = SP_NODE_CONTEXT(data);
225 SPEventContext *ec = SP_EVENT_CONTEXT(nc);
227 Inkscape::XML::Node *old_repr = NULL;
229 if (nc->nodepath) {
230 old_repr = nc->nodepath->repr;
231 sp_nodepath_destroy(nc->nodepath);
232 nc->nodepath = NULL;
233 }
235 if (ec->shape_knot_holder) {
236 old_repr = ec->shape_knot_holder->repr;
237 sp_knot_holder_destroy(ec->shape_knot_holder);
238 }
240 if (old_repr) { // remove old listener
241 sp_repr_remove_listener_by_data(old_repr, ec);
242 Inkscape::GC::release(old_repr);
243 }
245 SPItem *item = selection->singleItem();
247 SPDesktop *desktop = selection->desktop();
248 nc->nodepath = NULL;
249 ec->shape_knot_holder = NULL;
250 if (item) {
251 nc->nodepath = sp_nodepath_new(desktop, item, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
252 if (nc->nodepath) {
253 nc->nodepath->nodeContext = nc;
254 }
255 ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
257 if (nc->nodepath || ec->shape_knot_holder) {
258 // setting new listener
259 Inkscape::XML::Node *repr;
260 if (ec->shape_knot_holder)
261 repr = ec->shape_knot_holder->repr;
262 else
263 repr = SP_OBJECT_REPR(item);
264 if (repr) {
265 Inkscape::GC::anchor(repr);
266 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
267 }
268 }
269 }
270 sp_nodepath_update_statusbar(nc->nodepath);
271 }
273 /**
274 \brief Regenerates nodepath when the item's repr was change outside of node edit
275 (e.g. by undo, or xml editor, or edited in another view). The item is assumed to be the same
276 (otherwise sp_node_context_selection_changed() would have been called), so repr and listeners
277 are not changed.
278 */
279 void
280 sp_nodepath_update_from_item(SPNodeContext *nc, SPItem *item)
281 {
282 g_assert(nc);
283 SPEventContext *ec = ((SPEventContext *) nc);
285 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(SP_EVENT_CONTEXT(nc));
286 g_assert(desktop);
288 if (nc->nodepath) {
289 sp_nodepath_destroy(nc->nodepath);
290 nc->nodepath = NULL;
291 }
293 if (ec->shape_knot_holder) {
294 sp_knot_holder_destroy(ec->shape_knot_holder);
295 ec->shape_knot_holder = NULL;
296 }
298 Inkscape::Selection *selection = sp_desktop_selection(desktop);
299 item = selection->singleItem();
301 if (item) {
302 nc->nodepath = sp_nodepath_new(desktop, item, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
303 if (nc->nodepath) {
304 nc->nodepath->nodeContext = nc;
305 }
306 ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
307 }
308 sp_nodepath_update_statusbar(nc->nodepath);
309 }
311 /**
312 \brief Callback that is fired whenever an attribute of the selected item (which we have in the nodepath) changes
313 */
314 static void
315 nodepath_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
316 gchar const *old_value, gchar const *new_value,
317 bool is_interactive, gpointer data)
318 {
319 SPItem *item = NULL;
320 gboolean changed = FALSE;
322 g_assert(data);
323 SPNodeContext *nc = ((SPNodeContext *) data);
324 SPEventContext *ec = ((SPEventContext *) data);
325 g_assert(nc);
326 Inkscape::NodePath::Path *np = nc->nodepath;
327 SPKnotHolder *kh = ec->shape_knot_holder;
329 if (np) {
330 item = SP_ITEM(np->path);
331 if (!strcmp(name, "d") || !strcmp(name, "sodipodi:nodetypes")) { // With paths, we only need to act if one of the path-affecting attributes has changed.
332 changed = (np->local_change == 0);
333 if (np->local_change > 0)
334 np->local_change--;
335 }
337 } else if (kh) {
338 item = SP_ITEM(kh->item);
339 changed = !(kh->local_change);
340 kh->local_change = FALSE;
341 }
343 if (np && changed) {
344 GList *saved = NULL;
345 SPDesktop *desktop = np->desktop;
346 g_assert(desktop);
347 Inkscape::Selection *selection = desktop->selection;
348 g_assert(selection);
350 saved = save_nodepath_selection(nc->nodepath);
351 sp_nodepath_update_from_item(nc, item);
352 if (nc->nodepath && saved) restore_nodepath_selection(nc->nodepath, saved);
354 } else if (kh && changed) {
355 sp_nodepath_update_from_item(nc, item);
356 }
358 sp_nodepath_update_statusbar(nc->nodepath);
359 }
361 void
362 sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event)
363 {
364 sp_event_show_modifier_tip
365 (event_context->defaultMessageContext(), event,
366 _("<b>Ctrl</b>: toggle node type, snap handle angle, move hor/vert; <b>Ctrl+Alt</b>: move along handles"),
367 _("<b>Shift</b>: toggle node selection, disable snapping, rotate both handles"),
368 _("<b>Alt</b>: lock handle length; <b>Ctrl+Alt</b>: move along handles"));
369 }
371 bool
372 sp_node_context_is_over_stroke (SPNodeContext *nc, SPItem *item, NR::Point event_p, bool remember)
373 {
374 SPDesktop *desktop = SP_EVENT_CONTEXT (nc)->desktop;
376 //Translate click point into proper coord system
377 nc->curvepoint_doc = desktop->w2d(event_p);
378 nc->curvepoint_doc *= sp_item_dt2i_affine(item);
379 nc->curvepoint_doc *= sp_item_i2doc_affine(item);
381 sp_nodepath_ensure_livarot_path(nc->nodepath);
382 NR::Maybe<Path::cut_position> position = get_nearest_position_on_Path(nc->nodepath->livarot_path, nc->curvepoint_doc);
383 NR::Point nearest = get_point_on_Path(nc->nodepath->livarot_path, 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_desktop_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));
434 bool over_stroke = false;
435 if (item_over && nc->nodepath) {
436 over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), false);
437 }
439 if (over_stroke || nc->added_node) {
440 switch (event->type) {
441 case GDK_BUTTON_RELEASE:
442 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
443 //add a node
444 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
445 } else {
446 if (nc->added_node) { // we just received double click, ignore release
447 nc->added_node = false;
448 break;
449 }
450 //select the segment
451 if (event->button.state & GDK_SHIFT_MASK) {
452 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, true);
453 } else {
454 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
455 }
456 desktop->updateNow();
457 }
458 break;
459 case GDK_2BUTTON_PRESS:
460 //add a node
461 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
462 nc->added_node = true;
463 break;
464 default:
465 break;
466 }
467 } else if (event->button.state & GDK_SHIFT_MASK) {
468 selection->toggle(item_clicked);
469 desktop->updateNow();
470 } else {
471 selection->set(item_clicked);
472 desktop->updateNow();
473 }
475 ret = TRUE;
476 }
477 break;
478 }
479 break;
480 case GDK_BUTTON_PRESS:
481 if (event->button.button == 1 && !(event->button.state & GDK_SHIFT_MASK)) {
482 // save drag origin
483 event_context->xp = (gint) event->button.x;
484 event_context->yp = (gint) event->button.y;
485 event_context->within_tolerance = true;
486 nc->hit = false;
488 if (!nc->drag) {
489 // find out if we're over the selected item, disregarding groups
490 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
491 NR::Point(event->button.x, event->button.y));
493 if (nc->nodepath && selection->single() && item_over) {
495 // save drag origin
496 bool over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), true);
497 //only dragging curves
498 if (over_stroke) {
499 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
500 ret = TRUE;
501 } else {
502 break;
503 }
504 } else {
505 break;
506 }
508 ret = TRUE;
509 }
510 break;
511 }
512 break;
513 default:
514 break;
515 }
517 if (!ret) {
518 if (((SPEventContextClass *) parent_class)->item_handler)
519 ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
520 }
522 return ret;
523 }
525 static gint
526 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
527 {
528 SPDesktop *desktop = event_context->desktop;
529 Inkscape::Selection *selection = sp_desktop_selection (desktop);
531 // fixme: nc->nodepath can potentially become NULL after retrieving nc.
532 // A general method for handling this possibility should be created.
533 // For now, the number of checks for a NULL nc->nodepath have been
534 // increased, both here and in the called sp_nodepath_* functions.
536 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
537 double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
538 event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
539 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
540 double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
542 gint ret = FALSE;
544 switch (event->type) {
545 case GDK_BUTTON_PRESS:
546 if (event->button.button == 1) {
547 // save drag origin
548 event_context->xp = (gint) event->button.x;
549 event_context->yp = (gint) event->button.y;
550 event_context->within_tolerance = true;
551 nc->hit = false;
553 NR::Point const button_w(event->button.x,
554 event->button.y);
555 NR::Point const button_dt(desktop->w2d(button_w));
556 Inkscape::Rubberband::get()->start(desktop, button_dt);
557 desktop->updateNow();
558 ret = TRUE;
559 }
560 break;
561 case GDK_MOTION_NOTIFY:
562 if (event->motion.state & GDK_BUTTON1_MASK) {
564 if ( event_context->within_tolerance
565 && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
566 && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
567 break; // do not drag if we're within tolerance from origin
568 }
570 // The path went away while dragging; throw away any further motion
571 // events until the mouse pointer is released.
572 if (nc->hit && (nc->nodepath == NULL)) {
573 break;
574 }
576 // Once the user has moved farther than tolerance from the original location
577 // (indicating they intend to move the object, not click), then always process the
578 // motion notify coordinates as given (no snapping back to origin)
579 event_context->within_tolerance = false;
581 if (nc->nodepath && nc->hit) {
582 NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
583 event->motion.y - nc->curvepoint_event[NR::Y]);
584 NR::Point const delta_dt(desktop->w2d(delta_w));
585 sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
586 nc->curvepoint_event[NR::X] = (gint) event->motion.x;
587 nc->curvepoint_event[NR::Y] = (gint) event->motion.y;
588 gobble_motion_events(GDK_BUTTON1_MASK);
589 } else {
590 NR::Point const motion_w(event->motion.x,
591 event->motion.y);
592 NR::Point const motion_dt(desktop->w2d(motion_w));
593 Inkscape::Rubberband::get()->move(motion_dt);
594 }
595 nc->drag = TRUE;
596 ret = TRUE;
597 } else {
598 if (!nc->nodepath || selection->singleItem() == NULL) {
599 break;
600 }
602 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
603 NR::Point(event->motion.x, event->motion.y));
604 bool over_stroke = false;
605 if (item_over && nc->nodepath) {
606 over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
607 }
609 if (nc->cursor_drag && !over_stroke) {
610 event_context->cursor_shape = cursor_node_xpm;
611 event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
612 -1,
613 cursor_node_pixbuf,
614 FALSE,
615 NULL);
616 event_context->hot_x = 1;
617 event_context->hot_y = 1;
618 sp_event_context_update_cursor(event_context);
619 nc->cursor_drag = false;
620 } else if (!nc->cursor_drag && over_stroke) {
621 event_context->cursor_shape = cursor_node_d_xpm;
622 event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
623 -1,
624 cursor_node_d_pixbuf,
625 FALSE,
626 NULL);
627 event_context->hot_x = 1;
628 event_context->hot_y = 1;
629 sp_event_context_update_cursor(event_context);
630 nc->cursor_drag = true;
631 }
632 }
633 break;
634 case GDK_BUTTON_RELEASE:
635 event_context->xp = event_context->yp = 0;
636 if (event->button.button == 1) {
638 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
640 if (nc->hit && !event_context->within_tolerance) { //drag curve
641 if (nc->nodepath) {
642 sp_nodepath_update_repr (nc->nodepath, _("Drag curve"));
643 }
644 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
645 if (nc->nodepath) {
646 sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
647 }
648 } else {
649 if (!(nc->rb_escaped)) { // unless something was cancelled
650 if (nc->nodepath && nc->nodepath->selected)
651 sp_nodepath_deselect(nc->nodepath);
652 else
653 sp_desktop_selection(desktop)->clear();
654 }
655 }
656 ret = TRUE;
657 Inkscape::Rubberband::get()->stop();
658 desktop->updateNow();
659 nc->rb_escaped = false;
660 nc->drag = FALSE;
661 nc->hit = false;
662 break;
663 }
664 break;
665 case GDK_KEY_PRESS:
666 switch (get_group0_keyval(&event->key)) {
667 case GDK_Insert:
668 case GDK_KP_Insert:
669 // with any modifiers
670 sp_node_selected_add_node();
671 ret = TRUE;
672 break;
673 case GDK_Delete:
674 case GDK_KP_Delete:
675 case GDK_BackSpace:
676 if (MOD__CTRL_ONLY) {
677 sp_node_selected_delete();
678 } else {
679 if (nc->nodepath && nc->nodepath->selected) {
680 sp_node_delete_preserve(g_list_copy(nc->nodepath->selected));
681 }
682 }
683 ret = TRUE;
684 break;
685 case GDK_C:
686 case GDK_c:
687 if (MOD__SHIFT_ONLY) {
688 sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
689 ret = TRUE;
690 }
691 break;
692 case GDK_S:
693 case GDK_s:
694 if (MOD__SHIFT_ONLY) {
695 sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
696 ret = TRUE;
697 }
698 break;
699 case GDK_Y:
700 case GDK_y:
701 if (MOD__SHIFT_ONLY) {
702 sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
703 ret = TRUE;
704 }
705 break;
706 case GDK_B:
707 case GDK_b:
708 if (MOD__SHIFT_ONLY) {
709 sp_node_selected_break();
710 ret = TRUE;
711 }
712 break;
713 case GDK_J:
714 case GDK_j:
715 if (MOD__SHIFT_ONLY) {
716 sp_node_selected_join();
717 ret = TRUE;
718 }
719 break;
720 case GDK_D:
721 case GDK_d:
722 if (MOD__SHIFT_ONLY) {
723 sp_node_selected_duplicate();
724 ret = TRUE;
725 }
726 break;
727 case GDK_L:
728 case GDK_l:
729 if (MOD__SHIFT_ONLY) {
730 sp_node_selected_set_line_type(NR_LINETO);
731 ret = TRUE;
732 }
733 break;
734 case GDK_U:
735 case GDK_u:
736 if (MOD__SHIFT_ONLY) {
737 sp_node_selected_set_line_type(NR_CURVETO);
738 ret = TRUE;
739 }
740 break;
741 case GDK_R:
742 case GDK_r:
743 if (MOD__SHIFT_ONLY) {
744 // FIXME: add top panel button
745 sp_selected_path_reverse();
746 ret = TRUE;
747 }
748 break;
749 case GDK_Left: // move selection left
750 case GDK_KP_Left:
751 case GDK_KP_4:
752 if (!MOD__CTRL) { // not ctrl
753 if (MOD__ALT) { // alt
754 if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
755 else sp_node_selected_move_screen(-1, 0); // no shift
756 }
757 else { // no alt
758 if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
759 else sp_node_selected_move(-nudge, 0); // no shift
760 }
761 ret = TRUE;
762 }
763 break;
764 case GDK_Up: // move selection up
765 case GDK_KP_Up:
766 case GDK_KP_8:
767 if (!MOD__CTRL) { // not ctrl
768 if (MOD__ALT) { // alt
769 if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
770 else sp_node_selected_move_screen(0, 1); // no shift
771 }
772 else { // no alt
773 if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
774 else sp_node_selected_move(0, nudge); // no shift
775 }
776 ret = TRUE;
777 }
778 break;
779 case GDK_Right: // move selection right
780 case GDK_KP_Right:
781 case GDK_KP_6:
782 if (!MOD__CTRL) { // not ctrl
783 if (MOD__ALT) { // alt
784 if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
785 else sp_node_selected_move_screen(1, 0); // no shift
786 }
787 else { // no alt
788 if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
789 else sp_node_selected_move(nudge, 0); // no shift
790 }
791 ret = TRUE;
792 }
793 break;
794 case GDK_Down: // move selection down
795 case GDK_KP_Down:
796 case GDK_KP_2:
797 if (!MOD__CTRL) { // not ctrl
798 if (MOD__ALT) { // alt
799 if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
800 else sp_node_selected_move_screen(0, -1); // no shift
801 }
802 else { // no alt
803 if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
804 else sp_node_selected_move(0, -nudge); // no shift
805 }
806 ret = TRUE;
807 }
808 break;
809 case GDK_Tab: // Tab - cycle selection forward
810 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
811 sp_nodepath_select_next(nc->nodepath);
812 ret = TRUE;
813 }
814 break;
815 case GDK_ISO_Left_Tab: // Shift Tab - cycle selection backward
816 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
817 sp_nodepath_select_prev(nc->nodepath);
818 ret = TRUE;
819 }
820 break;
821 case GDK_Escape:
822 {
823 NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
824 if (b != NR::Nothing()) {
825 Inkscape::Rubberband::get()->stop();
826 nc->rb_escaped = true;
827 } else {
828 if (nc->nodepath && nc->nodepath->selected) {
829 sp_nodepath_deselect(nc->nodepath);
830 } else {
831 sp_desktop_selection(desktop)->clear();
832 }
833 }
834 ret = TRUE;
835 break;
836 }
838 case GDK_bracketleft:
839 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
840 if (nc->leftctrl)
841 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
842 if (nc->rightctrl)
843 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
844 } else if ( MOD__ALT && !MOD__CTRL ) {
845 if (nc->leftalt && nc->rightalt)
846 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
847 else {
848 if (nc->leftalt)
849 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
850 if (nc->rightalt)
851 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
852 }
853 } else if ( snaps != 0 ) {
854 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
855 }
856 ret = TRUE;
857 break;
858 case GDK_bracketright:
859 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
860 if (nc->leftctrl)
861 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
862 if (nc->rightctrl)
863 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
864 } else if ( MOD__ALT && !MOD__CTRL ) {
865 if (nc->leftalt && nc->rightalt)
866 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
867 else {
868 if (nc->leftalt)
869 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
870 if (nc->rightalt)
871 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
872 }
873 } else if ( snaps != 0 ) {
874 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
875 }
876 ret = TRUE;
877 break;
878 case GDK_less:
879 case GDK_comma:
880 if (MOD__CTRL) {
881 if (nc->leftctrl)
882 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
883 if (nc->rightctrl)
884 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
885 } else if (MOD__ALT) {
886 if (nc->leftalt && nc->rightalt)
887 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
888 else {
889 if (nc->leftalt)
890 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
891 if (nc->rightalt)
892 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
893 }
894 } else {
895 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
896 }
897 ret = TRUE;
898 break;
899 case GDK_greater:
900 case GDK_period:
901 if (MOD__CTRL) {
902 if (nc->leftctrl)
903 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
904 if (nc->rightctrl)
905 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
906 } else if (MOD__ALT) {
907 if (nc->leftalt && nc->rightalt)
908 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
909 else {
910 if (nc->leftalt)
911 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
912 if (nc->rightalt)
913 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
914 }
915 } else {
916 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
917 }
918 ret = TRUE;
919 break;
921 case GDK_Alt_L:
922 nc->leftalt = TRUE;
923 sp_node_context_show_modifier_tip(event_context, event);
924 break;
925 case GDK_Alt_R:
926 nc->rightalt = TRUE;
927 sp_node_context_show_modifier_tip(event_context, event);
928 break;
929 case GDK_Control_L:
930 nc->leftctrl = TRUE;
931 sp_node_context_show_modifier_tip(event_context, event);
932 break;
933 case GDK_Control_R:
934 nc->rightctrl = TRUE;
935 sp_node_context_show_modifier_tip(event_context, event);
936 break;
937 case GDK_Shift_L:
938 case GDK_Shift_R:
939 case GDK_Meta_L:
940 case GDK_Meta_R:
941 sp_node_context_show_modifier_tip(event_context, event);
942 break;
943 default:
944 ret = node_key(event);
945 break;
946 }
947 break;
948 case GDK_KEY_RELEASE:
949 switch (get_group0_keyval(&event->key)) {
950 case GDK_Alt_L:
951 nc->leftalt = FALSE;
952 event_context->defaultMessageContext()->clear();
953 break;
954 case GDK_Alt_R:
955 nc->rightalt = FALSE;
956 event_context->defaultMessageContext()->clear();
957 break;
958 case GDK_Control_L:
959 nc->leftctrl = FALSE;
960 event_context->defaultMessageContext()->clear();
961 break;
962 case GDK_Control_R:
963 nc->rightctrl = FALSE;
964 event_context->defaultMessageContext()->clear();
965 break;
966 case GDK_Shift_L:
967 case GDK_Shift_R:
968 case GDK_Meta_L:
969 case GDK_Meta_R:
970 event_context->defaultMessageContext()->clear();
971 break;
972 }
973 break;
974 default:
975 break;
976 }
978 if (!ret) {
979 if (((SPEventContextClass *) parent_class)->root_handler)
980 ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
981 }
983 return ret;
984 }
987 /*
988 Local Variables:
989 mode:c++
990 c-file-style:"stroustrup"
991 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
992 indent-tabs-mode:nil
993 fill-column:99
994 End:
995 */
996 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :