mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-13 19:01:21 +00:00
A separate toolbar tool for the simple case of texture displacement: put a colour picture on the model with no relief at all. It is a thin wrapper, not a fork. GLGizmoImage subclasses GLGizmoTextureDisplacement and overrides one new protected virtual, image_mode(). Painting, the layer stack, the paint masks, the texture library, the previews and the bake are the same code reached the same way; a zero-depth bake refines the painted area and writes the per-triangle filament indices exactly as a relief bake does, it just displaces by nothing. In the base gizmo, image_mode() does three things: - apply_image_mode_presets() pins every layer to depth 0 and turns colour on wherever the image has any, alongside the existing Standard-mode presets. - The panel leaves out Depth, Midlevel, Invert, Edge fade and Blend, and the Standard/Pro switch, which is what keeps the mesh-preparation sections hidden. Bake is disabled when no image carries colour, since that bake would refine the mesh and produce nothing. - add_texture_layer() seeds a new layer from the first library texture that has colour, rather than the first one, because most of the shipped library is grayscale height maps. The two tools share one layer stack, so opening the Image tool on a volume whose layers carry relief flattens them. Only depth changes: the paint, the image, the tiling, the mapping and any relief already baked into the mesh are untouched. The toolbar icon follows Orca's menu-icon style: 16 px, 1 px #949494 frame, #009688 on the picture inside. The shortcut is registered but unbound: every single letter the Plater context can reach is already taken, and a default that shadows one of them is worse than none. docs/HLSD/keyboard-shortcuts.md now documents that an empty default chord is a supported choice. Measured with a probe built against this tree in the texture-bake harness: a depth-0 bake of a painted cube face leaves every output vertex within 7 nm of the original surface (tolerance 1 um) while colouring 17090 of 24226 output triangles across 3 filaments; the same scene at depth 0.4 mm moves vertices up to 0.216 mm.
394 lines
22 KiB
C++
394 lines
22 KiB
C++
#include "Shortcuts.hpp"
|
|
|
|
#include "I18N.hpp"
|
|
#include "libslic3r/AppConfig.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <map>
|
|
#include "slic3r/GUI/KeyChord.hpp"
|
|
#include <optional>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <wx/accel.h>
|
|
#include <vector>
|
|
|
|
namespace Slic3r { namespace GUI {
|
|
|
|
namespace {
|
|
|
|
constexpr uint8_t GLOBAL = context_bit(ShortcutContext::Global);
|
|
constexpr uint8_t PLATER = context_bit(ShortcutContext::Plater);
|
|
constexpr uint8_t PREVIEW = context_bit(ShortcutContext::Preview);
|
|
constexpr uint8_t OBJECT_LIST = context_bit(ShortcutContext::ObjectList);
|
|
constexpr uint8_t PAINTING = context_bit(ShortcutContext::Painting);
|
|
constexpr uint8_t CANVAS = PLATER | PREVIEW;
|
|
|
|
constexpr int CTRL = wxMOD_CONTROL;
|
|
constexpr int SHIFT = wxMOD_SHIFT;
|
|
constexpr int CTRL_SHIFT = wxMOD_CONTROL | wxMOD_SHIFT;
|
|
|
|
#ifdef __APPLE__
|
|
constexpr KeyChord PREFERENCES_CHORD{ ',', CTRL };
|
|
constexpr KeyChord DELETE_CHORD{ WXK_BACK };
|
|
constexpr KeyChord MOUSE3D_CHORD{ 'M', CTRL_SHIFT };
|
|
#else
|
|
constexpr KeyChord PREFERENCES_CHORD{ 'P', CTRL };
|
|
constexpr KeyChord DELETE_CHORD{ WXK_DELETE };
|
|
constexpr KeyChord MOUSE3D_CHORD{ 'M', CTRL };
|
|
#endif
|
|
|
|
// Each row: enum value, AppConfig key, description shown in the dialog, contexts the key is
|
|
// looked up in, and the default chord. Rows are in the order the dialog lists them, within
|
|
// the sections of the Shortcut enum.
|
|
// SHORTCUT runs once per key press.
|
|
// REPEATING runs again on every auto-repeat of a held key.
|
|
// STEPPING repeats, and Shift or Ctrl held with its key select a step variant of it instead
|
|
// of another shortcut (ShortcutInfo::modifier_variants).
|
|
#define SHORTCUT(id, key, name, contexts, ...) ShortcutInfo{ Shortcut::id, key, name, contexts, __VA_ARGS__, false, false }
|
|
#define REPEATING(id, key, name, contexts, ...) ShortcutInfo{ Shortcut::id, key, name, contexts, __VA_ARGS__, true, false }
|
|
#define STEPPING(id, key, name, contexts, ...) ShortcutInfo{ Shortcut::id, key, name, contexts, __VA_ARGS__, true, true }
|
|
|
|
constexpr std::array<ShortcutInfo, size_t(Shortcut::Count)> shortcut_table = {{
|
|
// Project
|
|
SHORTCUT(NewProject, "new_project", L("New Project"), GLOBAL, { 'N', CTRL }),
|
|
SHORTCUT(OpenProject, "open_project", L("Open Project"), GLOBAL, { 'O', CTRL }),
|
|
SHORTCUT(SaveProject, "save_project", L("Save Project"), GLOBAL, { 'S', CTRL }),
|
|
SHORTCUT(SaveProjectAs, "save_project_as", L("Save Project as"), GLOBAL, { 'S', CTRL_SHIFT }),
|
|
SHORTCUT(ImportModel, "import_model", L("Import geometry data from STL/STEP/3MF/OBJ/AMF files"), GLOBAL, { 'I', CTRL }),
|
|
SHORTCUT(Publish3mf, "publish_3mf", L("Publish 3MF"), GLOBAL, { 'E', CTRL_SHIFT }),
|
|
|
|
// Slicing and printing
|
|
SHORTCUT(SlicePlate, "slice_plate", L("Slice plate"), GLOBAL, { 'R', CTRL }),
|
|
SHORTCUT(ExportSlicedFile, "export_sliced_file", L("Export plate sliced file"), GLOBAL, { 'G', CTRL }),
|
|
SHORTCUT(PrintPlate, "print_plate", L("Print plate"), GLOBAL, { 'G', CTRL_SHIFT }),
|
|
SHORTCUT(PrintHostQueue, "print_host_queue", L("Print host upload queue"), GLOBAL, { 'J', CTRL }),
|
|
|
|
// Selection
|
|
SHORTCUT(SelectAll, "select_all", L("Select all objects"), PLATER | OBJECT_LIST, { 'A', CTRL }),
|
|
SHORTCUT(SelectAllPlates, "select_all_plates", L("Select all objects on all plates"), PLATER, { 'A', CTRL_SHIFT }),
|
|
|
|
// Editing
|
|
REPEATING(Undo, "undo", L("Undo"), PLATER | OBJECT_LIST, { 'Z', CTRL }),
|
|
REPEATING(Redo, "redo", L("Redo"), PLATER | OBJECT_LIST, { 'Y', CTRL }),
|
|
SHORTCUT(Cut, "cut", L("Cut"), PLATER | OBJECT_LIST, { 'X', CTRL }),
|
|
SHORTCUT(Copy, "copy", L("Copy to clipboard"), PLATER | OBJECT_LIST, { 'C', CTRL }),
|
|
SHORTCUT(Paste, "paste", L("Paste from clipboard"), PLATER | OBJECT_LIST, { 'V', CTRL }),
|
|
SHORTCUT(DeleteSelected, "delete_selected", L("Delete Selected"), PLATER | OBJECT_LIST, DELETE_CHORD),
|
|
SHORTCUT(DeleteAll, "delete_all", L("Delete All"), PLATER, { 'D', CTRL }),
|
|
SHORTCUT(CloneSelected, "clone_selected", L("Clone Selected"), PLATER | OBJECT_LIST, { 'K', CTRL }),
|
|
|
|
// Objects
|
|
REPEATING(AddInstance, "add_instance", L("Add instance"), PLATER | OBJECT_LIST, { '+' }),
|
|
REPEATING(RemoveInstance, "remove_instance", L("Remove instance"), PLATER | OBJECT_LIST, { '-' }),
|
|
SHORTCUT(TogglePrintable, "toggle_printable", L("Toggle printable for object/part"), PLATER | OBJECT_LIST, { 'V' }),
|
|
SHORTCUT(ToggleAutoDrop, "toggle_auto_drop", L("Auto Drop"), OBJECT_LIST, { 'D' }),
|
|
|
|
// Placement
|
|
SHORTCUT(Arrange, "arrange", L("Arrange all objects"), PLATER, { 'A' }),
|
|
SHORTCUT(ArrangePlate, "arrange_plate", L("Arrange objects on selected plates"), PLATER, { 'A', SHIFT }),
|
|
SHORTCUT(Orient, "orient", L("Auto orient all/selected objects"), PLATER, { 'Q' }),
|
|
SHORTCUT(OrientPlate, "orient_plate", L("Auto orient all objects on current plate"), PLATER, { 'Q', SHIFT }),
|
|
STEPPING(MoveSelectionLeft, "move_selection_left", L("Move selection 10mm in negative X direction"), PLATER, { WXK_LEFT }),
|
|
STEPPING(MoveSelectionRight, "move_selection_right", L("Move selection 10mm in positive X direction"), PLATER, { WXK_RIGHT }),
|
|
STEPPING(MoveSelectionUp, "move_selection_up", L("Move selection 10mm in positive Y direction"), PLATER, { WXK_UP }),
|
|
STEPPING(MoveSelectionDown, "move_selection_down", L("Move selection 10mm in negative Y direction"), PLATER, { WXK_DOWN }),
|
|
REPEATING(RotateSelectionLeft, "rotate_selection_left", L("Rotate selection 45 degrees counterclockwise"), PLATER, { WXK_PAGEUP }),
|
|
REPEATING(RotateSelectionRight, "rotate_selection_right", L("Rotate selection 45 degrees clockwise"), PLATER, { WXK_PAGEDOWN }),
|
|
|
|
// Gizmos
|
|
SHORTCUT(GizmoMove, "gizmo_move", L("Gizmo move"), PLATER, { 'M' }),
|
|
SHORTCUT(GizmoRotate, "gizmo_rotate", L("Gizmo rotate"), PLATER, { 'R' }),
|
|
SHORTCUT(GizmoScale, "gizmo_scale", L("Gizmo scale"), PLATER, { 'S' }),
|
|
SHORTCUT(GizmoFlatten, "gizmo_flatten", L("Gizmo place face on bed"), PLATER, { 'F' }),
|
|
SHORTCUT(GizmoCut, "gizmo_cut", L("Gizmo cut"), PLATER, { 'C' }),
|
|
SHORTCUT(GizmoMeshBoolean, "gizmo_mesh_boolean", L("Gizmo mesh boolean"), PLATER, { 'B' }),
|
|
SHORTCUT(GizmoFdmSupports, "gizmo_fdm_supports", L("Gizmo FDM paint-on supports"), PLATER, { 'L' }),
|
|
SHORTCUT(GizmoSeam, "gizmo_seam", L("Gizmo FDM paint-on seam"), PLATER, { 'P' }),
|
|
SHORTCUT(GizmoFuzzySkin, "gizmo_fuzzy_skin", L("Gizmo FDM paint-on fuzzy skin"), PLATER, { 'H' }),
|
|
SHORTCUT(GizmoMmuSegmentation, "gizmo_mmu_segmentation", L("Gizmo multi-material painting"), PLATER, { 'N' }),
|
|
SHORTCUT(GizmoEmboss, "gizmo_emboss", L("Gizmo text emboss/engrave"), PLATER, { 'T' }),
|
|
SHORTCUT(GizmoMeasure, "gizmo_measure", L("Gizmo measure"), PLATER, { 'U' }),
|
|
SHORTCUT(GizmoAssembly, "gizmo_assembly", L("Gizmo assemble"), PLATER, { 'Y' }),
|
|
SHORTCUT(GizmoBrimEars, "gizmo_brim_ears", L("Gizmo brim ears"), PLATER, { 'E' }),
|
|
SHORTCUT(GizmoDisplacement, "gizmo_displacement", L("Gizmo texture displacement painting"), PLATER, { 'D' }),
|
|
// Unbound by default: every single letter this context can reach is already taken, and a default
|
|
// that shadows one of them is worse than none. The shortcuts dialog lists it for binding.
|
|
SHORTCUT(GizmoImage, "gizmo_image", L("Gizmo image painting"), PLATER, KeyChord{}),
|
|
|
|
// Sliders
|
|
SHORTCUT(GoToLayer, "go_to_layer", L("Jump to layer"), PREVIEW, { 'G', SHIFT }),
|
|
STEPPING(LayerSliderUp, "layer_slider_up", L("Vertical slider - Move active thumb Up"), PREVIEW, { WXK_UP }),
|
|
STEPPING(LayerSliderDown, "layer_slider_down", L("Vertical slider - Move active thumb Down"), PREVIEW, { WXK_DOWN }),
|
|
STEPPING(MovesSliderLeft, "moves_slider_left", L("Horizontal slider - Move active thumb Left"), PREVIEW, { WXK_LEFT }),
|
|
STEPPING(MovesSliderRight, "moves_slider_right", L("Horizontal slider - Move active thumb Right"), PREVIEW, { WXK_RIGHT }),
|
|
REPEATING(MovesSliderStart, "moves_slider_start", L("Horizontal slider - Move to start position"), PREVIEW, { WXK_HOME }),
|
|
REPEATING(MovesSliderEnd, "moves_slider_end", L("Horizontal slider - Move to last position"), PREVIEW, { WXK_END }),
|
|
|
|
// Painting tools
|
|
SHORTCUT(PaintToolCircle, "paint_tool_circle", L("Circle"), PAINTING, { 'C' }),
|
|
SHORTCUT(PaintToolSphere, "paint_tool_sphere", L("Sphere"), PAINTING, { 'S' }),
|
|
SHORTCUT(PaintToolFill, "paint_tool_fill", L("Fill"), PAINTING, { 'F' }),
|
|
SHORTCUT(PaintToolGapFill, "paint_tool_gap_fill", L("Gap Fill"), PAINTING, { 'G' }),
|
|
SHORTCUT(PaintToolTriangle, "paint_tool_triangle", L("Triangle"), PAINTING, { 'T' }),
|
|
SHORTCUT(PaintToolHeightRange, "paint_tool_height_range", L("Height Range"), PAINTING, { 'H' }),
|
|
|
|
// Camera
|
|
SHORTCUT(ViewDefault, "view_default", L("Camera view - Default"), GLOBAL, { '0', CTRL }),
|
|
SHORTCUT(ViewTop, "view_top", L("Camera view - Top"), GLOBAL, { '1', CTRL }),
|
|
SHORTCUT(ViewBottom, "view_bottom", L("Camera view - Bottom"), GLOBAL, { '2', CTRL }),
|
|
SHORTCUT(ViewFront, "view_front", L("Camera view - Front"), GLOBAL, { '3', CTRL }),
|
|
SHORTCUT(ViewRear, "view_rear", L("Camera view - Behind"), GLOBAL, { '4', CTRL }),
|
|
SHORTCUT(ViewLeft, "view_left", L("Camera Angle - Left side"), GLOBAL, { '5', CTRL }),
|
|
SHORTCUT(ViewRight, "view_right", L("Camera Angle - Right side"), GLOBAL, { '6', CTRL }),
|
|
SHORTCUT(ViewPlate, "view_plate", L("Camera view - Current plate"), GLOBAL, { '7', CTRL }),
|
|
REPEATING(ZoomIn, "zoom_in", L("Zoom in"), CANVAS, { 'I' }),
|
|
REPEATING(ZoomOut, "zoom_out", L("Zoom out"), CANVAS, { 'O' }),
|
|
SHORTCUT(Mouse3DSettings, "mouse3d_settings", L("Show/Hide 3Dconnexion devices settings dialog"), CANVAS, MOUSE3D_CHORD),
|
|
|
|
// Display
|
|
SHORTCUT(ShowLabels, "show_labels", L("Show object labels in 3D scene."), GLOBAL, { 'E', CTRL }),
|
|
SHORTCUT(ShowWireframe, "show_wireframe", L("Show/Hide wireframe"), CANVAS, { WXK_RETURN, CTRL_SHIFT }),
|
|
SHORTCUT(ToggleGcodeWindow, "toggle_gcode_window", L("On/Off G-code window"), PREVIEW, { 'C' }),
|
|
SHORTCUT(ToggleOneLayerMode, "toggle_one_layer_mode", L("On/Off one layer mode of the vertical slider"), PREVIEW, { 'L' }),
|
|
SHORTCUT(ToggleBeltRawGcode, "toggle_belt_raw_gcode", L("Show raw G-code (belt only)"), PREVIEW, { 'B' }),
|
|
|
|
// Application
|
|
SHORTCUT(Preferences, "preferences", L("Preferences"), GLOBAL, PREFERENCES_CHORD),
|
|
SHORTCUT(Search, "search", L("Search"), GLOBAL, { 'F', CTRL }),
|
|
SHORTCUT(SwitchView, "switch_view", L("Switch between Prepare/Preview"), CANVAS, { WXK_TAB }),
|
|
SHORTCUT(CollapseSidebar, "collapse_sidebar", L("Collapse/Expand the sidebar"), CANVAS, { WXK_TAB, SHIFT }),
|
|
SHORTCUT(ReloadDevicePage, "reload_device_page", L("Reload the device page"), CANVAS, { WXK_F5 }),
|
|
SHORTCUT(KeyboardShortcuts, "keyboard_shortcuts", L("Show keyboard shortcuts list"), CANVAS, { '?' }),
|
|
|
|
// Speed Dial
|
|
SHORTCUT(SpeedDial, "speed_dial", L("Open the Speed Dial"), GLOBAL, { WXK_SPACE }),
|
|
}};
|
|
|
|
#undef SHORTCUT
|
|
#undef REPEATING
|
|
#undef STEPPING
|
|
|
|
// The first shortcut of each section's run in shortcut_table and the section's heading, indexed
|
|
// by ShortcutSection.
|
|
struct SectionInfo
|
|
{
|
|
Shortcut first;
|
|
const char* name;
|
|
};
|
|
|
|
constexpr std::array<SectionInfo, size_t(ShortcutSection::Count)> section_table = {{
|
|
{ Shortcut::NewProject, L("Project") },
|
|
{ Shortcut::SlicePlate, L("Slicing and printing") },
|
|
{ Shortcut::SelectAll, L("Selection") },
|
|
{ Shortcut::Undo, L("Editing") },
|
|
{ Shortcut::AddInstance, L("Objects") },
|
|
{ Shortcut::Arrange, L("Placement") },
|
|
{ Shortcut::GizmoMove, L("Gizmos") },
|
|
{ Shortcut::GoToLayer, L("Sliders") },
|
|
{ Shortcut::PaintToolCircle, L("Painting tools") },
|
|
{ Shortcut::ViewDefault, L("Camera") },
|
|
{ Shortcut::ShowLabels, L("Display") },
|
|
{ Shortcut::Preferences, L("Application") },
|
|
{ Shortcut::SpeedDial, L("Speed Dial") },
|
|
}};
|
|
|
|
constexpr bool sections_follow_table_order()
|
|
{
|
|
if (section_table.front().first != Shortcut(0))
|
|
return false;
|
|
for (size_t i = 1; i < section_table.size(); ++i)
|
|
if (section_table[i].first <= section_table[i - 1].first)
|
|
return false;
|
|
return true;
|
|
}
|
|
static_assert(sections_follow_table_order(), "section_table must begin at the first shortcut and ascend");
|
|
|
|
constexpr bool table_is_in_enum_order()
|
|
{
|
|
for (size_t i = 0; i < shortcut_table.size(); ++i)
|
|
if (shortcut_table[i].id != Shortcut(i))
|
|
return false;
|
|
return true;
|
|
}
|
|
static_assert(table_is_in_enum_order(), "shortcut_table must list every Shortcut in declaration order");
|
|
|
|
const char* CONFIG_SECTION = "shortcuts";
|
|
const char* UNBOUND = "none";
|
|
|
|
bool share_context(uint8_t a, uint8_t b) { return (a & b) != 0 || (a & GLOBAL) != 0 || (b & GLOBAL) != 0; }
|
|
|
|
// The modifiers a modifier_variants shortcut accepts on top of its binding.
|
|
constexpr int STEP_MODIFIERS = wxMOD_SHIFT | wxMOD_CONTROL;
|
|
|
|
// The step modifiers chord adds to binding, 0 when chord is not a step of it; a binding with
|
|
// Shift or Ctrl of its own has no steps, so no two bindings share one.
|
|
int step_modifiers(const KeyChord& chord, const KeyChord& binding)
|
|
{
|
|
if (!binding.valid() || (binding.modifiers & STEP_MODIFIERS) != 0 || chord.key != binding.key ||
|
|
(chord.modifiers & ~STEP_MODIFIERS) != binding.modifiers)
|
|
return 0;
|
|
return chord.modifiers & STEP_MODIFIERS;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
const ShortcutInfo& shortcut_info(Shortcut shortcut) { return shortcut_table[size_t(shortcut)]; }
|
|
|
|
ShortcutSection shortcut_section(Shortcut shortcut)
|
|
{
|
|
size_t section = 0;
|
|
for (size_t i = 1; i < section_table.size(); ++i)
|
|
if (section_table[i].first <= shortcut)
|
|
section = i;
|
|
return ShortcutSection(section);
|
|
}
|
|
|
|
const char* section_name(ShortcutSection section) { return section_table[size_t(section)].name; }
|
|
|
|
std::vector<Shortcut> shortcuts_in(ShortcutContext context)
|
|
{
|
|
std::vector<Shortcut> out;
|
|
for (const ShortcutInfo& info : shortcut_table)
|
|
if (info.contexts & context_bit(context))
|
|
out.push_back(info.id);
|
|
return out;
|
|
}
|
|
|
|
ShortcutRegistry::ShortcutRegistry() { rebuild_index(); }
|
|
|
|
KeyChord ShortcutRegistry::binding(Shortcut shortcut) const
|
|
{
|
|
const std::optional<KeyChord>& override = m_overrides[size_t(shortcut)];
|
|
return override.has_value() ? *override : shortcut_info(shortcut).default_chord;
|
|
}
|
|
|
|
bool ShortcutRegistry::is_customized(Shortcut shortcut) const { return m_overrides[size_t(shortcut)].has_value(); }
|
|
|
|
std::string ShortcutRegistry::display(Shortcut shortcut) const { return binding(shortcut).display(); }
|
|
|
|
std::string ShortcutRegistry::with_key(const std::string& text, Shortcut shortcut) const
|
|
{
|
|
const std::string key = display(shortcut);
|
|
return key.empty() ? text : text + " [" + key + "]";
|
|
}
|
|
|
|
std::string ShortcutRegistry::accelerator(Shortcut shortcut) const
|
|
{
|
|
const KeyChord chord = binding(shortcut);
|
|
return chord.is_menu_accelerator() ? chord.to_string() : std::string();
|
|
}
|
|
|
|
std::optional<Shortcut> ShortcutRegistry::lookup(ShortcutContext context, const KeyChord& chord) const
|
|
{
|
|
if (!chord.valid())
|
|
return std::nullopt;
|
|
auto it = m_index.find(chord);
|
|
if (it == m_index.end())
|
|
return std::nullopt;
|
|
for (Shortcut shortcut : it->second)
|
|
if (shortcut_info(shortcut).contexts & context_bit(context))
|
|
return shortcut;
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<ShortcutRegistry::Match> ShortcutRegistry::match(ShortcutContext context, const KeyChord& chord) const
|
|
{
|
|
if (const std::optional<Shortcut> exact = lookup(context, chord); exact.has_value())
|
|
return Match{ *exact, 0 };
|
|
if ((chord.modifiers & STEP_MODIFIERS) == 0)
|
|
return std::nullopt;
|
|
for (const ShortcutInfo& info : shortcut_table)
|
|
if (info.modifier_variants && (info.contexts & context_bit(context)))
|
|
if (const int step = step_modifiers(chord, binding(info.id)); step != 0)
|
|
return Match{ info.id, step };
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::vector<Shortcut> ShortcutRegistry::conflicts(Shortcut shortcut, const KeyChord& chord) const
|
|
{
|
|
std::vector<Shortcut> out;
|
|
if (!chord.valid())
|
|
return out;
|
|
const uint8_t contexts = shortcut_info(shortcut).contexts;
|
|
for (const ShortcutInfo& other : shortcut_table)
|
|
if (other.id != shortcut && share_context(contexts, other.contexts) && binding(other.id) == chord)
|
|
out.push_back(other.id);
|
|
return out;
|
|
}
|
|
|
|
std::optional<Shortcut> ShortcutRegistry::step_owner(Shortcut shortcut, const KeyChord& chord) const
|
|
{
|
|
const uint8_t contexts = shortcut_info(shortcut).contexts;
|
|
for (const ShortcutInfo& other : shortcut_table)
|
|
if (other.modifier_variants && other.id != shortcut && share_context(contexts, other.contexts))
|
|
if (const int step = step_modifiers(chord, binding(other.id)); step == wxMOD_SHIFT || step == wxMOD_CONTROL) // the combined step stays assignable
|
|
return other.id;
|
|
return std::nullopt;
|
|
}
|
|
|
|
void ShortcutRegistry::bind(Shortcut shortcut, const KeyChord& chord)
|
|
{
|
|
const ShortcutInfo& info = shortcut_info(shortcut);
|
|
if (chord == info.default_chord)
|
|
m_overrides[size_t(shortcut)].reset();
|
|
else
|
|
m_overrides[size_t(shortcut)] = chord;
|
|
rebuild_index();
|
|
}
|
|
|
|
void ShortcutRegistry::reset(Shortcut shortcut)
|
|
{
|
|
m_overrides[size_t(shortcut)].reset();
|
|
rebuild_index();
|
|
}
|
|
|
|
void ShortcutRegistry::reset_all()
|
|
{
|
|
m_overrides.fill(std::nullopt);
|
|
rebuild_index();
|
|
}
|
|
|
|
void ShortcutRegistry::load(const AppConfig& config)
|
|
{
|
|
m_overrides.fill(std::nullopt);
|
|
if (config.has_section(CONFIG_SECTION)) {
|
|
const std::map<std::string, std::string>& section = config.get_section(CONFIG_SECTION);
|
|
for (const ShortcutInfo& info : shortcut_table) {
|
|
auto it = section.find(info.key);
|
|
if (it == section.end())
|
|
continue;
|
|
if (it->second == UNBOUND)
|
|
m_overrides[size_t(info.id)] = KeyChord{};
|
|
else if (std::optional<KeyChord> chord = KeyChord::parse(it->second); chord.has_value())
|
|
// A Global chord is seen before any text field, so it needs a modifier or a non-typing key.
|
|
if ((info.contexts & GLOBAL) == 0 || chord->is_menu_accelerator())
|
|
m_overrides[size_t(info.id)] = *chord;
|
|
}
|
|
}
|
|
rebuild_index();
|
|
}
|
|
|
|
void ShortcutRegistry::save(AppConfig& config) const
|
|
{
|
|
for (const ShortcutInfo& info : shortcut_table) {
|
|
const std::optional<KeyChord>& override = m_overrides[size_t(info.id)];
|
|
if (override.has_value())
|
|
config.set(CONFIG_SECTION, info.key, override->valid() ? override->to_string() : UNBOUND);
|
|
else
|
|
config.erase(CONFIG_SECTION, info.key);
|
|
}
|
|
}
|
|
|
|
void ShortcutRegistry::rebuild_index()
|
|
{
|
|
m_index.clear();
|
|
for (const ShortcutInfo& info : shortcut_table)
|
|
if (const KeyChord chord = binding(info.id); chord.valid())
|
|
m_index[chord].push_back(info.id);
|
|
}
|
|
|
|
}} // namespace Slic3r::GUI
|