8f8a48bf7718f9ddf06a90ed3af9590be447af76
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 desktop->updateNow();
456 }
457 break;
458 case GDK_2BUTTON_PRESS:
459 //add a node
460 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
461 nc->added_node = true;
462 break;
463 default:
464 break;
465 }
466 } else if (event->button.state & GDK_SHIFT_MASK) {
467 selection->toggle(item_clicked);
468 desktop->updateNow();
469 } else {
470 selection->set(item_clicked);
471 desktop->updateNow();
472 }
474 ret = TRUE;
475 }
476 break;
477 }
478 break;
479 case GDK_BUTTON_PRESS:
480 if (event->button.button == 1 && !(event->button.state & GDK_SHIFT_MASK)) {
481 // save drag origin
482 event_context->xp = (gint) event->button.x;
483 event_context->yp = (gint) event->button.y;
484 event_context->within_tolerance = true;
485 nc->hit = false;
487 if (!nc->drag) {
488 // find out if we're over the selected item, disregarding groups
489 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
490 NR::Point(event->button.x, event->button.y));
492 if (nc->nodepath && selection->single() && item_over) {
494 // save drag origin
495 bool over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), true);
496 //only dragging curves
497 if (over_stroke) {
498 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
499 ret = TRUE;
500 } else {
501 break;
502 }
503 } else {
504 break;
505 }
507 ret = TRUE;
508 }
509 break;
510 }
511 break;
512 default:
513 break;
514 }
516 if (!ret) {
517 if (((SPEventContextClass *) parent_class)->item_handler)
518 ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
519 }
521 return ret;
522 }
524 static gint
525 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
526 {
527 SPDesktop *desktop = event_context->desktop;
528 Inkscape::Selection *selection = sp_desktop_selection (desktop);
530 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
531 double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
532 event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
533 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
534 double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
536 gint ret = FALSE;
538 switch (event->type) {
539 case GDK_BUTTON_PRESS:
540 if (event->button.button == 1) {
541 // save drag origin
542 event_context->xp = (gint) event->button.x;
543 event_context->yp = (gint) event->button.y;
544 event_context->within_tolerance = true;
545 nc->hit = false;
547 NR::Point const button_w(event->button.x,
548 event->button.y);
549 NR::Point const button_dt(desktop->w2d(button_w));
550 Inkscape::Rubberband::get()->start(desktop, button_dt);
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 }
562 // Once the user has moved farther than tolerance from the original location
563 // (indicating they intend to move the object, not click), then always process the
564 // motion notify coordinates as given (no snapping back to origin)
565 event_context->within_tolerance = false;
567 if (nc->nodepath && nc->hit) {
568 NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
569 event->motion.y - nc->curvepoint_event[NR::Y]);
570 NR::Point const delta_dt(desktop->w2d(delta_w));
571 sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
572 nc->curvepoint_event[NR::X] = (gint) event->motion.x;
573 nc->curvepoint_event[NR::Y] = (gint) event->motion.y;
574 gobble_motion_events(GDK_BUTTON1_MASK);
575 } else {
576 NR::Point const motion_w(event->motion.x,
577 event->motion.y);
578 NR::Point const motion_dt(desktop->w2d(motion_w));
579 Inkscape::Rubberband::get()->move(motion_dt);
580 }
581 nc->drag = TRUE;
582 ret = TRUE;
583 } else {
584 if (!nc->nodepath || selection->singleItem() == NULL) {
585 break;
586 }
588 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
589 NR::Point(event->motion.x, event->motion.y));
590 bool over_stroke = false;
591 if (item_over && nc->nodepath) {
592 over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
593 }
595 if (nc->cursor_drag && !over_stroke) {
596 event_context->cursor_shape = cursor_node_xpm;
597 event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
598 -1,
599 cursor_node_pixbuf,
600 FALSE,
601 NULL);
602 event_context->hot_x = 1;
603 event_context->hot_y = 1;
604 sp_event_context_update_cursor(event_context);
605 nc->cursor_drag = false;
606 } else if (!nc->cursor_drag && over_stroke) {
607 event_context->cursor_shape = cursor_node_d_xpm;
608 event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
609 -1,
610 cursor_node_d_pixbuf,
611 FALSE,
612 NULL);
613 event_context->hot_x = 1;
614 event_context->hot_y = 1;
615 sp_event_context_update_cursor(event_context);
616 nc->cursor_drag = true;
617 }
618 }
619 break;
620 case GDK_BUTTON_RELEASE:
621 event_context->xp = event_context->yp = 0;
622 if (event->button.button == 1) {
624 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
626 if (nc->hit && !event_context->within_tolerance) { //drag curve
627 sp_nodepath_update_repr (nc->nodepath, _("Drag curve"));
628 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
629 if (nc->nodepath) {
630 sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
631 }
632 } else {
633 if (!(nc->rb_escaped)) { // unless something was cancelled
634 if (nc->nodepath && nc->nodepath->selected)
635 sp_nodepath_deselect(nc->nodepath);
636 else
637 sp_desktop_selection(desktop)->clear();
638 }
639 }
640 ret = TRUE;
641 Inkscape::Rubberband::get()->stop();
642 desktop->updateNow();
643 nc->rb_escaped = false;
644 nc->drag = FALSE;
645 nc->hit = false;
646 break;
647 }
648 break;
649 case GDK_KEY_PRESS:
650 switch (get_group0_keyval(&event->key)) {
651 case GDK_Insert:
652 case GDK_KP_Insert:
653 // with any modifiers
654 sp_node_selected_add_node();
655 ret = TRUE;
656 break;
657 case GDK_Delete:
658 case GDK_KP_Delete:
659 case GDK_BackSpace:
660 if (MOD__CTRL_ONLY) {
661 sp_node_selected_delete();
662 } else {
663 if (nc->nodepath && nc->nodepath->selected) {
664 sp_node_delete_preserve(g_list_copy(nc->nodepath->selected));
665 }
666 }
667 ret = TRUE;
668 break;
669 case GDK_C:
670 case GDK_c:
671 if (MOD__SHIFT_ONLY) {
672 sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
673 ret = TRUE;
674 }
675 break;
676 case GDK_S:
677 case GDK_s:
678 if (MOD__SHIFT_ONLY) {
679 sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
680 ret = TRUE;
681 }
682 break;
683 case GDK_Y:
684 case GDK_y:
685 if (MOD__SHIFT_ONLY) {
686 sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
687 ret = TRUE;
688 }
689 break;
690 case GDK_B:
691 case GDK_b:
692 if (MOD__SHIFT_ONLY) {
693 sp_node_selected_break();
694 ret = TRUE;
695 }
696 break;
697 case GDK_J:
698 case GDK_j:
699 if (MOD__SHIFT_ONLY) {
700 sp_node_selected_join();
701 ret = TRUE;
702 }
703 break;
704 case GDK_D:
705 case GDK_d:
706 if (MOD__SHIFT_ONLY) {
707 sp_node_selected_duplicate();
708 ret = TRUE;
709 }
710 break;
711 case GDK_L:
712 case GDK_l:
713 if (MOD__SHIFT_ONLY) {
714 sp_node_selected_set_line_type(NR_LINETO);
715 ret = TRUE;
716 }
717 break;
718 case GDK_U:
719 case GDK_u:
720 if (MOD__SHIFT_ONLY) {
721 sp_node_selected_set_line_type(NR_CURVETO);
722 ret = TRUE;
723 }
724 break;
725 case GDK_R:
726 case GDK_r:
727 if (MOD__SHIFT_ONLY) {
728 // FIXME: add top panel button
729 sp_selected_path_reverse();
730 ret = TRUE;
731 }
732 break;
733 case GDK_Left: // move selection left
734 case GDK_KP_Left:
735 case GDK_KP_4:
736 if (!MOD__CTRL) { // not ctrl
737 if (MOD__ALT) { // alt
738 if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
739 else sp_node_selected_move_screen(-1, 0); // no shift
740 }
741 else { // no alt
742 if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
743 else sp_node_selected_move(-nudge, 0); // no shift
744 }
745 ret = TRUE;
746 }
747 break;
748 case GDK_Up: // move selection up
749 case GDK_KP_Up:
750 case GDK_KP_8:
751 if (!MOD__CTRL) { // not ctrl
752 if (MOD__ALT) { // alt
753 if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
754 else sp_node_selected_move_screen(0, 1); // no shift
755 }
756 else { // no alt
757 if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
758 else sp_node_selected_move(0, nudge); // no shift
759 }
760 ret = TRUE;
761 }
762 break;
763 case GDK_Right: // move selection right
764 case GDK_KP_Right:
765 case GDK_KP_6:
766 if (!MOD__CTRL) { // not ctrl
767 if (MOD__ALT) { // alt
768 if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
769 else sp_node_selected_move_screen(1, 0); // no shift
770 }
771 else { // no alt
772 if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
773 else sp_node_selected_move(nudge, 0); // no shift
774 }
775 ret = TRUE;
776 }
777 break;
778 case GDK_Down: // move selection down
779 case GDK_KP_Down:
780 case GDK_KP_2:
781 if (!MOD__CTRL) { // not ctrl
782 if (MOD__ALT) { // alt
783 if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
784 else sp_node_selected_move_screen(0, -1); // no shift
785 }
786 else { // no alt
787 if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
788 else sp_node_selected_move(0, -nudge); // no shift
789 }
790 ret = TRUE;
791 }
792 break;
793 case GDK_Tab: // Tab - cycle selection forward
794 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
795 sp_nodepath_select_next(nc->nodepath);
796 ret = TRUE;
797 }
798 break;
799 case GDK_ISO_Left_Tab: // Shift Tab - cycle selection backward
800 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
801 sp_nodepath_select_prev(nc->nodepath);
802 ret = TRUE;
803 }
804 break;
805 case GDK_Escape:
806 {
807 NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
808 if (b != NR::Nothing()) {
809 Inkscape::Rubberband::get()->stop();
810 nc->rb_escaped = true;
811 } else {
812 if (nc->nodepath && nc->nodepath->selected) {
813 sp_nodepath_deselect(nc->nodepath);
814 } else {
815 sp_desktop_selection(desktop)->clear();
816 }
817 }
818 ret = TRUE;
819 break;
820 }
822 case GDK_bracketleft:
823 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
824 if (nc->leftctrl)
825 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
826 if (nc->rightctrl)
827 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
828 } else if ( MOD__ALT && !MOD__CTRL ) {
829 if (nc->leftalt && nc->rightalt)
830 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
831 else {
832 if (nc->leftalt)
833 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
834 if (nc->rightalt)
835 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
836 }
837 } else if ( snaps != 0 ) {
838 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
839 }
840 ret = TRUE;
841 break;
842 case GDK_bracketright:
843 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
844 if (nc->leftctrl)
845 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
846 if (nc->rightctrl)
847 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
848 } else if ( MOD__ALT && !MOD__CTRL ) {
849 if (nc->leftalt && nc->rightalt)
850 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
851 else {
852 if (nc->leftalt)
853 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
854 if (nc->rightalt)
855 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
856 }
857 } else if ( snaps != 0 ) {
858 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
859 }
860 ret = TRUE;
861 break;
862 case GDK_less:
863 case GDK_comma:
864 if (MOD__CTRL) {
865 if (nc->leftctrl)
866 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
867 if (nc->rightctrl)
868 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
869 } else if (MOD__ALT) {
870 if (nc->leftalt && nc->rightalt)
871 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
872 else {
873 if (nc->leftalt)
874 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
875 if (nc->rightalt)
876 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
877 }
878 } else {
879 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
880 }
881 ret = TRUE;
882 break;
883 case GDK_greater:
884 case GDK_period:
885 if (MOD__CTRL) {
886 if (nc->leftctrl)
887 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
888 if (nc->rightctrl)
889 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
890 } else if (MOD__ALT) {
891 if (nc->leftalt && nc->rightalt)
892 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
893 else {
894 if (nc->leftalt)
895 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
896 if (nc->rightalt)
897 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
898 }
899 } else {
900 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
901 }
902 ret = TRUE;
903 break;
905 case GDK_Alt_L:
906 nc->leftalt = TRUE;
907 sp_node_context_show_modifier_tip(event_context, event);
908 break;
909 case GDK_Alt_R:
910 nc->rightalt = TRUE;
911 sp_node_context_show_modifier_tip(event_context, event);
912 break;
913 case GDK_Control_L:
914 nc->leftctrl = TRUE;
915 sp_node_context_show_modifier_tip(event_context, event);
916 break;
917 case GDK_Control_R:
918 nc->rightctrl = TRUE;
919 sp_node_context_show_modifier_tip(event_context, event);
920 break;
921 case GDK_Shift_L:
922 case GDK_Shift_R:
923 case GDK_Meta_L:
924 case GDK_Meta_R:
925 sp_node_context_show_modifier_tip(event_context, event);
926 break;
927 default:
928 ret = node_key(event);
929 break;
930 }
931 break;
932 case GDK_KEY_RELEASE:
933 switch (get_group0_keyval(&event->key)) {
934 case GDK_Alt_L:
935 nc->leftalt = FALSE;
936 event_context->defaultMessageContext()->clear();
937 break;
938 case GDK_Alt_R:
939 nc->rightalt = FALSE;
940 event_context->defaultMessageContext()->clear();
941 break;
942 case GDK_Control_L:
943 nc->leftctrl = FALSE;
944 event_context->defaultMessageContext()->clear();
945 break;
946 case GDK_Control_R:
947 nc->rightctrl = FALSE;
948 event_context->defaultMessageContext()->clear();
949 break;
950 case GDK_Shift_L:
951 case GDK_Shift_R:
952 case GDK_Meta_L:
953 case GDK_Meta_R:
954 event_context->defaultMessageContext()->clear();
955 break;
956 }
957 break;
958 default:
959 break;
960 }
962 if (!ret) {
963 if (((SPEventContextClass *) parent_class)->root_handler)
964 ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
965 }
967 return ret;
968 }
971 /*
972 Local Variables:
973 mode:c++
974 c-file-style:"stroustrup"
975 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
976 indent-tabs-mode:nil
977 fill-column:99
978 End:
979 */
980 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :