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 + +#include + +#include "message-context.h" + +namespace Inkscape { +namespace Modifiers { + +using KeyMask = int; +using Trigger = int; + +enum Key : KeyMask { + NON_USER = -1, + SHIFT = GDK_SHIFT_MASK, + CTRL = GDK_CONTROL_MASK, + ALT = GDK_MOD1_MASK, + SUPER = GDK_SUPER_MASK, + HYPER = GDK_HYPER_MASK, + META = GDK_META_MASK, +}; +// Triggers used for collision warnings, two tools are using the same trigger +enum Triggers : Trigger {CLICK, DRAG, SCROLL, HANDLE}; +// TODO: We may want to further define the tool, from ANY, SELECT, NODE etc. + +/** + * This anonymous enum is used to provide a list of the Shifts + */ +enum class Type { + // {TOOL_NAME}_{ACTION_NAME} + + // Canvas tools (applies to any tool selection) + CANVAS_SCROLL_Y, // Scroll up and down {NOTHING+SCROLL} + CANVAS_SCROLL_X, // Scroll left and right {SHIFT+SCROLL} + CANVAS_ZOOM, // Zoom in and out {CTRL+SCROLL} + CANVAS_ROTATE, // Rotate CW and CCW {CTRL+SHIFT+SCROLL} + + // Select tool (minus transform) + SELECT_ADD_TO, // Add selection {SHIFT+CLICK} + SELECT_IN_GROUPS, // Select within groups {CTRL+CLICK} + SELECT_TOUCH_PATH, // Draw band to select {ALT+DRAG+Nothing selected} + SELECT_ALWAYS_BOX, // Draw box to select {SHIFT+DRAG} + SELECT_FIRST_HIT, // Start dragging first item hit {CTRL+DRAG} (Is this an actual feature?) + 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} + + // Transform handles (applies to multiple tools) + MOVE_CONFINE, // Limit dragging to X OR Y only {DRAG+CTRL} + MOVE_FIXED_RATIO, // Move objects by fixed amounts {HANDLE+ALT} + SCALE_CONFINE, // Confine resize aspect ratio {HANDLE+CTRL} + SCALE_FIXED_RATIO, // Resize by fixed ratio sizes {HANDLE+ALT} + TRANS_FIXED_RATIO, // Rotate/skew by fixed ratio angles {HANDLE+CTRL} + TRANS_OFF_CENTER, // Scale/Rotate/skew from oposite corner {HANDLE+SHIFT} + TRANS_SNAPPING, // Disable snapping while transforming {HANDLE+SHIFT} + // TODO: Alignment ommitted because it's UX is not completed +}; + + +// Generate a label such as Shift+Ctrl from any KeyMask +std::string generate_label(KeyMask mask); +// Generate a responsivle tooltip set +void responsive_tooltip(Inkscape::MessageContext *message_context, GdkEvent *event, int num_args, ...); + +/** + * A class to represent ways functionality is driven by shift modifiers + */ +class Modifier { +private: + /** An easy to use definition of the table of modifiers by Type and ID. */ + typedef std::map Container; + typedef std::map Lookup; + + /** A table of all the created modifers and their ID lookups. */ + static Container _modifiers; + static Lookup _modifier_lookup; + + char const * _id; // A unique id used by keys.xml to identify it + char const * _name; // A descriptive name used in preferences UI + char const * _desc; // A more verbose description used in preferences UI + + Trigger _trigger; // The type of trigger used for collisions + + // Default values if nothing is set in keys.xml + KeyMask _and_mask_default; // The pressed keys must have these bits set + + // User set data, set by keys.xml (or other included file) + KeyMask _and_mask_user = NON_USER; + +protected: + +public: + + char const * get_id() const { return _id; } + char const * get_name() const { return _name; } + char const * get_description() const { return _desc; } + const Trigger get_trigger() const { return _trigger; } + + // Set user value + bool is_set() const { return _and_mask_user != NON_USER; } + void set(KeyMask and_mask) { + _and_mask_user = and_mask; + } + void unset() { set(NON_USER); } + + // Get value, either user defined value or default + const KeyMask get_and_mask() { + if(_and_mask_user != NON_USER) { + return _and_mask_user; + } + return _and_mask_default; + } + // Generate labels such as "Shift+Ctrl" for the active modifier + std::string get_label() { return generate_label(get_and_mask()); } + + /** + * Inititalizes the Modifier with the parameters. + * + * @param id Goes to \c _id. + * @param name Goes to \c _name. + * @param desc Goes to \c _desc. + * @param default_ Goes to \c _default. + */ + Modifier(char const * id, + char const * name, + char const * desc, + const KeyMask and_mask, + const Trigger trigger) : + _id(id), + _name(name), + _desc(desc), + _and_mask_default(and_mask), + _trigger(trigger) + { + _modifier_lookup.emplace(_id, this); + } + // Delete the destructor, because we are eternal + ~Modifier() = delete; + + static std::vectorgetList (); + bool active(int button_state); + + /** + * A function to turn an enum index into a modifier object. + * + * @param index The enum index to be translated + * @return A pointer to a modifier object or a NULL if not found. + */ + static Modifier * get(Type index) { + return _modifiers[index]; + } + /** + * A function to turn a string id into a modifier object. + * + * @param id The id string to be translated + * @return A pointer to a modifier object or a NULL if not found. + */ + static Modifier * get(char const * id) { + Modifier *modifier = nullptr; + Lookup::iterator mod_found = _modifier_lookup.find(id); + + if (mod_found != _modifier_lookup.end()) { + modifier = mod_found->second; + } + + return modifier; + } + +}; // Modifier class + +} // namespace Modifiers +} // namespace Inkscape + + +#endif // SEEN_SP_MODIFIERS_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/ui/tools/select-tool.cpp b/src/ui/tools/select-tool.cpp index 0f5c7520a620cfd0652140c1d5a8fa98c76e95a0..b779fc18a8f7da0f712d7806f424df1b3c4e3032 100644 --- a/src/ui/tools/select-tool.cpp +++ b/src/ui/tools/select-tool.cpp @@ -45,6 +45,7 @@ #include "object/box3d.h" #include "style.h" +#include "ui/modifiers.h" #include "ui/pixmaps/cursor-select-d.xpm" #include "ui/pixmaps/cursor-select-m.xpm" #include "ui/pixmaps/handles.xpm" @@ -60,6 +61,7 @@ using Inkscape::DocumentUndo; +using Inkscape::Modifiers::Modifier; GdkPixbuf *handles[23]; @@ -157,12 +159,19 @@ SelectTool::~SelectTool() { void SelectTool::setup() { ToolBase::setup(); + auto select_click = Modifier::get(Modifiers::Type::SELECT_ADD_TO)->get_label(); + auto select_scroll = Modifier::get(Modifiers::Type::SELECT_CYCLE)->get_label(); + + auto no_selection_msg = g_strdup_printf( + _("No objects selected. Click, %s+click, %s+scroll mouse on top of objects, or drag around objects to select."), + select_click.c_str(), select_scroll.c_str()); + this->_describer = new Inkscape::SelectionDescriber( desktop->selection, desktop->messageStack(), - _("Click selection to toggle scale/rotation handles (or Shift+s)"), - _("No objects selected. Click, Shift+click, Alt+scroll mouse on top of objects, or drag around objects to select.") - ); + _("Click selection again to toggle scale/rotation handles."), + no_selection_msg); + free(no_selection_msg); // Is this right? this->_seltrans = new Inkscape::SelTrans(desktop); @@ -206,11 +215,6 @@ bool SelectTool::sp_select_context_abort() { } sp_object_unref( this->item, nullptr); - } else if (this->button_press_state & GDK_CONTROL_MASK) { - // NOTE: This is a workaround to a bug. - // When the ctrl key is held, sc->item is not defined - // so in this case (only), we skip the object doc check - DocumentUndo::undo(desktop->getDocument()); } this->item = nullptr; @@ -295,27 +299,28 @@ bool SelectTool::item_handler(SPItem* item, GdkEvent* event) { // remember what modifiers were on before button press this->button_press_state = event->button.state; + bool first_hit = Modifier::get(Modifiers::Type::SELECT_FIRST_HIT)->active(this->button_press_state); + bool force_drag = Modifier::get(Modifiers::Type::SELECT_FORCE_DRAG)->active(this->button_press_state); + bool always_box = Modifier::get(Modifiers::Type::SELECT_ALWAYS_BOX)->active(this->button_press_state); + + // if shift or ctrl was pressed, do not move objects; + // pass the event to root handler which will perform rubberband, shift-click, ctrl-click, ctrl-drag + if (!(always_box || first_hit)) { - if (event->button.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) { - // if shift or ctrl was pressed, do not move objects; - // pass the event to root handler which will perform rubberband, shift-click, ctrl-click, ctrl-drag - } else { GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (desktop->getCanvas()->gobj())); - + this->dragging = TRUE; this->moved = FALSE; gdk_window_set_cursor(window, CursorSelectDragging); - // remember the clicked item in this->item: if (this->item) { sp_object_unref(this->item, nullptr); this->item = nullptr; } - this->item = sp_event_context_find_item (desktop, - Geom::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, FALSE); + this->item = sp_event_context_find_item (desktop, Geom::Point(event->button.x, event->button.y), force_drag, FALSE); sp_object_ref(this->item, nullptr); rb_escaped = drag_escaped = 0; @@ -387,7 +392,7 @@ bool SelectTool::item_handler(SPItem* item, GdkEvent* event) { return ret; } -void SelectTool::sp_select_context_cycle_through_items(Inkscape::Selection *selection, GdkEventScroll *scroll_event, bool shift_pressed) { +void SelectTool::sp_select_context_cycle_through_items(Inkscape::Selection *selection, GdkEventScroll *scroll_event) { if ( this->cycling_items.empty() ) return; @@ -444,7 +449,7 @@ void SelectTool::sp_select_context_cycle_through_items(Inkscape::Selection *sele arenaitem = cycling_cur_item->get_arenaitem(desktop->dkey); arenaitem->setOpacity(1.0); - if (shift_pressed) { + if (Modifier::get(Modifiers::Type::SELECT_ADD_TO)->active(scroll_event->state)) { selection->add(cycling_cur_item); } else { selection->set(cycling_cur_item); @@ -515,7 +520,7 @@ bool SelectTool::root_handler(GdkEvent* event) { Geom::Point const button_pt(event->button.x, event->button.y); Geom::Point const p(desktop->w2d(button_pt)); - if (event->button.state & GDK_MOD1_MASK) { + if(Modifier::get(Modifiers::Type::SELECT_TOUCH_PATH)->active(event->button.state)) { Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_TOUCHPATH); } @@ -551,6 +556,10 @@ bool SelectTool::root_handler(GdkEvent* event) { { tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + bool first_hit = Modifier::get(Modifiers::Type::SELECT_FIRST_HIT)->active(this->button_press_state); + bool force_drag = Modifier::get(Modifiers::Type::SELECT_FORCE_DRAG)->active(this->button_press_state); + bool always_box = Modifier::get(Modifiers::Type::SELECT_ALWAYS_BOX)->active(this->button_press_state); + if ((event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { Geom::Point const motion_pt(event->motion.x, event->motion.y); Geom::Point const p(desktop->w2d(motion_pt)); @@ -564,7 +573,7 @@ bool SelectTool::root_handler(GdkEvent* event) { // motion notify coordinates as given (no snapping back to origin) within_tolerance = false; - if ((this->button_press_state & GDK_CONTROL_MASK) || ((this->button_press_state & GDK_MOD1_MASK) && !(this->button_press_state & GDK_SHIFT_MASK) && !selection->isEmpty())) { + if (first_hit || (force_drag && !always_box && !selection->isEmpty())) { // if it's not click and ctrl or alt was pressed (the latter with some selection // but not with shift) we want to drag rather than rubberband this->dragging = TRUE; @@ -589,7 +598,7 @@ bool SelectTool::root_handler(GdkEvent* event) { item_at_point = desktop->getItemAtPoint(Geom::Point(xp, yp), FALSE); } - if (item_at_point || this->moved || this->button_press_state & GDK_MOD1_MASK) { + if (item_at_point || this->moved || force_drag) { // drag only if starting from an item, or if something is already grabbed, or if alt-dragging if (!this->moved) { item_in_group = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE); @@ -612,8 +621,7 @@ bool SelectTool::root_handler(GdkEvent* event) { // if neither a group nor an item (possibly in a group) at point are selected, set selection to the item at point if ((!item_in_group || !selection->includes(item_in_group)) && - (!group_at_point || !selection->includes(group_at_point)) - && !(this->button_press_state & GDK_MOD1_MASK)) { + (!group_at_point || !selection->includes(group_at_point)) && !force_drag) { // select what is under cursor if (!_seltrans->isEmpty()) { _seltrans->resetState(); @@ -647,10 +655,13 @@ bool SelectTool::root_handler(GdkEvent* event) { if (Inkscape::Rubberband::get(desktop)->is_started()) { Inkscape::Rubberband::get(desktop)->move(p); + auto touch_path = Modifier::get(Modifiers::Type::SELECT_TOUCH_PATH)->get_label(); if (Inkscape::Rubberband::get(desktop)->getMode() == RUBBERBAND_MODE_TOUCHPATH) { - this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Draw over objects to select them; release Alt to switch to rubberband selection")); + this->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE, + _("Draw over objects to select them; release %s to switch to rubberband selection"), touch_path.c_str()); } else { - this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Drag around objects to select them; press Alt to switch to touch selection")); + this->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE, + _("Drag around objects to select them; press %s to switch to touch selection"), touch_path.c_str()); } gobble_motion_events(GDK_BUTTON1_MASK); @@ -676,7 +687,7 @@ bool SelectTool::root_handler(GdkEvent* event) { } else if (this->item && !drag_escaped) { // item has not been moved -> simply a click, do selecting if (!selection->isEmpty()) { - if (event->button.state & GDK_SHIFT_MASK) { + if(Modifier::get(Modifiers::Type::SELECT_ADD_TO)->active(event->button.state)) { // with shift, toggle selection _seltrans->resetState(); selection->toggle(this->item); @@ -728,7 +739,7 @@ bool SelectTool::root_handler(GdkEvent* event) { r->stop(); this->defaultMessageContext()->clear(); - if (event->button.state & GDK_SHIFT_MASK) { + if(Modifier::get(Modifiers::Type::SELECT_ADD_TO)->active(event->button.state)) { // with shift, add to selection selection->addList (items); } else { @@ -739,21 +750,21 @@ bool SelectTool::root_handler(GdkEvent* event) { } else { // it was just a click, or a too small rubberband r->stop(); - bool shift_release = (event->button.state & GDK_SHIFT_MASK) ? true : false; - bool ctrl_release = (event->button.state & GDK_CONTROL_MASK) ? true : false; - bool alt_release = (event->button.state & GDK_MOD1_MASK) ? true : false; + bool add_to = Modifier::get(Modifiers::Type::SELECT_ADD_TO)->active(event->button.state); + bool in_groups = Modifier::get(Modifiers::Type::SELECT_IN_GROUPS)->active(event->button.state); + bool force_drag = Modifier::get(Modifiers::Type::SELECT_FORCE_DRAG)->active(event->button.state); - if (shift_release && !rb_escaped && !drag_escaped) { + if (add_to && !rb_escaped && !drag_escaped) { // this was a shift+click or alt+shift+click, select what was clicked upon - if (ctrl_release) { - // go into groups, honoring Alt + if (in_groups) { + // go into groups, honoring force_drag (Alt) item = sp_event_context_find_item (desktop, - Geom::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, TRUE); + Geom::Point(event->button.x, event->button.y), force_drag, TRUE); } else { // don't go into groups, honoring Alt item = sp_event_context_find_item (desktop, - Geom::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, FALSE); + Geom::Point(event->button.x, event->button.y), force_drag, FALSE); } if (item) { @@ -761,9 +772,9 @@ bool SelectTool::root_handler(GdkEvent* event) { item = nullptr; } - } else if ((ctrl_release || alt_release) && !rb_escaped && !drag_escaped) { // ctrl+click, alt+click + } else if ((in_groups || force_drag) && !rb_escaped && !drag_escaped) { // ctrl+click, alt+click item = sp_event_context_find_item (desktop, - Geom::Point(event->button.x, event->button.y), alt_release, ctrl_release); + Geom::Point(event->button.x, event->button.y), force_drag, in_groups); if (item) { if (selection->includes(item)) { @@ -777,7 +788,7 @@ bool SelectTool::root_handler(GdkEvent* event) { } } else { // click without shift, simply deselect, unless with Alt or something was cancelled if (!selection->isEmpty()) { - if (!(rb_escaped) && !(drag_escaped) && !(alt_release)) { + if (!(rb_escaped) && !(drag_escaped) && !force_drag) { selection->clear(); } @@ -807,10 +818,10 @@ bool SelectTool::root_handler(GdkEvent* event) { GdkEventScroll *scroll_event = (GdkEventScroll*) event; - if ( ! (scroll_event->state & GDK_MOD1_MASK)) // do nothing specific if alt was not pressed + // do nothing specific if alt was not pressed + if ( ! Modifier::get(Modifiers::Type::SELECT_CYCLE)->active(scroll_event->state)) break; - bool shift_pressed = scroll_event->state & GDK_SHIFT_MASK; is_cycling = true; /* Rebuild list of items underneath the mouse pointer */ @@ -847,7 +858,7 @@ bool SelectTool::root_handler(GdkEvent* event) { this->cycling_wrap = prefs->getBool("/options/selection/cycleWrap", true); // Cycle through the items underneath the mouse pointer, one-by-one - this->sp_select_context_cycle_through_items(selection, scroll_event, shift_pressed); + this->sp_select_context_cycle_through_items(selection, scroll_event); ret = TRUE; @@ -884,11 +895,11 @@ bool SelectTool::root_handler(GdkEvent* event) { break; } } else { - sp_event_show_modifier_tip (this->defaultMessageContext(), event, - _("Ctrl: click to select in groups; drag to move hor/vert"), - _("Shift: click to toggle select; drag for rubberband selection"), - _("Alt: click to select under; scroll mouse-wheel to cycle-select; drag to move selected or select by touch")); - + Modifiers::responsive_tooltip(this->defaultMessageContext(), event, 6, + Modifiers::Type::SELECT_IN_GROUPS, Modifiers::Type::MOVE_CONFINE, + Modifiers::Type::SELECT_ADD_TO, Modifiers::Type::SELECT_TOUCH_PATH, + Modifiers::Type::SELECT_CYCLE, Modifiers::Type::SELECT_FORCE_DRAG); + // if Alt and nonempty selection, show moving cursor ("move selected"): if (alt && !selection->isEmpty() && !desktop->isWaitingCursor()) { GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (desktop->getCanvas()->gobj())); diff --git a/src/ui/tools/select-tool.h b/src/ui/tools/select-tool.h index 9fe85492f485221c9f8226feb6b17dc64ab1879d..d59da02942a858a3c84d9b0f5e6e607a9635108f 100644 --- a/src/ui/tools/select-tool.h +++ b/src/ui/tools/select-tool.h @@ -59,7 +59,7 @@ public: private: bool sp_select_context_abort(); - void sp_select_context_cycle_through_items(Inkscape::Selection *selection, GdkEventScroll *scroll_event, bool shift_pressed); + void sp_select_context_cycle_through_items(Inkscape::Selection *selection, GdkEventScroll *scroll_event); void sp_select_context_reset_opacities(); }; diff --git a/src/ui/tools/tool-base.cpp b/src/ui/tools/tool-base.cpp index c5033f1d31fa9c7b64f3615363ac0737a5568525..1f6598a7f46cf284e64589a27fd6f0a0b2d87414 100644 --- a/src/ui/tools/tool-base.cpp +++ b/src/ui/tools/tool-base.cpp @@ -41,6 +41,7 @@ #include "object/sp-guide.h" +#include "ui/modifiers.h" #include "ui/contextmenu.h" #include "ui/interface.h" #include "ui/event-debug.h" @@ -333,6 +334,11 @@ bool ToolBase::root_handler(GdkEvent* event) { bool allow_panning = prefs->getBool("/options/spacebarpans/value"); gint ret = FALSE; + bool mod_rotate = Modifiers::Modifier::get(Modifiers::Type::CANVAS_ROTATE)->active(event->scroll.state); + bool mod_zoom = Modifiers::Modifier::get(Modifiers::Type::CANVAS_ZOOM)->active(event->scroll.state); + bool mod_scroll_x = Modifiers::Modifier::get(Modifiers::Type::CANVAS_SCROLL_X)->active(event->scroll.state); + bool mod_scroll_y = Modifiers::Modifier::get(Modifiers::Type::CANVAS_SCROLL_Y)->active(event->scroll.state); + switch (event->type) { case GDK_2BUTTON_PRESS: if (panning) { @@ -370,7 +376,7 @@ bool ToolBase::root_handler(GdkEvent* event) { break; case 2: - if ((event->button.state & GDK_CONTROL_MASK) && !desktop->get_rotation_lock()) { + if (mod_rotate && !desktop->get_rotation_lock()) { // On screen canvas rotation preview // Grab background before doing anything else @@ -386,7 +392,7 @@ bool ToolBase::root_handler(GdkEvent* event) { Gdk::POINTER_MOTION_MASK, nullptr); // Cursor is null. - } else if (event->button.state & GDK_SHIFT_MASK) { + } else if (mod_zoom) { zoom_rb = 2; } else { // When starting panning, make sure there are no snap events pending because these might disable the panning again @@ -403,7 +409,7 @@ bool ToolBase::root_handler(GdkEvent* event) { break; case 3: - if ((event->button.state & GDK_SHIFT_MASK) || (event->button.state & GDK_CONTROL_MASK)) { + if (mod_scroll_x || mod_scroll_y) { // When starting panning, make sure there are no snap events pending because these might disable the panning again if (_uses_snap) { sp_event_context_discard_delayed_snap_event(this); @@ -524,8 +530,7 @@ bool ToolBase::root_handler(GdkEvent* event) { double const zoom_inc = prefs->getDoubleLimited( "/options/zoomincrement/value", M_SQRT2, 1.01, 10); - desktop->zoom_relative_keep_point(event_dt, (event->button.state - & GDK_SHIFT_MASK) ? 1 / zoom_inc : zoom_inc); + desktop->zoom_relative_keep_point(event_dt, zoom_inc); desktop->updateNow(); ret = TRUE; @@ -738,9 +743,6 @@ bool ToolBase::root_handler(GdkEvent* event) { break; case GDK_SCROLL: { - bool ctrl = (event->scroll.state & GDK_CONTROL_MASK); - bool shift = (event->scroll.state & GDK_SHIFT_MASK); - bool wheelzooms = prefs->getBool("/options/wheelzooms/value"); int constexpr WHEEL_SCROLL_DEFAULT = 40; int const wheel_scroll = prefs->getIntLimited( @@ -750,8 +752,7 @@ bool ToolBase::root_handler(GdkEvent* event) { gdouble delta_x = 0; gdouble delta_y = 0; - if ((ctrl & shift) && !desktop->get_rotation_lock()) { - /* ctrl + shift, rotate */ + if (mod_rotate && !desktop->get_rotation_lock()) { double rotate_inc = prefs->getDoubleLimited( "/options/rotateincrement/value", 15, 1, 90, "°" ); @@ -787,7 +788,7 @@ bool ToolBase::root_handler(GdkEvent* event) { desktop->rotate_relative_keep_point(scroll_dt, rotate_inc); } - } else if (shift && !ctrl) { + } else if (mod_scroll_x || mod_scroll_y) { /* shift + wheel, pan left--right */ switch (event->scroll.direction) { @@ -815,7 +816,7 @@ bool ToolBase::root_handler(GdkEvent* event) { break; } - } else if ((ctrl && !wheelzooms) || (!ctrl && wheelzooms)) { + } else if (mod_zoom) { /* ctrl + wheel, zoom in--out */ double rel_zoom; double const zoom_inc = prefs->getDoubleLimited( diff --git a/src/widgets/desktop-widget.cpp b/src/widgets/desktop-widget.cpp index d53f534a91c8d8c7ad65d1769bc9e0a7279af208..136416e4172d8654fb68c7d59e08b548886b7c3f 100644 --- a/src/widgets/desktop-widget.cpp +++ b/src/widgets/desktop-widget.cpp @@ -38,6 +38,7 @@ #include "file.h" #include "inkscape-version.h" #include "verbs.h" +#include "shortcuts.h" #include "display/control/canvas-axonomgrid.h" #include "display/control/canvas-item-drawing.h" @@ -1418,6 +1419,9 @@ SPDesktopWidget::SPDesktopWidget(SPDocument *document) dtw->_ruler_origin = Geom::Point(0,0); //namedview->gridorigin; Why was the grid origin used here? + // Loading the shortcuts first will allow initial strings to be correct. + sp_shortcut_ensure_init(); + // This section seems backwards! dtw->desktop = new SPDesktop(); dtw->desktop->init (namedview, dtw->_canvas, this);