mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-04 22:31:02 +00:00
feat(gui): assignable keyboard shortcuts (#15706)
This commit is contained in:
@@ -6,359 +6,319 @@
|
||||
#include "Notebook.hpp"
|
||||
#include <wx/scrolwin.h>
|
||||
#include <wx/display.h>
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include "GUI_App.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "Preferences.hpp"
|
||||
#include "Widgets/Button.hpp"
|
||||
#include "Widgets/DialogButtons.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
#include "Widgets/StaticBox.hpp"
|
||||
#include "Widgets/StaticLine.hpp"
|
||||
#include "Widgets/TabCtrl.hpp"
|
||||
#include <wx/notebook.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
wxDEFINE_EVENT(EVT_PREFERENCES_SELECT_TAB, wxCommandEvent);
|
||||
namespace {
|
||||
|
||||
KBShortcutsDialog::KBShortcutsDialog()
|
||||
: DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY,_L("Keyboard Shortcuts"),
|
||||
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
|
||||
wxString shortcut_names(const std::vector<Shortcut>& shortcuts)
|
||||
{
|
||||
// fonts
|
||||
const wxFont& font = wxGetApp().normal_font();
|
||||
const wxFont& bold_font = wxGetApp().bold_font();
|
||||
SetFont(font);
|
||||
|
||||
this->SetSizeHints(wxDefaultSize, wxDefaultSize);
|
||||
this->SetBackgroundColour(wxColour(255, 255, 255));
|
||||
|
||||
wxBoxSizer *m_sizer_top = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto m_top_line = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL);
|
||||
m_top_line->SetBackgroundColour(wxColour(166, 169, 170));
|
||||
|
||||
m_sizer_top->Add(m_top_line, 0, wxEXPAND, 0);
|
||||
m_sizer_body = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
m_panel_selects = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
m_panel_selects->SetBackgroundColour(wxColour(248, 248, 248));
|
||||
wxBoxSizer *m_sizer_left = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_sizer_left->Add(0, 0, 0, wxEXPAND | wxTOP, FromDIP(20));
|
||||
|
||||
m_sizer_left->Add(create_button(0, _L("Global")), 0, wxEXPAND, 0);
|
||||
m_sizer_left->Add(create_button(1, _L("Prepare")), 0, wxEXPAND, 0);
|
||||
m_sizer_left->Add(create_button(2, _L("Toolbar")), 0, wxEXPAND, 0);
|
||||
m_sizer_left->Add(create_button(3, _L("Objects list")), 0, wxEXPAND, 0);
|
||||
m_sizer_left->Add(create_button(4, _L("Preview")), 0, wxEXPAND, 0);
|
||||
|
||||
m_panel_selects->SetSizer(m_sizer_left);
|
||||
m_panel_selects->Layout();
|
||||
m_sizer_left->Fit(m_panel_selects);
|
||||
m_sizer_body->Add(m_panel_selects, 0, wxEXPAND, 0);
|
||||
|
||||
m_sizer_right = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
m_sizer_right->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(12));
|
||||
|
||||
m_simplebook = new wxSimplebook(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(870), FromDIP(500)), 0);
|
||||
|
||||
m_sizer_right->Add(m_simplebook, 1, wxEXPAND, 0);
|
||||
m_sizer_body->Add(m_sizer_right, 1, wxEXPAND, 0);
|
||||
m_sizer_top->Add(m_sizer_body, 1, wxEXPAND, 0);
|
||||
|
||||
fill_shortcuts();
|
||||
for (size_t i = 0; i < m_full_shortcuts.size(); ++i) {
|
||||
wxPanel *page = create_page(m_simplebook, m_full_shortcuts[i], font, bold_font);
|
||||
m_pages.push_back(page);
|
||||
m_simplebook->AddPage(page, m_full_shortcuts[i].first.first, i == 0);
|
||||
wxString names;
|
||||
for (Shortcut shortcut : shortcuts) {
|
||||
if (!names.empty())
|
||||
names += ", ";
|
||||
names += _(shortcut_info(shortcut).name);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
Bind(EVT_PREFERENCES_SELECT_TAB, &KBShortcutsDialog::OnSelectTabel, this);
|
||||
const wxColour ERROR_COLOUR("#D01B1B");
|
||||
|
||||
SetSizer(m_sizer_top);
|
||||
Layout();
|
||||
Fit();
|
||||
// The camera action a mouse button drags, as set in Preferences > Control.
|
||||
const char* mouse_action(const char* preference)
|
||||
{
|
||||
const std::string action = wxGetApp().app_config->get(preference);
|
||||
return action == "1" ? L("Pan View") : action == "2" ? L("Rotate View") : L("None");
|
||||
}
|
||||
|
||||
// Page layout in DIPs; titles and rows are indented as in the Preferences dialog.
|
||||
constexpr int PAGE_WIDTH = 640;
|
||||
constexpr int TITLE_MARGIN = DESIGN_LEFT_MARGIN - 10;
|
||||
constexpr int ROW_MARGIN = DESIGN_LEFT_MARGIN;
|
||||
constexpr int ROW_GAP = 16;
|
||||
|
||||
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
||||
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
std::vector<wxString> to_wx(const std::vector<std::string>& parts)
|
||||
{
|
||||
std::vector<wxString> out;
|
||||
for (const std::string& part : parts)
|
||||
out.push_back(from_u8(part));
|
||||
return out;
|
||||
}
|
||||
|
||||
// The keys a Global shortcut can use, the second line of its hint and of a rejection.
|
||||
wxString global_key_advice()
|
||||
{
|
||||
return wxString::Format(_L("Use %s or %s, or a key that does not type a character."),
|
||||
from_u8(KeyChord::modifier_name(wxMOD_CONTROL)), from_u8(KeyChord::modifier_name(wxMOD_ALT)));
|
||||
}
|
||||
|
||||
// The pieces of a chord, spaced out for the dialog: "Ctrl + Shift + A".
|
||||
wxString join_keys(const std::vector<wxString>& parts)
|
||||
{
|
||||
wxString out;
|
||||
for (const wxString& part : parts)
|
||||
out += (out.empty() ? "" : " + ") + part;
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
KBShortcutsDialog::KBShortcutsDialog(wxWindow* parent, ShortcutContext page)
|
||||
: DPIDialog(parent, wxID_ANY, _L("Keyboard Shortcuts"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
|
||||
{
|
||||
SetFont(wxGetApp().normal_font());
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
|
||||
fill_pages();
|
||||
|
||||
ScalableButton* probe = new ScalableButton(this, wxID_ANY, "edit");
|
||||
m_edit_size = probe->GetBestSize();
|
||||
probe->Destroy();
|
||||
m_buttons_width = 2 * m_edit_size.x + FromDIP(6);
|
||||
m_row_text_width = FromDIP(PAGE_WIDTH) - FromDIP(ROW_MARGIN) - FromDIP(TITLE_MARGIN) - 2 * FromDIP(ROW_GAP) - m_buttons_width;
|
||||
GetTextExtent("W", &m_key_slot, nullptr, nullptr, nullptr, &Label::Head_14);
|
||||
|
||||
// The page tabs follow the Preferences dialog.
|
||||
m_tabs = new TabCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_NONE | wxWANTS_CHARS | wxTR_FULL_ROW_HIGHLIGHT);
|
||||
m_tabs->Bind(wxEVT_RIGHT_DOWN, [](auto&) {});
|
||||
m_tabs->SetFont(Label::Body_14);
|
||||
m_simplebook = new wxSimplebook(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(660), FromDIP(500)));
|
||||
for (const Page& page : m_pages) {
|
||||
m_tabs->AppendItem(page.title);
|
||||
m_simplebook->AddPage(create_page(m_simplebook, page), page.title);
|
||||
}
|
||||
const StateColor tab_colour(std::make_pair(wxColour("#6B6B6C"), (int) StateColor::NotChecked), std::make_pair(wxColour("#363636"), (int) StateColor::Normal));
|
||||
for (size_t i = 0; i < m_tabs->GetCount(); ++i)
|
||||
m_tabs->SetItemTextColour(i, tab_colour);
|
||||
m_tabs->Bind(wxEVT_TAB_SEL_CHANGED, [this](wxCommandEvent& e) {
|
||||
for (size_t i = 0; i < m_tabs->GetCount(); ++i)
|
||||
m_tabs->SetItemBold(i, int(i) == e.GetSelection());
|
||||
m_simplebook->SetSelection(e.GetSelection());
|
||||
});
|
||||
const auto shown = std::find_if(m_pages.begin(), m_pages.end(), [page](const Page& entry) { return entry.context == page; });
|
||||
m_tabs->SelectItem(shown == m_pages.end() ? 0 : int(shown - m_pages.begin()));
|
||||
|
||||
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(m_tabs, 0, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(5));
|
||||
sizer->Add(m_simplebook, 1, wxEXPAND);
|
||||
SetSizerAndFit(sizer);
|
||||
CenterOnParent();
|
||||
|
||||
// select first
|
||||
auto event = wxCommandEvent(EVT_PREFERENCES_SELECT_TAB);
|
||||
event.SetInt(0);
|
||||
event.SetEventObject(this);
|
||||
wxPostEvent(this, event);
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
}
|
||||
|
||||
void KBShortcutsDialog::OnSelectTabel(wxCommandEvent &event)
|
||||
{
|
||||
auto id = event.GetInt();
|
||||
SelectHash::iterator i = m_hash_selector.begin();
|
||||
while (i != m_hash_selector.end()) {
|
||||
Select *sel = i->second;
|
||||
if (id == sel->m_index) {
|
||||
sel->m_tab_button->SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#BFE1DE"))); // ORCA color for selected tab background
|
||||
sel->m_tab_text->SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#BFE1DE"))); // ORCA color for selected tab background
|
||||
sel->m_tab_text->SetFont(::Label::Head_13);
|
||||
sel->m_tab_button->Refresh();
|
||||
sel->m_tab_text->Refresh();
|
||||
|
||||
m_simplebook->SetSelection(id);
|
||||
} else {
|
||||
sel->m_tab_button->SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#F8F8F8")));
|
||||
sel->m_tab_text->SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#F8F8F8")));
|
||||
sel->m_tab_text->SetFont(::Label::Body_13);
|
||||
sel->m_tab_button->Refresh();
|
||||
sel->m_tab_text->Refresh();
|
||||
}
|
||||
i++;
|
||||
}
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
}
|
||||
|
||||
wxWindow *KBShortcutsDialog::create_button(int id, wxString text)
|
||||
{
|
||||
auto tab_button = new wxWindow(m_panel_selects, wxID_ANY, wxDefaultPosition, wxSize( FromDIP(150), FromDIP(28)), wxTAB_TRAVERSAL);
|
||||
|
||||
wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
sizer->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(22));
|
||||
|
||||
auto stext = new wxStaticText(tab_button, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, 0);
|
||||
stext->SetFont(::Label::Body_13);
|
||||
stext->SetForegroundColour(wxColour(38, 46, 48));
|
||||
stext->Wrap(-1);
|
||||
sizer->Add(stext, 1, wxALIGN_CENTER, 0);
|
||||
|
||||
tab_button->Bind(wxEVT_LEFT_DOWN, [this, id](auto &e) {
|
||||
auto event = wxCommandEvent(EVT_PREFERENCES_SELECT_TAB);
|
||||
event.SetInt(id);
|
||||
event.SetEventObject(this);
|
||||
wxPostEvent(this, event);
|
||||
});
|
||||
|
||||
stext->Bind(wxEVT_LEFT_DOWN, [this, id](wxMouseEvent &e) {
|
||||
auto event = wxCommandEvent(EVT_PREFERENCES_SELECT_TAB);
|
||||
event.SetInt(id);
|
||||
event.SetEventObject(this);
|
||||
wxPostEvent(this, event);
|
||||
});
|
||||
|
||||
Select *sel = new Select;
|
||||
sel->m_index = id;
|
||||
sel->m_tab_button = tab_button;
|
||||
sel->m_tab_text = stext;
|
||||
m_hash_selector[sel->m_index] = sel;
|
||||
|
||||
tab_button->SetSizer(sizer);
|
||||
tab_button->Layout();
|
||||
return tab_button;
|
||||
}
|
||||
|
||||
void KBShortcutsDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
{
|
||||
m_logo_bmp.msw_rescale();
|
||||
m_header_bitmap->SetBitmap(m_logo_bmp.bmp());
|
||||
msw_buttons_rescale(this, em_unit(), { wxID_OK });
|
||||
|
||||
m_tabs->Rescale();
|
||||
Layout();
|
||||
Fit();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void KBShortcutsDialog::fill_shortcuts()
|
||||
void KBShortcutsDialog::fill_pages()
|
||||
{
|
||||
const std::string ctrl = GUI::shortkey_ctrl_prefix();
|
||||
const std::string alt = GUI::shortkey_alt_prefix();
|
||||
const std::string shift = L("Shift+");
|
||||
// A fixed row is listed in the section of the shortcuts it belongs with.
|
||||
auto fixed = [](ShortcutSection section, std::vector<wxString> keys, const char* description) { return Row{ FixedKey{ std::move(keys), description }, section }; };
|
||||
auto mouse = [](ShortcutSection section, const wxString& button, const char* preference) { return Row{ MouseAction{ button, preference }, section }; };
|
||||
auto key = [](const std::string& key) { return _L_CONTEXT(key, "Keyboard Shortcut"); };
|
||||
auto page = [this](const wxString& title, const wxString& caption, ShortcutContext context, std::vector<Row> fixed_rows) {
|
||||
Page entry{ title, caption, context, {} };
|
||||
for (Shortcut shortcut : shortcuts_in(context))
|
||||
entry.rows.push_back({ shortcut, shortcut_section(shortcut) });
|
||||
entry.rows.insert(entry.rows.end(), fixed_rows.begin(), fixed_rows.end());
|
||||
std::stable_sort(entry.rows.begin(), entry.rows.end(), [](const Row& a, const Row& b) { return a.section < b.section; });
|
||||
m_pages.push_back(std::move(entry));
|
||||
};
|
||||
|
||||
const wxString ctrl = from_u8(KeyChord::modifier_name(wxMOD_CONTROL));
|
||||
const wxString alt = from_u8(KeyChord::modifier_name(wxMOD_ALT));
|
||||
const wxString shift = from_u8(KeyChord::modifier_name(wxMOD_SHIFT));
|
||||
const wxString shift_ctrl = shift + "/" + ctrl; // either one
|
||||
const wxString any_key = key(L_CONTEXT("Key", "Keyboard Shortcut")); // the key the row's shortcut is bound to
|
||||
const wxString esc = key(L_CONTEXT("Esc", "Keyboard Shortcut"));
|
||||
const wxString left_button = _L("Left mouse");
|
||||
const wxString wheel = _L("Mouse wheel");
|
||||
using Section = ShortcutSection;
|
||||
|
||||
if (wxGetApp().is_editor()) {
|
||||
Shortcuts global_shortcuts = {
|
||||
// File
|
||||
{ ctrl + "N", L("New Project") },
|
||||
{ ctrl + "O", L("Open Project") },
|
||||
{ ctrl + "S", L("Save Project") },
|
||||
{ ctrl + shift + "S", L("Save Project as")},
|
||||
{ ctrl + shift + "E", L("Publish 3MF") },
|
||||
// File>Import
|
||||
{ ctrl + "I", L("Import geometry data from STL/STEP/3MF/OBJ/AMF files") },
|
||||
// File>Export
|
||||
{ ctrl + "G", L("Export plate sliced file")},
|
||||
// Slice plate
|
||||
{ ctrl + "R", L("Slice plate")},
|
||||
// Send to Print
|
||||
{ ctrl + shift + "G", L("Print plate")},
|
||||
// Edit
|
||||
{ ctrl + "X", L("Cut") },
|
||||
{ ctrl + "C", L("Copy to clipboard") },
|
||||
{ ctrl + "V", L("Paste from clipboard") },
|
||||
// Configuration
|
||||
{ ctrl + "P", L("Preferences") },
|
||||
//3D control
|
||||
#ifdef __APPLE__
|
||||
{ ctrl + shift + "M", L("Show/Hide 3Dconnexion devices settings dialog") },
|
||||
#else
|
||||
{ ctrl + "M", L("Show/Hide 3Dconnexion devices settings dialog") },
|
||||
#endif // __APPLE
|
||||
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::Application, { ctrl, key(L_CONTEXT("Tab", "Keyboard Shortcut")) }, L("Switch to the next main tab")),
|
||||
});
|
||||
|
||||
// Switch table page
|
||||
{ ctrl + L("Tab"), L("Switch table page")},
|
||||
// Open speed dial
|
||||
{ L_CONTEXT("Space", "Keyboard Shortcut"), L("Open speed dial") },
|
||||
{ alt + "1..9,0", L("Run a Speed Dial favourite (while the Speed Dial is open)") },
|
||||
//DEL
|
||||
#ifdef __APPLE__
|
||||
{"fn+⌫", L("Delete Selected")},
|
||||
#else
|
||||
{L_CONTEXT("Del", "Keyboard Shortcut"), L("Delete Selected")},
|
||||
#endif
|
||||
// Help
|
||||
{ "?", L("Show keyboard shortcuts list") }
|
||||
};
|
||||
m_full_shortcuts.push_back({{_L("Global shortcuts"), ""}, global_shortcuts});
|
||||
page(_L("Prepare"), _L("Available while the 3D view on the Prepare tab has focus."), ShortcutContext::Plater, {
|
||||
fixed(Section::Selection, { alt, left_button }, L("Select a part")),
|
||||
fixed(Section::Selection, { ctrl, left_button }, L("Select multiple objects")),
|
||||
fixed(Section::Selection, { shift, left_button }, L("Select objects by rectangle")),
|
||||
fixed(Section::Selection, { esc }, L("Deselect All")),
|
||||
fixed(Section::Objects, { "1-9" }, L("Keyboard 1-9: set filament for object/part")),
|
||||
fixed(Section::Placement, { shift, any_key }, L("Movement step set to 1mm")),
|
||||
fixed(Section::Placement, { ctrl, any_key }, L("Movement in camera space")),
|
||||
mouse(Section::Camera, left_button, "left_mouse_drag_action"),
|
||||
mouse(Section::Camera, _L("Middle mouse"), "middle_mouse_drag_action"),
|
||||
mouse(Section::Camera, _L("Right mouse"), "right_mouse_drag_action"),
|
||||
fixed(Section::Camera, { wheel }, L("Zoom View")),
|
||||
});
|
||||
|
||||
|
||||
// Retrieve mouse actions from config and map to MouseAction
|
||||
std::map<std::string, std::string> mouse_actions;
|
||||
mouse_actions["0"] = L("None");
|
||||
mouse_actions["1"] = L("Pan View");
|
||||
mouse_actions["2"] = L("Rotate View");
|
||||
page(_L("Painting"), _L("Available while a painting gizmo is open: supports, seam, fuzzy skin or color painting."), ShortcutContext::Painting, {
|
||||
fixed(Section::Gizmos, { esc }, L("Deselect All")),
|
||||
fixed(Section::Gizmos, { shift, left_button }, L("Move: press to snap by 1mm")),
|
||||
fixed(Section::PaintingTools, { ctrl, wheel }, L("Support/Color Painting: adjust pen radius")),
|
||||
fixed(Section::PaintingTools, { alt, wheel }, L("Support/Color Painting: adjust section position")),
|
||||
});
|
||||
|
||||
Shortcuts plater_shortcuts = {
|
||||
{ L("Left mouse button"), mouse_actions[wxGetApp().app_config->get("left_mouse_drag_action").c_str()]},
|
||||
{ L("Middle mouse button"), mouse_actions[wxGetApp().app_config->get("middle_mouse_drag_action").c_str()]},
|
||||
{ L("Right mouse button"), mouse_actions[wxGetApp().app_config->get("right_mouse_drag_action").c_str()]},
|
||||
{ L("Mouse wheel"), L("Zoom View") },
|
||||
{ "A", L("Arrange all objects") },
|
||||
{ shift + "A", L("Arrange objects on selected plates") },
|
||||
|
||||
{ "Q", L("Auto orients selected objects or all objects. If there are selected objects, it just orients the selected ones. Otherwise, it will orient all objects in the current project.") },
|
||||
{ shift + "Q", L("Auto orients all objects on the active plate.") },
|
||||
|
||||
{shift + L("Tab"), L("Collapse/Expand the sidebar")},
|
||||
{ctrl + L("Any arrow"), L("Movement in camera space")},
|
||||
{alt + L("Left mouse button"), L("Select a part")},
|
||||
{ctrl + L("Left mouse button"), L("Select multiple objects")},
|
||||
{shift + L("Left mouse button"), L("Select objects by rectangle")},
|
||||
{L_CONTEXT("Arrow Up", "Keyboard Shortcut"), L("Move selection 10mm in positive Y direction")},
|
||||
{L_CONTEXT("Arrow Down", "Keyboard Shortcut"), L("Move selection 10mm in negative Y direction")},
|
||||
{L_CONTEXT("Arrow Left", "Keyboard Shortcut"), L("Move selection 10mm in negative X direction")},
|
||||
{L_CONTEXT("Arrow Right", "Keyboard Shortcut"), L("Move selection 10mm in positive X direction")},
|
||||
{shift + L("Any arrow"), L("Movement step set to 1mm")},
|
||||
{L_CONTEXT("Esc", "Keyboard Shortcut"), L("Deselect All")},
|
||||
{"1-9", L("Keyboard 1-9: set filament for object/part")},
|
||||
{ctrl + "0", L("Camera view - Default")},
|
||||
{ctrl + "1", L("Camera view - Top")},
|
||||
{ctrl + "2", L("Camera view - Bottom")},
|
||||
{ctrl + "3", L("Camera view - Front")},
|
||||
{ctrl + "4", L("Camera view - Behind")},
|
||||
{ctrl + "5", L("Camera Angle - Left side")},
|
||||
{ctrl + "6", L("Camera Angle - Right side")},
|
||||
|
||||
{ctrl + "A", L("Select all objects")},
|
||||
{ctrl + "D", L("Delete All")},
|
||||
{ctrl + "Z", L("Undo")},
|
||||
{ctrl + "Y", L("Redo")},
|
||||
{ "M", L("Gizmo move") },
|
||||
{ "R", L("Gizmo rotate") },
|
||||
{ "S", L("Gizmo scale") },
|
||||
{ "F", L("Gizmo place face on bed") },
|
||||
{ "C", L("Gizmo cut") },
|
||||
{ "B", L("Gizmo mesh boolean") },
|
||||
{ "H", L("Gizmo FDM paint-on fuzzy skin") },
|
||||
{ "L", L("Gizmo SLA support points") },
|
||||
{ "P", L("Gizmo FDM paint-on seam") },
|
||||
{ "T", L("Gizmo text emboss/engrave") },
|
||||
{ "U", L("Gizmo measure") },
|
||||
{ "Y", L("Gizmo assemble") },
|
||||
{ "E", L("Gizmo brim ears") },
|
||||
{ "I", L("Zoom in") },
|
||||
{ "O", L("Zoom out") },
|
||||
{ "V", L("Toggle printable for object/part") },
|
||||
{ L_CONTEXT("Tab", "Keyboard Shortcut"), L("Switch between Prepare/Preview") },
|
||||
};
|
||||
m_full_shortcuts.push_back({ { _L("Plater"), "" }, plater_shortcuts });
|
||||
|
||||
Shortcuts gizmos_shortcuts = {
|
||||
{L_CONTEXT("Esc", "Keyboard Shortcut"), L("Deselect All")},
|
||||
{shift, L("Move: press to snap by 1mm")},
|
||||
{ctrl + L("Mouse wheel"), L("Support/Color Painting: adjust pen radius")},
|
||||
{alt + L("Mouse wheel"), L("Support/Color Painting: adjust section position")},
|
||||
};
|
||||
m_full_shortcuts.push_back({{_L("Gizmo"), ""}, gizmos_shortcuts});
|
||||
|
||||
Shortcuts object_list_shortcuts = {
|
||||
{"1-9", L("Set extruder number for the objects and parts") },
|
||||
{L_CONTEXT("Del", "Keyboard Shortcut"), L("Delete objects, parts, modifiers")},
|
||||
{L_CONTEXT("Esc", "Keyboard Shortcut"), L("Deselect All")},
|
||||
{ctrl + "C", L("Copy to clipboard")},
|
||||
{ctrl + "V", L("Paste from clipboard")},
|
||||
{ctrl + "X", L("Cut")},
|
||||
{ctrl + "A", L("Select all objects")},
|
||||
{ctrl + "K", L("Clone Selected")},
|
||||
{ctrl + "Z", L("Undo")},
|
||||
{ctrl + "Y", L("Redo")},
|
||||
{L_CONTEXT("Space", "Keyboard Shortcut"), L("Select the object/part and press space to change the name")},
|
||||
{L("Mouse click"), L("Select the object/part and mouse click to change the name")},
|
||||
};
|
||||
m_full_shortcuts.push_back({ { _L("Objects List"), "" }, object_list_shortcuts });
|
||||
page(_L("Objects list"), _L("Available while the object list has focus."), ShortcutContext::ObjectList, {
|
||||
fixed(Section::Selection, { esc }, L("Deselect All")),
|
||||
fixed(Section::Objects, { "1-9" }, L("Set extruder number for the objects and parts")),
|
||||
fixed(Section::Objects, { key(L_CONTEXT("Space", "Keyboard Shortcut")) }, L("Select the object/part and press space to change the name")),
|
||||
fixed(Section::Objects, { _L("Mouse click") }, L("Select the object/part and mouse click to change the name")),
|
||||
});
|
||||
}
|
||||
|
||||
Shortcuts preview_shortcuts = {
|
||||
{ L_CONTEXT("Arrow Up", "Keyboard Shortcut"), L("Vertical slider - Move active thumb Up")},
|
||||
{ L_CONTEXT("Arrow Down", "Keyboard Shortcut"), L("Vertical slider - Move active thumb Down")},
|
||||
{ L_CONTEXT("Arrow Left", "Keyboard Shortcut"), L("Horizontal slider - Move active thumb Left")},
|
||||
{ L_CONTEXT("Arrow Right", "Keyboard Shortcut"), L("Horizontal slider - Move active thumb Right")},
|
||||
{ "L", L("On/Off one layer mode of the vertical slider")},
|
||||
{ "C", L("On/Off G-code window")},
|
||||
{ L_CONTEXT("Tab", "Keyboard Shortcut"), L("Switch between Prepare/Preview")},
|
||||
{shift + L("Any arrow"), L("Move slider 5x faster")},
|
||||
{shift + L("Mouse wheel"), L("Move slider 5x faster")},
|
||||
{ctrl + L("Any arrow"), L("Move slider 5x faster")},
|
||||
{ctrl + L("Mouse wheel"), L("Move slider 5x faster")},
|
||||
{ L_CONTEXT("Home", "Keyboard Shortcut"), L("Horizontal slider - Move to start position")},
|
||||
{ L_CONTEXT("End", "Keyboard Shortcut"), L("Horizontal slider - Move to last position")},
|
||||
};
|
||||
m_full_shortcuts.push_back({ { _L("Preview"), "" }, preview_shortcuts });
|
||||
page(_L("Preview"), _L("Available while the 3D view on the Preview tab has focus."), ShortcutContext::Preview, {
|
||||
fixed(Section::Sliders, { shift_ctrl, any_key }, L("Move slider 5x faster")),
|
||||
fixed(Section::Sliders, { shift_ctrl, wheel }, L("Scroll slider 5x faster")),
|
||||
});
|
||||
}
|
||||
|
||||
wxPanel* KBShortcutsDialog::create_page(wxWindow* parent, const ShortcutsItem& shortcuts, const wxFont& font, const wxFont& bold_font)
|
||||
wxPanel* KBShortcutsDialog::create_page(wxWindow* parent, const Page& page)
|
||||
{
|
||||
wxPanel* main_page = new wxPanel(parent);
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
if (!shortcuts.first.second.empty()) {
|
||||
main_sizer->AddSpacer(FromDIP(10));
|
||||
wxBoxSizer* info_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
info_sizer->AddStretchSpacer();
|
||||
info_sizer->Add(new wxStaticText(main_page, wxID_ANY, shortcuts.first.second), 0);
|
||||
info_sizer->AddStretchSpacer();
|
||||
main_sizer->Add(info_sizer, 0, wxEXPAND);
|
||||
main_sizer->AddSpacer(FromDIP(10));
|
||||
}
|
||||
|
||||
int items_count = (int) shortcuts.second.size();
|
||||
wxScrolledWindow *scrollable_panel = new wxScrolledWindow(main_page);
|
||||
wxGetApp().UpdateDarkUI(scrollable_panel);
|
||||
scrollable_panel->SetScrollbars(20, 20, 50, 50);
|
||||
scrollable_panel->SetInitialSize(wxSize(FromDIP(850), FromDIP(450)));
|
||||
const wxColour page_colour = StateColor::darkModeColorFor(*wxWHITE);
|
||||
scrollable_panel->SetBackgroundColour(page_colour);
|
||||
scrollable_panel->SetScrollRate(0, 20);
|
||||
const int page_width = FromDIP(PAGE_WIDTH);
|
||||
scrollable_panel->SetInitialSize(wxSize(page_width, FromDIP(450)));
|
||||
|
||||
wxBoxSizer * scrollable_panel_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
wxFlexGridSizer *grid_sizer = new wxFlexGridSizer(items_count, 2, FromDIP(10), FromDIP(20));
|
||||
const int title_margin = FromDIP(TITLE_MARGIN);
|
||||
const int row_margin = FromDIP(ROW_MARGIN);
|
||||
const int gap = FromDIP(ROW_GAP);
|
||||
|
||||
for (int i = 0; i < items_count; ++i) {
|
||||
const auto &[shortcut, description] = shortcuts.second[i];
|
||||
// Keyboard keys carry a "Keyboard Shortcut" context so translators keep them in English;
|
||||
// mouse-input labels are ordinary phrases and use the plain lookup.
|
||||
const bool is_mouse = shortcut.find("Mouse") != std::string::npos || shortcut.find("mouse") != std::string::npos;
|
||||
auto key = new wxStaticText(scrollable_panel, wxID_ANY, is_mouse ? _(shortcut) : _L_CONTEXT(shortcut, "Keyboard Shortcut"));
|
||||
key->SetForegroundColour(wxColour(50, 58, 61));
|
||||
key->SetFont(bold_font);
|
||||
grid_sizer->Add(key, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
wxBoxSizer* scrollable_panel_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto desc = new wxStaticText(scrollable_panel, wxID_ANY, _(description));
|
||||
desc->SetFont(font);
|
||||
desc->SetForegroundColour(wxColour(50, 58, 61));
|
||||
desc->Wrap(FromDIP(600));
|
||||
grid_sizer->Add(desc, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
const wxColour note_colour = StateColor::darkModeColorFor(wxColour("#F8F8F8"));
|
||||
const wxColour note_text = StateColor::darkModeColorFor(wxColour("#6B6B6A"));
|
||||
StaticBox* note = new StaticBox(scrollable_panel);
|
||||
note->SetCornerRadius(FromDIP(4));
|
||||
note->SetBorderWidth(0);
|
||||
note->SetBackgroundColor(note_colour);
|
||||
note->SetBackgroundColour(note_colour);
|
||||
auto note_icon = new wxStaticBitmap(note, wxID_ANY, ScalableBitmap(note, "help", 16).bmp());
|
||||
auto note_text_ctrl = new wxStaticText(note, wxID_ANY, page.caption);
|
||||
note_text_ctrl->SetFont(Label::Body_13);
|
||||
note_text_ctrl->SetForegroundColour(note_text);
|
||||
note_text_ctrl->SetBackgroundColour(note_colour);
|
||||
note_text_ctrl->Wrap(page_width - 2 * title_margin - FromDIP(10 + 16 + 8 + 10));
|
||||
wxBoxSizer* note_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
note_sizer->Add(note_icon, 0, wxALIGN_CENTRE_VERTICAL | wxLEFT, FromDIP(10));
|
||||
note_sizer->Add(note_text_ctrl, 1, wxALIGN_CENTRE_VERTICAL | wxALL, FromDIP(8));
|
||||
note->SetSizer(note_sizer);
|
||||
scrollable_panel_sizer->Add(note, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, title_margin);
|
||||
|
||||
auto key_parts = [](const Row& row) {
|
||||
return std::visit(overloaded{
|
||||
[](Shortcut shortcut) { return to_wx(wxGetApp().shortcuts().binding(shortcut).display_parts()); },
|
||||
[](const FixedKey& fixed) { return fixed.keys; },
|
||||
[](const MouseAction& mouse) { return std::vector<wxString>{ mouse.button }; },
|
||||
}, row.content);
|
||||
};
|
||||
auto description = [](const Row& row) {
|
||||
return std::visit(overloaded{
|
||||
[](Shortcut shortcut) { return _(shortcut_info(shortcut).name); },
|
||||
[](const FixedKey& fixed) { return _(fixed.description); },
|
||||
[](const MouseAction& mouse) { return _(mouse_action(mouse.preference)); },
|
||||
}, row.content);
|
||||
};
|
||||
auto icon_button = [&](const char* icon, const wxString& tooltip) {
|
||||
auto button = new ScalableButton(scrollable_panel, wxID_ANY, icon);
|
||||
button->SetBackgroundColour(page_colour);
|
||||
button->SetToolTip(tooltip);
|
||||
return button;
|
||||
};
|
||||
|
||||
std::optional<ShortcutSection> section;
|
||||
for (const Row& row : page.rows) {
|
||||
if (section != row.section) {
|
||||
auto heading = new StaticLine(scrollable_panel, false, _(section_name(row.section)));
|
||||
heading->SetFont(Label::Head_14);
|
||||
heading->SetForegroundColour(DESIGN_GRAY900_COLOR);
|
||||
wxBoxSizer* heading_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
heading_sizer->AddSpacer(title_margin);
|
||||
heading_sizer->Add(heading, 1, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(6));
|
||||
heading_sizer->AddSpacer(title_margin);
|
||||
scrollable_panel_sizer->Add(heading_sizer, 0, wxEXPAND | wxTOP, FromDIP(section.has_value() ? 10 : 6));
|
||||
section = row.section;
|
||||
}
|
||||
auto desc = new wxStaticText(scrollable_panel, wxID_ANY, description(row));
|
||||
desc->SetFont(Label::Body_14);
|
||||
desc->SetForegroundColour(DESIGN_GRAY900_COLOR);
|
||||
auto chord_label = [&](long style) {
|
||||
auto label = new wxStaticText(scrollable_panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, style);
|
||||
label->SetFont(Label::Head_14);
|
||||
label->SetForegroundColour(DESIGN_GRAY900_COLOR);
|
||||
return label;
|
||||
};
|
||||
wxStaticText* modifiers = chord_label(0);
|
||||
wxStaticText* key = chord_label(wxALIGN_CENTRE_HORIZONTAL); // a single key is centred in its column
|
||||
desc->Wrap(m_row_text_width - set_chord_labels(modifiers, key, key_parts(row)));
|
||||
|
||||
wxBoxSizer* buttons = new wxBoxSizer(wxHORIZONTAL);
|
||||
if (const MouseAction* mouse = std::get_if<MouseAction>(&row.content)) {
|
||||
auto settings = icon_button("settings", _L("Preferences"));
|
||||
settings->Bind(wxEVT_BUTTON, [this, preference = mouse->preference](wxCommandEvent&) { open_mouse_preferences(preference); });
|
||||
buttons->Add(settings, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
m_preference_rows.push_back({ mouse->preference, desc });
|
||||
} else if (const Shortcut* editable = std::get_if<Shortcut>(&row.content)) {
|
||||
const Shortcut shortcut = *editable;
|
||||
auto change = icon_button("edit", _L("Edit"));
|
||||
change->Bind(wxEVT_BUTTON, [this, shortcut](wxCommandEvent&) { edit_shortcut(shortcut); });
|
||||
auto reset = icon_button("undo", _L("Reset"));
|
||||
reset->Bind(wxEVT_BUTTON, [this, shortcut](wxCommandEvent&) { reset_shortcut(shortcut); });
|
||||
reset->Show(wxGetApp().shortcuts().is_customized(shortcut));
|
||||
buttons->Add(change, 0, wxALIGN_CENTRE_VERTICAL | wxRIGHT, FromDIP(6));
|
||||
buttons->Add(reset, 0, wxALIGN_CENTRE_VERTICAL | wxRESERVE_SPACE_EVEN_IF_HIDDEN);
|
||||
m_editable_rows.push_back({ shortcut, desc, modifiers, key, reset });
|
||||
} else {
|
||||
auto lock = new wxStaticBitmap(scrollable_panel, wxID_ANY, ScalableBitmap(scrollable_panel, "printer_status_lock", 16).bmp());
|
||||
lock->SetToolTip(_L("Not customizable"));
|
||||
buttons->Add((m_edit_size.x - lock->GetBestSize().x) / 2, m_edit_size.y); // centred under the edit icons, at their height
|
||||
buttons->Add(lock, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
}
|
||||
if (const int used = buttons->GetMinSize().x; used < m_buttons_width) // a box sizer recomputes its own min size, so pad it
|
||||
buttons->AddSpacer(m_buttons_width - used);
|
||||
|
||||
wxBoxSizer* row_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
row_sizer->AddSpacer(row_margin);
|
||||
row_sizer->Add(desc, 1, wxALIGN_CENTRE_VERTICAL);
|
||||
row_sizer->AddSpacer(gap);
|
||||
row_sizer->Add(modifiers, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
row_sizer->Add(key, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
row_sizer->Add(buttons, 0, wxALIGN_CENTRE_VERTICAL | wxLEFT, gap);
|
||||
row_sizer->AddSpacer(title_margin);
|
||||
scrollable_panel_sizer->Add(row_sizer, 0, wxEXPAND | wxTOP, FromDIP(4));
|
||||
}
|
||||
|
||||
scrollable_panel_sizer->Add(grid_sizer, 1, wxEXPAND | wxALL, FromDIP(20));
|
||||
scrollable_panel_sizer->AddSpacer(title_margin);
|
||||
scrollable_panel->SetSizer(scrollable_panel_sizer);
|
||||
|
||||
main_sizer->Add(scrollable_panel, 1, wxEXPAND);
|
||||
@@ -367,5 +327,227 @@ wxPanel* KBShortcutsDialog::create_page(wxWindow* parent, const ShortcutsItem& s
|
||||
return main_page;
|
||||
}
|
||||
|
||||
void KBShortcutsDialog::edit_shortcut(Shortcut shortcut)
|
||||
{
|
||||
ShortcutCaptureDialog dlg(this, shortcut);
|
||||
if (dlg.ShowModal() != wxID_OK)
|
||||
return;
|
||||
const wxString question = wxString::Format(_L("%s is assigned to %s. Reassign it to %s?"),
|
||||
join_keys(to_wx(dlg.chord().display_parts())), shortcut_names(dlg.conflicts()), _(shortcut_info(shortcut).name));
|
||||
if (!take_chord_from(shortcut, dlg.conflicts(), question))
|
||||
return;
|
||||
wxGetApp().shortcuts().bind(shortcut, dlg.chord());
|
||||
apply_bindings();
|
||||
}
|
||||
|
||||
void KBShortcutsDialog::reset_shortcut(Shortcut shortcut)
|
||||
{
|
||||
const std::vector<Shortcut> conflicts = wxGetApp().shortcuts().conflicts(shortcut, shortcut_info(shortcut).default_chord);
|
||||
const wxString question = wxString::Format(_L("The default %s is assigned to %s. Reassign it to %s?"),
|
||||
join_keys(to_wx(shortcut_info(shortcut).default_chord.display_parts())), shortcut_names(conflicts), _(shortcut_info(shortcut).name));
|
||||
if (!take_chord_from(shortcut, conflicts, question))
|
||||
return;
|
||||
wxGetApp().shortcuts().reset(shortcut);
|
||||
apply_bindings();
|
||||
}
|
||||
|
||||
bool KBShortcutsDialog::take_chord_from(Shortcut shortcut, const std::vector<Shortcut>& conflicts, const wxString& question)
|
||||
{
|
||||
if (conflicts.empty())
|
||||
return true;
|
||||
MessageDialog confirm(this, question, _(shortcut_info(shortcut).name), wxICON_QUESTION | wxOK | wxCANCEL);
|
||||
if (confirm.ShowModal() != wxID_OK)
|
||||
return false;
|
||||
for (Shortcut other : conflicts)
|
||||
wxGetApp().shortcuts().bind(other, KeyChord{});
|
||||
return true;
|
||||
}
|
||||
|
||||
void KBShortcutsDialog::apply_bindings()
|
||||
{
|
||||
const ShortcutRegistry& shortcuts = wxGetApp().shortcuts();
|
||||
std::set<wxWindow*> pages;
|
||||
for (const EditableRow& row : m_editable_rows) {
|
||||
const int chord_width = set_chord_labels(row.modifiers, row.key, to_wx(shortcuts.binding(row.shortcut).display_parts()));
|
||||
row.description->SetLabel(_(shortcut_info(row.shortcut).name));
|
||||
row.description->Wrap(m_row_text_width - chord_width);
|
||||
row.reset->Show(shortcuts.is_customized(row.shortcut));
|
||||
pages.insert(row.key->GetParent());
|
||||
}
|
||||
for (wxWindow* page : pages)
|
||||
page->Layout();
|
||||
wxGetApp().on_shortcuts_changed();
|
||||
}
|
||||
|
||||
int KBShortcutsDialog::set_chord_labels(wxStaticText* modifiers, wxStaticText* key, std::vector<wxString> parts)
|
||||
{
|
||||
const wxString last = parts.empty() ? wxString() : parts.back();
|
||||
if (!parts.empty())
|
||||
parts.pop_back();
|
||||
modifiers->SetLabel(parts.empty() ? wxString() : join_keys(parts) + " + ");
|
||||
modifiers->Show(!parts.empty());
|
||||
key->SetLabel(last);
|
||||
const int key_width = last.length() == 1 ? m_key_slot : key->GetBestSize().x;
|
||||
key->SetMinSize(wxSize(key_width, -1));
|
||||
return (parts.empty() ? 0 : modifiers->GetBestSize().x) + key_width;
|
||||
}
|
||||
|
||||
void KBShortcutsDialog::open_mouse_preferences(const char* preference)
|
||||
{
|
||||
// Opened from Preferences > Control, the settings are right behind this dialog.
|
||||
if (auto preferences = dynamic_cast<PreferencesDialog*>(GetParent()); preferences != nullptr) {
|
||||
// Runs once this dialog has closed and the focus is back in Preferences.
|
||||
preferences->CallAfter([preferences, preference] { preferences->select_tab(PreferencesTab::Control, preference); });
|
||||
EndModal(wxID_OK);
|
||||
return;
|
||||
}
|
||||
wxGetApp().open_preferences(PreferencesTab::Control, preference);
|
||||
// A language change rebuilds the main frame, taking this dialog with it.
|
||||
if (GetParent() != wxGetApp().mainframe) {
|
||||
EndModal(wxID_CANCEL);
|
||||
return;
|
||||
}
|
||||
for (const PreferenceRow& row : m_preference_rows)
|
||||
row.description->SetLabel(_(mouse_action(row.preference)));
|
||||
}
|
||||
|
||||
ShortcutCaptureDialog::ShortcutCaptureDialog(wxWindow* parent, Shortcut shortcut)
|
||||
: DPIDialog(parent, wxID_ANY, _(shortcut_info(shortcut).name), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
|
||||
, m_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.
|
||||
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_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 }) {
|
||||
int extent = 0;
|
||||
GetTextExtent(sentence, &extent, nullptr, nullptr, nullptr, &wxGetApp().normal_font());
|
||||
width = std::max(width, extent);
|
||||
}
|
||||
width = std::min(width, FromDIP(550));
|
||||
|
||||
auto prompt = new Label(this, wxGetApp().normal_font(), wxString::Format(_L("Press the new shortcut for\n\"%s\""), _(shortcut_info(shortcut).name)), LB_AUTO_WRAP);
|
||||
prompt->SetMinSize(wxSize(width, -1));
|
||||
sizer->Add(prompt, 0, wxALL, FromDIP(20));
|
||||
|
||||
// Keyboard focus stays on this box so the buttons never receive the key presses.
|
||||
const wxColour box_colour = StateColor::darkModeColorFor(*wxWHITE);
|
||||
StaticBox* capture = new StaticBox(this, wxID_ANY, wxDefaultPosition, wxSize(width, FromDIP(60)), wxWANTS_CHARS);
|
||||
capture->SetCornerRadius(FromDIP(4));
|
||||
capture->SetBorderColorNormal(StateColor::darkModeColorFor(wxColour("#009688"))); // the focused-input colour, since the box always has the focus
|
||||
capture->SetBackgroundColorNormal(box_colour);
|
||||
capture->SetBackgroundColour(box_colour);
|
||||
wxBoxSizer* capture_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_chord_label = new wxStaticText(capture, wxID_ANY, join_keys(to_wx(wxGetApp().shortcuts().binding(shortcut).display_parts())));
|
||||
m_chord_label->SetFont(::Label::Head_14);
|
||||
m_chord_label->SetBackgroundColour(box_colour);
|
||||
capture_sizer->AddStretchSpacer();
|
||||
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->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));
|
||||
|
||||
m_status = new Label(this, wxGetApp().normal_font(), m_hint, LB_AUTO_WRAP);
|
||||
m_status->SetMinSize(wxSize(width, 3 * m_status->GetCharHeight())); // room for three lines, so the dialog keeps its size while keys are tried
|
||||
m_status_colour = m_status->GetForegroundColour();
|
||||
sizer->Add(m_status, 0, wxLEFT | wxRIGHT | wxTOP, FromDIP(20));
|
||||
|
||||
auto dlg_btns = new DialogButtons(this, {"Unbind", "OK", "Cancel"}, "", 1 /*left_aligned*/);
|
||||
dlg_btns->GetFIRST()->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
|
||||
m_chord = KeyChord{};
|
||||
m_conflicts.clear();
|
||||
EndModal(wxID_OK);
|
||||
});
|
||||
dlg_btns->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { EndModal(wxID_CANCEL); });
|
||||
m_ok = dlg_btns->GetOK();
|
||||
m_ok->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { EndModal(wxID_OK); });
|
||||
m_ok->Enable(false);
|
||||
sizer->Add(dlg_btns, 0, wxEXPAND | wxTOP, FromDIP(10));
|
||||
|
||||
SetSizerAndFit(sizer);
|
||||
CenterOnParent();
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
capture->CallAfter([capture]() { capture->SetFocus(); });
|
||||
}
|
||||
|
||||
void ShortcutCaptureDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
{
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
void ShortcutCaptureDialog::on_key(wxKeyEvent& evt)
|
||||
{
|
||||
if (!evt.HasAnyModifiers()) {
|
||||
if (evt.GetKeyCode() == WXK_ESCAPE) {
|
||||
EndModal(wxID_CANCEL);
|
||||
return;
|
||||
}
|
||||
if (evt.GetKeyCode() == WXK_RETURN || evt.GetKeyCode() == WXK_NUMPAD_ENTER) {
|
||||
if (m_ok->IsEnabled())
|
||||
EndModal(wxID_OK);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const KeyChord chord = KeyChord::from_event(evt);
|
||||
if (!chord.valid())
|
||||
return;
|
||||
if (chord.needs_char_event()) {
|
||||
evt.Skip();
|
||||
return;
|
||||
}
|
||||
record(chord);
|
||||
}
|
||||
|
||||
void ShortcutCaptureDialog::on_char(wxKeyEvent& evt)
|
||||
{
|
||||
const KeyChord chord = KeyChord::from_event(evt);
|
||||
if (chord.is_punctuation())
|
||||
record(chord);
|
||||
}
|
||||
|
||||
void ShortcutCaptureDialog::record(const KeyChord& chord)
|
||||
{
|
||||
m_chord = chord;
|
||||
m_chord_label->SetLabel(join_keys(to_wx(chord.display_parts())));
|
||||
m_chord_label->GetParent()->Layout();
|
||||
|
||||
auto reject = [this](const wxString& reason) {
|
||||
m_status->SetForegroundColour(ERROR_COLOUR);
|
||||
m_status->SetLabel(reason);
|
||||
m_conflicts.clear();
|
||||
m_ok->Enable(false);
|
||||
};
|
||||
const bool global = (shortcut_info(m_shortcut).contexts & context_bit(ShortcutContext::Global)) != 0;
|
||||
if (global && !chord.is_menu_accelerator()) {
|
||||
reject(m_rejection);
|
||||
} else if (const std::optional<Shortcut> 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)));
|
||||
} else {
|
||||
m_conflicts = wxGetApp().shortcuts().conflicts(m_shortcut, chord);
|
||||
m_status->SetForegroundColour(m_status_colour);
|
||||
if (m_conflicts.empty())
|
||||
m_status->SetLabel(m_hint);
|
||||
else
|
||||
m_status->SetLabel(wxString::Format(_L("Already assigned to %s. Press OK to reassign it."), shortcut_names(m_conflicts)));
|
||||
m_ok->Enable(true);
|
||||
}
|
||||
m_status->Refresh(); // a colour change alone does not repaint
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
Reference in New Issue
Block a user