From: Krzysztof KosiƄski Date: Sun, 10 Jan 2010 00:46:28 +0000 (+0100) Subject: * Implement node snapping. X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=70d31ae8a7a27e57cfcdc921ea0d2f47c92442a4;p=inkscape.git * Implement node snapping. * Fix minor bug in linear grow. * Add --fixes. * Move some node selection-related functions to ControlPointSelection. --- diff --git a/src/2geom/path.cpp b/src/2geom/path.cpp index 981c9f044..88c7a99b9 100644 --- a/src/2geom/path.cpp +++ b/src/2geom/path.cpp @@ -203,11 +203,10 @@ Path::nearestPointPerCurve(Point const& _point) const { //return a single nearest point for each curve in this path std::vector np; - const Path& _path = *this; - for (Sequence::const_iterator it = _path.get_curves().begin() ; it != _path.get_curves().end()-1 ; ++it) + for (const_iterator it = begin() ; it != end_default(); ++it) //for (std::vector::const_iterator it = _path.begin(); it != _path.end(), ++it){ { - np.push_back((*it)->nearestPoint(_point)); + np.push_back(it->nearestPoint(_point)); } return np; } diff --git a/src/event-context.cpp b/src/event-context.cpp index ec0169573..13e7e9410 100644 --- a/src/event-context.cpp +++ b/src/event-context.cpp @@ -57,6 +57,7 @@ #include "rubberband.h" #include "selcue.h" #include "lpe-tool-context.h" +#include "ui/tool/control-point.h" static void sp_event_context_class_init(SPEventContextClass *klass); static void sp_event_context_init(SPEventContext *event_context); @@ -1238,6 +1239,7 @@ void sp_event_context_snap_delay_handler(SPEventContext *ec, SPItem* const item, gboolean sp_event_context_snap_watchdog_callback(gpointer data) { + if (!data) return FALSE; // Snap NOW! For this the "postponed" flag will be reset and the last motion event will be repeated DelayedSnapEvent *dse = reinterpret_cast(data); @@ -1276,6 +1278,11 @@ gboolean sp_event_context_snap_watchdog_callback(gpointer data) } } break; + case DelayedSnapEvent::CONTROL_POINT_HANDLER: { + using Inkscape::UI::ControlPoint; + ControlPoint *point = reinterpret_cast(dse->getKnot()); + point->_eventHandler(dse->getEvent()); + } break; default: g_warning("Origin of snap-delay event has not been defined!;"); break; diff --git a/src/event-context.h b/src/event-context.h index 5285bdb87..5be2e19fb 100644 --- a/src/event-context.h +++ b/src/event-context.h @@ -49,7 +49,8 @@ public: UNDEFINED_HANDLER = 0, EVENTCONTEXT_ROOT_HANDLER, EVENTCONTEXT_ITEM_HANDLER, - KNOT_HANDLER + KNOT_HANDLER, + CONTROL_POINT_HANDLER }; DelayedSnapEvent(SPEventContext *event_context, SPItem* const item, SPKnot* knot, GdkEventMotion const *event, DelayedSnapEvent::DelayedSnapEventOrigin const origin) diff --git a/src/node-context.cpp b/src/node-context.cpp deleted file mode 100644 index 7efa57290..000000000 --- a/src/node-context.cpp +++ /dev/null @@ -1,868 +0,0 @@ -#define __SP_NODE_CONTEXT_C__ - -/* - * Node editing context - * - * Authors: - * Lauris Kaplinski - * bulia byak - * - * This code is in public domain - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif -#include -#include -#include -#include "macros.h" -#include -#include "display/sp-canvas-util.h" -#include "object-edit.h" -#include "sp-path.h" -#include "path-chemistry.h" -#include "rubberband.h" -#include "desktop.h" -#include "desktop-handles.h" -#include "selection.h" -#include "pixmaps/cursor-node.xpm" -#include "message-context.h" -#include "node-context.h" -#include "pixmaps/cursor-node-d.xpm" -#include "preferences.h" -#include "xml/node-event-vector.h" -#include "style.h" -#include "splivarot.h" -#include "shape-editor.h" -#include "live_effects/effect.h" - -#include "sp-lpe-item.h" - -// needed for flash nodepath upon mouseover: -#include "display/canvas-bpath.h" -#include "display/curve.h" - -static void sp_node_context_class_init(SPNodeContextClass *klass); -static void sp_node_context_init(SPNodeContext *node_context); -static void sp_node_context_dispose(GObject *object); - -static void sp_node_context_setup(SPEventContext *ec); -static gint sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event); -static gint sp_node_context_item_handler(SPEventContext *event_context, - SPItem *item, GdkEvent *event); - -static SPEventContextClass *parent_class; - -GType -sp_node_context_get_type() -{ - static GType type = 0; - if (!type) { - GTypeInfo info = { - sizeof(SPNodeContextClass), - NULL, NULL, - (GClassInitFunc) sp_node_context_class_init, - NULL, NULL, - sizeof(SPNodeContext), - 4, - (GInstanceInitFunc) sp_node_context_init, - NULL, /* value_table */ - }; - type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPNodeContext", &info, (GTypeFlags)0); - } - return type; -} - -static void -sp_node_context_class_init(SPNodeContextClass *klass) -{ - GObjectClass *object_class = (GObjectClass *) klass; - SPEventContextClass *event_context_class = (SPEventContextClass *) klass; - - parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass); - - object_class->dispose = sp_node_context_dispose; - - event_context_class->setup = sp_node_context_setup; - event_context_class->root_handler = sp_node_context_root_handler; - event_context_class->item_handler = sp_node_context_item_handler; -} - -static void -sp_node_context_init(SPNodeContext *node_context) -{ - SPEventContext *event_context = SP_EVENT_CONTEXT(node_context); - - event_context->cursor_shape = cursor_node_xpm; - event_context->hot_x = 1; - event_context->hot_y = 1; - - node_context->leftalt = FALSE; - node_context->rightalt = FALSE; - node_context->leftctrl = FALSE; - node_context->rightctrl = FALSE; - - new (&node_context->sel_changed_connection) sigc::connection(); - - node_context->flash_tempitem = NULL; - node_context->flashed_item = NULL; - node_context->remove_flash_counter = 0; -} - -static void -sp_node_context_dispose(GObject *object) -{ - SPNodeContext *nc = SP_NODE_CONTEXT(object); - SPEventContext *ec = SP_EVENT_CONTEXT(object); - - ec->enableGrDrag(false); - - if (nc->grabbed) { - sp_canvas_item_ungrab(nc->grabbed, GDK_CURRENT_TIME); - nc->grabbed = NULL; - } - - nc->sel_changed_connection.disconnect(); - nc->sel_changed_connection.~connection(); - - delete ec->shape_editor; - - if (nc->_node_message_context) { - delete nc->_node_message_context; - } - - G_OBJECT_CLASS(parent_class)->dispose(object); -} - -static void -sp_node_context_setup(SPEventContext *ec) -{ - SPNodeContext *nc = SP_NODE_CONTEXT(ec); - - if (((SPEventContextClass *) parent_class)->setup) - ((SPEventContextClass *) parent_class)->setup(ec); - - Inkscape::Selection *selection = sp_desktop_selection (ec->desktop); - nc->sel_changed_connection.disconnect(); - nc->sel_changed_connection = - selection->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc)); - - SPItem *item = selection->singleItem(); - - ec->shape_editor = new ShapeEditor(ec->desktop); - - nc->rb_escaped = false; - - nc->cursor_drag = false; - - nc->added_node = false; - - nc->current_state = SP_NODE_CONTEXT_INACTIVE; - - if (item) { - ec->shape_editor->set_item(item, SH_NODEPATH); - ec->shape_editor->set_item(item, SH_KNOTHOLDER); - } - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/tools/nodes/selcue")) { - ec->enableSelectionCue(); - } - if (prefs->getBool("/tools/nodes/gradientdrag")) { - ec->enableGrDrag(); - } - - ec->desktop->emitToolSubselectionChanged(NULL); // sets the coord entry fields to inactive - - nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack()); - - ec->shape_editor->update_statusbar(); -} - -static void -sp_node_context_flash_path(SPEventContext *event_context, SPItem *item, guint timeout) { - SPNodeContext *nc = SP_NODE_CONTEXT(event_context); - - nc->remove_flash_counter = 3; // for some reason root_handler is called twice after each item_handler... - if (nc->flashed_item != item) { - // we entered a new item - nc->flashed_item = item; - SPDesktop *desktop = event_context->desktop; - if (nc->flash_tempitem) { - desktop->remove_temporary_canvasitem(nc->flash_tempitem); - nc->flash_tempitem = NULL; - } - - SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, item); - - if (canvasitem) { - nc->flash_tempitem = desktop->add_temporary_canvasitem (canvasitem, timeout); - } - } -} - -/** -\brief Callback that processes the "changed" signal on the selection; -destroys old and creates new nodepath and reassigns listeners to the new selected item's repr -*/ -void -sp_node_context_selection_changed(Inkscape::Selection *selection, gpointer data) -{ - SPEventContext *ec = SP_EVENT_CONTEXT(data); - - // TODO: update ShapeEditorsCollective instead - ec->shape_editor->unset_item(SH_NODEPATH); - ec->shape_editor->unset_item(SH_KNOTHOLDER); - SPItem *item = selection->singleItem(); - ec->shape_editor->set_item(item, SH_NODEPATH); - ec->shape_editor->set_item(item, SH_KNOTHOLDER); - ec->shape_editor->update_statusbar(); -} - -void -sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event) -{ - sp_event_show_modifier_tip - (event_context->defaultMessageContext(), event, - _("Ctrl: toggle node type, snap handle angle, move hor/vert; Ctrl+Alt: move along handles"), - _("Shift: toggle node selection, disable snapping, rotate both handles"), - _("Alt: lock handle length; Ctrl+Alt: move along handles")); -} - -static gint -sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event) -{ - gint ret = FALSE; - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - SPDesktop *desktop = event_context->desktop; - - switch (event->type) { - case GDK_MOTION_NOTIFY: - { - // find out actual item we're over, disregarding groups - SPItem *actual_item = sp_event_context_find_item (desktop, - Geom::Point(event->button.x, event->button.y), FALSE, TRUE); - if (!actual_item) - break; - - - if (prefs->getBool("/tools/nodes/pathflash_enabled")) { - if (prefs->getBool("/tools/nodes/pathflash_unselected")) { - // do not flash if we have some path selected and a single item in selection (i.e. it - // is the same path that we're editing) - SPDesktop *desktop = event_context->desktop; - ShapeEditor* se = event_context->shape_editor; - Inkscape::Selection *selection = sp_desktop_selection (desktop); - if (se->has_nodepath() && selection->singleItem()) { - break; - } - } - if (SP_IS_LPE_ITEM(actual_item)) { - Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(actual_item)); - if (lpe && (lpe->providesOwnFlashPaths() || - lpe->pathFlashType() == Inkscape::LivePathEffect::SUPPRESS_FLASH)) { - // path should be suppressed or permanent; this is handled in - // sp_node_context_selection_changed() - break; - } - } - guint timeout = prefs->getInt("/tools/nodes/pathflash_timeout", 500); - sp_node_context_flash_path(event_context, actual_item, timeout); - } - } - break; - - default: - break; - } - - if (((SPEventContextClass *) parent_class)->item_handler) - ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event); - - return ret; -} - -static gint -sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event) -{ - SPDesktop *desktop = event_context->desktop; - ShapeEditor* se = event_context->shape_editor; - Inkscape::Selection *selection = sp_desktop_selection (desktop); - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - SPNodeContext *nc = SP_NODE_CONTEXT(event_context); - double const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000); // in px - event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); // read every time, to make prefs changes really live - int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12); - double const offset = prefs->getDoubleLimited("/options/defaultscale/value", 2, 0, 1000); - - if ( (nc->flash_tempitem) && (nc->remove_flash_counter <= 0) ) { - desktop->remove_temporary_canvasitem(nc->flash_tempitem); - nc->flash_tempitem = NULL; - nc->flashed_item = NULL; // also reset this one, so the next time the same object is hovered over it shows again the highlight - } else { - nc->remove_flash_counter--; - } - - gint ret = FALSE; - switch (event->type) { - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !event_context->space_panning) { - // save drag origin - event_context->xp = (gint) event->button.x; - event_context->yp = (gint) event->button.y; - event_context->within_tolerance = true; - se->cancel_hit(); - - if (!(event->button.state & GDK_SHIFT_MASK)) { - if (!nc->drag) { - if (se->has_nodepath() && selection->single() /* && item_over */) { - // save drag origin - bool over_stroke = se->is_over_stroke(Geom::Point(event->button.x, event->button.y), true); - //only dragging curves - if (over_stroke) { - ret = TRUE; - break; - } - } - } - } - Geom::Point const button_w(event->button.x, - event->button.y); - Geom::Point const button_dt(desktop->w2d(button_w)); - Inkscape::Rubberband::get(desktop)->start(desktop, button_dt); - - if (nc->grabbed) { - sp_canvas_item_ungrab(nc->grabbed, event->button.time); - nc->grabbed = NULL; - } - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK, - NULL, event->button.time); - nc->grabbed = SP_CANVAS_ITEM(desktop->acetate); - - nc->current_state = SP_NODE_CONTEXT_INACTIVE; - desktop->updateNow(); - ret = TRUE; - } - break; - case GDK_MOTION_NOTIFY: - if (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 - } - - // The path went away while dragging; throw away any further motion - // events until the mouse pointer is released. - - if (se->hits_curve() && !se->has_nodepath()) { - 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) - event_context->within_tolerance = false; - - // Once we determine what the user is doing (dragging either a node or the - // selection rubberband), make sure we continue to perform that operation - // until the mouse pointer is lifted. - if (nc->current_state == SP_NODE_CONTEXT_INACTIVE) { - if (se->hits_curve() && se->has_nodepath()) { - nc->current_state = SP_NODE_CONTEXT_NODE_DRAGGING; - } else { - nc->current_state = SP_NODE_CONTEXT_RUBBERBAND_DRAGGING; - } - } - - switch (nc->current_state) { - case SP_NODE_CONTEXT_NODE_DRAGGING: - { - se->curve_drag (event->motion.x, event->motion.y); - - gobble_motion_events(GDK_BUTTON1_MASK); - break; - } - case SP_NODE_CONTEXT_RUBBERBAND_DRAGGING: - if (Inkscape::Rubberband::get(desktop)->is_started()) { - Geom::Point const motion_w(event->motion.x, - event->motion.y); - Geom::Point const motion_dt(desktop->w2d(motion_w)); - Inkscape::Rubberband::get(desktop)->move(motion_dt); - } - break; - } - - nc->drag = TRUE; - ret = TRUE; - } else { - if (!se->has_nodepath() || selection->singleItem() == NULL) { - break; - } - - bool over_stroke = false; - over_stroke = se->is_over_stroke(Geom::Point(event->motion.x, event->motion.y), false); - - if (nc->cursor_drag && !over_stroke) { - event_context->cursor_shape = cursor_node_xpm; - event_context->hot_x = 1; - event_context->hot_y = 1; - sp_event_context_update_cursor(event_context); - nc->cursor_drag = false; - } else if (!nc->cursor_drag && over_stroke) { - event_context->cursor_shape = cursor_node_d_xpm; - event_context->hot_x = 1; - event_context->hot_y = 1; - sp_event_context_update_cursor(event_context); - nc->cursor_drag = true; - } - } - break; - - case GDK_2BUTTON_PRESS: - case GDK_BUTTON_RELEASE: - if ( (event->button.button == 1) && (!nc->drag) && !event_context->space_panning) { - // find out clicked item, disregarding groups, honoring Alt - SPItem *item_clicked = sp_event_context_find_item (desktop, - Geom::Point(event->button.x, event->button.y), - (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE); - - event_context->xp = event_context->yp = 0; - - bool over_stroke = false; - if (se->has_nodepath()) { - over_stroke = se->is_over_stroke(Geom::Point(event->button.x, event->button.y), false); - } - - if (item_clicked || over_stroke) { - if (over_stroke || nc->added_node) { - switch (event->type) { - case GDK_BUTTON_RELEASE: - if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) { - //add a node - se->add_node_near_point(); - } else { - if (nc->added_node) { // we just received double click, ignore release - nc->added_node = false; - break; - } - //select the segment - if (event->button.state & GDK_SHIFT_MASK) { - se->select_segment_near_point(true); - } else { - se->select_segment_near_point(false); - } - desktop->updateNow(); - } - break; - case GDK_2BUTTON_PRESS: - //add a node - se->add_node_near_point(); - nc->added_node = true; - break; - default: - break; - } - } else if (event->button.state & GDK_SHIFT_MASK) { - selection->toggle(item_clicked); - desktop->updateNow(); - } else { - selection->set(item_clicked); - desktop->updateNow(); - } - Inkscape::Rubberband::get(desktop)->stop(); - if (nc->grabbed) { - sp_canvas_item_ungrab(nc->grabbed, event->button.time); - nc->grabbed = NULL; - } - ret = TRUE; - break; - } - } - if (event->type == GDK_BUTTON_RELEASE) { - event_context->xp = event_context->yp = 0; - if (event->button.button == 1) { - Geom::OptRect b = Inkscape::Rubberband::get(desktop)->getRectangle(); - - if (se->hits_curve() && !event_context->within_tolerance) { //drag curve - se->finish_drag(); - } else if (b && !event_context->within_tolerance) { // drag to select - se->select_rect(*b, event->button.state & GDK_SHIFT_MASK); - } else { - if (!(nc->rb_escaped)) { // unless something was canceled - if (se->has_selection()) - se->deselect(); - else - sp_desktop_selection(desktop)->clear(); - } - } - ret = TRUE; - Inkscape::Rubberband::get(desktop)->stop(); - - if (nc->grabbed) { - sp_canvas_item_ungrab(nc->grabbed, event->button.time); - nc->grabbed = NULL; - } - - desktop->updateNow(); - nc->rb_escaped = false; - nc->drag = FALSE; - se->cancel_hit(); - nc->current_state = SP_NODE_CONTEXT_INACTIVE; - } - } - break; - case GDK_KEY_PRESS: - switch (get_group0_keyval(&event->key)) { - case GDK_Insert: - case GDK_KP_Insert: - // with any modifiers - se->add_node(); - ret = TRUE; - break; - case GDK_I: - case GDK_i: - // apple keyboards have no Insert - if (MOD__SHIFT_ONLY) { - se->add_node(); - ret = TRUE; - } - break; - case GDK_Delete: - case GDK_KP_Delete: - case GDK_BackSpace: - if (MOD__CTRL_ONLY) { - se->delete_nodes(); - } else { - se->delete_nodes_preserving_shape(); - } - ret = TRUE; - break; - case GDK_C: - case GDK_c: - if (MOD__SHIFT_ONLY) { - se->set_node_type(Inkscape::NodePath::NODE_CUSP); - ret = TRUE; - } - break; - case GDK_S: - case GDK_s: - if (MOD__SHIFT_ONLY) { - se->set_node_type(Inkscape::NodePath::NODE_SMOOTH); - ret = TRUE; - } - break; - case GDK_A: - case GDK_a: - if (MOD__SHIFT_ONLY) { - se->set_node_type(Inkscape::NodePath::NODE_AUTO); - ret = TRUE; - } - break; - case GDK_Y: - case GDK_y: - if (MOD__SHIFT_ONLY) { - se->set_node_type(Inkscape::NodePath::NODE_SYMM); - ret = TRUE; - } - break; - case GDK_B: - case GDK_b: - if (MOD__SHIFT_ONLY) { - se->break_at_nodes(); - ret = TRUE; - } - break; - case GDK_J: - case GDK_j: - if (MOD__SHIFT_ONLY) { - se->join_nodes(); - ret = TRUE; - } - break; - case GDK_D: - case GDK_d: - if (MOD__SHIFT_ONLY) { - se->duplicate_nodes(); - ret = TRUE; - } - break; - case GDK_L: - case GDK_l: - if (MOD__SHIFT_ONLY) { - se->set_type_of_segments(NR_LINETO); - ret = TRUE; - } - break; - case GDK_U: - case GDK_u: - if (MOD__SHIFT_ONLY) { - se->set_type_of_segments(NR_CURVETO); - ret = TRUE; - } - break; - case GDK_R: - case GDK_r: - if (MOD__SHIFT_ONLY) { - // FIXME: add top panel button - sp_selected_path_reverse(desktop); - ret = TRUE; - } - break; - case GDK_x: - case GDK_X: - if (MOD__ALT_ONLY) { - desktop->setToolboxFocusTo ("altx-nodes"); - ret = TRUE; - } - break; - case GDK_Left: // move selection 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) se->move_nodes_screen(desktop, mul*-10, 0); // shift - else se->move_nodes_screen(desktop, mul*-1, 0); // no shift - } - else { // no alt - if (MOD__SHIFT) se->move_nodes(mul*-10*nudge, 0); // shift - else se->move_nodes(mul*-nudge, 0); // no shift - } - ret = TRUE; - } - break; - case GDK_Up: // move selection 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) se->move_nodes_screen(desktop, 0, mul*10); // shift - else se->move_nodes_screen(desktop, 0, mul*1); // no shift - } - else { // no alt - if (MOD__SHIFT) se->move_nodes(0, mul*10*nudge); // shift - else se->move_nodes(0, mul*nudge); // no shift - } - ret = TRUE; - } - break; - case GDK_Right: // move selection 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) se->move_nodes_screen(desktop, mul*10, 0); // shift - else se->move_nodes_screen(desktop, mul*1, 0); // no shift - } - else { // no alt - if (MOD__SHIFT) se->move_nodes(mul*10*nudge, 0); // shift - else se->move_nodes(mul*nudge, 0); // no shift - } - ret = TRUE; - } - break; - case GDK_Down: // move selection 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) se->move_nodes_screen(desktop, 0, mul*-10); // shift - else se->move_nodes_screen(desktop, 0, mul*-1); // no shift - } - else { // no alt - if (MOD__SHIFT) se->move_nodes(0, mul*-10*nudge); // shift - else se->move_nodes(0, mul*-nudge); // no shift - } - ret = TRUE; - } - break; - case GDK_Escape: - { - Geom::OptRect const b = Inkscape::Rubberband::get(desktop)->getRectangle(); - if (b) { - Inkscape::Rubberband::get(desktop)->stop(); - nc->current_state = SP_NODE_CONTEXT_INACTIVE; - nc->rb_escaped = true; - } else { - if (se->has_selection()) { - se->deselect(); - } else { - sp_desktop_selection(desktop)->clear(); - } - } - ret = TRUE; - break; - } - - case GDK_bracketleft: - if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) { - if (nc->leftctrl) - se->rotate_nodes (M_PI/snaps, -1, false); - if (nc->rightctrl) - se->rotate_nodes (M_PI/snaps, 1, false); - } else if ( MOD__ALT && !MOD__CTRL ) { - if (nc->leftalt && nc->rightalt) - se->rotate_nodes (1, 0, true); - else { - if (nc->leftalt) - se->rotate_nodes (1, -1, true); - if (nc->rightalt) - se->rotate_nodes (1, 1, true); - } - } else if ( snaps != 0 ) { - se->rotate_nodes (M_PI/snaps, 0, false); - } - ret = TRUE; - break; - case GDK_bracketright: - if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) { - if (nc->leftctrl) - se->rotate_nodes (-M_PI/snaps, -1, false); - if (nc->rightctrl) - se->rotate_nodes (-M_PI/snaps, 1, false); - } else if ( MOD__ALT && !MOD__CTRL ) { - if (nc->leftalt && nc->rightalt) - se->rotate_nodes (-1, 0, true); - else { - if (nc->leftalt) - se->rotate_nodes (-1, -1, true); - if (nc->rightalt) - se->rotate_nodes (-1, 1, true); - } - } else if ( snaps != 0 ) { - se->rotate_nodes (-M_PI/snaps, 0, false); - } - ret = TRUE; - break; - case GDK_less: - case GDK_comma: - if (MOD__CTRL) { - if (nc->leftctrl) - se->scale_nodes(-offset, -1); - if (nc->rightctrl) - se->scale_nodes(-offset, 1); - } else if (MOD__ALT) { - if (nc->leftalt && nc->rightalt) - se->scale_nodes_screen (-1, 0); - else { - if (nc->leftalt) - se->scale_nodes_screen (-1, -1); - if (nc->rightalt) - se->scale_nodes_screen (-1, 1); - } - } else { - se->scale_nodes (-offset, 0); - } - ret = TRUE; - break; - case GDK_greater: - case GDK_period: - if (MOD__CTRL) { - if (nc->leftctrl) - se->scale_nodes (offset, -1); - if (nc->rightctrl) - se->scale_nodes (offset, 1); - } else if (MOD__ALT) { - if (nc->leftalt && nc->rightalt) - se->scale_nodes_screen (1, 0); - else { - if (nc->leftalt) - se->scale_nodes_screen (1, -1); - if (nc->rightalt) - se->scale_nodes_screen (1, 1); - } - } else { - se->scale_nodes (offset, 0); - } - ret = TRUE; - break; - - case GDK_Alt_L: - nc->leftalt = TRUE; - sp_node_context_show_modifier_tip(event_context, event); - break; - case GDK_Alt_R: - nc->rightalt = TRUE; - sp_node_context_show_modifier_tip(event_context, event); - break; - case GDK_Control_L: - nc->leftctrl = TRUE; - sp_node_context_show_modifier_tip(event_context, event); - break; - case GDK_Control_R: - nc->rightctrl = TRUE; - sp_node_context_show_modifier_tip(event_context, event); - break; - case GDK_Shift_L: - case GDK_Shift_R: - case GDK_Meta_L: - case GDK_Meta_R: - sp_node_context_show_modifier_tip(event_context, event); - break; - default: - ret = node_key(event); - break; - } - break; - case GDK_KEY_RELEASE: - switch (get_group0_keyval(&event->key)) { - case GDK_Alt_L: - nc->leftalt = FALSE; - event_context->defaultMessageContext()->clear(); - break; - case GDK_Alt_R: - nc->rightalt = FALSE; - event_context->defaultMessageContext()->clear(); - break; - case GDK_Control_L: - nc->leftctrl = FALSE; - event_context->defaultMessageContext()->clear(); - break; - case GDK_Control_R: - nc->rightctrl = FALSE; - event_context->defaultMessageContext()->clear(); - break; - case GDK_Shift_L: - case GDK_Shift_R: - case GDK_Meta_L: - case GDK_Meta_R: - event_context->defaultMessageContext()->clear(); - break; - } - break; - default: - break; - } - - if (!ret) { - if (((SPEventContextClass *) parent_class)->root_handler) - ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event); - } - - return ret; -} - - -/* - 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 : diff --git a/src/node-context.h b/src/node-context.h deleted file mode 100644 index 2345ffc7e..000000000 --- a/src/node-context.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef __SP_NODE_CONTEXT_H__ -#define __SP_NODE_CONTEXT_H__ - -/* - * Node editing context - * - * Authors: - * Lauris Kaplinski - * bulia byak - * - * This code is in public domain - */ - -#include -#include -#include "event-context.h" -#include "forward.h" -#include "display/display-forward.h" -#include "nodepath.h" -namespace Inkscape { class Selection; } - -#define SP_TYPE_NODE_CONTEXT (sp_node_context_get_type ()) -#define SP_NODE_CONTEXT(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_NODE_CONTEXT, SPNodeContext)) -#define SP_NODE_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_NODE_CONTEXT, SPNodeContextClass)) -#define SP_IS_NODE_CONTEXT(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_NODE_CONTEXT)) -#define SP_IS_NODE_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_NODE_CONTEXT)) - -enum { SP_NODE_CONTEXT_INACTIVE, - SP_NODE_CONTEXT_NODE_DRAGGING, - SP_NODE_CONTEXT_RUBBERBAND_DRAGGING }; - -class SPNodeContext; -class SPNodeContextClass; - -struct SPNodeContext { - // FIXME: shouldn't this be a pointer??? - SPEventContext event_context; - - guint drag : 1; - - gboolean leftalt; - gboolean rightalt; - gboolean leftctrl; - gboolean rightctrl; - - /// If true, rubberband was cancelled by esc, so the next button release should not deselect. - bool rb_escaped; - - sigc::connection sel_changed_connection; - - Inkscape::MessageContext *_node_message_context; - - bool cursor_drag; - - bool added_node; - - unsigned int current_state; - - SPItem * flashed_item; - SPCanvasItem *grabbed; - Inkscape::Display::TemporaryItem * flash_tempitem; - int remove_flash_counter; -}; - -struct SPNodeContextClass { - SPEventContextClass parent_class; -}; - -/* Standard Gtk function */ - -GtkType sp_node_context_get_type (void); - -void sp_node_context_selection_changed (Inkscape::Selection * selection, gpointer data); -void sp_node_context_selection_modified (Inkscape::Selection * selection, guint flags, gpointer data); - -#endif - -/* - 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 : diff --git a/src/snap.cpp b/src/snap.cpp index 545607889..558f61814 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -30,6 +30,7 @@ #include "inkscape.h" #include "desktop.h" +#include "selection.h" #include "sp-guide.h" #include "preferences.h" #include "event-context.h" @@ -206,29 +207,15 @@ Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::SnapPreferences::PointTyp bool first_point, Geom::OptRect const &bbox_to_snap) const { - if (!someSnapperMightSnap()) { + if (!someSnapperMightSnap()) { return Inkscape::SnappedPoint(p, source_type, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false); } - std::vector *items_to_ignore; - if (_item_to_ignore) { // If we have only a single item to ignore - // then build a list containing this single item; - // This single-item list will prevail over any other _items_to_ignore list, should that exist - items_to_ignore = new std::vector; - items_to_ignore->push_back(_item_to_ignore); - } else { - items_to_ignore = _items_to_ignore; - } - SnappedConstraints sc; SnapperList const snappers = getSnappers(); for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) { - (*i)->freeSnap(sc, point_type, p, source_type, first_point, bbox_to_snap, items_to_ignore, _unselected_nodes); - } - - if (_item_to_ignore) { - delete items_to_ignore; + (*i)->freeSnap(sc, point_type, p, source_type, first_point, bbox_to_snap, &_items_to_ignore, _unselected_nodes); } return findBestSnap(p, source_type, sc, false); @@ -368,17 +355,6 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::P return Inkscape::SnappedPoint(p, source_type, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false); } - std::vector *items_to_ignore; - if (_item_to_ignore) { // If we have only a single item to ignore - // then build a list containing this single item; - // This single-item list will prevail over any other _items_to_ignore list, should that exist - items_to_ignore = new std::vector; - items_to_ignore->push_back(_item_to_ignore); - } else { - items_to_ignore = _items_to_ignore; - } - - // First project the mouse pointer onto the constraint Geom::Point pp = constraint.projection(p); // Then try to snap the projected point @@ -386,11 +362,7 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::P SnappedConstraints sc; SnapperList const snappers = getSnappers(); for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) { - (*i)->constrainedSnap(sc, point_type, pp, source_type, first_point, bbox_to_snap, constraint, items_to_ignore); - } - - if (_item_to_ignore) { - delete items_to_ignore; + (*i)->constrainedSnap(sc, point_type, pp, source_type, first_point, bbox_to_snap, constraint, &_items_to_ignore); } return findBestSnap(pp, source_type, sc, true); @@ -1033,22 +1005,7 @@ Inkscape::SnappedPoint SnapManager::findBestSnap(Geom::Point const &p, return bestSnappedPoint; } -/** - * \brief Prepare the snap manager for the actual snapping, which includes building a list of snap targets - * to ignore and toggling the snap indicator - * - * There are two overloaded setup() methods, of which this one only allows for a single item to be ignored - * whereas the other one will take a list of items to ignore - * - * \param desktop Reference to the desktop to which this snap manager is attached - * \param snapindicator If true then a snap indicator will be displayed automatically (when enabled in the preferences) - * \param item_to_ignore This item will not be snapped to, e.g. the item that is currently being dragged. This avoids "self-snapping" - * \param unselected_nodes Stationary nodes of the path that is currently being edited in the node tool and - * that can be snapped too. Nodes not in this list will not be snapped to, to avoid "self-snapping". Of each - * unselected node both the position (Geom::Point) and the type (Inkscape::SnapTargetType) will be stored - * \param guide_to_ignore Guide that is currently being dragged and should not be snapped to - */ - +/// Convenience shortcut when there is only one item to ignore void SnapManager::setup(SPDesktop const *desktop, bool snapindicator, SPItem const *item_to_ignore, @@ -1056,8 +1013,8 @@ void SnapManager::setup(SPDesktop const *desktop, SPGuide *guide_to_ignore) { g_assert(desktop != NULL); - _item_to_ignore = item_to_ignore; - _items_to_ignore = NULL; + _items_to_ignore.clear(); + _items_to_ignore.push_back(item_to_ignore); _desktop = desktop; _snapindicator = snapindicator; _unselected_nodes = unselected_nodes; @@ -1082,17 +1039,35 @@ void SnapManager::setup(SPDesktop const *desktop, void SnapManager::setup(SPDesktop const *desktop, bool snapindicator, - std::vector &items_to_ignore, + std::vector const &items_to_ignore, std::vector > *unselected_nodes, SPGuide *guide_to_ignore) { g_assert(desktop != NULL); - _item_to_ignore = NULL; - _items_to_ignore = &items_to_ignore; + _items_to_ignore = items_to_ignore; + _desktop = desktop; + _snapindicator = snapindicator; + _unselected_nodes = unselected_nodes; + _guide_to_ignore = guide_to_ignore; +} + +/// Setup, taking the list of items to ignore from the desktop's selection. +void SnapManager::setupIgnoreSelection(SPDesktop const *desktop, + bool snapindicator, + std::vector > *unselected_nodes, + SPGuide *guide_to_ignore) +{ _desktop = desktop; _snapindicator = snapindicator; _unselected_nodes = unselected_nodes; _guide_to_ignore = guide_to_ignore; + _items_to_ignore.clear(); + + Inkscape::Selection *sel = _desktop->selection; + GSList const *items = sel->itemList(); + for (GSList *i = const_cast(items); i; i = i->next) { + _items_to_ignore.push_back(static_cast(i->data)); + } } SPDocument *SnapManager::getDocument() const diff --git a/src/snap.h b/src/snap.h index e621bdb60..5696dcd53 100644 --- a/src/snap.h +++ b/src/snap.h @@ -1,17 +1,7 @@ -#ifndef SEEN_SNAP_H -#define SEEN_SNAP_H - /** * \file snap.h - * \brief SnapManager class. - * - * The SnapManager class handles most (if not all) of the interfacing of the snapping mechanisms with the - * other parts of the code base. It stores the references to the various types of snappers for grid, guides - * and objects, and it stores most of the snapping preferences. Besides that it provides methods to setup - * the snapping environment (e.g. keeps a list of the items to ignore when looking for snap target candidates, - * and toggling of the snap indicator), and it provides many different methods for the snapping itself (free - * snapping vs. constrained snapping, returning the result by reference or through a return statement, etc.) - * + * \brief Per-desktop object that handles snapping queries + *//* * Authors: * Lauris Kaplinski * Frank Felfe @@ -25,8 +15,10 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ -#include +#ifndef SEEN_SNAP_H +#define SEEN_SNAP_H +#include #include "guide-snapper.h" #include "object-snapper.h" #include "snap-preferences.h" @@ -42,11 +34,33 @@ enum SPGuideDragType { // used both here and in desktop-events.cpp class SPNamedView; /// Class to coordinate snapping operations - /** - * Each SPNamedView has one of these. It offers methods to snap points to whatever - * snappers are defined (e.g. grid, guides etc.). It also allows callers to snap - * points which have undergone some transformation (e.g. translation, scaling etc.) + * The SnapManager class handles most (if not all) of the interfacing of the snapping mechanisms + * with the other parts of the code base. It stores the references to the various types of snappers + * for grid, guides and objects, and it stores most of the snapping preferences. Besides that + * it provides methods to setup the snapping environment (e.g. keeps a list of the items to ignore + * when looking for snap target candidates, and toggling of the snap indicator), and it provides + * many different methods for snapping queries (free snapping vs. constrained snapping, + * returning the result by reference or through a return statement, etc.) + * + * Each SPNamedView has one of these. It offers methods to snap points to whatever + * snappers are defined (e.g. grid, guides etc.). It also allows callers to snap + * points which have undergone some transformation (e.g. translation, scaling etc.) + * + * \par How snapping is implemented in Inkscape + * \par + * The snapping system consists of two key elements. The first one is the snap manager + * (this class), which keeps some data about objects in the document and answers queries + * of the type "given this point and type of transformation, what is the best place + * to snap to?". + * + * The second is in event-context.cpp and implements the snapping timeout. Whenever a motion + * events happens over the canvas, it stores it for later use and initiates a timeout. + * This timeout is discarded whenever a new motion event occurs. When the timeout expires, + * a global flag in SnapManager, accessed via getSnapPostponedGlobally(), is set to true + * and the stored event is replayed, but this time with snapping enabled. This way you can + * write snapping code directly in your control point's dragged handler as if there was + * no timeout. */ class SnapManager @@ -74,9 +88,13 @@ public: void setup(SPDesktop const *desktop, bool snapindicator, - std::vector &items_to_ignore, + std::vector const &items_to_ignore, std::vector > *unselected_nodes = NULL, SPGuide *guide_to_ignore = NULL); + void setupIgnoreSelection(SPDesktop const *desktop, + bool snapindicator = true, + std::vector > *unselected_nodes = NULL, + SPGuide *guide_to_ignore = NULL); // freeSnapReturnByRef() is preferred over freeSnap(), because it only returns a // point if snapping has occurred (by overwriting p); otherwise p is untouched @@ -171,8 +189,7 @@ protected: SPNamedView const *_named_view; private: - std::vector *_items_to_ignore; ///< Items that should not be snapped to, for example the items that are currently being dragged. Set using the setup() method - SPItem const *_item_to_ignore; ///< Single item that should not be snapped to. If not NULL then this takes precedence over _items_to_ignore. Set using the setup() method + std::vector _items_to_ignore; ///< Items that should not be snapped to, for example the items that are currently being dragged. Set using the setup() method SPGuide *_guide_to_ignore; ///< A guide that should not be snapped to, e.g. the guide that is currently being dragged SPDesktop const *_desktop; bool _snapindicator; ///< When true, an indicator will be drawn at the position that was being snapped to diff --git a/src/snapper.h b/src/snapper.h index 110b3d36a..1801f309c 100644 --- a/src/snapper.h +++ b/src/snapper.h @@ -71,6 +71,7 @@ public: public: ConstraintLine(Geom::Point const &d) : _has_point(false), _direction(d) {} ConstraintLine(Geom::Point const &p, Geom::Point const &d) : _has_point(true), _point(p), _direction(d) {} + ConstraintLine(Geom::Line const &l) : _has_point(true), _point(l.origin()), _direction(l.versor()) {} bool hasPoint() const { return _has_point; diff --git a/src/ui/tool/control-point-selection.cpp b/src/ui/tool/control-point-selection.cpp index d10045c62..5a84592b6 100644 --- a/src/ui/tool/control-point-selection.cpp +++ b/src/ui/tool/control-point-selection.cpp @@ -140,7 +140,60 @@ void ControlPointSelection::clear() erase(i++); } -/** Transform all selected control points by the supplied affine transformation. */ +/** Select all points that this selection can contain. */ +void ControlPointSelection::selectAll() +{ + for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) { + insert(*i); + } +} +/** Select all points inside the given rectangle (in desktop coordinates). */ +void ControlPointSelection::selectArea(Geom::Rect const &r) +{ + for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) { + if (r.contains(**i)) + insert(*i); + } +} +/** Unselect all selected points and select all unselected points. */ +void ControlPointSelection::invertSelection() +{ + for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) { + if ((*i)->selected()) erase(*i); + else insert(*i); + } +} +void ControlPointSelection::spatialGrow(SelectableControlPoint *origin, int dir) +{ + bool grow = (dir > 0); + Geom::Point p = origin->position(); + double best_dist = grow ? HUGE_VAL : 0; + SelectableControlPoint *match = NULL; + for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) { + bool selected = (*i)->selected(); + if (grow && !selected) { + double dist = Geom::distance((*i)->position(), p); + if (dist < best_dist) { + best_dist = dist; + match = *i; + } + } + if (!grow && selected) { + double dist = Geom::distance((*i)->position(), p); + // use >= to also deselect the origin node when it's the last one selected + if (dist >= best_dist) { + best_dist = dist; + match = *i; + } + } + } + if (match) { + if (grow) insert(match); + else erase(match); + } +} + +/** Transform all selected control points by the given affine transformation. */ void ControlPointSelection::transform(Geom::Matrix const &m) { for (iterator i = _points.begin(); i != _points.end(); ++i) { diff --git a/src/ui/tool/control-point-selection.h b/src/ui/tool/control-point-selection.h index 0f0daffaa..38df5c7e5 100644 --- a/src/ui/tool/control-point-selection.h +++ b/src/ui/tool/control-point-selection.h @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -43,12 +44,12 @@ public: typedef std::list connlist_type; typedef std::unordered_map< SelectableControlPoint *, boost::shared_ptr > map_type; + typedef std::unordered_set< SelectableControlPoint * > set_type; + typedef set_type Set; // convenience alias - // boilerplate typedefs typedef map_type::iterator iterator; typedef map_type::const_iterator const_iterator; typedef map_type::size_type size_type; - typedef SelectableControlPoint *value_type; typedef SelectableControlPoint *key_type; @@ -80,6 +81,15 @@ public: // find iterator find(const key_type &k) { return _points.find(k); } + // Sometimes it is very useful to keep a list of all selectable points. + set_type const &allPoints() const { return _all_points; } + set_type &allPoints() { return _all_points; } + // ...for example in these methods. Another useful case is snapping. + void selectAll(); + void selectArea(Geom::Rect const &); + void invertSelection(); + void spatialGrow(SelectableControlPoint *origin, int dir); + virtual bool event(GdkEvent *); void transform(Geom::Matrix const &m); @@ -113,6 +123,7 @@ private: void _keyboardTransform(Geom::Matrix const &); void _commitTransform(CommitEvent ce); map_type _points; + set_type _all_points; boost::optional _rot_radius; TransformHandleSet *_handles; SelectableControlPoint *_grabbed_point; diff --git a/src/ui/tool/control-point.cpp b/src/ui/tool/control-point.cpp index 74dd6e31c..0d076a5ab 100644 --- a/src/ui/tool/control-point.cpp +++ b/src/ui/tool/control-point.cpp @@ -12,13 +12,14 @@ #include #include #include <2geom/point.h> -#include "ui/tool/control-point.h" -#include "ui/tool/event-utils.h" -#include "preferences.h" #include "desktop.h" #include "desktop-handles.h" +#include "display/snap-indicator.h" #include "event-context.h" #include "message-context.h" +#include "preferences.h" +#include "ui/tool/control-point.h" +#include "ui/tool/event-utils.h" namespace Inkscape { namespace UI { @@ -397,6 +398,7 @@ bool ControlPoint::_eventHandler(GdkEvent *event) case GDK_MOTION_NOTIFY: if (held_button<1>(event->motion) && !_desktop->event_context->space_panning) { + _desktop->snapindicator->remove_snaptarget(); bool transferred = false; if (!_drag_initiated) { bool t = fabs(event->motion.x - _drag_event_origin[Geom::X]) <= drag_tolerance && @@ -414,48 +416,57 @@ bool ControlPoint::_eventHandler(GdkEvent *event) _drag_initiated = true; } } - if (transferred) return true; - // the point was moved beyond the drag tolerance - Geom::Point new_pos = _desktop->w2d(event_point(event->motion)) + pointer_offset; - - // the new position is passed by reference and can be changed in the handlers. - signal_dragged.emit(_position, new_pos, &event->motion); - move(new_pos); - _updateDragTip(&event->motion); // update dragging tip after moving to new position - - _desktop->scroll_to_point(new_pos); - _desktop->set_coordinate_status(_position); + if (!transferred) { + // dragging in progress + Geom::Point new_pos = _desktop->w2d(event_point(event->motion)) + pointer_offset; + + // the new position is passed by reference and can be changed in the handlers. + signal_dragged.emit(_position, new_pos, &event->motion); + move(new_pos); + _updateDragTip(&event->motion); // update dragging tip after moving to new position + + _desktop->scroll_to_point(new_pos); + _desktop->set_coordinate_status(_position); + sp_event_context_snap_delay_handler(_desktop->event_context, NULL, + reinterpret_cast(this), &event->motion, + DelayedSnapEvent::CONTROL_POINT_HANDLER); + } return true; } break; case GDK_BUTTON_RELEASE: - if (_event_grab) { - sp_canvas_item_ungrab(_canvas_item, event->button.time); - _setMouseover(this, event->button.state); - _event_grab = false; + if (!_event_grab) break; - if (_drag_initiated) { - sp_canvas_end_forced_full_redraws(_desktop->canvas); - } + // TODO I think this "feature" is wrong. + // sp_event_context_snap_watchdog_callback(_desktop->event_context->_delayed_snap_event); + sp_event_context_discard_delayed_snap_event(_desktop->event_context); + _desktop->snapindicator->remove_snaptarget(); + + sp_canvas_item_ungrab(_canvas_item, event->button.time); + _setMouseover(this, event->button.state); + _event_grab = false; - if (event->button.button == next_release_doubleclick) { + if (_drag_initiated) { + sp_canvas_end_forced_full_redraws(_desktop->canvas); + } + + if (event->button.button == next_release_doubleclick) { + _drag_initiated = false; + return signal_doubleclicked.emit(&event->button); + } + if (event->button.button == 1) { + if (_drag_initiated) { + // it is the end of a drag + signal_ungrabbed.emit(&event->button); _drag_initiated = false; - return signal_doubleclicked.emit(&event->button); + return true; + } else { + // it is the end of a click + return signal_clicked.emit(&event->button); } - if (event->button.button == 1) { - if (_drag_initiated) { - // it is the end of a drag - signal_ungrabbed.emit(&event->button); - _drag_initiated = false; - return true; - } else { - // it is the end of a click - return signal_clicked.emit(&event->button); - } - } - _drag_initiated = false; } + _drag_initiated = false; break; case GDK_ENTER_NOTIFY: diff --git a/src/ui/tool/control-point.h b/src/ui/tool/control-point.h index c4b0a42be..4997c5ef4 100644 --- a/src/ui/tool/control-point.h +++ b/src/ui/tool/control-point.h @@ -90,6 +90,9 @@ public: static sigc::signal signal_mouseover_change; static Glib::ustring format_tip(char const *format, ...) G_GNUC_PRINTF(1,2); + // temporarily public, until snapping is refactored a little + virtual bool _eventHandler(GdkEvent *event); + protected: ControlPoint(SPDesktop *d, Geom::Point const &initial_pos, Gtk::AnchorType anchor, SPCtrlShapeType shape, unsigned int size, ColorSet *cset = 0, SPCanvasGroup *group = 0); @@ -112,14 +115,13 @@ protected: void _setPixbuf(Glib::RefPtr); /// @} - virtual bool _eventHandler(GdkEvent *event); virtual Glib::ustring _getTip(unsigned state) { return ""; } virtual Glib::ustring _getDragTip(GdkEventMotion *event) { return ""; } virtual bool _hasDragTips() { return false; } SPDesktop *const _desktop; ///< The desktop this control point resides on. SPCanvasItem * _canvas_item; ///< Visual representation of the control point. - ColorSet *_cset; ///< Describes the colors used to represent the point + ColorSet *_cset; ///< Colors used to represent the point State _state; static int const _grab_event_mask; diff --git a/src/ui/tool/multi-path-manipulator.cpp b/src/ui/tool/multi-path-manipulator.cpp index ac0165e1a..33d96c706 100644 --- a/src/ui/tool/multi-path-manipulator.cpp +++ b/src/ui/tool/multi-path-manipulator.cpp @@ -181,71 +181,21 @@ void MultiPathManipulator::setItems(std::set const &s) void MultiPathManipulator::selectSubpaths() { if (_selection.empty()) { - invokeForAll(&PathManipulator::selectAll); + _selection.selectAll(); } else { invokeForAll(&PathManipulator::selectSubpaths); } } -void MultiPathManipulator::selectAll() -{ - invokeForAll(&PathManipulator::selectAll); -} - -void MultiPathManipulator::selectArea(Geom::Rect const &area, bool take) -{ - if (take) _selection.clear(); - invokeForAll(&PathManipulator::selectArea, area); -} void MultiPathManipulator::shiftSelection(int dir) { invokeForAll(&PathManipulator::shiftSelection, dir); } -void MultiPathManipulator::spatialGrow(NodeList::iterator origin, int dir) -{ - double extr_dist = dir > 0 ? HUGE_VAL : -HUGE_VAL; - NodeList::iterator target; - do { // this substitutes for goto - if ((dir > 0 && !origin->selected())) { - target = origin; - break; - } - - bool closest = dir > 0; // when growing, find closest node - bool selected = dir < 0; // when growing, consider only unselected nodes - - for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) { - NodeList::iterator t = i->second->extremeNode(origin, selected, !selected, closest); - if (!t) continue; - double dist = Geom::distance(*t, *origin); - bool cond = closest ? (dist < extr_dist) : (dist > extr_dist); - if (cond) { - extr_dist = dist; - target = t; - } - } - } while (0); - - if (!target) return; - if (dir > 0) { - _selection.insert(target.ptr()); - } else { - _selection.erase(target.ptr()); - } -} -void MultiPathManipulator::invertSelection() -{ - invokeForAll(&PathManipulator::invertSelection); -} void MultiPathManipulator::invertSelectionInSubpaths() { invokeForAll(&PathManipulator::invertSelectionInSubpaths); } -void MultiPathManipulator::deselect() -{ - _selection.clear(); -} void MultiPathManipulator::setNodeType(NodeType type) { diff --git a/src/ui/tool/multi-path-manipulator.h b/src/ui/tool/multi-path-manipulator.h index 4fbbf1b05..46ad3a8d2 100644 --- a/src/ui/tool/multi-path-manipulator.h +++ b/src/ui/tool/multi-path-manipulator.h @@ -45,13 +45,8 @@ public: void cleanup(); void selectSubpaths(); - void selectAll(); - void selectArea(Geom::Rect const &area, bool take); void shiftSelection(int dir); - void spatialGrow(NodeList::iterator center, int dir); - void invertSelection(); void invertSelectionInSubpaths(); - void deselect(); void setNodeType(NodeType t); void setSegmentType(SegmentType t); diff --git a/src/ui/tool/node-tool.cpp b/src/ui/tool/node-tool.cpp index 735ddf87e..c1ba3394e 100644 --- a/src/ui/tool/node-tool.cpp +++ b/src/ui/tool/node-tool.cpp @@ -62,12 +62,15 @@ * it might handle all shapes. Handles XML commit of actions that affect all paths or * the node selection and removes PathManipulators that have no nodes left after e.g. node * deletes. - * - ControlPointSelection: keeps track of node selection. Performs actions that require no + * - ControlPointSelection: keeps track of node selection and a set of nodes that can potentially + * be selected. There can be more than one selection. Performs actions that require no * knowledge about the path, only about the nodes, like dragging and transforms. It is not * specific to nodes and can accomodate any control point derived from SelectableControlPoint. * Transforms nodes in response to transform handle events. * - TransformHandleSet: displays nodeset transform handles and emits transform events. The aim * is to eventually use a common class for object and control point transforms. + * - SelectableControlPoint: base for any type of selectable point. It can belong to only one + * selection. * * @par Plans for the future * @par @@ -384,9 +387,6 @@ void ink_node_tool_selection_changed(InkNodeTool *nt, Inkscape::Selection *sel) std::set shapes; - // TODO this is ugly!!! - //typedef std::map > TransMap; - //typedef std::map > PathMap; GSList const *ilist = sel->itemList(); for (GSList *i = const_cast(ilist); i; i = i->next) { @@ -477,7 +477,7 @@ gint ink_node_tool_root_handler(SPEventContext *event_context, GdkEvent *event) case GDK_a: if (held_control(event->key)) { if (held_alt(event->key)) { - nt->_multipath->selectAll(); + nt->_selected_nodes->selectAll(); } else { // select all nodes in subpaths that have something selected // if nothing is selected, select everything @@ -554,7 +554,7 @@ void ink_node_tool_select_area(InkNodeTool *nt, Geom::Rect const &sel, GdkEventB selection->setList(items); g_slist_free(items); } else { - nt->_multipath->selectArea(sel, !held_shift(*event)); + nt->_selected_nodes->selectArea(sel); } } void ink_node_tool_select_point(InkNodeTool *nt, Geom::Point const &sel, GdkEventButton *event) diff --git a/src/ui/tool/node.cpp b/src/ui/tool/node.cpp index 22d4ddc47..adef8e5a7 100644 --- a/src/ui/tool/node.cpp +++ b/src/ui/tool/node.cpp @@ -22,6 +22,8 @@ #include "desktop.h" #include "desktop-handles.h" #include "preferences.h" +#include "snap.h" +#include "snap-preferences.h" #include "sp-metrics.h" #include "sp-namedview.h" #include "ui/tool/control-point-selection.h" @@ -638,10 +640,8 @@ bool Node::_eventHandler(GdkEvent *event) } else if (event->scroll.direction == GDK_SCROLL_DOWN) { dir = -1; } else break; - origin = NodeList::get_iterator(this); - if (held_control(event->scroll)) { - list()->_list._path_manipulator._multi_path_manipulator.spatialGrow(origin, dir); + _selection.spatialGrow(this, dir); } else { _linearGrow(dir); } @@ -658,7 +658,6 @@ static double bezier_length (Geom::Point a0, Geom::Point a1, Geom::Point a2, Geo double lower = Geom::distance(a0, a3); double upper = Geom::distance(a0, a1) + Geom::distance(a1, a2) + Geom::distance(a2, a3); - // TODO maybe EPSILON is this is too big in this case? if (upper - lower < Geom::EPSILON) return (lower + upper)/2; Geom::Point // Casteljau subdivision @@ -732,7 +731,7 @@ void Node::_linearGrow(int dir) } else { // both iterators that store last selected nodes are initially empty NodeList::iterator last_fwd, last_rev; - double last_distance_back, last_distance_front; + double last_distance_back = 0, last_distance_front = 0; while (rev || fwd) { if (fwd && (!rev || distance_front <= distance_back)) { @@ -741,7 +740,7 @@ void Node::_linearGrow(int dir) last_distance_front = distance_front; } NodeList::iterator n = fwd.next(); - distance_front += bezier_length(*fwd, fwd->_front, n->_back, *n); + if (n) distance_front += bezier_length(*fwd, fwd->_front, n->_back, *n); fwd = n; } else if (rev && (!fwd || distance_front > distance_back)) { if (rev->selected()) { @@ -749,13 +748,27 @@ void Node::_linearGrow(int dir) last_distance_back = distance_back; } NodeList::iterator p = rev.prev(); - distance_back += bezier_length(*rev, rev->_back, p->_front, *p); + if (p) distance_back += bezier_length(*rev, rev->_back, p->_front, *p); rev = p; } // Check whether we walked the entire cyclic subpath. // This is initially true because both iterators start from this node, // so this check cannot go in the while condition. - if (fwd == rev) break; + // When this happens, we need to check the last node, pointed to by the iterators. + if (fwd && fwd == rev) { + if (!fwd->selected()) break; + NodeList::iterator fwdp = fwd.prev(), revn = rev.next(); + double df = distance_front + bezier_length(*fwdp, fwdp->_front, fwd->_back, *fwd); + double db = distance_back + bezier_length(*revn, revn->_back, rev->_front, *rev); + if (df > db) { + last_fwd = fwd; + last_distance_front = df; + } else { + last_rev = rev; + last_distance_back = db; + } + break; + } } NodeList::iterator t; @@ -825,34 +838,97 @@ bool Node::_grabbedHandler(GdkEventMotion *event) void Node::_draggedHandler(Geom::Point &new_pos, GdkEventMotion *event) { + // For a note on how snapping is implemented in Inkscape, see snap.h. + SnapManager &sm = _desktop->namedview->snap_manager; + Inkscape::SnapPreferences::PointType t = Inkscape::SnapPreferences::SNAPPOINT_NODE; + bool snap = sm.someSnapperMightSnap(); + std::vector< std::pair > unselected; + if (snap) { + // setup + // TODO we are doing this every time a snap happens. It should once be done only once + // per drag - maybe in the grabbed handler? + // TODO "unselected" must be valid during the snap run, because it is not copied. + // Fix this in snap.h and snap.cpp, then the above. + + // Build the list of unselected nodes. + typedef ControlPointSelection::Set Set; + Set nodes = _selection.allPoints(); + for (Set::iterator i = nodes.begin(); i != nodes.end(); ++i) { + if (!(*i)->selected()) { + Node *n = static_cast(*i); + unselected.push_back(std::make_pair((*i)->position(), (int) n->_snapTargetType())); + } + } + sm.setupIgnoreSelection(_desktop, true, &unselected); + } + if (held_control(*event)) { + Geom::Point origin = _last_drag_origin(); if (held_alt(*event)) { // with Ctrl+Alt, constrain to handle lines // project the new position onto a handle line that is closer - Geom::Point origin = _last_drag_origin(); - Geom::Line line_front(origin, origin + _front.relativePos()); - Geom::Line line_back(origin, origin + _back.relativePos()); - double dist_front, dist_back; - dist_front = Geom::distance(new_pos, line_front); - dist_back = Geom::distance(new_pos, line_back); - if (dist_front < dist_back) { - new_pos = Geom::projection(new_pos, line_front); + Inkscape::Snapper::ConstraintLine line_front(origin, _front.relativePos()); + Inkscape::Snapper::ConstraintLine line_back(origin, _back.relativePos()); + + // TODO: combine these two branches by modifying snap.h / snap.cpp + if (snap) { + Inkscape::SnappedPoint fp, bp; + fp = sm.constrainedSnap(t, position(), _snapSourceType(), line_front); + bp = sm.constrainedSnap(t, position(), _snapSourceType(), line_back); + + if (fp.isOtherSnapBetter(bp, false)) { + bp.getPoint(new_pos); + } else { + fp.getPoint(new_pos); + } } else { - new_pos = Geom::projection(new_pos, line_back); + Geom::Point p_front = line_front.projection(new_pos); + Geom::Point p_back = line_back.projection(new_pos); + if (Geom::distance(new_pos, p_front) < Geom::distance(new_pos, p_back)) { + new_pos = p_front; + } else { + new_pos = p_back; + } } } else { // with Ctrl, constrain to axes - // TODO maybe add diagonals when the distance from origin is large enough? - Geom::Point origin = _last_drag_origin(); - Geom::Point delta = new_pos - origin; - Geom::Dim2 d = (fabs(delta[Geom::X]) < fabs(delta[Geom::Y])) ? Geom::X : Geom::Y; - new_pos[d] = origin[d]; + // TODO combine the two branches + if (snap) { + Inkscape::SnappedPoint fp, bp; + Inkscape::Snapper::ConstraintLine line_x(origin, Geom::Point(1, 0)); + Inkscape::Snapper::ConstraintLine line_y(origin, Geom::Point(0, 1)); + fp = sm.constrainedSnap(t, position(), _snapSourceType(), line_x); + bp = sm.constrainedSnap(t, position(), _snapSourceType(), line_y); + + if (fp.isOtherSnapBetter(bp, false)) { + fp = bp; + } + fp.getPoint(new_pos); + } else { + Geom::Point origin = _last_drag_origin(); + Geom::Point delta = new_pos - origin; + Geom::Dim2 d = (fabs(delta[Geom::X]) < fabs(delta[Geom::Y])) ? Geom::X : Geom::Y; + new_pos[d] = origin[d]; + } } - } else { - // TODO snapping? + } else if (snap) { + sm.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, new_pos, _snapSourceType()); } } +Inkscape::SnapSourceType Node::_snapSourceType() +{ + if (_type == NODE_SMOOTH || _type == NODE_AUTO) + return SNAPSOURCE_NODE_SMOOTH; + return SNAPSOURCE_NODE_CUSP; +} +Inkscape::SnapTargetType Node::_snapTargetType() +{ + if (_type == NODE_SMOOTH || _type == NODE_AUTO) + return SNAPTARGET_NODE_SMOOTH; + return SNAPTARGET_NODE_CUSP; +} + Glib::ustring Node::_getTip(unsigned state) { if (state_held_shift(state)) { diff --git a/src/ui/tool/node.h b/src/ui/tool/node.h index 167cf90b8..a85877d5c 100644 --- a/src/ui/tool/node.h +++ b/src/ui/tool/node.h @@ -19,6 +19,7 @@ #include #include #include +#include "snapped-point.h" #include "ui/tool/selectable-control-point.h" #include "ui/tool/node-types.h" @@ -136,8 +137,9 @@ public: void sink(); static char const *node_type_to_localized_string(NodeType type); -protected: + // temporarily public virtual bool _eventHandler(GdkEvent *event); +protected: virtual void _setState(State state); virtual Glib::ustring _getTip(unsigned state); virtual Glib::ustring _getDragTip(GdkEventMotion *event); @@ -151,6 +153,8 @@ private: void _linearGrow(int dir); Node *_next(); Node *_prev(); + Inkscape::SnapSourceType _snapSourceType(); + Inkscape::SnapTargetType _snapTargetType(); static SPCtrlShapeType _node_type_to_shape(NodeType type); static bool _is_line_segment(Node *first, Node *second); diff --git a/src/ui/tool/path-manipulator.cpp b/src/ui/tool/path-manipulator.cpp index 2755d6fb3..cfa3846f8 100644 --- a/src/ui/tool/path-manipulator.cpp +++ b/src/ui/tool/path-manipulator.cpp @@ -214,42 +214,6 @@ void PathManipulator::selectSubpaths() } } -/** Select all nodes in the path. */ -void PathManipulator::selectAll() -{ - for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) { - for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) { - _selection.insert(j.ptr()); - } - } -} - -/** Select points inside the given rectangle. If all points inside it are already selected, - * they will be deselected. - * @param area Area to select - */ -void PathManipulator::selectArea(Geom::Rect const &area) -{ - bool nothing_selected = true; - std::vector in_area; - for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) { - for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) { - if (area.contains(j->position())) { - in_area.push_back(j.ptr()); - if (!j->selected()) { - _selection.insert(j.ptr()); - nothing_selected = false; - } - } - } - } - if (nothing_selected) { - for (std::vector::iterator i = in_area.begin(); i != in_area.end(); ++i) { - _selection.erase(*i); - } - } -} - /** Move the selection forward or backward by one node in each subpath, based on the sign * of the parameter. */ void PathManipulator::shiftSelection(int dir) @@ -298,17 +262,6 @@ void PathManipulator::shiftSelection(int dir) } } -/** Invert selection in the entire path. */ -void PathManipulator::invertSelection() -{ - for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) { - for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) { - if (j->selected()) _selection.erase(j.ptr()); - else _selection.insert(j.ptr()); - } - } -} - /** Invert selection in the selected subpaths. */ void PathManipulator::invertSelectionInSubpaths() { @@ -724,6 +677,7 @@ void PathManipulator::setControlsTransform(Geom::Matrix const &tnew) _createGeometryFromControlPoints(); } +/** Hide the curve drag point until the next motion event. */ void PathManipulator::hideDragPoint() { _dragpoint->setVisible(false); diff --git a/src/ui/tool/path-manipulator.h b/src/ui/tool/path-manipulator.h index 38f66dee0..99e183b45 100644 --- a/src/ui/tool/path-manipulator.h +++ b/src/ui/tool/path-manipulator.h @@ -64,10 +64,7 @@ public: SPPath *item() { return _path; } void selectSubpaths(); - void selectAll(); - void selectArea(Geom::Rect const &); void shiftSelection(int dir); - void invertSelection(); void invertSelectionInSubpaths(); void insertNodes(); diff --git a/src/ui/tool/selectable-control-point.cpp b/src/ui/tool/selectable-control-point.cpp index b189a713f..5b9aa4fc8 100644 --- a/src/ui/tool/selectable-control-point.cpp +++ b/src/ui/tool/selectable-control-point.cpp @@ -50,10 +50,12 @@ SelectableControlPoint::SelectableControlPoint(SPDesktop *d, Geom::Point const & SelectableControlPoint::~SelectableControlPoint() { _selection.erase(this); + _selection.allPoints().erase(this); } void SelectableControlPoint::_connectHandlers() { + _selection.allPoints().insert(this); signal_clicked.connect( sigc::mem_fun(*this, &SelectableControlPoint::_clickedHandler)); signal_grabbed.connect( diff --git a/src/verbs.cpp b/src/verbs.cpp index d26a4e6d5..f03be681a 100644 --- a/src/verbs.cpp +++ b/src/verbs.cpp @@ -79,6 +79,7 @@ #include "ui/dialog/layers.h" #include "ui/dialog/swatches.h" #include "ui/icon-names.h" +#include "ui/tool/control-point-selection.h" #include "ui/tool/multi-path-manipulator.h" #include "ui/tool/node-tool.h" @@ -937,7 +938,7 @@ EditVerb::perform(SPAction *action, void *data, void */*pdata*/) case SP_VERB_EDIT_SELECT_ALL_IN_ALL_LAYERS: if (tools_isactive(dt, TOOLS_NODES)) { InkNodeTool *nt = static_cast(dt->event_context); - nt->_multipath->selectAll(); + nt->_selected_nodes->selectAll(); } else { sp_edit_select_all_in_all_layers(dt); } @@ -945,7 +946,7 @@ EditVerb::perform(SPAction *action, void *data, void */*pdata*/) case SP_VERB_EDIT_INVERT_IN_ALL_LAYERS: if (tools_isactive(dt, TOOLS_NODES)) { InkNodeTool *nt = static_cast(dt->event_context); - nt->_multipath->invertSelection(); + nt->_selected_nodes->invertSelection(); } else { sp_edit_invert_in_all_layers(dt); } @@ -977,7 +978,7 @@ EditVerb::perform(SPAction *action, void *data, void */*pdata*/) case SP_VERB_EDIT_DESELECT: if (tools_isactive(dt, TOOLS_NODES)) { InkNodeTool *nt = static_cast(dt->event_context); - nt->_multipath->deselect(); + nt->_selected_nodes->clear(); } else { sp_desktop_selection(dt)->clear(); }