mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-18 23:08:25 -05:00
Let Global shortcuts run while typing, except keys that edit text (#16558)
#16505 skipped every Global shortcut while ImGui had the keyboard, so Ctrl+S, Ctrl+R and the others did nothing while the cursor was in the text tool's box. Skip only the chords that edit text, and only while a text field (a wx text entry, a spin control, a web view, or an active ImGui InputText) has the focus. Skip every Global shortcut while an ImGui modal is open on the focused canvas. KeyChord::edits_text() defines the text-editing chords. The Global page of the shortcuts dialog no longer says these shortcuts apply while typing.
This commit is contained in:
1 parent
c1fb801ff3
commit
0f59cd5d05
11 files changed
+284
-25
No files matched your search
@@ -53,6 +53,13 @@ Space counts as typing. The speed dial's default is the one bare Space, and
|
||||
`MainFrame` leaves it to a focused control that uses Space itself (text fields, buttons,
|
||||
combo boxes), so it opens the dial from the canvases and the tab strip only.
|
||||
|
||||
`MainFrame::handle_global_shortcut` skips a chord that `KeyChord::edits_text()` reports
|
||||
while a text field has the focus (a wx text entry, spin control, web view or ImGui
|
||||
InputText), and skips every chord while an ImGui modal is open, as a wx modal dialog
|
||||
would. `ImGuiWrapper::owns_key()` reads the active InputText and the popup stack
|
||||
directly, since `WantCaptureKeyboard` lags a frame, and is called only when the focus is on
|
||||
a visible canvas from `OpenGLManager::create_wxglcanvas()`.
|
||||
|
||||
## Which event a chord matches
|
||||
|
||||
Letters, digits and special keys match on `wxEVT_KEY_DOWN`. Its key codes do not depend
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
#include "Search.hpp"
|
||||
#include "BitmapCache.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "KeyChord.hpp"
|
||||
|
||||
#include <nanosvg/nanosvg.h>
|
||||
#include <nanosvg/nanosvgrast.h>
|
||||
@@ -2134,6 +2135,27 @@ bool ImGuiWrapper::want_any_input() const
|
||||
return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput;
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::text_input_active() const
|
||||
{
|
||||
const ImGuiID id = ImGui::GetActiveID();
|
||||
return id != 0 && ImGui::GetInputTextState(id) != nullptr;
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::modal_popup_open() const
|
||||
{
|
||||
// A modal stays on the popup stack while its canvas is hidden, so it counts only if the last finished
|
||||
// frame drew it, which NewFrame() moves from Active to WasActive.
|
||||
const ImGuiWindow* modal = ImGui::GetTopMostPopupModal();
|
||||
if (modal == nullptr)
|
||||
return false;
|
||||
return ImGui::GetCurrentContext()->WithinFrameScope ? modal->WasActive : modal->Active;
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::owns_key(const KeyChord& chord) const
|
||||
{
|
||||
return modal_popup_open() || (chord.edits_text() && text_input_active());
|
||||
}
|
||||
|
||||
ImFontAtlasCustomRect* ImGuiWrapper::GetTextureCustomRect(const wchar_t& tex_id)
|
||||
{
|
||||
auto item = m_custom_glyph_rects_ids.find(tex_id);
|
||||
|
||||
@@ -35,6 +35,7 @@ struct ImRect;
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
struct KeyChord;
|
||||
|
||||
bool get_data_from_svg(const std::string &filename, unsigned int max_size_px, ThumbnailData &thumbnail_data);
|
||||
|
||||
@@ -219,6 +220,12 @@ public:
|
||||
bool want_keyboard() const;
|
||||
bool want_text_input() const;
|
||||
bool want_any_input() const;
|
||||
// True while an ImGui InputText is active, without the one-frame lag of want_text_input().
|
||||
bool text_input_active() const;
|
||||
// True while an ImGui modal popup is on screen.
|
||||
bool modal_popup_open() const;
|
||||
// True for every key while a modal is open, and for text-editing keys while an InputText is active.
|
||||
bool owns_key(const KeyChord& chord) const;
|
||||
|
||||
// Optional inputs are used for set up value inside of an optional, with default value
|
||||
//
|
||||
|
||||
@@ -173,7 +173,7 @@ void KBShortcutsDialog::fill_pages()
|
||||
using Section = ShortcutSection;
|
||||
|
||||
if (wxGetApp().is_editor()) {
|
||||
page(_L("Global"), _L("Available anywhere in the window, even while typing in a text field."), ShortcutContext::Global, {
|
||||
page(_L("Global"), _L("Available anywhere in the window."), ShortcutContext::Global, {
|
||||
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.
|
||||
@@ -438,17 +438,16 @@ ShortcutCaptureDialog::ShortcutCaptureDialog(wxWindow* parent, Shortcut shortcut
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// A Global shortcut also runs while a text field has the focus, so its hint names the keys it can use.
|
||||
// A Global shortcut runs before the focused control gets the key, so its hint names the keys it can use.
|
||||
const bool global = (shortcut_info(shortcut).contexts & context_bit(ShortcutContext::Global)) != 0;
|
||||
const wxString advice = global_key_advice();
|
||||
const wxString typing = _L("Global shortcuts also apply while typing.");
|
||||
const wxString rule = _L("A key that types a character cannot be a global shortcut.");
|
||||
m_hint = global ? typing + "\n" + advice : _L("Esc cancels, Enter confirms.");
|
||||
m_hint = global ? advice : _L("Esc cancels, Enter confirms.");
|
||||
m_rejection = rule + "\n" + advice;
|
||||
|
||||
// Wide enough for each sentence on a line of its own where the translation allows, within limits.
|
||||
int width = FromDIP(450);
|
||||
for (const wxString& sentence : { typing, rule, advice }) {
|
||||
for (const wxString& sentence : { rule, advice }) {
|
||||
int extent = 0;
|
||||
GetTextExtent(sentence, &extent, nullptr, nullptr, nullptr, &wxGetApp().normal_font());
|
||||
width = std::max(width, extent);
|
||||
|
||||
@@ -187,6 +187,40 @@ bool KeyChord::is_menu_accelerator() const
|
||||
return valid() && ((modifiers & (wxMOD_CONTROL | wxMOD_ALT | wxMOD_RAW_CONTROL)) != 0 || (!is_printable(key) && key != WXK_SPACE));
|
||||
}
|
||||
|
||||
bool KeyChord::edits_text() const
|
||||
{
|
||||
if (!valid())
|
||||
return false;
|
||||
if (!is_menu_accelerator())
|
||||
return true;
|
||||
const int mods = modifiers & ~wxMOD_SHIFT;
|
||||
switch (key) {
|
||||
case WXK_LEFT: case WXK_RIGHT: case WXK_UP: case WXK_DOWN:
|
||||
case WXK_HOME: case WXK_END: case WXK_BACK: case WXK_DELETE:
|
||||
#ifdef __APPLE__
|
||||
return mods == wxMOD_NONE || mods == wxMOD_CONTROL || mods == wxMOD_ALT; // Option moves by word
|
||||
#else
|
||||
return mods == wxMOD_NONE || mods == wxMOD_CONTROL;
|
||||
#endif
|
||||
case WXK_INSERT:
|
||||
return mods == wxMOD_NONE || mods == wxMOD_CONTROL;
|
||||
case WXK_RETURN: case WXK_TAB: case WXK_ESCAPE: case WXK_PAGEUP: case WXK_PAGEDOWN:
|
||||
return mods == wxMOD_NONE;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!is_printable(key))
|
||||
return false;
|
||||
#ifdef __APPLE__
|
||||
if (mods == wxMOD_ALT || mods == wxMOD_RAW_CONTROL)
|
||||
return true; // Option types a character, Control runs the Cocoa text key bindings
|
||||
#else
|
||||
if (mods == (wxMOD_CONTROL | wxMOD_ALT))
|
||||
return true; // AltGr types a character
|
||||
#endif
|
||||
return mods == wxMOD_CONTROL && (key == 'A' || key == 'C' || key == 'V' || key == 'X' || key == 'Y' || key == 'Z');
|
||||
}
|
||||
|
||||
// Only a chord the desktop acts on while still delivering it to the app belongs here.
|
||||
bool KeyChord::is_system_shortcut() const
|
||||
{
|
||||
|
||||
@@ -35,6 +35,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 a focused text field acts on: typing a character (AltGr and macOS Option
|
||||
// characters included), moving the caret, selecting, deleting, or the clipboard and undo chords.
|
||||
bool edits_text() 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;
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
#include <wx/panel.h>
|
||||
#include <wx/settings.h>
|
||||
#include <wx/textentry.h>
|
||||
#include <wx/spinctrl.h>
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/listbook.h>
|
||||
#include <wx/simplebook.h>
|
||||
@@ -107,6 +108,8 @@
|
||||
#include "../Utils/PrintHost.hpp"
|
||||
|
||||
#include "GUI_App.hpp"
|
||||
#include "ImGuiWrapper.hpp"
|
||||
#include "OpenGLManager.hpp"
|
||||
#include "Shortcuts.hpp"
|
||||
#include "UnsavedChangesDialog.hpp"
|
||||
#include "PublishSettingsDialog.hpp"
|
||||
@@ -184,18 +187,31 @@ enum class ERescaleTarget
|
||||
|
||||
namespace {
|
||||
|
||||
bool focus_is_text_field(wxWindow* focus)
|
||||
{
|
||||
if (dynamic_cast<wxTextEntryBase*>(focus) || dynamic_cast<wxWebView*>(focus))
|
||||
return true; // a web page can have its own text fields
|
||||
if (dynamic_cast<wxSpinCtrl*>(focus) || dynamic_cast<wxSpinCtrlDouble*>(focus))
|
||||
return true; // the native Windows spin control is not a wxTextEntry
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only a scene canvas draws ImGui, and a hidden one can keep the wx focus after a tab switch.
|
||||
const ImGuiWrapper* focused_canvas_imgui(wxWindow* focus)
|
||||
{
|
||||
if (!OpenGLManager::is_scene_canvas(focus) || !focus->IsShownOnScreen())
|
||||
return nullptr;
|
||||
return wxGetApp().imgui();
|
||||
}
|
||||
|
||||
// Space opens the speed dial, but it is the activation key for buttons, checkboxes and other
|
||||
// controls. CHAR_HOOK runs before the focused child, so only take Space when the focused window has
|
||||
// no keyboard-activation meaning of its own. Canvases (GLCanvas3D) and panels are not controls and
|
||||
// fall through to "open"; the Notebook itself does too, so Space still opens the dial on any page.
|
||||
// controls, so only take Space when the focused window has no keyboard-activation meaning of its
|
||||
// own. Canvases (GLCanvas3D) and panels are not controls and fall through to "open"; the Notebook
|
||||
// itself does too, so Space still opens the dial on any page.
|
||||
bool focus_keeps_space(wxWindow* focus)
|
||||
{
|
||||
if (!focus)
|
||||
return false;
|
||||
if (dynamic_cast<wxTextEntryBase*>(focus))
|
||||
return true; // typing a space into a text field
|
||||
if (dynamic_cast<wxWebView*>(focus))
|
||||
return true; // web content scrolls and hosts its own text fields
|
||||
if (dynamic_cast<::Button*>(focus))
|
||||
return true; // custom button: Space clicks it (it is a wxWindow, not a wxControl)
|
||||
if (dynamic_cast<StaticBox*>(focus))
|
||||
@@ -816,16 +832,12 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
|
||||
bool MainFrame::handle_global_shortcut(const KeyChord& chord)
|
||||
{
|
||||
// ImGui draws its text fields (the text tool's box, a sketch value field) on the GL canvas, so
|
||||
// while one of them is typed into the focused wx window is that canvas or an in-canvas frame:
|
||||
// neither is a wxControl, so focus_keeps_space() cannot see it and CHAR_HOOK takes the chord
|
||||
// here first -- Space opened the Speed Dial, Ctrl+P the Preferences, and the field never saw
|
||||
// the key. ImGui raises WantCaptureKeyboard while one of its items owns the keyboard, so leave
|
||||
// the key to it then. A focused wxControl (text field, list, web view) is untouched: those
|
||||
// keep their own keys, and ImGui's flag can outlive the focus moving to one of them.
|
||||
// CHAR_HOOK runs before the focused window, so leave text-editing keys to a focused text field and
|
||||
// the keys ImGui owns to the focused canvas.
|
||||
wxWindow* const focus = wxWindow::FindFocus();
|
||||
ImGuiWrapper* const imgui = wxGetApp().imgui();
|
||||
if (focus != nullptr && imgui != nullptr && imgui->want_keyboard() && dynamic_cast<wxControl*>(focus) == nullptr)
|
||||
if (chord.edits_text() && focus_is_text_field(focus))
|
||||
return false;
|
||||
if (const ImGuiWrapper* imgui = focused_canvas_imgui(focus); imgui != nullptr && imgui->owns_key(chord))
|
||||
return false;
|
||||
|
||||
const std::optional<Shortcut> shortcut = wxGetApp().shortcuts().lookup(ShortcutContext::Global, chord);
|
||||
|
||||
@@ -427,6 +427,8 @@ wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas, const std::pair<i
|
||||
return m_context;
|
||||
}
|
||||
|
||||
static const char* const SCENE_CANVAS_NAME = "OrcaSceneCanvas";
|
||||
|
||||
wxGLCanvas* OpenGLManager::create_wxglcanvas(wxWindow& parent)
|
||||
{
|
||||
int antialiasing_samples = 4;
|
||||
@@ -470,12 +472,17 @@ wxGLCanvas* OpenGLManager::create_wxglcanvas(wxWindow& parent)
|
||||
attribList[samples_value_idx] = 0;
|
||||
}
|
||||
|
||||
wxGLCanvas* canvas = new wxGLCanvas(&parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS);
|
||||
wxGLCanvas* canvas = new wxGLCanvas(&parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS, SCENE_CANVAS_NAME);
|
||||
// The GL canvas paints its entire surface, so background erasing is unnecessary.
|
||||
canvas->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
bool OpenGLManager::is_scene_canvas(const wxWindow* window)
|
||||
{
|
||||
return window != nullptr && window->GetName() == SCENE_CANVAS_NAME;
|
||||
}
|
||||
|
||||
void OpenGLManager::detect_multisample(int* attribList)
|
||||
{
|
||||
int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER;
|
||||
|
||||
@@ -101,6 +101,8 @@ public:
|
||||
static bool are_framebuffers_supported() { return (s_framebuffers_type != EFramebufferType::Unknown); }
|
||||
static EFramebufferType get_framebuffers_type() { return s_framebuffers_type; }
|
||||
static wxGLCanvas* create_wxglcanvas(wxWindow& parent);
|
||||
// True for a canvas made by create_wxglcanvas(), the kind a GLCanvas3D draws its scene and ImGui into.
|
||||
static bool is_scene_canvas(const wxWindow* window);
|
||||
static const GLInfo& get_gl_info() { return s_gl_info; }
|
||||
static bool force_power_of_two_textures() { return s_force_power_of_two_textures; }
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// How ImGuiWrapper turns wx mouse events into ImGui input. ImGuiWrapper lives in libslic3r_gui; this is
|
||||
// the suite that links it. Same Windows include prologue as test_texture_color_palette.cpp (wx pulls in
|
||||
// <windows.h>; keep WIN32_LEAN_AND_MEAN / NOMINMAX ahead of it).
|
||||
// How ImGuiWrapper turns wx mouse events into ImGui input, and when it reports that ImGui owns a key.
|
||||
// ImGuiWrapper lives in libslic3r_gui; this is the suite that links it. Same Windows include prologue
|
||||
// as test_texture_color_palette.cpp (wx pulls in <windows.h>; keep WIN32_LEAN_AND_MEAN / NOMINMAX
|
||||
// ahead of it).
|
||||
#ifdef WIN32
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
@@ -17,12 +18,15 @@
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/imgui_internal.h>
|
||||
#include <wx/event.h>
|
||||
|
||||
#include "slic3r/GUI/ImGuiWrapper.hpp"
|
||||
#include "slic3r/GUI/KeyChord.hpp"
|
||||
|
||||
using Catch::Matchers::WithinAbs;
|
||||
using Slic3r::GUI::ImGuiWrapper;
|
||||
using Slic3r::GUI::KeyChord;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -41,6 +45,45 @@ float wheel_after(int rotation, wxMouseWheelAxis axis)
|
||||
return ImGui::GetIO().MouseWheel;
|
||||
}
|
||||
|
||||
// Runs ImGui frames without OpenGL by building the font atlas on the CPU.
|
||||
struct ImGuiFrames
|
||||
{
|
||||
ImGuiWrapper imgui;
|
||||
|
||||
ImGuiFrames()
|
||||
{
|
||||
imgui.set_display_size(800.f, 600.f);
|
||||
unsigned char* pixels = nullptr;
|
||||
int width = 0, height = 0;
|
||||
ImGui::GetIO().Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
|
||||
}
|
||||
|
||||
template<class Draw> void frame(Draw draw)
|
||||
{
|
||||
ImGui::NewFrame();
|
||||
draw();
|
||||
ImGui::EndFrame();
|
||||
}
|
||||
|
||||
// SetKeyboardFocusHere() activates the field in the next frame that draws it.
|
||||
void draw_input(bool focus)
|
||||
{
|
||||
ImGui::Begin("window");
|
||||
if (focus)
|
||||
ImGui::SetKeyboardFocusHere();
|
||||
ImGui::InputText("##text", text, sizeof(text));
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
char text[16] = "";
|
||||
};
|
||||
|
||||
void draw_modal()
|
||||
{
|
||||
if (ImGui::BeginPopupModal("modal"))
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("a vertical wheel event steps ImGui's wheel in its own direction", "[ImGuiWrapper]")
|
||||
@@ -59,3 +102,62 @@ TEST_CASE("a wheel event with no rotation or on the horizontal axis does not ste
|
||||
}));
|
||||
CHECK_THAT(wheel_after(rotation, axis), WithinAbs(0.0, 0.0));
|
||||
}
|
||||
|
||||
TEST_CASE("an InputText counts as active only while it holds the keyboard", "[ImGuiWrapper]")
|
||||
{
|
||||
ImGuiFrames f;
|
||||
f.frame([&] { f.draw_input(false); });
|
||||
CHECK_FALSE(f.imgui.text_input_active());
|
||||
|
||||
f.frame([&] { f.draw_input(true); });
|
||||
f.frame([&] { f.draw_input(false); });
|
||||
CHECK(f.imgui.text_input_active());
|
||||
|
||||
f.frame([] {});
|
||||
f.frame([] {});
|
||||
CHECK_FALSE(f.imgui.text_input_active());
|
||||
}
|
||||
|
||||
TEST_CASE("a modal counts as open from its first frame until a frame no longer draws it", "[ImGuiWrapper]")
|
||||
{
|
||||
ImGuiFrames f;
|
||||
CHECK_FALSE(f.imgui.modal_popup_open());
|
||||
|
||||
f.frame([] {
|
||||
ImGui::OpenPopup("modal");
|
||||
draw_modal();
|
||||
});
|
||||
CHECK(f.imgui.modal_popup_open());
|
||||
|
||||
// A frame opened by input, before anything is drawn in it.
|
||||
ImGui::NewFrame();
|
||||
CHECK(f.imgui.modal_popup_open());
|
||||
draw_modal();
|
||||
ImGui::EndFrame();
|
||||
CHECK(f.imgui.modal_popup_open());
|
||||
|
||||
// A frame that no longer draws it, as when its canvas is hidden.
|
||||
f.frame([] {});
|
||||
REQUIRE(ImGui::GetTopMostPopupModal() != nullptr);
|
||||
CHECK_FALSE(f.imgui.modal_popup_open());
|
||||
}
|
||||
|
||||
TEST_CASE("ImGui owns every key under a modal and text-editing keys in an active InputText", "[ImGuiWrapper]")
|
||||
{
|
||||
ImGuiFrames f;
|
||||
const KeyChord letter{ 'A' };
|
||||
const KeyChord save{ 'S', wxMOD_CONTROL };
|
||||
CHECK_FALSE(f.imgui.owns_key(letter));
|
||||
CHECK_FALSE(f.imgui.owns_key(save));
|
||||
|
||||
f.frame([&] { f.draw_input(true); });
|
||||
f.frame([&] { f.draw_input(false); });
|
||||
CHECK(f.imgui.owns_key(letter));
|
||||
CHECK_FALSE(f.imgui.owns_key(save));
|
||||
|
||||
f.frame([] {
|
||||
ImGui::OpenPopup("modal");
|
||||
draw_modal();
|
||||
});
|
||||
CHECK(f.imgui.owns_key(save));
|
||||
}
|
||||
@@ -126,6 +126,55 @@ TEST_CASE("Only modified or non-printable chords qualify as menu accelerators",
|
||||
CHECK(registry.accelerator(Shortcut::KeyboardShortcuts).empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Typing, caret, deletion and clipboard chords edit text", "[Shortcuts]")
|
||||
{
|
||||
CHECK(KeyChord{ 'A' }.edits_text());
|
||||
CHECK(KeyChord{ 'A', wxMOD_SHIFT }.edits_text());
|
||||
CHECK(KeyChord{ WXK_SPACE }.edits_text());
|
||||
CHECK(KeyChord{ WXK_BACK }.edits_text());
|
||||
CHECK(KeyChord{ WXK_DELETE }.edits_text());
|
||||
CHECK(KeyChord{ WXK_BACK, wxMOD_CONTROL }.edits_text());
|
||||
CHECK(KeyChord{ WXK_LEFT, wxMOD_SHIFT }.edits_text());
|
||||
CHECK(KeyChord{ WXK_RIGHT, wxMOD_CONTROL | wxMOD_SHIFT }.edits_text());
|
||||
CHECK(KeyChord{ WXK_HOME }.edits_text());
|
||||
CHECK(KeyChord{ WXK_END, wxMOD_CONTROL }.edits_text());
|
||||
CHECK(KeyChord{ WXK_DELETE, wxMOD_SHIFT }.edits_text());
|
||||
CHECK(KeyChord{ WXK_INSERT }.edits_text());
|
||||
CHECK(KeyChord{ WXK_INSERT, wxMOD_SHIFT }.edits_text());
|
||||
CHECK(KeyChord{ WXK_INSERT, wxMOD_CONTROL }.edits_text());
|
||||
CHECK(KeyChord{ WXK_PAGEUP, wxMOD_SHIFT }.edits_text());
|
||||
CHECK(KeyChord{ WXK_RETURN }.edits_text());
|
||||
CHECK(KeyChord{ WXK_TAB, wxMOD_SHIFT }.edits_text());
|
||||
CHECK(KeyChord{ WXK_ESCAPE }.edits_text());
|
||||
CHECK(KeyChord{ WXK_PAGEDOWN }.edits_text());
|
||||
for (int key : { 'A', 'C', 'V', 'X', 'Y', 'Z' })
|
||||
CHECK(KeyChord{ key, wxMOD_CONTROL }.edits_text());
|
||||
CHECK(KeyChord{ 'Z', wxMOD_CONTROL | wxMOD_SHIFT }.edits_text());
|
||||
|
||||
CHECK_FALSE(KeyChord{ WXK_TAB, wxMOD_CONTROL }.edits_text());
|
||||
CHECK_FALSE(KeyChord{ WXK_RETURN, wxMOD_CONTROL }.edits_text());
|
||||
CHECK_FALSE(KeyChord{ WXK_PAGEDOWN, wxMOD_CONTROL }.edits_text());
|
||||
CHECK_FALSE(KeyChord{ WXK_ESCAPE, wxMOD_CONTROL }.edits_text());
|
||||
CHECK_FALSE(KeyChord{ 'S', wxMOD_CONTROL }.edits_text());
|
||||
CHECK_FALSE(KeyChord{ 'S', wxMOD_CONTROL | wxMOD_SHIFT }.edits_text());
|
||||
CHECK_FALSE(KeyChord{ '0', wxMOD_CONTROL }.edits_text());
|
||||
CHECK_FALSE(KeyChord{ WXK_F5 }.edits_text());
|
||||
CHECK_FALSE(KeyChord{}.edits_text());
|
||||
#ifdef __APPLE__
|
||||
CHECK(KeyChord{ 'E', wxMOD_ALT }.edits_text());
|
||||
CHECK(KeyChord{ 'K', wxMOD_RAW_CONTROL }.edits_text());
|
||||
CHECK(KeyChord{ WXK_LEFT, wxMOD_ALT }.edits_text());
|
||||
CHECK(KeyChord{ WXK_LEFT, wxMOD_CONTROL }.edits_text());
|
||||
CHECK_FALSE(KeyChord{ WXK_LEFT, wxMOD_RAW_CONTROL }.edits_text());
|
||||
CHECK_FALSE(KeyChord{ 'E', wxMOD_CONTROL | wxMOD_ALT }.edits_text());
|
||||
#else
|
||||
CHECK(KeyChord{ 'Q', wxMOD_CONTROL | wxMOD_ALT }.edits_text());
|
||||
CHECK_FALSE(KeyChord{ 'E', wxMOD_ALT }.edits_text());
|
||||
CHECK_FALSE(KeyChord{ WXK_LEFT, wxMOD_ALT }.edits_text());
|
||||
CHECK_FALSE(KeyChord{ WXK_LEFT, wxMOD_CONTROL | wxMOD_ALT }.edits_text());
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("Chords the desktop keeps for itself are recognized", "[Shortcuts]")
|
||||
{
|
||||
#ifdef _WIN32
|
||||
@@ -186,6 +235,21 @@ TEST_CASE("Default bindings never collide inside a context", "[Shortcuts]")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("No default Global binding except the Speed Dial's Space edits text", "[Shortcuts]")
|
||||
{
|
||||
ShortcutRegistry registry;
|
||||
size_t checked = 0;
|
||||
for (size_t i = 0; i < size_t(Shortcut::Count); ++i) {
|
||||
const ShortcutInfo& info = shortcut_info(Shortcut(i));
|
||||
if ((info.contexts & context_bit(ShortcutContext::Global)) == 0 || info.id == Shortcut::SpeedDial)
|
||||
continue;
|
||||
CAPTURE(info.key);
|
||||
CHECK_FALSE(registry.binding(info.id).edits_text());
|
||||
++checked;
|
||||
}
|
||||
CHECK(checked > 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Shift and Ctrl variants of stepping shortcuts are left unbound", "[Shortcuts]")
|
||||
{
|
||||
ShortcutRegistry registry;
|
||||
|
||||
Reference in new issue
Block a user