146237346b1360899a7e97afecb2f8381ccbd096
1 #define __SP_EVENT_CONTEXT_C__
3 /** \file
4 * Main event handling, and related helper functions.
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * Frank Felfe <innerspace@iname.com>
9 * bulia byak <buliabyak@users.sf.net>
10 *
11 * Copyright (C) 1999-2005 authors
12 * Copyright (C) 2001-2002 Ximian, Inc.
13 *
14 * Released under GNU GPL, read the file 'COPYING' for more information
15 */
17 /** \class SPEventContext
18 * SPEventContext is an abstract base class of all tools. As the name
19 * indicates, event context implementations process UI events (mouse
20 * movements and keypresses) and take actions (like creating or modifying
21 * objects). There is one event context implementation for each tool,
22 * plus few abstract base classes. Writing a new tool involves
23 * subclassing SPEventContext.
24 */
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
30 #include <string.h>
31 #include <gdk/gdkkeysyms.h>
32 #include <gtk/gtkmain.h>
33 #include <gtk/gtkmenu.h>
34 #include <glibmm/i18n.h>
36 #include "display/sp-canvas.h"
37 #include "xml/node-event-vector.h"
38 #include "sp-cursor.h"
39 #include "shortcuts.h"
40 #include "desktop.h"
41 #include "desktop-handles.h"
42 #include "selection.h"
43 #include "file.h"
44 #include "interface.h"
45 #include "macros.h"
46 #include "tools-switch.h"
47 #include "prefs-utils.h"
48 #include "message-context.h"
49 #include "gradient-drag.h"
50 #include "object-edit.h"
51 #include "attributes.h"
52 #include "rubberband.h"
53 #include "selcue.h"
55 #include "event-context.h"
57 static void sp_event_context_class_init(SPEventContextClass *klass);
58 static void sp_event_context_init(SPEventContext *event_context);
59 static void sp_event_context_dispose(GObject *object);
61 static void sp_event_context_private_setup(SPEventContext *ec);
62 static gint sp_event_context_private_root_handler(SPEventContext *event_context, GdkEvent *event);
63 static gint sp_event_context_private_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
65 static void set_event_location(SPDesktop * desktop, GdkEvent * event);
67 static GObjectClass *parent_class;
69 // globals for temporary switching to selector by space
70 static bool selector_toggled = FALSE;
71 static int switch_selector_to = 0;
73 // globals for temporary switching to dropper by 'D'
74 static bool dropper_toggled = FALSE;
75 static int switch_dropper_to = 0;
77 static gint xp = 0, yp = 0; // where drag started
78 static gint tolerance = 0;
79 static bool within_tolerance = false;
81 // globals for keeping track of keyboard scroll events in order to accelerate
82 static guint32 scroll_event_time = 0;
83 static gdouble scroll_multiply = 1;
84 static guint scroll_keyval = 0;
86 /**
87 * Registers the SPEventContext class with Glib and returns its type number.
88 */
89 GType
90 sp_event_context_get_type(void)
91 {
92 static GType type = 0;
93 if (!type) {
94 GTypeInfo info = {
95 sizeof(SPEventContextClass),
96 NULL, NULL,
97 (GClassInitFunc) sp_event_context_class_init,
98 NULL, NULL,
99 sizeof(SPEventContext),
100 4,
101 (GInstanceInitFunc) sp_event_context_init,
102 NULL, /* value_table */
103 };
104 type = g_type_register_static(G_TYPE_OBJECT, "SPEventContext", &info, (GTypeFlags)0);
105 }
106 return type;
107 }
109 /**
110 * Callback to set up the SPEventContext vtable.
111 */
112 static void
113 sp_event_context_class_init(SPEventContextClass *klass)
114 {
115 GObjectClass *object_class;
117 object_class = (GObjectClass *) klass;
119 parent_class = (GObjectClass*)g_type_class_peek_parent(klass);
121 object_class->dispose = sp_event_context_dispose;
123 klass->setup = sp_event_context_private_setup;
124 klass->root_handler = sp_event_context_private_root_handler;
125 klass->item_handler = sp_event_context_private_item_handler;
126 }
128 /**
129 * Clears all SPEventContext object members.
130 */
131 static void
132 sp_event_context_init(SPEventContext *event_context)
133 {
134 event_context->desktop = NULL;
135 event_context->cursor = NULL;
136 event_context->_message_context = NULL;
137 event_context->_selcue = NULL;
138 event_context->_grdrag = NULL;
139 event_context->space_panning = false;
140 }
142 /**
143 * Callback to free and null member variables of SPEventContext object.
144 */
145 static void
146 sp_event_context_dispose(GObject *object)
147 {
148 SPEventContext *ec;
150 ec = SP_EVENT_CONTEXT(object);
152 if (ec->_message_context) {
153 delete ec->_message_context;
154 }
156 if (ec->cursor != NULL) {
157 gdk_cursor_unref(ec->cursor);
158 ec->cursor = NULL;
159 }
161 if (ec->desktop) {
162 ec->desktop = NULL;
163 }
165 if (ec->prefs_repr) {
166 sp_repr_remove_listener_by_data(ec->prefs_repr, ec);
167 Inkscape::GC::release(ec->prefs_repr);
168 ec->prefs_repr = NULL;
169 }
171 G_OBJECT_CLASS(parent_class)->dispose(object);
172 }
174 /**
175 * Recreates and draws cursor on desktop related to SPEventContext.
176 */
177 void
178 sp_event_context_update_cursor(SPEventContext *ec)
179 {
180 GtkWidget *w = GTK_WIDGET(sp_desktop_canvas(ec->desktop));
181 if (w->window) {
182 /* fixme: */
183 if (ec->cursor_shape) {
184 GdkBitmap *bitmap = NULL;
185 GdkBitmap *mask = NULL;
186 sp_cursor_bitmap_and_mask_from_xpm(&bitmap, &mask, ec->cursor_shape);
187 if ((bitmap != NULL) && (mask != NULL)) {
188 if (ec->cursor)
189 gdk_cursor_unref (ec->cursor);
190 ec->cursor = gdk_cursor_new_from_pixmap(bitmap, mask,
191 &w->style->black,
192 &w->style->white,
193 ec->hot_x, ec->hot_y);
194 g_object_unref (bitmap);
195 g_object_unref (mask);
196 }
197 }
198 gdk_window_set_cursor(w->window, ec->cursor);
199 }
200 ec->desktop->waiting_cursor = false;
201 }
203 /**
204 * Callback that gets called on initialization of SPEventContext object.
205 * Redraws mouse cursor, at the moment.
206 */
207 static void
208 sp_event_context_private_setup(SPEventContext *ec)
209 {
210 sp_event_context_update_cursor(ec);
211 }
213 /**
214 * \brief Gobbles next key events on the queue with the same keyval and mask. Returns the number of events consumed.
215 */
216 gint gobble_key_events(guint keyval, gint mask)
217 {
218 GdkEvent *event_next;
219 gint i = 0;
221 event_next = gdk_event_get();
222 // while the next event is also a key notify with the same keyval and mask,
223 while (event_next && (event_next->type == GDK_KEY_PRESS || event_next->type == GDK_KEY_RELEASE)
224 && event_next->key.keyval == keyval
225 && (!mask || (event_next->key.state & mask))) {
226 if (event_next->type == GDK_KEY_PRESS)
227 i ++;
228 // kill it
229 gdk_event_free(event_next);
230 // get next
231 event_next = gdk_event_get();
232 }
233 // otherwise, put it back onto the queue
234 if (event_next) gdk_event_put(event_next);
236 return i;
237 }
239 /**
240 * \brief Gobbles next motion notify events on the queue with the same mask. Returns the number of events consumed.
241 */
242 gint gobble_motion_events(gint mask)
243 {
244 GdkEvent *event_next;
245 gint i = 0;
247 event_next = gdk_event_get();
248 // while the next event is also a key notify with the same keyval and mask,
249 while (event_next && event_next->type == GDK_MOTION_NOTIFY
250 && (event_next->motion.state & mask)) {
251 // kill it
252 gdk_event_free(event_next);
253 // get next
254 event_next = gdk_event_get();
255 i ++;
256 }
257 // otherwise, put it back onto the queue
258 if (event_next) gdk_event_put(event_next);
260 return i;
261 }
263 /**
264 * Toggles current tool between active tool and selector tool.
265 * Subroutine of sp_event_context_private_root_handler().
266 */
267 static void
268 sp_toggle_selector(SPDesktop *dt)
269 {
270 if (!dt->event_context) return;
272 if (tools_isactive(dt, TOOLS_SELECT)) {
273 if (selector_toggled) {
274 if (switch_selector_to) tools_switch (dt, switch_selector_to);
275 selector_toggled = FALSE;
276 } else return;
277 } else {
278 selector_toggled = TRUE;
279 switch_selector_to = tools_active(dt);
280 tools_switch (dt, TOOLS_SELECT);
281 }
282 }
284 /**
285 * Toggles current tool between active tool and dropper tool.
286 * Subroutine of sp_event_context_private_root_handler().
287 */
288 static void
289 sp_toggle_dropper(SPDesktop *dt)
290 {
291 if (!dt->event_context) return;
293 if (tools_isactive(dt, TOOLS_DROPPER)) {
294 if (dropper_toggled) {
295 if (switch_dropper_to) tools_switch (dt, switch_dropper_to);
296 dropper_toggled = FALSE;
297 } else return;
298 } else {
299 dropper_toggled = TRUE;
300 switch_dropper_to = tools_active(dt);
301 tools_switch (dt, TOOLS_DROPPER);
302 }
303 }
305 /**
306 * Calculates and keeps track of scroll acceleration.
307 * Subroutine of sp_event_context_private_root_handler().
308 */
309 static gdouble accelerate_scroll(GdkEvent *event, gdouble acceleration, SPCanvas */*canvas*/)
310 {
311 guint32 time_diff = ((GdkEventKey *) event)->time - scroll_event_time;
313 /* key pressed within 500ms ? (1/2 second) */
314 if (time_diff > 500 || event->key.keyval != scroll_keyval) {
315 scroll_multiply = 1; // abort acceleration
316 } else {
317 scroll_multiply += acceleration; // continue acceleration
318 }
320 scroll_event_time = ((GdkEventKey *) event)->time;
321 scroll_keyval = event->key.keyval;
323 return scroll_multiply;
324 }
326 /**
327 * Main event dispatch, gets called from Gdk.
328 */
329 static gint sp_event_context_private_root_handler(SPEventContext *event_context, GdkEvent *event)
330 {
331 static NR::Point button_w;
332 static unsigned int panning = 0;
333 static unsigned int zoom_rb = 0;
335 SPDesktop *desktop = event_context->desktop;
337 tolerance = prefs_get_int_attribute_limited(
338 "options.dragtolerance","value", 0, 0, 100);
339 double const zoom_inc = prefs_get_double_attribute_limited(
340 "options.zoomincrement", "value", M_SQRT2, 1.01, 10);
341 double const acceleration = prefs_get_double_attribute_limited(
342 "options.scrollingacceleration", "value", 0, 0, 6);
343 int const key_scroll = prefs_get_int_attribute_limited(
344 "options.keyscroll", "value", 10, 0, 1000);
345 int const wheel_scroll = prefs_get_int_attribute_limited(
346 "options.wheelscroll", "value", 40, 0, 1000);
348 gint ret = FALSE;
350 switch (event->type) {
351 case GDK_2BUTTON_PRESS:
352 if (panning) {
353 panning = 0;
354 sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
355 event->button.time);
356 ret = TRUE;
357 } else {
358 /* sp_desktop_dialog(); */
359 }
360 break;
361 case GDK_BUTTON_PRESS:
363 // save drag origin
364 xp = (gint) event->button.x;
365 yp = (gint) event->button.y;
366 within_tolerance = true;
368 button_w = NR::Point(event->button.x, event->button.y);
370 switch (event->button.button) {
371 case 1:
372 if (event_context->space_panning) {
373 panning = 1;
374 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
375 GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
376 NULL, event->button.time-1);
377 ret = TRUE;
378 }
379 break;
380 case 2:
381 if (event->button.state == GDK_SHIFT_MASK) {
382 zoom_rb = 2;
383 } else {
384 panning = 2;
385 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
386 GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
387 NULL, event->button.time-1);
388 }
389 ret = TRUE;
390 break;
391 case 3:
392 if (event->button.state & GDK_SHIFT_MASK
393 || event->button.state & GDK_CONTROL_MASK) {
394 panning = 3;
395 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
396 GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
397 NULL, event->button.time);
398 ret = TRUE;
399 } else {
400 sp_event_root_menu_popup(desktop, NULL, event);
401 }
402 break;
403 default:
404 break;
405 }
406 break;
407 case GDK_MOTION_NOTIFY:
408 if (panning) {
409 if ((panning == 2 && !(event->motion.state & GDK_BUTTON2_MASK))
410 || (panning == 1 && !(event->motion.state & GDK_BUTTON1_MASK))
411 || (panning == 3 && !(event->motion.state & GDK_BUTTON3_MASK))
412 ) {
413 /* Gdk seems to lose button release for us sometimes :-( */
414 panning = 0;
415 sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
416 event->button.time);
417 ret = TRUE;
418 } else {
419 if ( within_tolerance
420 && ( abs( (gint) event->motion.x - xp ) < tolerance )
421 && ( abs( (gint) event->motion.y - yp ) < tolerance ))
422 {
423 // do not drag if we're within tolerance from origin
424 break;
425 }
426 // Once the user has moved farther than tolerance from
427 // the original location (indicating they intend to move
428 // the object, not click), then always process the motion
429 // notify coordinates as given (no snapping back to origin)
430 within_tolerance = false;
432 // gobble subsequent motion events to prevent "sticking"
433 // when scrolling is slow
434 gobble_motion_events(panning == 2 ?
435 GDK_BUTTON2_MASK :
436 (panning == 1 ? GDK_BUTTON1_MASK : GDK_BUTTON3_MASK));
438 NR::Point const motion_w(event->motion.x, event->motion.y);
439 NR::Point const moved_w( motion_w - button_w );
440 event_context->desktop->scroll_world(moved_w, true); // we're still scrolling, do not redraw
441 ret = TRUE;
442 }
443 } else if (zoom_rb) {
444 NR::Point const motion_w(event->motion.x, event->motion.y);
445 NR::Point const motion_dt(desktop->w2d(motion_w));
447 if ( within_tolerance
448 && ( abs( (gint) event->motion.x - xp ) < tolerance )
449 && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) {
450 break; // do not drag if we're within tolerance from origin
451 }
452 // Once the user has moved farther than tolerance from the original location
453 // (indicating they intend to move the object, not click), then always process the
454 // motion notify coordinates as given (no snapping back to origin)
455 within_tolerance = false;
457 if (Inkscape::Rubberband::get()->is_started()) {
458 Inkscape::Rubberband::get()->move(motion_dt);
459 } else {
460 Inkscape::Rubberband::get()->start(desktop, motion_dt);
461 }
462 if (zoom_rb == 2)
463 gobble_motion_events(GDK_BUTTON2_MASK);
464 }
465 break;
466 case GDK_BUTTON_RELEASE:
467 xp = yp = 0;
468 if (within_tolerance && (panning || zoom_rb)) {
469 zoom_rb = 0;
470 if (panning) {
471 panning = 0;
472 sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
473 event->button.time);
474 }
475 NR::Point const event_w(event->button.x, event->button.y);
476 NR::Point const event_dt(desktop->w2d(event_w));
477 desktop->zoom_relative_keep_point(event_dt,
478 (event->button.state & GDK_SHIFT_MASK) ? 1/zoom_inc : zoom_inc);
479 desktop->updateNow();
480 ret = TRUE;
481 } else if (panning == event->button.button) {
482 panning = 0;
483 sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
484 event->button.time);
486 // in slow complex drawings, some of the motion events are lost;
487 // to make up for this, we scroll it once again to the button-up event coordinates
488 // (i.e. canvas will always get scrolled all the way to the mouse release point,
489 // even if few intermediate steps were visible)
490 NR::Point const motion_w(event->button.x, event->button.y);
491 NR::Point const moved_w( motion_w - button_w );
492 event_context->desktop->scroll_world(moved_w);
493 desktop->updateNow();
494 ret = TRUE;
495 } else if (zoom_rb == event->button.button) {
496 zoom_rb = 0;
497 NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
498 Inkscape::Rubberband::get()->stop();
499 if (b && !within_tolerance) {
500 desktop->set_display_area(*b, 10);
501 }
502 ret = TRUE;
503 }
504 break;
505 case GDK_KEY_PRESS:
506 switch (get_group0_keyval(&event->key)) {
507 // GDK insists on stealing these keys (F1 for no idea what, tab for cycling widgets
508 // in the editing window). So we resteal them back and run our regular shortcut
509 // invoker on them.
510 unsigned int shortcut;
511 case GDK_Tab:
512 case GDK_ISO_Left_Tab:
513 case GDK_F1:
514 shortcut = get_group0_keyval(&event->key);
515 if (event->key.state & GDK_SHIFT_MASK)
516 shortcut |= SP_SHORTCUT_SHIFT_MASK;
517 if (event->key.state & GDK_CONTROL_MASK)
518 shortcut |= SP_SHORTCUT_CONTROL_MASK;
519 if (event->key.state & GDK_MOD1_MASK)
520 shortcut |= SP_SHORTCUT_ALT_MASK;
521 ret = sp_shortcut_invoke(shortcut, desktop);
522 break;
524 case GDK_D:
525 case GDK_d:
526 if (!MOD__SHIFT && !MOD__CTRL && !MOD__ALT) {
527 sp_toggle_dropper(desktop);
528 ret = TRUE;
529 }
530 break;
531 case GDK_W:
532 case GDK_w:
533 case GDK_F4:
534 /* Close view */
535 if (MOD__CTRL_ONLY) {
536 sp_ui_close_view(NULL);
537 ret = TRUE;
538 }
539 break;
540 case GDK_Left: // Ctrl Left
541 case GDK_KP_Left:
542 case GDK_KP_4:
543 if (MOD__CTRL_ONLY) {
544 int i = (int) floor(key_scroll * accelerate_scroll(event, acceleration, sp_desktop_canvas(desktop)));
545 gobble_key_events(get_group0_keyval(&event->key),
546 GDK_CONTROL_MASK);
547 event_context->desktop->scroll_world(i, 0);
548 ret = TRUE;
549 }
550 break;
551 case GDK_Up: // Ctrl Up
552 case GDK_KP_Up:
553 case GDK_KP_8:
554 if (MOD__CTRL_ONLY) {
555 int i = (int) floor(key_scroll * accelerate_scroll(event, acceleration, sp_desktop_canvas(desktop)));
556 gobble_key_events(get_group0_keyval(&event->key),
557 GDK_CONTROL_MASK);
558 event_context->desktop->scroll_world(0, i);
559 ret = TRUE;
560 }
561 break;
562 case GDK_Right: // Ctrl Right
563 case GDK_KP_Right:
564 case GDK_KP_6:
565 if (MOD__CTRL_ONLY) {
566 int i = (int) floor(key_scroll * accelerate_scroll(event, acceleration, sp_desktop_canvas(desktop)));
567 gobble_key_events(get_group0_keyval(&event->key),
568 GDK_CONTROL_MASK);
569 event_context->desktop->scroll_world(-i, 0);
570 ret = TRUE;
571 }
572 break;
573 case GDK_Down: // Ctrl Down
574 case GDK_KP_Down:
575 case GDK_KP_2:
576 if (MOD__CTRL_ONLY) {
577 int i = (int) floor(key_scroll * accelerate_scroll(event, acceleration, sp_desktop_canvas(desktop)));
578 gobble_key_events(get_group0_keyval(&event->key),
579 GDK_CONTROL_MASK);
580 event_context->desktop->scroll_world(0, -i);
581 ret = TRUE;
582 }
583 break;
584 case GDK_F10:
585 if (MOD__SHIFT_ONLY) {
586 sp_event_root_menu_popup(desktop, NULL, event);
587 ret= TRUE;
588 }
589 break;
590 case GDK_space:
591 if (prefs_get_int_attribute("options.spacepans","value", 0) == 1) {
592 event_context->space_panning = true;
593 event_context->_message_context->set(Inkscape::INFORMATION_MESSAGE, _("<b>Space+mouse drag</b> to pan canvas"));
594 ret= TRUE;
595 } else {
596 sp_toggle_selector(desktop);
597 ret= TRUE;
598 }
599 break;
600 case GDK_z:
601 case GDK_Z:
602 if (MOD__ALT_ONLY) {
603 desktop->zoom_grab_focus();
604 ret = TRUE;
605 }
606 break;
607 default:
608 break;
609 }
610 break;
611 case GDK_KEY_RELEASE:
612 switch (get_group0_keyval(&event->key)) {
613 case GDK_space:
614 if (event_context->space_panning) {
615 event_context->space_panning = false;
616 event_context->_message_context->clear();
617 if (panning == 1) {
618 panning = 0;
619 sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
620 event->key.time);
621 desktop->updateNow();
622 }
623 ret= TRUE;
624 }
625 break;
626 default:
627 break;
628 }
629 break;
630 case GDK_SCROLL:
631 {
632 bool ctrl = (event->scroll.state & GDK_CONTROL_MASK);
633 bool wheelzooms = (prefs_get_int_attribute("options.wheelzooms","value", 0) == 1);
634 /* shift + wheel, pan left--right */
635 if (event->scroll.state & GDK_SHIFT_MASK) {
636 switch (event->scroll.direction) {
637 case GDK_SCROLL_UP:
638 desktop->scroll_world(wheel_scroll, 0);
639 break;
640 case GDK_SCROLL_DOWN:
641 desktop->scroll_world(-wheel_scroll, 0);
642 break;
643 default:
644 break;
645 }
647 /* ctrl + wheel, zoom in--out */
648 } else if ((ctrl && !wheelzooms) || (!ctrl && wheelzooms)) {
649 double rel_zoom;
650 switch (event->scroll.direction) {
651 case GDK_SCROLL_UP:
652 rel_zoom = zoom_inc;
653 break;
654 case GDK_SCROLL_DOWN:
655 rel_zoom = 1 / zoom_inc;
656 break;
657 default:
658 rel_zoom = 0.0;
659 break;
660 }
661 if (rel_zoom != 0.0) {
662 NR::Point const scroll_dt = desktop->point();
663 desktop->zoom_relative_keep_point(scroll_dt, rel_zoom);
664 }
666 /* no modifier, pan up--down (left--right on multiwheel mice?) */
667 } else {
668 switch (event->scroll.direction) {
669 case GDK_SCROLL_UP:
670 desktop->scroll_world(0, wheel_scroll);
671 break;
672 case GDK_SCROLL_DOWN:
673 desktop->scroll_world(0, -wheel_scroll);
674 break;
675 case GDK_SCROLL_LEFT:
676 desktop->scroll_world(wheel_scroll, 0);
677 break;
678 case GDK_SCROLL_RIGHT:
679 desktop->scroll_world(-wheel_scroll, 0);
680 break;
681 }
682 }
683 break;
684 }
685 default:
686 break;
687 }
689 return ret;
690 }
692 /**
693 * Handles item specific events. Gets called from Gdk.
694 *
695 * Only reacts to right mouse button at the moment.
696 * \todo Fixme: do context sensitive popup menu on items.
697 */
698 gint
699 sp_event_context_private_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event)
700 {
701 int ret = FALSE;
703 switch (event->type) {
704 case GDK_BUTTON_PRESS:
705 if ((event->button.button == 3)
706 && !(event->button.state & GDK_SHIFT_MASK || event->button.state & GDK_CONTROL_MASK)) {
707 sp_event_root_menu_popup(ec->desktop, item, event);
708 ret = TRUE;
709 }
710 break;
711 default:
712 break;
713 }
715 return ret;
716 }
718 /**
719 * Gets called when attribute changes value.
720 */
721 static void
722 sp_ec_repr_attr_changed(Inkscape::XML::Node */*prefs_repr*/, gchar const *key, gchar const */*oldval*/, gchar const *newval,
723 bool /*is_interactive*/, gpointer data)
724 {
725 SPEventContext *ec;
727 ec = SP_EVENT_CONTEXT(data);
729 if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->set) {
730 ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->set(ec, key, newval);
731 }
732 }
734 Inkscape::XML::NodeEventVector sp_ec_event_vector = {
735 NULL, /* Child added */
736 NULL, /* Child removed */
737 sp_ec_repr_attr_changed,
738 NULL, /* Content changed */
739 NULL /* Order changed */
740 };
742 /**
743 * Creates new SPEventContext object and calls its virtual setup() function.
744 */
745 SPEventContext *
746 sp_event_context_new(GType type, SPDesktop *desktop, Inkscape::XML::Node *prefs_repr, unsigned int key)
747 {
748 g_return_val_if_fail(g_type_is_a(type, SP_TYPE_EVENT_CONTEXT), NULL);
749 g_return_val_if_fail(desktop != NULL, NULL);
751 SPEventContext *const ec = (SPEventContext*)g_object_new(type, NULL);
753 ec->desktop = desktop;
754 ec->_message_context = new Inkscape::MessageContext(desktop->messageStack());
755 ec->key = key;
756 ec->prefs_repr = prefs_repr;
757 if (ec->prefs_repr) {
758 Inkscape::GC::anchor(ec->prefs_repr);
759 sp_repr_add_listener(ec->prefs_repr, &sp_ec_event_vector, ec);
760 }
762 if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->setup)
763 ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->setup(ec);
765 return ec;
766 }
768 /**
769 * Finishes SPEventContext.
770 */
771 void
772 sp_event_context_finish(SPEventContext *ec)
773 {
774 g_return_if_fail(ec != NULL);
775 g_return_if_fail(SP_IS_EVENT_CONTEXT(ec));
777 ec->enableSelectionCue(false);
779 if (ec->next) {
780 g_warning("Finishing event context with active link\n");
781 }
783 if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->finish)
784 ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->finish(ec);
785 }
787 //-------------------------------member functions
789 /**
790 * Enables/disables the SPEventContext's SelCue.
791 */
792 void SPEventContext::enableSelectionCue(bool enable) {
793 if (enable) {
794 if (!_selcue) {
795 _selcue = new Inkscape::SelCue(desktop);
796 }
797 } else {
798 delete _selcue;
799 _selcue = NULL;
800 }
801 }
803 /**
804 * Enables/disables the SPEventContext's GrDrag.
805 */
806 void SPEventContext::enableGrDrag(bool enable) {
807 if (enable) {
808 if (!_grdrag) {
809 _grdrag = new GrDrag(desktop);
810 }
811 } else {
812 if (_grdrag) {
813 delete _grdrag;
814 _grdrag = NULL;
815 }
816 }
817 }
819 /**
820 * Calls virtual set() function of SPEventContext.
821 */
822 void
823 sp_event_context_read(SPEventContext *ec, gchar const *key)
824 {
825 g_return_if_fail(ec != NULL);
826 g_return_if_fail(SP_IS_EVENT_CONTEXT(ec));
827 g_return_if_fail(key != NULL);
829 if (ec->prefs_repr) {
830 gchar const *val = ec->prefs_repr->attribute(key);
831 if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->set)
832 ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->set(ec, key, val);
833 }
834 }
836 /**
837 * Calls virtual activate() function of SPEventContext.
838 */
839 void
840 sp_event_context_activate(SPEventContext *ec)
841 {
842 g_return_if_fail(ec != NULL);
843 g_return_if_fail(SP_IS_EVENT_CONTEXT(ec));
845 if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->activate)
846 ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->activate(ec);
847 }
849 /**
850 * Calls virtual deactivate() function of SPEventContext.
851 */
852 void
853 sp_event_context_deactivate(SPEventContext *ec)
854 {
855 g_return_if_fail(ec != NULL);
856 g_return_if_fail(SP_IS_EVENT_CONTEXT(ec));
858 if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->deactivate)
859 ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->deactivate(ec);
860 }
862 /**
863 * Calls virtual root_handler(), the main event handling function.
864 */
865 gint
866 sp_event_context_root_handler(SPEventContext * event_context, GdkEvent * event)
867 {
868 gint ret;
870 ret = ((SPEventContextClass *) G_OBJECT_GET_CLASS(event_context))->root_handler(event_context, event);
872 set_event_location(event_context->desktop, event);
874 return ret;
875 }
877 /**
878 * Calls virtual item_handler(), the item event handling function.
879 */
880 gint
881 sp_event_context_item_handler(SPEventContext * event_context, SPItem * item, GdkEvent * event)
882 {
883 gint ret;
885 ret = ((SPEventContextClass *) G_OBJECT_GET_CLASS(event_context))->item_handler(event_context, item, event);
887 if (! ret) {
888 ret = sp_event_context_root_handler(event_context, event);
889 } else {
890 set_event_location(event_context->desktop, event);
891 }
893 return ret;
894 }
896 /**
897 * Emits 'position_set' signal on desktop and shows coordinates on status bar.
898 */
899 static void set_event_location(SPDesktop *desktop, GdkEvent *event)
900 {
901 if (event->type != GDK_MOTION_NOTIFY) {
902 return;
903 }
905 NR::Point const button_w(event->button.x, event->button.y);
906 NR::Point const button_dt(desktop->w2d(button_w));
907 desktop-> setPosition (button_dt);
908 desktop->set_coordinate_status(button_dt);
909 }
911 //-------------------------------------------------------------------
912 /**
913 * Create popup menu and tell Gtk to show it.
914 */
915 void
916 sp_event_root_menu_popup(SPDesktop *desktop, SPItem *item, GdkEvent *event)
917 {
918 GtkWidget *menu;
920 /* fixme: This is not what I want but works for now (Lauris) */
921 if (event->type == GDK_KEY_PRESS) {
922 item = sp_desktop_selection(desktop)->singleItem();
923 }
924 menu = sp_ui_context_menu(desktop, item);
925 gtk_widget_show(menu);
927 switch (event->type) {
928 case GDK_BUTTON_PRESS:
929 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, 0, NULL, event->button.button, event->button.time);
930 break;
931 case GDK_KEY_PRESS:
932 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, 0, NULL, 0, event->key.time);
933 break;
934 default:
935 break;
936 }
937 }
939 /**
940 * Show tool context specific modifier tip.
941 */
942 void
943 sp_event_show_modifier_tip(Inkscape::MessageContext *message_context,
944 GdkEvent *event, gchar const *ctrl_tip, gchar const *shift_tip,
945 gchar const *alt_tip)
946 {
947 guint keyval = get_group0_keyval(&event->key);
949 bool ctrl = ctrl_tip && (MOD__CTRL
950 || (keyval == GDK_Control_L)
951 || (keyval == GDK_Control_R));
952 bool shift = shift_tip
953 && (MOD__SHIFT || (keyval == GDK_Shift_L) || (keyval == GDK_Shift_R));
954 bool alt = alt_tip
955 && (MOD__ALT
956 || (keyval == GDK_Alt_L)
957 || (keyval == GDK_Alt_R)
958 || (keyval == GDK_Meta_L)
959 || (keyval == GDK_Meta_R));
961 gchar *tip = g_strdup_printf("%s%s%s%s%s",
962 ( ctrl ? ctrl_tip : "" ),
963 ( ctrl && (shift || alt) ? "; " : "" ),
964 ( shift ? shift_tip : "" ),
965 ( (ctrl || shift) && alt ? "; " : "" ),
966 ( alt ? alt_tip : "" ));
968 if (strlen(tip) > 0) {
969 message_context->flash(Inkscape::INFORMATION_MESSAGE, tip);
970 }
972 g_free(tip);
973 }
975 /**
976 * Return the keyval corresponding to the key event in group 0, i.e.,
977 * in the main (English) layout.
978 *
979 * Use this instead of simply event->keyval, so that your keyboard shortcuts
980 * work regardless of layouts (e.g., in Cyrillic).
981 */
982 guint
983 get_group0_keyval(GdkEventKey *event)
984 {
985 guint keyval = 0;
986 gdk_keymap_translate_keyboard_state(
987 gdk_keymap_get_for_display(gdk_display_get_default()),
988 event->hardware_keycode,
989 (GdkModifierType) event->state,
990 0 /*event->key.group*/,
991 &keyval, NULL, NULL, NULL);
992 return keyval;
993 }
995 /**
996 * Returns item at point p in desktop.
997 *
998 * If state includes alt key mask, cyclically selects under; honors
999 * into_groups.
1000 */
1001 SPItem *
1002 sp_event_context_find_item (SPDesktop *desktop, NR::Point const p,
1003 bool select_under, bool into_groups)
1004 {
1005 SPItem *item;
1007 if (select_under) {
1008 SPItem *selected_at_point =
1009 desktop->item_from_list_at_point_bottom (desktop->selection->itemList(), p);
1010 item = desktop->item_at_point(p, into_groups, selected_at_point);
1011 if (item == NULL) { // we may have reached bottom, flip over to the top
1012 item = desktop->item_at_point(p, into_groups, NULL);
1013 }
1014 } else
1015 item = desktop->item_at_point(p, into_groups, NULL);
1017 return item;
1018 }
1020 /**
1021 * Returns item if it is under point p in desktop, at any depth; otherwise returns NULL.
1022 *
1023 * Honors into_groups.
1024 */
1025 SPItem *
1026 sp_event_context_over_item (SPDesktop *desktop, SPItem *item, NR::Point const p)
1027 {
1028 GSList *temp = NULL;
1029 temp = g_slist_prepend (temp, item);
1030 SPItem *item_at_point = desktop->item_from_list_at_point_bottom (temp, p);
1031 g_slist_free (temp);
1033 return item_at_point;
1034 }
1036 /**
1037 * Called when SPEventContext subclass node attribute changed.
1038 */
1039 void
1040 ec_shape_event_attr_changed(Inkscape::XML::Node */*shape_repr*/, gchar const *name,
1041 gchar const */*old_value*/, gchar const */*new_value*/,
1042 bool const /*is_interactive*/, gpointer const data)
1043 {
1044 if (!name
1045 || !strcmp(name, "style")
1046 || SP_ATTRIBUTE_IS_CSS(sp_attribute_lookup(name))) {
1047 // no need to regenrate knotholder if only style changed
1048 return;
1049 }
1051 SPEventContext *ec = SP_EVENT_CONTEXT(data);
1053 if (ec->shape_knot_holder) {
1054 sp_knot_holder_destroy(ec->shape_knot_holder);
1055 }
1056 ec->shape_knot_holder = NULL;
1058 SPDesktop *desktop = ec->desktop;
1060 SPItem *item = sp_desktop_selection(desktop)->singleItem();
1062 if (item) {
1063 ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
1064 }
1065 }
1068 /*
1069 Local Variables:
1070 mode:c++
1071 c-file-style:"stroustrup"
1072 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1073 indent-tabs-mode:nil
1074 fill-column:99
1075 End:
1076 */
1077 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :