diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 0b16212567..71caeb9ee3 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1088,8 +1088,6 @@ wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_SLIDERS, wxKeyEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_JUMP_TO, wxKeyEvent); wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_SWITCH_TO_OBJECT, SimpleEvent); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 663b72a1d3..57912ee7cd 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -185,8 +185,6 @@ wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); -wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_SLIDERS, wxKeyEvent); -wxDECLARE_EVENT(EVT_GLCANVAS_JUMP_TO, wxKeyEvent); wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_SWITCH_TO_OBJECT, SimpleEvent); diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index c03d331627..c9fdfe4ab4 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -159,8 +159,12 @@ void KBShortcutsDialog::fill_pages() if (wxGetApp().is_editor()) { page(_L("Global"), _L("Available anywhere in the window, even while typing in a text field."), ShortcutContext::Global, { - fixed(Section::Application, { alt, "1-9, 0" }, L("Run a speed dial favorite while the dial is open")), + fixed(Section::SpeedDial, { alt, "1-9, 0" }, L("Run favorite 1 to 10")), + fixed(Section::SpeedDial, { ctrl, "B" }, L("Pin or unpin the selected action")), + // wx cycles notebook pages on Ctrl+Tab, which is Cmd+Tab on macOS and never arrives there. +#ifndef __APPLE__ fixed(Section::Application, { ctrl, key(L_CONTEXT("Tab", "Keyboard Shortcut")) }, L("Switch to the next main tab")), +#endif }); page(_L("Prepare"), _L("Available while the 3D view on the Prepare tab has focus."), ShortcutContext::Plater, { @@ -454,7 +458,10 @@ ShortcutCaptureDialog::ShortcutCaptureDialog(wxWindow* parent, Shortcut shortcut capture_sizer->Add(m_chord_label, 0, wxALIGN_CENTER); capture_sizer->AddStretchSpacer(); capture->SetSizer(capture_sizer); - capture->Bind(wxEVT_KEY_DOWN, &ShortcutCaptureDialog::on_key, this); + capture->Layout(); // the box is created at its final size, so nothing resizes it into laying the sizer out + // The hook runs before the window procedure, so Windows does not open its window menu + // over the dialog on Alt+Space. + Bind(wxEVT_CHAR_HOOK, &ShortcutCaptureDialog::on_key, this); capture->Bind(wxEVT_CHAR, &ShortcutCaptureDialog::on_char, this); capture->Bind(wxEVT_LEFT_DOWN, [capture](wxMouseEvent&) { capture->SetFocus(); }); sizer->Add(capture, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(20)); @@ -531,10 +538,12 @@ void ShortcutCaptureDialog::record(const KeyChord& chord) m_ok->Enable(false); }; const bool global = (shortcut_info(m_shortcut).contexts & context_bit(ShortcutContext::Global)) != 0; - if (global && !chord.is_menu_accelerator()) { + if (chord.is_system_shortcut()) { + reject(_L("The system uses this shortcut, so it cannot be assigned.")); + } else if (global && !chord.is_menu_accelerator()) { reject(m_rejection); } else if (const std::optional owner = wxGetApp().shortcuts().step_owner(m_shortcut, chord); owner.has_value()) { - reject(wxString::Format(_L("Already used as a step of %s."), _(shortcut_info(*owner).name))); + reject(wxString::Format(_L("Shift and Ctrl with this key belong to %s and cannot be assigned."), _(shortcut_info(*owner).name))); } else { m_conflicts = wxGetApp().shortcuts().conflicts(m_shortcut, chord); m_status->SetForegroundColour(m_status_colour); diff --git a/src/slic3r/GUI/KeyChord.cpp b/src/slic3r/GUI/KeyChord.cpp index cd746d2f92..f6aeacd3ce 100644 --- a/src/slic3r/GUI/KeyChord.cpp +++ b/src/slic3r/GUI/KeyChord.cpp @@ -182,6 +182,16 @@ bool KeyChord::is_menu_accelerator() const return valid() && ((modifiers & (wxMOD_CONTROL | wxMOD_ALT | wxMOD_RAW_CONTROL)) != 0 || (!is_printable(key) && key != WXK_SPACE)); } +// Only a chord the desktop acts on while still delivering it to the app belongs here. +bool KeyChord::is_system_shortcut() const +{ +#ifdef _WIN32 + return modifiers == wxMOD_ALT && (key == WXK_F4 || key == WXK_SPACE); +#else + return false; +#endif +} + std::string KeyChord::to_string() const { if (!valid()) diff --git a/src/slic3r/GUI/KeyChord.hpp b/src/slic3r/GUI/KeyChord.hpp index c7884409eb..c2cfb88883 100644 --- a/src/slic3r/GUI/KeyChord.hpp +++ b/src/slic3r/GUI/KeyChord.hpp @@ -34,6 +34,9 @@ struct KeyChord // True when Ctrl or Alt is held or the key is non-printable, the chords a menu can own without // swallowing typing in text fields. bool is_menu_accelerator() const; + // True for a chord the desktop acts on although the app receives it, so a binding would + // take it from the system. + bool is_system_shortcut() const; // Platform-neutral text ("Ctrl+Shift+S") for persistence and wx accelerator strings. std::string to_string() const; diff --git a/src/slic3r/GUI/Shortcuts.cpp b/src/slic3r/GUI/Shortcuts.cpp index 0bcf0ccef9..be713b7cf8 100644 --- a/src/slic3r/GUI/Shortcuts.cpp +++ b/src/slic3r/GUI/Shortcuts.cpp @@ -147,9 +147,11 @@ constexpr std::array shortcut_table = {{ SHORTCUT(Search, "search", L("Search"), GLOBAL, { 'F', CTRL }), SHORTCUT(SwitchView, "switch_view", L("Switch between Prepare/Preview"), CANVAS, { WXK_TAB }), SHORTCUT(CollapseSidebar, "collapse_sidebar", L("Collapse/Expand the sidebar"), CANVAS, { WXK_TAB, SHIFT }), - SHORTCUT(SpeedDial, "speed_dial", L("Open the speed dial"), GLOBAL, { WXK_SPACE }), SHORTCUT(ReloadDevicePage, "reload_device_page", L("Reload the device page"), CANVAS, { WXK_F5 }), SHORTCUT(KeyboardShortcuts, "keyboard_shortcuts", L("Show keyboard shortcuts list"), CANVAS, { '?' }), + + // Speed Dial + SHORTCUT(SpeedDial, "speed_dial", L("Open the Speed Dial"), GLOBAL, { WXK_SPACE }), }}; #undef SHORTCUT @@ -177,6 +179,7 @@ constexpr std::array section_table { Shortcut::ViewDefault, L("Camera") }, { Shortcut::ShowLabels, L("Display") }, { Shortcut::Preferences, L("Application") }, + { Shortcut::SpeedDial, L("Speed Dial") }, }}; constexpr bool sections_follow_table_order() diff --git a/src/slic3r/GUI/Shortcuts.hpp b/src/slic3r/GUI/Shortcuts.hpp index ba002556ca..71fe299a98 100644 --- a/src/slic3r/GUI/Shortcuts.hpp +++ b/src/slic3r/GUI/Shortcuts.hpp @@ -47,7 +47,9 @@ enum class Shortcut : uint8_t { // Display ShowLabels, ShowWireframe, ToggleGcodeWindow, ToggleOneLayerMode, // Application - Preferences, Search, SwitchView, CollapseSidebar, SpeedDial, ReloadDevicePage, KeyboardShortcuts, + Preferences, Search, SwitchView, CollapseSidebar, ReloadDevicePage, KeyboardShortcuts, + // Speed Dial + SpeedDial, Count }; @@ -66,7 +68,7 @@ struct ShortcutInfo // Headings of the shortcuts dialog, in listing order. enum class ShortcutSection : uint8_t { - Project, SlicingAndPrinting, Selection, Editing, Objects, Placement, Gizmos, Sliders, PaintingTools, Camera, Display, Application, + Project, SlicingAndPrinting, Selection, Editing, Objects, Placement, Gizmos, Sliders, PaintingTools, Camera, Display, Application, SpeedDial, Count }; diff --git a/tests/slic3rutils/test_shortcuts.cpp b/tests/slic3rutils/test_shortcuts.cpp index 7826a0610a..350e87f48a 100644 --- a/tests/slic3rutils/test_shortcuts.cpp +++ b/tests/slic3rutils/test_shortcuts.cpp @@ -120,6 +120,20 @@ TEST_CASE("Only modified or non-printable chords qualify as menu accelerators", CHECK(registry.accelerator(Shortcut::KeyboardShortcuts).empty()); } +TEST_CASE("Chords the desktop keeps for itself are recognized", "[Shortcuts]") +{ +#ifdef _WIN32 + CHECK(KeyChord{ WXK_F4, wxMOD_ALT }.is_system_shortcut()); + CHECK(KeyChord{ WXK_SPACE, wxMOD_ALT }.is_system_shortcut()); +#else + CHECK_FALSE(KeyChord{ WXK_F4, wxMOD_ALT }.is_system_shortcut()); + CHECK_FALSE(KeyChord{ WXK_SPACE, wxMOD_ALT }.is_system_shortcut()); +#endif + CHECK_FALSE(KeyChord{ WXK_F4, wxMOD_ALT | wxMOD_SHIFT }.is_system_shortcut()); + CHECK_FALSE(KeyChord{ WXK_F4, wxMOD_CONTROL }.is_system_shortcut()); + CHECK_FALSE(KeyChord{ WXK_SPACE }.is_system_shortcut()); +} + TEST_CASE("Chords convert to wx accelerator entries", "[Shortcuts]") { const wxAcceleratorEntry entry = KeyChord{ 'S', wxMOD_CONTROL | wxMOD_SHIFT }.to_accelerator_entry(42);