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 sp_nodepath_ensure_livarot_path(nc->nodepath);
382 NR::Maybe<Path::cut_position> position = get_nearest_position_on_Path(nc->nodepath->livarot_path, nc->curvepoint_doc);
383 NR::Point nearest = get_point_on_Path(nc->nodepath->livarot_path, position.assume().piece, position.assume().t);
384 NR::Point delta = nearest - nc->curvepoint_doc;
386 delta = desktop->d2w(delta);
388 double stroke_tolerance =
389 (SP_OBJECT_STYLE (item)->stroke.type != SP_PAINT_TYPE_NONE?
390 desktop->current_zoom() *
391 SP_OBJECT_STYLE (item)->stroke_width.computed *
392 sp_item_i2d_affine (item).expansion() * 0.5
393 : 0.0)
394 + (double) SP_EVENT_CONTEXT(nc)->tolerance;
396 bool close = (NR::L2 (delta) < stroke_tolerance);
398 if (remember && close) {
399 nc->curvepoint_event[NR::X] = (gint) event_p [NR::X];
400 nc->curvepoint_event[NR::Y] = (gint) event_p [NR::Y];
401 nc->hit = true;
402 nc->grab_t = position.assume().t;
403 nc->grab_node = sp_nodepath_get_node_by_index(position.assume().piece);
404 }
406 return close;
407 }
410 static gint
411 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
412 {
413 gint ret = FALSE;
415 SPDesktop *desktop = event_context->desktop;
416 Inkscape::Selection *selection = sp_desktop_selection (desktop);
418 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
420 switch (event->type) {
421 case GDK_2BUTTON_PRESS:
422 case GDK_BUTTON_RELEASE:
423 if (event->button.button == 1) {
424 if (!nc->drag) {
426 // find out clicked item, disregarding groups, honoring Alt
427 SPItem *item_clicked = sp_event_context_find_item (desktop,
428 NR::Point(event->button.x, event->button.y),
429 (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
430 // find out if we're over the selected item, disregarding groups
431 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
432 NR::Point(event->button.x, event->button.y));
434 bool over_stroke = false;
435 if (item_over && nc->nodepath) {
436 over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), false);
437 }
439 if (over_stroke || nc->added_node) {
440 switch (event->type) {
441 case GDK_BUTTON_RELEASE:
442 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
443 //add a node
444 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
445 } else {
446 if (nc->added_node) { // we just received double click, ignore release
447 nc->added_node = false;
448 break;
449 }
450 //select the segment
451 if (event->button.state & GDK_SHIFT_MASK) {
452 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, true);
453 } else {
454 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
455 }
456 desktop->updateNow();
457 }
458 break;
459 case GDK_2BUTTON_PRESS:
460 //add a node
461 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
462 nc->added_node = true;
463 break;
464 default:
465 break;
466 }
467 } else if (event->button.state & GDK_SHIFT_MASK) {
468 selection->toggle(item_clicked);
469 desktop->updateNow();
470 } else {
471 selection->set(item_clicked);
472 desktop->updateNow();
473 }
475 ret = TRUE;
476 }
477 break;
478 }
479 break;
480 case GDK_BUTTON_PRESS:
481 if (event->button.button == 1 && !(event->button.state & GDK_SHIFT_MASK)) {
482 // save drag origin
483 event_context->xp = (gint) event->button.x;
484 event_context->yp = (gint) event->button.y;
485 event_context->within_tolerance = true;
486 nc->hit = false;
488 if (!nc->drag) {
489 // find out if we're over the selected item, disregarding groups
490 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
491 NR::Point(event->button.x, event->button.y));
493 if (nc->nodepath && selection->single() && item_over) {
495 // save drag origin
496 bool over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), true);
497 //only dragging curves
498 if (over_stroke) {
499 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
500 ret = TRUE;
501 } else {
502 break;
503 }
504 } else {
505 break;
506 }
508 ret = TRUE;
509 }
510 break;
511 }
512 break;
513 default:
514 break;
515 }
517 if (!ret) {
518 if (((SPEventContextClass *) parent_class)->item_handler)
519 ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
520 }
522 return ret;
523 }
525 static gint
526 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
527 {
528 SPDesktop *desktop = event_context->desktop;
529 Inkscape::Selection *selection = sp_desktop_selection (desktop);
531 // fixme: nc->nodepath can potentially become NULL after retrieving nc.
532 // A general method for handling this possibility should be created.
533 // For now, the number of checks for a NULL nc->nodepath have been
534 // increased, both here and in the called sp_nodepath_* functions.
536 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
537 double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
538 event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
539 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
540 double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
542 gint ret = FALSE;
544 switch (event->type) {
545 case GDK_BUTTON_PRESS:
546 if (event->button.button == 1) {
547 // save drag origin
548 event_context->xp = (gint) event->button.x;
549 event_context->yp = (gint) event->button.y;
550 event_context->within_tolerance = true;
551 nc->hit = false;
553 NR::Point const button_w(event->button.x,
554 event->button.y);
555 NR::Point const button_dt(desktop->w2d(button_w));
556 Inkscape::Rubberband::get()->start(desktop, button_dt);
557 desktop->updateNow();
558 ret = TRUE;
559 }
560 break;
561 case GDK_MOTION_NOTIFY:
562 if (event->motion.state & GDK_BUTTON1_MASK) {
564 if ( event_context->within_tolerance
565 && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
566 && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
567 break; // do not drag if we're within tolerance from origin
568 }
570 // The path went away while dragging; throw away any further motion
571 // events until the mouse pointer is released.
572 if (nc->hit && (nc->nodepath == NULL)) {
573 break;
574 }
576 // Once the user has moved farther than tolerance from the original location
577 // (indicating they intend to move the object, not click), then always process the
578 // motion notify coordinates as given (no snapping back to origin)
579 event_context->within_tolerance = false;
581 if (nc->nodepath && nc->hit) {
582 NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
583 event->motion.y - nc->curvepoint_event[NR::Y]);
584 NR::Point const delta_dt(desktop->w2d(delta_w));
585 sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
586 nc->curvepoint_event[NR::X] = (gint) event->motion.x;
587 nc->curvepoint_event[NR::Y] = (gint) event->motion.y;
588 gobble_motion_events(GDK_BUTTON1_MASK);
589 } else {
590 if (Inkscape::Rubberband::get()->is_started()) {
591 NR::Point const motion_w(event->motion.x,
592 event->motion.y);
593 NR::Point const motion_dt(desktop->w2d(motion_w));
594 Inkscape::Rubberband::get()->move(motion_dt);
595 }
596 }
597 nc->drag = TRUE;
598 ret = TRUE;
599 } else {
600 if (!nc->nodepath || selection->singleItem() == NULL) {
601 break;
602 }
604 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
605 NR::Point(event->motion.x, event->motion.y));
606 bool over_stroke = false;
607 if (item_over && nc->nodepath) {
608 over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
609 }
611 if (nc->cursor_drag && !over_stroke) {
612 event_context->cursor_shape = cursor_node_xpm;
613 event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
614 -1,
615 cursor_node_pixbuf,
616 FALSE,
617 NULL);
618 event_context->hot_x = 1;
619 event_context->hot_y = 1;
620 sp_event_context_update_cursor(event_context);
621 nc->cursor_drag = false;
622 } else if (!nc->cursor_drag && over_stroke) {
623 event_context->cursor_shape = cursor_node_d_xpm;
624 event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
625 -1,
626 cursor_node_d_pixbuf,
627 FALSE,
628 NULL);
629 event_context->hot_x = 1;
630 event_context->hot_y = 1;
631 sp_event_context_update_cursor(event_context);
632 nc->cursor_drag = true;
633 }
634 }
635 break;
636 case GDK_BUTTON_RELEASE:
637 event_context->xp = event_context->yp = 0;
638 if (event->button.button == 1) {
640 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
642 if (nc->hit && !event_context->within_tolerance) { //drag curve
643 if (nc->nodepath) {
644 sp_nodepath_update_repr (nc->nodepath, _("Drag curve"));
645 }
646 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
647 if (nc->nodepath) {
648 sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
649 }
650 } else {
651 if (!(nc->rb_escaped)) { // unless something was cancelled
652 if (nc->nodepath && nc->nodepath->selected)
653 sp_nodepath_deselect(nc->nodepath);
654 else
655 sp_desktop_selection(desktop)->clear();
656 }
657 }
658 ret = TRUE;
659 Inkscape::Rubberband::get()->stop();
660 desktop->updateNow();
661 nc->rb_escaped = false;
662 nc->drag = FALSE;
663 nc->hit = false;
664 break;
665 }
666 break;
667 case GDK_KEY_PRESS:
668 switch (get_group0_keyval(&event->key)) {
669 case GDK_Insert:
670 case GDK_KP_Insert:
671 // with any modifiers
672 sp_node_selected_add_node();
673 ret = TRUE;
674 break;
675 case GDK_Delete:
676 case GDK_KP_Delete:
677 case GDK_BackSpace:
678 if (MOD__CTRL_ONLY) {
679 sp_node_selected_delete();
680 } else {
681 if (nc->nodepath && nc->nodepath->selected) {
682 sp_node_delete_preserve(g_list_copy(nc->nodepath->selected));
683 }
684 }
685 ret = TRUE;
686 break;
687 case GDK_C:
688 case GDK_c:
689 if (MOD__SHIFT_ONLY) {
690 sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
691 ret = TRUE;
692 }
693 break;
694 case GDK_S:
695 case GDK_s:
696 if (MOD__SHIFT_ONLY) {
697 sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
698 ret = TRUE;
699 }
700 break;
701 case GDK_Y:
702 case GDK_y:
703 if (MOD__SHIFT_ONLY) {
704 sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
705 ret = TRUE;
706 }
707 break;
708 case GDK_B:
709 case GDK_b:
710 if (MOD__SHIFT_ONLY) {
711 sp_node_selected_break();
712 ret = TRUE;
713 }
714 break;
715 case GDK_J:
716 case GDK_j:
717 if (MOD__SHIFT_ONLY) {
718 sp_node_selected_join();
719 ret = TRUE;
720 }
721 break;
722 case GDK_D:
723 case GDK_d:
724 if (MOD__SHIFT_ONLY) {
725 sp_node_selected_duplicate();
726 ret = TRUE;
727 }
728 break;
729 case GDK_L:
730 case GDK_l:
731 if (MOD__SHIFT_ONLY) {
732 sp_node_selected_set_line_type(NR_LINETO);
733 ret = TRUE;
734 }
735 break;
736 case GDK_U:
737 case GDK_u:
738 if (MOD__SHIFT_ONLY) {
739 sp_node_selected_set_line_type(NR_CURVETO);
740 ret = TRUE;
741 }
742 break;
743 case GDK_R:
744 case GDK_r:
745 if (MOD__SHIFT_ONLY) {
746 // FIXME: add top panel button
747 sp_selected_path_reverse();
748 ret = TRUE;
749 }
750 break;
751 case GDK_Left: // move selection left
752 case GDK_KP_Left:
753 case GDK_KP_4:
754 if (!MOD__CTRL) { // not ctrl
755 if (MOD__ALT) { // alt
756 if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
757 else sp_node_selected_move_screen(-1, 0); // no shift
758 }
759 else { // no alt
760 if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
761 else sp_node_selected_move(-nudge, 0); // no shift
762 }
763 ret = TRUE;
764 }
765 break;
766 case GDK_Up: // move selection up
767 case GDK_KP_Up:
768 case GDK_KP_8:
769 if (!MOD__CTRL) { // not ctrl
770 if (MOD__ALT) { // alt
771 if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
772 else sp_node_selected_move_screen(0, 1); // no shift
773 }
774 else { // no alt
775 if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
776 else sp_node_selected_move(0, nudge); // no shift
777 }
778 ret = TRUE;
779 }
780 break;
781 case GDK_Right: // move selection right
782 case GDK_KP_Right:
783 case GDK_KP_6:
784 if (!MOD__CTRL) { // not ctrl
785 if (MOD__ALT) { // alt
786 if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
787 else sp_node_selected_move_screen(1, 0); // no shift
788 }
789 else { // no alt
790 if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
791 else sp_node_selected_move(nudge, 0); // no shift
792 }
793 ret = TRUE;
794 }
795 break;
796 case GDK_Down: // move selection down
797 case GDK_KP_Down:
798 case GDK_KP_2:
799 if (!MOD__CTRL) { // not ctrl
800 if (MOD__ALT) { // alt
801 if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
802 else sp_node_selected_move_screen(0, -1); // no shift
803 }
804 else { // no alt
805 if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
806 else sp_node_selected_move(0, -nudge); // no shift
807 }
808 ret = TRUE;
809 }
810 break;
811 case GDK_Tab: // Tab - cycle selection forward
812 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
813 sp_nodepath_select_next(nc->nodepath);
814 ret = TRUE;
815 }
816 break;
817 case GDK_ISO_Left_Tab: // Shift Tab - cycle selection backward
818 if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
819 sp_nodepath_select_prev(nc->nodepath);
820 ret = TRUE;
821 }
822 break;
823 case GDK_Escape:
824 {
825 NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
826 if (b != NR::Nothing()) {
827 Inkscape::Rubberband::get()->stop();
828 nc->rb_escaped = true;
829 } else {
830 if (nc->nodepath && nc->nodepath->selected) {
831 sp_nodepath_deselect(nc->nodepath);
832 } else {
833 sp_desktop_selection(desktop)->clear();
834 }
835 }
836 ret = TRUE;
837 break;
838 }
840 case GDK_bracketleft:
841 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
842 if (nc->leftctrl)
843 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
844 if (nc->rightctrl)
845 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
846 } else if ( MOD__ALT && !MOD__CTRL ) {
847 if (nc->leftalt && nc->rightalt)
848 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
849 else {
850 if (nc->leftalt)
851 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
852 if (nc->rightalt)
853 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
854 }
855 } else if ( snaps != 0 ) {
856 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
857 }
858 ret = TRUE;
859 break;
860 case GDK_bracketright:
861 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
862 if (nc->leftctrl)
863 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
864 if (nc->rightctrl)
865 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
866 } else if ( MOD__ALT && !MOD__CTRL ) {
867 if (nc->leftalt && nc->rightalt)
868 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
869 else {
870 if (nc->leftalt)
871 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
872 if (nc->rightalt)
873 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
874 }
875 } else if ( snaps != 0 ) {
876 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
877 }
878 ret = TRUE;
879 break;
880 case GDK_less:
881 case GDK_comma:
882 if (MOD__CTRL) {
883 if (nc->leftctrl)
884 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
885 if (nc->rightctrl)
886 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
887 } else if (MOD__ALT) {
888 if (nc->leftalt && nc->rightalt)
889 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
890 else {
891 if (nc->leftalt)
892 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
893 if (nc->rightalt)
894 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
895 }
896 } else {
897 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
898 }
899 ret = TRUE;
900 break;
901 case GDK_greater:
902 case GDK_period:
903 if (MOD__CTRL) {
904 if (nc->leftctrl)
905 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
906 if (nc->rightctrl)
907 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
908 } else if (MOD__ALT) {
909 if (nc->leftalt && nc->rightalt)
910 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
911 else {
912 if (nc->leftalt)
913 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
914 if (nc->rightalt)
915 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
916 }
917 } else {
918 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
919 }
920 ret = TRUE;
921 break;
923 case GDK_Alt_L:
924 nc->leftalt = TRUE;
925 sp_node_context_show_modifier_tip(event_context, event);
926 break;
927 case GDK_Alt_R:
928 nc->rightalt = TRUE;
929 sp_node_context_show_modifier_tip(event_context, event);
930 break;
931 case GDK_Control_L:
932 nc->leftctrl = TRUE;
933 sp_node_context_show_modifier_tip(event_context, event);
934 break;
935 case GDK_Control_R:
936 nc->rightctrl = TRUE;
937 sp_node_context_show_modifier_tip(event_context, event);
938 break;
939 case GDK_Shift_L:
940 case GDK_Shift_R:
941 case GDK_Meta_L:
942 case GDK_Meta_R:
943 sp_node_context_show_modifier_tip(event_context, event);
944 break;
945 default:
946 ret = node_key(event);
947 break;
948 }
949 break;
950 case GDK_KEY_RELEASE:
951 switch (get_group0_keyval(&event->key)) {
952 case GDK_Alt_L:
953 nc->leftalt = FALSE;
954 event_context->defaultMessageContext()->clear();
955 break;
956 case GDK_Alt_R:
957 nc->rightalt = FALSE;
958 event_context->defaultMessageContext()->clear();
959 break;
960 case GDK_Control_L:
961 nc->leftctrl = FALSE;
962 event_context->defaultMessageContext()->clear();
963 break;
964 case GDK_Control_R:
965 nc->rightctrl = FALSE;
966 event_context->defaultMessageContext()->clear();
967 break;
968 case GDK_Shift_L:
969 case GDK_Shift_R:
970 case GDK_Meta_L:
971 case GDK_Meta_R:
972 event_context->defaultMessageContext()->clear();
973 break;
974 }
975 break;
976 default:
977 break;
978 }
980 if (!ret) {
981 if (((SPEventContextClass *) parent_class)->root_handler)
982 ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
983 }
985 return ret;
986 }
989 /*
990 Local Variables:
991 mode:c++
992 c-file-style:"stroustrup"
993 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
994 indent-tabs-mode:nil
995 fill-column:99
996 End:
997 */
998 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :