mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-05 23:01:17 +00:00
* Add Missing Includes Across src/slic3r/GUI Every GUI source and header now directly includes the headers declaring what it uses, rather than relying on the precompiled header or transitive includes. Generated with clang-tidy misc-include-cleaner, plus one hand edit making CalibrationPanel.hpp self-contained. * Drop the OS-Specific Includes Added Outside Their Platform Guards GLib, GTK, D-Bus and POSIX headers are only used inside platform #if blocks, which already include them. Added unconditionally at the top of the file they broke the Windows build. * Add the clang-tidy Configuration That Generated These Includes Only misc-include-cleaner's missing-include check, with the headers it must never suggest: per-platform, internal and OS-specific ones that would break other platforms or are not meant to be included directly. * Match Windows Paths in the clang-tidy Ignore List Header paths use backslashes on Windows, so every / in a pattern is now [/\\]. The Windows SDK headers are ignored alongside the other OS-specific ones, and the list is one pattern per line. Suggested by @raistlin7447 from a Windows clang-cl run.
330 lines
11 KiB
C++
330 lines
11 KiB
C++
#include "KeyChord.hpp"
|
|
|
|
#include "GUI.hpp"
|
|
#include "I18N.hpp"
|
|
|
|
#include <wx/accel.h>
|
|
#include <string>
|
|
#include <cstddef>
|
|
#include <optional>
|
|
#include <vector>
|
|
#include <wx/event.h>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cctype>
|
|
|
|
namespace Slic3r { namespace GUI {
|
|
|
|
namespace {
|
|
|
|
constexpr int BINDABLE_MODIFIERS = wxMOD_CONTROL | wxMOD_SHIFT | wxMOD_ALT | wxMOD_RAW_CONTROL;
|
|
|
|
struct KeyName
|
|
{
|
|
int key;
|
|
const char* name; // canonical name, as wx parses it
|
|
const char* alias; // accepted when parsing; nullptr when there is none
|
|
const char* label; // translation key for KeyChord::display(); nullptr when name is it
|
|
};
|
|
|
|
constexpr std::array<KeyName, 15> special_keys{{
|
|
{ WXK_BACK, L_CONTEXT("Backspace", "Keyboard Shortcut"), "Back", nullptr },
|
|
{ WXK_TAB, L_CONTEXT("Tab", "Keyboard Shortcut"), nullptr, nullptr },
|
|
{ WXK_RETURN, L_CONTEXT("Enter", "Keyboard Shortcut"), "Return", nullptr },
|
|
{ WXK_ESCAPE, L_CONTEXT("Esc", "Keyboard Shortcut"), "Escape", nullptr },
|
|
{ WXK_SPACE, L_CONTEXT("Space", "Keyboard Shortcut"), nullptr, nullptr },
|
|
{ WXK_DELETE, L_CONTEXT("Del", "Keyboard Shortcut"), "Delete", nullptr },
|
|
{ WXK_INSERT, L_CONTEXT("Ins", "Keyboard Shortcut"), "Insert", nullptr },
|
|
{ WXK_HOME, L_CONTEXT("Home", "Keyboard Shortcut"), nullptr, nullptr },
|
|
{ WXK_END, L_CONTEXT("End", "Keyboard Shortcut"), nullptr, nullptr },
|
|
{ WXK_PAGEUP, L_CONTEXT("PgUp", "Keyboard Shortcut"), "PageUp", nullptr },
|
|
{ WXK_PAGEDOWN, L_CONTEXT("PgDn", "Keyboard Shortcut"), "PageDown", nullptr },
|
|
// Displayed as "Arrow Left" and so on, which is what the catalogs translate.
|
|
{ WXK_LEFT, "Left", nullptr, L_CONTEXT("Arrow Left", "Keyboard Shortcut") },
|
|
{ WXK_RIGHT, "Right", nullptr, L_CONTEXT("Arrow Right", "Keyboard Shortcut") },
|
|
{ WXK_UP, "Up", nullptr, L_CONTEXT("Arrow Up", "Keyboard Shortcut") },
|
|
{ WXK_DOWN, "Down", nullptr, L_CONTEXT("Arrow Down", "Keyboard Shortcut") },
|
|
}};
|
|
|
|
bool equals_ignoring_case(const std::string& a, const char* b)
|
|
{
|
|
if (b == nullptr)
|
|
return false;
|
|
size_t i = 0;
|
|
for (; i < a.size() && b[i] != '\0'; ++i)
|
|
if (std::tolower(static_cast<unsigned char>(a[i])) != std::tolower(static_cast<unsigned char>(b[i])))
|
|
return false;
|
|
return i == a.size() && b[i] == '\0';
|
|
}
|
|
|
|
bool is_letter(int key) { return key >= 'A' && key <= 'Z'; }
|
|
bool is_digit(int key) { return key >= '0' && key <= '9'; }
|
|
bool is_printable(int key) { return key > ' ' && key < 127; }
|
|
bool is_symbol(int key) { return is_printable(key) && !std::isalnum(key); }
|
|
bool is_function_key(int key) { return key >= WXK_F1 && key <= WXK_F24; }
|
|
|
|
bool is_special(int key)
|
|
{
|
|
if (is_function_key(key))
|
|
return true;
|
|
return std::any_of(special_keys.begin(), special_keys.end(), [key](const KeyName& k) { return k.key == key; });
|
|
}
|
|
|
|
std::string special_key_name(int key)
|
|
{
|
|
if (is_function_key(key))
|
|
return "F" + std::to_string(key - WXK_F1 + 1);
|
|
for (const KeyName& k : special_keys)
|
|
if (k.key == key)
|
|
return k.name;
|
|
return {};
|
|
}
|
|
|
|
std::string special_key_label(int key)
|
|
{
|
|
for (const KeyName& k : special_keys)
|
|
if (k.key == key)
|
|
return _u8L_CONTEXT(k.label != nullptr ? k.label : k.name, "Keyboard Shortcut");
|
|
return special_key_name(key);
|
|
}
|
|
|
|
int parse_key(const std::string& text)
|
|
{
|
|
if (text.size() == 1) {
|
|
const int key = static_cast<unsigned char>(text[0]);
|
|
return is_printable(key) ? std::toupper(key) : WXK_NONE;
|
|
}
|
|
for (const KeyName& k : special_keys)
|
|
if (equals_ignoring_case(text, k.name) || equals_ignoring_case(text, k.alias))
|
|
return k.key;
|
|
if ((text[0] == 'F' || text[0] == 'f') && text.size() <= 3 && std::all_of(text.begin() + 1, text.end(), [](char c) { return std::isdigit(static_cast<unsigned char>(c)); })) {
|
|
const int n = std::stoi(text.substr(1));
|
|
if (n >= 1 && n <= 24)
|
|
return WXK_F1 + n - 1;
|
|
}
|
|
return WXK_NONE;
|
|
}
|
|
|
|
int parse_modifier(const std::string& text)
|
|
{
|
|
if (equals_ignoring_case(text, "Ctrl") || equals_ignoring_case(text, "Control") || equals_ignoring_case(text, "Cmd") || equals_ignoring_case(text, "Command"))
|
|
return wxMOD_CONTROL;
|
|
if (equals_ignoring_case(text, "Shift"))
|
|
return wxMOD_SHIFT;
|
|
if (equals_ignoring_case(text, "Alt") || equals_ignoring_case(text, "Option"))
|
|
return wxMOD_ALT;
|
|
if (equals_ignoring_case(text, "RawCtrl"))
|
|
return wxMOD_RAW_CONTROL;
|
|
return wxMOD_NONE;
|
|
}
|
|
|
|
// Numpad keys act as their main-keyboard counterparts, so one binding covers both.
|
|
int fold_numpad(int key)
|
|
{
|
|
if (key >= WXK_NUMPAD0 && key <= WXK_NUMPAD9)
|
|
return '0' + (key - WXK_NUMPAD0);
|
|
switch (key) {
|
|
case WXK_NUMPAD_ENTER: return WXK_RETURN;
|
|
case WXK_NUMPAD_SPACE: return WXK_SPACE;
|
|
case WXK_NUMPAD_TAB: return WXK_TAB;
|
|
case WXK_NUMPAD_HOME: return WXK_HOME;
|
|
case WXK_NUMPAD_END: return WXK_END;
|
|
case WXK_NUMPAD_PAGEUP: return WXK_PAGEUP;
|
|
case WXK_NUMPAD_PAGEDOWN: return WXK_PAGEDOWN;
|
|
case WXK_NUMPAD_LEFT: return WXK_LEFT;
|
|
case WXK_NUMPAD_RIGHT: return WXK_RIGHT;
|
|
case WXK_NUMPAD_UP: return WXK_UP;
|
|
case WXK_NUMPAD_DOWN: return WXK_DOWN;
|
|
case WXK_NUMPAD_INSERT: return WXK_INSERT;
|
|
case WXK_NUMPAD_DELETE: return WXK_DELETE;
|
|
case WXK_NUMPAD_ADD: return '+';
|
|
case WXK_NUMPAD_SUBTRACT: return '-';
|
|
case WXK_NUMPAD_MULTIPLY: return '*';
|
|
case WXK_NUMPAD_DIVIDE: return '/';
|
|
case WXK_NUMPAD_DECIMAL: return '.';
|
|
case WXK_NUMPAD_EQUAL: return '=';
|
|
default: return key;
|
|
}
|
|
}
|
|
|
|
// Modifiers in the order the text forms list them; wxMOD_RAW_CONTROL is wxMOD_CONTROL off macOS.
|
|
#ifdef __APPLE__
|
|
constexpr std::array<int, 4> MODIFIER_ORDER{ wxMOD_CONTROL, wxMOD_SHIFT, wxMOD_ALT, wxMOD_RAW_CONTROL };
|
|
#else
|
|
constexpr std::array<int, 3> MODIFIER_ORDER{ wxMOD_CONTROL, wxMOD_SHIFT, wxMOD_ALT };
|
|
#endif
|
|
|
|
const char* canonical_modifier_prefix(int modifier)
|
|
{
|
|
if (modifier == wxMOD_CONTROL)
|
|
return "Ctrl+";
|
|
if (modifier == wxMOD_SHIFT)
|
|
return "Shift+";
|
|
if (modifier == wxMOD_ALT)
|
|
return "Alt+";
|
|
return "RawCtrl+";
|
|
}
|
|
|
|
template<typename Prefix>
|
|
std::string join_modifiers(int modifiers, Prefix prefix)
|
|
{
|
|
std::string out;
|
|
for (int modifier : MODIFIER_ORDER)
|
|
if (modifiers & modifier)
|
|
out += prefix(modifier);
|
|
return out;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool KeyChord::is_punctuation() const { return is_symbol(key) && modifiers == wxMOD_NONE; }
|
|
|
|
bool KeyChord::needs_char_event() const { return is_symbol(key) && (modifiers & ~wxMOD_SHIFT) == 0; }
|
|
|
|
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())
|
|
return {};
|
|
std::string out = join_modifiers(modifiers, canonical_modifier_prefix);
|
|
if (is_printable(key))
|
|
out += char(key);
|
|
else
|
|
out += special_key_name(key);
|
|
return out;
|
|
}
|
|
|
|
std::optional<KeyChord> KeyChord::parse(const std::string& text)
|
|
{
|
|
if (text.empty())
|
|
return std::nullopt;
|
|
|
|
// The key is whatever follows the last separator; a trailing '+' is the '+' key itself.
|
|
size_t key_start = text.size() - 1;
|
|
if (text.back() != '+') {
|
|
const size_t sep = text.rfind('+');
|
|
key_start = sep == std::string::npos ? 0 : sep + 1;
|
|
}
|
|
KeyChord chord;
|
|
chord.key = parse_key(text.substr(key_start));
|
|
if (chord.key == WXK_NONE)
|
|
return std::nullopt;
|
|
|
|
const std::string prefix = key_start == 0 ? std::string() : text.substr(0, key_start - 1);
|
|
size_t begin = 0;
|
|
while (begin < prefix.size()) {
|
|
size_t end = prefix.find('+', begin);
|
|
if (end == std::string::npos)
|
|
end = prefix.size();
|
|
const int modifier = parse_modifier(prefix.substr(begin, end - begin));
|
|
if (modifier == wxMOD_NONE)
|
|
return std::nullopt;
|
|
chord.modifiers |= modifier;
|
|
begin = end + 1;
|
|
}
|
|
if (is_letter(chord.key) || is_digit(chord.key) || !is_printable(chord.key) || chord.modifiers == wxMOD_NONE)
|
|
return chord;
|
|
// Shift is folded into the character for punctuation, so "Shift+/" is not accepted.
|
|
return (chord.modifiers & wxMOD_SHIFT) ? std::nullopt : std::optional<KeyChord>(chord);
|
|
}
|
|
|
|
std::string KeyChord::display() const
|
|
{
|
|
std::string out;
|
|
for (const std::string& part : display_parts())
|
|
out += (out.empty() ? "" : "+") + part;
|
|
return out;
|
|
}
|
|
|
|
std::vector<std::string> KeyChord::display_parts() const
|
|
{
|
|
std::vector<std::string> parts;
|
|
if (!valid())
|
|
return parts;
|
|
for (int modifier : MODIFIER_ORDER)
|
|
if (modifiers & modifier)
|
|
parts.push_back(modifier_name(modifier));
|
|
parts.push_back(is_printable(key) ? std::string(1, char(key)) : special_key_label(key));
|
|
return parts;
|
|
}
|
|
|
|
std::string KeyChord::modifier_prefix(int modifier)
|
|
{
|
|
if (modifier == wxMOD_CONTROL)
|
|
return shortkey_ctrl_prefix();
|
|
if (modifier == wxMOD_SHIFT)
|
|
return _u8L("Shift+");
|
|
if (modifier == wxMOD_ALT)
|
|
return shortkey_alt_prefix();
|
|
#ifdef __APPLE__
|
|
if (modifier == wxMOD_RAW_CONTROL)
|
|
return u8"⌃+";
|
|
#endif
|
|
return {};
|
|
}
|
|
|
|
// The catalogue holds the "Ctrl+" prefixes, so the bare name is the prefix without its "+"
|
|
// and any space before it ("Strg +" in German).
|
|
std::string KeyChord::modifier_name(int modifier)
|
|
{
|
|
std::string name = modifier_prefix(modifier);
|
|
if (!name.empty() && name.back() == '+')
|
|
name.pop_back();
|
|
while (!name.empty() && name.back() == ' ')
|
|
name.pop_back();
|
|
return name;
|
|
}
|
|
|
|
wxAcceleratorEntry KeyChord::to_accelerator_entry(int command) const
|
|
{
|
|
int flags = wxACCEL_NORMAL;
|
|
if (modifiers & wxMOD_CONTROL)
|
|
flags |= wxACCEL_CTRL;
|
|
if (modifiers & wxMOD_SHIFT)
|
|
flags |= wxACCEL_SHIFT;
|
|
if (modifiers & wxMOD_ALT)
|
|
flags |= wxACCEL_ALT;
|
|
#ifdef __APPLE__
|
|
if (modifiers & wxMOD_RAW_CONTROL)
|
|
flags |= wxACCEL_RAW_CTRL;
|
|
#endif
|
|
return wxAcceleratorEntry(flags, key, command);
|
|
}
|
|
|
|
KeyChord KeyChord::from_event(const wxKeyEvent& evt)
|
|
{
|
|
KeyChord chord;
|
|
chord.modifiers = evt.GetModifiers() & BINDABLE_MODIFIERS;
|
|
int key = fold_numpad(evt.GetKeyCode());
|
|
|
|
if (evt.GetEventType() == wxEVT_CHAR) {
|
|
if (key >= 1 && key <= 26 && evt.ControlDown())
|
|
key = 'A' + key - 1; // Ctrl+letter arrives as the control character
|
|
else if (is_symbol(key))
|
|
chord.modifiers &= ~wxMOD_SHIFT; // the character already reflects Shift
|
|
}
|
|
if (key >= 'a' && key <= 'z')
|
|
key -= 'a' - 'A';
|
|
|
|
if (is_printable(key) || is_special(key))
|
|
chord.key = key;
|
|
return chord;
|
|
}
|
|
|
|
}} // namespace Slic3r::GUI
|