diff --git a/po/POTFILES.src.in b/po/POTFILES.src.in
index f9bc268790f25109296b50655310dbbc4c028dc8..64569437bd234638d9886c4e468de0717c101da1 100644
--- a/po/POTFILES.src.in
+++ b/po/POTFILES.src.in
@@ -288,6 +288,7 @@
../src/ui/dialog/xml-tree.cpp
../src/ui/drag-and-drop.cpp
../src/ui/interface.cpp
+../src/ui/modifiers.cpp
../src/ui/shape-editor-knotholders.cpp
../src/ui/tool/curve-drag-point.cpp
../src/ui/tool/multi-path-manipulator.cpp
diff --git a/share/keys/inkscape.xml b/share/keys/inkscape.xml
index 3c82584d08de90c5d0387ef49a1623a8c5ee9340..337bca339104818b61fc1cfe1fe1a34c42526cca 100644
--- a/share/keys/inkscape.xml
+++ b/share/keys/inkscape.xml
@@ -637,4 +637,24 @@ override) the bindings in the main default.xml.
-->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/display/control/canvas-item-drawing.cpp b/src/display/control/canvas-item-drawing.cpp
index 5ecb5fdb6f7539caf8a2ceef7fe8adc5c196f3eb..a9919a7eb935bdf030839e41b5f3da2f2e3ec980 100644
--- a/src/display/control/canvas-item-drawing.cpp
+++ b/src/display/control/canvas-item-drawing.cpp
@@ -26,6 +26,7 @@
#include "display/drawing-surface.h"
#include "ui/widget/canvas.h"
+#include "ui/modifiers.h"
namespace Inkscape {
@@ -255,10 +256,7 @@ bool CanvasItemDrawing::handle_event(GdkEvent *event)
case GDK_SCROLL:
{
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
- bool wheelzooms = prefs->getBool("/options/wheelzooms/value");
- bool ctrl = (event->scroll.state & GDK_CONTROL_MASK);
- if ((ctrl && !wheelzooms) || (!ctrl && wheelzooms)) {
+ if (Modifiers::Modifier::get(Modifiers::Type::CANVAS_ZOOM)->active(event->scroll.state)) {
/* Zoom is emitted by the canvas as well, ignore here */
return false;
}
diff --git a/src/preferences-skeleton.h b/src/preferences-skeleton.h
index 9bfd660538a0b5b9f316821bddd67dcb6efb587b..e2a355f506ca527d4a039be17a62f81ec771bc81 100644
--- a/src/preferences-skeleton.h
+++ b/src/preferences-skeleton.h
@@ -278,7 +278,6 @@ R"=====(
-
diff --git a/src/seltrans.cpp b/src/seltrans.cpp
index 7e09e1d19121b46d7e9106279e8208d91829d9d7..823a76348df6027fd6d68ee57c5ce4271cb11ff9 100644
--- a/src/seltrans.cpp
+++ b/src/seltrans.cpp
@@ -49,6 +49,7 @@
#include "object/sp-namedview.h"
#include "object/sp-root.h"
+#include "ui/modifiers.h"
#include "ui/tools/select-tool.h"
using Inkscape::DocumentUndo;
@@ -791,7 +792,8 @@ gboolean Inkscape::SelTrans::handleRequest(SPKnot *knot, Geom::Point *position,
// When holding shift while rotating or skewing, the transformation will be
// relative to the point opposite of the handle; otherwise it will be relative
// to the center as set for the selection
- if ((!(state & GDK_SHIFT_MASK) == !(_state == STATE_ROTATE)) && (handle.type != HANDLE_CENTER)) {
+ auto off_center = Modifiers::Modifier::get(Modifiers::Type::TRANS_OFF_CENTER)->active(state);
+ if ((!off_center == !(_state == STATE_ROTATE)) && (handle.type != HANDLE_CENTER)) {
_origin = _opposite;
_origin_for_bboxpoints = _opposite_for_bboxpoints;
_origin_for_specpoints = _opposite_for_specpoints;
@@ -882,7 +884,8 @@ gboolean Inkscape::SelTrans::scaleRequest(Geom::Point &pt, guint state)
_absolute_affine = Geom::identity(); //Initialize the scaler
- if (state & GDK_MOD1_MASK) { // scale by an integer multiplier/divider
+ auto fixed_ratio = Modifiers::Modifier::get(Modifiers::Type::SCALE_FIXED_RATIO)->active(state);
+ if (fixed_ratio) { // scale by an integer multiplier/divider
// We're scaling either the visual or the geometric bbox here (see the comment above)
for ( unsigned int i = 0 ; i < 2 ; i++ ) {
if (fabs(default_scale[i]) > 1) {
@@ -898,7 +901,8 @@ gboolean Inkscape::SelTrans::scaleRequest(Geom::Point &pt, guint state)
// In all other cases we should try to snap now
Inkscape::PureScale *bb, *sn;
- if ((state & GDK_CONTROL_MASK) || _desktop->isToolboxButtonActive ("lock")) {
+ auto confine = Modifiers::Modifier::get(Modifiers::Type::SCALE_CONFINE)->active(state);
+ if (confine || _desktop->isToolboxButtonActive ("lock")) {
// Scale is locked to a 1:1 aspect ratio, so that s[X] must be made to equal s[Y].
//
// The aspect-ratio must be locked before snapping
@@ -993,7 +997,8 @@ gboolean Inkscape::SelTrans::stretchRequest(SPSelTransHandle const &handle, Geom
_absolute_affine = Geom::identity(); //Initialize the scaler
- if (state & GDK_MOD1_MASK) { // stretch by an integer multiplier/divider
+ auto fixed_ratio = Modifiers::Modifier::get(Modifiers::Type::SCALE_FIXED_RATIO)->active(state);
+ if (fixed_ratio) { // stretch by an integer multiplier/divider
if (fabs(default_scale[axis]) > 1) {
default_scale[axis] = round(default_scale[axis]);
} else if (default_scale[axis] != 0) {
@@ -1008,10 +1013,9 @@ gboolean Inkscape::SelTrans::stretchRequest(SPSelTransHandle const &handle, Geom
SnapManager &m = _desktop->namedview->snap_manager;
m.setup(_desktop, false, _items_const);
- bool symmetrical = state & GDK_CONTROL_MASK;
-
- Inkscape::PureStretchConstrained bb = Inkscape::PureStretchConstrained(Geom::Coord(default_scale[axis]), _origin_for_bboxpoints, Geom::Dim2(axis), symmetrical);
- Inkscape::PureStretchConstrained sn = Inkscape::PureStretchConstrained(Geom::Coord(geom_scale[axis]), _origin_for_specpoints, Geom::Dim2(axis), symmetrical);
+ auto confine = Modifiers::Modifier::get(Modifiers::Type::SCALE_CONFINE)->active(state);
+ Inkscape::PureStretchConstrained bb = Inkscape::PureStretchConstrained(Geom::Coord(default_scale[axis]), _origin_for_bboxpoints, Geom::Dim2(axis), confine);
+ Inkscape::PureStretchConstrained sn = Inkscape::PureStretchConstrained(Geom::Coord(geom_scale[axis]), _origin_for_specpoints, Geom::Dim2(axis), confine);
m.snapTransformed(_bbox_points, _point, bb);
m.snapTransformed(_snap_points, _point, sn);
@@ -1026,8 +1030,8 @@ gboolean Inkscape::SelTrans::stretchRequest(SPSelTransHandle const &handle, Geom
geom_scale[axis] = sn.getMagnitude();
}
- if (symmetrical) {
- // on ctrl, apply symmetrical scaling instead of stretching
+ if (confine) {
+ // on scale_confine, apply symmetrical scaling instead of stretching
// Preserve aspect ratio, but never flip in the dimension not being edited (by using fabs())
default_scale[perp] = fabs(default_scale[axis]);
geom_scale[perp] = fabs(geom_scale[axis]);
@@ -1145,7 +1149,8 @@ gboolean Inkscape::SelTrans::skewRequest(SPSelTransHandle const &handle, Geom::P
double radians = atan(skew[dim_a] / scale[dim_a]);
- if (state & GDK_CONTROL_MASK) {
+ auto fixed_ratio = Modifiers::Modifier::get(Modifiers::Type::TRANS_FIXED_RATIO)->active(state);
+ if (fixed_ratio) {
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
// Snap to defined angle increments
int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
@@ -1234,7 +1239,8 @@ gboolean Inkscape::SelTrans::rotateRequest(Geom::Point &pt, guint state)
Geom::Rotate r2(q2);
double radians = atan2(Geom::dot(Geom::rot90(d1), d2), Geom::dot(d1, d2));;
- if (state & GDK_CONTROL_MASK) {
+ auto fixed_ratio = Modifiers::Modifier::get(Modifiers::Type::TRANS_FIXED_RATIO)->active(state);
+ if (fixed_ratio) {
// Snap to defined angle increments
double cos_t = Geom::dot(q1, q2);
double sin_t = Geom::dot(Geom::rot90(q1), q2);
@@ -1293,17 +1299,17 @@ gboolean Inkscape::SelTrans::centerRequest(Geom::Point &pt, guint state)
m.setup(_desktop);
m.setRotationCenterSource(items);
- if (state & GDK_CONTROL_MASK) { // with Ctrl, constrain to axes
+ auto no_snap = Modifiers::Modifier::get(Modifiers::Type::TRANS_SNAPPING)->active(state);
+ auto confine = Modifiers::Modifier::get(Modifiers::Type::MOVE_CONFINE)->active(state);
+ if (confine) {
std::vector constraints;
constraints.emplace_back(_point, Geom::Point(1, 0));
constraints.emplace_back(_point, Geom::Point(0, 1));
- Inkscape::SnappedPoint sp = m.multipleConstrainedSnaps(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_ROTATION_CENTER), constraints, state & GDK_SHIFT_MASK);
+ Inkscape::SnappedPoint sp = m.multipleConstrainedSnaps(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_ROTATION_CENTER), constraints, no_snap);
pt = sp.getPoint();
}
- else {
- if (!(state & GDK_SHIFT_MASK)) { // Shift disables snapping
- m.freeSnapReturnByRef(pt, Inkscape::SNAPSOURCE_ROTATION_CENTER);
- }
+ else if (!no_snap) {
+ m.freeSnapReturnByRef(pt, Inkscape::SNAPSOURCE_ROTATION_CENTER);
}
m.unSetup();
@@ -1378,11 +1384,11 @@ void Inkscape::SelTrans::moveTo(Geom::Point const &xy, guint state)
/* The amount that we've moved by during this drag */
Geom::Point dxy = xy - _point;
- bool const alt = (state & GDK_MOD1_MASK);
- bool const control = (state & GDK_CONTROL_MASK);
- bool const shift = (state & GDK_SHIFT_MASK);
+ auto fixed_ratio = Modifiers::Modifier::get(Modifiers::Type::MOVE_FIXED_RATIO)->active(state);
+ auto no_snap = Modifiers::Modifier::get(Modifiers::Type::TRANS_SNAPPING)->active(state);
+ auto confine = Modifiers::Modifier::get(Modifiers::Type::MOVE_CONFINE)->active(state);
- if (control) { // constrained to the orthogonal axes
+ if (confine) {
if (fabs(dxy[Geom::X]) > fabs(dxy[Geom::Y])) {
dxy[Geom::Y] = 0;
} else {
@@ -1390,11 +1396,11 @@ void Inkscape::SelTrans::moveTo(Geom::Point const &xy, guint state)
}
}
- if (alt) {// Alt pressed means: move only by integer multiples of the grid spacing
+ if (fixed_ratio) {// Alt pressed means: move only by integer multiples of the grid spacing
m.setup(_desktop, true, _items_const);
dxy = m.multipleOfGridPitch(dxy, _point);
m.unSetup();
- } else if (!shift) { //!shift: with snapping
+ } else if (!no_snap) {
/* We're snapping to things, possibly with a constraint to horizontal or
** vertical movement. Obtain a list of possible translations and then
** pick the smallest.
@@ -1407,7 +1413,7 @@ void Inkscape::SelTrans::moveTo(Geom::Point const &xy, guint state)
Inkscape::PureTranslate *bb, *sn;
- if (control) { // constrained movement with snapping
+ if (confine) { // constrained movement with snapping
/* Snap to things, and also constrain to horizontal or vertical movement */
@@ -1419,7 +1425,7 @@ void Inkscape::SelTrans::moveTo(Geom::Point const &xy, guint state)
// individually for each point to be snapped; this will be handled however by snapTransformed()
bb = new Inkscape::PureTranslateConstrained(dxy[dim], dim);
sn = new Inkscape::PureTranslateConstrained(dxy[dim], dim);
- } else { // !control
+ } else {
/* Snap to things with no constraint */
bb = new Inkscape::PureTranslate(dxy);
sn = new Inkscape::PureTranslate(dxy);
@@ -1461,7 +1467,7 @@ void Inkscape::SelTrans::moveTo(Geom::Point const &xy, guint state)
} else {
// We didn't snap, so remove any previous snap indicator
_desktop->snapindicator->remove_snaptarget();
- if (control) {
+ if (confine) {
// If we didn't snap, then we should still constrain horizontally or vertically
// (When we did snap, then this constraint has already been enforced by
// calling constrainedSnapTranslate() above)
diff --git a/src/shortcuts.cpp b/src/shortcuts.cpp
index e801b9bb4f2b0b4b36c654ff354ec58486accaef..27dbe3e7334f42197e624a175bec664cf51199fd 100644
--- a/src/shortcuts.cpp
+++ b/src/shortcuts.cpp
@@ -49,14 +49,16 @@
#include "xml/repr.h"
#include "document.h"
#include "preferences.h"
-#include "ui/tools/tool-base.h"
#include "inkscape.h"
#include "desktop.h"
#include "path-prefix.h"
+#include "ui/modifiers.h"
+#include "ui/tools/tool-base.h"
#include "ui/dialog/filedialog.h"
using namespace Inkscape;
using namespace Inkscape::IO::Resource;
+using namespace Inkscape::Modifiers;
static bool try_shortcuts_file(char const *filename, bool const is_user_set=false);
static void read_shortcuts_file(char const *filename, bool const is_user_set=false);
@@ -138,6 +140,13 @@ void sp_shortcut_init()
try_shortcuts_file(get_path(USER, KEYS, "default.xml"), true);
}
+/*
+ * Loads the keys file if required, but not if verbs already exist.
+ */
+void sp_shortcut_ensure_init() {
+ if (!verbs) sp_shortcut_init();
+}
+
static bool try_shortcuts_file(char const *filename, bool const is_user_set) {
using Inkscape::IO::file_test;
@@ -684,54 +693,37 @@ static void _read_shortcuts_file(XML::Node const *root, bool const is_user_set)
continue;
}
- guint modifiers=0;
+ guint modifiers = parse_modifier_string(iter->attribute("modifiers"), verb_name);
+ sp_shortcut_set(keyval | modifiers, verb, is_primary, is_user_set);
- gchar const *modifiers_string=iter->attribute("modifiers");
- if (modifiers_string) {
- gchar const *iter=modifiers_string;
- while (*iter) {
- size_t length=strcspn(iter, ",");
- gchar *mod=g_strndup(iter, length);
- if (!strcmp(mod, "Control") || !strcmp(mod, "Ctrl")) {
- modifiers |= SP_SHORTCUT_CONTROL_MASK;
- } else if (!strcmp(mod, "Shift")) {
- modifiers |= SP_SHORTCUT_SHIFT_MASK;
- } else if (!strcmp(mod, "Alt")) {
- modifiers |= SP_SHORTCUT_ALT_MASK;
- } else if (!strcmp(mod, "Super")) {
- modifiers |= SP_SHORTCUT_SUPER_MASK;
- } else if (!strcmp(mod, "Hyper") || !strcmp(mod, "Cmd")) {
- modifiers |= SP_SHORTCUT_HYPER_MASK;
- } else if (!strcmp(mod, "Meta")) {
- modifiers |= SP_SHORTCUT_META_MASK;
- } else if (!strcmp(mod, "Primary")) {
- auto display = Gdk::Display::get_default();
- if (display) {
- GdkKeymap* keymap = display->get_keymap();
- GdkModifierType mod =
- gdk_keymap_get_modifier_mask (keymap, GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR);
- gdk_keymap_add_virtual_modifiers(keymap, &mod);
- if (mod & GDK_CONTROL_MASK)
- modifiers |= SP_SHORTCUT_CONTROL_MASK;
- else if (mod & GDK_META_MASK)
- modifiers |= SP_SHORTCUT_META_MASK;
- else {
- g_warning("unsupported primary accelerator ");
- modifiers |= SP_SHORTCUT_CONTROL_MASK;
- }
- } else {
- modifiers |= SP_SHORTCUT_CONTROL_MASK;
- }
- } else {
- g_warning("Unknown modifier %s for %s", mod, verb_name);
- }
- g_free(mod);
- iter += length;
- if (*iter) iter++;
- }
+ } else if (!strcmp(iter->name(), "modifier")) {
+ gchar const *mod_name = iter->attribute("action");
+ if (!mod_name) {
+ g_warning("Missing modifier name (action= attribute) for shortcut");
+ continue;
+ }
+ Modifier *mod = Modifier::get(mod_name);
+ if(mod == nullptr) {
+ g_warning("Can't find modifier '%s'", mod_name);
+ continue;
}
- sp_shortcut_set(keyval | modifiers, verb, is_primary, is_user_set);
+ // If mods isn't specified then it should use default, if it's an empty string
+ // then the modifier is None (i.e. happens all the time without a modifier)
+ gchar const *mod_attr = iter->attribute("modifiers");
+ if(!mod_attr) continue; // Default, do nothing and no warning
+ guint sp_mods = parse_modifier_string(mod_attr, mod_name);
+
+ // Convert SP_SHORTCUT modifiers to KeyMask
+ KeyMask user_modifier = 0;
+ if(sp_mods & SP_SHORTCUT_CONTROL_MASK) user_modifier |= CTRL;
+ if(sp_mods & SP_SHORTCUT_SHIFT_MASK) user_modifier |= SHIFT;
+ if(sp_mods & SP_SHORTCUT_ALT_MASK) user_modifier |= ALT;
+ if(sp_mods & SP_SHORTCUT_SUPER_MASK) user_modifier |= SUPER;
+ if(sp_mods & SP_SHORTCUT_HYPER_MASK) user_modifier |= HYPER;
+ if(sp_mods & SP_SHORTCUT_META_MASK) user_modifier |= META;
+
+ mod->set(user_modifier);
} else if (!strcmp(iter->name(), "keys")) {
// include another keys file
@@ -740,6 +732,59 @@ static void _read_shortcuts_file(XML::Node const *root, bool const is_user_set)
}
}
+/**
+ * Parse the modifier string out of the keys xml attribute
+ */
+guint
+parse_modifier_string(gchar const *modifiers_string, gchar const *verb_name)
+{
+ guint modifiers = 0;
+ if (modifiers_string) {
+ gchar const *iter=modifiers_string;
+ while (*iter) {
+ size_t length=strcspn(iter, ",");
+ gchar *mod=g_strndup(iter, length);
+ if (!strcmp(mod, "Control") || !strcmp(mod, "Ctrl")) {
+ modifiers |= SP_SHORTCUT_CONTROL_MASK;
+ } else if (!strcmp(mod, "Shift")) {
+ modifiers |= SP_SHORTCUT_SHIFT_MASK;
+ } else if (!strcmp(mod, "Alt")) {
+ modifiers |= SP_SHORTCUT_ALT_MASK;
+ } else if (!strcmp(mod, "Super")) {
+ modifiers |= SP_SHORTCUT_SUPER_MASK;
+ } else if (!strcmp(mod, "Hyper") || !strcmp(mod, "Cmd")) {
+ modifiers |= SP_SHORTCUT_HYPER_MASK;
+ } else if (!strcmp(mod, "Meta")) {
+ modifiers |= SP_SHORTCUT_META_MASK;
+ } else if (!strcmp(mod, "Primary")) {
+ auto display = Gdk::Display::get_default();
+ if (display) {
+ GdkKeymap* keymap = display->get_keymap();
+ GdkModifierType mod =
+ gdk_keymap_get_modifier_mask (keymap, GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR);
+ gdk_keymap_add_virtual_modifiers(keymap, &mod);
+ if (mod & GDK_CONTROL_MASK)
+ modifiers |= SP_SHORTCUT_CONTROL_MASK;
+ else if (mod & GDK_META_MASK)
+ modifiers |= SP_SHORTCUT_META_MASK;
+ else {
+ g_warning("unsupported primary accelerator ");
+ modifiers |= SP_SHORTCUT_CONTROL_MASK;
+ }
+ } else {
+ modifiers |= SP_SHORTCUT_CONTROL_MASK;
+ }
+ } else {
+ g_warning("Unknown modifier %s for %s", mod, verb_name);
+ }
+ g_free(mod);
+ iter += length;
+ if (*iter) iter++;
+ }
+ }
+ return modifiers;
+}
+
/**
* Removes a keyboard shortcut for the given verb.
* (Removes any existing binding for the given shortcut, including appropriately
@@ -748,7 +793,7 @@ static void _read_shortcuts_file(XML::Node const *root, bool const is_user_set)
void
sp_shortcut_unset(unsigned int const shortcut)
{
- if (!verbs) sp_shortcut_init();
+ sp_shortcut_ensure_init();
Inkscape::Verb *verb = (*verbs)[shortcut];
@@ -832,7 +877,7 @@ sp_shortcut_get_modifiers(unsigned int const shortcut)
void
sp_shortcut_set(unsigned int const shortcut, Inkscape::Verb *const verb, bool const is_primary, bool const is_user_set)
{
- if (!verbs) sp_shortcut_init();
+ sp_shortcut_ensure_init();
Inkscape::Verb *old_verb = (*verbs)[shortcut];
(*verbs)[shortcut] = verb;
@@ -856,7 +901,7 @@ sp_shortcut_set(unsigned int const shortcut, Inkscape::Verb *const verb, bool co
Inkscape::Verb *
sp_shortcut_get_verb(unsigned int shortcut)
{
- if (!verbs) sp_shortcut_init();
+ sp_shortcut_ensure_init();
return (*verbs)[shortcut];
}
diff --git a/src/shortcuts.h b/src/shortcuts.h
index ab41be8b83478834ed281eef8c7fef5f92124c72..279ad16af8e9aec0ffbda895fc2613fa43f748d0 100644
--- a/src/shortcuts.h
+++ b/src/shortcuts.h
@@ -51,6 +51,7 @@ namespace Inkscape {
bool sp_shortcut_invoke (unsigned int shortcut, Inkscape::UI::View::View *view);
void sp_shortcut_init();
+void sp_shortcut_ensure_init();
Inkscape::Verb * sp_shortcut_get_verb (unsigned int shortcut);
unsigned int sp_shortcut_get_primary (Inkscape::Verb * verb); // Returns GDK_VoidSymbol if no shortcut is found.
char* sp_shortcut_get_label (unsigned int shortcut); // Returns the human readable form of the shortcut (or NULL), for example Shift+Ctrl+F. Free the returned string with g_free.
@@ -71,6 +72,7 @@ void sp_shortcut_file_import_do(char const *importname);
void sp_shortcut_file_export_do(char const *exportname);
GtkAccelGroup *sp_shortcut_get_accel_group();
void sp_shortcut_add_accelerator(GtkWidget *item, unsigned int const shortcut);
+guint parse_modifier_string(gchar const *modifiers_string, gchar const *verb_name);
#endif
diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt
index 71217fa463a8de6ae5dcefb3baebba956c2a8d5a..d976381d3a431068bb18f8b8326917041551328d 100644
--- a/src/ui/CMakeLists.txt
+++ b/src/ui/CMakeLists.txt
@@ -18,6 +18,7 @@ set(ui_SRC
tools-switch.cpp
util.cpp
uxmanager.cpp
+ modifiers.cpp
cache/svg_preview_cache.cpp
@@ -250,6 +251,7 @@ set(ui_SRC
tools-switch.h
util.h
uxmanager.h
+ modifiers.h
cache/svg_preview_cache.h
diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp
index 5382671a4c308f06c5266cae762d469aecfbd6da..02d097bac1e8ec70f921efa8647a3dfdf930a64c 100644
--- a/src/ui/dialog/inkscape-preferences.cpp
+++ b/src/ui/dialog/inkscape-preferences.cpp
@@ -1881,9 +1881,6 @@ void InkscapePreferences::initPageBehavior()
_scroll_space.init ( _("Mouse move pans when Space is pressed"), "/options/spacebarpans/value", true);
_page_scrolling.add_line( true, "", _scroll_space, "",
_("When on, pressing and holding Space and dragging pans canvas"));
- _wheel_zoom.init ( _("Mouse wheel zooms by default"), "/options/wheelzooms/value", false);
- _page_scrolling.add_line( false, "", _wheel_zoom, "",
- _("When on, mouse wheel zooms without Ctrl and scrolls canvas with Ctrl; when off, it zooms with Ctrl and scrolls without Ctrl"));
this->AddPage(_page_scrolling, _("Scrolling"), iter_behavior, PREFS_PAGE_BEHAVIOR_SCROLLING);
// Snapping options
diff --git a/src/ui/dialog/inkscape-preferences.h b/src/ui/dialog/inkscape-preferences.h
index 7e785e2658d1853410fce048c829f594b0479b1d..a12a52f1c2e1f0b57f50529e73b8ea6a79487633 100644
--- a/src/ui/dialog/inkscape-preferences.h
+++ b/src/ui/dialog/inkscape-preferences.h
@@ -190,7 +190,6 @@ protected:
UI::Widget::PrefSpinButton _scroll_auto_speed;
UI::Widget::PrefSpinButton _scroll_auto_thres;
UI::Widget::PrefCheckButton _scroll_space;
- UI::Widget::PrefCheckButton _wheel_zoom;
Gtk::Scale *_slider_snapping_delay;
diff --git a/src/ui/modifiers.cpp b/src/ui/modifiers.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..afb02c8a3f462bb3007768160355849c5a9a5ce5
--- /dev/null
+++ b/src/ui/modifiers.cpp
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Modifiers for inkscape
+ *
+ * The file provides a definition of all the ways shift/ctrl/alt modifiers
+ * are used in Inkscape, and allows users to customise them in keys.xml
+ *
+ *//*
+ * Authors:
+ * 2020 Martin Owens
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include
+#include
+#include
+#include "modifiers.h"
+#include "ui/tools/tool-base.h"
+
+namespace Inkscape {
+namespace Modifiers {
+
+Modifier::Lookup Modifier::_modifier_lookup;
+
+// these must be in the same order as the * enum in "modifers.h"
+decltype(Modifier::_modifiers) Modifier::_modifiers {
+ // Canvas modifiers
+ {Type::CANVAS_SCROLL_Y, new Modifier("canvas-scroll-y", _("Vertical scroll"), _("Scroll up and down"), 0, SCROLL)},
+ {Type::CANVAS_SCROLL_X, new Modifier("canvas-scroll-x", _("Horizontal scroll"), _("Scroll left and right"), SHIFT, SCROLL)},
+ {Type::CANVAS_ZOOM, new Modifier("canvas-zoom", _("Canvas zoom"), _("Zoom in and out with scroll wheel"), CTRL, SCROLL)},
+ {Type::CANVAS_ROTATE, new Modifier("canvas-rotate", _("Canavas rotate"), _("Rotate the canvas with scroll wheel"), SHIFT & CTRL, SCROLL)},
+
+ // Select tool modifiers (minus transforms)
+ {Type::SELECT_ADD_TO, new Modifier("select-add-to", _("Add to selection"), _("Add items to existing selection"), SHIFT, CLICK)},
+ {Type::SELECT_IN_GROUPS, new Modifier("select-in-groups", _("Select inside groups"), _("Ignore groups when selecting items"), CTRL, CLICK)},
+ {Type::SELECT_TOUCH_PATH, new Modifier("select-touch-path", _("Select with touch-path"), _("Draw a band around items to select them"), ALT, DRAG)},
+ {Type::SELECT_ALWAYS_BOX, new Modifier("select-always-box", _("Select with box"), _("Don't drag items, select more with a box"), SHIFT, DRAG)},
+ {Type::SELECT_FIRST_HIT, new Modifier("select-first-hit", _("Select the first"), _("Drag the first item the mouse hits"), CTRL, DRAG)},
+ {Type::SELECT_FORCE_DRAG, new Modifier("select-force-drag", _("Forced Drag"), _("Drag objects even if the mouse isn't over them."), ALT, DRAG)},
+ {Type::SELECT_CYCLE, new Modifier("select-cycle", _("Cycle through objects"), _("Scroll through objects under the cursor."), ALT, SCROLL)},
+
+ // Transform handle modifiers (applies to multiple tools)
+ {Type::MOVE_CONFINE, new Modifier("move-confine", _("Move one axis only"), _("When dragging items, confine to either x or y axis."), CTRL, DRAG)}, // MOVE by DRAG
+ {Type::MOVE_FIXED_RATIO, new Modifier("move-fixed-ratio", _("Move fixed amounts"), _("Move the objects by fixed amounts when dragging."), ALT, DRAG)}, // MOVE by DRAG
+ {Type::SCALE_CONFINE, new Modifier("scale-confine", _("Keep aspect ratio"), _("When resizing objects, confine the aspect ratio."), CTRL, HANDLE)}, // SCALE/STRETCH
+ {Type::SCALE_FIXED_RATIO, new Modifier("scale-fixed-ratio", _("Scale fixed amounts"), _("When moving or resizing objects, use fixed amounts."), ALT, HANDLE)}, // SCALE/STRETCH
+ {Type::TRANS_FIXED_RATIO, new Modifier("trans-fixed-ratio", _("Transform in increments"), _("Rotate or skew by fixed amounts"), CTRL, HANDLE)}, // ROTATE/SKEW
+ {Type::TRANS_OFF_CENTER, new Modifier("trans-off-center", _("Transform against center"), _("Change the center point when transforming objects."), SHIFT, HANDLE)}, // SCALE/ROTATE/SKEW
+ {Type::TRANS_SNAPPING, new Modifier("trans-snapping", _("Disable Snapping"), _("Disable snapping when transforming objects."), SHIFT, DRAG)}, // MOVE/SCALE/STRETCH/ROTATE/SKEW
+ // Center handle click: seltrans.cpp:734 SHIFT
+ // Align handle click: seltrans.cpp:1365 SHIFT
+};
+
+/**
+ * List all the modifiers available. Used in UI listing.
+ *
+ * @return a vector of Modifier objects.
+ */
+std::vector
+Modifier::getList () {
+
+ std::vector modifiers;
+ // Go through the dynamic modifier table
+ for( auto const& [key, val] : _modifiers ) {
+ modifiers.push_back(val);
+ }
+
+ return modifiers;
+};
+
+/**
+ * Test if this modifier is currently active.
+ *
+ * @param button_state - The GDK button state from an event
+ * @return a boolean, true if the modifiers for this action are active.
+ */
+bool Modifier::active(int button_state)
+{
+ // TODO:
+ // * ALT key is sometimes MOD1, MOD2 etc, if we find other ALT keys, set the ALT bit
+ // * SUPER key could be HYPER or META, these cases need to be considered.
+ return get_and_mask() & button_state;
+}
+
+/**
+ * Generate a label for any modifier keys based on the mask
+ *
+ * @param mask - The Modifier Mask such as {SHIFT & CTRL}
+ * @return a string of the keys needed for this mask to be true.
+ */
+std::string generate_label(KeyMask mask)
+{
+ auto ret = std::string();
+ if(mask & CTRL) ret.append("Ctrl");
+ if(mask & SHIFT) {
+ if(!ret.empty()) ret.append("+");
+ ret.append("Shift");
+ }
+ if(mask & ALT) {
+ if(!ret.empty()) ret.append("+");
+ ret.append("Alt");
+ }
+ if(mask & SUPER) {
+ if(!ret.empty()) ret.append("+");
+ ret.append("Super");
+ }
+ if(mask & HYPER) {
+ if(!ret.empty()) ret.append("+");
+ ret.append("Hyper");
+ }
+ if(mask & META) {
+ if(!ret.empty()) ret.append("+");
+ ret.append("Meta");
+ }
+ return ret;
+}
+
+/**
+ * Set the responsive tooltip for this tool, given the selected types.
+ *
+ * @param message_context - The desktop's message context for showing tooltips
+ * @param event - The current event status (which keys are pressed)
+ * @param num_args - Number of Modifier::Type arguments to follow.
+ * @param ... - One or more Modifier::Type arguments.
+ */
+void responsive_tooltip(Inkscape::MessageContext *message_context, GdkEvent *event, int num_args, ...)
+{
+ std::string ctrl_msg = "Ctrl: ";
+ std::string shift_msg = "Shift: ";
+ std::string alt_msg = "Alt: ";
+
+ // NOTE: This will hide any keys changed to SUPER or multiple keys such as CTRL+SHIFT
+ va_list args;
+ va_start(args, num_args);
+ for(int i = 0; i < num_args; i++) {
+ auto modifier = Modifier::get(va_arg(args, Type));
+ auto name = std::string(modifier->get_name());
+ switch (modifier->get_and_mask()) {
+ case CTRL:
+ ctrl_msg += name + ", ";
+ break;
+ case SHIFT:
+ shift_msg += name + ", ";
+ break;
+ case ALT:
+ alt_msg += name + ", ";
+ break;
+ default:
+ g_warning("Unhandled responsivle tooltip: %s", name.c_str());
+ }
+ }
+ va_end(args);
+ ctrl_msg.erase(ctrl_msg.size() - 2);
+ shift_msg.erase(shift_msg.size() - 2);
+ alt_msg.erase(alt_msg.size() - 2);
+
+ Inkscape::UI::Tools::sp_event_show_modifier_tip(message_context, event,
+ ctrl_msg.c_str(), shift_msg.c_str(), alt_msg.c_str());
+}
+
+} // namespace Modifiers
+} // namespace Inkscape
+
+/*
+ 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:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/modifiers.h b/src/ui/modifiers.h
new file mode 100644
index 0000000000000000000000000000000000000000..98ac7e56dd5fd2e6bc7d01cce2896bcb47252d15
--- /dev/null
+++ b/src/ui/modifiers.h
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SP_MODIFIERS_H
+#define SEEN_SP_MODIFIERS_H
+/*
+ * Copyright (C) 2020 Martin Owens
+
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include
+#include
+#include
+#include
+#include