Files
OrcaSlicer/src/slic3r/GUI/Preferences.cpp
SoftFever e42ff0b57d when the stealth mode is enabled, log out cloud service and hide sideboard (#13531)
* when the stealth mode is enabled, log out cloud service and hide sideboard

* update tooltip
2026-05-09 00:46:41 +08:00

2040 lines
90 KiB
C++

#include "Preferences.hpp"
#include "OptionsGroup.hpp"
#include "GUI_App.hpp"
#include "MainFrame.hpp"
#include "Plater.hpp"
#include "MsgDialog.hpp"
#include "I18N.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Format/DRC.hpp"
#include <wx/language.h>
#include "OG_CustomCtrl.hpp"
#include "wx/graphics.h"
#include <wx/listimpl.cpp>
#include <wx/display.h>
#include "NetworkTestDialog.hpp"
#include "Widgets/StaticLine.hpp"
#include "Widgets/RadioGroup.hpp"
#include "slic3r/Utils/bambu_networking.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "DownloadProgressDialog.hpp"
#ifdef __WINDOWS__
#ifdef _MSW_DARK_MODE
#include "dark_mode.hpp"
#endif // _MSW_DARK_MODE
#endif //__WINDOWS__
namespace Slic3r { namespace GUI {
class MyscrolledWindow : public wxScrolledWindow {
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) {}
bool ShouldScrollToChildOnFocus(wxWindow* child) override { return false; }
};
wxBoxSizer *PreferencesDialog::create_item_title(wxString title)
{
wxBoxSizer *m_sizer_title = new wxBoxSizer(wxHORIZONTAL);
auto title_ctrl = new StaticLine(m_parent, 0, title);
title_ctrl->SetFont(Label::Head_14);
title_ctrl->SetForegroundColour(DESIGN_GRAY900_COLOR);
m_sizer_title->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN - 10));
m_sizer_title->Add(title_ctrl, 1, wxEXPAND | wxBOTTOM | wxTOP, FromDIP(6));
m_sizer_title->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN - 10));
return m_sizer_title;
}
std::tuple<wxBoxSizer*, ComboBox*> PreferencesDialog::create_item_combobox_base(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, unsigned int current_index)
{
wxBoxSizer *m_sizer_combox = new wxBoxSizer(wxHORIZONTAL);
m_sizer_combox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty
auto combo_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
combo_title->SetFont(::Label::Body_14);
combo_title->SetToolTip(tip);
combo_title->Wrap(DESIGN_TITLE_SIZE.x);
m_sizer_combox->Add(combo_title, 0, wxALIGN_CENTER);
auto combobox = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_LARGE_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY);
combobox->SetFont(::Label::Body_14);
combobox->GetDropDown().SetFont(::Label::Body_14);
std::vector<wxString>::iterator iter;
for (iter = vlist.begin(); iter != vlist.end(); iter++) {
combobox->Append(*iter);
}
combobox->SetSelection(current_index);
m_sizer_combox->Add(combobox, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5));
return {m_sizer_combox, combobox};
}
wxBoxSizer* PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::function<void(wxString)> onchange)
{
unsigned int current_index = 0;
auto current_setting = app_config->get(param);
if (!current_setting.empty()) {
current_index = atoi(current_setting.c_str());
}
auto [sizer, combobox] = create_item_combobox_base(title, tooltip, param, vlist, current_index);
//// save config
combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, onchange](wxCommandEvent& e) {
app_config->set(param, std::to_string(e.GetSelection()));
if (onchange)
onchange(std::to_string(e.GetSelection()));
e.Skip();
});
return sizer;
}
wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index)
{
assert(vlist.size() == config_name_index.size());
unsigned int current_index = 0;
auto current_setting = app_config->get(param);
if (!current_setting.empty()) {
auto compare = [current_setting](string possible_setting) { return current_setting == possible_setting; };
auto iterator = find_if(config_name_index.begin(), config_name_index.end(), compare);
current_index = iterator - config_name_index.begin();
}
auto [sizer, combobox] = create_item_combobox_base(title, tooltip, param, vlist, current_index);
//// save config
combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, config_name_index](wxCommandEvent& e) {
app_config->set(param, config_name_index[e.GetSelection()]);
e.Skip();
});
return sizer;
}
wxBoxSizer *PreferencesDialog::create_item_language_combobox(wxString title, wxString tooltip)
{
wxLanguage supported_languages[]{
wxLANGUAGE_ENGLISH,
wxLANGUAGE_CHINESE_SIMPLIFIED,
wxLANGUAGE_CHINESE,
wxLANGUAGE_GERMAN,
wxLANGUAGE_CZECH,
wxLANGUAGE_FRENCH,
wxLANGUAGE_SPANISH,
wxLANGUAGE_SWEDISH,
wxLANGUAGE_DUTCH,
wxLANGUAGE_HUNGARIAN,
wxLANGUAGE_JAPANESE,
wxLANGUAGE_ITALIAN,
wxLANGUAGE_KOREAN,
wxLANGUAGE_RUSSIAN,
wxLANGUAGE_UKRAINIAN,
wxLANGUAGE_TURKISH,
wxLANGUAGE_POLISH,
wxLANGUAGE_CATALAN,
wxLANGUAGE_PORTUGUESE_BRAZILIAN,
wxLANGUAGE_LITHUANIAN,
wxLANGUAGE_VIETNAMESE
};
auto translations = wxTranslations::Get()->GetAvailableTranslations(SLIC3R_APP_KEY);
std::vector<const wxLanguageInfo *> language_infos;
language_infos.emplace_back(wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH));
for (size_t i = 0; i < translations.GetCount(); ++i) {
const wxLanguageInfo *langinfo = wxLocale::FindLanguageInfo(translations[i]);
if (langinfo == nullptr) continue;
int language_num = sizeof(supported_languages) / sizeof(supported_languages[0]);
for (auto si = 0; si < language_num; si++) {
if (langinfo == wxLocale::GetLanguageInfo(supported_languages[si])) {
language_infos.emplace_back(langinfo);
}
}
//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 vlist = language_infos;
auto param = "language";
wxBoxSizer *m_sizer_combox = new wxBoxSizer(wxHORIZONTAL);
m_sizer_combox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty
auto combo_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
combo_title->SetFont(::Label::Body_14);
combo_title->SetToolTip(tip);
combo_title->Wrap(DESIGN_TITLE_SIZE.x);
m_sizer_combox->Add(combo_title, 0, wxALIGN_CENTER);
auto combobox = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_LARGE_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY);
combobox->SetFont(::Label::Body_14);
combobox->GetDropDown().SetFont(::Label::Body_14);
auto language = app_config->get(param);
m_current_language_selected = -1;
std::vector<wxString>::iterator iter;
for (size_t i = 0; i < vlist.size(); ++i) {
auto language_name = vlist[i]->Description;
if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_CHINESE_SIMPLIFIED)) {
language_name = wxString::FromUTF8("\xe4\xb8\xad\xe6\x96\x87\x28\xe7\xae\x80\xe4\xbd\x93\x29");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_CHINESE)) {
language_name = wxString::FromUTF8("\xe4\xb8\xad\xe6\x96\x87\x28\xe7\xb9\x81\xe9\xab\x94\x29");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_SPANISH)) {
language_name = wxString::FromUTF8("\x45\x73\x70\x61\xc3\xb1\x6f\x6c");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_GERMAN)) {
language_name = wxString::FromUTF8("Deutsch");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_CZECH)) {
language_name = wxString::FromUTF8("Czech");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_SWEDISH)) {
language_name = wxString::FromUTF8("\x53\x76\x65\x6e\x73\x6b\x61"); //Svenska
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_DUTCH)) {
language_name = wxString::FromUTF8("Nederlands");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_FRENCH)) {
language_name = wxString::FromUTF8("\x46\x72\x61\x6E\xC3\xA7\x61\x69\x73");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_HUNGARIAN)) {
language_name = wxString::FromUTF8("Magyar");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_JAPANESE)) {
language_name = wxString::FromUTF8("\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_ITALIAN)) {
language_name = wxString::FromUTF8("\x69\x74\x61\x6c\x69\x61\x6e\x6f");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_KOREAN)) {
language_name = wxString::FromUTF8("\xED\x95\x9C\xEA\xB5\xAD\xEC\x96\xB4");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_RUSSIAN)) {
language_name = wxString::FromUTF8("\xd0\xa0\xd1\x83\xd1\x81\xd1\x81\xd0\xba\xd0\xb8\xd0\xb9");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_UKRAINIAN)) {
language_name = wxString::FromUTF8("Ukrainian");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_TURKISH)) {
language_name = wxString::FromUTF8("Turkish");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_POLISH)) {
language_name = wxString::FromUTF8("Polski");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_CATALAN)) {
language_name = wxString::FromUTF8("Catalan");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_PORTUGUESE_BRAZILIAN)) {
language_name = wxString::FromUTF8("Português (Brasil)");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_LITHUANIAN)) {
language_name = wxString::FromUTF8("Lietuvių");
}
else if (vlist[i] == wxLocale::GetLanguageInfo(wxLANGUAGE_VIETNAMESE)) {
language_name = wxString::FromUTF8("Tiếng Việt");
}
if (app_config->get(param) == vlist[i]->CanonicalName) {
m_current_language_selected = i;
}
combobox->Append(language_name);
}
if (m_current_language_selected == -1 && language.size() >= 5) {
language = language.substr(0, 2);
for (size_t i = 0; i < vlist.size(); ++i) {
if (vlist[i]->CanonicalName.StartsWith(language)) {
m_current_language_selected = i;
break;
}
}
}
combobox->SetSelection(m_current_language_selected);
m_sizer_combox->Add(combobox, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5));
combobox->Bind(wxEVT_LEFT_DOWN, [this, combobox](wxMouseEvent &e) {
m_current_language_selected = combobox->GetSelection();
e.Skip();
});
combobox->Bind(wxEVT_COMBOBOX, [this, param, vlist, combobox](wxCommandEvent &e) {
if (combobox->GetSelection() == m_current_language_selected)
return;
if (e.GetString().ToStdString() != app_config->get(param)) {
{
//check if the project has changed
if (wxGetApp().plater()->is_project_dirty()) {
auto result = MessageDialog(static_cast<wxWindow*>(this), _L("The current project has unsaved changes, save it before continue?"),
wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Save"), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE).ShowModal();
if (result == wxID_YES) {
wxGetApp().plater()->save_project();
}
}
// the dialog needs to be destroyed before the call to switch_language()
// or sometimes the application crashes into wxDialogBase() destructor
// so we put it into an inner scope
MessageDialog msg_wingow(nullptr, _L("Switching the language requires application restart.\n") + "\n" + _L("Do you want to continue?"),
L("Language selection"), wxICON_QUESTION | wxOK | wxCANCEL);
if (msg_wingow.ShowModal() == wxID_CANCEL) {
combobox->SetSelection(m_current_language_selected);
return;
}
}
auto check = [this](bool yes_or_no) {
// if (yes_or_no)
// return true;
int act_btns = ActionButtons::SAVE;
return wxGetApp().check_and_keep_current_preset_changes(_L("Switching application language"),
_L("Switching application language while some presets are modified."), act_btns);
};
m_current_language_selected = combobox->GetSelection();
if (m_current_language_selected >= 0 && m_current_language_selected < vlist.size()) {
m_pending_language = vlist[m_current_language_selected]->CanonicalName.ToUTF8().data();
m_recreate_GUI = true;
EndModal(wxID_OK);
return;
}
}
e.Skip();
});
return m_sizer_combox;
}
wxBoxSizer *PreferencesDialog::create_item_region_combobox(wxString title, wxString tooltip)
{
std::vector<wxString> Regions = {_L("Asia-Pacific"), _L("China"), _L("Europe"), _L("North America"), _L("Others")};
std::vector<wxString> local_regions = {"Asia-Pacific", "China", "Europe", "North America", "Others"};
auto vlist = Regions;
wxBoxSizer *m_sizer_combox = new wxBoxSizer(wxHORIZONTAL);
m_sizer_combox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty
auto combo_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
combo_title->SetFont(::Label::Body_14);
combo_title->SetToolTip(tip);
combo_title->Wrap(DESIGN_TITLE_SIZE.x);
m_sizer_combox->Add(combo_title, 0, wxALIGN_CENTER);
auto combobox = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_LARGE_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY);
combobox->SetFont(::Label::Body_14);
combobox->GetDropDown().SetFont(::Label::Body_14);
m_sizer_combox->Add(combobox, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5));
std::vector<wxString>::iterator iter;
for (iter = vlist.begin(); iter != vlist.end(); iter++) { combobox->Append(*iter); }
AppConfig * config = GUI::wxGetApp().app_config;
int current_region = 0;
if (!config->get("region").empty()) {
std::string country_code = config->get("region");
for (auto i = 0; i < vlist.size(); i++) {
if (local_regions[i].ToStdString() == country_code) {
combobox->SetSelection(i);
current_region = i;
}
}
}
combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, combobox, current_region, local_regions](wxCommandEvent &e) {
auto region_index = e.GetSelection();
auto region = local_regions[region_index];
/*auto area = "";
if (region == "CHN" || region == "China")
area = "CN";
else if (region == "USA")
area = "US";
else if (region == "Asia-Pacific")
area = "Others";
else if (region == "Europe")
area = "US";
else if (region == "North America")
area = "US";
else
area = "Others";*/
combobox->SetSelection(region_index);
NetworkAgent* agent = wxGetApp().getAgent();
AppConfig* config = GUI::wxGetApp().app_config;
if (agent) {
MessageDialog msg_wingow(this, _L("Changing the region will log out your account.\n") + "\n" + _L("Do you want to continue?"), _L("Region selection"),
wxICON_QUESTION | wxOK | wxCANCEL);
if (msg_wingow.ShowModal() == wxID_CANCEL) {
combobox->SetSelection(current_region);
return;
} else {
wxGetApp().request_user_logout();
config->set("region", region.ToStdString());
auto area = config->get_country_code();
if (agent) {
agent->set_country_code(area);
}
EndModal(wxID_CANCEL);
}
} else {
config->set("region", region.ToStdString());
}
wxGetApp().update_publish_status();
e.Skip();
});
return m_sizer_combox;
}
wxBoxSizer *PreferencesDialog::create_item_loglevel_combobox(wxString title, wxString tooltip, std::vector<wxString> vlist)
{
wxBoxSizer *m_sizer_combox = new wxBoxSizer(wxHORIZONTAL);
m_sizer_combox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
auto combo_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
combo_title->SetFont(::Label::Body_14);
combo_title->SetToolTip(tooltip);
combo_title->Wrap(DESIGN_TITLE_SIZE.x);
m_sizer_combox->Add(combo_title, 0, wxALIGN_CENTER);
auto combobox = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY);
combobox->SetFont(::Label::Body_14);
combobox->GetDropDown().SetFont(::Label::Body_14);
std::vector<wxString>::iterator iter;
for (iter = vlist.begin(); iter != vlist.end(); iter++) { combobox->Append(*iter); }
auto severity_level = app_config->get("log_severity_level");
if (!severity_level.empty()) { combobox->SetValue(severity_level); }
m_sizer_combox->Add(combobox, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5));
//// 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("log_severity_level",level);
e.Skip();
});
return m_sizer_combox;
}
wxBoxSizer *PreferencesDialog::create_item_multiple_combobox(
wxString title, wxString tooltip, std::string param, std::vector<wxString> vlista, std::vector<wxString> vlistb)
{
std::vector<wxString> params;
Split(app_config->get(param), "/", params);
std::vector<wxString>::iterator iter;
wxBoxSizer *m_sizer_tcombox= new wxBoxSizer(wxHORIZONTAL);
m_sizer_tcombox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
auto combo_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
combo_title->SetToolTip(tooltip);
combo_title->Wrap(DESIGN_TITLE_SIZE.x);
combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
combo_title->SetFont(::Label::Body_14);
m_sizer_tcombox->Add(combo_title, 0, wxALIGN_CENTER);
auto combobox_left = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY);
combobox_left->SetFont(::Label::Body_14);
combobox_left->GetDropDown().SetFont(::Label::Body_14);
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 | wxLEFT, FromDIP(5));
auto combo_title_add = new wxStaticText(m_parent, wxID_ANY, wxT("+"), wxDefaultPosition, wxDefaultSize, 0);
combo_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
combo_title->SetFont(::Label::Body_14);
combo_title_add->Wrap(-1);
m_sizer_tcombox->Add(combo_title_add, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(5));
auto combobox_right = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, DESIGN_COMBOBOX_SIZE, 0, nullptr, wxCB_READONLY);
combobox_right->SetFont(::Label::Body_14);
combobox_right->GetDropDown().SetFont(::Label::Body_14);
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 | wxLEFT, FromDIP(5));
// 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()));
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()));
e.Skip();
});
return m_sizer_tcombox;
}
wxBoxSizer *PreferencesDialog::create_item_input(wxString title, wxString title2, wxString tooltip, std::string param, std::function<void(wxString)> onchange)
{
wxBoxSizer *sizer_input = new wxBoxSizer(wxHORIZONTAL);
auto input_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
input_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
input_title->SetFont(::Label::Body_14);
input_title->SetToolTip(tooltip);
input_title->Wrap(DESIGN_TITLE_SIZE.x);
auto input = new ::TextInput(m_parent, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER);
StateColor input_bg(std::pair<wxColour, int>(wxColour("#F0F0F1"), StateColor::Disabled), std::pair<wxColour, int>(*wxWHITE, StateColor::Enabled));
input->SetBackgroundColor(input_bg);
input->GetTextCtrl()->SetValue(app_config->get(param));
wxTextValidator validator(wxFILTER_DIGITS);
input->SetToolTip(tooltip);
input->GetTextCtrl()->SetValidator(validator);
auto second_title = new wxStaticText(m_parent, wxID_ANY, title2, wxDefaultPosition, wxDefaultSize, 0);
second_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
second_title->SetFont(::Label::Body_14);
second_title->SetToolTip(tooltip);
second_title->Wrap(-1);
sizer_input->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
sizer_input->Add(input_title , 0, wxALIGN_CENTER_VERTICAL);
sizer_input->Add(input , 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5));
sizer_input->Add(second_title, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(2));
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);
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()));
onchange(value);
e.Skip();
});
return sizer_input;
}
wxBoxSizer *PreferencesDialog::create_item_spinctrl(wxString title, wxString title2, wxString side_label, wxString tooltip, std::string param, int min, int max, std::function<void(int)> onchange)
{
wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);
auto label = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
label->SetForegroundColour(DESIGN_GRAY900_COLOR);
label->SetFont(::Label::Body_14);
label->SetToolTip(tooltip);
label->Wrap(DESIGN_TITLE_SIZE.x);
label->Wrap(DESIGN_TITLE_SIZE.x);
auto input = new SpinInput(m_parent, wxEmptyString, side_label, wxDefaultPosition, DESIGN_INPUT_SIZE, wxSP_ARROW_KEYS, min, max, stoi(app_config->get(param)));
input->SetToolTip(tooltip);
sizer->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL);
sizer->Add(input, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5));
if(!title2.empty()){
auto second_title = new wxStaticText(m_parent, wxID_ANY, title2, wxDefaultPosition, wxDefaultSize, 0);
second_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
second_title->SetFont(::Label::Body_14);
second_title->SetToolTip(tooltip);
sizer->Add(second_title, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5));
}
input->Bind(wxEVT_TEXT_ENTER, [this, param, input, onchange](wxCommandEvent& e) {
auto value = input->GetValue();
app_config->set(param, std::to_string(value));
app_config->save();
if (onchange != nullptr) onchange(value);
e.Skip();
});
input->Bind(wxEVT_SPINCTRL, [this, param, input, onchange](wxCommandEvent& e) {
auto value = input->GetValue();
app_config->set(param, std::to_string(value));
app_config->save();
if (onchange != nullptr) onchange(value);
e.Skip();
});
input->Bind(wxEVT_KILL_FOCUS, [this, param, input, onchange](wxFocusEvent &e) {
auto value = input->GetValue();
app_config->set(param, std::to_string(value));
if (onchange != nullptr) onchange(value);
e.Skip();
});
return sizer;
}
wxBoxSizer *PreferencesDialog::create_camera_orbit_mult_input(wxString title, wxString tooltip)
{
wxBoxSizer *sizer_input = new wxBoxSizer(wxHORIZONTAL);
auto input_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
input_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
input_title->SetFont(::Label::Body_14);
input_title->SetToolTip(tooltip);
input_title->Wrap(DESIGN_TITLE_SIZE.x);
auto param = "camera_orbit_mult";
auto input = new ::TextInput(m_parent, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER);
StateColor input_bg(std::pair<wxColour, int>(wxColour("#F0F0F1"), StateColor::Disabled), std::pair<wxColour, int>(*wxWHITE, StateColor::Enabled));
input->SetBackgroundColor(input_bg);
input->GetTextCtrl()->SetValue(app_config->get(param));
wxTextValidator validator(wxFILTER_NUMERIC);
input->SetToolTip(tooltip);
input->GetTextCtrl()->SetValidator(validator);
sizer_input->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
sizer_input->Add(input_title, 0, wxALIGN_CENTER_VERTICAL);
sizer_input->Add(input , 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5));
const double min = 0.05;
const double max = 2.0;
input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [this, param, input, min, max](wxCommandEvent &e) {
auto value = input->GetTextCtrl()->GetValue();
double conv = 1.0;
if (value.ToCDouble(&conv)) {
conv = conv < min ? min : conv > max ? max : conv;
auto strval = std::string(wxString::FromCDouble(conv, 2).mb_str());
input->GetTextCtrl()->SetValue(strval);
app_config->set(param, strval);
app_config->save();
}
e.Skip();
});
input->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, [this, param, input, min, max](wxFocusEvent &e) {
auto value = input->GetTextCtrl()->GetValue();
double conv = 1.0;
if (value.ToCDouble(&conv)) {
conv = conv < min ? min : conv > max ? max : conv;
auto strval = std::string(wxString::FromCDouble(conv, 2).mb_str());
input->GetTextCtrl()->SetValue(strval);
app_config->set(param, strval);
}
e.Skip();
});
return sizer_input;
}
wxBoxSizer *PreferencesDialog::create_item_backup(wxString title, wxString tooltip)
{
wxBoxSizer *m_sizer_input = new wxBoxSizer(wxHORIZONTAL);
m_sizer_input->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
auto checkbox_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
checkbox_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
checkbox_title->SetFont(::Label::Body_14);
checkbox_title->Wrap(DESIGN_TITLE_SIZE.x);
checkbox_title->SetToolTip(tooltip);
auto checkbox = new ::CheckBox(m_parent);
checkbox->SetValue(app_config->get_bool("backup_switch"));
checkbox->SetToolTip(tooltip);
checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, checkbox](wxCommandEvent &e) {
app_config->set_bool("backup_switch", checkbox->GetValue());
app_config->save();
bool pbool = app_config->get("backup_switch") == "true" ? true : false;
std::string backup_interval = "10";
app_config->get("backup_interval", backup_interval);
Slic3r::set_backup_interval(pbool ? boost::lexical_cast<long>(backup_interval) : 0);
if (m_backup_interval_textinput != nullptr) { m_backup_interval_textinput->Enable(pbool); }
e.Skip();
});
m_backup_interval_time = app_config->get("backup_interval");
auto input = new ::TextInput(m_parent, wxEmptyString, _L("sec"), "loop", wxDefaultPosition, wxSize(FromDIP(97), -1), wxTE_PROCESS_ENTER);
StateColor input_bg(std::pair<wxColour, int>(wxColour("#F0F0F1"), StateColor::Disabled), std::pair<wxColour, int>(*wxWHITE, StateColor::Enabled));
input->SetBackgroundColor(input_bg);
input->GetTextCtrl()->SetValue(m_backup_interval_time);
wxTextValidator validator(wxFILTER_DIGITS);
input->SetToolTip(_L("The period of backup in seconds."));
input->GetTextCtrl()->SetValidator(validator);
m_sizer_input->Add(checkbox_title, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(3));
m_sizer_input->Add(checkbox , 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(5));
m_sizer_input->Add(input , 0, wxALIGN_CENTER_VERTICAL);
input->GetTextCtrl()->Bind(wxEVT_COMMAND_TEXT_UPDATED, [this, input](wxCommandEvent &e) {
m_backup_interval_time = input->GetTextCtrl()->GetValue();
e.Skip();
});
std::function<void()> backup_interval = [this, 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();
});
input->Enable(app_config->get("backup_switch") == "true");
input->Refresh();
m_backup_interval_textinput = input;
return m_sizer_input;
}
wxBoxSizer *PreferencesDialog::create_item_auto_reslice(wxString title, wxString checkbox_tooltip, wxString delay_tooltip)
{
wxBoxSizer *sizer_row = new wxBoxSizer(wxHORIZONTAL);
sizer_row->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
auto checkbox_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
checkbox_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
checkbox_title->SetFont(::Label::Body_14);
checkbox_title->Wrap(DESIGN_TITLE_SIZE.x);
checkbox_title->SetToolTip(checkbox_tooltip);
auto checkbox = new ::CheckBox(m_parent);
checkbox->SetValue(app_config->get_bool("auto_slice_after_change"));
checkbox->SetToolTip(checkbox_tooltip);
wxString delay_value = app_config->get("auto_slice_change_delay_seconds");
if (delay_value.empty())
delay_value = "0";
auto input = new ::TextInput(m_parent, wxEmptyString, _L("sec"), wxEmptyString, wxDefaultPosition, wxSize(FromDIP(97), -1), wxTE_PROCESS_ENTER);
StateColor input_bg(std::pair<wxColour, int>(wxColour("#F0F0F1"), StateColor::Disabled), std::pair<wxColour, int>(*wxWHITE, StateColor::Enabled));
input->SetBackgroundColor(input_bg);
input->GetTextCtrl()->SetValue(delay_value);
wxTextValidator validator(wxFILTER_DIGITS);
input->SetToolTip(delay_tooltip);
input->GetTextCtrl()->SetValidator(validator);
sizer_row->Add(checkbox_title, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(3));
sizer_row->Add(checkbox, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(5));
sizer_row->Add(input, 0, wxALIGN_CENTER_VERTICAL);
auto commit_delay = [this, input]() {
wxString value = input->GetTextCtrl()->GetValue();
long seconds = 0;
if (!value.ToLong(&seconds) || seconds < 0)
seconds = 0;
wxString sanitized = wxString::Format("%ld", seconds);
input->GetTextCtrl()->SetValue(sanitized);
app_config->set("auto_slice_change_delay_seconds", std::string(sanitized.mb_str()));
app_config->save();
};
input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [commit_delay](wxCommandEvent &e) {
commit_delay();
e.Skip();
});
input->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, [commit_delay](wxFocusEvent &e) {
commit_delay();
e.Skip();
});
checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, checkbox, input](wxCommandEvent &e) {
const bool enabled = checkbox->GetValue();
app_config->set_bool("auto_slice_after_change", enabled);
app_config->save();
input->Enable(enabled);
input->Refresh();
e.Skip();
});
input->Enable(checkbox->GetValue());
input->Refresh();
return sizer_row;
}
wxBoxSizer* PreferencesDialog::create_item_draco(wxString title, wxString side_label, wxString tooltip)
{
wxBoxSizer* sizer_input = new wxBoxSizer(wxHORIZONTAL);
auto input_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
input_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
input_title->SetFont(::Label::Body_14);
input_title->SetToolTip(tooltip);
input_title->Wrap(DESIGN_TITLE_SIZE.x);
input_title->SetToolTip(tooltip);
auto input = new ::TextInput(m_parent, wxEmptyString, side_label, wxEmptyString, wxDefaultPosition, DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER);
StateColor input_bg(std::pair<wxColour, int>(wxColour("#F0F0F1"), StateColor::Disabled),
std::pair<wxColour, int>(*wxWHITE, StateColor::Enabled));
input->SetBackgroundColor(input_bg);
input->GetTextCtrl()->SetValue(app_config->get("drc_bits"));
wxTextValidator validator(wxFILTER_DIGITS);
input->SetToolTip(tooltip);
input->GetTextCtrl()->SetValidator(validator);
sizer_input->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
sizer_input->Add(input_title, 0, wxALIGN_CENTER_VERTICAL);
sizer_input->Add(input , 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5));
std::function<void()> set_draco_bits = [this, input]() {
long drc_bits = DRC_BITS_DEFAULT;
input->GetTextCtrl()->GetValue().ToLong(&drc_bits);
if (drc_bits > DRC_BITS_MAX) {
drc_bits = DRC_BITS_MAX;
input->GetTextCtrl()->SetValue(std::to_string(drc_bits));
} else if (drc_bits < DRC_BITS_MIN && drc_bits != 0) {
drc_bits = DRC_BITS_MIN;
input->GetTextCtrl()->SetValue(std::to_string(drc_bits));
}
app_config->set("drc_bits", std::to_string(drc_bits));
app_config->save();
};
input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [set_draco_bits](wxCommandEvent& e) {
set_draco_bits();
e.Skip();
});
input->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, [set_draco_bits](wxFocusEvent& e) {
set_draco_bits();
e.Skip();
});
return sizer_input;
}
wxBoxSizer* PreferencesDialog::create_item_darkmode(wxString title,wxString tooltip, std::string param)
{
wxBoxSizer* m_sizer_checkbox = new wxBoxSizer(wxHORIZONTAL);
m_sizer_checkbox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
auto checkbox = new ::CheckBox(m_parent);
checkbox->SetValue((app_config->get(param) == "1") ? true : false);
m_dark_mode_ckeckbox = checkbox;
auto checkbox_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
checkbox_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
checkbox_title->SetFont(::Label::Body_14);
checkbox_title->Wrap(DESIGN_TITLE_SIZE.x);
m_sizer_checkbox->Add(checkbox_title, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(3));
m_sizer_checkbox->Add(checkbox , 0, wxALIGN_CENTER | wxRIGHT | wxLEFT, FromDIP(5));
//// save config
checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, checkbox, param](wxCommandEvent& e) {
app_config->set(param, checkbox->GetValue() ? "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
SimpleEvent evt = SimpleEvent(EVT_GLCANVAS_COLOR_MODE_CHANGED);
wxPostEvent(wxGetApp().plater(), evt);
e.Skip();
});
auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty
checkbox_title->SetToolTip(tip);
checkbox->SetToolTip(tip);
return m_sizer_checkbox;
}
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, wxString tooltip, std::string param, const wxString secondary_title)
{
wxBoxSizer *m_sizer_checkbox = new wxBoxSizer(wxHORIZONTAL);
m_sizer_checkbox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
auto tip = tooltip.IsEmpty() ? title : tooltip; // auto fill tooltips with title if its empty
auto checkbox_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
checkbox_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
checkbox_title->SetFont(::Label::Body_14);
checkbox_title->Wrap(DESIGN_TITLE_SIZE.x);
checkbox_title->SetToolTip(tip);
auto checkbox = new ::CheckBox(m_parent);
checkbox->SetValue(app_config->get_bool(param));
checkbox->SetToolTip(tip);
if (param == "sync_user_preset") { m_sync_user_preset_checkbox = checkbox; }
m_sizer_checkbox->Add(checkbox_title, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(3));
m_sizer_checkbox->Add(checkbox , 0, wxALIGN_CENTER | wxRIGHT | wxLEFT, FromDIP(5));
if(!secondary_title.IsEmpty()){
auto sec_title = new wxStaticText(m_parent, wxID_ANY, secondary_title);
sec_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
sec_title->SetFont(::Label::Body_14);
sec_title->Wrap(-1);
sec_title->SetToolTip(tip);
m_sizer_checkbox->Add(sec_title, 0, wxALIGN_CENTER);
}
//// save config
checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, checkbox, param](wxCommandEvent &e) {
app_config->set_bool(param, checkbox->GetValue());
app_config->save();
// if (param == "staff_pick_switch") {
// bool pbool = app_config->get("staff_pick_switch") == "true";
// wxGetApp().switch_staff_pick(pbool);
// }
if (param == "sync_user_preset") {
bool sync = app_config->get("sync_user_preset") == "true" ? true : false;
if (sync) {
wxGetApp().start_sync_user_preset();
} else {
wxGetApp().stop_sync_user_preset();
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " sync_user_preset: " << (sync ? "true" : "false");
}
else if (param == "stealth_mode") {
bool enabled = app_config->get_stealth_mode();
if (enabled) wxGetApp().on_stealth_mode_enter();
if (m_sync_user_preset_checkbox) m_sync_user_preset_checkbox->Enable(!enabled);
if (m_bambu_cloud_checkbox) m_bambu_cloud_checkbox->Enable(!enabled);
}
#ifdef __WXMSW__
if (param == "associate_3mf") {
bool pbool = app_config->get("associate_3mf") == "true" ? true : false;
if (pbool) {
wxGetApp().associate_files(L"3mf");
} else {
wxGetApp().disassociate_files(L"3mf");
}
}
if (param == "associate_drc") {
bool pbool = app_config->get("associate_drc") == "true" ? true : false;
if (pbool) {
wxGetApp().associate_files(L"drc");
} else {
wxGetApp().disassociate_files(L"drc");
}
}
if (param == "associate_stl") {
bool pbool = app_config->get("associate_stl") == "true" ? true : false;
if (pbool) {
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) {
wxGetApp().associate_files(L"step");
} else {
wxGetApp().disassociate_files(L"step");
}
}
if (param == "installed_networking") {
bool pbool = app_config->get_bool("installed_networking");
if (pbool) {
GUI::wxGetApp().CallAfter([] { GUI::wxGetApp().ShowDownNetPluginDlg(); });
}
}
#endif // __WXMSW__
if (param == "developer_mode") {
m_developer_mode_def = app_config->get("developer_mode");
Slic3r::GUI::wxGetApp().update_mode();
}
// 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();
Slic3r::GUI::wxGetApp().mainframe->show_log_window();
} else {
Slic3r::GUI::wxGetApp().update_internal_development();
}
}
if (param == "enable_high_low_temp_mixed_printing") {
if (checkbox->GetValue()) {
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?");
std::function<void(const wxString&)> 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";
const wxString wiki_url = wxString::Format(
L"https://wiki.bambulab.com/%s/filament-acc/filament/h2d-filament-config-limit",
region
);
wxGetApp().open_browser_with_warning_dialog(wiki_url);
};
MessageDialog msg_dialog(
nullptr,
warning_message,
warning_title,
wxICON_WARNING | wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE,
wxEmptyString,
_L("Click Wiki for help."),
link_callback
);
if (msg_dialog.ShowModal() != wxID_YES) {
checkbox->SetValue(false);
app_config->set_bool(param, false);
app_config->save();
}
}
}
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; }
return m_sizer_checkbox;
}
wxBoxSizer* PreferencesDialog::create_item_button(wxString title, wxString title2, wxString tooltip, wxString tooltip2, std::function<void()> onclick)
{
wxBoxSizer *m_sizer_checkbox = new wxBoxSizer(wxHORIZONTAL);
m_sizer_checkbox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
auto m_staticTextPath = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
m_staticTextPath->SetForegroundColour(DESIGN_GRAY900_COLOR);
m_staticTextPath->SetFont(::Label::Body_14);
m_staticTextPath->Wrap(DESIGN_TITLE_SIZE.x);
m_staticTextPath->SetToolTip(tooltip.IsEmpty() ? tooltip2 : tooltip); // use button tooltip if label tooltip empty
auto m_button_download = new Button(m_parent, title2);
m_button_download->SetStyle(title2 == _L("Clear") ? ButtonStyle::Alert : ButtonStyle::Regular, ButtonType::Parameter);
m_button_download->SetToolTip(tooltip2.IsEmpty() ? tooltip : tooltip2); // use label tooltip if button tooltip empty
m_button_download->Bind(wxEVT_BUTTON, [this, onclick](auto &e) { onclick(); });
m_sizer_checkbox->Add(m_staticTextPath , 0, wxALIGN_CENTER_VERTICAL);
m_sizer_checkbox->Add(m_button_download, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5));
return m_sizer_checkbox;
}
wxBoxSizer* PreferencesDialog::create_item_downloads(wxString title, wxString tooltip)
{
wxString download_path = wxString::FromUTF8(app_config->get("download_path"));
wxBoxSizer* m_sizer_checkbox = new wxBoxSizer(wxHORIZONTAL);
wxPanel* label_panel = new wxPanel(m_parent);
wxBoxSizer* label_sizer = new wxBoxSizer(wxHORIZONTAL);
m_sizer_checkbox->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
auto downloads_folder = new wxStaticText(label_panel, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE);
downloads_folder->SetForegroundColour(DESIGN_GRAY900_COLOR);
downloads_folder->SetFont(::Label::Body_14);
downloads_folder->SetToolTip(tooltip);
downloads_folder->Wrap(-1);
auto m_staticTextPath = new wxStaticText(label_panel, wxID_ANY, download_path, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
m_staticTextPath->SetForegroundColour(DESIGN_GRAY600_COLOR);
m_staticTextPath->SetFont(::Label::Body_14);
m_staticTextPath->Wrap(-1);
m_staticTextPath->SetToolTip(download_path);
label_sizer->Add(downloads_folder , 0, wxALIGN_CENTER_VERTICAL);
label_sizer->Add(m_staticTextPath , 0, wxALIGN_CENTER_VERTICAL);
label_panel->SetSize( wxSize(DESIGN_TITLE_SIZE.x, -1));
label_panel->SetMinSize(wxSize(DESIGN_TITLE_SIZE.x, -1));
label_panel->SetMaxSize(wxSize(DESIGN_TITLE_SIZE.x, -1));
label_panel->SetSizer(label_sizer);
label_panel->Layout();
auto m_button_download = new Button(m_parent, _L("Browse") + " " + dots);
m_button_download->SetStyle(ButtonStyle::Regular, ButtonType::Parameter);
m_button_download->SetToolTip(_L("Choose folder for downloaded items"));
m_button_download->Bind(wxEVT_BUTTON, [this, m_staticTextPath, m_sizer_checkbox](auto& e) {
wxString defaultPath = wxT("/");
wxDirDialog dialog(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);
m_staticTextPath->SetLabelText(download_path);
m_staticTextPath->SetToolTip(download_path);
m_sizer_checkbox->Layout();
}
});
m_sizer_checkbox->Add(label_panel , 0, wxALIGN_CENTER_VERTICAL);
m_sizer_checkbox->Add(m_button_download, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5));
return m_sizer_checkbox;
}
#ifdef WIN32
wxBoxSizer* PreferencesDialog::create_item_link_association( wxString url_prefix, wxString website_name)
{
wxString title = _L("Associate") + (boost::format(" %1%://") % url_prefix.c_str()).str();
wxString tooltip = _L("Associate") + " " + url_prefix + ":// " + _L("with OrcaSlicer so that Orca can open models from") + " " + website_name;
std::wstring registered_bin; // not used, just here to provide a ref to check fn
bool reg_to_current_instance = wxGetApp().check_url_association(url_prefix.ToStdWstring(), registered_bin);
auto* h_sizer = new wxBoxSizer(wxHORIZONTAL); // contains checkbox and other elements on the first line
h_sizer->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
// build checkbox
auto checkbox = new ::CheckBox(m_parent);
checkbox->SetToolTip(tooltip);
checkbox->SetValue(reg_to_current_instance); // If registered to the current instance, checkbox should be checked
checkbox->Enable(!reg_to_current_instance); // Since unregistering isn't supported, checkbox is disabled when checked
// build text next to checkbox
auto checkbox_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE);
checkbox_title->SetToolTip(tooltip);
checkbox_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
checkbox_title->SetFont(::Label::Body_14);
checkbox_title->Wrap(-1);
h_sizer->Add(checkbox_title, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(3));
h_sizer->Add(checkbox , 0, wxALIGN_CENTER | wxLEFT , FromDIP(5));
auto* v_sizer = new wxBoxSizer(wxVERTICAL);
v_sizer->Add(h_sizer);
// build text below checkbox that indicates the instance currently registered to handle the link type
auto* registered_instance_title = new wxStaticText(m_parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
registered_instance_title->SetForegroundColour(DESIGN_GRAY600_COLOR);
registered_instance_title->SetFont(::Label::Body_14);
registered_instance_title->Wrap(-1);
// update the text below checkbox
auto update_current_association_str = [=, &reg_to_current_instance](){
// get registered binary for given link type
std::wstring registered_bin;
reg_to_current_instance = wxGetApp().check_url_association(url_prefix.wc_str(), registered_bin);
// format registered binary to get only the path and remove excess chars
if (!registered_bin.empty())
// skip idx 0 because it is the first quotation mark
registered_bin = registered_bin.substr(1, registered_bin.find(L'\"', 1) - 1);
wxString current_association_str = _L("Current Association: ");
if (reg_to_current_instance) {
current_association_str += _L("Current Instance");
registered_instance_title->SetToolTip(_L("Current Instance Path: ") + registered_bin);
} else if (registered_bin.empty())
current_association_str += _L("None");
else{
current_association_str += registered_bin;
registered_instance_title->SetToolTip(current_association_str);
}
registered_instance_title->SetLabel(current_association_str);
registered_instance_title->SetMaxSize(wxSize(DESIGN_WINDOW_SIZE.x - FromDIP(DESIGN_LEFT_MARGIN) - FromDIP(40), -1)); // prevent horizontal scroll
};
update_current_association_str();
v_sizer->Add(registered_instance_title, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(DESIGN_LEFT_MARGIN));
checkbox->Bind(wxEVT_TOGGLEBUTTON, [=](wxCommandEvent& e) {
wxGetApp().associate_url(url_prefix.ToStdWstring());
checkbox->Disable();
update_current_association_str();
e.Skip();
});
return v_sizer;
}
#endif // WIN32
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);
SetMinSize(DESIGN_WINDOW_SIZE);
create();
wxGetApp().UpdateDlgDarkUI(this);
}
void PreferencesDialog::create()
{
app_config = get_app_config();
m_parent = new MyscrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
m_parent->SetScrollRate(5, 5);
m_parent->SetBackgroundColour(*wxWHITE);
m_sizer_body = new wxBoxSizer(wxVERTICAL);
m_pref_tabs = new TabCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_NONE | wxWANTS_CHARS | wxTR_FULL_ROW_HIGHLIGHT);
m_pref_tabs->Bind(wxEVT_RIGHT_DOWN, [this](auto &e) {}); // disable right select
m_pref_tabs->SetFont(Label::Body_14);
create_items();
m_pref_tabs->Bind(wxEVT_TAB_SEL_CHANGED, [this](wxCommandEvent& e) {
Freeze();
#ifdef __linux__
m_pref_tabs->SetFocus();
#endif
int selection = e.GetSelection();
for (size_t i = 0; i < m_pref_tabs->GetCount(); ++i){
m_pref_tabs->SetItemBold(i, i == selection);
f_sizers[i]->Show(i == selection);
}
Layout();
Thaw();
});
auto item_color = StateColor(
std::make_pair(wxColour("#6B6B6C"), (int) StateColor::NotChecked),
std::make_pair(wxColour("#363636"), (int) StateColor::Normal)
);
for (size_t i = 0; i < m_pref_tabs->GetCount(); ++i)
m_pref_tabs->SetItemTextColour(i, item_color);
m_pref_tabs->SelectItem(0);
m_sizer_body->Add(m_pref_tabs, 0, wxEXPAND | wxBOTTOM | wxTOP, FromDIP(5));
m_sizer_body->Add(m_parent, 1, wxEXPAND);
SetSizer(m_sizer_body);
Layout();
Fit();
CenterOnParent();
}
PreferencesDialog::~PreferencesDialog()
{
}
void PreferencesDialog::on_dpi_changed(const wxRect &suggested_rect) { this->Refresh(); }
void PreferencesDialog::Split(const std::string &src, const std::string &separator, std::vector<wxString> &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);
substring = str.substr(start);
dest.push_back(substring);
}
void PreferencesDialog::create_items()
{
// ORCA
// Window focus follows item creation order. so below code has to be in same order with UI
// Create functions for custom controls to keep list clean
// Tooltips added automatically from related title if its empty
wxBoxSizer*sizer_page = new wxBoxSizer(wxVERTICAL);
wxFlexGridSizer* g_sizer; // use same name on all sizers to make easier to ordering without renaming
auto v_gap = FromDIP(4);
//////////////////////////
//// GENERAL TAB
/////////////////////////////////////
m_pref_tabs->AppendItem(_L("General"));
f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0));
g_sizer = f_sizers.back();
g_sizer->AddGrowableCol(0, 1);
//// GENERAL > Settings
g_sizer->Add(create_item_title(_L("Settings")), 1, wxEXPAND);
auto item_language = create_item_language_combobox(_L("Language"), "");
g_sizer->Add(item_language);
std::vector<wxString>Units = {_L("Metric") + " (mm, g)", _L("Imperial") + " (in, oz)"};
auto item_currency = create_item_combobox(_L("Units"), "", "use_inches", Units);
g_sizer->Add(item_currency);
std::vector<wxString> DefaultPage = {_L("Home"), _L("Prepare")};
auto item_default_page = create_item_combobox(_L("Default page"), _L("Set the page opened on startup."), "default_page", DefaultPage);
g_sizer->Add(item_default_page);
#ifdef _WIN32
auto item_darkmode = create_item_darkmode(_L("Enable dark mode"), "", "dark_color_mode");
g_sizer->Add(item_darkmode);
#endif
auto item_single_instance = create_item_checkbox(_L("Allow only one OrcaSlicer instance"),
#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 OrcaSlicer and another instance of the same OrcaSlicer is already running, that instance will be reactivated instead."),
#endif
"single_instance");
g_sizer->Add(item_single_instance);
auto item_show_splash_scr = create_item_checkbox(_L("Show splash screen"), _L("Show the splash screen during startup."), "show_splash_screen");
g_sizer->Add(item_show_splash_scr);
auto item_shared_profiles = create_item_checkbox(_L("Show shared profiles notification"), _L("Show a notification with a link to browse shared profiles when the selected printer is changed."), "show_shared_profiles_notification");
g_sizer->Add(item_shared_profiles);
#ifdef __linux__
auto item_window_button_pos = create_item_checkbox(_L("Use window buttons on left side"), "", "window_buttons_on_left", _L("(Requires restart)"));
g_sizer->Add(item_window_button_pos);
#endif
//auto item_hints = create_item_checkbox(_L("Show \"Daily Tips\" after start"), page, _L("If enabled, useful hints are displayed at startup."), "show_daily_tips");
//g_sizer->Add(item_hints);
auto item_downloads = create_item_downloads(_L("Downloads folder") + ": ", _L("Target folder for downloaded items"));
g_sizer->Add(item_downloads);
//// GENERAL > Project
g_sizer->Add(create_item_title(_L("Project")), 1, wxEXPAND);
std::vector<wxString> projectLoadSettingsBehaviourOptions = {_L("Load All"), _L("Ask When Relevant"), _L("Always Ask"), _L("Load Geometry Only")};
std::vector<string> projectLoadSettingsConfigOptions = { OPTION_PROJECT_LOAD_BEHAVIOUR_LOAD_ALL, OPTION_PROJECT_LOAD_BEHAVIOUR_ASK_WHEN_RELEVANT, OPTION_PROJECT_LOAD_BEHAVIOUR_ALWAYS_ASK, OPTION_PROJECT_LOAD_BEHAVIOUR_LOAD_GEOMETRY };
auto item_project_load = create_item_combobox(_L("Load behaviour"), _L("Should printer/filament/process settings be loaded when opening a 3MF file?"), SETTING_PROJECT_LOAD_BEHAVIOUR, projectLoadSettingsBehaviourOptions, projectLoadSettingsConfigOptions);
g_sizer->Add(item_project_load);
auto item_max_recent_count = create_item_input(_L("Maximum recent files"), "", _L("Maximum count of recent files"), "max_recent_count", [](wxString value) {
long max = 0;
if (value.ToLong(&max))
wxGetApp().mainframe->set_max_recent_count(max);
});
g_sizer->Add(item_max_recent_count);
auto item_recent_models = create_item_checkbox(_L("Add STL/STEP files to recent files list"), "", "recent_models");
g_sizer->Add(item_recent_models);
auto item_gcodes_warning = create_item_checkbox(_L("Don't warn when loading 3MF with modified G-code"), "", "no_warn_when_modified_gcodes");
g_sizer->Add(item_gcodes_warning);
auto item_step_dialog = create_item_checkbox(_L("Show options when importing STEP file"), _L("If enabled, a parameter settings dialog will appear during STEP file import."), "enable_step_mesh_setting");
g_sizer->Add(item_step_dialog);
auto item_backup = create_item_backup(_L("Auto backup"), _L("Backup your project periodically for restoring from the occasional crash."));
g_sizer->Add(item_backup);
//// GENERAL > Preset
g_sizer->Add(create_item_title(_L("Preset")), 1, wxEXPAND);
auto item_remember_printer = create_item_checkbox(_L("Remember printer configuration"), _L("If enabled, Orca will remember and switch filament/process configuration for each printer automatically."), "remember_printer_config");
g_sizer->Add(item_remember_printer);
auto item_filament_preset_grouping = create_item_combobox(_L("Group user filament presets"), _L("Group user filament presets based on selection"),
"group_filament_presets", {_L("All"), _L("None"), _L("By type"), _L("By vendor")}, [](wxString value) {wxGetApp().plater()->sidebar().update_presets(Preset::TYPE_FILAMENT);});
g_sizer->Add(item_filament_preset_grouping);
// prevent burst calling on keyboard / spin events
m_filament_height_timer.Bind(wxEVT_TIMER, [this](wxTimerEvent&) {
wxGetApp().plater()->sidebar().update_filaments_area_height();
UpdateSidebarLayout();
});
auto item_filament_area_height = create_item_spinctrl(_L("Optimize filaments area height for..."), "", _L("filaments"), _L("Optimizes filament area maximum height by chosen filament count."),
"filaments_area_preferred_count", 8, 99, [this](int value) {m_filament_height_timer.StartOnce(500);});
g_sizer->Add(item_filament_area_height);
//// GENERAL > Features
g_sizer->Add(create_item_title(_L("Features")), 1, wxEXPAND);
auto item_multi_machine = create_item_checkbox(_L("Multi device management"), _L("With this option enabled, you can send a task to multiple devices at the same time and manage multiple devices."), "enable_multi_machine", _L("(Requires restart)"));
g_sizer->Add(item_multi_machine);
#if 0
g_sizer->Add(create_item_title(_L("Filament Grouping")), 1, wxEXPAND);
//temporarily disable it
//auto item_ignore_ext_filament = create_item_checkbox(_L("Ignore ext filament when auto grouping"), _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"), _L("Pop up to select filament grouping mode"), 50, "pop_up_filament_map_dialog");
g_sizer->Add(item_pop_up_filament_map_dialog);
#endif
auto item_draco_bits = create_item_draco(_L("Quality level for Draco export"),
_L("bits"),
_L("Controls the quantization bit depth used when compressing the mesh to Draco format.\n"
"0 = lossless compression (geometry is preserved at full precision). Valid lossy values range from 8 to 30.\n"
"Lower values produce smaller files but lose more geometric detail; higher values preserve more detail at the cost of larger files."));
g_sizer->Add(item_draco_bits);
g_sizer->AddSpacer(FromDIP(10));
sizer_page->Add(g_sizer, 0, wxEXPAND);
//////////////////////////
//// CONTROL TAB
/////////////////////////////////////
m_pref_tabs->AppendItem(_L("Control"));
f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0));
g_sizer = f_sizers.back();
g_sizer->AddGrowableCol(0, 1);
//// CONTROL > Behaviour
g_sizer->Add(create_item_title(_L("Behaviour")), 1, wxEXPAND);
std::vector<wxString> FlushOptionLabels = {_L("All"),_L("Color"),_L("None")};
std::vector<std::string> FlushOptionValues = { "all","color change","disabled" };
auto item_auto_flush = create_item_combobox(_L("Auto flush after changing..."), _L("Auto calculate flushing volumes when selected values changed"), "auto_calculate_flush", FlushOptionLabels, FlushOptionValues);
g_sizer->Add(item_auto_flush);
auto item_auto_arrange = create_item_checkbox(_L("Auto arrange plate after cloning"), "", "auto_arrange");
g_sizer->Add(item_auto_arrange);
//// CONTROL > Slicing
g_sizer->Add(create_item_title(_L("Slicing")), 1, wxEXPAND);
auto item_auto_reslice = create_item_auto_reslice(
_L("Auto slice after changes"),
_L("If enabled, OrcaSlicer will re-slice automatically whenever slicing-related settings change."),
_L("Delay in seconds before auto slicing starts, allowing multiple edits to be grouped. Use 0 to slice immediately."));
g_sizer->Add(item_auto_reslice);
auto item_mix_print_high_low_temperature = create_item_checkbox(_L("Remove mixed temperature restriction"), _L("With this option enabled, you can print materials with a large temperature difference together."), "enable_high_low_temp_mixed_printing");
g_sizer->Add(item_mix_print_high_low_temperature);
//// CONTROL > Camera
g_sizer->Add(create_item_title(_L("Camera")), 1, wxEXPAND);
std::vector<wxString> CameraNavStyle = {_L("Default"), _L("Touchpad")};
auto item_camera_nav_style = create_item_combobox(_L("Camera style"), _L("Select camera navigation style.\nDefault: LMB+move for rotation, RMB/MMB+move for panning.\nTouchpad: Alt+move for rotation, Shift+move for panning."), "camera_navigation_style", CameraNavStyle);
g_sizer->Add(item_camera_nav_style);
auto camera_orbit_mult = create_camera_orbit_mult_input(_L("Orbit speed multiplier"), _L("Multiplies the orbit speed for finer or coarser camera movement."));
g_sizer->Add(camera_orbit_mult);
auto item_zoom_to_mouse = create_item_checkbox(_L("Zoom to mouse position"), _L("Zoom in towards the mouse pointer's position in the 3D view, rather than the 2D window center."), "zoom_to_mouse");
g_sizer->Add(item_zoom_to_mouse);
auto item_use_free_camera = create_item_checkbox(_L("Use free camera"), _L("If enabled, use free camera. If not enabled, use constrained camera."), "use_free_camera");
g_sizer->Add(item_use_free_camera);
auto swap_pan_rotate = create_item_checkbox(_L("Swap pan and rotate mouse buttons"), _L("If enabled, swaps the left and right mouse buttons pan and rotate functions."), "swap_mouse_buttons");
g_sizer->Add(swap_pan_rotate);
auto reverse_mouse_zoom = create_item_checkbox(_L("Reverse mouse zoom"), _L("If enabled, reverses the direction of zoom with mouse wheel."), "reverse_mouse_wheel_zoom");
g_sizer->Add(reverse_mouse_zoom);
//// CONTROL > Clear my choice on ...
g_sizer->Add(create_item_title(_L("Clear my choice on...")), 1, wxEXPAND);
auto item_save_choise = create_item_button(_L("Unsaved projects"), _L("Clear"), "", _L("Clear my choice on the unsaved projects."), []() {
wxGetApp().app_config->set("save_project_choise", "");
});
g_sizer->Add(item_save_choise);
auto item_save_presets = create_item_button(_L("Unsaved presets"), _L("Clear"), "", _L("Clear my choice on the unsaved presets."), []() {
wxGetApp().app_config->set("save_preset_choise", "");
});
g_sizer->Add(item_save_presets);
auto item_restore_hide_pop_ups = create_item_button(_L("Synchronizing printer preset"), _L("Clear"), L"", _L("Clear my choice for synchronizing printer preset after loading the file."), []() {
wxGetApp().app_config->erase("app", "sync_after_load_file_show_flag");
});
g_sizer->Add(item_restore_hide_pop_ups);
g_sizer->AddSpacer(FromDIP(10));
sizer_page->Add(g_sizer, 0, wxEXPAND);
//////////////////////////
//// ONLINE TAB
/////////////////////////////////////
m_pref_tabs->AppendItem(_L("Online"));
f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0));
g_sizer = f_sizers.back();
g_sizer->AddGrowableCol(0, 1);
//// ONLINE > Connection
g_sizer->Add(create_item_title(_L("Connection")), 1, wxEXPAND);
auto item_region = create_item_region_combobox(_L("Login region"), "");
g_sizer->Add(item_region);
auto item_stealth_mode = create_item_checkbox(_L("Stealth mode"), _L("This disables all cloud services e.g. Orca Cloud and Bambu Cloud. This stops the transmission of data to Bambu's cloud services too. Users who don't use BBL machines or use LAN mode only can safely turn on this function."), "stealth_mode");
g_sizer->Add(item_stealth_mode);
auto item_network_test = create_item_button(_L("Network test"), _L("Test") + " " + dots, "", _L("Open Network Test"), []() {
NetworkTestDialog dlg(wxGetApp().mainframe);
dlg.ShowModal();
});
g_sizer->Add(item_network_test);
//// ONLINE > Cloud Providers
g_sizer->Add(create_item_title(_L("Cloud Providers")), 1, wxEXPAND);
{
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
auto text = new wxStaticText(m_parent, wxID_ANY, _L("Enable Bambu Cloud"),
wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
text->SetForegroundColour(DESIGN_GRAY900_COLOR);
text->SetFont(::Label::Body_14);
text->SetToolTip(_L("Allow logging into Bambu Cloud alongside Orca Cloud. When enabled, a Bambu login section appears on the homepage."));
text->Wrap(DESIGN_TITLE_SIZE.x);
auto cb = new ::CheckBox(m_parent);
m_bambu_cloud_checkbox = cb;
cb->SetValue(app_config->has_cloud_provider(BBL_CLOUD_PROVIDER));
cb->SetToolTip(text->GetToolTipText());
cb->Bind(wxEVT_TOGGLEBUTTON, [this, cb](wxCommandEvent &e) {
e.Skip(); // let CheckBox::update() refresh the bitmap
if (cb->GetValue()) {
app_config->add_cloud_provider(BBL_CLOUD_PROVIDER);
} else {
app_config->remove_cloud_provider(BBL_CLOUD_PROVIDER);
}
app_config->save();
// Update homepage visibility immediately
auto *mainframe = wxGetApp().mainframe;
if (mainframe && mainframe->m_webview)
mainframe->m_webview->SendCloudProvidersInfo();
});
sizer->Add(text, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(3));
sizer->Add(cb, 0, wxALIGN_CENTER | wxRIGHT | wxLEFT, FromDIP(5));
g_sizer->Add(sizer);
}
//// ONLINE > Update & sync
g_sizer->Add(create_item_title(_L("Update & sync")), 1, wxEXPAND);
auto item_stable_updates = create_item_checkbox(_L("Check for stable updates only"), "", "check_stable_update_only");
g_sizer->Add(item_stable_updates);
auto item_user_sync = create_item_checkbox(_L("Auto sync user presets (Printer/Filament/Process)"), "", "sync_user_preset");
g_sizer->Add(item_user_sync);
if (app_config->get_stealth_mode()) {
if (m_bambu_cloud_checkbox) m_bambu_cloud_checkbox->Enable(false);
if (m_sync_user_preset_checkbox) m_sync_user_preset_checkbox->Enable(false);
}
auto item_system_sync = create_item_checkbox(_L("Update built-in Presets automatically."), "", "sync_system_preset");
g_sizer->Add(item_system_sync);
auto item_token_storage = create_item_checkbox(_L("Use encrypted file for token storage"),
_L("Store authentication tokens in an encrypted file instead of the system keychain. (Requires restart)"),
SETTING_USE_ENCRYPTED_TOKEN_FILE);
g_sizer->Add(item_token_storage);
//// ONLINE > Filament Sync Options
g_sizer->Add(create_item_title(_L("Filament Sync Options")), 1, wxEXPAND);
auto item_filament_sync_mode = create_item_combobox(
_L("Filament sync mode"),
_L("Choose whether sync updates both filament preset and color, or only color."),
"sync_ams_filament_mode",
{_L("Filament & Color"), _L("Color only")});
g_sizer->Add(item_filament_sync_mode);
//// ONLINE > Network plugin
g_sizer->Add(create_item_title(_L("Network plug-in")), 1, wxEXPAND);
auto item_enable_plugin = create_item_checkbox(_L("Enable network plug-in"), "", "installed_networking");
g_sizer->Add(item_enable_plugin);
m_network_version_sizer = new wxBoxSizer(wxHORIZONTAL);
m_network_version_sizer->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
auto version_title = new wxStaticText(m_parent, wxID_ANY, _L("Network plug-in version"), wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
version_title->SetForegroundColour(DESIGN_GRAY900_COLOR);
version_title->SetFont(::Label::Body_14);
version_title->SetToolTip(_L("Select the network plug-in version to use"));
version_title->Wrap(DESIGN_TITLE_SIZE.x);
m_network_version_sizer->Add(version_title, 0, wxALIGN_CENTER);
m_network_version_combo = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(180), -1), 0, nullptr, wxCB_READONLY);
m_network_version_combo->SetFont(::Label::Body_14);
m_network_version_combo->GetDropDown().SetFont(::Label::Body_14);
std::string current_version = app_config->get_network_plugin_version();
if (current_version.empty()) {
current_version = get_latest_network_version();
}
int current_selection = 0;
m_available_versions = get_all_available_versions();
for (size_t i = 0; i < m_available_versions.size(); i++) {
const auto& ver = m_available_versions[i];
wxString label;
if (!ver.suffix.empty()) {
label = wxString::FromUTF8("\xE2\x94\x94 ") + wxString::FromUTF8(ver.display_name);
} else {
label = wxString::FromUTF8(ver.display_name);
}
if (ver.is_latest) {
label += " " + _L("(Latest)");
}
m_network_version_combo->Append(label);
if (current_version == ver.version) {
current_selection = i;
}
}
m_network_version_combo->SetSelection(current_selection);
m_network_version_sizer->Add(m_network_version_combo, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5));
m_network_version_combo->GetDropDown().Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& e) {
int selection = e.GetSelection();
if (selection >= 0 && selection < (int)m_available_versions.size()) {
const auto& selected_ver = m_available_versions[selection];
std::string new_version = selected_ver.version;
std::string old_version = app_config->get_network_plugin_version();
if (old_version.empty()) {
old_version = get_latest_network_version();
}
app_config->set(SETTING_NETWORK_PLUGIN_VERSION, new_version);
app_config->save();
if (new_version != old_version) {
BOOST_LOG_TRIVIAL(info) << "Network plugin version changed from " << old_version << " to " << new_version;
// Update the use_legacy_network flag immediately
bool is_legacy = (new_version == BAMBU_NETWORK_AGENT_VERSION_LEGACY);
bool was_legacy = (old_version == BAMBU_NETWORK_AGENT_VERSION_LEGACY);
if (is_legacy != was_legacy) {
Slic3r::NetworkAgent::use_legacy_network = is_legacy;
BOOST_LOG_TRIVIAL(info) << "Updated use_legacy_network flag to " << is_legacy;
}
if (!selected_ver.warning.empty()) {
MessageDialog warn_dlg(this, wxString::FromUTF8(selected_ver.warning), _L("Warning"), wxOK | wxCANCEL | wxICON_WARNING);
if (warn_dlg.ShowModal() != wxID_OK) {
app_config->set(SETTING_NETWORK_PLUGIN_VERSION, old_version);
app_config->save();
Slic3r::NetworkAgent::use_legacy_network = was_legacy;
e.Skip();
return;
}
}
// Check if the selected version already exists on disk
if (Slic3r::NetworkAgent::versioned_library_exists(new_version)) {
BOOST_LOG_TRIVIAL(info) << "Version " << new_version << " already exists on disk, triggering hot reload";
if (wxGetApp().hot_reload_network_plugin()) {
MessageDialog dlg(this, _L("Network plug-in switched successfully."), _L("Success"), wxOK | wxICON_INFORMATION);
dlg.ShowModal();
} else {
MessageDialog dlg(this, _L("Failed to load network plug-in. Please restart the application."), _L("Restart Required"), wxOK | wxICON_WARNING);
dlg.ShowModal();
}
} else {
wxString msg = wxString::Format(
_L("You've selected network plug-in version %s.\n\nWould you like to download and install this version now?\n\nNote: The application may need to restart after installation."),
wxString::FromUTF8(new_version));
MessageDialog dlg(this, msg, _L("Download Network Plug-in"), wxYES_NO | wxICON_QUESTION);
if (dlg.ShowModal() == wxID_YES) {
DownloadProgressDialog progress_dlg(_L("Downloading Network Plug-in"));
progress_dlg.ShowModal();
}
}
}
}
e.Skip();
});
g_sizer->Add(m_network_version_sizer);
g_sizer->AddSpacer(FromDIP(10));
sizer_page->Add(g_sizer, 0, wxEXPAND);
//////////////////////////
//// ASSOCIATE TAB
/////////////////////////////////////
#ifdef _WIN32
m_pref_tabs->AppendItem(_L("Associate"));
f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0));
g_sizer = f_sizers.back();
g_sizer->AddGrowableCol(0, 1);
//// ASSOCIATE > Extensions
g_sizer->Add(create_item_title(_L("Associate files to OrcaSlicer")), 1, wxEXPAND);
auto item_associate_3mf = create_item_checkbox(_L("Associate 3MF files to OrcaSlicer"), _L("If enabled, sets OrcaSlicer as default application to open 3MF files.") , "associate_3mf");
g_sizer->Add(item_associate_3mf);
auto item_associate_drc = create_item_checkbox(_L("Associate DRC files to OrcaSlicer"), _L("If enabled, sets OrcaSlicer as default application to open DRC files."), "associate_drc");
g_sizer->Add(item_associate_drc);
auto item_associate_stl = create_item_checkbox(_L("Associate STL files to OrcaSlicer"), _L("If enabled, sets OrcaSlicer as default application to open STL files.") , "associate_stl");
g_sizer->Add(item_associate_stl);
auto item_associate_step = create_item_checkbox(_L("Associate STEP files to OrcaSlicer"), _L("If enabled, sets OrcaSlicer as default application to open STEP files."), "associate_step");
g_sizer->Add(item_associate_step);
//// ASSOCIATE > WebLinks
g_sizer->Add(create_item_title(_L("Associate web links to OrcaSlicer")), 1, wxEXPAND);
auto associate_url_prusa = create_item_link_association(L"prusaslicer", "Printables.com");
g_sizer->Add(associate_url_prusa);
auto associate_url_bambu = create_item_link_association(L"bambustudio", "Makerworld.com");
g_sizer->Add(associate_url_bambu);
auto associate_url_cura = create_item_link_association(L"cura", "Thingiverse.com");
g_sizer->Add(associate_url_cura);
g_sizer->AddSpacer(FromDIP(10));
sizer_page->Add(g_sizer, 0, wxEXPAND);
#endif // _WIN32
//////////////////////////
//// DEVELOPER TAB
/////////////////////////////////////
m_pref_tabs->AppendItem(_L("Developer"));
f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0));
g_sizer = f_sizers.back();
g_sizer->AddGrowableCol(0, 1);
//// DEVELOPER > Settings
g_sizer->Add(create_item_title(_L("Settings")), 1, wxEXPAND);
auto item_develop_mode = create_item_checkbox(_L("Develop mode"), "", "developer_mode");
g_sizer->Add(item_develop_mode);
auto item_ams_blacklist = create_item_checkbox(_L("Skip AMS blacklist check"), "", "skip_ams_blacklist_check");
g_sizer->Add(item_ams_blacklist);
g_sizer->Add(create_item_title(_L("Storage")), 1, wxEXPAND);
auto item_allow_abnormal_storage = create_item_checkbox(_L("Allow Abnormal Storage"), _L("This allows the use of Storage that is marked as abnormal by the Printer.\nUse at your own risk, can cause issues!"), "allow_abnormal_storage");
g_sizer->Add(item_allow_abnormal_storage);
g_sizer->Add(create_item_title(_L("Log Level")), 1, wxEXPAND);
auto log_level_list = std::vector<wxString>{_L("fatal"), _L("error"), _L("warning"), _L("info"), _L("debug"), _L("trace")};
auto loglevel_combox = create_item_loglevel_combobox(_L("Log Level"), _L("Log Level"), log_level_list);
g_sizer->Add(loglevel_combox);
g_sizer->Add(create_item_title(_L("Network plug-in")), 1, wxEXPAND);
auto item_reload_plugin = create_item_button(_L("Network plug-in"), _L("Reload"), _L("Reload the network plug-in without restarting the application"), "", [this]() {
if (wxGetApp().hot_reload_network_plugin()) {
MessageDialog dlg(this, _L("Network plug-in reloaded successfully."), _L("Reload"), wxOK | wxICON_INFORMATION);
dlg.ShowModal();
} else {
MessageDialog dlg(this, _L("Failed to reload network plug-in. Please restart the application."), _L("Reload Failed"), wxOK | wxICON_ERROR);
dlg.ShowModal();
}
});
g_sizer->Add(item_reload_plugin);
//// DEVELOPER > Debug
#if !BBL_RELEASE_TO_PUBLIC
g_sizer->Add(create_item_title(_L("Debug")), 1, wxEXPAND);
auto debug_page = create_debug_page();
g_sizer->Add(debug_page, 1, wxEXPAND);
#endif
g_sizer->AddSpacer(FromDIP(10));
sizer_page->Add(g_sizer, 0, wxEXPAND);
/////////////////////////////////////
//////////////////////////
g_sizer = nullptr;
// Hide all tabs instead first one
for (size_t i = 1; i < f_sizers.size(); ++i)
f_sizers[i]->Show(false);
/////////////////////////////////////
//////////////////////////
m_parent->SetSizer(sizer_page);
m_parent->Layout();
sizer_page->Fit(m_parent);
}
void PreferencesDialog::create_sync_page()
{
auto page = new wxWindow(this, wxID_ANY);
wxBoxSizer *sizer_page = new wxBoxSizer(wxVERTICAL);
auto title_sync_settingy = create_item_title(_L("Sync settings"));
auto item_user_sync = create_item_checkbox(_L("User sync"), _L("User sync"), "user_sync_switch");
auto item_preset_sync = create_item_checkbox(_L("Preset sync"), _L("Preset sync"), "preset_sync_switch");
auto item_preferences_sync = create_item_checkbox(_L("Preferences sync"), _L("Preferences sync"), "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);
}
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"));
std::vector<wxString> keyboard_supported;
Split(app_config->get("keyboard_supported"), "/", keyboard_supported);
std::vector<wxString> mouse_supported;
Split(app_config->get("mouse_supported"), "/", mouse_supported);
auto item_rotate_view = create_item_multiple_combobox(_L("Rotate view"), _L("Rotate view"), "rotate_view", keyboard_supported,
mouse_supported);
auto item_move_view = create_item_multiple_combobox(_L("Pan view"), _L("Pan view"), "move_view", keyboard_supported, mouse_supported);
auto item_zoom_view = create_item_multiple_combobox(_L("Zoom view"), _L("Zoom view"), "rotate_view", keyboard_supported, mouse_supported);
auto title_other = create_item_title(_L("Other"));
auto item_other = create_item_checkbox(_L("Mouse wheel reverses when zooming"), _L("Mouse wheel reverses when zooming"), "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);
}
wxBoxSizer* PreferencesDialog::create_debug_page()
{
m_internal_developer_mode_def = app_config->get("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);
auto enable_ssl_for_mqtt = create_item_checkbox(_L("Enable SSL(MQTT)"), _L("Enable SSL(MQTT)"), "enable_ssl_for_mqtt");
auto enable_ssl_for_ftp = create_item_checkbox(_L("Enable SSL(FTP)"), _L("Enable SSL(MQTT)"), "enable_ssl_for_ftp");
auto item_internal_developer = create_item_checkbox(_L("Internal developer mode"), _L("Internal developer mode"), "internal_developer_mode");
auto title_host = create_item_title(_L("Host Setting"));
// ORCA RadioGroup
auto radio_group = new RadioGroup(m_parent, {
_L("DEV host: api-dev.bambu-lab.com/v1"), // 0
_L("QA host: api-qa.bambu-lab.com/v1"), // 1
_L("PRE host: api-pre.bambu-lab.com/v1"), // 2
_L("Product host") // 3
}, wxVERTICAL);
radio_group->SetRadioTooltip(0, "dev_host");
radio_group->SetRadioTooltip(1, "qa_host");
radio_group->SetRadioTooltip(2, "pre_host");
radio_group->SetRadioTooltip(3, "product_host");
if (m_iot_environment_def == ENV_DEV_HOST) {
radio_group->SetSelection(0);
} else if (m_iot_environment_def == ENV_QAT_HOST) {
radio_group->SetSelection(1);
} else if (m_iot_environment_def == ENV_PRE_HOST) {
radio_group->SetSelection(2);
} else if (m_iot_environment_def == ENV_PRODUCT_HOST) {
radio_group->SetSelection(3);
}
Button* debug_button = new Button(m_parent, _L("Debug save button"));
debug_button->SetStyle(ButtonStyle::Confirm, ButtonType::Window);
debug_button->Bind(wxEVT_LEFT_DOWN, [this, radio_group](wxMouseEvent &e) {
// success message box
MessageDialog dialog(this, _L("Save debug settings"), _L("DEBUG settings have been 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_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_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) {
radio_group->SetSelection(0);
} else if (m_iot_environment_def == ENV_QAT_HOST) {
radio_group->SetSelection(1);
} else if (m_iot_environment_def == ENV_PRE_HOST) {
radio_group->SetSelection(2);
} else if (m_iot_environment_def == ENV_PRODUCT_HOST) {
radio_group->SetSelection(3);
}
break;
}
case wxID_YES: {
// bbs domain changed
auto param = radio_group->GetSelection();
std::map<wxString, wxString> 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 == 0) { // "dev_host"
app_config->set("iot_environment", ENV_DEV_HOST);
}
else if (param == 1) { // "qa_host"
app_config->set("iot_environment", ENV_QAT_HOST);
}
else if (param == 2) { // "pre_host"
app_config->set("iot_environment", ENV_PRE_HOST);
}
else if (param == 3) { // "product_host"
app_config->set("iot_environment", ENV_PRODUCT_HOST);
}
AppConfig* config = GUI::wxGetApp().app_config;
std::string country_code = config->get_country_code();
if (agent) {
wxGetApp().request_user_logout();
agent->set_country_code(country_code);
}
ConfirmBeforeSendDialog confirm_dlg(this, wxID_ANY, _L("Warning"), ConfirmBeforeSendDialog::VisibleButtons::ONLY_CONFIRM); // ORCA VisibleButtons instead ButtonStyle
confirm_dlg.update_text(_L("Cloud environment switched, please login again!"));
confirm_dlg.on_show();
}
// 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<long>(app_config->get("backup_interval")));
this->Close();
break;
}
}
});
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_host, 0, wxEXPAND | wxTOP, FromDIP(10));
bSizer->Add(radio_group, 0, wxEXPAND | wxLEFT, FromDIP(DESIGN_LEFT_MARGIN));
bSizer->Add(debug_button, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, FromDIP(15));
return bSizer;
}
void PreferencesDialog::UpdateSidebarLayout()
{
Plater* plater = wxGetApp().plater();
if (!plater) return;
Sidebar& sidebar = plater->sidebar();
sidebar.Freeze();
sidebar.Layout();
//plater->Layout();
//wxGetApp().mainframe->Layout();
sidebar.Thaw();
plater->PostSizeEvent();
}
}} // namespace Slic3r::GUI