Compare commits

..
Author SHA1 Message Date
Kris Austin 0f59cd5d05 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.
2026-10-18 13:32:41 -05:00
yw4z c1fb801ff3 Linux: Show display backend of app on Troubleshoot dialog (#16615)
* init

* Update TroubleshootDialog.cpp

* fix build
2026-10-18 22:45:30 +08:00
16 changed files with 313 additions and 31 deletions

No files matched your search

+7
View File
@@ -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
+22
View File
@@ -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);
+7
View File
@@ -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
//
+4 -5
View File
@@ -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);
+34
View File
@@ -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
{
+3
View File
@@ -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;
+28 -16
View File
@@ -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);
+8 -1
View File
@@ -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;
+2
View File
@@ -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 -1
View File
@@ -4087,7 +4087,7 @@ void SelectMachineDialog::update_user_printer()
{
m_printer_last_select = "";
update_select_layout(nullptr);
m_printer_box->GetPrinterComboBox()->SetValue(_L("No available printers"));
m_printer_box->GetPrinterComboBox()->SetTextLabel("");
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "for send task, current printer id = " << m_printer_last_select << std::endl;
+1 -1
View File
@@ -1229,7 +1229,7 @@ void SendToPrinterDialog::update_user_printer()
}
else {
m_printer_last_select = "";
m_comboBox_printer->SetValue(_L("No available printers"));
m_comboBox_printer->SetTextLabel("");
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "for send task, current printer id = " << m_printer_last_select << std::endl;
+23 -1
View File
@@ -74,6 +74,7 @@
#endif
#ifdef __LINUX__
#include "LinuxDisplayBackend.hpp"
#include <wx/textfile.h>
#include <wx/tokenzr.h>
#include <cstdlib>
@@ -201,7 +202,11 @@ TroubleshootDialog::TroubleshootDialog()
return std::vector<wxString>{GetOStype()};
return std::vector<wxString>{
GetOSinfo(),
#ifdef __LINUX__
GetPackageType() + GetAppDisplayBackend(),
#else
GetPackageType(),
#endif
GetCPUinfo(),
GetRAMinfo() + " RAM",
GetGPUinfo(),
@@ -434,10 +439,15 @@ wxString TroubleshootDialog::GetTimestamp()
wxString TroubleshootDialog::GetSysInfoAll()
{
wxString package = GetPackageType();
#ifdef __LINUX__
package += GetAppDisplayBackend();
#endif
wxString info;
info += "Version : " + wxString(SoftFever_VERSION) + "\n"
+ "Build : " + wxString(build_commit_label) + "\n"
+ "Package : " + GetPackageType() + "\n"
+ "Package : " + package + "\n"
+ "Platform : " + GetOSinfo() + "\n"
+ "Processor : " + GetCPUinfo() + "\n"
+ "Memory : " + GetRAMinfo() + "RAM" + "\n"
@@ -751,6 +761,18 @@ wxString TroubleshootDialog::GetLinuxDisplayServer()
return "";
}
wxString TroubleshootDialog::GetAppDisplayBackend()
{
// Display backend actually used by the app (GDK), not the session type
wxString display;
switch (get_linux_display_backend()) {
case LinuxDisplayBackend::X11: display = "X11"; break;
case LinuxDisplayBackend::Wayland: display = "Wayland"; break;
default: break;
}
return display.IsEmpty() ? "" : (" (" + display + ")"); // spacing included here to prevent non centered text
}
#endif
wxString TroubleshootDialog::GetPackageType()
+1
View File
@@ -68,6 +68,7 @@ protected:
#elif defined(__LINUX__)
wxString GetLinuxDistroName();
wxString GetLinuxDisplayServer();
wxString GetAppDisplayBackend();
#endif
wxString GetPackageType();
wxString GetCPUinfo();
+3 -3
View File
@@ -287,7 +287,7 @@ void ComboBox::DoDeleteOneItem(unsigned int pos)
void ComboBox::ForceDropdownOpen()
{
if (!IsEnabled() || items.empty())
if (!IsEnabled())
return;
if (!drop_down) {
@@ -398,7 +398,7 @@ void ComboBox::mouseDown(wxMouseEvent &event)
SetFocus();
if (drop_down) {
drop.Hide();
} else if (!items.empty() && drop.HasDismissLongTime()) { // DropDown paints nothing for an empty list
} else if (drop.HasDismissLongTime()) {
drop.autoPosition();
drop_down = true;
drop.Popup(&drop);
@@ -426,7 +426,7 @@ void ComboBox::keyDown(wxKeyEvent& event)
case WXK_SPACE:
if (drop_down) {
drop.DismissAndNotify();
} else if (!items.empty() && drop.HasDismissLongTime()) {
} else if (drop.HasDismissLongTime()) {
drop.autoPosition();
drop_down = true;
drop.Popup();
+105 -3
View File
@@ -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));
}
+64
View File
@@ -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;