Code

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