/**
 * Given a Geom::Rect that may, for example, correspond to the bbox of an object,
 * this function fits the canvas to that rect by resizing the canvas
 * and translating the document root into position.
 */
void SPDocument::fitToRect(Geom::Rect const &rect)
{
    double const w = rect.width();
    double const h = rect.height();

    double const old_height = sp_document_height(this);
    SPUnit const &px(sp_unit_get_by_id(SP_UNIT_PX));
    sp_document_set_width(this, w, &px);
    sp_document_set_height(this, h, &px);

    Geom::Translate const tr(Geom::Point(0, (old_height - h))
                             - to_2geom(rect.min()));
    SP_GROUP(root)->translateChildItems(tr);
    SPNamedView *nv = sp_document_namedview(this, 0);
    if(nv) {
        Geom::Translate tr2(-rect.min());
        nv->translateGuides(tr2);

        // update the viewport so the drawing appears to stay where it was
        nv->scrollAllDesktops(-tr2[0], tr2[1], false);
    }
}
Geom::Rect Inkscape::snap_rectangular_box(SPDesktop const *desktop, SPItem *item,
                                        Geom::Point const &pt, Geom::Point const &center, int state)
{
    Geom::Point p[2];

    bool const shift = state & GDK_SHIFT_MASK;
    bool const control = state & GDK_CONTROL_MASK;

    SnapManager &m = desktop->namedview->snap_manager;
    m.setup(desktop, false, item);
    Inkscape::SnappedPoint snappoint;

    if (control) {

        /* Control is down: we are constrained to producing integer-ratio rectangles */

        /* Vector from the centre of the box to the point we are dragging to */
        Geom::Point delta = pt - center;

        /* Round it so that we have an integer-ratio (or golden ratio) box */
        if (fabs(delta[Geom::X]) > fabs(delta[Geom::Y]) && (delta[Geom::Y] != 0.0)) {
            double ratio = delta[Geom::X] / delta[Geom::Y];
            double ratioabs = fabs (ratio);
            double sign = (ratio < 0 ? -1 : 1);
            if (midpt_1_goldenratio < ratioabs && ratioabs < midpt_goldenratio_2) {
                delta[Geom::X] = sign * goldenratio * delta[Geom::Y];
            } else {
                delta[Geom::X] = floor(ratio + 0.5) * delta[Geom::Y];
            }
        } else if (delta[Geom::X] != 0.0) {
            double ratio = delta[Geom::Y] / delta[Geom::X];
            double ratioabs = fabs (ratio);
            double sign = (ratio < 0 ? -1 : 1);
            if (midpt_1_goldenratio < ratioabs && ratioabs < midpt_goldenratio_2) {
                delta[Geom::Y] = sign * goldenratio * delta[Geom::X];
            } else {
                delta[Geom::Y] = floor(delta[Geom::Y] / delta[Geom::X] + 0.5) * delta[Geom::X];
            }
        }

        /* p[1] is the dragged point with the integer-ratio constraint */
        p[1] = center + delta;

        if (shift) {

            /* Shift is down, so our origin is the centre point rather than the corner
            ** point; this means that corner-point movements are bound to each other.
            */

            /* p[0] is the opposite corner of our box */
            p[0] = center - delta;

            Inkscape::SnappedPoint s[2];

            /* Try to snap p[0] (the opposite corner) along the constraint vector */
            s[0] = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[0]),
                                     Inkscape::Snapper::ConstraintLine(p[0] - p[1]));

            /* Try to snap p[1] (the dragged corner) along the constraint vector */
            s[1] = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[1]),
                                     Inkscape::Snapper::ConstraintLine(p[1] - p[0]));

            /* Choose the best snap and update points accordingly */
            if (s[0].getSnapDistance() < s[1].getSnapDistance()) {
                if (s[0].getSnapped()) {
                    p[0] = s[0].getPoint();
                    p[1] = 2 * center - s[0].getPoint();
                    snappoint = s[0];
                }
            } else {
                if (s[1].getSnapped()) {
                    p[0] = 2 * center - s[1].getPoint();
                    p[1] = s[1].getPoint();
                    snappoint = s[1];
                }
            }
        } else {

            /* Our origin is the opposite corner.  Snap the drag point along the constraint vector */
            p[0] = center;
            snappoint = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[1]),
                                          Inkscape::Snapper::ConstraintLine(p[1] - p[0]));
            if (snappoint.getSnapped()) {
                p[1] = snappoint.getPoint();
            }
        }

    } else if (shift) {

        /* Shift is down, so our origin is the centre point rather than the corner point;
        ** this means that corner-point movements are bound to each other.
        */

        p[1] = pt;
        p[0] = 2 * center - p[1];

        Inkscape::SnappedPoint s[2];

        s[0] = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[0]));
        s[1] = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(p[1]));

        if (s[0].getSnapDistance() < s[1].getSnapDistance()) {
            if (s[0].getSnapped()) {
                p[0] = s[0].getPoint();
                p[1] = 2 * center - s[0].getPoint();
                snappoint = s[0];
            }
        } else {
            if (s[1].getSnapped()) {
                p[0] = 2 * center - s[1].getPoint();
                p[1] = s[1].getPoint();
                snappoint = s[1];
            }
        }

    } else {

        /* There's no constraint on the corner point, so just snap it to anything */
        p[0] = center;
        p[1] = pt;
        snappoint = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(pt));
        if (snappoint.getSnapped()) {
            p[1] = snappoint.getPoint();
        }
    }

    if (snappoint.getSnapped()) {
        desktop->snapindicator->set_new_snaptarget(snappoint);
    }

    p[0] = sp_desktop_dt2doc_xy_point(desktop, p[0]);
    p[1] = sp_desktop_dt2doc_xy_point(desktop, p[1]);
    
    return Geom::Rect(Geom::Point(MIN(p[0][Geom::X], p[1][Geom::X]), MIN(p[0][Geom::Y], p[1][Geom::Y])),
                    Geom::Point(MAX(p[0][Geom::X], p[1][Geom::X]), MAX(p[0][Geom::Y], p[1][Geom::Y])));
}
Beispiel #3
0
static void
sp_spiral_drag(SPSpiralContext *sc, Geom::Point p, guint state)
{
    SPDesktop *desktop = SP_EVENT_CONTEXT(sc)->desktop;

    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
    int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);

    if (!sc->item) {

        if (Inkscape::have_viable_layer(desktop, sc->_message_context) == false) {
            return;
        }

        /* Create object */
        Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_EVENT_CONTEXT_DOCUMENT(sc));
        Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
        repr->setAttribute("sodipodi:type", "spiral");

        /* Set style */
        sp_desktop_apply_style_tool(desktop, repr, "/tools/shapes/spiral", false);

        sc->item = (SPItem *) desktop->currentLayer()->appendChildRepr(repr);
        Inkscape::GC::release(repr);
        sc->item->transform = sp_item_i2doc_affine(SP_ITEM(desktop->currentLayer())).inverse();
        sc->item->updateRepr();

        sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 5);
    }

    SnapManager &m = desktop->namedview->snap_manager;
    m.setup(desktop, true, sc->item);
    Geom::Point pt2g = to_2geom(p);
    m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, pt2g);

    Geom::Point const p0 = to_2geom(sp_desktop_dt2doc_xy_point(desktop, sc->center));
    Geom::Point const p1 = to_2geom(sp_desktop_dt2doc_xy_point(desktop, from_2geom(pt2g)));        
    
    SPSpiral *spiral = SP_SPIRAL(sc->item);

    Geom::Point const delta = p1 - p0;
    gdouble const rad = Geom::L2(delta);

    gdouble arg = Geom::atan2(delta) - 2.0*M_PI*spiral->revo;

    if (state & GDK_CONTROL_MASK) {
        arg = sp_round(arg, M_PI/snaps);
    }

    /* Fixme: these parameters should be got from dialog box */
    sp_spiral_position_set(spiral, p0[Geom::X], p0[Geom::Y],
                           /*expansion*/ sc->exp,
                           /*revolution*/ sc->revo,
                           rad, arg,
                           /*t0*/ sc->t0);

    /* status text */
    GString *rads = SP_PX_TO_METRIC_STRING(rad, desktop->namedview->getDefaultMetric());
    sc->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE,
                               _("<b>Spiral</b>: radius %s, angle %5g&#176;; with <b>Ctrl</b> to snap angle"),
                               rads->str, sp_round((arg + 2.0*M_PI*spiral->revo)*180/M_PI, 0.0001));
    g_string_free(rads, FALSE);
}
static gint
sp_gradient_context_root_handler(SPEventContext *event_context, GdkEvent *event)
{
    static bool dragging;

    SPDesktop *desktop = event_context->desktop;
    Inkscape::Selection *selection = sp_desktop_selection (desktop);
    Inkscape::Preferences *prefs = Inkscape::Preferences::get();

    SPGradientContext *rc = SP_GRADIENT_CONTEXT(event_context);

    event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
    double const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000); // in px

    GrDrag *drag = event_context->_grdrag;
    g_assert (drag);

    gint ret = FALSE;
    switch (event->type) {
    case GDK_2BUTTON_PRESS:
        if ( event->button.button == 1 ) {
            bool over_line = false;
            SPCtrlLine *line = NULL;
            if (drag->lines) {
                for (GSList *l = drag->lines; (l != NULL) && (!over_line); l = l->next) {
                    line = (SPCtrlLine*) l->data;
                    over_line |= sp_gradient_context_is_over_line (rc, (SPItem*) line, Geom::Point(event->motion.x, event->motion.y));
                }
            }
            if (over_line) {
                // we take the first item in selection, because with doubleclick, the first click
                // always resets selection to the single object under cursor
                sp_gradient_context_add_stop_near_point(rc, SP_ITEM(selection->itemList()->data), rc->mousepoint_doc, event->button.time);
            } else {
                for (GSList const* i = selection->itemList(); i != NULL; i = i->next) {
                    SPItem *item = SP_ITEM(i->data);
                    SPGradientType new_type = (SPGradientType) prefs->getInt("/tools/gradient/newgradient", SP_GRADIENT_TYPE_LINEAR);
                    guint new_fill = prefs->getInt("/tools/gradient/newfillorstroke", 1);

                    SPGradient *vector = sp_gradient_vector_for_object(sp_desktop_document(desktop), desktop,                                                                                   SP_OBJECT (item), new_fill);

                    SPGradient *priv = sp_item_set_gradient(item, vector, new_type, new_fill);
                    sp_gradient_reset_to_userspace(priv, item);
                }

                sp_document_done (sp_desktop_document (desktop), SP_VERB_CONTEXT_GRADIENT,
                                  _("Create default gradient"));
            }
            ret = TRUE;
        }
        break;
    case GDK_BUTTON_PRESS:
        if ( event->button.button == 1 && !event_context->space_panning ) {
            Geom::Point button_w(event->button.x, event->button.y);

            // save drag origin
            event_context->xp = (gint) button_w[Geom::X];
            event_context->yp = (gint) button_w[Geom::Y];
            event_context->within_tolerance = true;

            dragging = true;

            Geom::Point button_dt = to_2geom(desktop->w2d(button_w));
            if (event->button.state & GDK_SHIFT_MASK) {
                Inkscape::Rubberband::get(desktop)->start(desktop, from_2geom(button_dt));
            } else {
                // remember clicked item, disregarding groups, honoring Alt; do nothing with Crtl to
                // enable Ctrl+doubleclick of exactly the selected item(s)
                if (!(event->button.state & GDK_CONTROL_MASK))
                    event_context->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, TRUE);

                /* Snap center to nearest magnetic point */
                SnapManager &m = desktop->namedview->snap_manager;
                m.setup(desktop);
                m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, button_dt);
                rc->origin = from_2geom(button_dt);
            }

            ret = TRUE;
        }
        break;
    case GDK_MOTION_NOTIFY:
        if ( dragging
             && ( event->motion.state & GDK_BUTTON1_MASK ) && !event_context->space_panning )
        {
            if ( event_context->within_tolerance
                 && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
                 && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
                break; // do not drag if we're within tolerance from origin
            }
            // Once the user has moved farther than tolerance from the original location
            // (indicating they intend to draw, not click), then always process the
            // motion notify coordinates as given (no snapping back to origin)
            event_context->within_tolerance = false;

            Geom::Point const motion_w(event->motion.x,
                                     event->motion.y);
            Geom::Point const motion_dt = event_context->desktop->w2d(motion_w);

            if (Inkscape::Rubberband::get(desktop)->is_started()) {
                Inkscape::Rubberband::get(desktop)->move(motion_dt);
                event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Draw around</b> handles to select them"));
            } else {
                sp_gradient_drag(*rc, motion_dt, event->motion.state, event->motion.time);
            }
            gobble_motion_events(GDK_BUTTON1_MASK);

            ret = TRUE;
        } else {
            bool over_line = false;
            if (drag->lines) {
                for (GSList *l = drag->lines; l != NULL; l = l->next) {
                    over_line |= sp_gradient_context_is_over_line (rc, (SPItem*) l->data, Geom::Point(event->motion.x, event->motion.y));
                }
            }

            if (rc->cursor_addnode && !over_line) {
                event_context->cursor_shape = cursor_gradient_xpm;
                sp_event_context_update_cursor(event_context);
                rc->cursor_addnode = false;
            } else if (!rc->cursor_addnode && over_line) {
                event_context->cursor_shape = cursor_gradient_add_xpm;
                sp_event_context_update_cursor(event_context);
                rc->cursor_addnode = true;
            }
        }
        break;
    case GDK_BUTTON_RELEASE:
        event_context->xp = event_context->yp = 0;
        if ( event->button.button == 1 && !event_context->space_panning ) {
            if ( (event->button.state & GDK_CONTROL_MASK) && (event->button.state & GDK_MOD1_MASK ) ) {
                bool over_line = false;
                SPCtrlLine *line = NULL;
                if (drag->lines) {
                    for (GSList *l = drag->lines; (l != NULL) && (!over_line); l = l->next) {
                        line = (SPCtrlLine*) l->data;
                        over_line = sp_gradient_context_is_over_line (rc, (SPItem*) line, Geom::Point(event->motion.x, event->motion.y));
                        if (over_line)
                            break;
                    }
                }
                if (over_line && line) {
                    sp_gradient_context_add_stop_near_point(rc, line->item, rc->mousepoint_doc, 0);
                    ret = TRUE;
                }
            } else {
                dragging = false;

                // unless clicked with Ctrl (to enable Ctrl+doubleclick).  
                if (event->button.state & GDK_CONTROL_MASK) {
                    ret = TRUE;
                    break;
                }

                if (!event_context->within_tolerance) {
                    // we've been dragging, either do nothing (grdrag handles that),
                    // or rubberband-select if we have rubberband
                    Inkscape::Rubberband::Rubberband *r = Inkscape::Rubberband::get(desktop);
                    if (r->is_started() && !event_context->within_tolerance) {
                        // this was a rubberband drag
                        if (r->getMode() == RUBBERBAND_MODE_RECT) {
                            Geom::OptRect const b = r->getRectangle();
                            drag->selectRect(*b);
                        }
                    }

                } else if (event_context->item_to_select) {
                    // no dragging, select clicked item if any
                    if (event->button.state & GDK_SHIFT_MASK) {
                        selection->toggle(event_context->item_to_select);
                    } else {
                        selection->set(event_context->item_to_select);
                    }
                } else {
                    // click in an empty space; do the same as Esc
                    if (drag->selected) {
                        drag->deselectAll();
                    } else {
                        selection->clear();
                    }
                }

                event_context->item_to_select = NULL;
                ret = TRUE;
            }
            Inkscape::Rubberband::get(desktop)->stop(); 
        }
        break;
    case GDK_KEY_PRESS:
        switch (get_group0_keyval (&event->key)) {
        case GDK_Alt_L:
        case GDK_Alt_R:
        case GDK_Control_L:
        case GDK_Control_R:
        case GDK_Shift_L:
        case GDK_Shift_R:
        case GDK_Meta_L:  // Meta is when you press Shift+Alt (at least on my machine)
        case GDK_Meta_R:
            sp_event_show_modifier_tip (event_context->defaultMessageContext(), event,
                                        _("<b>Ctrl</b>: snap gradient angle"),
                                        _("<b>Shift</b>: draw gradient around the starting point"),
                                        NULL);
            break;

        case GDK_x:
        case GDK_X:
            if (MOD__ALT_ONLY) {
                desktop->setToolboxFocusTo ("altx-grad");
                ret = TRUE;
            }
            break;

        case GDK_A:
        case GDK_a:
            if (MOD__CTRL_ONLY && drag->isNonEmpty()) {
                drag->selectAll();
                ret = TRUE;
            }
            break;

        case GDK_L:
        case GDK_l:
            if (MOD__CTRL_ONLY && drag->isNonEmpty() && drag->hasSelection()) {
                sp_gradient_simplify(rc, 1e-4);
                ret = TRUE;
            }
            break;

        case GDK_Escape:
            if (drag->selected) {
                drag->deselectAll();
            } else {
                selection->clear();
            }
            ret = TRUE;
            //TODO: make dragging escapable by Esc
            break;

        case GDK_Left: // move handle left
        case GDK_KP_Left:
        case GDK_KP_4:
            if (!MOD__CTRL) { // not ctrl
                gint mul = 1 + gobble_key_events(
                    get_group0_keyval(&event->key), 0); // with any mask
                if (MOD__ALT) { // alt
                    if (MOD__SHIFT) drag->selected_move_screen(mul*-10, 0); // shift
                    else drag->selected_move_screen(mul*-1, 0); // no shift
                }
                else { // no alt
                    if (MOD__SHIFT) drag->selected_move(mul*-10*nudge, 0); // shift
                    else drag->selected_move(mul*-nudge, 0); // no shift
                }
                ret = TRUE;
            }
            break;
        case GDK_Up: // move handle up
        case GDK_KP_Up:
        case GDK_KP_8:
            if (!MOD__CTRL) { // not ctrl
                gint mul = 1 + gobble_key_events(
                    get_group0_keyval(&event->key), 0); // with any mask
                if (MOD__ALT) { // alt
                    if (MOD__SHIFT) drag->selected_move_screen(0, mul*10); // shift
                    else drag->selected_move_screen(0, mul*1); // no shift
                }
                else { // no alt
                    if (MOD__SHIFT) drag->selected_move(0, mul*10*nudge); // shift
                    else drag->selected_move(0, mul*nudge); // no shift
                }
                ret = TRUE;
            }
            break;
        case GDK_Right: // move handle right
        case GDK_KP_Right:
        case GDK_KP_6:
            if (!MOD__CTRL) { // not ctrl
                gint mul = 1 + gobble_key_events(
                    get_group0_keyval(&event->key), 0); // with any mask
                if (MOD__ALT) { // alt
                    if (MOD__SHIFT) drag->selected_move_screen(mul*10, 0); // shift
                    else drag->selected_move_screen(mul*1, 0); // no shift
                }
                else { // no alt
                    if (MOD__SHIFT) drag->selected_move(mul*10*nudge, 0); // shift
                    else drag->selected_move(mul*nudge, 0); // no shift
                }
                ret = TRUE;
            }
            break;
        case GDK_Down: // move handle down
        case GDK_KP_Down:
        case GDK_KP_2:
            if (!MOD__CTRL) { // not ctrl
                gint mul = 1 + gobble_key_events(
                    get_group0_keyval(&event->key), 0); // with any mask
                if (MOD__ALT) { // alt
                    if (MOD__SHIFT) drag->selected_move_screen(0, mul*-10); // shift
                    else drag->selected_move_screen(0, mul*-1); // no shift
                }
                else { // no alt
                    if (MOD__SHIFT) drag->selected_move(0, mul*-10*nudge); // shift
                    else drag->selected_move(0, mul*-nudge); // no shift
                }
                ret = TRUE;
            }
            break;
        case GDK_r:
        case GDK_R:
            if (MOD__SHIFT_ONLY) {
                // First try selected dragger
                if (drag && drag->selected) {
                    drag->selected_reverse_vector();
                } else { // If no drag or no dragger selected, act on selection (both fill and stroke gradients)
                    for (GSList const* i = selection->itemList(); i != NULL; i = i->next) {
                        sp_item_gradient_reverse_vector (SP_ITEM(i->data), true);
                        sp_item_gradient_reverse_vector (SP_ITEM(i->data), false);
                    }
                }
                // we did an undoable action
                sp_document_done (sp_desktop_document (desktop), SP_VERB_CONTEXT_GRADIENT,
                                  _("Invert gradient"));
                ret = TRUE;
            }
            break;

        case GDK_Insert:
        case GDK_KP_Insert:
            // with any modifiers:
            sp_gradient_context_add_stops_between_selected_stops (rc);
            ret = TRUE;
            break;

        case GDK_Delete:
        case GDK_KP_Delete:
        case GDK_BackSpace:
            if ( drag->selected ) {
                drag->deleteSelected(MOD__CTRL_ONLY);
                ret = TRUE;
            }
            break;
        default:
            break;
        }
        break;
    case GDK_KEY_RELEASE:
        switch (get_group0_keyval (&event->key)) {
        case GDK_Alt_L:
        case GDK_Alt_R:
        case GDK_Control_L:
        case GDK_Control_R:
        case GDK_Shift_L:
        case GDK_Shift_R:
        case GDK_Meta_L:  // Meta is when you press Shift+Alt
        case GDK_Meta_R:
            event_context->defaultMessageContext()->clear();
            break;
        default:
            break;
        }
        break;
    default:
        break;
    }

    if (!ret) {
        if (((SPEventContextClass *) parent_class)->root_handler) {
            ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
        }
    }

    return ret;
}
Beispiel #5
0
static gint
sp_spiral_context_root_handler(SPEventContext *event_context, GdkEvent *event)
{
    static gboolean dragging;

    SPDesktop *desktop = event_context->desktop;
    Inkscape::Selection *selection = sp_desktop_selection (desktop);
    SPSpiralContext *sc = SP_SPIRAL_CONTEXT(event_context);

    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
    event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);

    gint ret = FALSE;

    switch (event->type) {
        case GDK_BUTTON_PRESS:
            if (event->button.button == 1 && !event_context->space_panning) {

                dragging = TRUE;
                sc->center = Inkscape::setup_for_drag_start(desktop, event_context, event);

                SnapManager &m = desktop->namedview->snap_manager;
                m.setup(desktop);
                Geom::Point pt2g = to_2geom(sc->center);
                m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, pt2g);
                sc->center = from_2geom(pt2g);

                sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
                                    ( GDK_KEY_PRESS_MASK |
                                      GDK_BUTTON_RELEASE_MASK |
                                      GDK_POINTER_MOTION_MASK |
                                      GDK_POINTER_MOTION_HINT_MASK |
                                      GDK_BUTTON_PRESS_MASK    ),
                                    NULL, event->button.time);
                ret = TRUE;
            }
            break;
        case GDK_MOTION_NOTIFY:
            if (dragging && (event->motion.state & GDK_BUTTON1_MASK) && !event_context->space_panning) {

                if ( event_context->within_tolerance
                     && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
                     && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
                    break; // do not drag if we're within tolerance from origin
                }
                // Once the user has moved farther than tolerance from the original location
                // (indicating they intend to draw, not click), then always process the
                // motion notify coordinates as given (no snapping back to origin)
                event_context->within_tolerance = false;

                Geom::Point const motion_w(event->motion.x, event->motion.y);
                Geom::Point motion_dt(event_context->desktop->w2d(motion_w));
                
                SnapManager &m = desktop->namedview->snap_manager;
                m.setup(desktop, true, sc->item);
                m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, motion_dt);
                sp_spiral_drag(sc, from_2geom(motion_dt), event->motion.state);

                gobble_motion_events(GDK_BUTTON1_MASK);

                ret = TRUE;
            }
            break;
        case GDK_BUTTON_RELEASE:
            event_context->xp = event_context->yp = 0;
            if (event->button.button == 1 && !event_context->space_panning) {
                dragging = FALSE;
                if (!event_context->within_tolerance) {
                    // we've been dragging, finish the spiral
                    sp_spiral_finish(sc);
                } else if (event_context->item_to_select) {
                    // no dragging, select clicked item if any
                    if (event->button.state & GDK_SHIFT_MASK) {
                        selection->toggle(event_context->item_to_select);
                    } else {
                        selection->set(event_context->item_to_select);
                    }
                } else {
                    // click in an empty space
                    selection->clear();
                }

                event_context->item_to_select = NULL;
                ret = TRUE;
                sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
            }
            break;
        case GDK_KEY_PRESS:
            switch (get_group0_keyval(&event->key)) {
                case GDK_Alt_R:
                case GDK_Control_L:
                case GDK_Control_R:
                case GDK_Shift_L:
                case GDK_Shift_R:
                case GDK_Meta_L:  // Meta is when you press Shift+Alt (at least on my machine)
                case GDK_Meta_R:
                    sp_event_show_modifier_tip(event_context->defaultMessageContext(), event,
                                               _("<b>Ctrl</b>: snap angle"),
                                               NULL,
                                               _("<b>Alt</b>: lock spiral radius"));
                    break;
                case GDK_Up:
                case GDK_Down:
                case GDK_KP_Up:
                case GDK_KP_Down:
                    // prevent the zoom field from activation
                    if (!MOD__CTRL_ONLY)
                        ret = TRUE;
                    break;
                case GDK_x:
                case GDK_X:
                    if (MOD__ALT_ONLY) {
                        desktop->setToolboxFocusTo ("altx-spiral");
                        ret = TRUE;
                    }
                    break;
                case GDK_Escape:
                    sp_desktop_selection(desktop)->clear();
                    //TODO: make dragging escapable by Esc
                    break;

                case GDK_space:
                    if (dragging) {
                        sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
                                              event->button.time);
                        dragging = false;
                        if (!event_context->within_tolerance) {
                            // we've been dragging, finish the rect
                            sp_spiral_finish(sc);
                        }
                        // do not return true, so that space would work switching to selector
                    }
                    break;

                default:
                    break;
            }
            break;
        case GDK_KEY_RELEASE:
            switch (get_group0_keyval(&event->key)) {
                case GDK_Alt_L:
                case GDK_Alt_R:
                case GDK_Control_L:
                case GDK_Control_R:
                case GDK_Shift_L:
                case GDK_Shift_R:
                case GDK_Meta_L:  // Meta is when you press Shift+Alt
                case GDK_Meta_R:
                    event_context->defaultMessageContext()->clear();
                    break;
                default:
                    break;
            }
            break;
        default:
            break;
    }

    if (!ret) {
        if (((SPEventContextClass *) parent_class)->root_handler)
            ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
    }

    return ret;
}