feat(gui): assignable keyboard shortcuts (#15706)

This commit is contained in:
Kris Austin
2026-09-21 17:36:04 -03:00
committed by GitHub
parent bfe458507b
commit 824b216f18
52 changed files with 2685 additions and 1299 deletions
+4
View File
@@ -123,6 +123,8 @@ set(SLIC3R_GUI_SOURCES
GUI/PluginPickerDialog.hpp
GUI/PluginsDialog.cpp
GUI/PluginsDialog.hpp
GUI/Shortcuts.cpp
GUI/Shortcuts.hpp
GUI/SpeedDialDialog.cpp
GUI/SpeedDialDialog.hpp
GUI/ActionRegistry.cpp
@@ -335,6 +337,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Jobs/Worker.hpp
GUI/KBShortcutsDialog.cpp
GUI/KBShortcutsDialog.hpp
GUI/KeyChord.cpp
GUI/KeyChord.hpp
GUI/LibVGCode/LibVGCodeWrapper.hpp
GUI/LibVGCode/LibVGCodeWrapper.cpp
GUI/LinuxDisplayBackend.cpp
+287 -537
View File
@@ -29,6 +29,7 @@
#include "MainFrame.hpp"
#include "WipeTowerDialog.hpp"
#include "GUI_App.hpp"
#include "Shortcuts.hpp"
#include "GUI_ObjectList.hpp"
#include "GUI_Colors.hpp"
#include "Mouse3DController.hpp"
@@ -77,6 +78,7 @@
#include <float.h>
#include <algorithm>
#include <cmath>
#include <set>
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
@@ -1055,7 +1057,6 @@ wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_SLIDERS, wxKeyEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_JUMP_TO, wxKeyEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
@@ -1174,7 +1175,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed)
, m_dynamic_background_enabled(false)
, m_multisample_allowed(false)
, m_moving(false)
, m_tab_down(false)
, m_camera_movement(false)
, m_cursor_type(Standard)
, m_color_by("volume")
@@ -3229,6 +3229,9 @@ void GLCanvas3D::bind_event_handlers()
m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this);
m_canvas->Bind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this);
m_canvas->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& evt) {
// The key-up that would commit a keyboard edit goes to whatever took the focus.
if (m_selection_edit.kind != SelectionEdit::None)
finish_selection_edit();
ImGui::SetWindowFocus(nullptr);
render();
evt.Skip();
@@ -3358,8 +3361,6 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
// see include/wx/defs.h enum wxKeyCode
int keyCode = evt.GetKeyCode();
int ctrlMask = wxMOD_CONTROL;
int shiftMask = wxMOD_SHIFT;
auto imgui = wxGetApp().imgui();
if (imgui->update_key_data(evt)) {
@@ -3367,6 +3368,11 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
return;
}
#ifdef SLIC3R_CAD
const int ctrlMask = wxMOD_CONTROL;
const int shiftMask = wxMOD_SHIFT;
#endif
// Design tab: Delete/Backspace removes the selected sketch entities while a
// sketch tool is active and the canvas has focus (dialog text fields are separate
// wx controls, so this never eats their editing keys).
@@ -3425,12 +3431,6 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
}
#endif
bool is_in_painting_mode = false;
GLGizmoPainterBase *current_gizmo_painter = dynamic_cast<GLGizmoPainterBase *>(get_gizmos_manager().get_current());
if (current_gizmo_painter != nullptr) {
is_in_painting_mode = true;
}
//BBS: add orient deactivate logic
if (keyCode == WXK_ESCAPE
&& (_deactivate_arrange_menu() || _deactivate_orient_menu()))
@@ -3439,361 +3439,271 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
if (m_gizmos.on_char(evt))
return;
if ((evt.GetModifiers() & ctrlMask) != 0) {
// CTRL is pressed
switch (keyCode) {
#ifdef __APPLE__
case 'a':
case 'A':
#else /* __APPLE__ */
case WXK_CONTROL_A:
#endif /* __APPLE__ */
if (!is_in_painting_mode && !m_layers_editing.is_enabled()) {
if (evt.ShiftDown())
post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL));
else
post_event(SimpleEvent(EVT_GLCANVAS_SELECT_CURR_PLATE_ALL));
}
if (const KeyChord chord = KeyChord::from_event(evt); chord.is_punctuation() && handle_shortcut(chord))
return;
if (evt.HasModifiers()) {
evt.Skip();
return;
}
auto obj_list = wxGetApp().obj_list();
switch (keyCode)
{
case WXK_ESCAPE: { deselect_all(); break; }
// BBS: use keypad to change extruder
case '1': {
if (!m_timer_set_color.IsRunning()) {
m_timer_set_color.StartOnce(500);
break;
}
}
case '0': //Color logic for material 10
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': {
if (m_timer_set_color.IsRunning()) {
if (keyCode < '7') keyCode += 10;
m_timer_set_color.Stop();
}
if (m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation)
obj_list->set_extruder_for_selected_items(keyCode - '0');
break;
#ifdef __APPLE__
case 'c':
case 'C':
#else /* __APPLE__ */
case WXK_CONTROL_C:
#endif /* __APPLE__ */
if (!is_in_painting_mode)
post_event(SimpleEvent(EVT_GLTOOLBAR_COPY));
break;
#ifdef __APPLE__
case 'm':
case 'M':
#else /* __APPLE__ */
case WXK_CONTROL_M:
#endif /* __APPLE__ */
{
#ifdef _WIN32
if (wxGetApp().app_config->get("use_legacy_3DConnexion") == "true") {
#endif //_WIN32
#ifdef __APPLE__
// On OSX use Cmd+Shift+M to "Show/Hide 3Dconnexion devices settings dialog"
if ((evt.GetModifiers() & shiftMask) != 0) {
#endif // __APPLE__
Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller();
controller.show_settings_dialog(!controller.is_settings_dialog_shown());
m_dirty = true;
#ifdef __APPLE__
}
else
// and Cmd+M to minimize application
wxGetApp().mainframe->Iconize();
#endif // __APPLE__
#ifdef _WIN32
}
#endif //_WIN32
break;
}
#ifdef __APPLE__
case 'v':
case 'V':
#else /* __APPLE__ */
case WXK_CONTROL_V:
#endif /* __APPLE__ */
if (!is_in_painting_mode)
post_event(SimpleEvent(EVT_GLTOOLBAR_PASTE));
break;
#ifdef __APPLE__
case 'x':
case 'X':
#else /* __APPLE__ */
case WXK_CONTROL_X:
#endif /* __APPLE__ */
if (!is_in_painting_mode)
post_event(SimpleEvent(EVT_GLTOOLBAR_CUT));
break;
#ifdef __APPLE__
case 'f':
case 'F':
#else /* __APPLE__ */
case WXK_CONTROL_F:
#endif /* __APPLE__ */
break;
#ifdef __APPLE__
case 'y':
case 'Y':
#else /* __APPLE__ */
case WXK_CONTROL_Y:
#endif /* __APPLE__ */
if (m_canvas_type == CanvasView3D || m_canvas_type == CanvasAssembleView) {
post_event(SimpleEvent(EVT_GLCANVAS_REDO));
}
break;
#ifdef __APPLE__
case 'z':
case 'Z':
#else /* __APPLE__ */
case WXK_CONTROL_Z:
#endif /* __APPLE__ */
// only support redu/undo in CanvasView3D
if (m_canvas_type == CanvasView3D || m_canvas_type == CanvasAssembleView) {
post_event(SimpleEvent(EVT_GLCANVAS_UNDO));
}
break;
// BBS
#ifdef __APPLE__
case 'E':
case 'e':
#else /* __APPLE__ */
case WXK_CONTROL_E:
#endif /* __APPLE__ */
{ m_labels.show(!m_labels.is_shown()); m_dirty = true; break; }
case '0': {
select_view("plate");
zoom_to_bed();
break; }
case '1': { select_view("top"); break; }
case '2': { select_view("bottom"); break; }
case '3': { select_view("front"); break; }
case '4': { select_view("rear"); break; }
case '5': { select_view("left"); break; }
case '6': { select_view("right"); break; }
case '7': { select_plate(); break; }
//case WXK_BACK:
//case WXK_DELETE:
#ifdef __APPLE__
case 'd':
case 'D':
#else /* __APPLE__ */
case WXK_CONTROL_D:
#endif /* __APPLE__ */
post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL));
break;
#ifdef __APPLE__
case 'k':
case 'K':
#else /* __APPLE__ */
case WXK_CONTROL_K:
#endif /* __APPLE__ */
post_event(SimpleEvent(EVT_GLTOOLBAR_CLONE));
break;
default: evt.Skip();
}
} else {
auto obj_list = wxGetApp().obj_list();
switch (keyCode)
{
//case WXK_BACK:
case WXK_DELETE: { post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; }
// BBS
#ifdef __APPLE__
case WXK_BACK: { post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; }
#endif
case WXK_ESCAPE: { deselect_all(); break; }
case WXK_F5: {
if (wxGetApp().mainframe->is_printer_view())
wxGetApp().mainframe->load_printer_url();
//if ((wxGetApp().is_editor() && !wxGetApp().plater()->model().objects.empty()) ||
// (wxGetApp().is_gcode_viewer() && !wxGetApp().plater()->get_last_loaded_gcode().empty()))
// post_event(SimpleEvent(EVT_GLCANVAS_RELOAD_FROM_DISK));
break;
}
// BBS: use keypad to change extruder
case '1': {
if (!m_timer_set_color.IsRunning()) {
m_timer_set_color.StartOnce(500);
break;
}
}
case '0': //Color logic for material 10
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': {
if (m_timer_set_color.IsRunning()) {
if (keyCode < '7') keyCode += 10;
m_timer_set_color.Stop();
}
if (m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation)
obj_list->set_extruder_for_selected_items(keyCode - '0');
break;
}
case '+': {
if (dynamic_cast<Preview*>(m_canvas->GetParent()) != nullptr)
post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt));
else
post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, +1));
break;
}
case '-': {
if (dynamic_cast<Preview*>(m_canvas->GetParent()) != nullptr)
post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt));
else
post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, -1));
break;
}
case '?': { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; }
case 'A':
case 'a':
{
if ((evt.GetModifiers() & shiftMask) != 0)
post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE_PARTPLATE));
else
post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE));
break;
}
//case 'B':
//case 'b': { zoom_to_bed(); break; }
case 'C':
case 'c': { wxGetApp().toggle_show_gcode_window(); m_dirty = true; request_extra_frame(); break; }
//case 'G':
//case 'g': {
// if ((evt.GetModifiers() & shiftMask) != 0) {
// if (dynamic_cast<Preview*>(m_canvas->GetParent()) != nullptr)
// post_event(wxKeyEvent(EVT_GLCANVAS_JUMP_TO, evt));
// }
// break;
//}
case 'I':
case 'i': { _update_camera_zoom(1.0); break; }
//case 'K':
//case 'k': { wxGetApp().plater()->get_camera().select_next_type(); m_dirty = true; break; }
//case 'L':
//case 'l': {
//if (!m_main_toolbar.is_enabled()) {
// m_gcode_viewer.enable_legend(!m_gcode_viewer.is_legend_enabled());
// m_dirty = true;
// wxGetApp().plater()->update_preview_bottom_toolbar();
//}
//break;
//}
case 'O':
case 'o': { _update_camera_zoom(-1.0); break; }
case 'q':
case 'Q':
{
if ((evt.GetModifiers() & shiftMask) != 0)
post_event(SimpleEvent(EVT_GLCANVAS_ORIENT_PARTPLATE));
else
post_event(SimpleEvent(EVT_GLCANVAS_ORIENT));
break;
}
//case 'Z':
//case 'z': {
// if (!m_selection.is_empty())
// zoom_to_selection();
// else {
// if (!m_volumes.empty())
// zoom_to_volumes();
// else
// _zoom_to_box(m_gcode_viewer.get_paths_bounding_box());
// }
// break;
//}
case 'v':
case 'V': { post_event(SimpleEvent(EVT_GLCANVAS_PRINTABLE)); break; }
default: { evt.Skip(); break; }
}
}
default: { evt.Skip(); break; }
}
}
class TranslationProcessor
bool GLCanvas3D::handle_shortcut(const KeyChord& chord)
{
using UpAction = std::function<void(void)>;
using DownAction = std::function<void(const Vec3d&, bool, bool)>;
const ShortcutContext context = m_canvas_type == CanvasPreview ? ShortcutContext::Preview : ShortcutContext::Plater;
const std::optional<ShortcutRegistry::Match> match = wxGetApp().shortcuts().match(context, chord);
if (!match.has_value())
return false;
const Shortcut shortcut = match->shortcut;
const int held = match->step_modifiers;
if (m_key_down.repeat && !shortcut_info(shortcut).repeatable)
return true;
UpAction m_up_action{ nullptr };
DownAction m_down_action{ nullptr };
const bool painting = dynamic_cast<GLGizmoPainterBase*>(m_gizmos.get_current()) != nullptr;
const bool can_edit = m_canvas_type == CanvasView3D || m_canvas_type == CanvasAssembleView;
auto edit_selection = [this](SelectionEdit::Kind kind, const Vec3d& direction) {
if (!m_gizmos.is_enabled() || m_selection.is_empty() || m_canvas_type == CanvasAssembleView)
return false;
m_selection_edit.kind = kind;
m_selection_edit.key = m_key_down.code;
m_selection_edit.direction = direction;
return true;
};
auto move_selection = [&](const Vec3d& direction) {
if (edit_selection(SelectionEdit::Move, direction))
apply_selection_move((held & wxMOD_SHIFT) != 0, (held & wxMOD_CONTROL) != 0);
};
auto rotate_selection = [&](double angle_z_rad) {
if (edit_selection(SelectionEdit::Rotate, Vec3d::UnitZ()))
apply_selection_rotate(angle_z_rad);
};
auto step_slider = [this, held](auto&& step) {
IMSlider* layers = get_gcode_viewer().get_layers_slider();
IMSlider* moves = get_gcode_viewer().get_moves_slider();
step(layers, moves, held != 0 ? 5 : 1);
if (layers->is_dirty() && layers->is_one_layer())
layers->SetLowerValue(layers->GetHigherValue());
m_dirty = true;
};
bool m_running{ false };
Vec3d m_direction{ Vec3d::UnitX() };
public:
TranslationProcessor(UpAction up_action, DownAction down_action)
: m_up_action(up_action), m_down_action(down_action)
{
}
void process(wxKeyEvent& evt)
{
const int keyCode = evt.GetKeyCode();
wxEventType type = evt.GetEventType();
if (type == wxEVT_KEY_UP) {
switch (keyCode)
{
case WXK_NUMPAD_LEFT: case WXK_LEFT:
case WXK_NUMPAD_RIGHT: case WXK_RIGHT:
case WXK_NUMPAD_UP: case WXK_UP:
case WXK_NUMPAD_DOWN: case WXK_DOWN:
{
m_running = false;
m_up_action();
break;
}
default: { break; }
}
}
else if (type == wxEVT_KEY_DOWN) {
bool apply = false;
switch (keyCode)
{
case WXK_SHIFT:
{
if (m_running)
apply = true;
break;
}
case WXK_NUMPAD_LEFT:
case WXK_LEFT:
{
m_direction = -Vec3d::UnitX();
apply = true;
break;
}
case WXK_NUMPAD_RIGHT:
case WXK_RIGHT:
{
m_direction = Vec3d::UnitX();
apply = true;
break;
}
case WXK_NUMPAD_UP:
case WXK_UP:
{
m_direction = Vec3d::UnitY();
apply = true;
break;
}
case WXK_NUMPAD_DOWN:
case WXK_DOWN:
{
m_direction = -Vec3d::UnitY();
apply = true;
break;
}
default: { break; }
}
if (apply) {
m_running = true;
m_down_action(m_direction, evt.ShiftDown(), evt.CmdDown());
}
switch (shortcut) {
case Shortcut::SelectAll:
if (!painting && !m_layers_editing.is_enabled())
post_event(SimpleEvent(EVT_GLCANVAS_SELECT_CURR_PLATE_ALL));
break;
case Shortcut::SelectAllPlates:
if (!painting && !m_layers_editing.is_enabled())
post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL));
break;
case Shortcut::Copy: if (!painting) post_event(SimpleEvent(EVT_GLTOOLBAR_COPY)); break;
case Shortcut::Paste: if (!painting) post_event(SimpleEvent(EVT_GLTOOLBAR_PASTE)); break;
case Shortcut::Cut: if (!painting) post_event(SimpleEvent(EVT_GLTOOLBAR_CUT)); break;
case Shortcut::Undo: if (can_edit) post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); break;
case Shortcut::Redo: if (can_edit) post_event(SimpleEvent(EVT_GLCANVAS_REDO)); break;
case Shortcut::DeleteSelected:
if (!m_gizmos.on_delete_key())
post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE));
break;
case Shortcut::DeleteAll: post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); break;
case Shortcut::CloneSelected: post_event(SimpleEvent(EVT_GLTOOLBAR_CLONE)); break;
case Shortcut::AddInstance: post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); break;
case Shortcut::RemoveInstance: post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); break;
case Shortcut::TogglePrintable: post_event(SimpleEvent(EVT_GLCANVAS_PRINTABLE)); break;
case Shortcut::Arrange:
if (!m_gizmos.is_running())
post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE));
break;
case Shortcut::ArrangePlate:
if (!m_gizmos.is_running())
post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE_PARTPLATE));
break;
case Shortcut::Orient: post_event(SimpleEvent(EVT_GLCANVAS_ORIENT)); break;
case Shortcut::OrientPlate: post_event(SimpleEvent(EVT_GLCANVAS_ORIENT_PARTPLATE)); break;
case Shortcut::RotateSelectionLeft: rotate_selection(0.25 * M_PI); break;
case Shortcut::RotateSelectionRight: rotate_selection(-0.25 * M_PI); break;
case Shortcut::MoveSelectionLeft: move_selection(-Vec3d::UnitX()); break;
case Shortcut::MoveSelectionRight: move_selection(Vec3d::UnitX()); break;
case Shortcut::MoveSelectionUp: move_selection(Vec3d::UnitY()); break;
case Shortcut::MoveSelectionDown: move_selection(-Vec3d::UnitY()); break;
case Shortcut::ZoomIn: _update_camera_zoom(1.0); break;
case Shortcut::ZoomOut: _update_camera_zoom(-1.0); break;
case Shortcut::SwitchView: post_event(SimpleEvent(EVT_GLCANVAS_TAB)); break;
case Shortcut::CollapseSidebar:
if (!wxGetApp().is_gcode_viewer())
post_event(SimpleEvent(EVT_GLCANVAS_COLLAPSE_SIDEBAR));
break;
case Shortcut::ShowWireframe:
wxGetApp().plater()->toggle_show_wireframe();
m_dirty = true;
break;
case Shortcut::Mouse3DSettings: {
#ifdef _WIN32
if (wxGetApp().app_config->get("use_legacy_3DConnexion") == "true") {
#endif //_WIN32
Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller();
controller.show_settings_dialog(!controller.is_settings_dialog_shown());
m_dirty = true;
#ifdef _WIN32
}
#endif //_WIN32
break;
}
};
case Shortcut::ReloadDevicePage:
if (wxGetApp().mainframe->is_printer_view())
wxGetApp().mainframe->load_printer_url();
break;
case Shortcut::KeyboardShortcuts: post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break;
case Shortcut::ToggleGcodeWindow:
wxGetApp().toggle_show_gcode_window();
m_dirty = true;
request_extra_frame();
break;
case Shortcut::ToggleOneLayerMode:
get_gcode_viewer().get_layers_slider()->switch_one_layer_mode();
m_dirty = true;
break;
case Shortcut::GoToLayer:
if (!m_gizmos.is_enabled()) {
get_gcode_viewer().get_layers_slider()->show_go_to_layer(true);
m_dirty = true;
}
break;
case Shortcut::LayerSliderUp:
case Shortcut::LayerSliderDown:
step_slider([up = shortcut == Shortcut::LayerSliderUp](IMSlider* layers, IMSlider* moves, int increment) {
const int delta = up ? increment : -increment;
if (layers->GetSelection() == ssHigher) {
layers->SetHigherValue(layers->GetHigherValue() + delta);
moves->SetHigherValue(moves->GetMaxValue());
}
else if (layers->GetSelection() == ssLower)
layers->SetLowerValue(layers->GetLowerValue() + delta);
});
break;
case Shortcut::MovesSliderLeft:
step_slider([](IMSlider* layers, IMSlider* moves, int increment) {
if (moves->GetHigherValue() == moves->GetMinValue() && layers->GetHigherValue() > layers->GetMinValue()) {
layers->SetHigherValue(layers->GetHigherValue() - 1);
moves->SetHigherValue(moves->GetMaxValue());
}
else
moves->SetHigherValue(moves->GetHigherValue() - increment);
});
break;
case Shortcut::MovesSliderRight:
step_slider([](IMSlider* layers, IMSlider* moves, int increment) {
if (moves->GetHigherValue() == moves->GetMaxValue() && layers->GetHigherValue() < layers->GetMaxValue()) {
layers->SetHigherValue(layers->GetHigherValue() + 1);
moves->SetHigherValue(moves->GetMinValue());
}
else
moves->SetHigherValue(moves->GetHigherValue() + increment);
});
break;
case Shortcut::MovesSliderStart:
case Shortcut::MovesSliderEnd:
step_slider([start = shortcut == Shortcut::MovesSliderStart](IMSlider*, IMSlider* moves, int) {
moves->SetHigherValue(start ? moves->GetMinValue() : moves->GetMaxValue());
moves->set_as_dirty();
});
break;
default:
if (!m_gizmos.open_gizmo_by_shortcut(shortcut))
return false;
m_dirty = true;
break;
}
return true;
}
void GLCanvas3D::apply_selection_move(bool slow, bool camera_space)
{
m_selection.setup_cache();
const double multiplier = slow ? 1.0 : 10.0;
Vec3d displacement;
if (camera_space) {
Eigen::Matrix<double, 3, 3, Eigen::DontAlign> inv_view_3x3 = wxGetApp().plater()->get_camera().get_view_matrix().inverse().matrix().block(0, 0, 3, 3);
displacement = multiplier * (inv_view_3x3 * m_selection_edit.direction);
displacement.z() = 0.0;
}
else
displacement = multiplier * m_selection_edit.direction;
TransformationType trafo_type;
trafo_type.set_relative();
m_selection.translate(displacement, trafo_type);
m_dirty = true;
}
void GLCanvas3D::apply_selection_rotate(double angle_z_rad)
{
m_selection.setup_cache();
m_selection.rotate(angle_z_rad * m_selection_edit.direction, TransformationType(TransformationType::World_Relative_Joint));
m_dirty = true;
}
void GLCanvas3D::finish_selection_edit()
{
const SelectionEdit::Kind kind = m_selection_edit.kind;
m_selection_edit.kind = SelectionEdit::None;
if (kind == SelectionEdit::Move)
do_move(L("Tool move"));
else
do_rotate(L("Tool Rotate"));
m_gizmos.update_data();
// Let the plater know that the dragging finished, so a delayed refresh
// of the scene with the background processing data should be performed.
post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
// updates camera target constraints
refresh_camera_scene_box();
m_dirty = true;
}
// Keys held since their last key-up, shared by every canvas because a shortcut can move the
// focus, and with it the key-up, to another one.
static std::set<int> s_keys_down;
static bool key_repeats(int key)
{
for (auto it = s_keys_down.begin(); it != s_keys_down.end();) // drops keys released while no canvas had the focus
it = wxGetKeyState(wxKeyCode(*it)) ? std::next(it) : s_keys_down.erase(it);
return !s_keys_down.insert(key).second;
}
static void key_released(int key) { s_keys_down.erase(key); }
void GLCanvas3D::on_key(wxKeyEvent& evt)
{
@@ -3811,45 +3721,11 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
}
#endif
static GLCanvas3D const * thiz = nullptr;
static TranslationProcessor translationProcessor(nullptr, nullptr);
if (thiz != this) {
thiz = this;
translationProcessor = TranslationProcessor(
[this]() {
do_move(L("Tool move"));
m_gizmos.update_data();
// BBS
//wxGetApp().obj_manipul()->set_dirty();
// Let the plater know that the dragging finished, so a delayed refresh
// of the scene with the background processing data should be performed.
post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
// updates camera target constraints
refresh_camera_scene_box();
m_dirty = true;
},
[this](const Vec3d& direction, bool slow, bool camera_space) {
m_selection.setup_cache();
double multiplier = slow ? 1.0 : 10.0;
Vec3d displacement;
if (camera_space) {
Eigen::Matrix<double, 3, 3, Eigen::DontAlign> inv_view_3x3 = wxGetApp().plater()->get_camera().get_view_matrix().inverse().matrix().block(0, 0, 3, 3);
displacement = multiplier * (inv_view_3x3 * direction);
displacement.z() = 0.0;
}
else
displacement = multiplier * direction;
TransformationType trafo_type;
trafo_type.set_relative();
m_selection.translate(displacement, trafo_type);
m_dirty = true;
}
);}
const int keyCode = evt.GetKeyCode();
if (evt.GetEventType() == wxEVT_KEY_DOWN)
m_key_down = { keyCode, key_repeats(keyCode) };
else if (evt.GetEventType() == wxEVT_KEY_UP)
key_released(keyCode);
auto imgui = wxGetApp().imgui();
if (imgui->update_key_data(evt))
@@ -3863,23 +3739,8 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
wxGetApp().plater()->toggle_render_statistic_dialog();
m_dirty = true;
#endif
} else if ((evt.ShiftDown() && evt.ControlDown() && keyCode == WXK_RETURN) ||
(evt.ShiftDown() && evt.AltDown() && keyCode == WXK_RETURN)) {
wxGetApp().plater()->toggle_show_wireframe();
m_dirty = true;
}
else if (m_tab_down && keyCode == WXK_TAB && !evt.HasAnyModifiers()) {
// Enable switching between 3D and Preview with Tab
// m_canvas->HandleAsNavigationKey(evt); // XXX: Doesn't work in some cases / on Linux
post_event(SimpleEvent(EVT_GLCANVAS_TAB));
}
else if (keyCode == WXK_TAB && evt.ShiftDown() && !evt.ControlDown() && ! wxGetApp().is_gcode_viewer()) {
// Collapse side-panel with Shift+Tab
post_event(SimpleEvent(EVT_GLCANVAS_COLLAPSE_SIDEBAR));
}
else if (keyCode == WXK_SHIFT) {
translationProcessor.process(evt);
if (m_picking_enabled && m_rectangle_selection.is_dragging()) {
_update_selection_from_hover();
m_rectangle_selection.stop_dragging();
@@ -3905,70 +3766,15 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
}
else if (keyCode == WXK_CONTROL)
m_dirty = true;
else if (m_gizmos.is_enabled() && !m_selection.is_empty() && m_canvas_type != CanvasAssembleView) {
translationProcessor.process(evt);
switch (keyCode)
{
case WXK_NUMPAD_PAGEUP: case WXK_PAGEUP:
case WXK_NUMPAD_PAGEDOWN: case WXK_PAGEDOWN:
{
do_rotate(L("Tool Rotate"));
m_gizmos.update_data();
// BBS
//wxGetApp().obj_manipul()->set_dirty();
// Let the plater know that the dragging finished, so a delayed refresh
// of the scene with the background processing data should be performed.
post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
// updates camera target constraints
refresh_camera_scene_box();
m_dirty = true;
break;
}
default: { break; }
}
}
// BBS: add select view logic
if (evt.ControlDown()) {
switch (keyCode) {
case '0':
case WXK_NUMPAD0: //0 on numpad
{ select_view("plate");
zoom_to_bed();
break;
}
case '1':
case WXK_NUMPAD1: //1 on numpad
{ select_view("top"); break; }
case '2':
case WXK_NUMPAD2: //2 on numpad
{ select_view("bottom"); break; }
case '3':
case WXK_NUMPAD3: //3 on numpad
{ select_view("front"); break; }
case '4':
case WXK_NUMPAD4: //4 on numpad
{ select_view("rear"); break; }
case '5':
case WXK_NUMPAD5: //5 on numpad
{ select_view("left"); break; }
case '6':
case WXK_NUMPAD6: //6 on numpad
{ select_view("right"); break; }
case '7':
case WXK_NUMPAD7: //7 on numpad
{ select_plate(); break; }
default: break;
}
}
else if (m_selection_edit.kind != SelectionEdit::None && keyCode == m_selection_edit.key)
finish_selection_edit();
}
else if (evt.GetEventType() == wxEVT_KEY_DOWN) {
m_tab_down = keyCode == WXK_TAB && !evt.HasAnyModifiers();
if (handle_shortcut(KeyChord::from_event(evt)))
return;
if (keyCode == WXK_SHIFT) {
translationProcessor.process(evt);
if (m_selection_edit.kind == SelectionEdit::Move)
apply_selection_move(true, evt.CmdDown());
if (m_picking_enabled /*&& (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)*/)
{
@@ -3985,68 +3791,6 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
}
else if (keyCode == WXK_CONTROL)
m_dirty = true;
else if (m_gizmos.is_enabled() && !m_selection.is_empty() && m_canvas_type != CanvasAssembleView) {
auto _do_rotate = [this](double angle_z_rad) {
m_selection.setup_cache();
m_selection.rotate(Vec3d(0.0, 0.0, angle_z_rad), TransformationType(TransformationType::World_Relative_Joint));
m_dirty = true;
// wxGetApp().obj_manipul()->set_dirty();
};
translationProcessor.process(evt);
switch (keyCode)
{
case WXK_NUMPAD_PAGEUP: case WXK_PAGEUP: { _do_rotate(0.25 * M_PI); break; }
case WXK_NUMPAD_PAGEDOWN: case WXK_PAGEDOWN: { _do_rotate(-0.25 * M_PI); break; }
default: { break; }
}
} else if (!m_gizmos.is_enabled()) {
// DoubleSlider navigation in Preview
if (m_canvas_type == CanvasPreview) {
IMSlider *m_layers_slider = get_gcode_viewer().get_layers_slider();
IMSlider *m_moves_slider = get_gcode_viewer().get_moves_slider();
int increment = (evt.CmdDown() || evt.ShiftDown()) ? 5 : 1;
if ((evt.CmdDown() || evt.ShiftDown()) && evt.GetKeyCode() == 'G') {
m_layers_slider->show_go_to_layer(true);
}
else if (keyCode == WXK_UP || keyCode == WXK_DOWN) {
int new_pos;
if (m_layers_slider->GetSelection() == ssHigher) {
new_pos = keyCode == WXK_UP ? m_layers_slider->GetHigherValue() + increment : m_layers_slider->GetHigherValue() - increment;
m_layers_slider->SetHigherValue(new_pos);
m_moves_slider->SetHigherValue(m_moves_slider->GetMaxValue());
}
else if (m_layers_slider->GetSelection() == ssLower) {
new_pos = keyCode == WXK_UP ? m_layers_slider->GetLowerValue() + increment : m_layers_slider->GetLowerValue() - increment;
m_layers_slider->SetLowerValue(new_pos);
}
} else if (keyCode == WXK_LEFT) {
if (m_moves_slider->GetHigherValue() == m_moves_slider->GetMinValue() && (m_layers_slider->GetHigherValue() > m_layers_slider->GetMinValue())) {
m_layers_slider->SetHigherValue(m_layers_slider->GetHigherValue() - 1);
m_moves_slider->SetHigherValue(m_moves_slider->GetMaxValue());
} else {
m_moves_slider->SetHigherValue(m_moves_slider->GetHigherValue() - increment);
}
} else if (keyCode == WXK_RIGHT) {
if (m_moves_slider->GetHigherValue() == m_moves_slider->GetMaxValue() && (m_layers_slider->GetHigherValue() < m_layers_slider->GetMaxValue())) {
m_layers_slider->SetHigherValue(m_layers_slider->GetHigherValue() + 1);
m_moves_slider->SetHigherValue(m_moves_slider->GetMinValue());
} else {
m_moves_slider->SetHigherValue(m_moves_slider->GetHigherValue() + increment);
}
} else if (keyCode == WXK_HOME || keyCode == WXK_END) {
const int new_pos = keyCode == WXK_HOME ? m_moves_slider->GetMinValue() : m_moves_slider->GetMaxValue();
m_moves_slider->SetHigherValue(new_pos);
m_moves_slider->set_as_dirty();
}
if (m_layers_slider->is_dirty() && m_layers_slider->is_one_layer())
m_layers_slider->SetLowerValue(m_layers_slider->GetHigherValue());
m_dirty = true;
}
}
}
}
else return;
@@ -7115,7 +6859,6 @@ bool GLCanvas3D::_init_main_toolbar()
item.name = "add";
item.icon_filename = m_is_dark ? "toolbar_open_dark.svg" : "toolbar_open.svg";
item.tooltip = _utf8(L("Add")) + " [" + GUI::shortkey_ctrl_prefix() + "I]";
item.sprite_id = 0;
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); };
item.enabling_callback = []()->bool {return wxGetApp().plater()->can_add_model(); };
@@ -7133,7 +6876,6 @@ bool GLCanvas3D::_init_main_toolbar()
item.name = "orient";
item.icon_filename = m_is_dark ? "toolbar_orient_dark.svg" : "toolbar_orient.svg";
item.tooltip = _utf8(L("Auto orient all/selected objects")) + " [Q]\n" + _utf8(L("Auto orient all objects on current plate")) + " [" + _utf8(L("Shift+")) + "Q]";
item.sprite_id++;
item.left.render_callback = nullptr;
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); };
@@ -7155,7 +6897,6 @@ bool GLCanvas3D::_init_main_toolbar()
item.name = "arrange";
item.icon_filename = m_is_dark ? "toolbar_arrange_dark.svg" : "toolbar_arrange.svg";
item.tooltip = _utf8(L("Arrange all objects")) + " [A]\n" + _utf8(L("Arrange objects on selected plates")) + " [" + _utf8(L("Shift+")) + "A]";
item.sprite_id++;
item.left.action_callback = []() {};
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); };
@@ -7179,7 +6920,6 @@ bool GLCanvas3D::_init_main_toolbar()
item.name = "more";
item.icon_filename = m_is_dark ? "instance_add_dark.svg" : "instance_add.svg";
item.tooltip = _utf8(L("Add instance")) + " [+]";
item.sprite_id++;
item.left.render_callback = nullptr;
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); };
@@ -7191,7 +6931,6 @@ bool GLCanvas3D::_init_main_toolbar()
item.name = "fewer";
item.icon_filename = m_is_dark ? "instance_remove_dark.svg" : "instance_remove.svg";
item.tooltip = _utf8(L("Remove instance")) + " [-]";
item.sprite_id++;
item.left.render_callback = nullptr;
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); };
@@ -7241,9 +6980,20 @@ bool GLCanvas3D::_init_main_toolbar()
if (!m_main_toolbar.add_item(item))
return false;
update_shortcut_tooltips();
return true;
}
void GLCanvas3D::update_shortcut_tooltips()
{
const ShortcutRegistry& shortcuts = wxGetApp().shortcuts();
m_main_toolbar.set_tooltip(m_main_toolbar.get_item_id("add"), shortcuts.with_key(_u8L("Add"), Shortcut::ImportModel));
m_main_toolbar.set_tooltip(m_main_toolbar.get_item_id("orient"), shortcuts.with_key(_u8L("Auto orient all/selected objects"), Shortcut::Orient) + "\n" + shortcuts.with_key(_u8L("Auto orient all objects on current plate"), Shortcut::OrientPlate));
m_main_toolbar.set_tooltip(m_main_toolbar.get_item_id("arrange"), shortcuts.with_key(_u8L("Arrange all objects"), Shortcut::Arrange) + "\n" + shortcuts.with_key(_u8L("Arrange objects on selected plates"), Shortcut::ArrangePlate));
m_main_toolbar.set_tooltip(m_main_toolbar.get_item_id("more"), shortcuts.with_key(_u8L("Add instance"), Shortcut::AddInstance));
m_main_toolbar.set_tooltip(m_main_toolbar.get_item_id("fewer"), shortcuts.with_key(_u8L("Remove instance"), Shortcut::RemoveInstance));
}
//BBS: GUI refactor: GLToolbar
bool GLCanvas3D::_init_select_plate_toolbar()
{
+25 -2
View File
@@ -63,6 +63,7 @@ class PartPlateList;
#ifdef SLIC3R_CAD
class DesignSketchTool; // Design tab: interactive 2D sketch tool
#endif
struct KeyChord;
#if ENABLE_RETINA_GL
class RetinaHelper;
@@ -185,7 +186,6 @@ wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_SLIDERS, wxKeyEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_JUMP_TO, wxKeyEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
@@ -626,7 +626,23 @@ private:
bool m_dynamic_background_enabled;
bool m_multisample_allowed;
bool m_moving;
bool m_tab_down;
// The key-down being dispatched, kept for the char event that may follow it.
struct KeyDown
{
int code = WXK_NONE;
bool repeat = false;
};
KeyDown m_key_down;
// A keyboard move or rotation of the selection runs from the key-down that started it to
// that key's release, so a held key becomes one undo step.
struct SelectionEdit
{
enum Kind { None, Move, Rotate };
Kind kind = None;
int key = WXK_NONE; // raw key code of the key-down, matched against the key-up
Vec3d direction{ Vec3d::UnitX() };
};
SelectionEdit m_selection_edit;
bool m_camera_movement;
//BBS: add toolpath outside
bool m_toolpath_outside{ false };
@@ -1094,6 +1110,13 @@ public:
void on_idle(wxIdleEvent& evt);
void on_char(wxKeyEvent& evt);
void on_key(wxKeyEvent& evt);
// Runs the Plater/Preview shortcut bound to chord, swallowing auto-repeats of one-shot
// shortcuts; false when nothing is bound.
bool handle_shortcut(const KeyChord& chord);
void apply_selection_move(bool slow, bool camera_space);
void apply_selection_rotate(double angle_z_rad);
void finish_selection_edit();
void update_shortcut_tooltips();
void on_mouse_wheel(wxMouseEvent& evt);
void on_timer(wxTimerEvent& evt);
void on_render_timer(wxTimerEvent& evt);
+26 -4
View File
@@ -3,6 +3,7 @@
#include "libslic3r/Technologies.hpp"
#include "libslic3r/Platform.hpp"
#include "GUI_App.hpp"
#include "Shortcuts.hpp"
#include "BindDialog.hpp"
#include "DeviceManager.hpp"
#include "HMS.hpp"
@@ -1120,6 +1121,8 @@ GUI_App::GUI_App()
{
//app config initializes early becasuse it is used in instance checking in OrcaSlicer.cpp
this->init_app_config();
m_shortcuts = std::make_unique<ShortcutRegistry>();
m_shortcuts->load(*app_config);
this->init_download_path();
// Note: the WebView2 runtime check (init_webview_runtime) used to run here, but
// the constructor executes before wxWidgets is fully initialized and before the
@@ -4677,12 +4680,28 @@ void GUI_App::system_info()
//dlg.ShowModal();
}
void GUI_App::keyboard_shortcuts()
void GUI_App::keyboard_shortcuts(ShortcutContext page, wxWindow* parent)
{
KBShortcutsDialog dlg;
KBShortcutsDialog dlg(parent != nullptr ? parent : mainframe, page);
dlg.ShowModal();
}
void GUI_App::on_shortcuts_changed()
{
m_shortcuts->save(*app_config);
app_config->save();
if (mainframe == nullptr)
return;
mainframe->update_shortcut_labels();
if (Plater* plater = this->plater(); plater != nullptr) {
if (GLCanvas3D* canvas = plater->get_view3D_canvas3D(); canvas != nullptr)
canvas->update_shortcut_tooltips();
#ifdef __WXOSX__
obj_list()->update_shortcut_accelerators();
#endif
}
}
void GUI_App::troubleshoot()
{
TroubleshootDialog dlg;
@@ -8465,7 +8484,9 @@ void GUI_App::open_exportpresetbundledialog(size_t open_on_tab, const std::strin
}
}
void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_option)
void GUI_App::open_preferences() { open_preferences(PreferencesTab::General); }
void GUI_App::open_preferences(PreferencesTab tab, const std::string& highlight_option)
{
// Render settings the canvas reads every frame; a change needs one redraw to show.
static constexpr const char* opengl_render_setting_keys[] = {
@@ -8482,7 +8503,8 @@ void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_
// the dialog needs to be destroyed before the call to recreate_GUI()
// or sometimes the application crashes into wxDialogBase() destructor
// so we put it into an inner scope
PreferencesDialog dlg(mainframe, open_on_tab, highlight_option);
PreferencesDialog dlg(mainframe);
dlg.select_tab(tab, highlight_option);
dlg.ShowModal();
need_recreate_gui = dlg.recreate_GUI();
pending_language = dlg.pending_language();
+10 -2
View File
@@ -70,6 +70,9 @@ namespace GUI{
class RemovableDriveManager;
class OtherInstanceMessageHandler;
class ShortcutRegistry;
enum class ShortcutContext : uint8_t;
enum class PreferencesTab;
class MainFrame;
class Sidebar;
class ObjectSettings;
@@ -286,6 +289,7 @@ private:
std::unique_ptr<RemovableDriveManager> m_removable_drive_manager;
std::unique_ptr<ImGuiWrapper> m_imgui;
std::unique_ptr<ShortcutRegistry> m_shortcuts;
std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
std::unique_ptr <OtherInstanceMessageHandler> m_other_instance_message_handler;
std::unique_ptr <wxSingleInstanceChecker> m_single_instance_checker;
@@ -483,7 +487,7 @@ public:
void recreate_GUI(const wxString& message);
void system_info();
void keyboard_shortcuts();
void keyboard_shortcuts(ShortcutContext page, wxWindow* parent = nullptr); // the main frame when null
void troubleshoot();
void load_project(wxWindow *parent, wxString& input_file) const;
void import_model(wxWindow *parent, wxArrayString& input_files) const;
@@ -639,7 +643,8 @@ public:
wxString current_language_code_safe() const;
bool is_localized() const { return m_wxLocale->GetLocale() != "English"; }
void open_preferences(size_t open_on_tab = 0, const std::string& highlight_option = std::string());
void open_preferences(); // on the General tab
void open_preferences(PreferencesTab tab, const std::string& highlight_option = std::string());
void open_presetbundledialog(size_t open_on_tab = 0, const std::string& highlight_option = std::string());
void open_plugins_dialog(size_t open_on_tab = 0, const std::string& highlight_option = std::string());
// Dialog-free plugin actions used by the speed dial: they never require the Plugins dialog to be open.
@@ -737,6 +742,9 @@ public:
size_t get_instance_hash_int () { return m_instance_hash_int; }
ImGuiWrapper* imgui() { return m_imgui.get(); }
ShortcutRegistry& shortcuts() { return *m_shortcuts; }
// Saves the bindings and refreshes every menu label, tooltip and accelerator table that shows one.
void on_shortcuts_changed();
PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); }
+3 -7
View File
@@ -6,6 +6,7 @@
#include "GUI_Factories.hpp"
#include "GUI_ObjectList.hpp"
#include "GUI_App.hpp"
#include "Shortcuts.hpp"
#include "I18N.hpp"
#include "Plater.hpp"
#include "ObjectDataViewModel.hpp"
@@ -2083,13 +2084,8 @@ wxMenu* MenuFactory::assemble_part_menu()
void MenuFactory::append_menu_item_clone(wxMenu* menu)
{
#ifdef __APPLE__
static const wxString ctrl = ("Ctrl+");
#else
// FIXME: maybe should be using GUI::shortkey_ctrl_prefix() or equivalent?
static const wxString ctrl = _L("Ctrl+");
#endif
append_menu_item(menu, wxID_ANY, _L("Clone") + "\t" + ctrl + "K", "",
const std::string accel = wxGetApp().shortcuts().accelerator(Shortcut::CloneSelected);
append_menu_item(menu, wxID_ANY, _L("Clone") + (accel.empty() ? wxString() : "\t" + from_u8(accel)), "",
[](wxCommandEvent&) {
plater()->clone_selection();
}, "", nullptr,
+51 -80
View File
@@ -4,6 +4,7 @@
#include "GUI_Factories.hpp"
//#include "GUI_ObjectLayers.hpp"
#include "GUI_App.hpp"
#include "Shortcuts.hpp"
#include "I18N.hpp"
#include "Plater.hpp"
#include "BitmapComboBox.hpp"
@@ -245,56 +246,15 @@ ObjectList::ObjectList(wxWindow* parent) :
// Key events are not correctly processed by the wxDataViewCtrl on OSX.
// Our patched wxWidgets process the keyboard accelerators.
// On the other hand, using accelerators will break in-place editing on Windows & Linux/GTK (there is no in-place editing working on OSX for wxDataViewCtrl for now).
// Bind(wxEVT_KEY_DOWN, &ObjectList::OnChar, this);
{
// Accelerators
// wxAcceleratorEntry entries[25];
wxAcceleratorEntry entries[26];
int index = 0;
entries[index++].Set(wxACCEL_CTRL, (int)'C', wxID_COPY);
entries[index++].Set(wxACCEL_CTRL, (int)'X', wxID_CUT);
entries[index++].Set(wxACCEL_CTRL, (int)'V', wxID_PASTE);
entries[index++].Set(wxACCEL_CTRL, (int)'M', wxID_DUPLICATE);
entries[index++].Set(wxACCEL_CTRL, (int)'A', wxID_SELECTALL);
entries[index++].Set(wxACCEL_CTRL, (int)'Z', wxID_UNDO);
entries[index++].Set(wxACCEL_CTRL, (int)'Y', wxID_REDO);
entries[index++].Set(wxACCEL_NORMAL, WXK_BACK, wxID_DELETE);
//entries[index++].Set(wxACCEL_NORMAL, int('+'), wxID_ADD);
//entries[index++].Set(wxACCEL_NORMAL, WXK_NUMPAD_ADD, wxID_ADD);
//entries[index++].Set(wxACCEL_NORMAL, int('-'), wxID_REMOVE);
//entries[index++].Set(wxACCEL_NORMAL, WXK_NUMPAD_SUBTRACT, wxID_REMOVE);
//entries[index++].Set(wxACCEL_NORMAL, int('p'), wxID_PRINT);
int numbers_cnt = 0;
for (auto char_number : { '1', '2', '3', '4', '5', '6', '7', '8', '9' }) {
entries[index + numbers_cnt].Set(wxACCEL_NORMAL, int(char_number), wxID_LAST + numbers_cnt+1);
entries[index + 9 + numbers_cnt].Set(wxACCEL_NORMAL, WXK_NUMPAD0 + numbers_cnt - 1, wxID_LAST + numbers_cnt+1);
numbers_cnt++;
// index++;
}
wxAcceleratorTable accel(26, entries);
SetAcceleratorTable(accel);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->copy(); }, wxID_COPY);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->paste(); }, wxID_PASTE);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->select_item_all_children(); }, wxID_SELECTALL);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->remove(); }, wxID_DELETE);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->undo(); }, wxID_UNDO);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->redo(); }, wxID_REDO);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->cut(); }, wxID_CUT);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->clone(); }, wxID_DUPLICATE);
//this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->increase_instances(); }, wxID_ADD);
//this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->decrease_instances(); }, wxID_REMOVE);
//this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->toggle_printable_state(); }, wxID_PRINT);
for (int i = 1; i < 10; i++)
this->Bind(wxEVT_MENU, [this, i](wxCommandEvent &evt) {
if (filaments_count() > 1 && i <= filaments_count())
this->set_extruder_for_selected_items(i);
}, wxID_LAST+i);
m_accel = accel;
}
m_shortcut_id_base = wxWindow::NewControlId(int(Shortcut::Count));
for (size_t i = 0; i < size_t(Shortcut::Count); ++i)
this->Bind(wxEVT_MENU, [this, shortcut = Shortcut(i)](wxCommandEvent&) { dispatch_shortcut(shortcut); }, m_shortcut_id_base + int(i));
for (int i = 1; i < 10; i++)
this->Bind(wxEVT_MENU, [this, i](wxCommandEvent &evt) {
if (filaments_count() > 1 && i <= filaments_count())
this->set_extruder_for_selected_items(i);
}, wxID_LAST+i);
update_shortcut_accelerators();
#else //__WXOSX__
Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX
#endif
@@ -1796,36 +1756,10 @@ void ObjectList::decrease_instances()
#ifndef __WXOSX__
void ObjectList::key_event(wxKeyEvent& event)
{
//if (event.GetKeyCode() == WXK_TAB)
// Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward);
//else
if (event.GetKeyCode() == WXK_DELETE /*|| event.GetKeyCode() == WXK_BACK*/ )
remove();
//else if (event.GetKeyCode() == WXK_F5)
// wxGetApp().plater()->reload_all_from_disk();
else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL/*WXK_SHIFT*/))
select_item_all_children();
else if (wxGetKeyState(wxKeyCode('C')) && wxGetKeyState(WXK_CONTROL))
copy();
else if (wxGetKeyState(wxKeyCode('V')) && wxGetKeyState(WXK_CONTROL))
paste();
else if (wxGetKeyState(wxKeyCode('Y')) && wxGetKeyState(WXK_CONTROL))
redo();
else if (wxGetKeyState(wxKeyCode('Z')) && wxGetKeyState(WXK_CONTROL))
undo();
else if (wxGetKeyState(wxKeyCode('X')) && wxGetKeyState(WXK_CONTROL))
cut();
else if (wxGetKeyState(wxKeyCode('K')) && wxGetKeyState(WXK_CONTROL))
clone();
else if (event.GetUnicodeKey() == '+')
increase_instances();
else if (event.GetUnicodeKey() == '-')
decrease_instances();
else if (event.GetUnicodeKey() == 'p')
toggle_printable_state();
else if (event.GetUnicodeKey() == 'd')
toggle_auto_drop();
else if (filaments_count() > 1) {
const std::optional<Shortcut> shortcut = wxGetApp().shortcuts().lookup(ShortcutContext::ObjectList, KeyChord::from_event(event));
if (shortcut.has_value() && dispatch_shortcut(*shortcut))
return;
if (filaments_count() > 1) {
std::vector<wxChar> numbers = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
wxChar key_char = event.GetUnicodeKey();
if (std::find(numbers.begin(), numbers.end(), key_char) != numbers.end()) {
@@ -1842,6 +1776,43 @@ void ObjectList::key_event(wxKeyEvent& event)
}
#endif /* __WXOSX__ */
#ifdef __WXOSX__
void ObjectList::update_shortcut_accelerators()
{
std::vector<wxAcceleratorEntry> entries;
const ShortcutRegistry& shortcuts = wxGetApp().shortcuts();
for (Shortcut shortcut : shortcuts_in(ShortcutContext::ObjectList))
if (const KeyChord chord = shortcuts.binding(shortcut); chord.valid())
entries.push_back(chord.to_accelerator_entry(m_shortcut_id_base + int(shortcut)));
for (int i = 1; i < 10; ++i) {
entries.emplace_back(wxACCEL_NORMAL, '0' + i, wxID_LAST + i);
entries.emplace_back(wxACCEL_NORMAL, WXK_NUMPAD0 + i, wxID_LAST + i);
}
m_accel = wxAcceleratorTable(int(entries.size()), entries.data());
SetAcceleratorTable(m_accel);
}
#endif /* __WXOSX__ */
bool ObjectList::dispatch_shortcut(Shortcut shortcut)
{
switch (shortcut) {
case Shortcut::DeleteSelected: remove(); break;
case Shortcut::SelectAll: select_item_all_children(); break;
case Shortcut::Copy: copy(); break;
case Shortcut::Paste: paste(); break;
case Shortcut::Cut: cut(); break;
case Shortcut::Undo: undo(); break;
case Shortcut::Redo: redo(); break;
case Shortcut::CloneSelected: clone(); break;
case Shortcut::AddInstance: increase_instances(); break;
case Shortcut::RemoveInstance: decrease_instances(); break;
case Shortcut::TogglePrintable: toggle_printable_state(); break;
case Shortcut::ToggleAutoDrop: toggle_auto_drop(); break;
default: return false;
}
return true;
}
void ObjectList::OnBeginDrag(wxDataViewEvent &event)
{
const bool mult_sel = multiple_selection();
+8 -1
View File
@@ -40,6 +40,9 @@ typedef std::map<t_layer_height_range, ModelConfig> t_layer_config_ranges;
#define FIX_THROUGH_CGAL_ALWAYS 1
namespace GUI {
enum class Shortcut : uint8_t;
struct ObjectVolumeID {
ModelObject* object{ nullptr };
ModelVolume* volume{ nullptr };
@@ -270,7 +273,11 @@ public:
void extruder_editing();
#ifndef __WXOSX__
void key_event(wxKeyEvent& event);
#else
// wxDataViewCtrl never sees key events on macOS, so the bindings are installed as accelerators.
void update_shortcut_accelerators();
#endif /* __WXOSX__ */
bool dispatch_shortcut(Shortcut shortcut);
void copy();
void paste();
@@ -482,8 +489,8 @@ public:
private:
#ifdef __WXOSX__
// void OnChar(wxKeyEvent& event);
wxAcceleratorTable m_accel;
wxWindowID m_shortcut_id_base;
#endif /* __WXOSX__ */
void OnContextMenu(wxDataViewEvent &event);
void list_manipulation(const wxPoint& mouse_pos, bool evt_context_menu = false);
-24
View File
@@ -280,8 +280,6 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model)
m_canvas->enable_assemble_view_toolbar(false);
// sizer, m_canvas_widget
m_canvas_widget->Bind(wxEVT_KEY_DOWN, &Preview::update_layers_slider_from_canvas, this);
wxBoxSizer *main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0);
@@ -505,28 +503,6 @@ void Preview::update_layers_slider_mode()
m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder, can_change_color);
}
void Preview::update_layers_slider_from_canvas(wxKeyEvent &event)
{
if (event.HasModifiers()) {
event.Skip();
return;
}
const auto key = event.GetKeyCode();
IMSlider *m_layers_slider = m_canvas->get_gcode_viewer().get_layers_slider();
IMSlider *m_moves_slider = m_canvas->get_gcode_viewer().get_moves_slider();
if (key == 'L') {
if(!m_layers_slider->switch_one_layer_mode())
event.Skip();
m_canvas->set_as_dirty();
}
/*else if (key == WXK_SHIFT)
m_layers_slider->UseDefaultColors(false);*/
else
event.Skip();
}
void Preview::update_layers_slider(const std::vector<double>& layers_z, bool keep_z_range)
{
IMSlider *m_layers_slider = m_canvas->get_gcode_viewer().get_layers_slider();
-1
View File
@@ -171,7 +171,6 @@ private:
void update_layers_slider(const std::vector<double>& layers_z, bool keep_z_range = false);
void update_layers_slider_mode();
void update_layers_slider_from_canvas(wxKeyEvent &event);
//BBS: add only gcode mode
void load_print_as_fff(bool keep_z_range = false, bool only_gcode = false);
};
@@ -354,7 +354,6 @@ bool GLGizmoAdvancedCut::on_init()
if (!GLGizmoRotate3D::on_init())
return false;
m_shortcut_key = WXK_CONTROL_C;
// initiate info shortcuts
const wxString ctrl = GUI::shortkey_ctrl_prefix();
+2 -1
View File
@@ -1,6 +1,7 @@
#include "GLGizmoAssembly.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
@@ -46,7 +47,7 @@ bool GLGizmoAssembly::on_init()
{
GLGizmoMeasure::on_init();
m_shortcut_key = WXK_CONTROL_Y;
m_shortcut = Shortcut::GizmoAssembly;
return true;
}
+3 -6
View File
@@ -4,6 +4,7 @@
#include <glad/gl.h>
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_Colors.hpp"
@@ -299,7 +300,6 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D &parent, const std::string &icon_filename, u
: m_parent(parent)
, m_group_id(-1)
, m_state(Off)
, m_shortcut_key(NO_SHORTCUT_KEY_VALUE)
, m_icon_filename(icon_filename)
, m_sprite_id(sprite_id)
, m_imgui(wxGetApp().imgui())
@@ -515,11 +515,8 @@ void GLGizmoBase::render_input_window(float x, float y, float bottom_limit)
std::string GLGizmoBase::get_name(bool include_shortcut) const
{
int key = get_shortcut_key();
std::string out = on_get_name();
if (include_shortcut && key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z)
out += std::string(" [") + char(int('A') + key - int(WXK_CONTROL_A)) + "]";
return out;
const std::string name = on_get_name();
return include_shortcut && m_shortcut.has_value() ? wxGetApp().shortcuts().with_key(name, *m_shortcut) : name;
}
} // namespace GUI
+4 -5
View File
@@ -11,6 +11,7 @@
#include "slic3r/GUI/3DScene.hpp"
#include <cereal/archives/binary.hpp>
#include <optional>
#include <wx/event.h>
@@ -29,6 +30,7 @@ namespace GUI {
class ImGuiWrapper;
enum class Shortcut : uint8_t;
class GLCanvas3D;
enum class CommonGizmosDataID;
class CommonGizmosDataPool;
@@ -72,9 +74,6 @@ public:
NegZ = 1 << 5,
};
// Represents NO key(button on keyboard) value
static const int NO_SHORTCUT_KEY_VALUE = 0;
protected:
struct Grabber
{
@@ -138,7 +137,7 @@ protected:
int m_group_id; // TODO: remove only for rotate
EState m_state;
int m_shortcut_key;
std::optional<Shortcut> m_shortcut; // the registry entry that opens this gizmo
std::string m_icon_filename;
unsigned int m_sprite_id;
int m_hover_id{ -1 };
@@ -169,7 +168,7 @@ public:
EState get_state() const { return m_state; }
void set_state(EState state) { m_state = state; on_set_state(); }
int get_shortcut_key() const { return m_shortcut_key; }
std::optional<Shortcut> shortcut() const { return m_shortcut; }
const std::string& get_icon_filename() const { return m_icon_filename; }
+2 -1
View File
@@ -5,6 +5,7 @@
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/ExPolygon.hpp"
@@ -46,7 +47,7 @@ bool GLGizmoBrimEars::on_init()
{
m_new_point_head_radius = get_brim_default_radius();
m_shortcut_key = WXK_CONTROL_E;
m_shortcut = Shortcut::GizmoBrimEars;
const wxString ctrl = GUI::shortkey_ctrl_prefix();
const wxString alt = GUI::shortkey_alt_prefix();
+2 -1
View File
@@ -6,6 +6,7 @@
#include <algorithm>
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp"
#include "slic3r/GUI/format.hpp"
@@ -1310,7 +1311,7 @@ void GLGizmoCut3D::render_cut_line()
bool GLGizmoCut3D::on_init()
{
m_grabbers.emplace_back();
m_shortcut_key = WXK_CONTROL_C;
m_shortcut = Shortcut::GizmoCut;
// initiate info shortcuts
const wxString ctrl = GUI::shortkey_ctrl_prefix();
+2 -1
View File
@@ -1,6 +1,7 @@
#include "GLGizmoEmboss.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp"
#include "slic3r/GUI/MainFrame.hpp" // to update title when add text
@@ -727,7 +728,7 @@ bool GLGizmoEmboss::on_init()
m_rotate_gizmo.set_highlight_color(gray_color);
// NOTE: It has special handling in GLGizmosManager::handle_shortcut
m_shortcut_key = WXK_CONTROL_T;
m_shortcut = Shortcut::GizmoEmboss;
m_shortcuts = {
{_L("Drag"), _L("Position on surface")}
+10 -20
View File
@@ -8,6 +8,7 @@
//#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
@@ -78,7 +79,7 @@ std::string GLGizmoFdmSupports::on_get_name() const
bool GLGizmoFdmSupports::on_init()
{
// BBS
m_shortcut_key = WXK_CONTROL_L;
m_shortcut = Shortcut::GizmoFdmSupports;
m_desc["perform"] = _L("Apply");
m_desc["on_overhangs_only"] = _L("On highlighted overhangs only");
@@ -149,25 +150,14 @@ void GLGizmoFdmSupports::render_painter_gizmo()
glsafe(::glDisable(GL_BLEND));
}
// BBS
bool GLGizmoFdmSupports::on_key_down_select_tool_type(int keyCode) {
switch (keyCode)
{
case 'F':
m_current_tool = ImGui::FillButtonIcon;
break;
case 'S':
m_current_tool = ImGui::SphereButtonIcon;
break;
case 'C':
m_current_tool = ImGui::CircleButtonIcon;
break;
case 'G':
m_current_tool = ImGui::GapFillIcon;
break;
default:
return false;
break;
bool GLGizmoFdmSupports::on_tool_shortcut(Shortcut shortcut)
{
switch (shortcut) {
case Shortcut::PaintToolFill: m_current_tool = ImGui::FillButtonIcon; break;
case Shortcut::PaintToolSphere: m_current_tool = ImGui::SphereButtonIcon; break;
case Shortcut::PaintToolCircle: m_current_tool = ImGui::CircleButtonIcon; break;
case Shortcut::PaintToolGapFill: m_current_tool = ImGui::GapFillIcon; break;
default: return false;
}
return true;
}
+1 -2
View File
@@ -26,8 +26,7 @@ public:
state_ready
};
//BBS
bool on_key_down_select_tool_type(int keyCode);
bool on_tool_shortcut(Shortcut shortcut) override;
protected:
void on_render_input_window(float x, float y, float bottom_limit) override;
+2 -1
View File
@@ -1,6 +1,7 @@
#include "GLGizmoFlatten.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
@@ -54,7 +55,7 @@ void GLGizmoFlatten::data_changed(bool is_serializing)
bool GLGizmoFlatten::on_init()
{
m_shortcut_key = WXK_CONTROL_F;
m_shortcut = Shortcut::GizmoFlatten;
return true;
}
+2 -1
View File
@@ -6,6 +6,7 @@
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
@@ -31,7 +32,7 @@ std::string GLGizmoFuzzySkin::on_get_name() const
bool GLGizmoFuzzySkin::on_init()
{
m_shortcut_key = WXK_CONTROL_H;
m_shortcut = Shortcut::GizmoFuzzySkin;
const wxString ctrl = GUI::shortkey_ctrl_prefix();
const wxString alt = GUI::shortkey_alt_prefix();
-1
View File
@@ -25,7 +25,6 @@ GLGizmoHollow::GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filenam
bool GLGizmoHollow::on_init()
{
m_shortcut_key = WXK_CONTROL_H;
m_desc["enable"] = _(L("Hollow this object"));
m_desc["preview"] = _(L("Preview hollowed and drilled model"));
m_desc["offset"] = _(L("Offset")) + ": ";
+2 -1
View File
@@ -2,6 +2,7 @@
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
@@ -449,7 +450,7 @@ bool GLGizmoMeasure::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_po
bool GLGizmoMeasure::on_init()
{
m_shortcut_key = WXK_CONTROL_U;
m_shortcut = Shortcut::GizmoMeasure;
const wxString shift = GUI::shortkey_shift_prefix();
+2 -1
View File
@@ -1,5 +1,6 @@
#include "GLGizmoMeshBoolean.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "libslic3r/MeshBoolean.hpp"
@@ -104,7 +105,7 @@ bool GLGizmoMeshBoolean::on_mouse(const wxMouseEvent &mouse_event)
bool GLGizmoMeshBoolean::on_init()
{
m_shortcut_key = WXK_CONTROL_B;
m_shortcut = Shortcut::GizmoMeshBoolean;
return true;
}
@@ -2,6 +2,7 @@
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/Plater.hpp"
@@ -90,7 +91,7 @@ void GLGizmoMmuSegmentation::init_extruders_data()
bool GLGizmoMmuSegmentation::on_init()
{
// BBS
m_shortcut_key = WXK_CONTROL_N;
m_shortcut = Shortcut::GizmoMmuSegmentation;
const wxString ctrl = GUI::shortkey_ctrl_prefix();
const wxString alt = GUI::shortkey_alt_prefix();
@@ -209,30 +210,16 @@ bool GLGizmoMmuSegmentation::on_number_key_down(int number)
return true;
}
bool GLGizmoMmuSegmentation::on_key_down_select_tool_type(int keyCode) {
switch (keyCode)
{
case 'F':
m_current_tool = ImGui::FillButtonIcon;
break;
case 'T':
m_current_tool = ImGui::TriangleButtonIcon;
break;
case 'S':
m_current_tool = ImGui::SphereButtonIcon;
break;
case 'C':
m_current_tool = ImGui::CircleButtonIcon;
break;
case 'H':
m_current_tool = ImGui::HeightRangeIcon;
break;
case 'G':
m_current_tool = ImGui::GapFillIcon;
break;
default:
return false;
break;
bool GLGizmoMmuSegmentation::on_tool_shortcut(Shortcut shortcut)
{
switch (shortcut) {
case Shortcut::PaintToolFill: m_current_tool = ImGui::FillButtonIcon; break;
case Shortcut::PaintToolTriangle: m_current_tool = ImGui::TriangleButtonIcon; break;
case Shortcut::PaintToolSphere: m_current_tool = ImGui::SphereButtonIcon; break;
case Shortcut::PaintToolCircle: m_current_tool = ImGui::CircleButtonIcon; break;
case Shortcut::PaintToolHeightRange: m_current_tool = ImGui::HeightRangeIcon; break;
case Shortcut::PaintToolGapFill: m_current_tool = ImGui::GapFillIcon; break;
default: return false;
}
return true;
}
@@ -82,7 +82,7 @@ public:
// BBS
bool on_number_key_down(int number);
bool on_key_down_select_tool_type(int keyCode);
bool on_tool_shortcut(Shortcut shortcut) override;
protected:
// BBS
+2 -1
View File
@@ -1,6 +1,7 @@
#include "GLGizmoMove.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
//BBS: GUI refactor
#include "slic3r/GUI/Plater.hpp"
#include "libslic3r/AppConfig.hpp"
@@ -61,7 +62,7 @@ bool GLGizmoMove3D::on_init()
m_grabbers[0].angles = { 0.0, 0.5 * double(PI), 0.0 };
m_grabbers[1].angles = { -0.5 * double(PI), 0.0, 0.0 };
m_shortcut_key = WXK_CONTROL_M;
m_shortcut = Shortcut::GizmoMove;
return true;
}
@@ -192,6 +192,8 @@ public:
~GLGizmoPainterBase() override;
void data_changed(bool is_serializing) override;
virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
// Switches the painting tool a Painting-context shortcut names; false when this gizmo has no such tool.
virtual bool on_tool_shortcut(Shortcut shortcut) { return false; }
// Following function renders the triangles and cursor. Having this separated
// from usual on_render method allows to render them before transparent
+2 -1
View File
@@ -3,6 +3,7 @@
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/Jobs/RotoptimizeJob.hpp"
@@ -555,7 +556,7 @@ bool GLGizmoRotate3D::on_init()
for (unsigned int i = 0; i < 3; ++i)
m_gizmos[i].set_highlight_color(AXES_COLOR[i]);
m_shortcut_key = WXK_CONTROL_R;
m_shortcut = Shortcut::GizmoRotate;
return true;
}
+2 -1
View File
@@ -1,6 +1,7 @@
#include "GLGizmoScale.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
#include "slic3r/GUI/Plater.hpp"
#include <glad/gl.h>
@@ -135,7 +136,7 @@ bool GLGizmoScale3D::on_init()
// BBS
m_grabbers[4].enabled = false;
m_shortcut_key = WXK_CONTROL_S;
m_shortcut = Shortcut::GizmoScale;
return true;
}
+8 -14
View File
@@ -5,6 +5,7 @@
//#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
@@ -28,7 +29,7 @@ void GLGizmoSeam::on_shutdown()
bool GLGizmoSeam::on_init()
{
m_shortcut_key = WXK_CONTROL_P;
m_shortcut = Shortcut::GizmoSeam;
const wxString ctrl = GUI::shortkey_ctrl_prefix();
const wxString alt = GUI::shortkey_alt_prefix();
@@ -86,19 +87,12 @@ void GLGizmoSeam::render_painter_gizmo()
glsafe(::glDisable(GL_BLEND));
}
// BBS
bool GLGizmoSeam::on_key_down_select_tool_type(int keyCode) {
switch (keyCode)
{
case 'S':
m_current_tool = ImGui::SphereButtonIcon;
break;
case 'C':
m_current_tool = ImGui::CircleButtonIcon;
break;
default:
return false;
break;
bool GLGizmoSeam::on_tool_shortcut(Shortcut shortcut)
{
switch (shortcut) {
case Shortcut::PaintToolSphere: m_current_tool = ImGui::SphereButtonIcon; break;
case Shortcut::PaintToolCircle: m_current_tool = ImGui::CircleButtonIcon; break;
default: return false;
}
return true;
}
+1 -2
View File
@@ -12,8 +12,7 @@ public:
void render_painter_gizmo() override;
//BBS
bool on_key_down_select_tool_type(int keyCode);
bool on_tool_shortcut(Shortcut shortcut) override;
protected:
// BBS
@@ -34,7 +34,6 @@ GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& ic
bool GLGizmoSlaSupports::on_init()
{
m_shortcut_key = WXK_CONTROL_L;
m_desc["head_diameter"] = _L("Head diameter") + ": ";
m_desc["lock_supports"] = _L("Lock supports under new islands");
-1
View File
@@ -261,7 +261,6 @@ bool GLGizmoText::on_init()
//m_avail_font_names = init_occt_fonts();
update_font_texture();
m_scale = m_imgui->get_font_size();
m_shortcut_key = WXK_CONTROL_T;
m_grabbers.push_back(Grabber());
+27 -59
View File
@@ -4,6 +4,7 @@
#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
@@ -486,15 +487,13 @@ bool GLGizmosManager::is_running() const
return m_current != Undefined;
}
bool GLGizmosManager::handle_shortcut(int key)
bool GLGizmosManager::open_gizmo_by_shortcut(Shortcut shortcut)
{
if (!m_enabled)
return false;
auto is_key = [pressed_key = key](int gizmo_key) { return (gizmo_key == pressed_key - 64) || (gizmo_key == pressed_key - 96); };
// allowe open shortcut even when selection is empty
if (GLGizmoBase* gizmo_emboss = m_gizmos[Emboss].get();
is_key(gizmo_emboss->get_shortcut_key())) {
// The text tool opens without a selection because it creates its own object.
if (GLGizmoBase* gizmo_emboss = m_gizmos[Emboss].get(); gizmo_emboss->shortcut() == shortcut) {
dynamic_cast<GLGizmoEmboss *>(gizmo_emboss)->on_shortcut_key();
return true;
}
@@ -502,16 +501,21 @@ bool GLGizmosManager::handle_shortcut(int key)
if (m_parent.get_selection().is_empty())
return false;
auto is_gizmo = [is_key](const std::unique_ptr<GLGizmoBase> &gizmo) {
return gizmo->is_activable() && is_key(gizmo->get_shortcut_key());
};
auto it = std::find_if(m_gizmos.begin(), m_gizmos.end(), is_gizmo);
auto it = std::find_if(m_gizmos.begin(), m_gizmos.end(), [shortcut](const std::unique_ptr<GLGizmoBase> &gizmo) {
return gizmo->is_activable() && gizmo->shortcut() == shortcut;
});
if (it == m_gizmos.end())
return false;
EType gizmo_type = EType(it - m_gizmos.begin());
return open_gizmo(gizmo_type);
return open_gizmo(EType(it - m_gizmos.begin()));
}
bool GLGizmosManager::on_delete_key()
{
const bool processed = (m_current == Cut || m_current == Measure || m_current == Assembly) && gizmo_event(SLAGizmoEventType::Delete);
if (processed)
m_parent.set_as_dirty();
return processed;
}
bool GLGizmosManager::is_dragging() const
@@ -856,15 +860,6 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
}
break;
}
//skip some keys when gizmo
case 'A':
case 'a':
{
if (is_running()) {
processed = true;
}
break;
}
//case WXK_RETURN:
//{
// if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::ApplyChanges))
@@ -883,12 +878,6 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
//}
case WXK_BACK:
case WXK_DELETE: {
if ((m_current == Cut || m_current == Measure || m_current == Assembly) && gizmo_event(SLAGizmoEventType::Delete))
processed = true;
break;
}
//case 'A':
//case 'a':
//{
@@ -932,11 +921,6 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
}
}
if (!processed && !evt.HasModifiers()) {
if (handle_shortcut(keyCode))
processed = true;
}
if (processed)
m_parent.set_as_dirty();
@@ -1077,40 +1061,24 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
processed = select(digit);
}
}
else if (keyCode == 'F' || keyCode == 'T' || keyCode == 'S' || keyCode == 'C' || keyCode == 'H' || keyCode == 'G') {
processed = mmu_seg->on_key_down_select_tool_type(keyCode);
if (processed) {
// force extra frame to automatically update window size
wxGetApp().imgui()->set_requires_extra_frame();
}
}
}
}
else if (m_current == FdmSupports) {
GLGizmoFdmSupports* fdm_support = dynamic_cast<GLGizmoFdmSupports*>(get_current());
if (fdm_support != nullptr && (keyCode == 'F' || keyCode == 'S' || keyCode == 'C' || keyCode == 'G')) {
processed = fdm_support->on_key_down_select_tool_type(keyCode);
}
if (processed) {
// force extra frame to automatically update window size
wxGetApp().imgui()->set_requires_extra_frame();
}
}
else if (m_current == Seam) {
GLGizmoSeam* seam = dynamic_cast<GLGizmoSeam*>(get_current());
if (seam != nullptr && (keyCode == 'S' || keyCode == 'C')) {
processed = seam->on_key_down_select_tool_type(keyCode);
}
if (processed) {
// force extra frame to automatically update window size
wxGetApp().imgui()->set_requires_extra_frame();
}
} else if (m_current == Measure || m_current == Assembly) {
else if (m_current == Measure || m_current == Assembly) {
if (keyCode == WXK_CONTROL)
gizmo_event(SLAGizmoEventType::CtrlDown, Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.CmdDown());
else if (keyCode == WXK_SHIFT)
gizmo_event(SLAGizmoEventType::ShiftDown, Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.CmdDown());
}
if (!processed) {
if (auto painter = dynamic_cast<GLGizmoPainterBase*>(get_current()); painter != nullptr) {
const std::optional<Shortcut> shortcut = wxGetApp().shortcuts().lookup(ShortcutContext::Painting, KeyChord::from_event(evt));
processed = shortcut.has_value() && painter->on_tool_shortcut(*shortcut);
if (processed)
// force extra frame to automatically update window size
wxGetApp().imgui()->set_requires_extra_frame();
}
}
}
if (processed)
+4 -1
View File
@@ -263,7 +263,10 @@ public:
EType get_gizmo_from_name(const std::string& gizmo_name) const;
bool is_running() const;
bool handle_shortcut(int key);
// Opens the gizmo bound to a Plater-context shortcut; false when no gizmo has it or it cannot open now.
bool open_gizmo_by_shortcut(Shortcut shortcut);
// Lets the current gizmo consume the delete key; false when it did not.
bool on_delete_key();
bool is_dragging() const;
+2 -2
View File
@@ -5,6 +5,7 @@
#include "GUI_ObjectList.hpp"
#include "GLCanvas3D.hpp"
#include "MainFrame.hpp"
#include "Preferences.hpp"
#include "Tab.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Utils.hpp"
@@ -444,9 +445,8 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
// open preferences
}
else if (dict["hypertext_type"] == "preferences") {
std::string page = dict["hypertext_preferences_page"];
std::string item = dict["hypertext_preferences_item"];
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, img_url, [page, item]() { wxGetApp().open_preferences(1, page); } };// 1 is to modify
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, img_url, [item]() { wxGetApp().open_preferences(PreferencesTab::Control, item); } };
m_loaded_hints.emplace_back(hint_data);
}
else if (dict["hypertext_type"] == "plater") {
+491 -309
View File
@@ -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
+95 -28
View File
@@ -3,53 +3,120 @@
#include <wx/wx.h>
#include <map>
#include <variant>
#include <vector>
#include "GUI_Utils.hpp"
#include "Shortcuts.hpp"
#include "wxExtensions.hpp"
#include <wx/simplebook.h>
class Button;
class Label;
class TabCtrl;
namespace Slic3r {
namespace GUI {
class Select
{
public:
int m_index;
wxWindow *m_tab_button;
wxWindow *m_tab_text;
};
WX_DECLARE_HASH_MAP(int, Select *, wxIntegerHash, wxIntegerEqual, SelectHash);
// Lists every shortcut per context and lets the user rebind the assignable ones.
class KBShortcutsDialog : public DPIDialog
{
typedef std::pair<std::string, std::string> Shortcut;
typedef std::vector<Shortcut> Shortcuts;
typedef std::pair<std::pair<wxString, wxString>, Shortcuts> ShortcutsItem;
typedef std::vector<ShortcutsItem> ShortcutsVec;
// A key the user cannot rebind.
struct FixedKey
{
std::vector<wxString> keys; // modifier names and the key, shown joined with "+"
const char* description; // untranslated
};
// A mouse button whose camera action is chosen in Preferences.
struct MouseAction
{
wxString button;
const char* preference; // AppConfig key of the action
};
struct Row
{
std::variant<Shortcut, FixedKey, MouseAction> content;
ShortcutSection section;
};
struct Page
{
wxString title;
wxString caption; // when the page's keys apply
ShortcutContext context;
std::vector<Row> rows;
};
struct EditableRow
{
Shortcut shortcut;
wxStaticText* description;
wxStaticText* modifiers;
wxStaticText* key;
ScalableButton* reset;
};
struct PreferenceRow
{
const char* preference;
wxStaticText* description;
};
ShortcutsVec m_full_shortcuts;
ScalableBitmap m_logo_bmp;
wxStaticBitmap* m_header_bitmap;
std::vector<wxPanel*> m_pages;
std::vector<Page> m_pages;
std::vector<EditableRow> m_editable_rows;
std::vector<PreferenceRow> m_preference_rows;
// Row geometry, measured once and shared by every page.
wxSize m_edit_size; // an edit or reset icon
int m_buttons_width = 0; // every row's buttons column, so the right-aligned keys share an edge
int m_row_text_width = 0; // what a description and its chord share; the description wraps at the rest
int m_key_slot = 0; // width of the widest single key, the column single keys line up in
TabCtrl* m_tabs;
wxSimplebook* m_simplebook;
public:
KBShortcutsDialog();
wxWindow* create_button(int id, wxString text);
void OnSelectTabel(wxCommandEvent &event);
wxPanel *m_panel_selects;
wxBoxSizer *m_sizer_right;
wxSimplebook *m_simplebook;
wxBoxSizer * m_sizer_body;
SelectHash m_hash_selector;
KBShortcutsDialog(wxWindow* parent, ShortcutContext page); // opens on the page of that context
protected:
void on_dpi_changed(const wxRect &suggested_rect) override;
private:
void fill_shortcuts();
wxPanel* create_header(wxWindow* parent, const wxFont& bold_font);
wxPanel* create_page(wxWindow* parent, const ShortcutsItem& shortcuts, const wxFont& font, const wxFont& bold_font);
void fill_pages();
wxPanel* create_page(wxWindow* parent, const Page& page);
void edit_shortcut(Shortcut shortcut);
void reset_shortcut(Shortcut shortcut);
// Asks question before unbinding conflicts; false when the user declined.
bool take_chord_from(Shortcut shortcut, const std::vector<Shortcut>& conflicts, const wxString& question);
void apply_bindings(); // refreshes the rows and pushes the change to the rest of the app
// Puts a chord on a row's two labels, a single key in the shared column, and returns the width the chord takes.
int set_chord_labels(wxStaticText* modifiers, wxStaticText* key, std::vector<wxString> parts);
void open_mouse_preferences(const char* preference);
};
// Records one key chord for a shortcut, warning about the shortcuts it would take the chord from.
class ShortcutCaptureDialog : public DPIDialog
{
public:
ShortcutCaptureDialog(wxWindow* parent, Shortcut shortcut);
// Valid after ShowModal() returned wxID_OK; an invalid chord means "unbind".
const KeyChord& chord() const { return m_chord; }
const std::vector<Shortcut>& conflicts() const { return m_conflicts; }
protected:
void on_dpi_changed(const wxRect& suggested_rect) override;
private:
void on_key(wxKeyEvent& evt);
void on_char(wxKeyEvent& evt);
void record(const KeyChord& chord);
Shortcut m_shortcut;
KeyChord m_chord;
std::vector<Shortcut> m_conflicts;
wxStaticText* m_chord_label;
wxString m_hint; // what m_status shows while there is nothing to warn about
wxString m_rejection; // what it shows for a key a Global shortcut cannot use
Label* m_status;
wxColour m_status_colour;
Button* m_ok;
};
} // namespace GUI
+314
View File
@@ -0,0 +1,314 @@
#include "KeyChord.hpp"
#include "GUI.hpp"
#include "I18N.hpp"
#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));
}
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
+64
View File
@@ -0,0 +1,64 @@
#pragma once
#include <wx/accel.h>
#include <wx/defs.h>
#include <functional>
#include <optional>
#include <string>
#include <vector>
class wxKeyEvent;
namespace Slic3r { namespace GUI {
// One key press: a key code as wxEVT_KEY_DOWN reports it (letters upper-case, numpad keys
// folded onto their main-keyboard equivalents) plus the wxMOD_* modifiers held with it.
// Printable punctuation is stored as the character it produces, so "+" means the key that
// types "+" on the user's layout.
struct KeyChord
{
int key = WXK_NONE;
int modifiers = wxMOD_NONE;
bool valid() const { return key != WXK_NONE; }
bool operator==(const KeyChord& other) const { return key == other.key && modifiers == other.modifiers; }
bool operator!=(const KeyChord& other) const { return !(*this == other); }
// Bare printable keys other than letters and digits are matched on wxEVT_CHAR, because
// only the char event knows which character a key produces under the active layout.
bool is_punctuation() const;
// True for a printable non-alphanumeric key pressed with nothing but Shift, which only the
// char event that follows can resolve.
bool needs_char_event() const;
// 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;
// Platform-neutral text ("Ctrl+Shift+S") for persistence and wx accelerator strings.
std::string to_string() const;
static std::optional<KeyChord> parse(const std::string& text);
// Text for menus, tooltips and the shortcuts dialog, with translated modifier names and the
// command and option glyphs on macOS.
std::string display() const;
// The pieces display() joins with "+": the modifier names, then the key name.
std::vector<std::string> display_parts() const;
// Translated text of one wxMOD_* modifier, as a "Ctrl+" prefix or the bare "Ctrl" name.
static std::string modifier_prefix(int modifier);
static std::string modifier_name(int modifier);
wxAcceleratorEntry to_accelerator_entry(int command) const;
// Builds the chord a key event describes, or an invalid chord for pure modifier presses and
// keys outside the bindable set. wxEVT_CHAR events are normalized to the key codes
// wxEVT_KEY_DOWN reports for letters, digits and special keys.
static KeyChord from_event(const wxKeyEvent& evt);
};
struct KeyChordHash
{
size_t operator()(const KeyChord& chord) const { return std::hash<long long>()((static_cast<long long>(chord.modifiers) << 32) | unsigned(chord.key)); }
};
}} // namespace Slic3r::GUI
+175 -139
View File
@@ -56,6 +56,7 @@
#include "../Utils/PrintHost.hpp"
#include "GUI_App.hpp"
#include "Shortcuts.hpp"
#include "UnsavedChangesDialog.hpp"
#include "PublishSettingsDialog.hpp"
#include "MsgDialog.hpp"
@@ -310,17 +311,6 @@ static wxIcon main_frame_icon(GUI_App::EAppMode app_mode)
wxDEFINE_EVENT(EVT_SYNC_CLOUD_PRESET, SimpleEvent);
#ifdef __APPLE__
static const wxString ctrl = ("Ctrl+");
// FIXME: maybe should be using GUI::shortkey_ctrl_prefix() or equivalent?
static const std::string ctrl_t = u8"\u2318+"; // "⌘" (Mac Command)
#else
static const wxString ctrl = _L("Ctrl+");
// FIXME: maybe should be using GUI::shortkey_ctrl_prefix() or equivalent?
static const wxString ctrl_t = ctrl;
#endif
static const wxString shift = _L("Shift+");
MainFrame::MainFrame() :
DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_STYLE, "mainframe")
, m_printhost_queue_dlg(new PrintHostQueueDialog(this))
@@ -730,69 +720,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
}
return;}
#endif
// Orca: open the speed dial from any page with a bare Space. Only when no modifier is held (so
// editing shortcuts like Ctrl+Shift+Space in the canvas still reach it) and the focused window
// doesn't use Space to activate itself (buttons, checkboxes, list/choice controls, text fields),
// so a bare Space there still clicks/toggles instead of being hijacked. Gated by a preference
// (default on) so users can hand Space back to the focused control entirely.
if (wxGetApp().app_config->get_bool("enable_speed_dial") && !evt.CmdDown() && !evt.ShiftDown() &&
!evt.AltDown() && evt.GetKeyCode() == WXK_SPACE) {
if (focus_keeps_space(wxWindow::FindFocus())) {
evt.Skip(); // let the focused control keep Space
return;
}
// Defer out of the native key-event stack: open_speed_dial() may create a WebView and
// run script, the same window work the codebase avoids doing on native callbacks.
this->CallAfter([] { wxGetApp().open_speed_dial(); });
return;
}
if (evt.CmdDown() && evt.GetKeyCode() == 'R') { if (m_slice_enable) { wxGetApp().plater()->update(true, true); wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE)); this->m_tabpanel->SelectPageByName(TAB_ID_PREVIEW); } return; }
if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'G') {
m_plater->apply_background_progress();
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
if (m_print_enable) {
if (wxGetApp().preset_bundle->use_bbl_network() || wxGetApp().app_config->get_bool("use_printer_agents"))
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_PRINT_PLATE));
else
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SEND_GCODE));
}
if (!handle_global_shortcut(KeyChord::from_event(evt)))
evt.Skip();
return;
}
else if (evt.CmdDown() && evt.GetKeyCode() == 'G') { if (can_export_gcode()) { wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_EXPORT_SLICED_FILE)); } evt.Skip(); return; }
if (evt.CmdDown() && evt.GetKeyCode() == 'J') { m_printhost_queue_dlg->Show(); return; }
if (evt.CmdDown() && evt.GetKeyCode() == 'N') { m_plater->new_project(); return;}
if (evt.CmdDown() && evt.GetKeyCode() == 'O') { m_plater->load_project(); return;}
if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'S') { if (can_save_as()) m_plater->save_project(true); return;}
else if (evt.CmdDown() && evt.GetKeyCode() == 'S') { if (can_save()) m_plater->save_project(); return;}
if (evt.CmdDown() && evt.GetKeyCode() == 'F') {
if (m_plater && is_prepare_or_preview_tab()) {
m_plater->sidebar().can_search();
}
}
#ifdef __APPLE__
if (evt.CmdDown() && evt.GetKeyCode() == ',')
#else
if (evt.CmdDown() && evt.GetKeyCode() == 'P')
#endif
{
// Orca: Use GUI_App::open_preferences instead of direct call so windows associations are updated on exit
wxGetApp().open_preferences();
plater()->get_current_canvas3D()->force_set_focus();
return;
}
if (evt.CmdDown() && evt.GetKeyCode() == 'I' && !evt.ShiftDown()) {
if (!can_add_models()) return;
if (m_plater) { m_plater->add_file(); }
return;
}
if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'E') {
if (can_export_model()) publish_project();
return;
}
evt.Skip();
});
Bind(wxEVT_SHOW, [](wxShowEvent &evt) {
@@ -813,6 +742,96 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
bind_diff_dialog();
}
bool MainFrame::handle_global_shortcut(const KeyChord& chord)
{
const std::optional<Shortcut> shortcut = wxGetApp().shortcuts().lookup(ShortcutContext::Global, chord);
if (!shortcut.has_value())
return false;
switch (*shortcut) {
case Shortcut::SlicePlate:
if (m_slice_enable) {
wxGetApp().plater()->update(true, true);
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE));
m_tabpanel->SelectPageByName(TAB_ID_PREVIEW);
}
break;
case Shortcut::PrintPlate:
m_plater->apply_background_progress();
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
if (m_print_enable) {
if (wxGetApp().preset_bundle->use_bbl_network() || wxGetApp().app_config->get_bool("use_printer_agents"))
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_PRINT_PLATE));
else
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SEND_GCODE));
}
return false;
case Shortcut::ExportSlicedFile:
if (can_export_gcode())
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_EXPORT_SLICED_FILE));
return false;
case Shortcut::PrintHostQueue: m_printhost_queue_dlg->Show(); break;
case Shortcut::SpeedDial:
if (!wxGetApp().app_config->get_bool("enable_speed_dial") || (chord == KeyChord{ WXK_SPACE } && focus_keeps_space(wxWindow::FindFocus())))
return false;
// Deferred out of the native key-event stack: open_speed_dial() may create a WebView and run script.
CallAfter([] { wxGetApp().open_speed_dial(); });
break;
case Shortcut::NewProject: m_plater->new_project(); break;
case Shortcut::OpenProject: m_plater->load_project(); break;
case Shortcut::SaveProjectAs:
if (can_save_as())
m_plater->save_project(true);
break;
case Shortcut::SaveProject:
if (can_save())
m_plater->save_project();
break;
case Shortcut::Search:
if (m_plater && is_prepare_or_preview_tab())
m_plater->sidebar().can_search();
return false;
case Shortcut::Preferences:
// Orca: Use GUI_App::open_preferences instead of direct call so windows associations are updated on exit
wxGetApp().open_preferences();
plater()->get_current_canvas3D()->force_set_focus();
break;
case Shortcut::ImportModel:
if (can_add_models() && m_plater)
m_plater->add_file();
break;
case Shortcut::Publish3mf:
if (can_export_model())
publish_project();
break;
case Shortcut::ShowLabels:
if (m_plater && m_plater->is_view3D_shown()) {
m_plater->show_view3D_labels(!m_plater->are_view3D_labels_shown());
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
}
break;
case Shortcut::ViewDefault:
if (m_plater) {
select_view("plate");
m_plater->get_current_canvas3D()->zoom_to_bed();
}
break;
case Shortcut::ViewTop: select_view("top"); break;
case Shortcut::ViewBottom: select_view("bottom"); break;
case Shortcut::ViewFront: select_view("front"); break;
case Shortcut::ViewRear: select_view("rear"); break;
case Shortcut::ViewLeft: select_view("left"); break;
case Shortcut::ViewRight: select_view("right"); break;
case Shortcut::ViewPlate:
if (m_plater)
m_plater->get_current_canvas3D()->select_plate();
break;
default: return false;
}
return true;
}
void MainFrame::bind_diff_dialog()
{
auto get_tab = [](Preset::Type type) {
@@ -2766,13 +2785,31 @@ static const wxString sep = " - ";
static const wxString sep = "\t";
#endif
static wxMenu* generate_help_menu()
wxString MainFrame::shortcut_label(const wxString& label, Shortcut shortcut, bool accelerator)
{
const ShortcutRegistry& shortcuts = wxGetApp().shortcuts();
if (accelerator) {
const std::string accel = shortcuts.accelerator(shortcut);
if (!accel.empty())
return label + " " + from_u8(accel);
}
const std::string text = shortcuts.display(shortcut);
return text.empty() ? label : label + sep + from_u8(text);
}
void MainFrame::update_shortcut_labels()
{
for (const ShortcutMenuItem& entry : m_shortcut_menu_items)
entry.item->SetItemLabel(shortcut_label(entry.label, entry.shortcut, entry.accelerator));
}
wxMenu* MainFrame::generate_help_menu()
{
wxMenu* helpMenu = new wxMenu();
// shortcut key
append_menu_item(helpMenu, wxID_ANY, _L("Keyboard Shortcuts") + sep + "&?", _L("Show the list of keyboard shortcuts"),
[](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); });
append_shortcut_item(helpMenu, Shortcut::KeyboardShortcuts, false, _L("Keyboard Shortcuts"), _L("Show the list of keyboard shortcuts"),
[](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(ShortcutContext::Global); });
// Show Beginner's Tutorial
append_menu_item(helpMenu, wxID_ANY, _L("Setup Wizard"), _L("Setup Wizard"), [](wxCommandEvent &) {wxGetApp().ShowUserGuide();});
@@ -2854,29 +2891,28 @@ static void add_common_publish_menu_items(wxMenu* publish_menu, MainFrame* mainF
#endif
}
static void add_common_view_menu_items(wxMenu* view_menu, MainFrame* mainFrame, std::function<bool(void)> can_change_view)
void MainFrame::add_common_view_menu_items(wxMenu* view_menu, std::function<bool(void)> can_change_view)
{
// The camera control accelerators are captured by GLCanvas3D::on_char().
append_menu_item(view_menu, wxID_ANY, _L("Default View") + "\t" + ctrl + "0", _L("Default View"), [mainFrame](wxCommandEvent&) {
mainFrame->select_view("plate");
mainFrame->plater()->get_current_canvas3D()->zoom_to_bed();
append_shortcut_item(view_menu, Shortcut::ViewDefault, true, _L("Default View"), _L("Default View"), [this](wxCommandEvent&) {
select_view("plate");
plater()->get_current_canvas3D()->zoom_to_bed();
},
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
"", nullptr, [can_change_view]() { return can_change_view(); }, this);
//view_menu->AppendSeparator();
//TRN To be shown in the main menu View->Top
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Top", "Camera View") + "\t" + ctrl + "1", _L("Top View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("top"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
append_shortcut_item(view_menu, Shortcut::ViewTop, true, _L_CONTEXT("Top", "Camera View"), _L("Top View"), [this](wxCommandEvent&) { select_view("top"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, this);
//TRN To be shown in the main menu View->Bottom
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Bottom", "Camera View") + "\t" + ctrl + "2", _L("Bottom View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("bottom"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Front", "Camera View") + "\t" + ctrl + "3", _L("Front View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("front"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Rear", "Camera View") + "\t" + ctrl + "4", _L("Rear View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("rear"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Left", "Camera View") + "\t" + ctrl + "5", _L("Left View"),[mainFrame](wxCommandEvent &) {mainFrame->select_view("left"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Right", "Camera View") + "\t" + ctrl + "6", _L("Right View"),[mainFrame](wxCommandEvent &) { mainFrame->select_view("right"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
append_shortcut_item(view_menu, Shortcut::ViewBottom, true, _L_CONTEXT("Bottom", "Camera View"), _L("Bottom View"), [this](wxCommandEvent&) { select_view("bottom"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, this);
append_shortcut_item(view_menu, Shortcut::ViewFront, true, _L_CONTEXT("Front", "Camera View"), _L("Front View"), [this](wxCommandEvent&) { select_view("front"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, this);
append_shortcut_item(view_menu, Shortcut::ViewRear, true, _L_CONTEXT("Rear", "Camera View"), _L("Rear View"), [this](wxCommandEvent&) { select_view("rear"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, this);
append_shortcut_item(view_menu, Shortcut::ViewLeft, true, _L_CONTEXT("Left", "Camera View"), _L("Left View"), [this](wxCommandEvent &) { select_view("left"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, this);
append_shortcut_item(view_menu, Shortcut::ViewRight, true, _L_CONTEXT("Right", "Camera View"), _L("Right View"), [this](wxCommandEvent &) { select_view("right"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, this);
}
void MainFrame::init_menubar_as_editor()
@@ -2895,17 +2931,17 @@ void MainFrame::init_menubar_as_editor()
[this] { return m_plater != nullptr && wxGetApp().app_config->get("app", "single_instance") == "false"; }, this);
#endif
// New Project
append_menu_item(fileMenu, wxID_ANY, _L("New Project") + "\t" + ctrl + "N", _L("Start a new project"),
append_shortcut_item(fileMenu, Shortcut::NewProject, true, _L("New Project"), _L("Start a new project"),
[this](wxCommandEvent&) { if (m_plater) m_plater->new_project(); }, "", nullptr,
[this](){return can_start_new_project(); }, this);
// Open Project
#ifndef __APPLE__
append_menu_item(fileMenu, wxID_ANY, _L("Open Project") + dots + "\t" + ctrl + "O", _L("Open a project file"),
append_shortcut_item(fileMenu, Shortcut::OpenProject, true, _L("Open Project") + dots, _L("Open a project file"),
[this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, "menu_open", nullptr,
[this](){return can_open_project(); }, this);
#else
append_menu_item(fileMenu, wxID_ANY, _L("Open Project") + dots + "\t" + ctrl + "O", _L("Open a project file"),
append_shortcut_item(fileMenu, Shortcut::OpenProject, true, _L("Open Project") + dots, _L("Open a project file"),
[this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, "", nullptr,
[this](){return can_open_project(); }, this);
#endif
@@ -2932,21 +2968,21 @@ void MainFrame::init_menubar_as_editor()
// BBS: close save project
#ifndef __APPLE__
append_menu_item(fileMenu, wxID_ANY, _L("Save Project") + "\t" + ctrl + "S", _L("Save current project to file"),
append_shortcut_item(fileMenu, Shortcut::SaveProject, true, _L("Save Project"), _L("Save current project to file"),
[this](wxCommandEvent&) { if (m_plater) m_plater->save_project(); }, "menu_save", nullptr,
[this](){return m_plater != nullptr && can_save(); }, this);
#else
append_menu_item(fileMenu, wxID_ANY, _L("Save Project") + "\t" + ctrl + "S", _L("Save current project to file"),
append_shortcut_item(fileMenu, Shortcut::SaveProject, true, _L("Save Project"), _L("Save current project to file"),
[this](wxCommandEvent&) { if (m_plater) m_plater->save_project(); }, "", nullptr,
[this](){return m_plater != nullptr && can_save(); }, this);
#endif
#ifndef __APPLE__
append_menu_item(fileMenu, wxID_ANY, _L("Save Project as") + dots + "\t" + ctrl + shift + "S", _L("Save current project as"),
append_shortcut_item(fileMenu, Shortcut::SaveProjectAs, true, _L("Save Project as") + dots, _L("Save current project as"),
[this](wxCommandEvent&) { if (m_plater) m_plater->save_project(true); }, "menu_save", nullptr,
[this](){return m_plater != nullptr && can_save_as(); }, this);
#else
append_menu_item(fileMenu, wxID_ANY, _L("Save Project as") + dots + "\t" + ctrl + shift + "S", _L("Save current project as"),
append_shortcut_item(fileMenu, Shortcut::SaveProjectAs, true, _L("Save Project as") + dots, _L("Save current project as"),
[this](wxCommandEvent&) { if (m_plater) m_plater->save_project(true); }, "", nullptr,
[this](){return m_plater != nullptr && can_save_as(); }, this);
#endif
@@ -2956,11 +2992,11 @@ void MainFrame::init_menubar_as_editor()
auto publish_handler = [this](wxCommandEvent&) { publish_project(); };
#ifndef __APPLE__
append_menu_item(fileMenu, wxID_ANY, _L("Publish 3MF") + dots + "\t" + ctrl + shift + "E", _L("Export a 3MF file with the selected settings embedded"),
append_shortcut_item(fileMenu, Shortcut::Publish3mf, true, _L("Publish 3MF") + dots, _L("Export a 3MF file with the selected settings embedded"),
publish_handler, "menu_publish", nullptr,
[this](){return can_export_model(); }, this);
#else
append_menu_item(fileMenu, wxID_ANY, _L("Publish 3MF") + dots + "\t" + ctrl + shift + "E", _L("Export a 3MF file with the selected settings embedded"),
append_shortcut_item(fileMenu, Shortcut::Publish3mf, true, _L("Publish 3MF") + dots, _L("Export a 3MF file with the selected settings embedded"),
publish_handler, "", nullptr,
[this](){return can_export_model(); }, this);
#endif
@@ -2971,13 +3007,13 @@ void MainFrame::init_menubar_as_editor()
// BBS
wxMenu *import_menu = new wxMenu();
#ifndef __APPLE__
append_menu_item(import_menu, wxID_ANY, _L("Import 3MF/STL/STEP/SVG/OBJ/AMF") + dots + "\t" + ctrl + "I", _L("Load a model"),
append_shortcut_item(import_menu, Shortcut::ImportModel, true, _L("Import 3MF/STL/STEP/SVG/OBJ/AMF") + dots, _L("Load a model"),
[this](wxCommandEvent&) { if (m_plater) {
m_plater->add_file();
} }, "menu_import", nullptr,
[this](){return can_add_models(); }, this);
#else
append_menu_item(import_menu, wxID_ANY, _L("Import 3MF/STL/STEP/SVG/OBJ/AMF") + dots + "\t" + ctrl + "I", _L("Load a model"),
append_shortcut_item(import_menu, Shortcut::ImportModel, true, _L("Import 3MF/STL/STEP/SVG/OBJ/AMF") + dots, _L("Load a model"),
[this](wxCommandEvent&) { if (m_plater) { m_plater->add_model(); } }, "", nullptr,
[this](){return can_add_models(); }, this);
#endif
@@ -3009,7 +3045,7 @@ void MainFrame::init_menubar_as_editor()
[this](wxCommandEvent&) { if (m_plater) m_plater->export_core_3mf(); }, "menu_export_sliced_file", nullptr,
[this](){return can_export_model(); }, this);
// BBS export .gcode.3mf
append_menu_item(export_menu, wxID_ANY, _L("Export plate sliced file") + dots + "\t" + ctrl + "G", _L("Export current sliced file"),
append_shortcut_item(export_menu, Shortcut::ExportSlicedFile, true, _L("Export plate sliced file") + dots, _L("Export current sliced file"),
[this](wxCommandEvent&) { if (m_plater) wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_EXPORT_SLICED_FILE)); }, "menu_export_sliced_file", nullptr,
[this](){return can_export_gcode(); }, this);
@@ -3059,37 +3095,37 @@ void MainFrame::init_menubar_as_editor()
};
#ifndef __APPLE__
// BBS undo
append_menu_item(editMenu, wxID_ANY, _L("Undo") + "\t" + ctrl + "Z",
append_shortcut_item(editMenu, Shortcut::Undo, true, _L("Undo"),
_L("Undo"), [this](wxCommandEvent&) { m_plater->undo(); },
"menu_undo", nullptr, [this](){return m_plater->can_undo(); }, this);
// BBS redo
append_menu_item(editMenu, wxID_ANY, _L("Redo") + "\t" + ctrl + "Y",
append_shortcut_item(editMenu, Shortcut::Redo, true, _L("Redo"),
_L("Redo"), [this](wxCommandEvent&) { m_plater->redo(); },
"menu_redo", nullptr, [this](){return m_plater->can_redo(); }, this);
editMenu->AppendSeparator();
// BBS Cut TODO
append_menu_item(editMenu, wxID_ANY, _L("Cut") + "\t" + ctrl + "X",
append_shortcut_item(editMenu, Shortcut::Cut, true, _L("Cut"),
_L("Cut selection to clipboard"), [this](wxCommandEvent&) {m_plater->cut_selection_to_clipboard(); },
"menu_cut", nullptr, [this]() {return m_plater->can_copy_to_clipboard(); }, this);
// BBS Copy
append_menu_item(editMenu, wxID_ANY, _L("Copy") + "\t" + ctrl + "C",
append_shortcut_item(editMenu, Shortcut::Copy, true, _L("Copy"),
_L("Copy selection to clipboard"), [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); },
"menu_copy", nullptr, [this](){return m_plater->can_copy_to_clipboard(); }, this);
// BBS Paste
append_menu_item(editMenu, wxID_ANY, _L("Paste") + "\t" + ctrl + "V",
append_shortcut_item(editMenu, Shortcut::Paste, true, _L("Paste"),
_L("Paste clipboard"), [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); },
"menu_paste", nullptr, [this](){return m_plater->can_paste_from_clipboard(); }, this);
// BBS Delete selected
append_menu_item(editMenu, wxID_ANY, _L("Delete Selected") + "\t" + _L_CONTEXT("Del", "Keyboard Shortcut"),
append_shortcut_item(editMenu, Shortcut::DeleteSelected, true, _L("Delete Selected"),
_L("Deletes the current selection"),[this](wxCommandEvent&) { m_plater->remove_selected(); },
"menu_remove", nullptr, [this](){return can_delete(); }, this);
//BBS: delete all
append_menu_item(editMenu, wxID_ANY, _L("Delete All") + "\t" + ctrl + "D",
append_shortcut_item(editMenu, Shortcut::DeleteAll, true, _L("Delete All"),
_L("Deletes all objects"),[this](wxCommandEvent&) { m_plater->delete_all_objects_from_model(); },
"menu_remove", nullptr, [this](){return can_delete_all(); }, this);
editMenu->AppendSeparator();
// BBS Clone Selected
append_menu_item(editMenu, wxID_ANY, _L("Clone Selected") /*+ "\t" + ctrl + "M"*/,
append_shortcut_item(editMenu, Shortcut::CloneSelected, true, _L("Clone Selected"),
_L("Clone copies of selections"),[this](wxCommandEvent&) {
m_plater->clone_selection();
},
@@ -3103,7 +3139,7 @@ void MainFrame::init_menubar_as_editor()
editMenu->AppendSeparator();
#else
// BBS undo
append_menu_item(editMenu, wxID_ANY, _L("Undo") + sep + ctrl_t + "Z",
append_shortcut_item(editMenu, Shortcut::Undo, false, _L("Undo"),
_L("Undo"), [this, handle_key_event](wxCommandEvent&) {
wxKeyEvent e;
e.SetEventType(wxEVT_KEY_DOWN);
@@ -3115,7 +3151,7 @@ void MainFrame::init_menubar_as_editor()
m_plater->undo(); },
"", nullptr, [this](){return m_plater->can_undo(); }, this);
// BBS redo
append_menu_item(editMenu, wxID_ANY, _L("Redo") + sep + ctrl_t + "Y",
append_shortcut_item(editMenu, Shortcut::Redo, false, _L("Redo"),
_L("Redo"), [this, handle_key_event](wxCommandEvent&) {
wxKeyEvent e;
e.SetEventType(wxEVT_KEY_DOWN);
@@ -3128,7 +3164,7 @@ void MainFrame::init_menubar_as_editor()
"", nullptr, [this](){return m_plater->can_redo(); }, this);
editMenu->AppendSeparator();
// BBS Cut TODO
append_menu_item(editMenu, wxID_ANY, _L("Cut") + sep + ctrl_t + "X",
append_shortcut_item(editMenu, Shortcut::Cut, false, _L("Cut"),
_L("Cut selection to clipboard"), [this, handle_key_event](wxCommandEvent&) {
wxKeyEvent e;
e.SetEventType(wxEVT_KEY_DOWN);
@@ -3140,7 +3176,7 @@ void MainFrame::init_menubar_as_editor()
m_plater->cut_selection_to_clipboard(); },
"", nullptr, [this]() {return m_plater->can_copy_to_clipboard(); }, this);
// BBS Copy
append_menu_item(editMenu, wxID_ANY, _L("Copy") + sep + ctrl_t + "C",
append_shortcut_item(editMenu, Shortcut::Copy, false, _L("Copy"),
_L("Copy selection to clipboard"), [this, handle_key_event](wxCommandEvent&) {
wxKeyEvent e;
e.SetEventType(wxEVT_KEY_DOWN);
@@ -3152,7 +3188,7 @@ void MainFrame::init_menubar_as_editor()
m_plater->copy_selection_to_clipboard(); },
"", nullptr, [this](){return m_plater->can_copy_to_clipboard(); }, this);
// BBS Paste
append_menu_item(editMenu, wxID_ANY, _L("Paste") + sep + ctrl_t + "V",
append_shortcut_item(editMenu, Shortcut::Paste, false, _L("Paste"),
_L("Paste clipboard"), [this, handle_key_event](wxCommandEvent&) {
wxKeyEvent e;
e.SetEventType(wxEVT_KEY_DOWN);
@@ -3165,14 +3201,14 @@ void MainFrame::init_menubar_as_editor()
"", nullptr, [this](){return m_plater->can_paste_from_clipboard(); }, this);
#if 0
// BBS Delete selected
append_menu_item(editMenu, wxID_ANY, _L("Delete Selected") + "\t" + _L_CONTEXT("Backspace", "Keyboard Shortcut"),
append_shortcut_item(editMenu, Shortcut::DeleteSelected, true, _L("Delete Selected"),
_L("Deletes the current selection"),[this](wxCommandEvent&) {
m_plater->remove_selected();
},
"", nullptr, [this](){return can_delete(); }, this);
#endif
//BBS: delete all
append_menu_item(editMenu, wxID_ANY, _L("Delete All") + "\t" + ctrl + "D",
append_shortcut_item(editMenu, Shortcut::DeleteAll, true, _L("Delete All"),
_L("Deletes all objects"),[this, handle_key_event](wxCommandEvent&) {
wxKeyEvent e;
e.SetEventType(wxEVT_KEY_DOWN);
@@ -3185,7 +3221,7 @@ void MainFrame::init_menubar_as_editor()
"", nullptr, [this](){return can_delete_all(); }, this);
editMenu->AppendSeparator();
// BBS Clone Selected
append_menu_item(editMenu, wxID_ANY, _L("Clone Selected") + "\t" + ctrl + "K",
append_shortcut_item(editMenu, Shortcut::CloneSelected, true, _L("Clone Selected"),
_L("Clone copies of selections"),[this, handle_key_event](wxCommandEvent&) {
wxKeyEvent e;
e.SetEventType(wxEVT_KEY_DOWN);
@@ -3208,7 +3244,7 @@ void MainFrame::init_menubar_as_editor()
#endif
// BBS Select All
append_menu_item(editMenu, wxID_ANY, _L("Select All") + sep + ctrl_t + "A",
append_shortcut_item(editMenu, Shortcut::SelectAll, false, _L("Select All"),
_L("Selects all objects"), [this, handle_key_event](wxCommandEvent&) {
wxKeyEvent e;
e.SetEventType(wxEVT_KEY_DOWN);
@@ -3260,7 +3296,7 @@ void MainFrame::init_menubar_as_editor()
wxMenu* viewMenu = nullptr;
if (m_plater) {
viewMenu = new wxMenu();
add_common_view_menu_items(viewMenu, this, std::bind(&MainFrame::can_change_view, this));
add_common_view_menu_items(viewMenu, std::bind(&MainFrame::can_change_view, this));
viewMenu->AppendSeparator();
//BBS perspective view
@@ -3291,13 +3327,14 @@ void MainFrame::init_menubar_as_editor()
[]() { return wxGetApp().app_config->get_bool("auto_perspective"); }, this);
viewMenu->AppendSeparator();
append_menu_check_item(viewMenu, wxID_ANY, _L("Show &G-code Window") + sep + "C", _L("Show G-code window in Preview scene."),
wxMenuItem* gcode_window = append_menu_check_item(viewMenu, wxID_ANY, shortcut_label(_L("Show &G-code Window"), Shortcut::ToggleGcodeWindow, true), _L("Show G-code window in Preview scene."),
[this](wxCommandEvent &) {
wxGetApp().toggle_show_gcode_window();
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
},
this, [this]() { return m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW; },
[]() { return wxGetApp().show_gcode_window(); }, this);
m_shortcut_menu_items.push_back({ gcode_window, Shortcut::ToggleGcodeWindow, _L("Show &G-code Window"), true });
append_menu_check_item(
viewMenu, wxID_ANY, _L("Show 3D Navigator"), _L("Show 3D navigator in Prepare and Preview scene."),
@@ -3325,9 +3362,10 @@ void MainFrame::init_menubar_as_editor()
this);
viewMenu->AppendSeparator();
append_menu_check_item(viewMenu, wxID_ANY, _L("Show &Labels") + "\t" + ctrl + "E", _L("Show object labels in 3D scene."),
wxMenuItem* show_labels = append_menu_check_item(viewMenu, wxID_ANY, shortcut_label(_L("Show &Labels"), Shortcut::ShowLabels, true), _L("Show object labels in 3D scene."),
[this](wxCommandEvent&) { m_plater->show_view3D_labels(!m_plater->are_view3D_labels_shown()); m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT)); }, this,
[this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->are_view3D_labels_shown(); }, this);
m_shortcut_menu_items.push_back({ show_labels, Shortcut::ShowLabels, _L("Show &Labels"), true });
append_menu_check_item(viewMenu, wxID_ANY, _L("Show &Overhang"), _L("Show object overhang highlight in 3D scene."),
[this](wxCommandEvent &) {
@@ -3372,8 +3410,6 @@ void MainFrame::init_menubar_as_editor()
//auto preference_item = new wxMenuItem(parent_menu, OrcaSlicerMenuPreferences + bambu_studio_id_base, _L("Preferences") + "\t" + ctrl + ",", "");
#else
wxMenu* parent_menu = m_topbar->GetTopMenu();
auto preference_item = new wxMenuItem(parent_menu, ConfigMenuPreferences + config_id_base, _L("Preferences") + "\t" + ctrl + "P", "");
#endif
#ifdef __APPLE__
@@ -3382,15 +3418,15 @@ void MainFrame::init_menubar_as_editor()
parent_menu, wxID_ANY, _L(about_title), "",
[](wxCommandEvent &) { Slic3r::GUI::about();},
"", nullptr, []() { return true; }, this, 0);
append_menu_item(
parent_menu, wxID_ANY, _L("Preferences") + "\t" + ctrl + ",", "",
append_shortcut_item(
parent_menu, Shortcut::Preferences, true, _L("Preferences"), "",
[](wxCommandEvent &) {
wxGetApp().open_preferences();
},
"", nullptr, []() { return true; }, this, 1);
parent_menu->AppendSeparator();
append_menu_item(
parent_menu, wxID_ANY, _L("Open speed dial...") + sep + "Space", "",
append_shortcut_item(
parent_menu, Shortcut::SpeedDial, false, _L("Open speed dial..."), "",
[](wxCommandEvent &) { wxGetApp().open_speed_dial(); },
"", nullptr, []() { return true; }, this);
//parent_menu->Insert(1, preference_item);
@@ -3406,8 +3442,8 @@ void MainFrame::init_menubar_as_editor()
m_topbar->AddDropDownSubMenu(viewMenu, _L("View"));
//BBS add Preference
append_menu_item(
m_topbar->GetTopMenu(), wxID_ANY, _L("Preferences") + "\t" + ctrl + "P", "",
append_shortcut_item(
m_topbar->GetTopMenu(), Shortcut::Preferences, true, _L("Preferences"), "",
[](wxCommandEvent &) {
// Orca: Use GUI_App::open_preferences instead of direct call so windows associations are updated on exit
wxGetApp().open_preferences();
@@ -3417,8 +3453,8 @@ void MainFrame::init_menubar_as_editor()
auto top_menu = m_topbar->GetTopMenu();
top_menu->AppendSeparator();
append_menu_item(
top_menu, wxID_ANY, _L("Open speed dial...") + "\t" + "Space", "",
append_shortcut_item(
top_menu, Shortcut::SpeedDial, false, _L("Open speed dial..."), "",
[](wxCommandEvent &) { wxGetApp().open_speed_dial(); },
"", nullptr, []() { return true; }, this);
top_menu->AppendSeparator();
@@ -3524,8 +3560,8 @@ void MainFrame::init_menubar_as_editor()
#else
// On Mac, the Apple menu ignores non-standard custom items, so add Preset Bundle to the File menu
fileMenu->AppendSeparator();
append_menu_item(
fileMenu, wxID_ANY, _L("Open speed dial...") + sep + "Space", "",
append_shortcut_item(
fileMenu, Shortcut::SpeedDial, false, _L("Open speed dial..."), "",
[](wxCommandEvent&) { wxGetApp().open_speed_dial(); },
"", nullptr, []() { return true; }, this);
append_menu_item(
@@ -3728,7 +3764,7 @@ void MainFrame::init_menubar_as_gcodeviewer()
wxMenu* viewMenu = nullptr;
if (m_plater != nullptr) {
viewMenu = new wxMenu();
add_common_view_menu_items(viewMenu, this, std::bind(&MainFrame::can_change_view, this));
add_common_view_menu_items(viewMenu, std::bind(&MainFrame::can_change_view, this));
}
// helpmenu
+26
View File
@@ -74,6 +74,8 @@ class DesignPanel;
class MainFrame;
class WebViewPanel;
class ParamsDialog;
enum class Shortcut : uint8_t;
struct KeyChord;
#ifdef __WXGTK__
class ResizeEdgePanel;
#endif
@@ -195,6 +197,29 @@ class MainFrame : public DPIFrame
// vector of a MenuBar items changeable in respect to printer technology
std::vector<wxMenuItem*> m_changeable_menu_items;
// Menu items whose label shows a key binding; update_shortcut_labels() rewrites them.
struct ShortcutMenuItem
{
wxMenuItem* item;
Shortcut shortcut;
wxString label;
bool accelerator; // false keeps the binding display-only on macOS, where the menu bar's accelerators are live
};
std::vector<ShortcutMenuItem> m_shortcut_menu_items;
wxString shortcut_label(const wxString& label, Shortcut shortcut, bool accelerator);
template<typename... Args>
wxMenuItem* append_shortcut_item(wxMenu* menu, Shortcut shortcut, bool accelerator, const wxString& label, Args&&... args)
{
wxMenuItem* item = append_menu_item(menu, wxID_ANY, shortcut_label(label, shortcut, accelerator), std::forward<Args>(args)...);
m_shortcut_menu_items.push_back({ item, shortcut, label, accelerator });
return item;
}
// Runs the Global shortcut bound to chord; false when the focused control should see the key as well.
bool handle_global_shortcut(const KeyChord& chord);
void add_common_view_menu_items(wxMenu* view_menu, std::function<bool(void)> can_change_view);
wxMenu* generate_help_menu();
struct FileHistory : wxFileHistory
{
FileHistory(int max) : wxFileHistory(max) {}
@@ -354,6 +379,7 @@ public:
void request_select_tab(const wxString& id);
int get_calibration_curr_tab();
void select_view(const std::string& direction);
void update_shortcut_labels();
// Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig
void on_config_changed(DynamicPrintConfig* cfg) const ;
void set_print_button_to_default(PrintSelectType select_type);
+2 -1
View File
@@ -10,6 +10,7 @@
#include "GUI_Factories.hpp"
#include "GUI_ObjectList.hpp"
#include "I18N.hpp"
#include "Shortcuts.hpp"
#include "IMSlider.hpp"
#include "MainFrame.hpp"
#include "NetworkTestDialog.hpp"
@@ -563,7 +564,7 @@ std::vector<NativeCommand> build_command_catalog()
// ---- Help ---- (mirrors the top-bar Help menu, plus the wiki/YouTube links)
add("help_keyboard_shortcuts", _u8L("Keyboard Shortcuts"), _u8L("Help"), [](const std::string&) {
wxGetApp().keyboard_shortcuts();
wxGetApp().keyboard_shortcuts(ShortcutContext::Global);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("help_setup_wizard", _u8L("Setup Wizard"), _u8L("Help"), [](const std::string&) {
+6 -6
View File
@@ -82,6 +82,7 @@
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "Shortcuts.hpp"
#include "GUI_ObjectList.hpp"
#ifdef __WXGTK__
#include "LinuxDisplayBackend.hpp"
@@ -7595,7 +7596,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLCANVAS_PRINTABLE, [this](SimpleEvent& evt) { this->sidebar->obj_list()->toggle_printable_state(); });
view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); });
view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) {
wxGetApp().keyboard_shortcuts(view3D->get_canvas3d()->get_gizmos_manager().is_paint_gizmo() ? ShortcutContext::Painting : ShortcutContext::Plater);
});
view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int>& evt)
{ if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); });
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); });
@@ -7672,7 +7675,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [q](SimpleEvent&) { q->set_bed_shape(); });
// Preview events:
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(ShortcutContext::Preview); });
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [q](SimpleEvent&) { q->set_bed_shape(); });
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE, [this](SimpleEvent &) {
preview->get_canvas3d()->set_as_dirty();
@@ -8133,10 +8136,7 @@ void Plater::priv::collapse_sidebar(bool collapse)
sidebar_layout.is_collapsed = collapse;
// Now update the tooltip in the toolbar.
std::string new_tooltip = collapse
? _u8L("Expand sidebar")
: _u8L("Collapse sidebar");
new_tooltip += " [" + _u8L("Shift+") + _u8L("Tab") + "]";
const std::string new_tooltip = wxGetApp().shortcuts().with_key(collapse ? _u8L("Expand sidebar") : _u8L("Collapse sidebar"), Shortcut::CollapseSidebar);
int id = collapse_toolbar.get_item_id("collapse_sidebar");
collapse_toolbar.set_tooltip(id, new_tooltip);
+29 -6
View File
@@ -18,6 +18,7 @@
#include "NetworkTestDialog.hpp"
#include "Widgets/StaticLine.hpp"
#include "Widgets/RadioGroup.hpp"
#include "Shortcuts.hpp"
#include "slic3r/Utils/bambu_networking.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "NetworkPluginDialog.hpp"
@@ -285,6 +286,7 @@ std::tuple<wxBoxSizer*, ComboBox*> PreferencesDialog::create_item_combobox_base(
auto combobox = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_LARGE_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY);
combobox->GetDropDown().SetUseContentWidth(true);
combobox->SetToolTip(tip);
combobox->SetName(param); // select_tab() finds the row by this name
std::vector<wxString>::iterator iter;
for (iter = vlist.begin(); iter != vlist.end(); iter++) {
@@ -1522,6 +1524,19 @@ PreferencesDialog::~PreferencesDialog()
{
}
void PreferencesDialog::select_tab(PreferencesTab tab, const std::string& option)
{
if (const auto index = m_tab_index.find(tab); index != m_tab_index.end())
m_pref_tabs->SelectItem(index->second);
wxWindow* control = option.empty() ? nullptr : m_parent->FindWindow(wxString(option));
if (control == nullptr)
return;
int unit = 1;
m_parent->GetScrollPixelsPerUnit(nullptr, &unit);
m_parent->Scroll(wxDefaultCoord, (m_parent->CalcUnscrolledPosition(control->GetPosition()).y - FromDIP(10)) / unit);
control->SetFocus(); // the focused tint marks the row
}
void PreferencesDialog::on_dpi_changed(const wxRect &suggested_rect) {
m_pref_tabs->Rescale();
@@ -1599,7 +1614,7 @@ void PreferencesDialog::create_items()
//////////////////////////
//// GENERAL TAB
/////////////////////////////////////
m_pref_tabs->AppendItem(_L("General"));
m_tab_index[PreferencesTab::General] = m_pref_tabs->AppendItem(_L("General"));
f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0));
g_sizer = f_sizers.back();
g_sizer->AddGrowableCol(0, 1);
@@ -1730,8 +1745,8 @@ void PreferencesDialog::create_items()
auto item_multi_machine = create_item_checkbox(_L("Multi device management"), _L("With this option enabled, you can send a task to multiple devices at the same time and manage multiple devices."), "enable_multi_machine", _L("(Requires restart)"));
g_sizer->Add(item_multi_machine);
auto item_speed_dial = create_item_checkbox(_L("Open the Speed Dial with the Space key"),
_L("When enabled, pressing Space (with no other key held) opens the Speed Dial action search from any page."),
auto item_speed_dial = create_item_checkbox(_L("Open the Speed Dial from the keyboard"),
_L("When enabled, the Speed Dial keyboard shortcut (Space by default) opens the action search from any page."),
"enable_speed_dial");
g_sizer->Add(item_speed_dial);
@@ -1789,7 +1804,7 @@ void PreferencesDialog::create_items()
//////////////////////////
//// CONTROL TAB
/////////////////////////////////////
m_pref_tabs->AppendItem(_L("Control"));
m_tab_index[PreferencesTab::Control] = m_pref_tabs->AppendItem(_L("Control"));
f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0));
g_sizer = f_sizers.back();
g_sizer->AddGrowableCol(0, 1);
@@ -1859,6 +1874,14 @@ void PreferencesDialog::create_items()
auto item_right_mouse_drag = create_item_combobox(_L("Right Mouse Drag"), _L("Set the action that dragging the right mouse button should perform."), "right_mouse_drag_action", ButtonDragActions);
g_sizer->Add(item_right_mouse_drag);
//// CONTROL > Keyboard
g_sizer->Add(create_item_title(_L("Keyboard")), 1, wxEXPAND);
auto item_shortcuts = create_item_button(_L("Keyboard shortcuts"), _L("Edit") + dots, "", _L("Choose the key for each action."), [this]() {
wxGetApp().keyboard_shortcuts(ShortcutContext::Global, this);
});
g_sizer->Add(item_shortcuts);
//// CONTROL > Clear my choice on ...
g_sizer->Add(create_item_title(_L("Clear my choice on...")), 1, wxEXPAND);
@@ -1883,7 +1906,7 @@ void PreferencesDialog::create_items()
//////////////////////////
//// GRAPHICS TAB
/////////////////////////////////////
m_pref_tabs->AppendItem(_L("Graphics"));
m_tab_index[PreferencesTab::Graphics] = m_pref_tabs->AppendItem(_L("Graphics"));
f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0));
g_sizer = f_sizers.back();
g_sizer->AddGrowableCol(0, 1);
@@ -2031,7 +2054,7 @@ void PreferencesDialog::create_items()
//////////////////////////
//// ONLINE TAB
/////////////////////////////////////
m_pref_tabs->AppendItem(_L("Online"));
m_tab_index[PreferencesTab::Online] = m_pref_tabs->AppendItem(_L("Online"));
f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0));
g_sizer = f_sizers.back();
g_sizer->AddGrowableCol(0, 1);
+6
View File
@@ -29,6 +29,9 @@ namespace Slic3r { namespace GUI {
#define DESIGN_INPUT_SIZE wxSize(FromDIP(120), -1)
#define DESIGN_LEFT_MARGIN 25
// The tabs other dialogs open Preferences on.
enum class PreferencesTab { General, Control, Graphics, Online };
class PreferencesDialog : public DPIDialog
{
private:
@@ -38,6 +41,7 @@ protected:
wxBoxSizer * m_sizer_body;
wxScrolledWindow* m_parent;
TabCtrl* m_pref_tabs;
std::map<PreferencesTab, int> m_tab_index; // position of each tab in m_pref_tabs
// bool m_settings_layout_changed {false};
bool m_seq_top_layer_only_changed{false};
@@ -60,6 +64,8 @@ public:
~PreferencesDialog();
void select_tab(PreferencesTab tab, const std::string& option = {}); // and scrolls a combobox option's row into view, focused
wxString m_backup_interval_time;
wxTimer m_filament_height_timer;
+377
View File
@@ -0,0 +1,377 @@
#include "Shortcuts.hpp"
#include "I18N.hpp"
#include "libslic3r/AppConfig.hpp"
#include <algorithm>
#include <map>
#include <utility>
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' }),
// 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' }),
// 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(SpeedDial, "speed_dial", L("Open the speed dial"), GLOBAL, { WXK_SPACE }),
SHORTCUT(ReloadDevicePage, "reload_device_page", L("Reload the device page"), CANVAS, { WXK_F5 }),
SHORTCUT(KeyboardShortcuts, "keyboard_shortcuts", L("Show keyboard shortcuts list"), CANVAS, { '?' }),
}};
#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") },
}};
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
+127
View File
@@ -0,0 +1,127 @@
#pragma once
#include "KeyChord.hpp"
#include <array>
#include <cstdint>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
namespace Slic3r {
class AppConfig;
namespace GUI {
// Where a key press is looked up. A shortcut may belong to several contexts; Global ones are
// dispatched by the main frame before any child window sees the key.
enum class ShortcutContext : uint8_t { Global, Plater, Preview, ObjectList, Painting, Count };
constexpr uint8_t context_bit(ShortcutContext context) { return uint8_t(1u << unsigned(context)); }
enum class Shortcut : uint8_t {
// Project
NewProject, OpenProject, SaveProject, SaveProjectAs, ImportModel, Publish3mf,
// Slicing and printing
SlicePlate, ExportSlicedFile, PrintPlate, PrintHostQueue,
// Selection
SelectAll, SelectAllPlates,
// Editing
Undo, Redo, Cut, Copy, Paste, DeleteSelected, DeleteAll, CloneSelected,
// Objects
AddInstance, RemoveInstance, TogglePrintable, ToggleAutoDrop,
// Placement
Arrange, ArrangePlate, Orient, OrientPlate,
MoveSelectionLeft, MoveSelectionRight, MoveSelectionUp, MoveSelectionDown, RotateSelectionLeft, RotateSelectionRight,
// Gizmos
GizmoMove, GizmoRotate, GizmoScale, GizmoFlatten, GizmoCut, GizmoMeshBoolean, GizmoFdmSupports, GizmoSeam, GizmoFuzzySkin,
GizmoMmuSegmentation, GizmoEmboss, GizmoMeasure, GizmoAssembly, GizmoBrimEars,
// Sliders
GoToLayer, LayerSliderUp, LayerSliderDown, MovesSliderLeft, MovesSliderRight, MovesSliderStart, MovesSliderEnd,
// Painting tools
PaintToolCircle, PaintToolSphere, PaintToolFill, PaintToolGapFill, PaintToolTriangle, PaintToolHeightRange,
// Camera
ViewDefault, ViewTop, ViewBottom, ViewFront, ViewRear, ViewLeft, ViewRight, ViewPlate, ZoomIn, ZoomOut, Mouse3DSettings,
// Display
ShowLabels, ShowWireframe, ToggleGcodeWindow, ToggleOneLayerMode,
// Application
Preferences, Search, SwitchView, CollapseSidebar, SpeedDial, ReloadDevicePage, KeyboardShortcuts,
Count
};
struct ShortcutInfo
{
Shortcut id;
const char* key; // AppConfig key
const char* name; // untranslated description
uint8_t contexts; // context_bit() mask
KeyChord default_chord;
bool repeatable; // runs on key auto-repeat as well
// Shift or Ctrl held with the bound key select a variant of this action, such as the finer
// move step or the faster slider step.
bool modifier_variants;
};
// Headings of the shortcuts dialog, in listing order.
enum class ShortcutSection : uint8_t {
Project, SlicingAndPrinting, Selection, Editing, Objects, Placement, Gizmos, Sliders, PaintingTools, Camera, Display, Application,
Count
};
const ShortcutInfo& shortcut_info(Shortcut shortcut);
ShortcutSection shortcut_section(Shortcut shortcut);
const char* section_name(ShortcutSection section); // untranslated heading
std::vector<Shortcut> shortcuts_in(ShortcutContext context); // in table order
// Effective key bindings: the built-in defaults overlaid with the user's own. Owns the
// chord -> shortcut index every dispatcher queries.
class ShortcutRegistry
{
public:
ShortcutRegistry();
KeyChord binding(Shortcut shortcut) const; // invalid when unbound
bool is_customized(Shortcut shortcut) const;
std::string display(Shortcut shortcut) const; // "Ctrl+N" for tooltips and the shortcuts dialog
std::string with_key(const std::string& text, Shortcut shortcut) const; // "text [Ctrl+N]", or text alone when unbound
std::string accelerator(Shortcut shortcut) const; // wx accelerator text for menu labels; empty when unbound or not menu-safe
// Matches a key event in one context. Global shortcuts are only found through the Global context.
std::optional<Shortcut> lookup(ShortcutContext context, const KeyChord& chord) const;
// lookup(), then the modifier_variants shortcuts, which also match with Shift or Ctrl added
// to their binding; step_modifiers holds whichever of the two were added.
struct Match
{
Shortcut shortcut;
int step_modifiers;
};
std::optional<Match> match(ShortcutContext context, const KeyChord& chord) const;
// Shortcuts other than shortcut bound to chord in a context it shares; Global shortcuts
// share every context.
std::vector<Shortcut> conflicts(Shortcut shortcut, const KeyChord& chord) const;
// The modifier_variants shortcut in a shared context whose Shift or Ctrl step is chord;
// such a chord is reserved for that step.
std::optional<Shortcut> step_owner(Shortcut shortcut, const KeyChord& chord) const;
void bind(Shortcut shortcut, const KeyChord& chord); // an invalid chord unbinds
void reset(Shortcut shortcut);
void reset_all();
void load(const AppConfig& config);
void save(AppConfig& config) const;
private:
void rebuild_index();
std::array<std::optional<KeyChord>, size_t(Shortcut::Count)> m_overrides;
std::unordered_map<KeyChord, std::vector<Shortcut>, KeyChordHash> m_index;
};
} // namespace GUI
} // namespace Slic3r