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