Code

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