2b30d067b66449620c84e5ba5baaaf83e0c8f410
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 // We round off the extra precision in the motion coordinates provided
592 // by some input devices (like tablets). As we'll store the coordinates
593 // as integers in curvepoint_event we need to do this rounding before
594 // comparing them with the last coordinates from curvepoint_event.
595 // See bug #1593499 for details.
597 gint x = (gint) Inkscape::round(event->motion.x);
598 gint y = (gint) Inkscape::round(event->motion.y);
600 // The coordinates hasn't changed since the last motion event, abort
601 if (nc->curvepoint_event[NR::X] == x &&
602 nc->curvepoint_event[NR::Y] == y)
603 break;
605 NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
606 event->motion.y - nc->curvepoint_event[NR::Y]);
607 NR::Point const delta_dt(desktop->w2d(delta_w));
609 sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
610 nc->curvepoint_event[NR::X] = x;
611 nc->curvepoint_event[NR::Y] = y;
612 gobble_motion_events(GDK_BUTTON1_MASK);
613 break;
614 }
615 case SP_NODE_CONTEXT_RUBBERBAND_DRAGGING:
616 if (Inkscape::Rubberband::get()->is_started()) {
617 NR::Point const motion_w(event->motion.x,
618 event->motion.y);
619 NR::Point const motion_dt(desktop->w2d(motion_w));
620 Inkscape::Rubberband::get()->move(motion_dt);
621 }
622 break;
623 }
625 nc->drag = TRUE;
626 ret = TRUE;
627 } else {
628 if (!nc->nodepath || selection->singleItem() == NULL) {
629 break;
630 }
632 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
633 NR::Point(event->motion.x, event->motion.y));
634 bool over_stroke = false;
635 if (item_over && nc->nodepath) {
636 over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
637 }
639 if (nc->cursor_drag && !over_stroke) {
640 event_context->cursor_shape = cursor_node_xpm;
641 event_context->hot_x = 1;
642 event_context->hot_y = 1;
643 sp_event_context_update_cursor(event_context);
644 nc->cursor_drag = false;
645 } else if (!nc->cursor_drag && over_stroke) {
646 event_context->cursor_shape = cursor_node_d_xpm;
647 event_context->hot_x = 1;
648 event_context->hot_y = 1;
649 sp_event_context_update_cursor(event_context);
650 nc->cursor_drag = true;
651 }
652 }
653 break;
654 case GDK_BUTTON_RELEASE:
655 event_context->xp = event_context->yp = 0;
656 if (event->button.button == 1) {
658 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
660 if (nc->hit && !event_context->within_tolerance) { //drag curve
661 if (nc->nodepath) {
662 sp_nodepath_update_repr (nc->nodepath, _("Drag curve"));
663 }
664 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
665 if (nc->nodepath) {
666 sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
667 }
668 } else {
669 if (!(nc->rb_escaped)) { // unless something was cancelled
670 if (nc->nodepath && nc->nodepath->selected)
671 sp_nodepath_deselect(nc->nodepath);
672 else
673 sp_desktop_selection(desktop)->clear();
674 }
675 }
676 ret = TRUE;
677 Inkscape::Rubberband::get()->stop();
678 desktop->updateNow();
679 nc->rb_escaped = false;
680 nc->drag = FALSE;
681 nc->hit = false;
682 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
683 break;
684 }
685 break;
686 case GDK_KEY_PRESS:
687 switch (get_group0_keyval(&event->key)) {
688 case GDK_Insert:
689 case GDK_KP_Insert:
690 // with any modifiers
691 sp_node_selected_add_node();
692 ret = TRUE;
693 break;
694 case GDK_Delete:
695 case GDK_KP_Delete:
696 case GDK_BackSpace:
697 if (MOD__CTRL_ONLY) {
698 sp_node_selected_delete();
699 } else {
700 if (nc->nodepath && nc->nodepath->selected) {
701 sp_node_delete_preserve(g_list_copy(nc->nodepath->selected));
702 }
703 }
704 ret = TRUE;
705 break;
706 case GDK_C:
707 case GDK_c:
708 if (MOD__SHIFT_ONLY) {
709 sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
710 ret = TRUE;
711 }
712 break;
713 case GDK_S:
714 case GDK_s:
715 if (MOD__SHIFT_ONLY) {
716 sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
717 ret = TRUE;
718 }
719 break;
720 case GDK_Y:
721 case GDK_y:
722 if (MOD__SHIFT_ONLY) {
723 sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
724 ret = TRUE;
725 }
726 break;
727 case GDK_B:
728 case GDK_b:
729 if (MOD__SHIFT_ONLY) {
730 sp_node_selected_break();
731 ret = TRUE;
732 }
733 break;
734 case GDK_J:
735 case GDK_j:
736 if (MOD__SHIFT_ONLY) {
737 sp_node_selected_join();
738 ret = TRUE;
739 }
740 break;
741 case GDK_D:
742 case GDK_d:
743 if (MOD__SHIFT_ONLY) {
744 sp_node_selected_duplicate();
745 ret = TRUE;
746 }
747 break;
748 case GDK_L:
749 case GDK_l:
750 if (MOD__SHIFT_ONLY) {
751 sp_node_selected_set_line_type(NR_LINETO);
752 ret = TRUE;
753 }
754 break;
755 case GDK_U:
756 case GDK_u:
757 if (MOD__SHIFT_ONLY) {
758 sp_node_selected_set_line_type(NR_CURVETO);
759 ret = TRUE;
760 }
761 break;
762 case GDK_R:
763 case GDK_r:
764 if (MOD__SHIFT_ONLY) {
765 // FIXME: add top panel button
766 sp_selected_path_reverse();
767 ret = TRUE;
768 }
769 break;
770 case GDK_Left: // move selection left
771 case GDK_KP_Left:
772 case GDK_KP_4:
773 if (!MOD__CTRL) { // not ctrl
774 if (MOD__ALT) { // alt
775 if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
776 else sp_node_selected_move_screen(-1, 0); // no shift
777 }
778 else { // no alt
779 if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
780 else sp_node_selected_move(-nudge, 0); // no shift
781 }
782 ret = TRUE;
783 }
784 break;
785 case GDK_Up: // move selection up
786 case GDK_KP_Up:
787 case GDK_KP_8:
788 if (!MOD__CTRL) { // not ctrl
789 if (MOD__ALT) { // alt
790 if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
791 else sp_node_selected_move_screen(0, 1); // no shift
792 }
793 else { // no alt
794 if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
795 else sp_node_selected_move(0, nudge); // no shift
796 }
797 ret = TRUE;
798 }
799 break;
800 case GDK_Right: // move selection right
801 case GDK_KP_Right:
802 case GDK_KP_6:
803 if (!MOD__CTRL) { // not ctrl
804 if (MOD__ALT) { // alt
805 if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
806 else sp_node_selected_move_screen(1, 0); // no shift
807 }
808 else { // no alt
809 if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
810 else sp_node_selected_move(nudge, 0); // no shift
811 }
812 ret = TRUE;
813 }
814 break;
815 case GDK_Down: // move selection down
816 case GDK_KP_Down:
817 case GDK_KP_2:
818 if (!MOD__CTRL) { // not ctrl
819 if (MOD__ALT) { // alt
820 if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
821 else sp_node_selected_move_screen(0, -1); // no shift
822 }
823 else { // no alt
824 if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
825 else sp_node_selected_move(0, -nudge); // no shift
826 }
827 ret = TRUE;
828 }
829 break;
830 case GDK_Escape:
831 {
832 NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
833 if (b != NR::Nothing()) {
834 Inkscape::Rubberband::get()->stop();
835 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
836 nc->rb_escaped = true;
837 } else {
838 if (nc->nodepath && nc->nodepath->selected) {
839 sp_nodepath_deselect(nc->nodepath);
840 } else {
841 sp_desktop_selection(desktop)->clear();
842 }
843 }
844 ret = TRUE;
845 break;
846 }
848 case GDK_bracketleft:
849 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
850 if (nc->leftctrl)
851 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
852 if (nc->rightctrl)
853 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
854 } else if ( MOD__ALT && !MOD__CTRL ) {
855 if (nc->leftalt && nc->rightalt)
856 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
857 else {
858 if (nc->leftalt)
859 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
860 if (nc->rightalt)
861 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
862 }
863 } else if ( snaps != 0 ) {
864 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
865 }
866 ret = TRUE;
867 break;
868 case GDK_bracketright:
869 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
870 if (nc->leftctrl)
871 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
872 if (nc->rightctrl)
873 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
874 } else if ( MOD__ALT && !MOD__CTRL ) {
875 if (nc->leftalt && nc->rightalt)
876 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
877 else {
878 if (nc->leftalt)
879 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
880 if (nc->rightalt)
881 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
882 }
883 } else if ( snaps != 0 ) {
884 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
885 }
886 ret = TRUE;
887 break;
888 case GDK_less:
889 case GDK_comma:
890 if (MOD__CTRL) {
891 if (nc->leftctrl)
892 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
893 if (nc->rightctrl)
894 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
895 } else if (MOD__ALT) {
896 if (nc->leftalt && nc->rightalt)
897 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
898 else {
899 if (nc->leftalt)
900 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
901 if (nc->rightalt)
902 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
903 }
904 } else {
905 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
906 }
907 ret = TRUE;
908 break;
909 case GDK_greater:
910 case GDK_period:
911 if (MOD__CTRL) {
912 if (nc->leftctrl)
913 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
914 if (nc->rightctrl)
915 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
916 } else if (MOD__ALT) {
917 if (nc->leftalt && nc->rightalt)
918 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
919 else {
920 if (nc->leftalt)
921 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
922 if (nc->rightalt)
923 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
924 }
925 } else {
926 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
927 }
928 ret = TRUE;
929 break;
931 case GDK_Alt_L:
932 nc->leftalt = TRUE;
933 sp_node_context_show_modifier_tip(event_context, event);
934 break;
935 case GDK_Alt_R:
936 nc->rightalt = TRUE;
937 sp_node_context_show_modifier_tip(event_context, event);
938 break;
939 case GDK_Control_L:
940 nc->leftctrl = TRUE;
941 sp_node_context_show_modifier_tip(event_context, event);
942 break;
943 case GDK_Control_R:
944 nc->rightctrl = TRUE;
945 sp_node_context_show_modifier_tip(event_context, event);
946 break;
947 case GDK_Shift_L:
948 case GDK_Shift_R:
949 case GDK_Meta_L:
950 case GDK_Meta_R:
951 sp_node_context_show_modifier_tip(event_context, event);
952 break;
953 default:
954 ret = node_key(event);
955 break;
956 }
957 break;
958 case GDK_KEY_RELEASE:
959 switch (get_group0_keyval(&event->key)) {
960 case GDK_Alt_L:
961 nc->leftalt = FALSE;
962 event_context->defaultMessageContext()->clear();
963 break;
964 case GDK_Alt_R:
965 nc->rightalt = FALSE;
966 event_context->defaultMessageContext()->clear();
967 break;
968 case GDK_Control_L:
969 nc->leftctrl = FALSE;
970 event_context->defaultMessageContext()->clear();
971 break;
972 case GDK_Control_R:
973 nc->rightctrl = FALSE;
974 event_context->defaultMessageContext()->clear();
975 break;
976 case GDK_Shift_L:
977 case GDK_Shift_R:
978 case GDK_Meta_L:
979 case GDK_Meta_R:
980 event_context->defaultMessageContext()->clear();
981 break;
982 }
983 break;
984 default:
985 break;
986 }
988 if (!ret) {
989 if (((SPEventContextClass *) parent_class)->root_handler)
990 ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
991 }
993 return ret;
994 }
997 /*
998 Local Variables:
999 mode:c++
1000 c-file-style:"stroustrup"
1001 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1002 indent-tabs-mode:nil
1003 fill-column:99
1004 End:
1005 */
1006 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :