summaryrefslogtreecommitdiffstats
path: root/src/event-context.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/event-context.cpp')
-rw-r--r--src/event-context.cpp1033
1 files changed, 1033 insertions, 0 deletions
diff --git a/src/event-context.cpp b/src/event-context.cpp
new file mode 100644
index 000000000..d67572e9c
--- /dev/null
+++ b/src/event-context.cpp
@@ -0,0 +1,1033 @@
+#define __SP_EVENT_CONTEXT_C__
+
+/** \file
+ * Main event handling, and related helper functions.
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Frank Felfe <innerspace@iname.com>
+ * bulia byak <buliabyak@users.sf.net>
+ *
+ * Copyright (C) 1999-2005 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+/** \class SPEventContext
+ * SPEventContext is an abstract base class of all tools. As the name
+ * indicates, event context implementations process UI events (mouse
+ * movements and keypresses) and take actions (like creating or modifying
+ * objects). There is one event context implementation for each tool,
+ * plus few abstract base classes. Writing a new tool involves
+ * subclassing SPEventContext.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtkmenu.h>
+
+#include "display/sp-canvas.h"
+#include "xml/node-event-vector.h"
+#include "sp-cursor.h"
+#include "shortcuts.h"
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "selection.h"
+#include "file.h"
+#include "interface.h"
+#include "macros.h"
+#include "tools-switch.h"
+#include "prefs-utils.h"
+#include "message-context.h"
+#include "gradient-drag.h"
+#include "object-edit.h"
+#include "attributes.h"
+#include "rubberband.h"
+#include "selcue.h"
+
+#include "event-context.h"
+
+static void sp_event_context_class_init(SPEventContextClass *klass);
+static void sp_event_context_init(SPEventContext *event_context);
+static void sp_event_context_dispose(GObject *object);
+
+static void sp_event_context_private_setup(SPEventContext *ec);
+static gint sp_event_context_private_root_handler(SPEventContext *event_context, GdkEvent *event);
+static gint sp_event_context_private_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
+
+static void set_event_location(SPDesktop * desktop, GdkEvent * event);
+
+static GObjectClass *parent_class;
+
+// globals for temporary switching to selector by space
+static bool selector_toggled = FALSE;
+static int switch_selector_to = 0;
+
+static gint xp = 0, yp = 0; // where drag started
+static gint tolerance = 0;
+static bool within_tolerance = false;
+
+// globals for keeping track of keyboard scroll events in order to accelerate
+static guint32 scroll_event_time = 0;
+static gdouble scroll_multiply = 1;
+static guint scroll_keyval = 0;
+
+/**
+ * Registers the SPEventContext class with Glib and returns its type number.
+ */
+GType
+sp_event_context_get_type(void)
+{
+ static GType type = 0;
+ if (!type) {
+ GTypeInfo info = {
+ sizeof(SPEventContextClass),
+ NULL, NULL,
+ (GClassInitFunc) sp_event_context_class_init,
+ NULL, NULL,
+ sizeof(SPEventContext),
+ 4,
+ (GInstanceInitFunc) sp_event_context_init,
+ NULL, /* value_table */
+ };
+ type = g_type_register_static(G_TYPE_OBJECT, "SPEventContext", &info, (GTypeFlags)0);
+ }
+ return type;
+}
+
+/**
+ * Callback to set up the SPEventContext vtable.
+ */
+static void
+sp_event_context_class_init(SPEventContextClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = (GObjectClass *) klass;
+
+ parent_class = (GObjectClass*)g_type_class_peek_parent(klass);
+
+ object_class->dispose = sp_event_context_dispose;
+
+ klass->setup = sp_event_context_private_setup;
+ klass->root_handler = sp_event_context_private_root_handler;
+ klass->item_handler = sp_event_context_private_item_handler;
+}
+
+/**
+ * Clears all SPEventContext object members.
+ */
+static void
+sp_event_context_init(SPEventContext *event_context)
+{
+ event_context->desktop = NULL;
+ event_context->cursor = NULL;
+ event_context->_message_context = NULL;
+ event_context->_selcue = NULL;
+ event_context->_grdrag = NULL;
+}
+
+/**
+ * Callback to free and null member variables of SPEventContext object.
+ */
+static void
+sp_event_context_dispose(GObject *object)
+{
+ SPEventContext *ec;
+
+ ec = SP_EVENT_CONTEXT(object);
+
+ if (ec->_message_context) {
+ delete ec->_message_context;
+ }
+
+ if (ec->cursor != NULL) {
+ gdk_cursor_unref(ec->cursor);
+ ec->cursor = NULL;
+ }
+
+ if (ec->desktop) {
+ ec->desktop = NULL;
+ }
+
+ if (ec->prefs_repr) {
+ sp_repr_remove_listener_by_data(ec->prefs_repr, ec);
+ Inkscape::GC::release(ec->prefs_repr);
+ ec->prefs_repr = NULL;
+ }
+
+ G_OBJECT_CLASS(parent_class)->dispose(object);
+}
+
+/**
+ * Recreates and draws cursor on desktop related to SPEventContext.
+ */
+void
+sp_event_context_update_cursor(SPEventContext *ec)
+{
+ GtkWidget *w = GTK_WIDGET(SP_DT_CANVAS(ec->desktop));
+ if (w->window) {
+ /* fixme: */
+ if (ec->cursor_shape) {
+ GdkBitmap *bitmap = NULL;
+ GdkBitmap *mask = NULL;
+ sp_cursor_bitmap_and_mask_from_xpm(&bitmap, &mask, ec->cursor_shape);
+ if ((bitmap != NULL) && (mask != NULL)) {
+ if (ec->cursor)
+ gdk_cursor_unref (ec->cursor);
+ ec->cursor = gdk_cursor_new_from_pixmap(bitmap, mask,
+ &w->style->black,
+ &w->style->white,
+ ec->hot_x, ec->hot_y);
+ g_object_unref (bitmap);
+ g_object_unref (mask);
+ }
+ }
+ gdk_window_set_cursor(w->window, ec->cursor);
+ }
+}
+
+/**
+ * Callback that gets called on initialization of SPEventContext object.
+ * Redraws mouse cursor, at the moment.
+ */
+static void
+sp_event_context_private_setup(SPEventContext *ec)
+{
+ sp_event_context_update_cursor(ec);
+}
+
+/**
+ * \brief Gobbles next key events on the queue with the same keyval and mask. Returns the number of events consumed.
+ */
+gint gobble_key_events(guint keyval, gint mask)
+{
+ GdkEvent *event_next;
+ gint i = 0;
+
+ event_next = gdk_event_get();
+ // while the next event is also a key notify with the same keyval and mask,
+ while (event_next && event_next->type == GDK_KEY_PRESS
+ && event_next->key.keyval == keyval
+ && (event_next->key.state & mask)) {
+ // kill it
+ gdk_event_free(event_next);
+ // get next
+ event_next = gdk_event_get();
+ i ++;
+ }
+ // otherwise, put it back onto the queue
+ if (event_next) gdk_event_put(event_next);
+
+ return i;
+}
+
+/**
+ * \brief Gobbles next motion notify events on the queue with the same mask. Returns the number of events consumed.
+*/
+gint gobble_motion_events(gint mask)
+{
+ GdkEvent *event_next;
+ gint i = 0;
+
+ event_next = gdk_event_get();
+ // while the next event is also a key notify with the same keyval and mask,
+ while (event_next && event_next->type == GDK_MOTION_NOTIFY
+ && (event_next->motion.state & mask)) {
+ // kill it
+ gdk_event_free(event_next);
+ // get next
+ event_next = gdk_event_get();
+ i ++;
+ }
+ // otherwise, put it back onto the queue
+ if (event_next) gdk_event_put(event_next);
+
+ return i;
+}
+
+/**
+ * Toggles current tool between active tool and selector tool.
+ * Subroutine of sp_event_context_private_root_handler().
+ */
+static void
+sp_toggle_selector(SPDesktop *dt)
+{
+ if (!dt->event_context) return;
+
+ if (tools_isactive(dt, TOOLS_SELECT)) {
+ if (selector_toggled) {
+ if (switch_selector_to) tools_switch (dt, switch_selector_to);
+ selector_toggled = FALSE;
+ } else return;
+ } else {
+ selector_toggled = TRUE;
+ switch_selector_to = tools_active(dt);
+ tools_switch (dt, TOOLS_SELECT);
+ }
+}
+
+/**
+ * Calculates and keeps track of scroll acceleration.
+ * Subroutine of sp_event_context_private_root_handler().
+ */
+static gdouble accelerate_scroll(GdkEvent *event, gdouble acceleration)
+{
+ guint32 time_diff = ((GdkEventKey *) event)->time - scroll_event_time;
+
+ /* key pressed within 500ms ? (1/2 second) */
+ if (time_diff > 500 || event->key.keyval != scroll_keyval) {
+ scroll_multiply = 1;
+ } else {
+ scroll_multiply += acceleration;
+ }
+
+ scroll_event_time = ((GdkEventKey *) event)->time;
+ scroll_keyval = event->key.keyval;
+
+ return scroll_multiply;
+}
+
+// This is a hack that is necessary because when middle-clicking too fast,
+// button_press events come for all clicks but there's button_release only
+// for the first one. So after a release, we must prohibit the next grab for
+// some time, or the grab will be stuck. Perhaps this is caused by some
+// wrong handling of events among contexts and not by a GDK bug;
+// if someone can fix this properly this would be great.
+static gint dontgrab = 0;
+static bool
+grab_allow_again()
+{
+ dontgrab--;
+ if (dontgrab < 0) dontgrab = 0;
+ return FALSE; // so that it is only called once
+}
+
+/**
+ * Main event dispatch, gets called from Gdk.
+ */
+static gint sp_event_context_private_root_handler(SPEventContext *event_context, GdkEvent *event)
+{
+ static NR::Point button_w;
+ static unsigned int panning = 0;
+ static unsigned int zoom_rb = 0;
+
+ SPDesktop *desktop = event_context->desktop;
+
+ tolerance = prefs_get_int_attribute_limited(
+ "options.dragtolerance","value", 0, 0, 100);
+ double const zoom_inc = prefs_get_double_attribute_limited(
+ "options.zoomincrement", "value", M_SQRT2, 1.01, 10);
+ double const acceleration = prefs_get_double_attribute_limited(
+ "options.scrollingacceleration", "value", 0, 0, 6);
+ int const key_scroll = prefs_get_int_attribute_limited(
+ "options.keyscroll", "value", 10, 0, 1000);
+ int const wheel_scroll = prefs_get_int_attribute_limited(
+ "options.wheelscroll", "value", 40, 0, 1000);
+
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_2BUTTON_PRESS:
+ if (panning) {
+ panning = 0;
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
+ event->button.time);
+ ret = TRUE;
+ } else {
+ /* sp_desktop_dialog(); */
+ }
+ break;
+ case GDK_BUTTON_PRESS:
+
+ // save drag origin
+ xp = (gint) event->button.x;
+ yp = (gint) event->button.y;
+ within_tolerance = true;
+
+ switch (event->button.button) {
+ case 2:
+
+ if (dontgrab)
+ // double-click, still not permitted to grab;
+ // increase the counter to guard against triple click
+ {
+ dontgrab ++;
+ gtk_timeout_add(250, (GtkFunction) grab_allow_again, NULL);
+ break;
+ }
+
+ button_w = NR::Point(event->button.x,
+ event->button.y);
+ if (event->button.state == GDK_SHIFT_MASK) {
+ zoom_rb = 2;
+ } else {
+ panning = 2;
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
+ NULL, event->button.time-1);
+ }
+ ret = TRUE;
+ break;
+ case 3:
+ if (event->button.state & GDK_SHIFT_MASK
+ || event->button.state & GDK_CONTROL_MASK) {
+ button_w = NR::Point(event->button.x,
+ event->button.y);
+ panning = 3;
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
+ NULL, event->button.time);
+ ret = TRUE;
+ } else {
+ sp_event_root_menu_popup(desktop, NULL, event);
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case GDK_MOTION_NOTIFY:
+ if (panning) {
+ if ((panning == 2 && !(event->motion.state & GDK_BUTTON2_MASK))
+ || (panning == 3 && !(event->motion.state & GDK_BUTTON3_MASK))) {
+ /* Gdk seems to lose button release for us sometimes :-( */
+ panning = 0;
+ dontgrab = 0;
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
+ event->button.time);
+ ret = TRUE;
+ } else {
+ if ( within_tolerance
+ && ( abs( (gint) event->motion.x - xp ) < tolerance )
+ && ( abs( (gint) event->motion.y - yp ) < tolerance ))
+ {
+ // do not drag if we're within tolerance from origin
+ break;
+ }
+ // Once the user has moved farther than tolerance from
+ // the original location (indicating they intend to move
+ // the object, not click), then always process the motion
+ // notify coordinates as given (no snapping back to origin)
+ within_tolerance = false;
+
+ // gobble subsequent motion events to prevent "sticking"
+ // when scrolling is slow
+ gobble_motion_events(panning == 2 ?
+ GDK_BUTTON2_MASK : GDK_BUTTON3_MASK);
+
+ NR::Point const motion_w(event->motion.x,
+ event->motion.y);
+ NR::Point const moved_w( motion_w - button_w );
+ event_context->desktop->scroll_world(moved_w);
+ ret = TRUE;
+ }
+ } else if (zoom_rb) {
+ NR::Point const motion_w(event->motion.x, event->motion.y);
+ NR::Point const motion_dt(desktop->w2d(motion_w));
+
+ if ( within_tolerance
+ && ( abs( (gint) event->motion.x - xp ) < tolerance )
+ && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) {
+ break; // do not drag if we're within tolerance from origin
+ }
+
+ if (within_tolerance) {
+ Inkscape::Rubberband::get()->start(desktop, motion_dt);
+ } else {
+ Inkscape::Rubberband::get()->move(motion_dt);
+ }
+
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to move the object, not click), then always process the
+ // motion notify coordinates as given (no snapping back to origin)
+ within_tolerance = false;
+ }
+ break;
+ case GDK_BUTTON_RELEASE:
+ if (within_tolerance && (panning || zoom_rb)) {
+ dontgrab ++;
+ NR::Point const event_w(event->button.x, event->button.y);
+ NR::Point const event_dt(desktop->w2d(event_w));
+ double const zoom_power = ( (event->button.state & GDK_SHIFT_MASK)
+ ? -dontgrab : dontgrab );
+ desktop->zoom_relative_keep_point(event_dt,
+ pow(zoom_inc, zoom_power));
+ gtk_timeout_add(250, (GtkFunction) grab_allow_again, NULL);
+ }
+ if (panning == event->button.button) {
+ panning = 0;
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
+ event->button.time);
+ ret = TRUE;
+ } else if (zoom_rb == event->button.button) {
+ zoom_rb = 0;
+ NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
+ if (b != NR::Nothing() && !within_tolerance) {
+ desktop->set_display_area(b.assume(), 10);
+ }
+ Inkscape::Rubberband::get()->stop();
+ }
+ xp = yp = 0;
+ break;
+ case GDK_KEY_PRESS:
+ switch (get_group0_keyval(&event->key)) {
+ unsigned int shortcut;
+ case GDK_F1:
+ /* Grab it away from Gtk */
+ shortcut = get_group0_keyval(&event->key);
+ if (event->key.state & GDK_SHIFT_MASK)
+ shortcut |= SP_SHORTCUT_SHIFT_MASK;
+ if (event->key.state & GDK_CONTROL_MASK)
+ shortcut |= SP_SHORTCUT_CONTROL_MASK;
+ if (event->key.state & GDK_MOD1_MASK)
+ shortcut |= SP_SHORTCUT_ALT_MASK;
+ ret = sp_shortcut_invoke(shortcut, desktop);
+ break;
+
+ case GDK_Tab: // disable tab/shift-tab which cycle widget focus
+ case GDK_ISO_Left_Tab: // they will get different functions
+ if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
+ ret = TRUE;
+ } else {
+ /* Grab it away from Gtk */
+ shortcut = get_group0_keyval(&event->key);
+ if (event->key.state & GDK_SHIFT_MASK)
+ shortcut |= SP_SHORTCUT_SHIFT_MASK;
+ if (event->key.state & GDK_CONTROL_MASK)
+ shortcut |= SP_SHORTCUT_CONTROL_MASK;
+ if (event->key.state & GDK_MOD1_MASK)
+ shortcut |= SP_SHORTCUT_ALT_MASK;
+ ret = sp_shortcut_invoke(shortcut, desktop);
+ }
+ break;
+ case GDK_W:
+ case GDK_w:
+ case GDK_F4:
+ /* Close view */
+ if (MOD__CTRL_ONLY) {
+ sp_ui_close_view(NULL);
+ ret = TRUE;
+ }
+ break;
+ // FIXME: make import a verb
+ case GDK_i: // Ctrl i - import file
+ if (MOD__CTRL_ONLY) {
+ sp_file_import(NULL);
+ ret = TRUE;
+ }
+ break;
+ case GDK_Left: // Ctrl Left
+ case GDK_KP_Left:
+ case GDK_KP_4:
+ if (MOD__CTRL_ONLY) {
+ int i = (int) floor(key_scroll * accelerate_scroll(event, acceleration));
+ gobble_key_events(get_group0_keyval(&event->key),
+ GDK_CONTROL_MASK);
+ event_context->desktop->scroll_world(i, 0);
+ ret = TRUE;
+ }
+ break;
+ case GDK_Up: // Ctrl Up
+ case GDK_KP_Up:
+ case GDK_KP_8:
+ if (MOD__CTRL_ONLY) {
+ int i = (int) floor(key_scroll * accelerate_scroll(event, acceleration));
+ gobble_key_events(get_group0_keyval(&event->key),
+ GDK_CONTROL_MASK);
+ event_context->desktop->scroll_world(0, i);
+ ret = TRUE;
+ }
+ break;
+ case GDK_Right: // Ctrl Right
+ case GDK_KP_Right:
+ case GDK_KP_6:
+ if (MOD__CTRL_ONLY) {
+ int i = (int) floor(key_scroll * accelerate_scroll(event, acceleration));
+ gobble_key_events(get_group0_keyval(&event->key),
+ GDK_CONTROL_MASK);
+ event_context->desktop->scroll_world(-i, 0);
+ ret = TRUE;
+ }
+ break;
+ case GDK_Down: // Ctrl Down
+ case GDK_KP_Down:
+ case GDK_KP_2:
+ if (MOD__CTRL_ONLY) {
+ int i = (int) floor(key_scroll * accelerate_scroll(event, acceleration));
+ gobble_key_events(get_group0_keyval(&event->key),
+ GDK_CONTROL_MASK);
+ event_context->desktop->scroll_world(0, -i);
+ ret = TRUE;
+ }
+ break;
+ case GDK_F10:
+ if (MOD__SHIFT_ONLY) {
+ sp_event_root_menu_popup(desktop, NULL, event);
+ ret= TRUE;
+ }
+ break;
+ case GDK_space:
+ sp_toggle_selector(desktop);
+ ret= TRUE;
+ break;
+ case GDK_z:
+ case GDK_Z:
+ if (MOD__ALT_ONLY) {
+ desktop->zoom_grab_focus();
+ ret = TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case GDK_SCROLL:
+ /* shift + wheel, pan left--right */
+ if (event->scroll.state & GDK_SHIFT_MASK) {
+ switch (event->scroll.direction) {
+ case GDK_SCROLL_UP:
+ desktop->scroll_world(wheel_scroll, 0);
+ break;
+ case GDK_SCROLL_DOWN:
+ desktop->scroll_world(-wheel_scroll, 0);
+ break;
+ default:
+ break;
+ }
+
+ /* ctrl + wheel, zoom in--out */
+ } else if (event->scroll.state & GDK_CONTROL_MASK) {
+ double rel_zoom;
+ switch (event->scroll.direction) {
+ case GDK_SCROLL_UP:
+ rel_zoom = zoom_inc;
+ break;
+ case GDK_SCROLL_DOWN:
+ rel_zoom = 1 / zoom_inc;
+ break;
+ default:
+ rel_zoom = 0.0;
+ break;
+ }
+ if (rel_zoom != 0.0) {
+ NR::Point const scroll_dt = desktop->point();
+ desktop->zoom_relative_keep_point(scroll_dt, rel_zoom);
+ }
+
+ /* no modifier, pan up--down (left--right on multiwheel mice?) */
+ } else {
+ switch (event->scroll.direction) {
+ case GDK_SCROLL_UP:
+ desktop->scroll_world(0, wheel_scroll);
+ break;
+ case GDK_SCROLL_DOWN:
+ desktop->scroll_world(0, -wheel_scroll);
+ break;
+ case GDK_SCROLL_LEFT:
+ desktop->scroll_world(wheel_scroll, 0);
+ break;
+ case GDK_SCROLL_RIGHT:
+ desktop->scroll_world(-wheel_scroll, 0);
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * Handles item specific events. Gets called from Gdk.
+ *
+ * Only reacts to right mouse button at the moment.
+ * \todo Fixme: do context sensitive popup menu on items.
+ */
+gint
+sp_event_context_private_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event)
+{
+ int ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if ((event->button.button == 3)
+ && !(event->button.state & GDK_SHIFT_MASK || event->button.state & GDK_CONTROL_MASK)) {
+ sp_event_root_menu_popup(ec->desktop, item, event);
+ ret = TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * Gets called when attribute changes value.
+ */
+static void
+sp_ec_repr_attr_changed(Inkscape::XML::Node *prefs_repr, gchar const *key, gchar const *oldval, gchar const *newval,
+ bool is_interactive, gpointer data)
+{
+ SPEventContext *ec;
+
+ ec = SP_EVENT_CONTEXT(data);
+
+ if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->set) {
+ ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->set(ec, key, newval);
+ }
+}
+
+Inkscape::XML::NodeEventVector sp_ec_event_vector = {
+ NULL, /* Child added */
+ NULL, /* Child removed */
+ sp_ec_repr_attr_changed,
+ NULL, /* Content changed */
+ NULL /* Order changed */
+};
+
+/**
+ * Creates new SPEventContext object and calls its virtual setup() function.
+ */
+SPEventContext *
+sp_event_context_new(GType type, SPDesktop *desktop, Inkscape::XML::Node *prefs_repr, unsigned int key)
+{
+ g_return_val_if_fail(g_type_is_a(type, SP_TYPE_EVENT_CONTEXT), NULL);
+ g_return_val_if_fail(desktop != NULL, NULL);
+
+ SPEventContext *const ec = (SPEventContext*)g_object_new(type, NULL);
+
+ ec->desktop = desktop;
+ ec->_message_context = new Inkscape::MessageContext(desktop->messageStack());
+ ec->key = key;
+ ec->prefs_repr = prefs_repr;
+ if (ec->prefs_repr) {
+ Inkscape::GC::anchor(ec->prefs_repr);
+ sp_repr_add_listener(ec->prefs_repr, &sp_ec_event_vector, ec);
+ }
+
+ if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->setup)
+ ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->setup(ec);
+
+ return ec;
+}
+
+/**
+ * Finishes SPEventContext.
+ */
+void
+sp_event_context_finish(SPEventContext *ec)
+{
+ g_return_if_fail(ec != NULL);
+ g_return_if_fail(SP_IS_EVENT_CONTEXT(ec));
+
+ ec->enableSelectionCue(false);
+
+ if (ec->next) {
+ g_warning("Finishing event context with active link\n");
+ }
+
+ if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->finish)
+ ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->finish(ec);
+}
+
+//-------------------------------member functions
+
+/**
+ * Enables/disables the SPEventContext's SelCue.
+ */
+void SPEventContext::enableSelectionCue(bool enable) {
+ if (enable) {
+ if (!_selcue) {
+ _selcue = new Inkscape::SelCue(desktop);
+ }
+ } else {
+ delete _selcue;
+ _selcue = NULL;
+ }
+}
+
+/**
+ * Enables/disables the SPEventContext's GrDrag.
+ */
+void SPEventContext::enableGrDrag(bool enable) {
+ if (enable) {
+ if (!_grdrag) {
+ _grdrag = new GrDrag(desktop);
+ }
+ } else {
+ if (_grdrag) {
+ delete _grdrag;
+ _grdrag = NULL;
+ }
+ }
+}
+
+/**
+ * Calls virtual set() function of SPEventContext.
+ */
+void
+sp_event_context_read(SPEventContext *ec, gchar const *key)
+{
+ g_return_if_fail(ec != NULL);
+ g_return_if_fail(SP_IS_EVENT_CONTEXT(ec));
+ g_return_if_fail(key != NULL);
+
+ if (ec->prefs_repr) {
+ gchar const *val = ec->prefs_repr->attribute(key);
+ if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->set)
+ ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->set(ec, key, val);
+ }
+}
+
+/**
+ * Calls virtual activate() function of SPEventContext.
+ */
+void
+sp_event_context_activate(SPEventContext *ec)
+{
+ g_return_if_fail(ec != NULL);
+ g_return_if_fail(SP_IS_EVENT_CONTEXT(ec));
+
+ if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->activate)
+ ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->activate(ec);
+}
+
+/**
+ * Calls virtual deactivate() function of SPEventContext.
+ */
+void
+sp_event_context_deactivate(SPEventContext *ec)
+{
+ g_return_if_fail(ec != NULL);
+ g_return_if_fail(SP_IS_EVENT_CONTEXT(ec));
+
+ if (((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->activate)
+ ((SPEventContextClass *) G_OBJECT_GET_CLASS(ec))->activate(ec);
+}
+
+/**
+ * Calls virtual root_handler(), the main event handling function.
+ */
+gint
+sp_event_context_root_handler(SPEventContext * event_context, GdkEvent * event)
+{
+ gint ret;
+
+ ret = ((SPEventContextClass *) G_OBJECT_GET_CLASS(event_context))->root_handler(event_context, event);
+
+ set_event_location(event_context->desktop, event);
+
+ return ret;
+}
+
+/**
+ * Calls virtual item_handler(), the item event handling function.
+ */
+gint
+sp_event_context_item_handler(SPEventContext * event_context, SPItem * item, GdkEvent * event)
+{
+ gint ret;
+
+ ret = ((SPEventContextClass *) G_OBJECT_GET_CLASS(event_context))->item_handler(event_context, item, event);
+
+ if (! ret) {
+ ret = sp_event_context_root_handler(event_context, event);
+ } else {
+ set_event_location(event_context->desktop, event);
+ }
+
+ return ret;
+}
+
+/**
+ * Emits 'position_set' signal on desktop and shows coordinates on status bar.
+ */
+static void set_event_location(SPDesktop *desktop, GdkEvent *event)
+{
+ if (event->type != GDK_MOTION_NOTIFY) {
+ return;
+ }
+
+ NR::Point const button_w(event->button.x, event->button.y);
+ NR::Point const button_dt(desktop->w2d(button_w));
+ desktop-> setPosition (button_dt);
+ desktop->set_coordinate_status(button_dt);
+}
+
+//-------------------------------------------------------------------
+/**
+ * Create popup menu and tell Gtk to show it.
+ */
+void
+sp_event_root_menu_popup(SPDesktop *desktop, SPItem *item, GdkEvent *event)
+{
+ GtkWidget *menu;
+
+ /* fixme: This is not what I want but works for now (Lauris) */
+ if (event->type == GDK_KEY_PRESS) {
+ item = SP_DT_SELECTION(desktop)->singleItem();
+ }
+ menu = sp_ui_context_menu(desktop, item);
+ gtk_widget_show(menu);
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, 0, NULL, event->button.button, event->button.time);
+ break;
+ case GDK_KEY_PRESS:
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, 0, NULL, 0, event->key.time);
+ break;
+ default:
+ break;
+ }
+}
+
+/**
+ * Show tool context specific modifier tip.
+ */
+void
+sp_event_show_modifier_tip(Inkscape::MessageContext *message_context,
+ GdkEvent *event, gchar const *ctrl_tip, gchar const *shift_tip,
+ gchar const *alt_tip)
+{
+ guint keyval = get_group0_keyval(&event->key);
+
+ bool ctrl = ctrl_tip && (MOD__CTRL
+ || (keyval == GDK_Control_L)
+ || (keyval == GDK_Control_R));
+ bool shift = shift_tip
+ && (MOD__SHIFT || (keyval == GDK_Shift_L) || (keyval == GDK_Shift_R));
+ bool alt = alt_tip
+ && (MOD__ALT
+ || (keyval == GDK_Alt_L)
+ || (keyval == GDK_Alt_R)
+ || (keyval == GDK_Meta_L)
+ || (keyval == GDK_Meta_R));
+
+ gchar *tip = g_strdup_printf("%s%s%s%s%s",
+ ( ctrl ? ctrl_tip : "" ),
+ ( ctrl && (shift || alt) ? "; " : "" ),
+ ( shift ? shift_tip : "" ),
+ ( (ctrl || shift) && alt ? "; " : "" ),
+ ( alt ? alt_tip : "" ));
+
+ if (strlen(tip) > 0) {
+ message_context->flash(Inkscape::INFORMATION_MESSAGE, tip);
+ }
+
+ g_free(tip);
+}
+
+/**
+ * Return the keyval corresponding to the key event in group 0, i.e.,
+ * in the main (English) layout.
+ *
+ * Use this instead of simply event->keyval, so that your keyboard shortcuts
+ * work regardless of layouts (e.g., in Cyrillic).
+ */
+guint
+get_group0_keyval(GdkEventKey *event)
+{
+ guint keyval = 0;
+ gdk_keymap_translate_keyboard_state(
+ gdk_keymap_get_for_display(gdk_display_get_default()),
+ event->hardware_keycode,
+ (GdkModifierType) event->state,
+ 0 /*event->key.group*/,
+ &keyval, NULL, NULL, NULL);
+ return keyval;
+}
+
+/**
+ * Returns item at point p in desktop.
+ *
+ * If state includes alt key mask, cyclically selects under; honors
+ * into_groups.
+ */
+SPItem *
+sp_event_context_find_item (SPDesktop *desktop, NR::Point const p,
+ bool select_under, bool into_groups)
+{
+ SPItem *item;
+
+ if (select_under) {
+ SPItem *selected_at_point =
+ desktop->item_from_list_at_point_bottom (desktop->selection->itemList(), p);
+ item = desktop->item_at_point(p, into_groups, selected_at_point);
+ if (item == NULL) { // we may have reached bottom, flip over to the top
+ item = desktop->item_at_point(p, into_groups, NULL);
+ }
+ } else
+ item = desktop->item_at_point(p, into_groups, NULL);
+
+ return item;
+}
+
+/**
+ * Returns item if it is under point p in desktop, at any depth; otherwise returns NULL.
+ *
+ * Honors into_groups.
+ */
+SPItem *
+sp_event_context_over_item (SPDesktop *desktop, SPItem *item, NR::Point const p)
+{
+ GSList *temp = NULL;
+ temp = g_slist_prepend (temp, item);
+ SPItem *item_at_point = desktop->item_from_list_at_point_bottom (temp, p);
+ g_slist_free (temp);
+
+ return item_at_point;
+}
+
+/**
+ * Called when SPEventContext subclass node attribute changed.
+ */
+void
+ec_shape_event_attr_changed(Inkscape::XML::Node *shape_repr, gchar const *name,
+ gchar const *old_value, gchar const *new_value,
+ bool const is_interactive, gpointer const data)
+{
+ if (!name
+ || !strcmp(name, "style")
+ || SP_ATTRIBUTE_IS_CSS(sp_attribute_lookup(name))) {
+ // no need to regenrate knotholder if only style changed
+ return;
+ }
+
+ SPEventContext *ec = SP_EVENT_CONTEXT(data);
+
+ if (ec->shape_knot_holder) {
+ sp_knot_holder_destroy(ec->shape_knot_holder);
+ }
+ ec->shape_knot_holder = NULL;
+
+ SPDesktop *desktop = ec->desktop;
+
+ SPItem *item = SP_DT_SELECTION(desktop)->singleItem();
+
+ if (item) {
+ ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
+ }
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :