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 // fixme: nc->nodepath can potentially become NULL after retrieving nc.
531 // A general method for handling this possibility should be created.
532 // For now, the number of checks for a NULL nc->nodepath have been
533 // increased, both here and in the called sp_nodepath_* functions.
535 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
536 double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
537 event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
538 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
539 double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
541 gint ret = FALSE;
543 switch (event->type) {
544 case GDK_BUTTON_PRESS:
545 if (event->button.button == 1) {
546 // save drag origin
547 event_context->xp = (gint) event->button.x;
548 event_context->yp = (gint) event->button.y;
549 event_context->within_tolerance = true;
550 nc->hit = false;
552 NR::Point const button_w(event->button.x,
553 event->button.y);
554 NR::Point const button_dt(desktop->w2d(button_w));
555 Inkscape::Rubberband::get()->start(desktop, button_dt);
556 desktop->updateNow();
557 ret = TRUE;
558 }
559 break;
560 case GDK_MOTION_NOTIFY:
561 if (event->motion.state & GDK_BUTTON1_MASK) {
563 if ( event_context->within_tolerance
564 && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
565 && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
566 break; // do not drag if we're within tolerance from origin
567 }
569 // The path went away while dragging; throw away any further motion
570 // events until the mouse pointer is released.
571 if (nc->hit && (nc->nodepath == NULL)) {
572 break;
573 }
575 // Once the user has moved farther than tolerance from the original location
576 // (indicating they intend to move the object, not click), then always process the
577 // motion notify coordinates as given (no snapping back to origin)
578 event_context->within_tolerance = false;
580 if (nc->nodepath && nc->hit) {
581 NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
582 event->motion.y - nc->curvepoint_event[NR::Y]);
583 NR::Point const delta_dt(desktop->w2d(delta_w));
584 sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
585 nc->curvepoint_event[NR::X] = (gint) event->motion.x;
586 nc->curvepoint_event[NR::Y] = (gint) event->motion.y;
587 gobble_motion_events(GDK_BUTTON1_MASK);
588 } else {
589 NR::Point const motion_w(event->motion.x,
590 event->motion.y);
591 NR::Point const motion_dt(desktop->w2d(motion_w));
592 Inkscape::Rubberband::get()->move(motion_dt);
593 }
594 nc->drag = TRUE;
595 ret = TRUE;
596 } else {
597 if (!nc->nodepath || selection->singleItem() == NULL) {
598 break;
599 }
601 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
602 NR::Point(event->motion.x, event->motion.y));
603 bool over_stroke = false;
604 if (item_over && nc->nodepath) {
605 over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
606 }
608 if (nc->cursor_drag && !over_stroke) {
609 event_context->cursor_shape = cursor_node_xpm;
610 event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
611 -1,
612 cursor_node_pixbuf,
613 FALSE,
614 NULL);
615 event_context->hot_x = 1;
616 event_context->hot_y = 1;
617 sp_event_context_update_cursor(event_context);
618 nc->cursor_drag = false;
619 } else if (!nc->cursor_drag && over_stroke) {
620 event_context->cursor_shape = cursor_node_d_xpm;
621 event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
622 -1,
623 cursor_node_d_pixbuf,
624 FALSE,
625 NULL);
626 event_context->hot_x = 1;
627 event_context->hot_y = 1;
628 sp_event_context_update_cursor(event_context);
629 nc->cursor_drag = true;
630 }
631 }
632 break;
633 case GDK_BUTTON_RELEASE:
634 event_context->xp = event_context->yp = 0;
635 if (event->button.button == 1) {
637 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
639 if (nc->hit && !event_context->within_tolerance) { //drag curve
640 if (nc->nodepath) {
641 sp_nodepath_update_repr (nc->nodepath, _("Drag curve"));
642 }
643 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
644 if (nc->nodepath) {
645 sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
646 }
647 } else {
648 if (!(nc->rb_escaped)) { // unless something was cancelled
649 if (nc->nodepath && nc->nodepath->selected)
650 sp_nodepath_deselect(nc->nodepath);
651 else
652 sp_desktop_selection(desktop)->clear();
653 }
654 }
655 ret = TRUE;
656 Inkscape::Rubberband::get()->stop();
657 desktop->updateNow();
658 nc->rb_escaped = false;
659 nc->drag = FALSE;
660 nc->hit = false;
661 break;
662 }
663 break;
664 case GDK_KEY_PRESS:
665 switch (get_group0_keyval(&event->key)) {
666 case GDK_Insert:
667 case GDK_KP_Insert:
668 // with any modifiers
669 sp_node_selected_add_node();
670 ret = TRUE;
671 break;
672 case GDK_Delete:
673 case GDK_KP_Delete:
674 case GDK_BackSpace:
675 if (MOD__CTRL_ONLY) {
676 sp_node_selected_delete();
677 } else {
678 if (nc->nodepath && nc->nodepath->selected) {
679 sp_node_delete_preserve(g_list_copy(nc->nodepath->selected));
680 }
681 }
682 ret = TRUE;
683 break;
684 case GDK_C:
685 case GDK_c:
686 if (MOD__SHIFT_ONLY) {
687 sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
688 ret = TRUE;
689 }
690 break;
691 case GDK_S:
692 case GDK_s:
693 if (MOD__SHIFT_ONLY) {
694 sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
695 ret = TRUE;
696 }
697 break;
698 case GDK_Y:
699 case GDK_y:
700 if (MOD__SHIFT_ONLY) {
701 sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
702 ret = TRUE;
703 }
704 break;
705 case GDK_B:
706 case GDK_b:
707 if (MOD__SHIFT_ONLY) {
708 sp_node_selected_break();
709 ret = TRUE;
710 }
711 break;
712 case GDK_J:
713 case GDK_j:
714 if (MOD__SHIFT_ONLY) {
715 sp_node_selected_join();
716 ret = TRUE;
717 }
718 break;
719 case GDK_D:
720 case GDK_d:
721 if (MOD__SHIFT_ONLY) {
722 sp_node_selected_duplicate();
723 ret = TRUE;
724 }
725 break;
726 case GDK_L:
727 case GDK_l:
728 if (MOD__SHIFT_ONLY) {
729 sp_node_selected_set_line_type(NR_LINETO);
730 ret = TRUE;
731 }
732 break;
733 case GDK_U:
734 case GDK_u:
735 if (MOD__SHIFT_ONLY) {
736 sp_node_selected_set_line_type(NR_CURVETO);
737 ret = TRUE;
738 }
739 break;
740 case GDK_R:
741 case GDK_r:
742 if (MOD__SHIFT_ONLY) {
743 // FIXME: add top panel button
744 sp_selected_path_reverse();
745 ret = TRUE;
746 }
747 break;
748 case GDK_Left: // move selection left
749 case GDK_KP_Left:
750 case GDK_KP_4:
751 if (!MOD__CTRL) { // not ctrl
752 if (MOD__ALT) { // alt
753 if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
754 else sp_node_selected_move_screen(-1, 0); // no shift
755 }
756 else { // no alt
757 if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
758 else sp_node_selected_move(-nudge, 0); // no shift
759 }
760 ret = TRUE;
761 }
762 break;
763 case GDK_Up: // move selection up
764 case GDK_KP_Up:
765 case GDK_KP_8:
766 if (!MOD__CTRL) { // not ctrl
767 if (MOD__ALT) { // alt
768 if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
769 else sp_node_selected_move_screen(0, 1); // no shift
770 }
771 else { // no alt
772 if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
773 else sp_node_selected_move(0, nudge); // no shift
774 }
775 ret = TRUE;
776 }
777 break;
778 case GDK_Right: // move selection right
779 case GDK_KP_Right:
780 case GDK_KP_6:
781 if (!MOD__CTRL) { // not ctrl
782 if (MOD__ALT) { // alt
783 if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
784 else sp_node_selected_move_screen(1, 0); // no shift
785 }
786 else { // no alt
787 if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
788 else sp_node_selected_move(nudge, 0); // no shift
789 }
790 ret = TRUE;
791 }
792 break;
793 case GDK_Down: // move selection down
794 case GDK_KP_Down:
795 case GDK_KP_2:
796 if (!MOD__CTRL) { // not ctrl
797 if (MOD__ALT) { // alt
798 if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
799 else sp_node_selected_move_screen(0, -1); // no shift
800 }
801 else { // no alt
802 if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
803 else sp_node_selected_move(0, -nudge); // no shift
804 }
805 ret = TRUE;
806 }
807 break;
808 case GDK_Tab: // Tab - cycle selection forward
809 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
810 sp_nodepath_select_next(nc->nodepath);
811 ret = TRUE;
812 }
813 break;
814 case GDK_ISO_Left_Tab: // Shift Tab - cycle selection backward
815 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
816 sp_nodepath_select_prev(nc->nodepath);
817 ret = TRUE;
818 }
819 break;
820 case GDK_Escape:
821 {
822 NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
823 if (b != NR::Nothing()) {
824 Inkscape::Rubberband::get()->stop();
825 nc->rb_escaped = true;
826 } else {
827 if (nc->nodepath && nc->nodepath->selected) {
828 sp_nodepath_deselect(nc->nodepath);
829 } else {
830 sp_desktop_selection(desktop)->clear();
831 }
832 }
833 ret = TRUE;
834 break;
835 }
837 case GDK_bracketleft:
838 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
839 if (nc->leftctrl)
840 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
841 if (nc->rightctrl)
842 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
843 } else if ( MOD__ALT && !MOD__CTRL ) {
844 if (nc->leftalt && nc->rightalt)
845 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
846 else {
847 if (nc->leftalt)
848 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
849 if (nc->rightalt)
850 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
851 }
852 } else if ( snaps != 0 ) {
853 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
854 }
855 ret = TRUE;
856 break;
857 case GDK_bracketright:
858 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
859 if (nc->leftctrl)
860 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
861 if (nc->rightctrl)
862 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
863 } else if ( MOD__ALT && !MOD__CTRL ) {
864 if (nc->leftalt && nc->rightalt)
865 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
866 else {
867 if (nc->leftalt)
868 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
869 if (nc->rightalt)
870 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
871 }
872 } else if ( snaps != 0 ) {
873 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
874 }
875 ret = TRUE;
876 break;
877 case GDK_less:
878 case GDK_comma:
879 if (MOD__CTRL) {
880 if (nc->leftctrl)
881 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
882 if (nc->rightctrl)
883 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
884 } else if (MOD__ALT) {
885 if (nc->leftalt && nc->rightalt)
886 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
887 else {
888 if (nc->leftalt)
889 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
890 if (nc->rightalt)
891 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
892 }
893 } else {
894 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
895 }
896 ret = TRUE;
897 break;
898 case GDK_greater:
899 case GDK_period:
900 if (MOD__CTRL) {
901 if (nc->leftctrl)
902 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
903 if (nc->rightctrl)
904 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
905 } else if (MOD__ALT) {
906 if (nc->leftalt && nc->rightalt)
907 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
908 else {
909 if (nc->leftalt)
910 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
911 if (nc->rightalt)
912 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
913 }
914 } else {
915 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
916 }
917 ret = TRUE;
918 break;
920 case GDK_Alt_L:
921 nc->leftalt = TRUE;
922 sp_node_context_show_modifier_tip(event_context, event);
923 break;
924 case GDK_Alt_R:
925 nc->rightalt = TRUE;
926 sp_node_context_show_modifier_tip(event_context, event);
927 break;
928 case GDK_Control_L:
929 nc->leftctrl = TRUE;
930 sp_node_context_show_modifier_tip(event_context, event);
931 break;
932 case GDK_Control_R:
933 nc->rightctrl = TRUE;
934 sp_node_context_show_modifier_tip(event_context, event);
935 break;
936 case GDK_Shift_L:
937 case GDK_Shift_R:
938 case GDK_Meta_L:
939 case GDK_Meta_R:
940 sp_node_context_show_modifier_tip(event_context, event);
941 break;
942 default:
943 ret = node_key(event);
944 break;
945 }
946 break;
947 case GDK_KEY_RELEASE:
948 switch (get_group0_keyval(&event->key)) {
949 case GDK_Alt_L:
950 nc->leftalt = FALSE;
951 event_context->defaultMessageContext()->clear();
952 break;
953 case GDK_Alt_R:
954 nc->rightalt = FALSE;
955 event_context->defaultMessageContext()->clear();
956 break;
957 case GDK_Control_L:
958 nc->leftctrl = FALSE;
959 event_context->defaultMessageContext()->clear();
960 break;
961 case GDK_Control_R:
962 nc->rightctrl = FALSE;
963 event_context->defaultMessageContext()->clear();
964 break;
965 case GDK_Shift_L:
966 case GDK_Shift_R:
967 case GDK_Meta_L:
968 case GDK_Meta_R:
969 event_context->defaultMessageContext()->clear();
970 break;
971 }
972 break;
973 default:
974 break;
975 }
977 if (!ret) {
978 if (((SPEventContextClass *) parent_class)->root_handler)
979 ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
980 }
982 return ret;
983 }
986 /*
987 Local Variables:
988 mode:c++
989 c-file-style:"stroustrup"
990 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
991 indent-tabs-mode:nil
992 fill-column:99
993 End:
994 */
995 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :