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 NR::Maybe<Path::cut_position> position = get_nearest_position_on_Path(nc->nodepath->livarot_path, nc->curvepoint_doc);
382 NR::Point nearest = get_point_on_Path(nc->nodepath->livarot_path, position.assume().piece, position.assume().t);
383 NR::Point delta = nearest - nc->curvepoint_doc;
385 delta = desktop->d2w(delta);
387 double stroke_tolerance =
388 (SP_OBJECT_STYLE (item)->stroke.type != SP_PAINT_TYPE_NONE?
389 desktop->current_zoom() *
390 SP_OBJECT_STYLE (item)->stroke_width.computed *
391 sp_item_i2d_affine (item).expansion() * 0.5
392 : 0.0)
393 + (double) SP_EVENT_CONTEXT(nc)->tolerance;
395 bool close = (NR::L2 (delta) < stroke_tolerance);
397 if (remember && close) {
398 nc->curvepoint_event[NR::X] = (gint) event_p [NR::X];
399 nc->curvepoint_event[NR::Y] = (gint) event_p [NR::Y];
400 nc->hit = true;
401 nc->grab_t = position.assume().t;
402 nc->grab_node = sp_nodepath_get_node_by_index(position.assume().piece);
403 }
405 return close;
406 }
409 static gint
410 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
411 {
412 gint ret = FALSE;
414 SPDesktop *desktop = event_context->desktop;
415 Inkscape::Selection *selection = sp_desktop_selection (desktop);
417 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
419 switch (event->type) {
420 case GDK_2BUTTON_PRESS:
421 case GDK_BUTTON_RELEASE:
422 if (event->button.button == 1) {
423 if (!nc->drag) {
425 // find out clicked item, disregarding groups, honoring Alt
426 SPItem *item_clicked = sp_event_context_find_item (desktop,
427 NR::Point(event->button.x, event->button.y),
428 (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
429 // find out if we're over the selected item, disregarding groups
430 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
431 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(nc->nodepath, 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(nc->nodepath, nc->curvepoint_doc, true);
452 } else {
453 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
454 }
455 }
456 break;
457 case GDK_2BUTTON_PRESS:
458 //add a node
459 sp_nodepath_add_node_near_point(nc->nodepath, 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(nc->nodepath, 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_desktop_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);
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->cursor_pixbuf = gdk_pixbuf_new_from_inline(
595 -1,
596 cursor_node_pixbuf,
597 FALSE,
598 NULL);
599 event_context->hot_x = 1;
600 event_context->hot_y = 1;
601 sp_event_context_update_cursor(event_context);
602 nc->cursor_drag = false;
603 } else if (!nc->cursor_drag && over_stroke) {
604 event_context->cursor_shape = cursor_node_d_xpm;
605 event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
606 -1,
607 cursor_node_d_pixbuf,
608 FALSE,
609 NULL);
610 event_context->hot_x = 1;
611 event_context->hot_y = 1;
612 sp_event_context_update_cursor(event_context);
613 nc->cursor_drag = true;
614 }
615 }
616 break;
617 case GDK_BUTTON_RELEASE:
618 event_context->xp = event_context->yp = 0;
619 if (event->button.button == 1) {
621 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
623 if (nc->hit && !event_context->within_tolerance) { //drag curve
624 sp_nodepath_update_repr (nc->nodepath, _("Drag curve"));
625 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
626 if (nc->nodepath) {
627 sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
628 }
629 } else {
630 if (!(nc->rb_escaped)) { // unless something was cancelled
631 if (nc->nodepath && nc->nodepath->selected)
632 sp_nodepath_deselect(nc->nodepath);
633 else
634 sp_desktop_selection(desktop)->clear();
635 }
636 }
637 ret = TRUE;
638 Inkscape::Rubberband::get()->stop();
639 nc->rb_escaped = false;
640 nc->drag = FALSE;
641 nc->hit = false;
642 break;
643 }
644 break;
645 case GDK_KEY_PRESS:
646 switch (get_group0_keyval(&event->key)) {
647 case GDK_Insert:
648 case GDK_KP_Insert:
649 // with any modifiers
650 sp_node_selected_add_node();
651 ret = TRUE;
652 break;
653 case GDK_Delete:
654 case GDK_KP_Delete:
655 case GDK_BackSpace:
656 if (MOD__CTRL_ONLY) {
657 sp_node_selected_delete();
658 } else {
659 if (nc->nodepath && nc->nodepath->selected) {
660 sp_node_delete_preserve(g_list_copy(nc->nodepath->selected));
661 }
662 }
663 ret = TRUE;
664 break;
665 case GDK_C:
666 case GDK_c:
667 if (MOD__SHIFT_ONLY) {
668 sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
669 ret = TRUE;
670 }
671 break;
672 case GDK_S:
673 case GDK_s:
674 if (MOD__SHIFT_ONLY) {
675 sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
676 ret = TRUE;
677 }
678 break;
679 case GDK_Y:
680 case GDK_y:
681 if (MOD__SHIFT_ONLY) {
682 sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
683 ret = TRUE;
684 }
685 break;
686 case GDK_B:
687 case GDK_b:
688 if (MOD__SHIFT_ONLY) {
689 sp_node_selected_break();
690 ret = TRUE;
691 }
692 break;
693 case GDK_J:
694 case GDK_j:
695 if (MOD__SHIFT_ONLY) {
696 sp_node_selected_join();
697 ret = TRUE;
698 }
699 break;
700 case GDK_D:
701 case GDK_d:
702 if (MOD__SHIFT_ONLY) {
703 sp_node_selected_duplicate();
704 ret = TRUE;
705 }
706 break;
707 case GDK_L:
708 case GDK_l:
709 if (MOD__SHIFT_ONLY) {
710 sp_node_selected_set_line_type(NR_LINETO);
711 ret = TRUE;
712 }
713 break;
714 case GDK_U:
715 case GDK_u:
716 if (MOD__SHIFT_ONLY) {
717 sp_node_selected_set_line_type(NR_CURVETO);
718 ret = TRUE;
719 }
720 break;
721 case GDK_R:
722 case GDK_r:
723 if (MOD__SHIFT_ONLY) {
724 // FIXME: add top panel button
725 sp_selected_path_reverse();
726 ret = TRUE;
727 }
728 break;
729 case GDK_Left: // move selection left
730 case GDK_KP_Left:
731 case GDK_KP_4:
732 if (!MOD__CTRL) { // not ctrl
733 if (MOD__ALT) { // alt
734 if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
735 else sp_node_selected_move_screen(-1, 0); // no shift
736 }
737 else { // no alt
738 if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
739 else sp_node_selected_move(-nudge, 0); // no shift
740 }
741 ret = TRUE;
742 }
743 break;
744 case GDK_Up: // move selection up
745 case GDK_KP_Up:
746 case GDK_KP_8:
747 if (!MOD__CTRL) { // not ctrl
748 if (MOD__ALT) { // alt
749 if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
750 else sp_node_selected_move_screen(0, 1); // no shift
751 }
752 else { // no alt
753 if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
754 else sp_node_selected_move(0, nudge); // no shift
755 }
756 ret = TRUE;
757 }
758 break;
759 case GDK_Right: // move selection right
760 case GDK_KP_Right:
761 case GDK_KP_6:
762 if (!MOD__CTRL) { // not ctrl
763 if (MOD__ALT) { // alt
764 if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
765 else sp_node_selected_move_screen(1, 0); // no shift
766 }
767 else { // no alt
768 if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
769 else sp_node_selected_move(nudge, 0); // no shift
770 }
771 ret = TRUE;
772 }
773 break;
774 case GDK_Down: // move selection down
775 case GDK_KP_Down:
776 case GDK_KP_2:
777 if (!MOD__CTRL) { // not ctrl
778 if (MOD__ALT) { // alt
779 if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
780 else sp_node_selected_move_screen(0, -1); // no shift
781 }
782 else { // no alt
783 if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
784 else sp_node_selected_move(0, -nudge); // no shift
785 }
786 ret = TRUE;
787 }
788 break;
789 case GDK_Tab: // Tab - cycle selection forward
790 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
791 sp_nodepath_select_next(nc->nodepath);
792 ret = TRUE;
793 }
794 break;
795 case GDK_ISO_Left_Tab: // Shift Tab - cycle selection backward
796 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
797 sp_nodepath_select_prev(nc->nodepath);
798 ret = TRUE;
799 }
800 break;
801 case GDK_Escape:
802 {
803 NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
804 if (b != NR::Nothing()) {
805 Inkscape::Rubberband::get()->stop();
806 nc->rb_escaped = true;
807 } else {
808 if (nc->nodepath && nc->nodepath->selected) {
809 sp_nodepath_deselect(nc->nodepath);
810 } else {
811 sp_desktop_selection(desktop)->clear();
812 }
813 }
814 ret = TRUE;
815 break;
816 }
818 case GDK_bracketleft:
819 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
820 if (nc->leftctrl)
821 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
822 if (nc->rightctrl)
823 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
824 } else if ( MOD__ALT && !MOD__CTRL ) {
825 if (nc->leftalt && nc->rightalt)
826 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
827 else {
828 if (nc->leftalt)
829 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
830 if (nc->rightalt)
831 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
832 }
833 } else if ( snaps != 0 ) {
834 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
835 }
836 ret = TRUE;
837 break;
838 case GDK_bracketright:
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_less:
859 case GDK_comma:
860 if (MOD__CTRL) {
861 if (nc->leftctrl)
862 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
863 if (nc->rightctrl)
864 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
865 } else if (MOD__ALT) {
866 if (nc->leftalt && nc->rightalt)
867 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
868 else {
869 if (nc->leftalt)
870 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
871 if (nc->rightalt)
872 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
873 }
874 } else {
875 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
876 }
877 ret = TRUE;
878 break;
879 case GDK_greater:
880 case GDK_period:
881 if (MOD__CTRL) {
882 if (nc->leftctrl)
883 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
884 if (nc->rightctrl)
885 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
886 } else if (MOD__ALT) {
887 if (nc->leftalt && nc->rightalt)
888 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
889 else {
890 if (nc->leftalt)
891 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
892 if (nc->rightalt)
893 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
894 }
895 } else {
896 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
897 }
898 ret = TRUE;
899 break;
901 case GDK_Alt_L:
902 nc->leftalt = TRUE;
903 sp_node_context_show_modifier_tip(event_context, event);
904 break;
905 case GDK_Alt_R:
906 nc->rightalt = TRUE;
907 sp_node_context_show_modifier_tip(event_context, event);
908 break;
909 case GDK_Control_L:
910 nc->leftctrl = TRUE;
911 sp_node_context_show_modifier_tip(event_context, event);
912 break;
913 case GDK_Control_R:
914 nc->rightctrl = TRUE;
915 sp_node_context_show_modifier_tip(event_context, event);
916 break;
917 case GDK_Shift_L:
918 case GDK_Shift_R:
919 case GDK_Meta_L:
920 case GDK_Meta_R:
921 sp_node_context_show_modifier_tip(event_context, event);
922 break;
923 default:
924 ret = node_key(event);
925 break;
926 }
927 break;
928 case GDK_KEY_RELEASE:
929 switch (get_group0_keyval(&event->key)) {
930 case GDK_Alt_L:
931 nc->leftalt = FALSE;
932 event_context->defaultMessageContext()->clear();
933 break;
934 case GDK_Alt_R:
935 nc->rightalt = FALSE;
936 event_context->defaultMessageContext()->clear();
937 break;
938 case GDK_Control_L:
939 nc->leftctrl = FALSE;
940 event_context->defaultMessageContext()->clear();
941 break;
942 case GDK_Control_R:
943 nc->rightctrl = FALSE;
944 event_context->defaultMessageContext()->clear();
945 break;
946 case GDK_Shift_L:
947 case GDK_Shift_R:
948 case GDK_Meta_L:
949 case GDK_Meta_R:
950 event_context->defaultMessageContext()->clear();
951 break;
952 }
953 break;
954 default:
955 break;
956 }
958 if (!ret) {
959 if (((SPEventContextClass *) parent_class)->root_handler)
960 ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
961 }
963 return ret;
964 }
967 /*
968 Local Variables:
969 mode:c++
970 c-file-style:"stroustrup"
971 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
972 indent-tabs-mode:nil
973 fill-column:99
974 End:
975 */
976 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :