diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 0c808063d6..72496b01a7 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1,75 +1,231 @@ #include "Preferences.hpp" -#include "OptionsGroup.hpp" #include "GUI_App.hpp" #include "MainFrame.hpp" +#include "OpenGLManager.hpp" #include "Plater.hpp" #include "MsgDialog.hpp" #include "I18N.hpp" #include "UxProgramTermsDialog.hpp" #include "libslic3r/AppConfig.hpp" -#include -#include "Notebook.hpp" #include "ReleaseNote.hpp" -#include "OG_CustomCtrl.hpp" +#include "Tabbook.hpp" #include "fila_manager/wgtFilaManagerFeature.h" -#include "wx/graphics.h" - -#include -#include #include "Gizmos/GLGizmoBase.hpp" -#include "OpenGLManager.hpp" +#include "Widgets/Button.hpp" +#include "Widgets/ComboBox.hpp" +#include "Widgets/CheckBox.hpp" +#include "Widgets/LinkLabel.hpp" +#include "Widgets/RadioBox.hpp" +#include "Widgets/StaticLine.hpp" +#include "Widgets/TextInput.hpp" +#include + #ifdef __WINDOWS__ #ifdef _MSW_DARK_MODE #include "dark_mode.hpp" #endif // _MSW_DARK_MODE #endif //__WINDOWS__ +#define DESIGN_GRAY900_COLOR wxColour(38, 46, 48) +#define DESIGN_GRAY800_COLOR wxColour(50, 58, 61) +#define DESIGN_GRAY600_COLOR wxColour(144, 144, 144) +#define DESIGN_GRAY400_COLOR wxColour(166, 169, 170) +#define DESIGN_CONTENT_BG_COLOR *wxWHITE +#define DESIGN_SIDEBAR_BG_COLOR wxColour(241, 241, 241) +#define DESIGN_HIGHLIGHT_COLOR wxColour(0, 174, 66) // BBL green + +#define DESIGN_PAGE_PADDING FromDIP(12) // padding around page content +#define DESIGN_ITEM_SPACING FromDIP(3) // vertical spacing between items +#define DESIGN_SECTION_SPACING DESIGN_PAGE_PADDING // vertical spacing between titled sections on a page +#define DESIGN_ITEM_INDENT FromDIP(23) // left side offset for all editable properties, indent relative to section titles +#define DESIGN_INPUT_PAD FromDIP(8) // horizontal space between label(s) and input widget + +#define DESIGN_LABEL_W 100 // common width of labels on left side of input (forms an even column) +#define DESIGN_COMBO_W 140 +#define DESIGN_COMBO_LRG_W 160 +#define DESIGN_INPUT_W 100 // default text box width +#define DESIGN_BUTTON_RADIUS 12 +#define DESIGN_BUTTON_SIZE wxSize(58, 22) + namespace Slic3r { namespace GUI { WX_DEFINE_LIST(RadioSelectorList); -wxDEFINE_EVENT(EVT_PREFERENCES_SELECT_TAB, wxCommandEvent); -#define PreferenceBtnSize wxSize(FromDIP(58), FromDIP(22)) -class MyscrolledWindow : public wxScrolledWindow { +// Scrollable panel container for property pages. +class ScrolledPanel : public wxScrolledWindow +{ + bool m_has_top_title { false }; + wxBoxSizer *m_content_sizer; public: - MyscrolledWindow(wxWindow* parent, - wxWindowID id = wxID_ANY, - const wxPoint& pos = wxDefaultPosition, - const wxSize& size = wxDefaultSize, - long style = wxVSCROLL) : wxScrolledWindow(parent, id, pos, size, style) {} + ScrolledPanel(wxWindow* parent, bool top_level = true) : + m_content_sizer{ new wxBoxSizer(wxVERTICAL) }, + wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER | wxTAB_TRAVERSAL) + { + if (top_level) + SetScrollRate(FromDIP(15), FromDIP(15)); + SetBackgroundColour(DESIGN_CONTENT_BG_COLOR); + SetSizer(new wxBoxSizer(wxVERTICAL)); + GetSizer()->Add(m_content_sizer, 0, wxEXPAND | wxALL, top_level ? DESIGN_PAGE_PADDING : 0); + } + + wxSizerItem* Add(wxSizerItem *item) + { + if (item->GetBorder() == wxDefaultCoord) + item->SetBorder(DESIGN_ITEM_SPACING); + if (!item->GetFlag()) + item->SetFlag(wxTOP | wxEXPAND); + return m_content_sizer->Add(item); + } + + wxSizerItem* Add(wxWindow *window, int padding = wxDefaultCoord, int flag = 0) { + return Add(new wxSizerItem(window, 0, flag, padding)); + } + + wxSizerItem* Add(wxSizer *sizer, int padding = wxDefaultCoord, int flag = 0) { + return Add(new wxSizerItem(sizer, 0, flag, padding)); + } + + wxSizerItem* AddTitle(const wxString &title, const wxString &tooltip = wxEmptyString, int padding = wxDefaultCoord, int flag = 0) + { + auto line = new StaticLine(this, false, title); + line->SetFont(::Label::Head_13); + line->SetToolTip(tooltip); + + wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(line, 1, wxEXPAND | wxBOTTOM, FromDIP(3)); + + if (padding == wxDefaultCoord) + padding = m_has_top_title ? DESIGN_SECTION_SPACING : 0; + m_has_top_title = true; + + return Add(sizer, padding, flag); + } + + wxSizerItem* AddSubtitle(const wxString &title, const wxString &tooltip = wxEmptyString) + { + auto text = new wxStaticText(this, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, 0); + text->SetForegroundColour(DESIGN_GRAY800_COLOR); + text->SetFont(::Label::Head_11); + text->SetToolTip(tooltip); + return Add(text, FromDIP(5), wxTOP | wxLEFT | wxEXPAND); + } + + ScrolledPanel* Finalize() + { + GetSizer()->SetSizeHints(this); + return this; + } - bool ShouldScrollToChildOnFocus(wxWindow* child) override { return false; } + bool ShouldScrollToChildOnFocus(wxWindow* /* child */) override { return false; } }; -wxBoxSizer *PreferencesDialog::create_item_title(wxString title, wxWindow *parent, wxString tooltip) +// ---------------------------------------------------------------------------- +// PreferencesDialog +// ---------------------------------------------------------------------------- + +// static +int PreferencesDialog::m_last_selected_page = 0; + + +// Static utility functions + +static wxStaticText* new_static_text(wxWindow *parent, const wxString &text, const wxString &tooltip = wxEmptyString, int width = DESIGN_LABEL_W, int style = 0) +{ + if (!width) + width = DESIGN_LABEL_W; + auto ctrl = new wxStaticText(parent, wxID_ANY, text, wxDefaultPosition, wxSize(parent->FromDIP(width), -1), style | wxBORDER_NONE); + ctrl->SetForegroundColour(DESIGN_GRAY900_COLOR); + ctrl->SetFont(::Label::Body_13); + ctrl->SetToolTip(tooltip); + // ctrl->Wrap(-1); + return ctrl; +} + +static TextInput* new_text_input(wxWindow *parent, const wxString &text, const wxString &tooltip, int validator = wxFILTER_DIGITS) { + auto ctrl = new TextInput( + parent, text, wxEmptyString, wxEmptyString, wxDefaultPosition, + parent->FromDIP(wxSize(DESIGN_INPUT_W, -1)), wxTE_PROCESS_ENTER + ); + ctrl->SetToolTip(tooltip); + if (validator > -1) + ctrl->GetTextCtrl()->SetValidator(wxTextValidator(validator)); + return ctrl; +} + +static ComboBox* new_combobox(wxWindow *parent, const wxString &tooltip = wxEmptyString, std::vector *list = nullptr, int width = DESIGN_COMBO_LRG_W) +{ + if (!width) + width = DESIGN_COMBO_LRG_W; + auto combobox = new ::ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(parent->FromDIP(width), -1), 0, nullptr, wxCB_READONLY); + combobox->SetFont(::Label::Body_13); + combobox->GetDropDown().SetFont(::Label::Body_13); + combobox->SetToolTip(tooltip); + if (list) + list->push_back(combobox); + return combobox; +} + +static wxBoxSizer* new_labeled_combobox( + wxWindow *parent, ComboBox *&combobox, const wxString &label, const wxString &tooltip = wxEmptyString, + std::vector *list = nullptr, int label_width = DESIGN_LABEL_W, int cb_width = DESIGN_COMBO_LRG_W +) +{ + wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(0, 0, 0, wxEXPAND | wxLEFT, parent->DESIGN_ITEM_INDENT); + + auto combo_title = new_static_text(parent, label, tooltip, label_width); + sizer->Add(combo_title, 0, wxALIGN_CENTER | wxALL, parent->FromDIP(3)); + + combobox = new_combobox(parent, tooltip, list, cb_width); + if (label_width < 0) + sizer->AddSpacer(parent->DESIGN_INPUT_PAD); + sizer->Add(combobox, 0, wxALIGN_CENTER); + return sizer; +} + +static Button* new_button(wxWindow *parent, const wxString &title, const wxString &tooltip, std::vector *list = nullptr, const wxSize &size = DESIGN_BUTTON_SIZE) { - wxBoxSizer *m_sizer_title = new wxBoxSizer(wxHORIZONTAL); - - auto m_title = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, 0); - m_title->SetForegroundColour(DESIGN_GRAY800_COLOR); - m_title->SetFont(::Label::Head_13); - m_title->Wrap(-1); - //m_title->SetToolTip(tooltip); - - auto m_line = new wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL); - m_line->SetBackgroundColour(DESIGN_GRAY400_COLOR); - - m_sizer_title->Add(m_title, 0, wxALIGN_CENTER | wxALL, 3); - m_sizer_title->Add(0, 0, 0, wxLEFT, 9); - //m_sizer_title->Add(m_line, 0, wxEXPAND, 0); - wxBoxSizer *sizer_line = new wxBoxSizer(wxVERTICAL); - sizer_line->Add( m_line, 0, wxEXPAND, 0 ); - m_sizer_title->Add( sizer_line, 1, wxALIGN_CENTER, 0 ); - //m_sizer_title->Add( 0, 0, 0, wxEXPAND|wxLEFT, 80 ); - - return m_sizer_title; + static const StateColor bg_color( + std::make_pair(wxColour(255, 255, 255), (int)StateColor::Disabled), + std::make_pair(wxColour(206, 206, 206), (int)StateColor::Pressed), + std::make_pair(wxColour(238, 238, 238), (int)StateColor::Hovered), + std::make_pair(wxColour(255, 255, 255), (int)StateColor::Enabled), + std::make_pair(wxColour(255, 255, 255), (int)StateColor::Normal) + ); + static const StateColor bd_color( + std::make_pair(wxColour(144, 144, 144), (int)StateColor::Disabled), + std::make_pair(wxColour(38, 46, 48), (int)StateColor::Enabled) + ); + static const StateColor text_color( + std::make_pair(wxColour(144, 144, 144), (int)StateColor::Disabled), + std::make_pair(wxColour(38, 46, 48), (int)StateColor::Enabled) + ); + + auto ctrl = new Button(parent, title); + ctrl->SetBackgroundColor(bg_color); + ctrl->SetBorderColor(bd_color); + ctrl->SetTextColor(text_color); + ctrl->SetFont(Label::Body_10); + ctrl->SetCornerRadius(parent->FromDIP(DESIGN_BUTTON_RADIUS)); + ctrl->SetToolTip(tooltip); + if (size != wxDefaultSize) + ctrl->SetMinSize(parent->FromDIP(size)); + + if (list) + list->push_back(ctrl); + return ctrl; } +// Editor widget builders -wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxWindow *parent, wxString tooltip, std::string param, const std::vector& label_list, const std::vector& value_list, std::function callback, int title_width, int combox_width) +wxBoxSizer *PreferencesDialog::create_item_combobox(const wxString &title, wxWindow *parent, const wxString &tooltip, const std::string ¶m, + const std::vector &label_list, const std::vector &value_list, std::function callback, int title_width, int combox_width) { - auto get_value_idx = [value_list](const std::string value) { + ComboBox *combobox = nullptr; + wxBoxSizer *sizer_combox = new_labeled_combobox(parent, combobox, title, tooltip, &m_combobox_list, title_width, combox_width); + + auto get_value_idx = [value_list](const std::string &value) { size_t idx = 0; auto iter = std::find(value_list.begin(), value_list.end(), value); if (iter != value_list.end()) @@ -77,23 +233,7 @@ wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxWindow *pa return idx; }; - wxBoxSizer *m_sizer_combox = new wxBoxSizer(wxHORIZONTAL); - m_sizer_combox->Add(0, 0, 0, wxEXPAND | wxLEFT, 23); - - auto combo_title = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, title_width == 0?DESIGN_TITLE_SIZE:wxSize(title_width, -1), 0); - combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - combo_title->SetFont(::Label::Body_13); - combo_title->SetToolTip(tooltip); - combo_title->Wrap(-1); - m_sizer_combox->Add(combo_title, 0, wxALIGN_CENTER | wxALL, 3); - - auto combobox = new ::ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, combox_width == 0?DESIGN_LARGE_COMBOBOX_SIZE:wxSize(combox_width, -1), 0, nullptr, wxCB_READONLY); - m_combobox_list[m_combobox_list.size()] = combobox; - combobox->SetFont(::Label::Body_13); - combobox->GetDropDown().SetFont(::Label::Body_13); - - std::vector::iterator iter; - for (auto label : label_list) + for (const wxString &label : label_list) combobox->Append(label); auto old_value = app_config->get(param); @@ -104,10 +244,8 @@ wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxWindow *pa combobox->SetSelection(0); } - m_sizer_combox->Add(combobox, 0, wxALIGN_CENTER, 0); - //// save config - combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, value_list, callback](wxCommandEvent &e) { + combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [=](wxCommandEvent &e) { app_config->set(param, value_list[e.GetSelection()]); app_config->save(); if (callback) { @@ -115,30 +253,18 @@ wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxWindow *pa } e.Skip(); }); - return m_sizer_combox; + return sizer_combox; } wxBoxSizer *PreferencesDialog::create_item_language_combobox( - wxString title, wxWindow *parent, wxString tooltip, int padding_left, std::string param, std::vector vlist) + const wxString &title, wxWindow *parent, const wxString &tooltip, const std::string ¶m, const std::vector &vlist) { - wxBoxSizer *m_sizer_combox = new wxBoxSizer(wxHORIZONTAL); - m_sizer_combox->Add(0, 0, 0, wxEXPAND | wxLEFT, 23); - - auto combo_title = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, 0); - combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - combo_title->SetFont(::Label::Body_13); - combo_title->SetToolTip(tooltip); - combo_title->Wrap(-1); - m_sizer_combox->Add(combo_title, 0, wxALIGN_CENTER | wxALL, 3); + ComboBox *combobox = nullptr; + wxBoxSizer *sizer_combox = new_labeled_combobox(parent, combobox, title, tooltip, &m_combobox_list); - auto combobox = new ::ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_LARGE_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY); - m_combobox_list[m_combobox_list.size()] = combobox; - combobox->SetFont(::Label::Body_13); - combobox->GetDropDown().SetFont(::Label::Body_13); auto language = app_config->get(param); m_current_language_selected = -1; - std::vector::iterator iter; for (size_t i = 0; i < vlist.size(); ++i) { auto language_name = vlist[i]->Description; @@ -219,8 +345,6 @@ wxBoxSizer *PreferencesDialog::create_item_language_combobox( } combobox->SetSelection(m_current_language_selected); - m_sizer_combox->Add(combobox, 0, wxALIGN_CENTER, 0); - combobox->Bind(wxEVT_LEFT_DOWN, [this, combobox](wxMouseEvent &e) { m_current_language_selected = combobox->GetSelection(); e.Skip(); @@ -279,31 +403,18 @@ wxBoxSizer *PreferencesDialog::create_item_language_combobox( e.Skip(); }); - return m_sizer_combox; + return sizer_combox; } -wxBoxSizer *PreferencesDialog::create_item_region_combobox(wxString title, wxWindow *parent, wxString tooltip, std::vector vlist) +wxBoxSizer *PreferencesDialog::create_item_region_combobox(const wxString &title, wxWindow *parent, const wxString &tooltip, const std::vector &vlist) { - std::vector local_regions = {"Asia-Pacific", "China", "Europe", "North America", "Others"}; + static const std::vector local_regions = {"Asia-Pacific", "China", "Europe", "North America", "Others"}; - wxBoxSizer *m_sizer_combox = new wxBoxSizer(wxHORIZONTAL); - m_sizer_combox->Add(0, 0, 0, wxEXPAND | wxLEFT, 23); + ComboBox *combobox = nullptr; + wxBoxSizer *sizer_combox = new_labeled_combobox(parent, combobox, title, tooltip, &m_combobox_list); - auto combo_title = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, 0); - combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - combo_title->SetFont(::Label::Body_13); - combo_title->SetToolTip(tooltip); - combo_title->Wrap(-1); - m_sizer_combox->Add(combo_title, 0, wxALIGN_CENTER | wxALL, 3); - - auto combobox = new ::ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_LARGE_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY); - m_combobox_list[m_combobox_list.size()] = combobox; - combobox->SetFont(::Label::Body_13); - combobox->GetDropDown().SetFont(::Label::Body_13); - m_sizer_combox->Add(combobox, 0, wxALIGN_CENTER, 0); - - std::vector::iterator iter; - for (iter = vlist.begin(); iter != vlist.end(); iter++) { combobox->Append(*iter); } + for (const wxString &v : vlist) + combobox->Append(v); AppConfig * config = GUI::wxGetApp().app_config; @@ -318,7 +429,7 @@ wxBoxSizer *PreferencesDialog::create_item_region_combobox(wxString title, wxWin } } - combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, combobox, current_region, local_regions](wxCommandEvent &e) { + combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [=](wxCommandEvent &e) { auto region_index = e.GetSelection(); auto region = local_regions[region_index]; @@ -349,633 +460,334 @@ wxBoxSizer *PreferencesDialog::create_item_region_combobox(wxString title, wxWin //e.Skip(); }); - return m_sizer_combox; + return sizer_combox; } -wxBoxSizer *PreferencesDialog::create_item_loglevel_combobox(wxString title, wxWindow *parent, wxString tooltip, std::vector vlist) +wxBoxSizer *PreferencesDialog::create_item_input( + const wxString &title, const wxString &title2, wxWindow *parent, const wxString &tooltip, + const std::string ¶m, std::function onchange, int label_width /* = 0 */ +) { - wxBoxSizer *m_sizer_combox = new wxBoxSizer(wxHORIZONTAL); - m_sizer_combox->Add(0, 0, 0, wxEXPAND | wxLEFT, 23); - - auto combo_title = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, 0); - combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - combo_title->SetFont(::Label::Body_13); - combo_title->SetToolTip(tooltip); - combo_title->Wrap(-1); - m_sizer_combox->Add(combo_title, 0, wxALIGN_CENTER | wxALL, 3); - - auto combobox = new ::ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY); - m_combobox_list[m_combobox_list.size()] = combobox; - combobox->SetFont(::Label::Body_13); - combobox->GetDropDown().SetFont(::Label::Body_13); + auto input = new_text_input(parent, app_config->get(param), tooltip); - std::vector::iterator iter; - for (iter = vlist.begin(); iter != vlist.end(); iter++) { combobox->Append(*iter); } - - auto severity_level = app_config->get("severity_level"); - if (!severity_level.empty()) { combobox->SetValue(severity_level); } - - m_sizer_combox->Add(combobox, 0, wxALIGN_CENTER, 0); - - //// save config - combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &e) { - auto level = Slic3r::get_string_logging_level(e.GetSelection()); - Slic3r::set_logging_level(Slic3r::level_string_to_boost(level)); - app_config->set("severity_level",level); - app_config->save(); - e.Skip(); - }); - return m_sizer_combox; -} - - -wxBoxSizer *PreferencesDialog::create_item_multiple_combobox( - wxString title, wxWindow *parent, wxString tooltip, int padding_left, std::string param, std::vector vlista, std::vector vlistb) -{ - std::vector params; - Split(app_config->get(param), "/", params); - - std::vector::iterator iter; - - wxBoxSizer *m_sizer_tcombox= new wxBoxSizer(wxHORIZONTAL); - m_sizer_tcombox->Add(0, 0, 0, wxEXPAND | wxLEFT, 23); - - auto combo_title = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, 0); - combo_title->SetToolTip(tooltip); - combo_title->Wrap(-1); - combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - combo_title->SetFont(::Label::Body_13); - m_sizer_tcombox->Add(combo_title, 0, wxALIGN_CENTER | wxALL, 3); - - auto combobox_left = new ::ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY); - m_combobox_list[m_combobox_list.size()] = combobox_left; - combobox_left->SetFont(::Label::Body_13); - combobox_left->GetDropDown().SetFont(::Label::Body_13); - - - for (iter = vlista.begin(); iter != vlista.end(); iter++) { combobox_left->Append(*iter); } - combobox_left->SetValue(std::string(params[0].mb_str())); - m_sizer_tcombox->Add(combobox_left, 0, wxALIGN_CENTER, 0); - - auto combo_title_add = new wxStaticText(parent, wxID_ANY, wxT("+"), wxDefaultPosition, wxDefaultSize, 0); - combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - combo_title->SetFont(::Label::Body_13); - combo_title_add->Wrap(-1); - m_sizer_tcombox->Add(combo_title_add, 0, wxALIGN_CENTER | wxALL, 3); - - auto combobox_right = new ::ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY); - m_combobox_list[m_combobox_list.size()] = combobox_right; - combobox_right->SetFont(::Label::Body_13); - combobox_right->GetDropDown().SetFont(::Label::Body_13); - - for (iter = vlistb.begin(); iter != vlistb.end(); iter++) { combobox_right->Append(*iter); } - combobox_right->SetValue(std::string(params[1].mb_str())); - m_sizer_tcombox->Add(combobox_right, 0, wxALIGN_CENTER, 0); - - // save config - combobox_left->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, combobox_right](wxCommandEvent &e) { - auto config = e.GetString() + wxString("/") + combobox_right->GetValue(); - app_config->set(param, std::string(config.mb_str())); - app_config->save(); - e.Skip(); - }); - - combobox_right->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, combobox_left](wxCommandEvent &e) { - auto config = combobox_left->GetValue() + wxString("/") + e.GetString(); - app_config->set(param, std::string(config.mb_str())); - app_config->save(); - e.Skip(); - }); - - return m_sizer_tcombox; -} - -wxBoxSizer *PreferencesDialog::create_item_input(wxString title, wxString title2, wxWindow *parent, wxString tooltip, std::string param, std::function onchange) -{ wxBoxSizer *sizer_input = new wxBoxSizer(wxHORIZONTAL); - auto input_title = new wxStaticText(parent, wxID_ANY, title); - input_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - input_title->SetFont(::Label::Body_13); - input_title->SetToolTip(tooltip); - input_title->Wrap(-1); - - auto input = new ::TextInput(parent, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER); - StateColor input_bg(std::pair(wxColour("#F0F0F1"), StateColor::Disabled), std::pair(*wxWHITE, StateColor::Enabled)); - input->SetBackgroundColor(input_bg); - input->GetTextCtrl()->SetValue(app_config->get(param)); - wxTextValidator validator(wxFILTER_DIGITS); - input->GetTextCtrl()->SetValidator(validator); - - auto second_title = new wxStaticText(parent, wxID_ANY, title2, wxDefaultPosition, DESIGN_TITLE_SIZE, 0); - second_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - second_title->SetFont(::Label::Body_13); - second_title->SetToolTip(tooltip); - second_title->Wrap(-1); - - sizer_input->Add(0, 0, 0, wxEXPAND | wxLEFT, 23); - sizer_input->Add(input_title, 0, wxALIGN_CENTER_VERTICAL | wxALL, 3); - sizer_input->Add(input, 0, wxALIGN_CENTER_VERTICAL, 0); - sizer_input->Add(0, 0, 0, wxEXPAND | wxLEFT, 3); - sizer_input->Add(second_title, 0, wxALIGN_CENTER_VERTICAL | wxALL, 3); - - input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [this, param, input, onchange](wxCommandEvent &e) { - auto value = input->GetTextCtrl()->GetValue(); - app_config->set(param, std::string(value.mb_str())); - app_config->save(); - onchange(value); + sizer_input->Add(0, 0, 0, wxEXPAND | wxLEFT, DESIGN_ITEM_INDENT); + sizer_input->Add(new_static_text(parent, title, tooltip, label_width), 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(3)); + if (label_width < 0) + sizer_input->AddSpacer(DESIGN_INPUT_PAD); + sizer_input->Add(input, 0, wxALIGN_CENTER_VERTICAL); + if (!title2.IsEmpty()) + sizer_input->Add(new_static_text(parent, title2, tooltip, -1), 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(3)); + + const auto set_value_to_app = [=](wxEvent &e) { e.Skip(); - }); - - input->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, [this, param, input, onchange](wxFocusEvent &e) { - auto value = input->GetTextCtrl()->GetValue(); - app_config->set(param, std::string(value.mb_str())); + const auto value = input->GetTextCtrl()->GetValue(); + if (value == app_config->get(param)) + return; + app_config->set(param, value.ToStdString()); app_config->save(); - onchange(value); - e.Skip(); - }); + if (onchange) + onchange(value); + }; + input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, set_value_to_app); + input->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, set_value_to_app); return sizer_input; } -wxBoxSizer *PreferencesDialog::create_item_range_input( - wxString title, wxWindow *parent, wxString tooltip, std::string param, float range_min, float range_max, int keep_digital, std::function onchange) +TextInput *PreferencesDialog::create_range_input( + wxWindow *parent, const wxString &tooltip, const std::string ¶m, float range_min, float range_max, int precision, std::function onchange +) { - wxBoxSizer *sizer_input = new wxBoxSizer(wxHORIZONTAL); - auto input_title = new wxStaticText(parent, wxID_ANY, title); - input_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - input_title->SetFont(::Label::Body_13); - input_title->SetToolTip(tooltip); - input_title->Wrap(-1); - - auto float_value = std::atof(app_config->get(param).c_str()); - if (float_value < range_min || float_value > range_max) { - float_value = range_min; - app_config->set(param, std::to_string(range_min)); - app_config->save(); - } - auto input = new ::TextInput(parent, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER); - StateColor input_bg(std::pair(wxColour("#F0F0F1"), StateColor::Disabled), std::pair(*wxWHITE, StateColor::Enabled)); - input->SetBackgroundColor(input_bg); - input->GetTextCtrl()->SetValue(app_config->get(param)); - wxTextValidator validator(wxFILTER_NUMERIC); - input->GetTextCtrl()->SetValidator(validator); - - sizer_input->Add(0, 0, 0, wxEXPAND | wxLEFT, 23); - sizer_input->Add(input_title, 0, wxALIGN_CENTER_VERTICAL | wxALL, 3); - sizer_input->Add(input, 0, wxALIGN_CENTER_VERTICAL, 0); - auto format_str=[](int keep_digital,float val){ + const auto format_str = [=](const float val){ std::stringstream ss; - ss << std::fixed << std::setprecision(keep_digital) << val; + ss << std::fixed << std::setprecision(precision) << val; return ss.str(); }; - auto set_value_to_app = [this, param, onchange, input, range_min, range_max, format_str, keep_digital](float value, bool update_slider) { - if (value < range_min) { value = range_min; } - if (value > range_max) { value = range_max; } - auto str = format_str(keep_digital, value); - app_config->set(param, str); - app_config->save(); - if (onchange) { - onchange(str); - } - input->GetTextCtrl()->SetValue(str); - }; - input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [this, set_value_to_app, input](wxCommandEvent &e) { - auto value = std::atof(input->GetTextCtrl()->GetValue().c_str()); - set_value_to_app(value,true); - e.Skip(); - }); - - input->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, [this, set_value_to_app, input](wxFocusEvent &e) { - auto value = std::atof(input->GetTextCtrl()->GetValue().c_str()); - set_value_to_app(value, true); - e.Skip(); - }); - - return sizer_input; -} -wxBoxSizer *PreferencesDialog::create_item_range_two_input(wxString title, - wxWindow * parent, - wxString tooltip, - std::string param, - std::string param1, - float range_min, - float range_max, - int keep_digital, - std::function onchange, - std::function onchange1) -{ - wxBoxSizer *sizer_input = new wxBoxSizer(wxHORIZONTAL); - auto input_title = new wxStaticText(parent, wxID_ANY, title); - input_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - input_title->SetFont(::Label::Body_13); - input_title->SetToolTip(tooltip); - input_title->Wrap(-1); - - auto float_value = std::atof(app_config->get(param).c_str()); - if (float_value < range_min || float_value > range_max) { - float_value = range_min; - app_config->set(param, std::to_string(range_min)); - app_config->save(); - } - float_value = std::atof(app_config->get(param1).c_str()); - if (float_value < range_min || float_value > range_max) { - float_value = range_min; - app_config->set(param1, std::to_string(range_min)); + // validate current value + const float float_value = std::atof(app_config->get(param).c_str()); + const float check_value = std::clamp(float_value, range_min, range_max); + if (float_value != check_value) { + app_config->set(param, format_str(check_value)); app_config->save(); } - auto input = new ::TextInput(parent, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER); - StateColor input_bg(std::pair(wxColour("#F0F0F1"), StateColor::Disabled), std::pair(*wxWHITE, StateColor::Enabled)); - input->SetBackgroundColor(input_bg); - input->GetTextCtrl()->SetValue(app_config->get(param)); - wxTextValidator validator(wxFILTER_NUMERIC); - input->GetTextCtrl()->SetValidator(validator); - - auto input1 = new ::TextInput(parent, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER); - input1->SetBackgroundColor(input_bg); - input1->GetTextCtrl()->SetValue(app_config->get(param1)); - input1->GetTextCtrl()->SetValidator(validator); - - sizer_input->Add(0, 0, 0, wxEXPAND | wxLEFT, 23); - sizer_input->Add(input_title, 0, wxALIGN_CENTER_VERTICAL | wxALL, 3); - sizer_input->Add(input, 0, wxALIGN_CENTER_VERTICAL, 0); - sizer_input->AddSpacer(FromDIP(8)); - sizer_input->Add(input1, 0, wxALIGN_CENTER_VERTICAL, 0); - auto format_str = [](int keep_digital, float val) { - std::stringstream ss; - ss << std::fixed << std::setprecision(keep_digital) << val; - return ss.str(); - }; - auto set_value_to_app = [this, param, onchange, input, range_min, range_max, format_str, keep_digital](float value, bool update_slider) { - if (value < range_min) { value = range_min; } - if (value > range_max) { value = range_max; } - auto str = format_str(keep_digital, value); - app_config->set(param, str); - app_config->save(); - if (onchange) { onchange(str); } - input->GetTextCtrl()->SetValue(str); - }; - input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [this, set_value_to_app, input](wxCommandEvent &e) { - auto value = std::atof(input->GetTextCtrl()->GetValue().c_str()); - set_value_to_app(value, true); - e.Skip(); - }); + auto input = new_text_input(parent, app_config->get(param), tooltip, wxFILTER_NUMERIC); - input->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, [this, set_value_to_app, input](wxFocusEvent &e) { - auto value = std::atof(input->GetTextCtrl()->GetValue().c_str()); - set_value_to_app(value, true); + const auto set_value_to_app = [=](wxEvent &e) { e.Skip(); - }); - - auto set_value1_to_app = [this, param1, onchange1, input1, range_min, range_max, format_str, keep_digital](float value, bool update_slider) { - if (value < range_min) { value = range_min; } - if (value > range_max) { value = range_max; } - auto str = format_str(keep_digital, value); - app_config->set(param1, str); + const auto value = format_str(std::clamp(std::atof(input->GetTextCtrl()->GetValue().c_str()), range_min, range_max)); + if (value == app_config->get(param)) + return; + app_config->set(param, value); app_config->save(); - if (onchange1) { onchange1(str); } - input1->GetTextCtrl()->SetValue(str); + if (onchange) { + onchange(value); + } + input->GetTextCtrl()->SetValue(value); }; - input1->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [this, set_value1_to_app, input1](wxCommandEvent &e) { - auto value = std::atof(input1->GetTextCtrl()->GetValue().c_str()); - set_value1_to_app(value, true); - e.Skip(); - }); - - input1->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, [this, set_value1_to_app, input1](wxFocusEvent &e) { - auto value = std::atof(input1->GetTextCtrl()->GetValue().c_str()); - set_value1_to_app(value, true); - e.Skip(); - }); + input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, set_value_to_app); + input->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, set_value_to_app); - return sizer_input; + return input; } -wxBoxSizer *PreferencesDialog::create_item_backup_input(wxString title, wxWindow *parent, wxString tooltip, std::string param) +wxBoxSizer *PreferencesDialog::create_item_range_input( + const wxString &title, wxWindow *parent, const wxString &tooltip, const std::string ¶m, + float range_min, float range_max, int precision, std::function onchange, int label_width /* = 0 */ +) { - wxBoxSizer *m_sizer_input = new wxBoxSizer(wxHORIZONTAL); - auto input_title = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, 0); - input_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - input_title->SetFont(::Label::Body_13); - input_title->SetToolTip(tooltip); - input_title->Wrap(-1); - - auto input = new ::TextInput(parent, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER); - StateColor input_bg(std::pair(wxColour("#F0F0F1"), StateColor::Disabled), std::pair(*wxWHITE, StateColor::Enabled)); - input->SetBackgroundColor(input_bg); - input->GetTextCtrl()->SetValue(app_config->get(param)); - wxTextValidator validator(wxFILTER_DIGITS); - input->GetTextCtrl()->SetValidator(validator); - - - auto second_title = new wxStaticText(parent, wxID_ANY, _L("Second"), wxDefaultPosition, DESIGN_TITLE_SIZE, 0); - second_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - second_title->SetFont(::Label::Body_13); - second_title->SetToolTip(tooltip); - second_title->Wrap(-1); - - m_sizer_input->Add(0, 0, 0, wxEXPAND | wxLEFT, 23); - m_sizer_input->Add(input_title, 0, wxALIGN_CENTER_VERTICAL | wxALL, 3); - m_sizer_input->Add(input, 0, wxALIGN_CENTER_VERTICAL, 0); - m_sizer_input->Add(0, 0, 0, wxEXPAND | wxLEFT, 3); - m_sizer_input->Add(second_title, 0, wxALIGN_CENTER_VERTICAL | wxALL, 3); - - - input->GetTextCtrl()->Bind(wxEVT_COMMAND_TEXT_UPDATED, [this, param, input](wxCommandEvent &e) { - m_backup_interval_time = input->GetTextCtrl()->GetValue(); - e.Skip(); - }); - - std::function backup_interval = [this, param, input]() { - m_backup_interval_time = input->GetTextCtrl()->GetValue(); - app_config->set("backup_interval", std::string(m_backup_interval_time.mb_str())); - app_config->save(); - long backup_interval = 0; - m_backup_interval_time.ToLong(&backup_interval); - Slic3r::set_backup_interval(backup_interval); - }; - - input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [backup_interval](wxCommandEvent &e) { - backup_interval(); - e.Skip(); - }); - - input->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, [backup_interval](wxFocusEvent &e) { - backup_interval(); - e.Skip(); - }); + auto input = create_range_input(parent, tooltip, param, range_min, range_max, precision, onchange); - if (app_config->get("backup_switch") == "true") { - input->Enable(true); - input->Refresh(); - } else { - input->Enable(false); - input->Refresh(); - } - - if (param == "backup_interval") { m_backup_interval_textinput = input; } - return m_sizer_input; + wxBoxSizer *sizer_input = new wxBoxSizer(wxHORIZONTAL); + sizer_input->Add(0, 0, 0, wxEXPAND | wxLEFT, DESIGN_ITEM_INDENT); + sizer_input->Add(new_static_text(parent, title, tooltip, label_width), 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(3)); + if (label_width < 0) + sizer_input->AddSpacer(DESIGN_INPUT_PAD); + sizer_input->Add(input, 0, wxALIGN_CENTER_VERTICAL); + return sizer_input; } - -wxBoxSizer *PreferencesDialog::create_item_switch(wxString title, wxWindow *parent, wxString tooltip ,std::string param) +wxBoxSizer *PreferencesDialog::create_item_range_two_input( + const wxString & title, + wxWindow * parent, + const wxString & tooltip, + const std::string & param, + const std::string & param1, + float range_min, + float range_max, + int precision, + std::function onchange, + std::function onchange1, + int label_width /* = 0 */ +) { - wxBoxSizer *m_sizer_switch = new wxBoxSizer(wxHORIZONTAL); - auto switch_title = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, 0); - switch_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - switch_title->SetFont(::Label::Body_13); - switch_title->SetToolTip(tooltip); - switch_title->Wrap(-1); - auto switchbox = new ::SwitchButton(parent, wxID_ANY); - - /*auto index = app_config->get(param); - if (!index.empty()) { combobox->SetSelection(atoi(index.c_str())); }*/ - - m_sizer_switch->Add(0, 0, 0, wxEXPAND | wxLEFT, 23); - m_sizer_switch->Add(switch_title, 0, wxALIGN_CENTER | wxALL, 3); - m_sizer_switch->Add( 0, 0, 1, wxEXPAND, 0 ); - m_sizer_switch->Add(switchbox, 0, wxALIGN_CENTER, 0); - m_sizer_switch->Add( 0, 0, 0, wxEXPAND|wxLEFT, 40 ); + auto input = create_range_input(parent, tooltip, param, range_min, range_max, precision, onchange); + auto input1 = create_range_input(parent, tooltip, param1, range_min, range_max, precision, onchange1); - //// save config - switchbox->Bind(wxEVT_TOGGLEBUTTON, [this, param](wxCommandEvent &e) { - /* app_config->set(param, std::to_string(e.GetSelection())); - app_config->save();*/ - e.Skip(); - }); - return m_sizer_switch; + wxBoxSizer *sizer_input = new wxBoxSizer(wxHORIZONTAL); + sizer_input->Add(0, 0, 0, wxEXPAND | wxLEFT, DESIGN_ITEM_INDENT); + sizer_input->Add(new_static_text(parent, title, tooltip, label_width), 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(3)); + if (label_width < 0) + sizer_input->AddSpacer(DESIGN_INPUT_PAD); + sizer_input->Add(input, 0, wxALIGN_CENTER_VERTICAL, 0); + sizer_input->AddSpacer(DESIGN_INPUT_PAD); + sizer_input->Add(input1, 0, wxALIGN_CENTER_VERTICAL, 0); + return sizer_input; } -wxBoxSizer* PreferencesDialog::create_item_darkmode_checkbox(wxString title, wxWindow* parent, wxString tooltip, int padding_left, std::string param) +wxBoxSizer *PreferencesDialog::create_item_backup_input(const wxString &title, wxWindow *parent, const wxString &tooltip) { - wxBoxSizer* m_sizer_checkbox = new wxBoxSizer(wxHORIZONTAL); - - m_sizer_checkbox->Add(0, 0, 0, wxEXPAND | wxLEFT, 23); + static const std::string param("backup_interval"); - auto checkbox = new ::CheckBox(parent); - m_checkbox_list[m_checkbox_list.size()] = checkbox; - checkbox->SetValue((app_config->get(param) == "1") ? true : false); - m_dark_mode_ckeckbox = checkbox; + auto input = new_text_input(parent, app_config->get(param), tooltip); - m_sizer_checkbox->Add(checkbox, 0, wxALIGN_CENTER, 0); - m_sizer_checkbox->Add(0, 0, 0, wxEXPAND | wxLEFT, 8); + const auto save_value = [=](wxEvent &e) { + e.Skip(); + const wxString value = input->GetTextCtrl()->GetValue(); + if (value == app_config->get(param)) + return; + app_config->set(param, value.ToStdString()); + app_config->save(); + long interval = 0; + value.ToLong(&interval); + Slic3r::set_backup_interval(interval); + m_backup_interval_time = value; + }; + input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, save_value); + input->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, save_value); - auto checkbox_title = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, 0); - checkbox_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - checkbox_title->SetFont(::Label::Body_13); + input->Enable(app_config->get_bool("backup_switch")); + input->Refresh(); - auto size = checkbox_title->GetTextExtent(title); - checkbox_title->SetMinSize(wxSize(size.x + FromDIP(40), -1)); - checkbox_title->Wrap(-1); - m_sizer_checkbox->Add(checkbox_title, 0, wxALIGN_CENTER | wxALL, 3); + m_backup_interval_textinput = input; + wxBoxSizer *sizer_input = new wxBoxSizer(wxHORIZONTAL); + sizer_input->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(3)); + sizer_input->Add(new_static_text(parent, title, tooltip, -1), 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(3)); + sizer_input->AddSpacer(DESIGN_INPUT_PAD); + sizer_input->Add(input, 0, wxALIGN_CENTER_VERTICAL, 0); + sizer_input->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(3)); + sizer_input->Add(new_static_text(parent, _L("Second"), tooltip, -1), 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(3)); + return sizer_input; +} +wxBoxSizer* PreferencesDialog::create_item_darkmode_checkbox(const wxString &title, wxWindow* parent, const wxString &tooltip, const std::string ¶m) +{ //// save config - checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, checkbox, param](wxCommandEvent& e) { - app_config->set(param, checkbox->GetValue() ? "1" : "0"); + auto callback = [this, param](int value) { + app_config->set(param, value ? "1" : "0"); app_config->save(); wxGetApp().Update_dark_mode_flag(); - - //dark mode -#ifdef _MSW_DARK_MODE wxGetApp().force_colors_update(); wxGetApp().update_ui_from_settings(); - set_dark_mode(); -#endif + wxGetApp().UpdateDlgDarkUI(this); + SimpleEvent evt = SimpleEvent(EVT_GLCANVAS_COLOR_MODE_CHANGED); wxPostEvent(wxGetApp().plater(), evt); - e.Skip(); - }); + }; - checkbox->SetToolTip(tooltip); - return m_sizer_checkbox; + return create_item_checkbox(title, parent, tooltip, param, callback); } -void PreferencesDialog::set_dark_mode() -{ -#ifdef __WINDOWS__ -#ifdef _MSW_DARK_MODE - NppDarkMode::SetDarkExplorerTheme(this->GetHWND()); - NppDarkMode::SetDarkTitleBar(this->GetHWND()); - wxGetApp().UpdateDlgDarkUI(this); - SetActiveWindow(wxGetApp().mainframe->GetHWND()); - SetActiveWindow(GetHWND()); -#endif -#endif -} - -wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxWindow *parent, wxString tooltip, int padding_left, std::string param) +// TODO: Move special handling of individual properties in the change handler into their own callbacks. +wxBoxSizer *PreferencesDialog::create_item_checkbox( + const wxString &title, wxWindow *parent, const wxString &tooltip, + const std::string ¶m, std::function callback /* = nullptr */ +) { wxBoxSizer *m_sizer_checkbox = new wxBoxSizer(wxHORIZONTAL); - m_sizer_checkbox->Add(0, 0, 0, wxEXPAND | wxLEFT, 23); + m_sizer_checkbox->Add(0, 0, 0, wxEXPAND | wxLEFT, DESIGN_ITEM_INDENT); auto checkbox = new ::CheckBox(parent); - m_checkbox_list[m_checkbox_list.size()] = checkbox; - if (param == "privacyuse") { - checkbox->SetValue((app_config->get("firstguide", param) == "true") ? true : false); - } else if (param == "auto_stop_liveview") { - checkbox->SetValue((app_config->get("liveview", param) == "true") ? false : true); - } else { - checkbox->SetValue((app_config->get(param) == "true") ? true : false); + checkbox->SetToolTip(tooltip); + m_checkbox_list.push_back(checkbox); + + if (!param.empty()) { + bool current_value = app_config->get_bool(param); + // special exception for reversed item label vs. actual config value + if (param == "no_warn_when_modified_gcodes") + current_value = !current_value; + checkbox->SetValue(current_value); } m_sizer_checkbox->Add(checkbox, 0, wxALIGN_CENTER, 0); m_sizer_checkbox->Add(0, 0, 0, wxEXPAND | wxLEFT, 8); - auto checkbox_title = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, 0); - checkbox_title->SetForegroundColour(DESIGN_GRAY900_COLOR); - checkbox_title->SetFont(::Label::Body_13); - - auto size = checkbox_title->GetTextExtent(title); + auto checkbox_title = new_static_text(parent, title, tooltip, -1); + const auto size = checkbox_title->GetTextExtent(title); checkbox_title->SetMinSize(wxSize(size.x + FromDIP(5), -1)); - checkbox_title->Wrap(-1); m_sizer_checkbox->Add(checkbox_title, 0, wxALIGN_CENTER | wxALL, 3); - //// save config - checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, checkbox, param](wxCommandEvent &e) { - if (param == "privacyuse") { - app_config->set("firstguide", param, checkbox->GetValue()); - NetworkAgent* agent = GUI::wxGetApp().getAgent(); - if (!checkbox->GetValue()) { - if (agent) { - agent->track_enable(false); - agent->track_remove_files(); - } - } - wxGetApp().save_privacy_policy_history(checkbox->GetValue(), "preferences"); + checkbox->Bind(wxEVT_TOGGLEBUTTON, [=](wxCommandEvent &e) + { + bool value = checkbox->GetValue(); + + if (!param.empty()) { + // special exception for reversed item label vs. actual config value + if (param == "no_warn_when_modified_gcodes") + value = !value; + app_config->set_bool(param, value); app_config->save(); } - else if (param == "auto_stop_liveview") { - app_config->set("liveview", param, !checkbox->GetValue()); + + if (callback) { + callback(e.GetInt()); } - else { - app_config->set_bool(param, checkbox->GetValue()); - app_config->save(); + + else if (param == "firstguide") { + if (NetworkAgent* agent = GUI::wxGetApp().getAgent(); agent && !value) { + agent->track_enable(false); + agent->track_remove_files(); + } + wxGetApp().save_privacy_policy_history(value, "preferences"); } - if (param == "staff_pick_switch") { - bool pbool = app_config->get("staff_pick_switch") == "true"; - wxGetApp().switch_staff_pick(pbool); + else if (param == "staff_pick_switch") { + wxGetApp().switch_staff_pick(value); } // backup - if (param == "backup_switch") { - bool pbool = app_config->get("backup_switch") == "true" ? true : false; + else if (param == "backup_switch") { std::string backup_interval = "10"; app_config->get("backup_interval", backup_interval); - Slic3r::set_backup_interval(pbool ? boost::lexical_cast(backup_interval) : 0); - if (m_backup_interval_textinput != nullptr) { m_backup_interval_textinput->Enable(pbool); } + Slic3r::set_backup_interval(value ? boost::lexical_cast(backup_interval) : 0); + if (m_backup_interval_textinput != nullptr) { m_backup_interval_textinput->Enable(value); } } - if (param == "sync_user_preset") { - bool sync = app_config->get("sync_user_preset") == "true" ? true : false; - if (sync) { + else if (param == "sync_user_preset") { + if (value) { wxGetApp().start_sync_user_preset(); } else { wxGetApp().stop_sync_user_preset(); } - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " sync_user_preset: " << (sync ? "true" : "false"); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " sync_user_preset: " << (value ? "true" : "false"); } - #ifdef __WXMSW__ - if (param == "associate_3mf") { - bool pbool = app_config->get("associate_3mf") == "true" ? true : false; - if (pbool) { +#ifdef __WXMSW__ + else if (param == "associate_3mf") { + if (value) { wxGetApp().associate_files(L"3mf"); } else { wxGetApp().disassociate_files(L"3mf"); } } - if (param == "associate_stl") { - bool pbool = app_config->get("associate_stl") == "true" ? true : false; - if (pbool) { + else if (param == "associate_stl") { + if (value) { wxGetApp().associate_files(L"stl"); } else { wxGetApp().disassociate_files(L"stl"); } } - if (param == "associate_step") { - bool pbool = app_config->get("associate_step") == "true" ? true : false; - if (pbool) { + else if (param == "associate_step") { + if (value) { wxGetApp().associate_files(L"step"); } else { wxGetApp().disassociate_files(L"step"); } } +#endif // __WXMSW__ - #endif // __WXMSW__ - - if (param == "developer_mode") + else if (param == "developer_mode") { - m_developer_mode_def = app_config->get("developer_mode"); - if (m_developer_mode_def == "true") { - Slic3r::GUI::wxGetApp().save_mode(comDevelop); - } else { - Slic3r::GUI::wxGetApp().save_mode(comAdvanced); - } + Slic3r::GUI::wxGetApp().save_mode(value ? comDevelop : comAdvanced); + m_developer_mode_def = value; } // webview dump_vedio - if (param == "internal_developer_mode") { - m_internal_developer_mode_def = app_config->get("internal_developer_mode"); - if (m_internal_developer_mode_def == "true") { - Slic3r::GUI::wxGetApp().update_internal_development(); + else if (param == "internal_developer_mode") { + Slic3r::GUI::wxGetApp().update_internal_development(); + if (value) Slic3r::GUI::wxGetApp().mainframe->show_log_window(); - } else { - Slic3r::GUI::wxGetApp().update_internal_development(); - } + m_internal_developer_mode_def = value; } - if (param == "show_print_history") { - auto show_history = app_config->get_bool("show_print_history"); - if (show_history == true) { - if (wxGetApp().mainframe && wxGetApp().mainframe->m_webview) { wxGetApp().mainframe->m_webview->ShowUserPrintTask(true,true); } - } else { - if (wxGetApp().mainframe && wxGetApp().mainframe->m_webview) { wxGetApp().mainframe->m_webview->ShowUserPrintTask(false); } - } + else if (param == "show_print_history") { + if (wxGetApp().mainframe && wxGetApp().mainframe->m_webview) + wxGetApp().mainframe->m_webview->ShowUserPrintTask(value, value); } - if (param == "enable_lod") { - if (wxGetApp().plater()->is_project_dirty()) { - auto result = MessageDialog(static_cast(this), _L("The current project has unsaved changes, save it before continuing?"), - wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Save"), wxYES_NO | wxYES_DEFAULT | wxCENTRE) - .ShowModal(); - if (result == wxID_YES) { - wxGetApp().plater()->save_project(); - } - } - MessageDialog msg_wingow(nullptr, _L("Please note that the model show will undergo certain changes at small pixels case.\nEnabled LOD requires application restart.") + "\n" + _L("Do you want to continue?"), _L("Enable LOD"), - wxYES| wxYES_DEFAULT | wxCANCEL | wxCENTRE); + else if (param == "enable_lod") { + MessageDialog msg_wingow(this, + _L("Please note that the model show will undergo certain changes at small pixels case.\nEnabled LOD requires application restart.") + "\n" + _L("Do you want to continue?"), + _L("Enable LOD"), wxICON_WARNING | wxYES_NO | wxYES_DEFAULT | wxCENTRE); if (msg_wingow.ShowModal() == wxID_YES) { + if (wxGetApp().plater()->is_project_dirty()) { + MessageDialog save_dlg(this, _L("The current project has unsaved changes, save it before continuing?"), + wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Save"), wxICON_WARNING | wxYES_NO | wxYES_DEFAULT | wxCENTRE); + if (save_dlg.ShowModal() == wxID_YES) + wxGetApp().plater()->save_project(); + } Close(); GetParent()->RemoveChild(this); wxGetApp().recreate_GUI(_L("Enable LOD")); - } else { - checkbox->SetValue(!checkbox->GetValue()); - app_config->set_bool(param, checkbox->GetValue()); + } + else { + checkbox->SetValue(!value); + app_config->set_bool(param, !value); app_config->save(); } } - if (param == "enable_record_gcodeviewer_option_item"){ + else if (param == "enable_record_gcodeviewer_option_item"){ SimpleEvent evt(EVT_ENABLE_GCODE_OPTION_ITEM_CHANGED); wxPostEvent(wxGetApp().plater(), evt); } - if (param == "enable_high_low_temp_mixed_printing") { - if (checkbox->GetValue()) { + else if (param == "enable_high_low_temp_mixed_printing") { + if (value) { const wxString warning_title = _L("Bed Temperature Difference Warning"); const wxString warning_message = _L("Using filaments with significantly different temperatures may cause:\n" "• Extruder clogging\n" "• Nozzle damage\n" "• Layer adhesion issues\n\n" - "Continue with enabling this feature?"); + "Continue with enabling this feature?") + "\n"; std::function link_callback = [](const wxString&) { const std::string lang_code = wxGetApp().app_config->get("language"); const wxString region = (lang_code.find("zh") != std::string::npos) ? L"zh" : L"en"; @@ -987,10 +799,10 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxWindow *pa }; MessageDialog msg_dialog( - nullptr, + this, warning_message, warning_title, - wxICON_WARNING | wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE, + wxICON_WARNING | wxYES_NO | wxYES_DEFAULT | wxCENTRE, wxEmptyString, _L("Click Wiki for help."), link_callback @@ -1003,141 +815,109 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxWindow *pa } } } + e.Skip(); }); //// for debug mode if (param == "developer_mode") { m_developer_mode_ckeckbox = checkbox; } - if (param == "internal_developer_mode") { m_internal_developer_mode_ckeckbox = checkbox; } + else if (param == "internal_developer_mode") { m_internal_developer_mode_ckeckbox = checkbox; } - - checkbox->SetToolTip(tooltip); return m_sizer_checkbox; } -wxBoxSizer *PreferencesDialog::create_item_button(wxString title, wxString title2, wxWindow *parent, wxString tooltip, std::function onclick) +wxWindow* PreferencesDialog::create_item_downloads(wxWindow* parent, const std::string ¶m) { - wxBoxSizer *m_sizer_checkbox = new wxBoxSizer(wxHORIZONTAL); - - m_sizer_checkbox->Add(0, 0, 0, wxEXPAND | wxLEFT, 23); - auto m_staticTextPath = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END); - // m_staticTextPath->SetMaxSize(wxSize(FromDIP(440), -1)); - m_staticTextPath->SetForegroundColour(DESIGN_GRAY900_COLOR); - m_staticTextPath->SetFont(::Label::Body_13); - m_staticTextPath->Wrap(-1); - - - auto temp_button = new Button(parent, title2); - m_button_list[m_button_list.size()] = temp_button; - StateColor abort_bg(std::pair(wxColour(255, 255, 255), StateColor::Disabled), std::pair(wxColour(206, 206, 206), StateColor::Pressed), - std::pair(wxColour(238, 238, 238), StateColor::Hovered), std::pair(wxColour(255, 255, 255), StateColor::Enabled), - std::pair(wxColour(255, 255, 255), StateColor::Normal)); - - temp_button->SetBackgroundColor(abort_bg); - StateColor abort_bd(std::pair(wxColour(144, 144, 144), StateColor::Disabled), std::pair(wxColour(38, 46, 48), StateColor::Enabled)); - - temp_button->SetBorderColor(abort_bd); - StateColor abort_text(std::pair(wxColour(144, 144, 144), StateColor::Disabled), std::pair(wxColour(38, 46, 48), StateColor::Enabled)); - - temp_button->SetTextColor(abort_text); - temp_button->SetFont(Label::Body_10); - temp_button->SetMinSize(PreferenceBtnSize); - temp_button->SetSize(wxSize(FromDIP(58), FromDIP(22))); - temp_button->SetCornerRadius(FromDIP(12)); - if(!tooltip.empty()) temp_button->SetToolTip(tooltip); - - - temp_button->Bind(wxEVT_BUTTON, [this, onclick](auto &e) { onclick(); }); - - m_sizer_checkbox->Add(m_staticTextPath, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5)); - m_sizer_checkbox->Add(temp_button, 0, wxALL, FromDIP(5)); + auto item_panel = new wxWindow(parent, wxID_ANY); + item_panel->SetBackgroundColour(DESIGN_CONTENT_BG_COLOR); - return m_sizer_checkbox; -} + auto label = new_static_text(item_panel, _L("Downloads Folder:"), wxEmptyString, -1); + auto m_staticTextPath = new_static_text(item_panel, wxString::FromUTF8(app_config->get(param)), wxEmptyString, -1, wxST_ELLIPSIZE_END); + auto m_button_download = new_button(item_panel, _L("Browse"), _L("Select default folder for downloads."), &m_button_list); -wxWindow* PreferencesDialog::create_item_downloads(wxWindow* parent, int padding_left, std::string param) -{ - wxString download_path = wxString::FromUTF8(app_config->get("download_path")); - auto item_panel = new wxWindow(parent, wxID_ANY); - item_panel->SetBackgroundColour(*wxWHITE); - wxBoxSizer* m_sizer_checkbox = new wxBoxSizer(wxHORIZONTAL); - - m_sizer_checkbox->Add(0, 0, 0, wxEXPAND | wxLEFT, 23); - auto m_staticTextPath = new wxStaticText(item_panel, wxID_ANY, download_path, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END); - //m_staticTextPath->SetMaxSize(wxSize(FromDIP(440), -1)); - m_staticTextPath->SetForegroundColour(DESIGN_GRAY600_COLOR); - m_staticTextPath->SetFont(::Label::Body_13); - m_staticTextPath->Wrap(-1); - - auto m_button_download = new Button(item_panel, _L("Browse")); - m_button_list[m_button_list.size()] = m_button_download; - StateColor abort_bg(std::pair(wxColour(255, 255, 255), StateColor::Disabled), std::pair(wxColour(206, 206, 206), StateColor::Pressed), - std::pair(wxColour(238, 238, 238), StateColor::Hovered), std::pair(wxColour(255, 255, 255), StateColor::Enabled), - std::pair(wxColour(255, 255, 255), StateColor::Normal)); - m_button_download->SetBackgroundColor(abort_bg); - StateColor abort_bd(std::pair(wxColour(144, 144, 144), StateColor::Disabled), std::pair(wxColour(38, 46, 48), StateColor::Enabled)); - m_button_download->SetBorderColor(abort_bd); - StateColor abort_text(std::pair(wxColour(144, 144, 144), StateColor::Disabled), std::pair(wxColour(38, 46, 48), StateColor::Enabled)); - m_button_download->SetTextColor(abort_text); - m_button_download->SetFont(Label::Body_10); - m_button_download->SetMinSize(PreferenceBtnSize); - m_button_download->SetSize(wxSize(FromDIP(58), FromDIP(22))); - m_button_download->SetCornerRadius(FromDIP(12)); - - m_button_download->Bind(wxEVT_BUTTON, [this, m_staticTextPath, item_panel](auto& e) { - wxString defaultPath = wxT("/"); - wxDirDialog dialog(this, _L("Choose Download Directory"), defaultPath, wxDD_NEW_DIR_BUTTON); + m_button_download->Bind(wxEVT_BUTTON, [=](auto& e) { + const wxString defaultPath = wxString::FromUTF8(app_config->get(param)); + wxDirDialog dialog(const_cast(this), _L("Choose Download Directory"), defaultPath, wxDD_NEW_DIR_BUTTON); if (dialog.ShowModal() == wxID_OK) { - wxString download_path = dialog.GetPath(); - std::string download_path_str = download_path.ToUTF8().data(); - app_config->set("download_path", download_path_str); + const wxString download_path = dialog.GetPath(); + app_config->set(param, download_path.ToUTF8().data()); m_staticTextPath->SetLabelText(download_path); item_panel->Layout(); } - }); + }); - m_sizer_checkbox->Add(m_staticTextPath, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5)); - m_sizer_checkbox->Add(m_button_download, 0, wxALL, FromDIP(5)); + wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(0, 0, 0, wxEXPAND | wxLEFT, DESIGN_ITEM_INDENT); + sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5)); + sizer->AddSpacer(DESIGN_INPUT_PAD); + sizer->Add(m_staticTextPath, 0, wxALIGN_CENTER_VERTICAL); + sizer->Add(m_button_download, 0, wxALL, FromDIP(5)); - item_panel->SetSizer(m_sizer_checkbox); + item_panel->SetSizer(sizer); item_panel->Layout(); return item_panel; } -wxWindow *PreferencesDialog ::create_item_radiobox(wxString title, wxWindow *parent, wxString tooltip, int padding_left, int groupid, std::string param) +wxWindow *PreferencesDialog::create_item_radiobox( + const wxString &title, + wxWindow *parent, + const wxString &tooltip, + int groupid, + const std::string ¶m, + bool select /* = false */ +) { wxWindow *item = new wxWindow(parent, wxID_ANY, wxDefaultPosition, wxSize(-1, FromDIP(28))); - item->SetBackgroundColour(*wxWHITE); + item->SetBackgroundColour(DESIGN_CONTENT_BG_COLOR); RadioBox *radiobox = new RadioBox(item); - m_radiobox_list[m_radiobox_list.size()] = radiobox; - radiobox->SetPosition(wxPoint(padding_left, (item->GetSize().GetHeight() - radiobox->GetSize().GetHeight()) / 2)); - radiobox->Bind(wxEVT_LEFT_DOWN, &PreferencesDialog::OnSelectRadio, this); + radiobox->SetValue(select); + radiobox->SetToolTip(tooltip); + radiobox->SetPosition(wxPoint(DESIGN_ITEM_INDENT, (item->GetSize().GetHeight() - radiobox->GetSize().GetHeight()) / 2)); RadioSelector *rs = new RadioSelector; rs->m_groupid = groupid; rs->m_param_name = param; rs->m_radiobox = radiobox; - rs->m_selected = false; + rs->m_selected = select; m_radio_group.Append(rs); - wxStaticText *text = new wxStaticText(item, wxID_ANY, title, wxDefaultPosition, wxDefaultSize); - text->SetPosition(wxPoint(padding_left + radiobox->GetSize().GetWidth() + 10, (item->GetSize().GetHeight() - text->GetSize().GetHeight()) / 2)); + radiobox->Bind(wxEVT_LEFT_DOWN, [=](wxMouseEvent &event) { + // Ensure only one button is selected in exclusive groups. + int groupid = -1; + const int event_id = event.GetId(); + + for (auto rs : std::as_const(m_radio_group)) { + if (RadioBox *rb = rs->m_radiobox; rb && rb->GetId() == event_id) { + groupid = rs->m_groupid; + break; + } + } + if (groupid < 0) + return; + + for (auto rs : std::as_const(m_radio_group)) + if (RadioBox *rb = rs->m_radiobox; rb && rs->m_groupid == groupid) + rb->SetValue(rb->GetId() == event_id); + }); + + wxStaticText *text = new_static_text(item, title, tooltip, -1); + text->SetPosition(wxPoint(DESIGN_ITEM_INDENT + radiobox->GetSize().GetWidth() + 10, (item->GetSize().GetHeight() - text->GetSize().GetHeight()) / 2)); - radiobox->SetToolTip(tooltip); - text->SetToolTip(tooltip); return item; } + +// +// Begin implementation +// + PreferencesDialog::PreferencesDialog(wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &pos, const wxSize &size, long style) : DPIDialog(parent, id, _L("Preferences"), pos, size, style) { - SetBackgroundColour(*wxWHITE); - m_original_use_12h_time_format = wxGetApp().app_config->get("use_12h_time_format"); create(); - wxGetApp().UpdateDlgDarkUI(this); Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) { try { NetworkAgent* agent = GUI::wxGetApp().getAgent(); @@ -1149,336 +929,271 @@ PreferencesDialog::PreferencesDialog(wxWindow *parent, wxWindowID id, const wxSt agent->track_event("preferences_changed", j.dump()); } } catch(...) {} - - // Check if time format changed - std::string current_use_12h_time_format = wxGetApp().app_config->get("use_12h_time_format"); - m_use_12h_time_format_changed = (m_original_use_12h_time_format != current_use_12h_time_format); - event.Skip(); - }); + }); +} + +PreferencesDialog::~PreferencesDialog() +{ + m_radio_group.DeleteContents(true); } void PreferencesDialog::create() { app_config = get_app_config(); m_backup_interval_time = app_config->get("backup_interval"); + m_original_use_12h_time_format = app_config->get_bool("use_12h_time_format"); // set icon for dialog - std::string icon_path = (boost::format("%1%/images/BambuStudioTitle.ico") % resources_dir()).str(); + const std::string icon_path = (boost::format("%1%/images/BambuStudioTitle.ico") % resources_dir()).str(); SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO)); - SetSizeHints(wxDefaultSize, wxDefaultSize); - + SetSizeHints(wxDefaultSize, wxDefaultSize); // ensure dialog is resizable + SetBackgroundColour(DESIGN_CONTENT_BG_COLOR); + + // The page display and navigation handler. + m_page_book = new Tabbook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, nullptr, wxBK_LEFT); + + // Customize the navigation button list sidebar control. + auto *ctrl = m_page_book->GetBtnsListCtrl(); + ctrl->SetBackgroundColour(DESIGN_SIDEBAR_BG_COLOR); + // make scrollable vertically + ctrl->SetScrollRate(0, FromDIP(10)); + // option to have vertical scrollbar use allotted sidebar space instead of expanding the sidebar area horizontally + // ctrl->SetUseClientAreaForScrollbar(true); + // Add top padding above buttons. + ctrl->SetListTopPadding(DESIGN_PAGE_PADDING); + // Reduce button padding around text label. + ctrl->SetPaddingSize({ FromDIP(20), FromDIP(16) }); + // Use natural button size by default (we'll adjust width after adding pages). + ctrl->SetButtonSize(wxDefaultSize); + // No button borders + ctrl->SetButtonBorderWidth(0); + // Make selected or hovered button background match the content page background color, otherwise same as sidebar + ctrl->SetButtonBGColors(StateColor( + std::make_pair(DESIGN_CONTENT_BG_COLOR, (int)StateColor::Checked), + std::make_pair(DESIGN_CONTENT_BG_COLOR, (int)StateColor::Hovered), + std::make_pair(DESIGN_SIDEBAR_BG_COLOR, (int)StateColor::Normal) + )); + + // Add all our pages. + m_page_book->Add(create_general_page(), _L("General"), _L("Settings affecting basic application behaviors.")); + m_page_book->Add(create_online_page(), _L("Online"), _L("Configure features requiring an Internet connection.")); + m_page_book->Add(create_projects_page(), _L("Projects"), _L("Settings related to working with projects.")); + m_page_book->Add(create_files_page(), _L("File Handling"), _L("Settings related to opening and importing files of various types, including 3MF projects.")); + m_page_book->Add(create_3Dview_page(), _L("3D View"), _L("The \"Prepare\" and \"Preview\" 3D workspace and graphics settings.")); + m_page_book->Add(create_advanced_page(), _L("Advanced"), _L("Other settings for more advanced control over program behavior.")); + + // Set nav button width to increase padding on the right side, with constraint in case of some erroneous translation. + ctrl->SetButtonSize({ std::min(int(ctrl->GetBestSize().x * 1.33), FromDIP(240)), -1 }); + + // Final layout auto main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->Add(m_page_book, 1, wxEXPAND /* | wxTOP, FromDIP(10) */); + SetSizer(main_sizer); - m_scrolledWindow = new MyscrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL); - m_scrolledWindow->SetScrollRate(5, 5); - - m_sizer_body = new wxBoxSizer(wxVERTICAL); - - auto m_top_line = new wxPanel(m_scrolledWindow, wxID_ANY, wxDefaultPosition, wxSize(DESIGN_RESOUTION_PREFERENCES.x, 1), wxTAB_TRAVERSAL); - m_top_line->SetBackgroundColour(DESIGN_GRAY400_COLOR); + // Resize and position the dialog. + set_window_size(); + CenterOnParent(); + // update for dark mode + wxGetApp().UpdateDlgDarkUI(this); - m_sizer_body->Add(m_top_line, 0, wxEXPAND, 0); + // Ensure static selection ID is valid after all pages have been added. + if (m_last_selected_page < 0 || m_last_selected_page >= (int)m_page_book->GetPageCount()) + m_last_selected_page = 0; - auto general_page = create_general_page(); -#if !BBL_RELEASE_TO_PUBLIC - auto debug_page = create_debug_page(); -#endif + // track page changes + m_page_book->Bind(wxEVT_BOOKCTRL_PAGE_CHANGED, &PreferencesDialog::on_menu_item_selected, this); - m_sizer_body->Add(0, 0, 0, wxTOP, FromDIP(28)); - m_sizer_body->Add(general_page, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(38)); -#if !BBL_RELEASE_TO_PUBLIC - m_sizer_body->Add(debug_page, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(38)); -#endif - m_sizer_body->Add(0, 0, 0, wxBOTTOM, FromDIP(28)); - m_scrolledWindow->SetSizerAndFit(m_sizer_body); + // set current page + m_page_book->SetSelection((size_t)m_last_selected_page); +} - main_sizer->Add(m_scrolledWindow, 1, wxEXPAND); +void PreferencesDialog::on_menu_item_selected(wxBookCtrlEvent &ev) +{ + m_last_selected_page = m_page_book->GetSelection(); + // Move focus from sidebar nav control to the current page. + if (wxWindow *pg = m_page_book->GetCurrentPage()) + pg->SetFocus(); + ev.Skip(); +} - SetSizer(main_sizer); +void PreferencesDialog::set_window_size() +{ Layout(); Fit(); - int m_screen_height = std::numeric_limits::max(); - int count = wxDisplay::GetCount(); - for (int i = 0; i < count; ++i) { - wxDisplay display(i); - wxRect rect = display.GetGeometry(); - m_screen_height = std::min(m_screen_height, rect.GetHeight()); - } - if (m_screen_height == std::numeric_limits::max()) - m_screen_height = wxGetDisplaySize().GetY(); - this->SetSize(this->GetSize().GetX() + FromDIP(40), m_screen_height * 0.7); - - CenterOnParent(); - wxPoint start_pos = this->GetPosition(); - if (start_pos.y < 0) { this->SetPosition(wxPoint(start_pos.x, 0)); } - - //select first - auto event = wxCommandEvent(EVT_PREFERENCES_SELECT_TAB); - event.SetInt(0); - event.SetEventObject(this); - wxPostEvent(this, event); + // Make sure dialog window size doesn't overflow the display it is, or will be, shown on. + int disp_idx = wxDisplay::GetFromWindow(this); + if (disp_idx == wxNOT_FOUND && GetParent()) + disp_idx = wxDisplay::GetFromWindow(GetParent()); + // Use default (primary) display if we couldn't find the specific one (unlikely). + const wxDisplay disp = disp_idx == wxNOT_FOUND ? wxDisplay() : wxDisplay(disp_idx); + const wxSize disp_sz = disp.GetClientArea().GetSize(); + const wxSize this_sz = GetBestSize(); + // Do not stretch the dialog if it will already fit at its natural size. + SetSize(std::min(this_sz.x + FromDIP(40), disp_sz.x - FromDIP(20)), std::min(this_sz.y, disp_sz.y - FromDIP(40))); } -PreferencesDialog::~PreferencesDialog() +void PreferencesDialog::on_dpi_changed(const wxRect &/* suggested_rect */) { - m_radio_group.DeleteContents(true); - m_hash_selector.clear(); -} - -void PreferencesDialog::on_dpi_changed(const wxRect &suggested_rect) { - for (auto item : m_button_list) { - item.second->Rescale(); - item.second->SetMinSize(PreferenceBtnSize); - item.second->SetCornerRadius(FromDIP(12)); + for (auto item : std::as_const(m_button_list)) { + item->Rescale(); + item->SetMinSize(FromDIP(DESIGN_BUTTON_SIZE)); + item->SetCornerRadius(FromDIP(DESIGN_BUTTON_RADIUS)); } - for (auto item : m_checkbox_list) { - item.second->Rescale(); + for (auto item : std::as_const(m_checkbox_list)) { + item->Rescale(); } - for (auto item : m_radiobox_list) { - item.second->Rescale(); + for (auto rs : std::as_const(m_radio_group)) { + if (RadioBox *rb = rs->m_radiobox) + rb->Rescale(); } - for (auto item : m_combobox_list) { - item.second->Rescale(); + for (auto item : std::as_const(m_combobox_list)) { + item->Rescale(); } + m_page_book->Rescale(); this->Refresh(); - Layout(); - Fit(); - int displayIndex = wxDisplay::GetFromWindow(this); - if (displayIndex != wxNOT_FOUND) { - wxDisplay display(displayIndex); - wxRect screenRect = display.GetGeometry(); - if (m_screen_height != screenRect.GetHeight()) { - m_screen_height = screenRect.GetHeight(); - this->SetSize(this->GetSize().GetX() + FromDIP(40), m_screen_height * 0.7); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " The display screen has switched"; - } - } + set_window_size(); } -void PreferencesDialog::Split(const std::string &src, const std::string &separator, std::vector &dest) -{ - std::string str = src; - std::string substring; - std::string::size_type start = 0, index; - dest.clear(); - index = str.find_first_of(separator, start); - do { - if (index != std::string::npos) { - substring = str.substr(start, index - start); - dest.push_back(substring); - start = index + separator.size(); - index = str.find(separator, start); - if (start == std::string::npos) break; - } - } while (index != std::string::npos); +bool PreferencesDialog::use_12h_time_format_changed() const { + return app_config->get_bool("use_12h_time_format") != m_original_use_12h_time_format; +} - substring = str.substr(start); - dest.push_back(substring); +ScrolledPanel* PreferencesDialog::create_book_page(wxWindow *parent /* = nullptr */) const +{ + if (!parent) + parent = m_page_book; + return new ScrolledPanel(parent, parent == m_page_book); } -wxWindow* PreferencesDialog::create_general_page() +wxWindow* PreferencesDialog::create_general_page(wxWindow *parent) { - auto page = new wxWindow(m_scrolledWindow, wxID_ANY); - page->SetBackgroundColour(*wxWHITE); - wxBoxSizer *sizer_page = new wxBoxSizer(wxVERTICAL); - - auto title_general_settings = create_item_title(_L("General Settings"), page, _L("General Settings")); - auto available_translations = wxTranslations::Get()->GetAvailableTranslations(SLIC3R_APP_KEY); - std::vector language_infos; - language_infos.emplace_back(wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH)); - for (size_t i = 0; i < available_translations.GetCount(); ++i) { - const wxLanguageInfo *available_lan = wxLocale::FindLanguageInfo(available_translations[i]); - if (available_lan == nullptr) continue; - - for (auto si = 0; si < s_supported_languages.size(); si++) { - auto* supported_lan = wxLocale::GetLanguageInfo(s_supported_languages[si]); - if (available_lan->CanonicalName == supported_lan->CanonicalName) { - language_infos.emplace_back(supported_lan); - break; + auto page = create_book_page(parent); + + page->AddTitle(_L("General Settings"), _L("Settings affecting basic application behaviors.")); + + // Language selector + const auto translations = wxTranslations::Get()->GetAvailableTranslations(SLIC3R_APP_KEY); + std::vector language_infos { wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH) }; + for (size_t i = 0, e = translations.GetCount(); i < e; ++i) { + if (const wxLanguageInfo *available_lan = wxLocale::FindLanguageInfo(translations[i])) { + for (size_t si = 0, se = s_supported_languages.size(); si < se; ++si) { + if (auto* supported_lan = wxLocale::GetLanguageInfo(s_supported_languages[si])){ + if (available_lan->CanonicalName == supported_lan->CanonicalName) { + language_infos.emplace_back(supported_lan); + break; + } + } } } - //if (langinfo != nullptr) language_infos.emplace_back(langinfo); } sort_remove_duplicates(language_infos); std::sort(language_infos.begin(), language_infos.end(), [](const wxLanguageInfo *l, const wxLanguageInfo *r) { return l->Description < r->Description; }); - auto item_language = create_item_language_combobox(_L("Language"), page, _L("Language"), 50, "language", language_infos); + page->Add( create_item_language_combobox(_L("Language"), page, _L("Language"), "language", language_infos) ); - std::vector Regions = {_L("Asia-Pacific"), _L("Chinese Mainland"), _L("Europe"), _L("North America"), _L("Others")}; - auto item_region= create_item_region_combobox(_L("Login Region"), page, _L("Login Region"), Regions); + static const std::vector Units = {_L("Metric") + " (mm, g)", _L("Imperial") + " (in, oz)"}; + page->Add( create_item_combobox(_L("Units"), page, _L("Units"), "use_inches", Units,{"0","1"}) ); - std::vector Units = {_L("Metric") + " (mm, g)", _L("Imperial") + " (in, oz)"}; - auto item_currency = create_item_combobox(_L("Units"), page, _L("Units"), "use_inches", Units,{"0","1"}); - auto item_12h_time_format = create_item_checkbox(_L("Use 12-hour time format"), page, _L("Display time in 12-hour format with AM/PM instead of 24-hour format"), 50, "use_12h_time_format"); - auto item_single_instance = create_item_checkbox(_L("Keep only one Bambu Studio instance"), page, -#if __APPLE__ - _L("On OSX there is always only one instance of app running by default. However it is allowed to run multiple instances " - "of same app from the command line. In such case this settings will allow only one instance."), -#else - _L("If this is enabled, when starting Bambu Studio and another instance of the same Bambu Studio is already running, that instance will be reactivated instead."), -#endif - 50, "single_instance"); - - auto item_auto_transfer_when_switch_preset = create_item_checkbox(_L("Automatically transfer modified value when switching process and filament presets"), page,_L("After closing, a popup will appear to ask each time"), 50, "auto_transfer_when_switch_preset"); - auto item_bed_type_follow_preset = create_item_checkbox(_L("Auto plate type"), page, - _L("Studio will remember build plate selected last time for certain printer model."), 50, - "user_bed_type"); - std::vector FlushOptionLabels = {_L("All"),_L("Color change"),_L("Disabled")}; - std::vector FlushOptionValues = { "all","color change","disabled" }; - auto item_auto_flush = create_item_combobox(_L("Auto Flush"), page, _L("Auto calculate flush volumes"), "auto_calculate_flush", FlushOptionLabels, FlushOptionValues); - //auto item_hints = create_item_checkbox(_L("Show \"Tip of the day\" notification after start"), page, _L("If enabled, useful hints are displayed at startup."), 50, "show_hints"); - auto item_multi_machine = create_item_checkbox(_L("Multi-device Management(Take effect after restarting Studio)."), page, _L("With this option enabled, you can send a task to multiple devices at the same time and manage multiple devices."), 50, "enable_multi_machine"); - auto item_fila_manager = create_item_checkbox(_L("Filament Manager") + " (" + _L("Take effect after restarting Studio") + ")", page, + page->Add( create_item_checkbox(_L("Use 12-hour time format"), page, _L("Display time in 12-hour format with AM/PM instead of 24-hour format"), "use_12h_time_format") ); + + page->Add( + create_item_checkbox(_L("Keep only one Bambu Studio instance"), page, #if __APPLE__ - _L("The Filament Manager is turned off by default on macOS because compatibility issues on some systems may cause the application to become unresponsive."), + _L("On OSX there is always only one instance of app running by default. However it is allowed to run multiple instances " + "of same app from the command line. In such case this settings will allow only one instance."), #else - wxEmptyString, -#endif - 50, FilaManagerEnabledConfigKey); - auto item_step_mesh_setting = create_item_checkbox(_L("Show the step mesh parameter setting dialog."), page, _L("If enabled,a parameter settings dialog will appear during STEP file import."), 50, "enable_step_mesh_setting"); - auto item_beta_version_update = create_item_checkbox(_L("Support beta version update."), page, _L("With this option enabled, you can receive beta version updates."), 50, "enable_beta_version_update"); - auto item_mix_print_high_low_temperature = create_item_checkbox(_L("Remove the restriction on mixed printing of high and low temperature filaments."), page, _L("With this option enabled, you can print materials with a large temperature difference together."), 50, "enable_high_low_temp_mixed_printing"); - auto item_camera_fullscreen_active_monitor_only = create_item_checkbox(_L("Open full screen camera view on active monitor only."), page, _L("When enabled, the camera full screen view opens only on the monitor that contains Bambu Studio."), 50, "camera_fullscreen_active_monitor_only"); - auto item_restore_hide_pop_ups = create_item_button(_L("Clear my choice for synchronizing printer preset after loading the file."), _L("Clear"), page, {}, []() { - wxGetApp().app_config->erase("app", "sync_after_load_file_show_flag"); - }); - auto item_restore_hide_3mf_info = create_item_button(_L("Clear my choice for Load 3mf dialog settings."), _L("Clear"), page, {}, []() { - wxGetApp().app_config->erase("app", "skip_non_bambu_3mf_warning"); - }); - auto item_restore_post_process_script_choice = create_item_button(_L("Clear my choice for executing post-processing scripts."), _L("Clear"), page, _L("Show the security warning dialog again before slicing when post-processing scripts are configured."), []() { - wxGetApp().app_config->erase("app", "post_process_script_choice"); - if (wxGetApp().plater()) - wxGetApp().plater()->reset_post_process_script_choice(); - }); - auto item_restore_support_recommend_dlg = create_item_button(_L("Clear my choice for disabling support parameter recommendation."), _L("Clear"), page, {}, []() { - wxGetApp().app_config->set("show_support_recommend_dialog", "true"); - }); - auto _3d_settings = create_item_title(_L("3D Settings"), page, _L("3D Settings")); - auto item_mouse_zoom_settings = create_item_checkbox(_L("Zoom to mouse position"), page, - _L("Zoom in towards the mouse pointer's position in the 3D view, rather than the 2D window center."), 50, - "zoom_to_mouse"); - auto item_show_shells_in_preview_settings = create_item_checkbox(_L("Always show shells in preview"), page, - _L("Always show shells or not in preview view tab. If you change this value, you should reslice."), 50, - "show_shells_in_preview"); - auto item_import_single_svg_and_split = create_item_checkbox(_L("Import a single SVG and split it"), page, - _L("Import a single SVG and then split it to several parts."), 50, - "import_single_svg_and_split"); - auto item_gamma_correct_in_import_obj = create_item_checkbox(_L("Enable gamma correction for the imported obj file"), page, - _L("Perform gamma correction on color after importing the obj model."), 50, - "gamma_correct_in_import_obj"); - auto item_enable_record_gcodeviewer_option_item = create_item_checkbox(_L("Remember last used color scheme"), page, - _L("When enabled, the last used color scheme (e.g., Line Type, Speed) will be automatically applied on next startup."), 50, - "enable_record_gcodeviewer_option_item"); - - std::vector assemble_view_preview_options = { _L("Auto"), _L("Open"), _L("Close") }; - auto enable_assemble_view_preview_settings = create_item_combobox(_L("Display overview"), page, - _L("Display overview"), "enable_assemble_view_preview", - assemble_view_preview_options, { "Auto", "Open", "Close" }, - [](int idx) { - wxGetApp().app_config->set("enable_assemble_view_preview", idx == 0 ? "Auto" : idx == 1 ? "Open" : "Close"); - if (wxGetApp().app_config->get("enable_assemble_view_preview") == "Auto") { - wxGetApp().app_config->set_bool("enable_bvh", true); - } else if (wxGetApp().app_config->get("enable_assemble_view_preview") == "Open") { - wxGetApp().app_config->set_bool("enable_bvh", false); - } - },FromDIP(150), FromDIP(120)); -#if !BBL_RELEASE_TO_PUBLIC - auto show_assembly_bvh_bounds_settings = create_item_checkbox(_L("Show assembly BVH primary bounds"), page, - _L("Display the BVH primary bounding box wireframe in assembly view."), 50, - "show_assembly_bvh_bounds"); + _L("If this is enabled, when starting Bambu Studio and another instance of the same Bambu Studio is already running, that instance will be reactivated instead."), #endif - auto enable_lod_settings = create_item_checkbox(_L("Improve rendering performance by lod"), page, - _L("Improved rendering performance under the scene of multiple plates and many models."), 50, - "enable_lod"); - - std::vector toolbar_style = { _L("Collapsible"), _L("Uncollapsible") }; - auto item_toolbar_style = create_item_combobox(_L("Toolbar Style"), page, _L("Toolbar Style"), "toolbar_style", toolbar_style, { "0","1" }, [](int idx)->void { - const auto& p_ogl_manager = wxGetApp().get_opengl_manager(); - p_ogl_manager->set_toolbar_rendering_style(idx); - }); - - auto enable_advanced_gcode_viewer = create_item_checkbox(_L("Enable advanced gcode viewer"), page, - _L("Enable advanced gcode viewer."), 50, - "enable_advanced_gcode_viewer_"); - - float range_min = 1.0, range_max = 2.5; - auto item_grabber_size_settings = create_item_range_input(_L("Grabber scale"), page, - _L("Set grabber size for move,rotate,scale tool.") + _L("Value range") + ":[" + std::to_string(range_min) + "," + - std::to_string(range_max) + - "]","grabber_size_factor", range_min, range_max, 1, - [](wxString value) { - double d_value = 0; - if (value.ToDouble(&d_value)) { - GLGizmoBase::Grabber::GrabberSizeFactor = d_value; - } - }); - range_min = 0.0f; - range_max = 150.0f; - auto item_tooltip_offset_size_settings = create_item_range_two_input(_L("Tooltip offset"), page, - _L("Set tooltip offset for different mouse size.") + _L("Value range") + ":[" + std::to_string(range_min) + "," + - std::to_string(range_max) + "]", - "3d_middle_tooltip_offset_x", "3d_middle_tooltip_offset_y", range_min, range_max, 1, nullptr, nullptr); - auto title_presets = create_item_title(_L("Presets"), page, _L("Presets")); - auto item_user_sync = create_item_checkbox(_L("Auto sync user presets(Printer/Filament/Process)"), page, _L("If enabled, auto sync user presets with cloud after Bambu Studio startup or presets modified."), 50, "sync_user_preset"); - auto item_system_sync = create_item_checkbox(_L("Auto check for system presets updates"), page, _L("If enabled, auto check whether there are system presets updates after Bambu Studio startup."), 50, "sync_system_preset"); + "single_instance" + )); #ifdef _WIN32 - auto title_associate_file = create_item_title(_L("Associate Files To Bambu Studio"), page, _L("Associate Files To Bambu Studio")); - - // associate file - auto item_associate_3mf = create_item_checkbox(_L("Associate .3mf files to Bambu Studio"), page, - _L("If enabled, sets Bambu Studio as default application to open .3mf files"), 50, "associate_3mf"); - auto item_associate_stl = create_item_checkbox(_L("Associate .stl files to Bambu Studio"), page, - _L("If enabled, sets Bambu Studio as default application to open .stl files"), 50, "associate_stl"); - auto item_associate_step = create_item_checkbox(_L("Associate .step/.stp files to Bambu Studio"), page, - _L("If enabled, sets Bambu Studio as default application to open .step files"), 50, "associate_step"); + page->Add( create_item_darkmode_checkbox(_L("Enable dark mode"), page, _L("Enable dark mode"), "dark_color_mode") ); #endif // _WIN32 - auto title_modelmall = create_item_title(_L("Online Models"), page, _L("Online Models")); - // auto item_backup = create_item_switch(_L("Backup switch"), page, _L("Backup switch"), "units"); - auto item_modelmall = create_item_checkbox(_L("Show online staff-picked models on the home page"), page, _L("Show online staff-picked models on the home page"), 50, "staff_pick_switch"); + //page->Add( create_item_checkbox(_L("Show \"Tip of the day\" notification after start"), page, _L("If enabled, useful hints are displayed at startup."), "show_hints") ); - auto item_show_history = create_item_checkbox(_L("Show history on the home page"), page, _L("Show history on the home page"), 50, "show_print_history"); + page->Add( + create_item_checkbox( + _L("Enable Filament Manager (Takes effect after restarting Studio)"), page, +#if __APPLE__ + _L("The Filament Manager is turned off by default on macOS because compatibility issues on some systems may cause the application to become unresponsive."), +#else + _L("Enable or disable the Filament Manager feature."), +#endif + FilaManagerEnabledConfigKey + )); - std::vector air_temp_timing_list = {_L("Reminder During Slicing"), _L("Automatic mode")}; - std::vector air_temp_timing_value_list = {"slicing", "auto"}; + page->Add( create_item_downloads(page, "download_path") ); - auto title_project = create_item_title(_L("Project"), page, ""); - auto item_max_recent_count = create_item_input(_L("Maximum recent projects"), "", page, _L("Maximum count of recent projects"), "max_recent_count", [](wxString value) { - long max = 0; - if (value.ToLong(&max)) - wxGetApp().mainframe->set_max_recent_count(max); - }); - auto item_save_choise = create_item_button(_L("Clear my choice on the unsaved projects."), _L("Clear"), page, {}, []() { - wxGetApp().app_config->set("save_project_choise", ""); - }); - // auto item_backup = create_item_switch(_L("Backup switch"), page, _L("Backup switch"), "units"); - auto item_gcodes_warning = create_item_checkbox(_L("No warnings when loading 3MF with modified G-codes"), page,_L("No warnings when loading 3MF with modified G-codes"), 50, "no_warn_when_modified_gcodes"); - auto item_backup = create_item_checkbox(_L("Auto-Backup"), page,_L("Backup your project periodically for restoring from the occasional crash."), 50, "backup_switch"); - auto item_backup_interval = create_item_backup_input(_L("every"), page, _L("The peroid of backup in seconds."), "backup_interval"); + // Embed Device properties here + page->Add( create_device_page(page), DESIGN_SECTION_SPACING ); - //downloads - auto title_downloads = create_item_title(_L("Downloads"), page, _L("Downloads")); - auto item_downloads = create_item_downloads(page,50,"download_path"); + return page->Finalize(); +} - auto title_media = create_item_title(_L("Media"), page, _L("Media")); - auto item_auto_stop_liveview = create_item_checkbox(_L("Keep liveview when printing."), page, _L("By default, Liveview will pause after 15 minutes of inactivity on the computer. Check this box to disable this feature during printing."), 50, "auto_stop_liveview"); +wxWindow* PreferencesDialog::create_device_page(wxWindow *parent) +{ + auto page = create_book_page(parent); + + page->AddTitle(_L("Device"), _L("Options for Device management.")); + + page->Add( + create_item_checkbox( + _L("Keep liveview when printing"), page, + _L("By default, Liveview will pause after 15 minutes of inactivity on the computer. Check this box to disable this feature during printing."), + "liveview" + )); + page->Add( + create_item_checkbox( + _L("Open full screen camera view on active monitor only"), page, + _L("When enabled, the camera full screen view opens only on the monitor that contains Bambu Studio."), + "camera_fullscreen_active_monitor_only" + )); + page->Add( + create_item_checkbox( + _L("Multi-device Management (Takes effect after restarting Studio)"), page, + _L("With this option enabled, you can send a task to multiple devices at the same time and manage multiple devices."), + "enable_multi_machine" + )); + + return page->Finalize(); +} - //dark mode -#ifdef _WIN32 - auto title_darkmode = create_item_title(_L("Dark Mode"), page, _L("Dark Mode")); - auto item_darkmode = create_item_darkmode_checkbox(_L("Enable dark mode"), page,_L("Enable dark mode"), 50, "dark_color_mode"); -#endif +wxWindow* PreferencesDialog::create_online_page(wxWindow *parent) +{ + auto page = create_book_page(parent); + + page->AddTitle(_L("Online Features")); + + static const std::vector Regions = {_L("Asia-Pacific"), _L("Chinese Mainland"), _L("Europe"), _L("North America"), _L("Others")}; + page->Add( create_item_region_combobox(_L("Login Region"), page, _L("Login Region"), Regions) ); + ComboBox *combo_region = m_combobox_list[m_combobox_list.size()-1]; + + page->Add( + create_item_checkbox( + _L("Auto sync user presets(Printer/Filament/Process)"), page, + _L("If enabled, auto sync user presets with cloud after Bambu Studio startup or presets modified."), + "sync_user_preset" + )); + page->Add( + create_item_checkbox( + _L("Auto check for system presets updates"), page, + _L("If enabled, auto check whether there are system presets updates after Bambu Studio startup."), + "sync_system_preset" + )); + page->Add( + create_item_checkbox( + _L("Support beta version update"), page, + _L("With this option enabled, you can receive beta version updates."), + "enable_beta_version_update" + )); -#if 0 - auto title_filament_group = create_item_title(_L("Filament Grouping"), page, _L("Filament Grouping")); - //temporarily disable it - //auto item_ignore_ext_filament = create_item_checkbox(_L("Ignore ext filament when auto grouping"), page, _L("Ignore ext filament when auto grouping"), 50, "ignore_ext_filament_when_group"); - auto item_pop_up_filament_map_dialog = create_item_checkbox(_L("Pop up to select filament grouping mode"), page, _L("Pop up to select filament grouping mode"), 50, "pop_up_filament_map_dialog"); -#endif +#ifdef _WIN32 + page->Add( create_item_checkbox(_L("Auto-fill previously logged-in accounts"), page, _L(""), "webview_auto_fill") ); +#endif // _WIN32 - auto title_user_experience = create_item_title(_L("User Experience"), page, _L("User Experience")); - auto item_priv_policy = create_item_checkbox(_L("Join the User Experience Improvement Program."), page, "", 50, "privacyuse"); + auto item_join_program = create_item_checkbox(_L("Join the User Experience Improvement Program."), page, "", "firstguide"); auto* hyperlink = new Label(page, wxString::FromUTF8(_CTX_utf8(L_CONTEXT("Learn more", "Preferences"), "Preferences"))); hyperlink->SetFont(Label::Head_13); hyperlink->SetForegroundColour(wxColour("#0078D4")); @@ -1488,303 +1203,431 @@ wxWindow* PreferencesDialog::create_general_page() UxProgramTermsDialog dlg(this); dlg.ShowModal(); }); - item_priv_policy->Add(hyperlink, 0, wxALIGN_CENTER, 0); - -#ifdef _WIN32 - auto item_webview_auto_fill = create_item_checkbox(_L("Auto-fill previously logged-in accounts."), page, - _L(""), 50, - "webview_auto_fill"); -#endif - - auto title_develop_mode = create_item_title(_L("Develop Mode"), page, _L("Develop Mode")); - auto item_develop_mode = create_item_checkbox(_L("Develop mode"), page, _L("Develop mode"), 50, "developer_mode"); - auto item_skip_ams_blacklist_check = create_item_checkbox(_L("Skip AMS blacklist check"), page, _L("Skip AMS blacklist check"), 50, "skip_ams_blacklist_check"); - - sizer_page->Add(title_general_settings, 0, wxEXPAND, 0); - sizer_page->Add(item_language, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_region, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_currency, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_auto_flush, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_single_instance, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_bed_type_follow_preset, 0, wxTOP, FromDIP(3)); - //sizer_page->Add(item_hints, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_multi_machine, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_fila_manager, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_12h_time_format, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_step_mesh_setting, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_beta_version_update, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_auto_transfer_when_switch_preset, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_mix_print_high_low_temperature, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_camera_fullscreen_active_monitor_only, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_restore_hide_pop_ups, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_restore_hide_3mf_info, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_restore_support_recommend_dlg, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_restore_post_process_script_choice, 0, wxTOP, FromDIP(3)); - sizer_page->Add(_3d_settings, 0, wxTOP | wxEXPAND, FromDIP(20)); - sizer_page->Add(item_mouse_zoom_settings, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_show_shells_in_preview_settings, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_import_single_svg_and_split, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_gamma_correct_in_import_obj, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_enable_record_gcodeviewer_option_item, 0, wxTOP, FromDIP(3)); - sizer_page->Add(enable_assemble_view_preview_settings, 0, wxTOP, FromDIP(3)); -#if !BBL_RELEASE_TO_PUBLIC - sizer_page->Add(show_assembly_bvh_bounds_settings, 0, wxTOP, FromDIP(3)); -#endif - - sizer_page->Add(enable_lod_settings, 0, wxTOP, FromDIP(3)); - sizer_page->Add(enable_advanced_gcode_viewer, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_toolbar_style, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_grabber_size_settings, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_tooltip_offset_size_settings, 0, wxTOP, FromDIP(3)); - sizer_page->Add(title_presets, 0, wxTOP | wxEXPAND, FromDIP(20)); - sizer_page->Add(item_user_sync, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_system_sync, 0, wxTOP, FromDIP(3)); -#ifdef _WIN32 - sizer_page->Add(title_associate_file, 0, wxTOP| wxEXPAND, FromDIP(20)); - sizer_page->Add(item_associate_3mf, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_associate_stl, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_associate_step, 0, wxTOP, FromDIP(3)); -#endif // _WIN32 - auto item_title_modelmall = sizer_page->Add(title_modelmall, 0, wxTOP | wxEXPAND, FromDIP(20)); - auto item_item_modelmall = sizer_page->Add(item_modelmall, 0, wxTOP, FromDIP(3)); - auto item_item_show_history = sizer_page->Add(item_show_history, 0, wxTOP, FromDIP(3)); - - auto update_modelmall = [this, item_title_modelmall, item_item_modelmall, item_item_show_history](wxEvent &e) { + item_join_program->Add(hyperlink, 0, wxALIGN_CENTER, 0); + page->Add(item_join_program); + + + auto item_hp_title = page->AddTitle(_L("Home Page")); + + auto item_modelmall = page->Add( + create_item_checkbox( + _L("Show online staff-picked models"), page, + _L("Show a selection of staff-picked models from MakerWorld on the home page."), + "staff_pick_switch" + )); + auto item_show_history = page->Add( + create_item_checkbox( + _L("Show MakerWorld history"), page, + _L("Show recently printed files from your MakerWorld history on the home page."), + "show_print_history" + )); + + // Hide online model features in unsupported regions + // NOTE: Do not hide `item_hp_title` if the "Home Page" section gets other options later. + auto update_modelmall = [=](wxEvent &) { bool has_model_mall = wxGetApp().has_model_mall(); - item_title_modelmall->Show(has_model_mall); - item_item_modelmall->Show(has_model_mall); - item_item_show_history->Show(has_model_mall); + item_modelmall->Show(has_model_mall); + item_show_history->Show(has_model_mall); + item_hp_title->Show(has_model_mall); Layout(); Fit(); }; - wxCommandEvent eee(wxEVT_COMBOBOX); + wxCommandEvent eee; update_modelmall(eee); - item_region->GetItem(size_t(2))->GetWindow()->Bind(wxEVT_COMBOBOX, update_modelmall); - - - sizer_page->Add(0, 0, 0, wxTOP, FromDIP(20)); - sizer_page->Add(title_project, 0, wxTOP| wxEXPAND, FromDIP(20)); - sizer_page->Add(item_max_recent_count, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_save_choise, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_gcodes_warning, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_backup, 0, wxTOP,FromDIP(3)); - item_backup->Add(item_backup_interval, 0, wxLEFT, 0); - - sizer_page->Add(title_downloads, 0, wxTOP | wxEXPAND, FromDIP(20)); - sizer_page->Add(item_downloads, 0, wxEXPAND, FromDIP(3)); + combo_region->Bind(wxEVT_COMBOBOX, update_modelmall); - sizer_page->Add(title_media, 0, wxTOP| wxEXPAND, FromDIP(20)); - sizer_page->Add(item_auto_stop_liveview, 0, wxEXPAND, FromDIP(3)); + return page->Finalize(); +} -#ifdef _WIN32 - sizer_page->Add(title_darkmode, 0, wxTOP | wxEXPAND, FromDIP(20)); - sizer_page->Add(item_darkmode, 0, wxEXPAND, FromDIP(3)); -#endif +wxWindow* PreferencesDialog::create_projects_page(wxWindow *parent) +{ + auto page = create_book_page(parent); + + page->AddTitle(_L("Projects"), _L("Settings affecting program behavior when working with projects.")); + + page->Add( + create_item_input( + _L("Maximum recent projects"), "", page, + _L("Maximum count of recent projects"), + "max_recent_count", + [](const wxString &value) { + long max = 0; + if (value.ToLong(&max)) + wxGetApp().mainframe->set_max_recent_count(max); + }, + -1 + )); + + static const std::vector FlushOptionLabels = {_L("All"),_L("Color change"),_L("Disabled")}; + static const std::vector FlushOptionValues = { "all","color change","disabled" }; + page->Add( + create_item_combobox( + _L("Auto Flush"), page, + _L("Auto calculate flush volumes"), + "auto_calculate_flush", FlushOptionLabels, FlushOptionValues + )); + + page->Add( + create_item_checkbox( + _L("Remember plate type"), page, + _L("Studio will remember build plate selected last time for certain printer model."), + "user_bed_type" + )); + page->Add( + create_item_checkbox( + _L("Automatically transfer modified value when switching process and filament presets"), page, + _L("After closing, a popup will appear to ask each time"), + "auto_transfer_when_switch_preset" + )); + page->Add( + create_item_checkbox( + _L("Remove the restriction on mixed printing of high and low temperature filaments"), page, + _L("With this option enabled, you can print materials with a large temperature difference together."), + "enable_high_low_temp_mixed_printing" + )); + + page->Add( + create_item_checkbox( + _L("Show recommendations dialog when a dedicated support filament is detected"), page, + _L("With this option enabled, the program will prompt to change support generation options when a dedicated support filament is first detected."), + "show_support_recommend_dialog" + )); + + auto item_backup = create_item_checkbox( + _L("Auto-Backup"), page, + _L("Backup your project periodically for restoring from the occasional crash."), + "backup_switch" + ); + item_backup->Add(create_item_backup_input(_L("every"), page, _L("The peroid of backup in seconds."))); + page->Add(item_backup); + + static const std::vector unsaved_labels = {_L("Prompt to Save"), _L("Always Save"), _L("Never Save")}; + static const std::vector unsaved_values = { "", "yes", "no" }; + page->Add( + create_item_combobox( + _L("When closing an unsaved project:"), page, + _L("Select what to do when a project with unsaved changes is being closed."), + "save_project_choise", unsaved_labels, unsaved_values, + nullptr, -1 + )); + + static const std::vector post_proc_labels = {_L("Always Ask"), _L("Always Execute"), _L("Never Execute")}; + static const std::vector post_proc_values = { "", "execute", "do_not_execute" }; + page->Add( + create_item_combobox( + _L("When slicing a project containing post-processing scripts:"), page, + _L("\"Always Ask\" will show the security warning dialog before slicing when post-processing scripts are configured."), + "post_process_script_choice", post_proc_labels, post_proc_values, + [](int sel) { + if (sel == 0) { + wxGetApp().app_config->erase("app", "post_process_script_choice"); + if (wxGetApp().plater()) + wxGetApp().plater()->reset_post_process_script_choice(); + } + }, + -1 + )); #if 0 - sizer_page->Add(title_filament_group, 0, wxTOP | wxEXPAND, FromDIP(20)); - //sizer_page->Add(item_ignore_ext_filament, 0, wxEXPAND, FromDIP(3)); - sizer_page->Add(item_pop_up_filament_map_dialog, 0, wxEXPAND, FromDIP(3)); + //temporarily disable it + page->AddTitle(_L("Filament Grouping")); + //page->Add(create_item_checkbox(_L("Ignore ext filament when auto grouping"), page, _L("Ignore ext filament when auto grouping"), "ignore_ext_filament_when_group")); + page->Add( create_item_checkbox(_L("Pop up to select filament grouping mode"), page, _L("Pop up to select filament grouping mode"), "pop_up_filament_map_dialog") ); #endif - sizer_page->Add(title_user_experience, 0, wxTOP | wxEXPAND, FromDIP(20)); - sizer_page->Add(item_priv_policy, 0, wxTOP, FromDIP(3)); -#ifdef _WIN32 - sizer_page->Add(item_webview_auto_fill, 0, wxTOP, FromDIP(3)); -#endif + return page->Finalize(); +} - sizer_page->Add(title_develop_mode, 0, wxTOP | wxEXPAND, FromDIP(20)); - sizer_page->Add(item_develop_mode, 0, wxTOP, FromDIP(3)); - sizer_page->Add(item_skip_ams_blacklist_check, 0, wxTOP, FromDIP(3)); +wxWindow* PreferencesDialog::create_files_page(wxWindow *parent) +{ + auto page = create_book_page(parent); + + page->AddTitle(_L("3MF Project Files"), _L("Settings affecting program behavior when opening 3MF project files.")); + + page->Add( + // Note that the language of the label here is the reverse of the actual config value, to stay consistent with rest of the labels. The exception is handled in create_item_checkbox(). + create_item_checkbox( + _L("Warn when loading 3MF with modified G-codes"), page, + _L("Show a warning dialog when loading 3MF projects with modified machine or filament G-code."), + "no_warn_when_modified_gcodes" + )); + + page->Add( + create_item_checkbox( + _L("Warn when opening incompatible 3MF project files"), page, + _L("Show a warning dialog when importing non-Bambu 3MF files."), + "skip_non_bambu_3mf_warning" + )); + + static const std::vector sync_labels = {_L("Prompt to Sync"), _L("Always Sync"), _L("Never Sync")}; + static const std::vector sync_values = { "", "true", "false" }; + page->Add( + create_item_combobox( + _L("When opening a project with unsynced properties:"), page, + _L("Select what to do when a project being loaded doesn't match the current printer or nozzle choices."), + "sync_after_load_file_show_flag", sync_labels, sync_values, + [](int sel) { + if (sel == 0) + wxGetApp().app_config->erase("app", "sync_after_load_file_show_flag"); + }, + -1 + )); + + page->AddTitle(_L("Imports"), _L("Settings affecting program behavior when importing models or other data.")); + + page->Add( + create_item_checkbox( + _L("Show the STEP mesh parameter setting dialog"), page, + _L("If enabled,a parameter settings dialog will appear during STEP file import."), + "enable_step_mesh_setting" + )); + page->Add( + create_item_checkbox( + _L("Import a single SVG and split it"), page, + _L("Import a single SVG and then split it to several parts."), + "import_single_svg_and_split" + )); + page->Add( + create_item_checkbox( + _L("Enable gamma correction for imported OBJ files"), page, + _L("Perform gamma correction on color after importing the OBJ model."), + "gamma_correct_in_import_obj" + )); + +#ifdef _WIN32 + page->AddTitle(_L("Associate Files To Bambu Studio"), _L("Select file type(s) to associate with Bambu Studio on this computer for the current user.")); + + page->Add( + create_item_checkbox(_L( + "Associate .3mf files to Bambu Studio"), page, + _L("If enabled, sets Bambu Studio as default application to open .3mf files"), "associate_3mf" + )); + page->Add( + create_item_checkbox( + _L("Associate .stl files to Bambu Studio"), page, + _L("If enabled, sets Bambu Studio as default application to open .stl files"), "associate_stl" + )); + page->Add( + create_item_checkbox( + _L("Associate .step/.stp files to Bambu Studio"), page, + _L("If enabled, sets Bambu Studio as default application to open .step files"), "associate_step" + )); +#endif // _WIN32 - page->SetSizer(sizer_page); - page->Layout(); - sizer_page->Fit(page); - return page; + return page->Finalize(); } -void PreferencesDialog::create_gui_page() +wxWindow* PreferencesDialog::create_3Dview_page(wxWindow *parent) { - auto page = new wxWindow(this, wxID_ANY); - wxBoxSizer *sizer_page = new wxBoxSizer(wxVERTICAL); + auto page = create_book_page(parent); + + page->AddTitle(_L("3D Workspace")); + + page->Add( + create_item_checkbox( + _L("Always show shells in preview"), page, + _L("Always show shells or not in preview view tab. If you change this value, you should reslice."), + "show_shells_in_preview" + )); + page->Add( + create_item_checkbox( + _L("Remember last used color scheme"), page, + _L("When enabled, the last used color scheme (e.g., Line Type, Speed) will be automatically applied on next startup."), + "enable_record_gcodeviewer_option_item" + )); + page->Add( + create_item_checkbox( + _L("Enable advanced gcode viewer"), page, + _L("Enable advanced gcode viewer."), + "enable_advanced_gcode_viewer_" + )); + + static const std::vector toolbar_style = { _L("Collapsible"), _L("Uncollapsible") }; + page->Add( + create_item_combobox( + _L("Toolbar Style"), page, + _L("Collapsible style will always fit the toolbar into the available horizontal space, and if necessary will add a button to show any hidden tools."), + "toolbar_style", toolbar_style, { "0","1" }, + [](int idx) { + if (const auto p_ogl_manager = wxGetApp().get_opengl_manager()) + p_ogl_manager->set_toolbar_rendering_style(idx); + } + )); + + float range_min = 1.0, + range_max = 2.5; + page->Add( + create_item_range_input( + _L("Grabber scale"), page, + _L("Set grabber size for move, rotate, and scale tools.") + " " + _L("Value range") + ": [" + std::to_string(range_min) + " - " + std::to_string(range_max) + "]", + "grabber_size_factor", + range_min, range_max, 1, + [](const wxString &value) { + double d_value = 0; + if (value.ToDouble(&d_value)) { + GLGizmoBase::Grabber::GrabberSizeFactor = d_value; + } + } + )); + + range_min = 0.0f; + range_max = 150.0f; + page->Add( + create_item_range_two_input( + _L("Tooltip offset"), page, + _L("Set tooltip offset for different mouse size.") + " " + _L("Value range") + ": [" + std::to_string(range_min) + " - " + std::to_string(range_max) + "]", + "3d_middle_tooltip_offset_x", "3d_middle_tooltip_offset_y", range_min, range_max, 1 + )); + + + page->AddTitle(_L("Camera"), _L("These settings adjust how to interact with 3D view camera controls.")); + + page->Add( + create_item_checkbox( + _L("Zoom to mouse position"), page, + _L("Zoom in towards the mouse pointer's position in the 3D view, rather than the 2D window center."), + "zoom_to_mouse" + )); + + page->AddTitle(_L("Assembly View")); + + static const std::vector assemble_overview_labels = { _L("Auto"), _L("Open"), _L("Close") }; + static const std::vector assemble_overview_values = { "Auto", "Open", "Close" }; + page->Add( + create_item_combobox( + _L("Display overview"), page, + _L("Display overview."), // FIXME + "enable_assemble_view_preview", assemble_overview_labels, assemble_overview_values, + [](int idx) { + if (idx < 2) // auto or open + wxGetApp().app_config->set_bool("enable_bvh", idx == 0); + }, + -1, FromDIP(DESIGN_COMBO_W) + )); + +#if !BBL_RELEASE_TO_PUBLIC + page->Add( create_item_checkbox(_L("Show assembly BVH primary bounds"), page, _L("Display the BVH primary bounding box wireframe in assembly view."), "show_assembly_bvh_bounds") ); +#endif - auto title_index_and_tip = create_item_title(_L("Home page and daily tips"), page, _L("Home page and daily tips")); - auto item_home_page = create_item_checkbox(_L("Show home page on startup"), page, _L("Show home page on startup"), 50, "show_home_page"); - //auto item_daily_tip = create_item_checkbox(_L("Show daily tip on startup"), page, _L("Show daily tip on startup"), 50, "show_daily_tips"); + page->AddTitle(_L("Graphics")); - sizer_page->Add(title_index_and_tip, 0, wxTOP, 26); - sizer_page->Add(item_home_page, 0, wxTOP, 6); - //sizer_page->Add(item_daily_tip, 0, wxTOP, 6); + page->Add( + create_item_checkbox( + _L("Improve rendering performance by LOD"), page, + _L("Improved rendering performance under the scene of multiple plates and many models."), + "enable_lod" + )); - page->SetSizer(sizer_page); - page->Layout(); - sizer_page->Fit(page); + return page->Finalize(); } -void PreferencesDialog::create_sync_page() +wxWindow* PreferencesDialog::create_advanced_page(wxWindow *parent) { - auto page = new wxWindow(this, wxID_ANY); - wxBoxSizer *sizer_page = new wxBoxSizer(wxVERTICAL); - - auto title_sync_settingy = create_item_title(_L("Sync settings"), page, _L("Sync settings")); - auto item_user_sync = create_item_checkbox(_L("User sync"), page, _L("User sync"), 50, "user_sync_switch"); - auto item_preset_sync = create_item_checkbox(_L("Preset sync"), page, _L("Preset sync"), 50, "preset_sync_switch"); - auto item_preferences_sync = create_item_checkbox(_L("Preferences sync"), page, _L("Preferences sync"), 50, "preferences_sync_switch"); - - sizer_page->Add(title_sync_settingy, 0, wxTOP, 26); - sizer_page->Add(item_user_sync, 0, wxTOP, 6); - sizer_page->Add(item_preset_sync, 0, wxTOP, 6); - sizer_page->Add(item_preferences_sync, 0, wxTOP, 6); - - page->SetSizer(sizer_page); - page->Layout(); - sizer_page->Fit(page); -} + auto page = create_book_page(parent); + + page->AddTitle(_L("Other Settings")); + + page->Add( + create_item_checkbox( + _L("Develop mode"), page, + _L("Enable extra and experimental features. Note that this also forces \"Advanced\" mode for all property editors."), + "developer_mode" + )); + page->Add( + create_item_checkbox( + _L("Skip AMS blacklist check"), page, + _L("Skip AMS blacklist check"), + "skip_ams_blacklist_check" + )); -void PreferencesDialog::create_shortcuts_page() -{ - auto page = new wxWindow(this, wxID_ANY); - wxBoxSizer *sizer_page = new wxBoxSizer(wxVERTICAL); - - auto title_view_control = create_item_title(_L("View control settings"), page, _L("View control settings")); - std::vector keyboard_supported; - Split(app_config->get("keyboard_supported"), "/", keyboard_supported); - - std::vector mouse_supported; - Split(app_config->get("mouse_supported"), "/", mouse_supported); - - auto item_rotate_view = create_item_multiple_combobox(_L("Rotate of view"), page, _L("Rotate of view"), 10, "rotate_view", keyboard_supported, - mouse_supported); - auto item_move_view = create_item_multiple_combobox(_L("Move of view"), page, _L("Move of view"), 10, "move_view", keyboard_supported, mouse_supported); - auto item_zoom_view = create_item_multiple_combobox(_L("Zoom of view"), page, _L("Zoom of view"), 10, "rotate_view", keyboard_supported, mouse_supported); - - auto title_other = create_item_title(_L("Other"), page, _L("Other")); - auto item_other = create_item_checkbox(_L("Mouse wheel reverses when zooming"), page, _L("Mouse wheel reverses when zooming"), 50, "mouse_wheel"); - - sizer_page->Add(title_view_control, 0, wxTOP, 26); - sizer_page->Add(item_rotate_view, 0, wxTOP, 8); - sizer_page->Add(item_move_view, 0, wxTOP, 8); - sizer_page->Add(item_zoom_view, 0, wxTOP, 8); - // sizer_page->Add(item_precise_control, 0, wxTOP, 0); - sizer_page->Add(title_other, 0, wxTOP, 20); - sizer_page->Add(item_other, 0, wxTOP, 5); - - page->SetSizer(sizer_page); - page->Layout(); - sizer_page->Fit(page); +#if !BBL_RELEASE_TO_PUBLIC + page->Add(create_debug_page(page), DESIGN_SECTION_SPACING); +#endif + + return page->Finalize(); } -wxWindow* PreferencesDialog::create_debug_page() +wxWindow* PreferencesDialog::create_debug_page(wxWindow *parent) { - auto page = new wxWindow(m_scrolledWindow, wxID_ANY); - page->SetBackgroundColour(*wxWHITE); + auto page = create_book_page(parent); - m_internal_developer_mode_def = app_config->get("internal_developer_mode"); + m_internal_developer_mode_def = app_config->get_bool("internal_developer_mode"); m_backup_interval_def = app_config->get("backup_interval"); m_iot_environment_def = app_config->get("iot_environment"); - wxBoxSizer *bSizer = new wxBoxSizer(wxVERTICAL); + page->AddTitle(_L("Application Developer")); + page->Add( create_item_checkbox(_L("Enable SSL(MQTT)"), page, _L("Enable SSL(MQTT)"), "enable_ssl_for_mqtt") ); + page->Add( create_item_checkbox(_L("Enable SSL(FTP)"), page, _L("Enable SSL(MQTT)"), "enable_ssl_for_ftp") ); + page->Add( create_item_checkbox(_L("Internal developer mode"), page, _L("Internal developer mode"), "internal_developer_mode") ); - auto enable_ssl_for_mqtt = create_item_checkbox(_L("Enable SSL(MQTT)"), page, _L("Enable SSL(MQTT)"), 50, "enable_ssl_for_mqtt"); - auto enable_ssl_for_ftp = create_item_checkbox(_L("Enable SSL(FTP)"), page, _L("Enable SSL(MQTT)"), 50, "enable_ssl_for_ftp"); - auto item_internal_developer = create_item_checkbox(_L("Internal developer mode"), page, _L("Internal developer mode"), 50, "internal_developer_mode"); - - auto title_log_level = create_item_title(_L("Log Level"), page, _L("Log Level")); - auto log_level_list = std::vector{_L("fatal"), _L("error"), _L("warning"), _L("info"), _L("debug"), _L("trace")}; - auto loglevel_combox = create_item_loglevel_combobox(_L("Log Level"), page, _L("Log Level"), log_level_list); + static const std::vector ll_names { _L("fatal"), _L("error"), _L("warning"), _L("info"), _L("debug"), _L("trace") }; + static const std::vector ll_values{ "fatal", "error", "warning", "info", "debug", "trace" }; + page->Add( + create_item_combobox( + _L("Log Level"), page, _L("Log Level"), "severity_level", ll_names, ll_values, + [](int selection) { + auto level = Slic3r::get_string_logging_level(selection); + Slic3r::set_logging_level(Slic3r::level_string_to_boost(level)); + } + )); - auto title_host = create_item_title(_L("Host Setting"), page, _L("Host Setting")); - auto radio1 = create_item_radiobox(_L("DEV host: api-dev.bambu-lab.com/v1"), page, wxEmptyString, 50, 1, "dev_host"); - auto radio2 = create_item_radiobox(_L("QA host: api-qa.bambu-lab.com/v1"), page, wxEmptyString, 50, 1, "qa_host"); - auto radio3 = create_item_radiobox(_L("PRE host: api-pre.bambu-lab.com/v1"), page, wxEmptyString, 50, 1, "pre_host"); - auto radio4 = create_item_radiobox(_L("Product host"), page, wxEmptyString, 50, 1, "product_host"); - - if (m_iot_environment_def == ENV_DEV_HOST) { - on_select_radio("dev_host"); - } else if (m_iot_environment_def == ENV_QAT_HOST) { - on_select_radio("qa_host"); - } else if (m_iot_environment_def == ENV_PRE_HOST) { - on_select_radio("pre_host"); - } else if (m_iot_environment_def == ENV_PRODUCT_HOST) { - on_select_radio("product_host"); - } + page->AddTitle(_L("Host Setting")); + static const int radio_grp_id = 1; + static const std::map iot_env_map { + { "dev_host", ENV_DEV_HOST }, + { "qa_host", ENV_QAT_HOST }, + { "pre_host", ENV_PRE_HOST }, + { "product_host", ENV_PRODUCT_HOST } + }; - StateColor btn_bg_white(std::pair(AMS_CONTROL_DISABLE_COLOUR, StateColor::Disabled), std::pair(AMS_CONTROL_DISABLE_COLOUR, StateColor::Pressed), - std::pair(AMS_CONTROL_DEF_BLOCK_BK_COLOUR, StateColor::Hovered), - std::pair(AMS_CONTROL_WHITE_COLOUR, StateColor::Normal)); - StateColor btn_bd_white(std::pair(AMS_CONTROL_WHITE_COLOUR, StateColor::Disabled), std::pair(wxColour(38, 46, 48), StateColor::Enabled)); + // TODO: perhaps these can be converted to a combo box selector and all radio button code removed. This is the only place that uses them. + page->Add( create_item_radiobox(_L("DEV host: api-dev.bambu-lab.com/v1"), page, "", radio_grp_id, "dev_host", m_iot_environment_def == ENV_DEV_HOST) ); + page->Add( create_item_radiobox(_L("QA host: api-qa.bambu-lab.com/v1"), page, "", radio_grp_id, "qa_host", m_iot_environment_def == ENV_QAT_HOST) ); + page->Add( create_item_radiobox(_L("PRE host: api-pre.bambu-lab.com/v1"), page, "", radio_grp_id, "pre_host", m_iot_environment_def == ENV_PRE_HOST) ); + page->Add( create_item_radiobox(_L("Product host"), page, "", radio_grp_id, "product_host", m_iot_environment_def == ENV_PRODUCT_HOST) ); - Button* debug_button = new Button(page, _L("debug save button")); - m_button_list[m_button_list.size()] = debug_button; - debug_button->SetBackgroundColor(btn_bg_white); - debug_button->SetBorderColor(btn_bd_white); + Button* debug_button = new_button(page, _L("debug save button"), wxEmptyString, &m_button_list, wxDefaultSize); debug_button->SetFont(Label::Body_13); - - - debug_button->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { + debug_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent &/* e */) { // success message box MessageDialog dialog(this, _L("save debug settings"), _L("DEBUG settings have saved successfully!"), wxNO_DEFAULT | wxYES_NO | wxICON_INFORMATION); dialog.SetSize(400,-1); switch (dialog.ShowModal()) { case wxID_NO: { - //if (m_developer_mode_def != app_config->get("developer_mode")) { - // app_config->set_bool("developer_mode", m_developer_mode_def == "true" ? true : false); - // m_developer_mode_ckeckbox->SetValue(m_developer_mode_def == "true" ? true : false); + //if (m_developer_mode_def != app_config->get_bool("developer_mode")) { + // app_config->set_bool("developer_mode", m_developer_mode_def); + // m_developer_mode_ckeckbox->SetValue(m_developer_mode_def); //} - //if (m_internal_developer_mode_def != app_config->get("internal_developer_mode")) { - // app_config->set_bool("internal_developer_mode", m_internal_developer_mode_def == "true" ? true : false); - // m_internal_developer_mode_ckeckbox->SetValue(m_internal_developer_mode_def == "true" ? true : false); + //if (m_internal_developer_mode_def != app_config->get_bool("internal_developer_mode")) { + // app_config->set_bool("internal_developer_mode", m_internal_developer_mode_def); + // m_internal_developer_mode_ckeckbox->SetValue(m_internal_developer_mode_def); //} if (m_backup_interval_def != m_backup_interval_time) { m_backup_interval_textinput->GetTextCtrl()->SetValue(m_backup_interval_def); } - if (m_iot_environment_def == ENV_DEV_HOST) { - on_select_radio("dev_host"); - } else if (m_iot_environment_def == ENV_QAT_HOST) { - on_select_radio("qa_host"); - } else if (m_iot_environment_def == ENV_PRE_HOST) { - on_select_radio("pre_host"); - } else if (m_iot_environment_def == ENV_PRODUCT_HOST) { - on_select_radio("product_host"); - } + if (auto res = std::find_if(iot_env_map.cbegin(), iot_env_map.cend(), [=](const auto &v) { return v.second == m_iot_environment_def; }); res != iot_env_map.end()) + select_radio_by_param(res->first); break; } case wxID_YES: { // bbs domain changed - auto param = get_select_radio(1); - - std::map iot_environment_map; - iot_environment_map["dev_host"] = ENV_DEV_HOST; - iot_environment_map["qa_host"] = ENV_QAT_HOST; - iot_environment_map["pre_host"] = ENV_PRE_HOST; - iot_environment_map["product_host"] = ENV_PRODUCT_HOST; - - //if (iot_environment_map[param] != m_iot_environment_def) { - if (true) { - NetworkAgent* agent = wxGetApp().getAgent(); - if (param == "dev_host") { - app_config->set("iot_environment", ENV_DEV_HOST); - } - else if (param == "qa_host") { - app_config->set("iot_environment", ENV_QAT_HOST); - } - else if (param == "pre_host") { - app_config->set("iot_environment", ENV_PRE_HOST); - } - else if (param == "product_host") { - app_config->set("iot_environment", ENV_PRODUCT_HOST); - } + const wxString param = get_selected_radio_param_by_group(radio_grp_id); + const wxString new_env = iot_env_map.at(param); + if (new_env != m_iot_environment_def) { + // if (true) { + app_config->set("iot_environment", new_env.ToStdString()); + app_config->save(); + m_iot_environment_def = new_env; wxGetApp().update_publish_status(); - AppConfig* config = GUI::wxGetApp().app_config; - std::string country_code = config->get_country_code(); - if (agent) { + if (NetworkAgent* agent = wxGetApp().getAgent()) { + std::string country_code = app_config->get_country_code(); wxGetApp().request_user_logout(); agent->set_country_code(country_code); } @@ -1794,86 +1637,50 @@ wxWindow* PreferencesDialog::create_debug_page() } // bbs backup - //app_config->set("backup_interval", std::string(m_backup_interval_time.mb_str())); - app_config->save(); - Slic3r::set_backup_interval(boost::lexical_cast(app_config->get("backup_interval"))); + if (m_backup_interval_def != m_backup_interval_time) { + // app_config->set("backup_interval", std::string(m_backup_interval_time.mb_str())); + // app_config->save(); + Slic3r::set_backup_interval(boost::lexical_cast(app_config->get("backup_interval"))); + } this->Close(); break; } } }); + page->Add(debug_button, FromDIP(15), wxALIGN_CENTER_HORIZONTAL | wxTOP); - - bSizer->Add(enable_ssl_for_mqtt, 0, wxTOP, FromDIP(3)); - bSizer->Add(enable_ssl_for_ftp, 0, wxTOP, FromDIP(3)); - bSizer->Add(item_internal_developer, 0, wxTOP, FromDIP(3)); - bSizer->Add(title_log_level, 0, wxTOP| wxEXPAND, FromDIP(20)); - bSizer->Add(loglevel_combox, 0, wxTOP, FromDIP(3)); - bSizer->Add(title_host, 0, wxTOP| wxEXPAND, FromDIP(20)); - bSizer->Add(radio1, 0, wxEXPAND | wxTOP, FromDIP(3)); - bSizer->Add(radio2, 0, wxEXPAND | wxTOP, FromDIP(3)); - bSizer->Add(radio3, 0, wxEXPAND | wxTOP, FromDIP(3)); - bSizer->Add(radio4, 0, wxEXPAND | wxTOP, FromDIP(3)); - bSizer->Add(debug_button, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, FromDIP(15)); - - page->SetSizer(bSizer); - page->Layout(); - bSizer->Fit(page); - return page; + return page->Finalize(); } -void PreferencesDialog::on_select_radio(std::string param) +void PreferencesDialog::select_radio_by_param(const wxString ¶m) { - RadioSelectorList::Node *node = m_radio_group.GetFirst(); - auto groupid = 0; + int groupid = -1; - while (node) { - RadioSelector *rs = node->GetData(); - if (rs->m_param_name == param) groupid = rs->m_groupid; - node = node->GetNext(); + for (auto rs : std::as_const(m_radio_group)) { + if (rs->m_param_name == param) { + groupid = rs->m_groupid; + break; + } } + if (groupid < 0) + return; - node = m_radio_group.GetFirst(); - while (node) { - RadioSelector *rs = node->GetData(); - if (rs->m_groupid == groupid && rs->m_param_name == param) rs->m_radiobox->SetValue(true); - if (rs->m_groupid == groupid && rs->m_param_name != param) rs->m_radiobox->SetValue(false); - node = node->GetNext(); + for (auto rs : std::as_const(m_radio_group)) { + if (RadioBox *rb = rs->m_radiobox; rb && rs->m_groupid == groupid) + rb->SetValue(rs->m_param_name == param); } } -wxString PreferencesDialog::get_select_radio(int groupid) +wxString PreferencesDialog::get_selected_radio_param_by_group(int groupid) { - RadioSelectorList::Node *node = m_radio_group.GetFirst(); - while (node) { - RadioSelector *rs = node->GetData(); - if (rs->m_groupid == groupid && rs->m_radiobox->GetValue()) { return rs->m_param_name; } - node = node->GetNext(); + for (auto rs : std::as_const(m_radio_group)) { + if (RadioBox *rb = rs->m_radiobox; rs->m_groupid == groupid && rb && rb->GetValue()) + return rs->m_param_name; } return wxEmptyString; } -void PreferencesDialog::OnSelectRadio(wxMouseEvent &event) -{ - RadioSelectorList::Node *node = m_radio_group.GetFirst(); - auto groupid = 0; - - while (node) { - RadioSelector *rs = node->GetData(); - if (rs->m_radiobox->GetId() == event.GetId()) groupid = rs->m_groupid; - node = node->GetNext(); - } - - node = m_radio_group.GetFirst(); - while (node) { - RadioSelector *rs = node->GetData(); - if (rs->m_groupid == groupid && rs->m_radiobox->GetId() == event.GetId()) rs->m_radiobox->SetValue(true); - if (rs->m_groupid == groupid && rs->m_radiobox->GetId() != event.GetId()) rs->m_radiobox->SetValue(false); - node = node->GetNext(); - } -} - }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index 2d675ae695..c777175d66 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -4,37 +4,21 @@ #include "GUI.hpp" #include "GUI_Utils.hpp" -#include -#include -#include +#include #include -#include -#include -#include "Widgets/ComboBox.hpp" -#include "Widgets/CheckBox.hpp" -#include "Widgets/TextInput.hpp" -#include "Widgets/Button.hpp" -#include "Widgets/RadioBox.hpp" -#include "Widgets/LinkLabel.hpp" -namespace Slic3r { namespace GUI { - -#define DESIGN_SELECTOR_NOMORE_COLOR wxColour(248, 248, 248) -#define DESIGN_GRAY900_COLOR wxColour(38, 46, 48) -#define DESIGN_GRAY800_COLOR wxColour(50, 58, 61) -#define DESIGN_GRAY600_COLOR wxColour(144, 144, 144) -#define DESIGN_GRAY400_COLOR wxColour(166, 169, 170) +class Button; +class CheckBox; +class ComboBox; +class Tabbook; +class TextInput; +class wxBookCtrlEvent; -class Selector -{ -public: - int m_index; - wxWindow *m_tab_button; - wxWindow *m_tab_text; -}; -WX_DECLARE_HASH_MAP(int, Selector *, wxIntegerHash, wxIntegerEqual, SelectorHash); +namespace Slic3r { namespace GUI { +class ScrolledPanel; class RadioBox; + class RadioSelector { public: @@ -45,123 +29,106 @@ class RadioSelector }; WX_DECLARE_LIST(RadioSelector, RadioSelectorList); -class CheckBox; -class TextInput; - - - -#define DESIGN_RESOUTION_PREFERENCES wxSize(FromDIP(540), -1) -#define DESIGN_TITLE_SIZE wxSize(FromDIP(100), -1) -#define DESIGN_COMBOBOX_SIZE wxSize(FromDIP(140), -1) -#define DESIGN_LARGE_COMBOBOX_SIZE wxSize(FromDIP(160), -1) -#define DESIGN_INPUT_SIZE wxSize(FromDIP(100), -1) - class PreferencesDialog : public DPIDialog { -private: - AppConfig *app_config; - -protected: - wxBoxSizer * m_sizer_body; - wxScrolledWindow* m_scrolledWindow; - - // bool m_settings_layout_changed {false}; - bool m_seq_top_layer_only_changed{false}; - bool m_recreate_GUI{false}; - bool m_use_12h_time_format_changed{false}; - std::string m_original_use_12h_time_format; - -public: - bool seq_top_layer_only_changed() const { return m_seq_top_layer_only_changed; } - bool recreate_GUI() const { return m_recreate_GUI; } - bool use_12h_time_format_changed() const { return m_use_12h_time_format_changed; } - void on_dpi_changed(const wxRect &suggested_rect) override; - public: PreferencesDialog(wxWindow * parent, wxWindowID id = wxID_ANY, const wxString &title = wxT(""), const wxPoint & pos = wxDefaultPosition, const wxSize & size = wxDefaultSize, - long style = wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX); + long style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); ~PreferencesDialog(); - wxString m_backup_interval_time; + bool use_12h_time_format_changed() const; + // TODO: legacy use, remove + bool seq_top_layer_only_changed() const { return false; } + bool seq_seq_top_gcode_indices_changed() const { return false; } + +private: + AppConfig *app_config; + Tabbook *m_page_book { nullptr }; + + int m_current_language_selected = { 0 }; + bool m_original_use_12h_time_format; + + std::vector