Code

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