diff --git a/src/seltrans.cpp b/src/seltrans.cpp index 4ee86cfb2a728a42d70070642d0832826ba022aa..163ea9b4393b830b666bb693f4bf6d57da761331 100644 --- a/src/seltrans.cpp +++ b/src/seltrans.cpp @@ -1560,6 +1560,14 @@ void Inkscape::SelTrans::moveTo(Geom::Point const &xy, guint state) auto increments = Modifiers::Modifier::get(Modifiers::Type::MOVE_INCREMENT)->active(state); auto no_snap = Modifiers::Modifier::get(Modifiers::Type::MOVE_SNAPPING)->active(state); auto confine = Modifiers::Modifier::get(Modifiers::Type::MOVE_CONFINE)->active(state); + // If a select-specific drag modifier shares the same mask (e.g. Alt) and is active, don't treat it as "move increment" + if (increments) { + if (auto force_drag = Modifiers::Modifier::get(Modifiers::Type::SELECT_FORCE_DRAG); force_drag && force_drag->active(state)) { + increments = false; + } else if (auto dup_drag = Modifiers::Modifier::get(Modifiers::Type::SELECT_DUPLICATE); dup_drag && dup_drag->active(state)) { + increments = false; + } + } if (confine) { if (fabs(dxy[Geom::X]) > fabs(dxy[Geom::Y])) { diff --git a/src/ui/modifiers.cpp b/src/ui/modifiers.cpp index af77aa77ceb80f5f5043a554caaaff1af593b121..14b6a4ef77b6fd58f118fc26e79125fada49f80b 100644 --- a/src/ui/modifiers.cpp +++ b/src/ui/modifiers.cpp @@ -37,6 +37,7 @@ ModifierIdToTypeMap const &modifier_type_from_id() {"select-remove-from", Type::SELECT_REMOVE_FROM}, {"select-force-drag", Type::SELECT_FORCE_DRAG}, {"select-cycle", Type::SELECT_CYCLE}, + {"select-duplicate", Type::SELECT_DUPLICATE}, {"move-confine", Type::MOVE_CONFINE}, {"move-increment", Type::MOVE_INCREMENT}, {"move-snapping", Type::MOVE_SNAPPING}, @@ -97,6 +98,7 @@ Modifier::Container &Modifier::_modifiers() make_modifier("select-remove-from", _("Remove from selection"), _("Remove items from existing selection"), SHIFT | CTRL, SELECT, DRAG), make_modifier("select-force-drag", _("Forced Drag"), _("Drag objects even if the mouse isn't over them"), ALT, SELECT, DRAG), make_modifier("select-cycle", _("Cycle through objects"), _("Scroll through objects under the cursor"), ALT, SELECT, SCROLL), + make_modifier("select-duplicate", _("Duplicate selection on drag"), _("Duplicate selection when starting a drag"), NEVER, SELECT, DRAG), // Transform handle modifiers (applies to multiple tools) make_modifier("move-confine", _("Move one axis only"), _("When dragging items, confine to either x or y axis"), CTRL, MOVE, DRAG), diff --git a/src/ui/modifiers.h b/src/ui/modifiers.h index aa9188bbf4052852f0c8e866e1c2a80b0c264c87..c53d06a951cd0c5aaf94abd24dbc692ebd68b2aa 100644 --- a/src/ui/modifiers.h +++ b/src/ui/modifiers.h @@ -65,6 +65,7 @@ enum class Type { SELECT_REMOVE_FROM, // Remove from selection {CTRL+DRAG} SELECT_FORCE_DRAG, // Drag objects even if the mouse isn't over them {ALT+DRAG+Selected} SELECT_CYCLE, // Cycle through objects under cursor {ALT+SCROLL} + SELECT_DUPLICATE, // Duplicate selection when starting a drag {ALT+DRAG+Selected} unassigned by default // Transform handles (applies to multiple tools) MOVE_CONFINE, // Limit dragging to X OR Y only {DRAG+CTRL} diff --git a/src/ui/tools/select-tool.cpp b/src/ui/tools/select-tool.cpp index 5a83d0d2604a8f255862648591b33ef94f782273..b5f2079906557abfdd988e56f9cf32a92e21c0f0 100644 --- a/src/ui/tools/select-tool.cpp +++ b/src/ui/tools/select-tool.cpp @@ -137,19 +137,20 @@ bool SelectTool::sp_select_context_abort() { } item = nullptr; - _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Move canceled.")); - return true; - } - } else { - if (Inkscape::Rubberband::get(_desktop)->isStarted()) { + _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Move canceled.")); + return true; + } + } else { + if (Inkscape::Rubberband::get(_desktop)->isStarted()) { Inkscape::Rubberband::get(_desktop)->stop(); rb_escaped = 1; defaultMessageContext()->clear(); _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Selection canceled.")); return true; + } } - } - return false; + _duplicate_drag_reset(); + return false; } static bool @@ -436,8 +437,13 @@ bool SelectTool::root_handler(CanvasEvent const &event) saveDragOrigin(event.pos); + bool has_selection = !selection->isEmpty(); + _duplicate_drag_on_press = has_selection && _duplicate_drag_state(event.modifiers); + auto item_down = has_selection ? _desktop->getItemAtPoint(event.pos, false) : nullptr; + _duplicate_down_on_selected = item_down && selection->includes(item_down, true); + bool suppress_touch_path = _duplicate_drag_on_press && _duplicate_down_on_selected; auto rubberband = Inkscape::Rubberband::get(_desktop); - if (Modifier::get(Modifiers::Type::SELECT_TOUCH_PATH)->active(event.modifiers)) { + if (!suppress_touch_path && Modifier::get(Modifiers::Type::SELECT_TOUCH_PATH)->active(event.modifiers)) { rubberband->setMode(Rubberband::Mode::TOUCHPATH); rubberband->setHandle(CanvasItemCtrlType::RUBBERBAND_TOUCHPATH_SELECT); } else { @@ -481,6 +487,7 @@ bool SelectTool::root_handler(CanvasEvent const &event) tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + bool duplicate_drag = _duplicate_drag_on_press; bool force_drag = Modifier::get(Modifiers::Type::SELECT_FORCE_DRAG)->active(button_press_state); bool always_box = Modifier::get(Modifiers::Type::SELECT_ALWAYS_BOX)->active(button_press_state); @@ -498,6 +505,11 @@ bool SelectTool::root_handler(CanvasEvent const &event) set_cursor("select-dragging.svg"); } + if (!dragging && duplicate_drag && (force_drag || _duplicate_down_on_selected) && !selection->isEmpty()) { + // allow duplicate-drag to initiate a drag even when force-drag is off + dragging = true; + } + if (dragging) { /* User has dragged fast, so we get events on root (lauris)*/ // not only that; we will end up here when ctrl-dragging as well @@ -545,7 +557,14 @@ bool SelectTool::root_handler(CanvasEvent const &event) } } // otherwise, do not change selection so that dragging selected-within-group items, as well as alt-dragging, is possible - _seltrans->grab(p, -1, -1, false, true); + bool down_on_selected = item_at_point && selection->includes(item_at_point, true); + bool allow_duplicate = duplicate_drag && (_duplicate_down_on_selected || down_on_selected); + + if (allow_duplicate) { + _duplicate_drag(p); + } else { + _seltrans->grab(p, -1, -1, false, true); + } moved = true; } @@ -646,6 +665,7 @@ bool SelectTool::root_handler(CanvasEvent const &event) } item = nullptr; + _duplicate_drag_reset(); } else { Inkscape::Rubberband *r = Inkscape::Rubberband::get(_desktop); @@ -1016,6 +1036,39 @@ bool SelectTool::root_handler(CanvasEvent const &event) return ret || ToolBase::root_handler(event); } +void SelectTool::_duplicate_drag(Geom::Point const &p) +{ + auto selection = _desktop->getSelection(); + if (selection->isEmpty()) { + return; + } + + selection->duplicate(true); + _seltrans->grab(p, -1, -1, false, true); +} + +bool SelectTool::_duplicate_drag_state(unsigned int state) const +{ + auto mod = Modifier::get(Modifiers::Type::SELECT_DUPLICATE); + if (!mod) { + return false; + } + + // Ignore the modifier when it's effectively unset (no required keys). + auto mask = mod->get_and_mask(); + if (mask == Modifiers::ALWAYS || mask == Modifiers::NEVER) { + return false; + } + + return mod->active(state); +} + +void SelectTool::_duplicate_drag_reset() +{ + _duplicate_drag_on_press = false; + _duplicate_down_on_selected = false; +} + /** * Update the toolbar description to this selection. */ diff --git a/src/ui/tools/select-tool.h b/src/ui/tools/select-tool.h index 1da9ad25f71a5be96256e0f455f05e0e6e6f16e6..d785d44a676ce15cdf840d18be652ba1a7b3cd86 100644 --- a/src/ui/tools/select-tool.h +++ b/src/ui/tools/select-tool.h @@ -56,6 +56,11 @@ private: void sp_select_context_cycle_through_items(Selection *selection, ScrollEvent const &scroll_event); void sp_select_context_reset_opacities(); static std::pair get_default_rubberband_state(); + void _duplicate_drag(Geom::Point const &p); + bool _duplicate_drag_state(unsigned int state) const; + void _duplicate_drag_reset(); + bool _duplicate_drag_on_press = false; + bool _duplicate_down_on_selected = false; bool _alt_on = false; bool _force_dragging = false;