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 || event_next->type == GDK_KEY_RELEASE)
219 && event_next->key.keyval == keyval
220 && (!mask || (event_next->key.state & mask))) {
221 if (event_next->type == GDK_KEY_PRESS)
222 i ++;
223 // kill it
224 gdk_event_free(event_next);
225 // get next
226 event_next = gdk_event_get();
227 }
228 // otherwise, put it back onto the queue
229 if (event_next) gdk_event_put(event_next);
231 return i;
232 }
234 /**
235 * \brief Gobbles next motion notify events on the queue with the same mask. Returns the number of events consumed.
236 */
237 gint gobble_motion_events(gint mask)
238 {
239 GdkEvent *event_next;
240 gint i = 0;
242 event_next = gdk_event_get();
243 // while the next event is also a key notify with the same keyval and mask,
244 while (event_next && event_next->type == GDK_MOTION_NOTIFY
245 && (event_next->motion.state & mask)) {
246 // kill it
247 gdk_event_free(event_next);
248 // get next
249 event_next = gdk_event_get();
250 i ++;
251 }
252 // otherwise, put it back onto the queue
253 if (event_next) gdk_event_put(event_next);
255 return i;
256 }
258 /**
259 * Toggles current tool between active tool and selector tool.
260 * Subroutine of sp_event_context_private_root_handler().
261 */
262 static void
263 sp_toggle_selector(SPDesktop *dt)
264 {
265 if (!dt->event_context) return;
267 if (tools_isactive(dt, TOOLS_SELECT)) {
268 if (selector_toggled) {
269 if (switch_selector_to) tools_switch (dt, switch_selector_to);
270 selector_toggled = FALSE;
271 } else return;
272 } else {
273 selector_toggled = TRUE;
274 switch_selector_to = tools_active(dt);
275 tools_switch (dt, TOOLS_SELECT);
276 }
277 }
279 /**
280 * Calculates and keeps track of scroll acceleration.
281 * Subroutine of sp_event_context_private_root_handler().
282 */
283 static gdouble accelerate_scroll(GdkEvent *event, gdouble acceleration, SPCanvas */*canvas*/)
284 {
285 guint32 time_diff = ((GdkEventKey *) event)->time - scroll_event_time;
287 /* key pressed within 500ms ? (1/2 second) */
288 if (time_diff > 500 || event->key.keyval != scroll_keyval) {
289 scroll_multiply = 1; // abort acceleration
290 } else {
291 scroll_multiply += acceleration; // continue acceleration
292 }
294 scroll_event_time = ((GdkEventKey *) event)->time;
295 scroll_keyval = event->key.keyval;
297 return scroll_multiply;
298 }
300 /**
301 * Main event dispatch, gets called from Gdk.
302 */
303 static gint sp_event_context_private_root_handler(SPEventContext *event_context, GdkEvent *event)
304 {
305 static NR::Point button_w;
306 static unsigned int panning = 0;
307 static unsigned int zoom_rb = 0;
309 SPDesktop *desktop = event_context->desktop;
311 tolerance = prefs_get_int_attribute_limited(
312 "options.dragtolerance","value", 0, 0, 100);
313 double const zoom_inc = prefs_get_double_attribute_limited(
314 "options.zoomincrement", "value", M_SQRT2, 1.01, 10);
315 double const acceleration = prefs_get_double_attribute_limited(
316 "options.scrollingacceleration", "value", 0, 0, 6);
317 int const key_scroll = prefs_get_int_attribute_limited(
318 "options.keyscroll", "value", 10, 0, 1000);
319 int const wheel_scroll = prefs_get_int_attribute_limited(
320 "options.wheelscroll", "value", 40, 0, 1000);
322 gint ret = FALSE;
324 switch (event->type) {
325 case GDK_2BUTTON_PRESS:
326 if (panning) {
327 panning = 0;
328 sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
329 event->button.time);
330 ret = TRUE;
331 } else {
332 /* sp_desktop_dialog(); */
333 }
334 break;
335 case GDK_BUTTON_PRESS:
337 // save drag origin
338 xp = (gint) event->button.x;
339 yp = (gint) event->button.y;
340 within_tolerance = true;
342 button_w = NR::Point(event->button.x, event->button.y);
344 switch (event->button.button) {
345 case 1:
346 if (event_context->space_panning) {
347 panning = 1;
348 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
349 GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
350 NULL, event->button.time-1);
351 ret = TRUE;
352 }
353 break;
354 case 2:
355 if (event->button.state == GDK_SHIFT_MASK) {
356 zoom_rb = 2;
357 } else {
358 panning = 2;
359 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
360 GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
361 NULL, event->button.time-1);
362 }
363 ret = TRUE;
364 break;
365 case 3:
366 if (event->button.state & GDK_SHIFT_MASK
367 || event->button.state & GDK_CONTROL_MASK) {
368 panning = 3;
369 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
370 GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
371 NULL, event->button.time);
372 ret = TRUE;
373 } else {
374 sp_event_root_menu_popup(desktop, NULL, event);
375 }
376 break;
377 default:
378 break;
379 }
380 break;
381 case GDK_MOTION_NOTIFY:
382 if (panning) {
383 if ((panning == 2 && !(event->motion.state & GDK_BUTTON2_MASK))
384 || (panning == 1 && !(event->motion.state & GDK_BUTTON1_MASK))
385 || (panning == 3 && !(event->motion.state & GDK_BUTTON3_MASK))
386 ) {
387 /* Gdk seems to lose button release for us sometimes :-( */
388 panning = 0;
389 sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
390 event->button.time);
391 ret = TRUE;
392 } else {
393 if ( within_tolerance
394 && ( abs( (gint) event->motion.x - xp ) < tolerance )
395 && ( abs( (gint) event->motion.y - yp ) < tolerance ))
396 {
397 // do not drag if we're within tolerance from origin
398 break;
399 }
400 // Once the user has moved farther than tolerance from
401 // the original location (indicating they intend to move
402 // the object, not click), then always process the motion
403 // notify coordinates as given (no snapping back to origin)
404 within_tolerance = false;
406 // gobble subsequent motion events to prevent "sticking"
407 // when scrolling is slow
408 gobble_motion_events(panning == 2 ?
409 GDK_BUTTON2_MASK :
410 (panning == 1 ? GDK_BUTTON1_MASK : GDK_BUTTON3_MASK));
412 NR::Point const motion_w(event->motion.x, event->motion.y);
413 NR::Point const moved_w( motion_w - button_w );
414 event_context->desktop->scroll_world(moved_w, true); // we're still scrolling, do not redraw
415 ret = TRUE;
416 }
417 } else if (zoom_rb) {
418 NR::Point const motion_w(event->motion.x, event->motion.y);
419 NR::Point const motion_dt(desktop->w2d(motion_w));
421 if ( within_tolerance
422 && ( abs( (gint) event->motion.x - xp ) < tolerance )
423 && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) {
424 break; // do not drag if we're within tolerance from origin
425 }
426 // Once the user has moved farther than tolerance from the original location
427 // (indicating they intend to move the object, not click), then always process the
428 // motion notify coordinates as given (no snapping back to origin)
429 within_tolerance = false;
431 if (Inkscape::Rubberband::get()->is_started()) {
432 Inkscape::Rubberband::get()->move(motion_dt);
433 } else {
434 Inkscape::Rubberband::get()->start(desktop, motion_dt);
435 }
436 if (zoom_rb == 2)
437 gobble_motion_events(GDK_BUTTON2_MASK);
438 }
439 break;
440 case GDK_BUTTON_RELEASE:
441 xp = yp = 0;
442 if (within_tolerance && (panning || zoom_rb)) {
443 zoom_rb = 0;
444 if (panning) {
445 panning = 0;
446 sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
447 event->button.time);
448 }
449 NR::Point const event_w(event->button.x, event->button.y);
450 NR::Point const event_dt(desktop->w2d(event_w));
451 desktop->zoom_relative_keep_point(event_dt,
452 (event->button.state & GDK_SHIFT_MASK) ? 1/zoom_inc : zoom_inc);
453 desktop->updateNow();
454 ret = TRUE;
455 } else if (panning == event->button.button) {
456 panning = 0;
457 sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
458 event->button.time);
460 // in slow complex drawings, some of the motion events are lost;
461 // to make up for this, we scroll it once again to the button-up event coordinates
462 // (i.e. canvas will always get scrolled all the way to the mouse release point,
463 // even if few intermediate steps were visible)
464 NR::Point const motion_w(event->button.x, event->button.y);
465 NR::Point const moved_w( motion_w - button_w );
466 event_context->desktop->scroll_world(moved_w);
467 desktop->updateNow();
468 ret = TRUE;
469 } else if (zoom_rb == event->button.button) {
470 zoom_rb = 0;
471 NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
472 Inkscape::Rubberband::get()->stop();
473 if (b && !within_tolerance) {
474 desktop->set_display_area(*b, 10);
475 }
476 ret = TRUE;
477 }
478 break;
479 case GDK_KEY_PRESS:
480 switch (get_group0_keyval(&event->key)) {
481 // GDK insists on stealing these keys (F1 for no idea what, tab for cycling widgets
482 // in the editing window). So we resteal them back and run our regular shortcut
483 // invoker on them.
484 unsigned int shortcut;
485 case GDK_Tab:
486 case GDK_ISO_Left_Tab:
487 case GDK_F1:
488 shortcut = get_group0_keyval(&event->key);
489 if (event->key.state & GDK_SHIFT_MASK)
490 shortcut |= SP_SHORTCUT_SHIFT_MASK;
491 if (event->key.state & GDK_CONTROL_MASK)
492 shortcut |= SP_SHORTCUT_CONTROL_MASK;
493 if (event->key.state & GDK_MOD1_MASK)
494 shortcut |= SP_SHORTCUT_ALT_MASK;
495 ret = sp_shortcut_invoke(shortcut, desktop);
496 break;
498 case GDK_W:
499 case GDK_w:
500 case GDK_F4:
501 /* Close view */
502 if (MOD__CTRL_ONLY) {
503 sp_ui_close_view(NULL);
504 ret = TRUE;
505 }
506 break;
507 case GDK_Left: // Ctrl Left
508 case GDK_KP_Left:
509 case GDK_KP_4:
510 if (MOD__CTRL_ONLY) {
511 int i = (int) floor(key_scroll * accelerate_scroll(event, acceleration, sp_desktop_canvas(desktop)));
512 gobble_key_events(get_group0_keyval(&event->key),
513 GDK_CONTROL_MASK);
514 event_context->desktop->scroll_world(i, 0);
515 ret = TRUE;
516 }
517 break;
518 case GDK_Up: // Ctrl Up
519 case GDK_KP_Up:
520 case GDK_KP_8:
521 if (MOD__CTRL_ONLY) {
522 int i = (int) floor(key_scroll * accelerate_scroll(event, acceleration, sp_desktop_canvas(desktop)));
523 gobble_key_events(get_group0_keyval(&event->key),
524 GDK_CONTROL_MASK);
525 event_context->desktop->scroll_world(0, i);
526 ret = TRUE;
527 }
528 break;
529 case GDK_Right: // Ctrl Right
530 case GDK_KP_Right:
531 case GDK_KP_6:
532 if (MOD__CTRL_ONLY) {
533 int i = (int) floor(key_scroll * accelerate_scroll(event, acceleration, sp_desktop_canvas(desktop)));
534 gobble_key_events(get_group0_keyval(&event->key),
535 GDK_CONTROL_MASK);
536 event_context->desktop->scroll_world(-i, 0);
537 ret = TRUE;
538 }
539 break;
540 case GDK_Down: // Ctrl Down
541 case GDK_KP_Down:
542 case GDK_KP_2:
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(0, -i);
548 ret = TRUE;
549 }
550 break;
551 case GDK_F10:
552 if (MOD__SHIFT_ONLY) {
553 sp_event_root_menu_popup(desktop, NULL, event);
554 ret= TRUE;
555 }
556 break;
557 case GDK_space:
558 if (prefs_get_int_attribute("options.spacepans","value", 0) == 1) {
559 event_context->space_panning = true;
560 event_context->_message_context->set(Inkscape::INFORMATION_MESSAGE, _("<b>Space+mouse drag</b> to pan canvas"));
561 ret= TRUE;
562 } else {
563 sp_toggle_selector(desktop);
564 ret= TRUE;
565 }
566 break;
567 case GDK_z:
568 case GDK_Z:
569 if (MOD__ALT_ONLY) {
570 desktop->zoom_grab_focus();
571 ret = TRUE;
572 }
573 break;
574 default:
575 break;
576 }
577 break;
578 case GDK_KEY_RELEASE:
579 switch (get_group0_keyval(&event->key)) {
580 case GDK_space:
581 if (event_context->space_panning) {
582 event_context->space_panning = false;
583 event_context->_message_context->clear();
584 if (panning == 1) {
585 panning = 0;
586 sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
587 event->key.time);
588 desktop->updateNow();
589 }
590 ret= TRUE;
591 }
592 break;
593 default:
594 break;
595 }
596 break;
597 case GDK_SCROLL:
598 {
599 bool ctrl = (event->scroll.state & GDK_CONTROL_MASK);
600 bool wheelzooms = (prefs_get_int_attribute("options.wheelzooms","value", 0) == 1);
601 /* shift + wheel, pan left--right */
602 if (event->scroll.state & GDK_SHIFT_MASK) {
603 switch (event->scroll.direction) {
604 case GDK_SCROLL_UP:
605 desktop->scroll_world(wheel_scroll, 0);
606 break;
607 case GDK_SCROLL_DOWN:
608 desktop->scroll_world(-wheel_scroll, 0);
609 break;
610 default:
611 break;
612 }
614 /* ctrl + wheel, zoom in--out */
615 } else if ((ctrl && !wheelzooms) || (!ctrl && wheelzooms)) {
616 double rel_zoom;
617 switch (event->scroll.direction) {
618 case GDK_SCROLL_UP:
619 rel_zoom = zoom_inc;
620 break;
621 case GDK_SCROLL_DOWN:
622 rel_zoom = 1 / zoom_inc;
623 break;
624 default:
625 rel_zoom = 0.0;
626 break;
627 }
628 if (rel_zoom != 0.0) {
629 NR::Point const scroll_dt = desktop->point();
630 desktop->zoom_relative_keep_point(scroll_dt, rel_zoom);
631 }
633 /* no modifier, pan up--down (left--right on multiwheel mice?) */
634 } else {
635 switch (event->scroll.direction) {
636 case GDK_SCROLL_UP:
637 desktop->scroll_world(0, wheel_scroll);
638 break;
639 case GDK_SCROLL_DOWN:
640 desktop->scroll_world(0, -wheel_scroll);
641 break;
642 case GDK_SCROLL_LEFT:
643 desktop->scroll_world(wheel_scroll, 0);
644 break;
645 case GDK_SCROLL_RIGHT:
646 desktop->scroll_world(-wheel_scroll, 0);
647 break;
648 }
649 }
650 break;
651 }
652 default:
653 break;
654 }
656 return ret;
657 }
659 /**
660 * Handles item specific events. Gets called from Gdk.
661 *
662 * Only reacts to right mouse button at the moment.
663 * \todo Fixme: do context sensitive popup menu on items.
664 */
665 gint
666 sp_event_context_private_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event)
667 {
668 int ret = FALSE;
670 switch (event->type) {
671 case GDK_BUTTON_PRESS:
672 if ((event->button.button == 3)
673 && !(event->button.state & GDK_SHIFT_MASK || event->button.state & GDK_CONTROL_MASK)) {
674 sp_event_root_menu_popup(ec->desktop, item, event);
675 ret = TRUE;
676 }
677 break;
678 default:
679 break;
680 }
682 return ret;
683 }
685 /**
686 * Gets called when attribute changes value.
687 */
688 static void
689 sp_ec_repr_attr_changed(Inkscape::XML::Node */*prefs_repr*/, gchar const *key, gchar const */*oldval*/, gchar const *newval,
690 bool /*is_interactive*/, gpointer data)
691 {
692 SPEventContext *ec;
694 ec = SP_EVENT_CONTEXT(data);
696 if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->set) {
697 ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->set(ec, key, newval);
698 }
699 }
701 Inkscape::XML::NodeEventVector sp_ec_event_vector = {
702 NULL, /* Child added */
703 NULL, /* Child removed */
704 sp_ec_repr_attr_changed,
705 NULL, /* Content changed */
706 NULL /* Order changed */
707 };
709 /**
710 * Creates new SPEventContext object and calls its virtual setup() function.
711 */
712 SPEventContext *
713 sp_event_context_new(GType type, SPDesktop *desktop, Inkscape::XML::Node *prefs_repr, unsigned int key)
714 {
715 g_return_val_if_fail(g_type_is_a(type, SP_TYPE_EVENT_CONTEXT), NULL);
716 g_return_val_if_fail(desktop != NULL, NULL);
718 SPEventContext *const ec = (SPEventContext*)g_object_new(type, NULL);
720 ec->desktop = desktop;
721 ec->_message_context = new Inkscape::MessageContext(desktop->messageStack());
722 ec->key = key;
723 ec->prefs_repr = prefs_repr;
724 if (ec->prefs_repr) {
725 Inkscape::GC::anchor(ec->prefs_repr);
726 sp_repr_add_listener(ec->prefs_repr, &sp_ec_event_vector, ec);
727 }
729 if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->setup)
730 ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->setup(ec);
732 return ec;
733 }
735 /**
736 * Finishes SPEventContext.
737 */
738 void
739 sp_event_context_finish(SPEventContext *ec)
740 {
741 g_return_if_fail(ec != NULL);
742 g_return_if_fail(SP_IS_EVENT_CONTEXT(ec));
744 ec->enableSelectionCue(false);
746 if (ec->next) {
747 g_warning("Finishing event context with active link\n");
748 }
750 if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->finish)
751 ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->finish(ec);
752 }
754 //-------------------------------member functions
756 /**
757 * Enables/disables the SPEventContext's SelCue.
758 */
759 void SPEventContext::enableSelectionCue(bool enable) {
760 if (enable) {
761 if (!_selcue) {
762 _selcue = new Inkscape::SelCue(desktop);
763 }
764 } else {
765 delete _selcue;
766 _selcue = NULL;
767 }
768 }
770 /**
771 * Enables/disables the SPEventContext's GrDrag.
772 */
773 void SPEventContext::enableGrDrag(bool enable) {
774 if (enable) {
775 if (!_grdrag) {
776 _grdrag = new GrDrag(desktop);
777 }
778 } else {
779 if (_grdrag) {
780 delete _grdrag;
781 _grdrag = NULL;
782 }
783 }
784 }
786 /**
787 * Calls virtual set() function of SPEventContext.
788 */
789 void
790 sp_event_context_read(SPEventContext *ec, gchar const *key)
791 {
792 g_return_if_fail(ec != NULL);
793 g_return_if_fail(SP_IS_EVENT_CONTEXT(ec));
794 g_return_if_fail(key != NULL);
796 if (ec->prefs_repr) {
797 gchar const *val = ec->prefs_repr->attribute(key);
798 if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->set)
799 ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->set(ec, key, val);
800 }
801 }
803 /**
804 * Calls virtual activate() function of SPEventContext.
805 */
806 void
807 sp_event_context_activate(SPEventContext *ec)
808 {
809 g_return_if_fail(ec != NULL);
810 g_return_if_fail(SP_IS_EVENT_CONTEXT(ec));
812 if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->activate)
813 ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->activate(ec);
814 }
816 /**
817 * Calls virtual deactivate() function of SPEventContext.
818 */
819 void
820 sp_event_context_deactivate(SPEventContext *ec)
821 {
822 g_return_if_fail(ec != NULL);
823 g_return_if_fail(SP_IS_EVENT_CONTEXT(ec));
825 if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->deactivate)
826 ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->deactivate(ec);
827 }
829 /**
830 * Calls virtual root_handler(), the main event handling function.
831 */
832 gint
833 sp_event_context_root_handler(SPEventContext * event_context, GdkEvent * event)
834 {
835 gint ret;
837 ret = ((SPEventContextClass *) G_OBJECT_GET_CLASS(event_context))->root_handler(event_context, event);
839 set_event_location(event_context->desktop, event);
841 return ret;
842 }
844 /**
845 * Calls virtual item_handler(), the item event handling function.
846 */
847 gint
848 sp_event_context_item_handler(SPEventContext * event_context, SPItem * item, GdkEvent * event)
849 {
850 gint ret;
852 ret = ((SPEventContextClass *) G_OBJECT_GET_CLASS(event_context))->item_handler(event_context, item, event);
854 if (! ret) {
855 ret = sp_event_context_root_handler(event_context, event);
856 } else {
857 set_event_location(event_context->desktop, event);
858 }
860 return ret;
861 }
863 /**
864 * Emits 'position_set' signal on desktop and shows coordinates on status bar.
865 */
866 static void set_event_location(SPDesktop *desktop, GdkEvent *event)
867 {
868 if (event->type != GDK_MOTION_NOTIFY) {
869 return;
870 }
872 NR::Point const button_w(event->button.x, event->button.y);
873 NR::Point const button_dt(desktop->w2d(button_w));
874 desktop-> setPosition (button_dt);
875 desktop->set_coordinate_status(button_dt);
876 }
878 //-------------------------------------------------------------------
879 /**
880 * Create popup menu and tell Gtk to show it.
881 */
882 void
883 sp_event_root_menu_popup(SPDesktop *desktop, SPItem *item, GdkEvent *event)
884 {
885 GtkWidget *menu;
887 /* fixme: This is not what I want but works for now (Lauris) */
888 if (event->type == GDK_KEY_PRESS) {
889 item = sp_desktop_selection(desktop)->singleItem();
890 }
891 menu = sp_ui_context_menu(desktop, item);
892 gtk_widget_show(menu);
894 switch (event->type) {
895 case GDK_BUTTON_PRESS:
896 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, 0, NULL, event->button.button, event->button.time);
897 break;
898 case GDK_KEY_PRESS:
899 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, 0, NULL, 0, event->key.time);
900 break;
901 default:
902 break;
903 }
904 }
906 /**
907 * Show tool context specific modifier tip.
908 */
909 void
910 sp_event_show_modifier_tip(Inkscape::MessageContext *message_context,
911 GdkEvent *event, gchar const *ctrl_tip, gchar const *shift_tip,
912 gchar const *alt_tip)
913 {
914 guint keyval = get_group0_keyval(&event->key);
916 bool ctrl = ctrl_tip && (MOD__CTRL
917 || (keyval == GDK_Control_L)
918 || (keyval == GDK_Control_R));
919 bool shift = shift_tip
920 && (MOD__SHIFT || (keyval == GDK_Shift_L) || (keyval == GDK_Shift_R));
921 bool alt = alt_tip
922 && (MOD__ALT
923 || (keyval == GDK_Alt_L)
924 || (keyval == GDK_Alt_R)
925 || (keyval == GDK_Meta_L)
926 || (keyval == GDK_Meta_R));
928 gchar *tip = g_strdup_printf("%s%s%s%s%s",
929 ( ctrl ? ctrl_tip : "" ),
930 ( ctrl && (shift || alt) ? "; " : "" ),
931 ( shift ? shift_tip : "" ),
932 ( (ctrl || shift) && alt ? "; " : "" ),
933 ( alt ? alt_tip : "" ));
935 if (strlen(tip) > 0) {
936 message_context->flash(Inkscape::INFORMATION_MESSAGE, tip);
937 }
939 g_free(tip);
940 }
942 /**
943 * Return the keyval corresponding to the key event in group 0, i.e.,
944 * in the main (English) layout.
945 *
946 * Use this instead of simply event->keyval, so that your keyboard shortcuts
947 * work regardless of layouts (e.g., in Cyrillic).
948 */
949 guint
950 get_group0_keyval(GdkEventKey *event)
951 {
952 guint keyval = 0;
953 gdk_keymap_translate_keyboard_state(
954 gdk_keymap_get_for_display(gdk_display_get_default()),
955 event->hardware_keycode,
956 (GdkModifierType) event->state,
957 0 /*event->key.group*/,
958 &keyval, NULL, NULL, NULL);
959 return keyval;
960 }
962 /**
963 * Returns item at point p in desktop.
964 *
965 * If state includes alt key mask, cyclically selects under; honors
966 * into_groups.
967 */
968 SPItem *
969 sp_event_context_find_item (SPDesktop *desktop, NR::Point const p,
970 bool select_under, bool into_groups)
971 {
972 SPItem *item;
974 if (select_under) {
975 SPItem *selected_at_point =
976 desktop->item_from_list_at_point_bottom (desktop->selection->itemList(), p);
977 item = desktop->item_at_point(p, into_groups, selected_at_point);
978 if (item == NULL) { // we may have reached bottom, flip over to the top
979 item = desktop->item_at_point(p, into_groups, NULL);
980 }
981 } else
982 item = desktop->item_at_point(p, into_groups, NULL);
984 return item;
985 }
987 /**
988 * Returns item if it is under point p in desktop, at any depth; otherwise returns NULL.
989 *
990 * Honors into_groups.
991 */
992 SPItem *
993 sp_event_context_over_item (SPDesktop *desktop, SPItem *item, NR::Point const p)
994 {
995 GSList *temp = NULL;
996 temp = g_slist_prepend (temp, item);
997 SPItem *item_at_point = desktop->item_from_list_at_point_bottom (temp, p);
998 g_slist_free (temp);
1000 return item_at_point;
1001 }
1003 /**
1004 * Called when SPEventContext subclass node attribute changed.
1005 */
1006 void
1007 ec_shape_event_attr_changed(Inkscape::XML::Node */*shape_repr*/, gchar const *name,
1008 gchar const */*old_value*/, gchar const */*new_value*/,
1009 bool const /*is_interactive*/, gpointer const data)
1010 {
1011 if (!name
1012 || !strcmp(name, "style")
1013 || SP_ATTRIBUTE_IS_CSS(sp_attribute_lookup(name))) {
1014 // no need to regenrate knotholder if only style changed
1015 return;
1016 }
1018 SPEventContext *ec = SP_EVENT_CONTEXT(data);
1020 if (ec->shape_knot_holder) {
1021 sp_knot_holder_destroy(ec->shape_knot_holder);
1022 }
1023 ec->shape_knot_holder = NULL;
1025 SPDesktop *desktop = ec->desktop;
1027 SPItem *item = sp_desktop_selection(desktop)->singleItem();
1029 if (item) {
1030 ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
1031 }
1032 }
1035 /*
1036 Local Variables:
1037 mode:c++
1038 c-file-style:"stroustrup"
1039 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1040 indent-tabs-mode:nil
1041 fill-column:99
1042 End:
1043 */
1044 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :