diff options
Diffstat (limited to 'src/event-context.cpp')
| -rw-r--r-- | src/event-context.cpp | 1033 |
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 : |
