1 #include "Preferences.hpp"
2 #include "OptionsGroup.hpp"
3 #include "GUI_App.hpp"
4 #include "Plater.hpp"
5 #include "I18N.hpp"
6 #include "libslic3r/AppConfig.hpp"
7 #include <wx/notebook.h>
8
9 namespace Slic3r {
10 namespace GUI {
11
PreferencesDialog(wxWindow * parent)12 PreferencesDialog::PreferencesDialog(wxWindow* parent) :
13 DPIDialog(parent, wxID_ANY, _L("Preferences"), wxDefaultPosition,
14 wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
15 {
16 #ifdef __WXOSX__
17 isOSX = true;
18 #endif
19 build();
20 }
21
create_options_tab(const wxString & title,wxNotebook * tabs)22 static std::shared_ptr<ConfigOptionsGroup>create_options_tab(const wxString& title, wxNotebook* tabs)
23 {
24 wxPanel* tab = new wxPanel(tabs, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
25 tabs->AddPage(tab, title);
26 tab->SetFont(wxGetApp().normal_font());
27
28 wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
29 sizer->SetSizeHints(tab);
30 tab->SetSizer(sizer);
31
32 std::shared_ptr<ConfigOptionsGroup> optgroup = std::make_shared<ConfigOptionsGroup>(tab);
33 optgroup->label_width = 40;
34 return optgroup;
35 }
36
activate_options_tab(std::shared_ptr<ConfigOptionsGroup> optgroup)37 static void activate_options_tab(std::shared_ptr<ConfigOptionsGroup> optgroup)
38 {
39 optgroup->activate();
40 optgroup->update_visibility(comSimple);
41 wxBoxSizer* sizer = static_cast<wxBoxSizer*>(static_cast<wxPanel*>(optgroup->parent())->GetSizer());
42 sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 20);
43 }
44
build()45 void PreferencesDialog::build()
46 {
47 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
48 const wxFont& font = wxGetApp().normal_font();
49 SetFont(font);
50
51 auto app_config = get_app_config();
52
53 wxNotebook* tabs = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME);
54
55 // Add "General" tab
56 m_optgroup_general = create_options_tab(_L("General"), tabs);
57 m_optgroup_general->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
58 if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset")
59 m_values[opt_key] = boost::any_cast<bool>(value) ? "none" : "discard";
60 else
61 m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
62 };
63
64 bool is_editor = wxGetApp().is_editor();
65
66 ConfigOptionDef def;
67 Option option(def, "");
68 if (is_editor) {
69 def.label = L("Remember output directory");
70 def.type = coBool;
71 def.tooltip = L("If this is enabled, Slic3r will prompt the last output directory "
72 "instead of the one containing the input files.");
73 def.set_default_value(new ConfigOptionBool{ app_config->has("remember_output_path") ? app_config->get("remember_output_path") == "1" : true });
74 option = Option(def, "remember_output_path");
75 m_optgroup_general->append_single_option_line(option);
76
77 def.label = L("Auto-center parts");
78 def.type = coBool;
79 def.tooltip = L("If this is enabled, Slic3r will auto-center objects "
80 "around the print bed center.");
81 def.set_default_value(new ConfigOptionBool{ app_config->get("autocenter") == "1" });
82 option = Option(def, "autocenter");
83 m_optgroup_general->append_single_option_line(option);
84
85 def.label = L("Background processing");
86 def.type = coBool;
87 def.tooltip = L("If this is enabled, Slic3r will pre-process objects as soon "
88 "as they\'re loaded in order to save time when exporting G-code.");
89 def.set_default_value(new ConfigOptionBool{ app_config->get("background_processing") == "1" });
90 option = Option(def, "background_processing");
91 m_optgroup_general->append_single_option_line(option);
92
93 // Please keep in sync with ConfigWizard
94 def.label = L("Check for application updates");
95 def.type = coBool;
96 def.tooltip = L("If enabled, PrusaSlicer will check for the new versions of itself online. When a new version becomes available a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done.");
97 def.set_default_value(new ConfigOptionBool(app_config->get("version_check") == "1"));
98 option = Option(def, "version_check");
99 m_optgroup_general->append_single_option_line(option);
100
101 // Please keep in sync with ConfigWizard
102 def.label = L("Export sources full pathnames to 3mf and amf");
103 def.type = coBool;
104 def.tooltip = L("If enabled, allows the Reload from disk command to automatically find and load the files when invoked.");
105 def.set_default_value(new ConfigOptionBool(app_config->get("export_sources_full_pathnames") == "1"));
106 option = Option(def, "export_sources_full_pathnames");
107 m_optgroup_general->append_single_option_line(option);
108
109 #if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN
110 #ifdef _WIN32
111 // Please keep in sync with ConfigWizard
112 def.label = L("Associate .3mf files to PrusaSlicer");
113 def.type = coBool;
114 def.tooltip = L("If enabled, sets PrusaSlicer as default application to open .3mf files.");
115 def.set_default_value(new ConfigOptionBool(app_config->get("associate_3mf") == "1"));
116 option = Option(def, "associate_3mf");
117 m_optgroup_general->append_single_option_line(option);
118
119 def.label = L("Associate .stl files to PrusaSlicer");
120 def.type = coBool;
121 def.tooltip = L("If enabled, sets PrusaSlicer as default application to open .stl files.");
122 def.set_default_value(new ConfigOptionBool(app_config->get("associate_stl") == "1"));
123 option = Option(def, "associate_stl");
124 m_optgroup_general->append_single_option_line(option);
125 #endif // _WIN32
126 #endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN
127
128 // Please keep in sync with ConfigWizard
129 def.label = L("Update built-in Presets automatically");
130 def.type = coBool;
131 def.tooltip = L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup.");
132 def.set_default_value(new ConfigOptionBool(app_config->get("preset_update") == "1"));
133 option = Option(def, "preset_update");
134 m_optgroup_general->append_single_option_line(option);
135
136 def.label = L("Suppress \" - default - \" presets");
137 def.type = coBool;
138 def.tooltip = L("Suppress \" - default - \" presets in the Print / Filament / Printer "
139 "selections once there are any other valid presets available.");
140 def.set_default_value(new ConfigOptionBool{ app_config->get("no_defaults") == "1" });
141 option = Option(def, "no_defaults");
142 m_optgroup_general->append_single_option_line(option);
143
144 def.label = L("Show incompatible print and filament presets");
145 def.type = coBool;
146 def.tooltip = L("When checked, the print and filament presets are shown in the preset editor "
147 "even if they are marked as incompatible with the active printer");
148 def.set_default_value(new ConfigOptionBool{ app_config->get("show_incompatible_presets") == "1" });
149 option = Option(def, "show_incompatible_presets");
150 m_optgroup_general->append_single_option_line(option);
151
152 def.label = L("Show drop project dialog");
153 def.type = coBool;
154 def.tooltip = L("When checked, whenever dragging and dropping a project file on the application, shows a dialog asking to select the action to take on the file to load.");
155 def.set_default_value(new ConfigOptionBool{ app_config->get("show_drop_project_dialog") == "1" });
156 option = Option(def, "show_drop_project_dialog");
157 m_optgroup_general->append_single_option_line(option);
158
159
160 #if __APPLE__
161 def.label = L("Allow just a single PrusaSlicer instance");
162 def.type = coBool;
163 def.tooltip = 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.");
164 #else
165 def.label = L("Allow just a single PrusaSlicer instance");
166 def.type = coBool;
167 def.tooltip = L("If this is enabled, when starting PrusaSlicer and another instance of the same PrusaSlicer is already running, that instance will be reactivated instead.");
168 #endif
169 def.set_default_value(new ConfigOptionBool{ app_config->has("single_instance") ? app_config->get("single_instance") == "1" : false });
170 option = Option(def, "single_instance");
171 m_optgroup_general->append_single_option_line(option);
172
173 def.label = L("Ask for unsaved changes when closing application");
174 def.type = coBool;
175 def.tooltip = L("When closing the application, always ask for unsaved changes");
176 def.set_default_value(new ConfigOptionBool{ app_config->get("default_action_on_close_application") == "none" });
177 option = Option(def, "default_action_on_close_application");
178 m_optgroup_general->append_single_option_line(option);
179
180 def.label = L("Ask for unsaved changes when selecting new preset");
181 def.type = coBool;
182 def.tooltip = L("Always ask for unsaved changes when selecting new preset");
183 def.set_default_value(new ConfigOptionBool{ app_config->get("default_action_on_select_preset") == "none" });
184 option = Option(def, "default_action_on_select_preset");
185 m_optgroup_general->append_single_option_line(option);
186 }
187 #if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN
188 #ifdef _WIN32
189 else {
190 def.label = L("Associate .gcode files to PrusaSlicer G-code Viewer");
191 def.type = coBool;
192 def.tooltip = L("If enabled, sets PrusaSlicer G-code Viewer as default application to open .gcode files.");
193 def.set_default_value(new ConfigOptionBool(app_config->get("associate_gcode") == "1"));
194 option = Option(def, "associate_gcode");
195 m_optgroup_general->append_single_option_line(option);
196 }
197 #endif // _WIN32
198 #endif // ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN
199
200 #if __APPLE__
201 def.label = L("Use Retina resolution for the 3D scene");
202 def.type = coBool;
203 def.tooltip = L("If enabled, the 3D scene will be rendered in Retina resolution. "
204 "If you are experiencing 3D performance problems, disabling this option may help.");
205 def.set_default_value(new ConfigOptionBool{ app_config->get("use_retina_opengl") == "1" });
206 option = Option (def, "use_retina_opengl");
207 m_optgroup_general->append_single_option_line(option);
208 #endif
209
210 // Show/Hide splash screen
211 def.label = L("Show splash screen");
212 def.type = coBool;
213 def.tooltip = L("Show splash screen");
214 def.set_default_value(new ConfigOptionBool{ app_config->get("show_splash_screen") == "1" });
215 option = Option(def, "show_splash_screen");
216 m_optgroup_general->append_single_option_line(option);
217
218 #if ENABLE_CTRL_M_ON_WINDOWS
219 #if defined(_WIN32) || defined(__APPLE__)
220 def.label = L("Enable support for legacy 3DConnexion devices");
221 def.type = coBool;
222 def.tooltip = L("If enabled, the legacy 3DConnexion devices settings dialog is available by pressing CTRL+M");
223 def.set_default_value(new ConfigOptionBool{ app_config->get("use_legacy_3DConnexion") == "1" });
224 option = Option(def, "use_legacy_3DConnexion");
225 m_optgroup_general->append_single_option_line(option);
226 #endif // _WIN32 || __APPLE__
227 #endif // ENABLE_CTRL_M_ON_WINDOWS
228
229 activate_options_tab(m_optgroup_general);
230
231 // Add "Camera" tab
232 m_optgroup_camera = create_options_tab(_L("Camera"), tabs);
233 m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
234 m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
235 };
236
237 def.label = L("Use perspective camera");
238 def.type = coBool;
239 def.tooltip = L("If enabled, use perspective camera. If not enabled, use orthographic camera.");
240 def.set_default_value(new ConfigOptionBool{ app_config->get("use_perspective_camera") == "1" });
241 option = Option(def, "use_perspective_camera");
242 m_optgroup_camera->append_single_option_line(option);
243
244 def.label = L("Use free camera");
245 def.type = coBool;
246 def.tooltip = L("If enabled, use free camera. If not enabled, use constrained camera.");
247 def.set_default_value(new ConfigOptionBool(app_config->get("use_free_camera") == "1"));
248 option = Option(def, "use_free_camera");
249 m_optgroup_camera->append_single_option_line(option);
250
251 def.label = L("Reverse direction of zoom with mouse wheel");
252 def.type = coBool;
253 def.tooltip = L("If enabled, reverses the direction of zoom with mouse wheel");
254 def.set_default_value(new ConfigOptionBool(app_config->get("reverse_mouse_wheel_zoom") == "1"));
255 option = Option(def, "reverse_mouse_wheel_zoom");
256 m_optgroup_camera->append_single_option_line(option);
257
258 activate_options_tab(m_optgroup_camera);
259
260 // Add "GUI" tab
261 m_optgroup_gui = create_options_tab(_L("GUI"), tabs);
262 m_optgroup_gui->m_on_change = [this, tabs](t_config_option_key opt_key, boost::any value) {
263 if (opt_key == "suppress_hyperlinks")
264 m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "";
265 else
266 m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
267
268 if (opt_key == "use_custom_toolbar_size") {
269 m_icon_size_sizer->ShowItems(boost::any_cast<bool>(value));
270 m_optgroup_gui->parent()->Layout();
271 tabs->Layout();
272 this->layout();
273 }
274 };
275
276 def.label = L("Sequential slider applied only to top layer");
277 def.type = coBool;
278 def.tooltip = L("If enabled, changes made using the sequential slider, in preview, apply only to gcode top layer. "
279 "If disabled, changes made using the sequential slider, in preview, apply to the whole gcode.");
280 def.set_default_value(new ConfigOptionBool{ app_config->get("seq_top_layer_only") == "1" });
281 option = Option(def, "seq_top_layer_only");
282 m_optgroup_gui->append_single_option_line(option);
283
284 if (is_editor) {
285 def.label = L("Show sidebar collapse/expand button");
286 def.type = coBool;
287 def.tooltip = L("If enabled, the button for the collapse sidebar will be appeared in top right corner of the 3D Scene");
288 def.set_default_value(new ConfigOptionBool{ app_config->get("show_collapse_button") == "1" });
289 option = Option(def, "show_collapse_button");
290 m_optgroup_gui->append_single_option_line(option);
291
292 def.label = L("Suppress to open hyperlink in browser");
293 def.type = coBool;
294 def.tooltip = L("If enabled, the descriptions of configuration parameters in settings tabs wouldn't work as hyperlinks. "
295 "If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks.");
296 def.set_default_value(new ConfigOptionBool{ app_config->get("suppress_hyperlinks") == "1" });
297 option = Option(def, "suppress_hyperlinks");
298 m_optgroup_gui->append_single_option_line(option);
299
300 def.label = L("Use custom size for toolbar icons");
301 def.type = coBool;
302 def.tooltip = L("If enabled, you can change size of toolbar icons manually.");
303 def.set_default_value(new ConfigOptionBool{ app_config->get("use_custom_toolbar_size") == "1" });
304 option = Option(def, "use_custom_toolbar_size");
305 m_optgroup_gui->append_single_option_line(option);
306 }
307
308 activate_options_tab(m_optgroup_gui);
309
310 if (is_editor) {
311 create_icon_size_slider();
312 m_icon_size_sizer->ShowItems(app_config->get("use_custom_toolbar_size") == "1");
313
314 create_settings_mode_widget();
315 }
316
317 #if ENABLE_ENVIRONMENT_MAP
318 if (is_editor) {
319 // Add "Render" tab
320 m_optgroup_render = create_options_tab(_L("Render"), tabs);
321 m_optgroup_render->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
322 m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
323 };
324
325 def.label = L("Use environment map");
326 def.type = coBool;
327 def.tooltip = L("If enabled, renders object using the environment map.");
328 def.set_default_value(new ConfigOptionBool{ app_config->get("use_environment_map") == "1" });
329 option = Option(def, "use_environment_map");
330 m_optgroup_render->append_single_option_line(option);
331
332 activate_options_tab(m_optgroup_render);
333 }
334 #endif // ENABLE_ENVIRONMENT_MAP
335
336 auto sizer = new wxBoxSizer(wxVERTICAL);
337 sizer->Add(tabs, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5);
338
339 auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
340 wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
341 btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); });
342 sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM | wxTOP, 10);
343
344 SetSizer(sizer);
345 sizer->SetSizeHints(this);
346 this->CenterOnParent();
347 }
348
accept()349 void PreferencesDialog::accept()
350 {
351 if (m_values.find("no_defaults") != m_values.end())
352 warning_catcher(this, wxString::Format(_L("You need to restart %s to make the changes effective."), SLIC3R_APP_NAME));
353
354 auto app_config = get_app_config();
355
356 m_seq_top_layer_only_changed = false;
357 if (auto it = m_values.find("seq_top_layer_only"); it != m_values.end())
358 m_seq_top_layer_only_changed = app_config->get("seq_top_layer_only") != it->second;
359
360 m_settings_layout_changed = false;
361 for (const std::string& key : { "old_settings_layout_mode",
362 "new_settings_layout_mode",
363 "dlg_settings_layout_mode" })
364 {
365 auto it = m_values.find(key);
366 if (it != m_values.end() && app_config->get(key) != it->second) {
367 m_settings_layout_changed = true;
368 break;
369 }
370 }
371
372 for (const std::string& key : {"default_action_on_close_application", "default_action_on_select_preset"}) {
373 auto it = m_values.find(key);
374 if (it != m_values.end() && it->second != "none" && app_config->get(key) != "none")
375 m_values.erase(it); // we shouldn't change value, if some of those parameters was selected, and then deselected
376 }
377
378 for (std::map<std::string, std::string>::iterator it = m_values.begin(); it != m_values.end(); ++it)
379 app_config->set(it->first, it->second);
380
381 app_config->save();
382 EndModal(wxID_OK);
383
384 if (m_settings_layout_changed)
385 ;// application will be recreated after Preference dialog will be destroyed
386 else
387 // Nothify the UI to update itself from the ini file.
388 wxGetApp().update_ui_from_settings();
389 }
390
on_dpi_changed(const wxRect & suggested_rect)391 void PreferencesDialog::on_dpi_changed(const wxRect &suggested_rect)
392 {
393 m_optgroup_general->msw_rescale();
394 m_optgroup_camera->msw_rescale();
395 m_optgroup_gui->msw_rescale();
396
397 msw_buttons_rescale(this, em_unit(), { wxID_OK, wxID_CANCEL });
398
399 layout();
400 }
401
layout()402 void PreferencesDialog::layout()
403 {
404 const int em = em_unit();
405
406 SetMinSize(wxSize(47 * em, 28 * em));
407 Fit();
408
409 Refresh();
410 }
411
create_icon_size_slider()412 void PreferencesDialog::create_icon_size_slider()
413 {
414 const auto app_config = get_app_config();
415
416 const int em = em_unit();
417
418 m_icon_size_sizer = new wxBoxSizer(wxHORIZONTAL);
419
420 wxWindow* parent = m_optgroup_gui->parent();
421
422 if (isOSX)
423 // For correct rendering of the slider and value label under OSX
424 // we should use system default background
425 parent->SetBackgroundStyle(wxBG_STYLE_ERASE);
426
427 auto label = new wxStaticText(parent, wxID_ANY, _L("Icon size in a respect to the default size") + " (%) :");
428
429 m_icon_size_sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL| wxRIGHT | (isOSX ? 0 : wxLEFT), em);
430
431 const int def_val = atoi(app_config->get("custom_toolbar_size").c_str());
432
433 long style = wxSL_HORIZONTAL;
434 if (!isOSX)
435 style |= wxSL_LABELS | wxSL_AUTOTICKS;
436
437 auto slider = new wxSlider(parent, wxID_ANY, def_val, 30, 100,
438 wxDefaultPosition, wxDefaultSize, style);
439
440 slider->SetTickFreq(10);
441 slider->SetPageSize(10);
442 slider->SetToolTip(_L("Select toolbar icon size in respect to the default one."));
443
444 m_icon_size_sizer->Add(slider, 1, wxEXPAND);
445
446 wxStaticText* val_label{ nullptr };
447 if (isOSX) {
448 val_label = new wxStaticText(parent, wxID_ANY, wxString::Format("%d", def_val));
449 m_icon_size_sizer->Add(val_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, em);
450 }
451
452 slider->Bind(wxEVT_SLIDER, ([this, slider, val_label](wxCommandEvent e) {
453 auto val = slider->GetValue();
454 m_values["custom_toolbar_size"] = (boost::format("%d") % val).str();
455
456 if (val_label)
457 val_label->SetLabelText(wxString::Format("%d", val));
458 }), slider->GetId());
459
460 for (wxWindow* win : std::vector<wxWindow*>{ slider, label, val_label }) {
461 if (!win) continue;
462 win->SetFont(wxGetApp().normal_font());
463
464 if (isOSX) continue; // under OSX we use wxBG_STYLE_ERASE
465 win->SetBackgroundStyle(wxBG_STYLE_PAINT);
466 }
467
468 m_optgroup_gui->sizer->Add(m_icon_size_sizer, 0, wxEXPAND | wxALL, em);
469 }
470
create_settings_mode_widget()471 void PreferencesDialog::create_settings_mode_widget()
472 {
473 wxString choices[] = { _L("Old regular layout with the tab bar"),
474 _L("New layout, access via settings button in the top menu"),
475 _L("Settings in non-modal window") };
476
477 auto app_config = get_app_config();
478 int selection = app_config->get("old_settings_layout_mode") == "1" ? 0 :
479 app_config->get("new_settings_layout_mode") == "1" ? 1 :
480 app_config->get("dlg_settings_layout_mode") == "1" ? 2 : 0;
481
482 wxWindow* parent = m_optgroup_gui->parent();
483
484 m_layout_mode_box = new wxRadioBox(parent, wxID_ANY, _L("Layout Options"), wxDefaultPosition, wxDefaultSize,
485 WXSIZEOF(choices), choices, 3, wxRA_SPECIFY_ROWS);
486 m_layout_mode_box->SetFont(wxGetApp().normal_font());
487 m_layout_mode_box->SetSelection(selection);
488
489 m_layout_mode_box->Bind(wxEVT_RADIOBOX, [this](wxCommandEvent& e) {
490 int selection = e.GetSelection();
491 m_values["old_settings_layout_mode"] = boost::any_cast<bool>(selection == 0) ? "1" : "0";
492 m_values["new_settings_layout_mode"] = boost::any_cast<bool>(selection == 1) ? "1" : "0";
493 m_values["dlg_settings_layout_mode"] = boost::any_cast<bool>(selection == 2) ? "1" : "0";
494 });
495
496 auto sizer = new wxBoxSizer(wxHORIZONTAL);
497 sizer->Add(m_layout_mode_box, 1, wxALIGN_CENTER_VERTICAL);
498 m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND);
499 }
500
501
502 } // GUI
503 } // Slic3r
504