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