1 #include "GUI.hpp"
2 #include "GUI_App.hpp"
3 #include "I18N.hpp"
4 
5 #include <string>
6 
7 #include <boost/algorithm/string.hpp>
8 #include <boost/algorithm/string/predicate.hpp>
9 #include <boost/any.hpp>
10 
11 #if __APPLE__
12 #import <IOKit/pwr_mgt/IOPMLib.h>
13 #elif _WIN32
14 #define WIN32_LEAN_AND_MEAN
15 #define NOMINMAX
16 #include <Windows.h>
17 #include "boost/nowide/convert.hpp"
18 #endif
19 
20 #include "AboutDialog.hpp"
21 #include "MsgDialog.hpp"
22 #include "format.hpp"
23 
24 #include "libslic3r/Print.hpp"
25 
26 namespace Slic3r {
27 
28 class AppConfig;
29 
30 namespace GUI {
31 
32 #if __APPLE__
33 IOPMAssertionID assertionID;
34 #endif
35 
disable_screensaver()36 void disable_screensaver()
37 {
38     #if __APPLE__
39     CFStringRef reasonForActivity = CFSTR("Slic3r");
40     IOReturn success = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
41         kIOPMAssertionLevelOn, reasonForActivity, &assertionID);
42     // ignore result: success == kIOReturnSuccess
43     #elif _WIN32
44     SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_CONTINUOUS);
45     #endif
46 }
47 
enable_screensaver()48 void enable_screensaver()
49 {
50     #if __APPLE__
51     IOReturn success = IOPMAssertionRelease(assertionID);
52     #elif _WIN32
53     SetThreadExecutionState(ES_CONTINUOUS);
54     #endif
55 }
56 
debugged()57 bool debugged()
58 {
59     #ifdef _WIN32
60     return IsDebuggerPresent() == TRUE;
61 	#else
62 	return false;
63     #endif /* _WIN32 */
64 }
65 
break_to_debugger()66 void break_to_debugger()
67 {
68     #ifdef _WIN32
69     if (IsDebuggerPresent())
70         DebugBreak();
71     #endif /* _WIN32 */
72 }
73 
shortkey_ctrl_prefix()74 const std::string& shortkey_ctrl_prefix()
75 {
76 	static const std::string str =
77 #ifdef __APPLE__
78 		"⌘"
79 #else
80 		"Ctrl+"
81 #endif
82 		;
83 	return str;
84 }
85 
shortkey_alt_prefix()86 const std::string& shortkey_alt_prefix()
87 {
88 	static const std::string str =
89 #ifdef __APPLE__
90 		"⌥"
91 #else
92 		"Alt+"
93 #endif
94 		;
95 	return str;
96 }
97 
98 // opt_index = 0, by the reason of zero-index in ConfigOptionVector by default (in case only one element)
change_opt_value(DynamicPrintConfig & config,const t_config_option_key & opt_key,const boost::any & value,int opt_index)99 void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/)
100 {
101 	try{
102 
103         if (config.def()->get(opt_key)->type == coBools && config.def()->get(opt_key)->nullable) {
104             ConfigOptionBoolsNullable* vec_new = new ConfigOptionBoolsNullable{ boost::any_cast<unsigned char>(value) };
105             config.option<ConfigOptionBoolsNullable>(opt_key)->set_at(vec_new, opt_index, 0);
106             return;
107         }
108 
109 		switch (config.def()->get(opt_key)->type) {
110 		case coFloatOrPercent:{
111 			std::string str = boost::any_cast<std::string>(value);
112 			bool percent = false;
113 			if (str.back() == '%') {
114 				str.pop_back();
115 				percent = true;
116 			}
117 			double val = stod(str);
118 			config.set_key_value(opt_key, new ConfigOptionFloatOrPercent(val, percent));
119 			break;}
120 		case coPercent:
121 			config.set_key_value(opt_key, new ConfigOptionPercent(boost::any_cast<double>(value)));
122 			break;
123 		case coFloat:{
124 			double& val = config.opt_float(opt_key);
125 			val = boost::any_cast<double>(value);
126 			break;
127 		}
128 		case coPercents:{
129 			ConfigOptionPercents* vec_new = new ConfigOptionPercents{ boost::any_cast<double>(value) };
130 			config.option<ConfigOptionPercents>(opt_key)->set_at(vec_new, opt_index, opt_index);
131 			break;
132 		}
133 		case coFloats:{
134 			ConfigOptionFloats* vec_new = new ConfigOptionFloats{ boost::any_cast<double>(value) };
135 			config.option<ConfigOptionFloats>(opt_key)->set_at(vec_new, opt_index, opt_index);
136  			break;
137 		}
138 		case coString:
139 			config.set_key_value(opt_key, new ConfigOptionString(boost::any_cast<std::string>(value)));
140 			break;
141 		case coStrings:{
142 			if (opt_key == "compatible_prints" || opt_key == "compatible_printers") {
143 				config.option<ConfigOptionStrings>(opt_key)->values =
144 					boost::any_cast<std::vector<std::string>>(value);
145 			}
146 			else if (config.def()->get(opt_key)->gui_flags.compare("serialized") == 0) {
147 				std::string str = boost::any_cast<std::string>(value);
148                 std::vector<std::string> values {};
149                 if (!str.empty()) {
150 				    if (str.back() == ';') str.pop_back();
151 				    // Split a string to multiple strings by a semi - colon.This is the old way of storing multi - string values.
152 				    // Currently used for the post_process config value only.
153 				    boost::split(values, str, boost::is_any_of(";"));
154 				    if (values.size() == 1 && values[0] == "")
155 					    values.resize(0);
156                 }
157 				config.option<ConfigOptionStrings>(opt_key)->values = values;
158 			}
159 			else{
160 				ConfigOptionStrings* vec_new = new ConfigOptionStrings{ boost::any_cast<std::string>(value) };
161 				config.option<ConfigOptionStrings>(opt_key)->set_at(vec_new, opt_index, 0);
162 			}
163 			}
164 			break;
165 		case coBool:
166 			config.set_key_value(opt_key, new ConfigOptionBool(boost::any_cast<bool>(value)));
167 			break;
168 		case coBools:{
169 			ConfigOptionBools* vec_new = new ConfigOptionBools{ boost::any_cast<unsigned char>(value) != 0 };
170 			config.option<ConfigOptionBools>(opt_key)->set_at(vec_new, opt_index, 0);
171 			break;}
172 		case coInt:
173 			config.set_key_value(opt_key, new ConfigOptionInt(boost::any_cast<int>(value)));
174 			break;
175 		case coInts:{
176 			ConfigOptionInts* vec_new = new ConfigOptionInts{ boost::any_cast<int>(value) };
177 			config.option<ConfigOptionInts>(opt_key)->set_at(vec_new, opt_index, 0);
178 			}
179 			break;
180 		case coEnum:{
181 			if (opt_key == "top_fill_pattern" ||
182 				opt_key == "bottom_fill_pattern" ||
183 				opt_key == "fill_pattern")
184 				config.set_key_value(opt_key, new ConfigOptionEnum<InfillPattern>(boost::any_cast<InfillPattern>(value)));
185 			else if (opt_key.compare("ironing_type") == 0)
186 				config.set_key_value(opt_key, new ConfigOptionEnum<IroningType>(boost::any_cast<IroningType>(value)));
187 			else if (opt_key.compare("gcode_flavor") == 0)
188 				config.set_key_value(opt_key, new ConfigOptionEnum<GCodeFlavor>(boost::any_cast<GCodeFlavor>(value)));
189 			else if (opt_key.compare("machine_limits_usage") == 0)
190 				config.set_key_value(opt_key, new ConfigOptionEnum<MachineLimitsUsage>(boost::any_cast<MachineLimitsUsage>(value)));
191 			else if (opt_key.compare("support_material_pattern") == 0)
192 				config.set_key_value(opt_key, new ConfigOptionEnum<SupportMaterialPattern>(boost::any_cast<SupportMaterialPattern>(value)));
193 			else if (opt_key.compare("seam_position") == 0)
194 				config.set_key_value(opt_key, new ConfigOptionEnum<SeamPosition>(boost::any_cast<SeamPosition>(value)));
195 			else if (opt_key.compare("host_type") == 0)
196 				config.set_key_value(opt_key, new ConfigOptionEnum<PrintHostType>(boost::any_cast<PrintHostType>(value)));
197 			else if (opt_key.compare("display_orientation") == 0)
198 				config.set_key_value(opt_key, new ConfigOptionEnum<SLADisplayOrientation>(boost::any_cast<SLADisplayOrientation>(value)));
199             else if(opt_key.compare("support_pillar_connection_mode") == 0)
200                 config.set_key_value(opt_key, new ConfigOptionEnum<SLAPillarConnectionMode>(boost::any_cast<SLAPillarConnectionMode>(value)));
201             else if(opt_key == "printhost_authorization_type")
202                 config.set_key_value(opt_key, new ConfigOptionEnum<AuthorizationType>(boost::any_cast<AuthorizationType>(value)));
203 			}
204 			break;
205 		case coPoints:{
206 			if (opt_key == "bed_shape" || opt_key == "thumbnails") {
207 				config.option<ConfigOptionPoints>(opt_key)->values = boost::any_cast<std::vector<Vec2d>>(value);
208 				break;
209 			}
210 			ConfigOptionPoints* vec_new = new ConfigOptionPoints{ boost::any_cast<Vec2d>(value) };
211 			config.option<ConfigOptionPoints>(opt_key)->set_at(vec_new, opt_index, 0);
212 			}
213 			break;
214 		case coNone:
215 			break;
216 		default:
217 			break;
218 		}
219 	}
220 	catch (const std::exception & /* e */)
221 	{
222 		// int i = 0;//no reason, just experiment
223 	}
224 }
225 
show_error(wxWindow * parent,const wxString & message,bool monospaced_font)226 void show_error(wxWindow* parent, const wxString& message, bool monospaced_font)
227 {
228 	ErrorDialog msg(parent, message, monospaced_font);
229 	msg.ShowModal();
230 }
231 
show_error(wxWindow * parent,const char * message,bool monospaced_font)232 void show_error(wxWindow* parent, const char* message, bool monospaced_font)
233 {
234 	assert(message);
235 	show_error(parent, wxString::FromUTF8(message), monospaced_font);
236 }
237 
show_error_id(int id,const std::string & message)238 void show_error_id(int id, const std::string& message)
239 {
240 	auto *parent = id != 0 ? wxWindow::FindWindowById(id) : nullptr;
241 	show_error(parent, message);
242 }
243 
show_info(wxWindow * parent,const wxString & message,const wxString & title)244 void show_info(wxWindow* parent, const wxString& message, const wxString& title)
245 {
246 	wxMessageDialog msg_wingow(parent, message, wxString(SLIC3R_APP_NAME " - ") + (title.empty() ? _L("Notice") : title), wxOK | wxICON_INFORMATION);
247 	msg_wingow.ShowModal();
248 }
249 
show_info(wxWindow * parent,const char * message,const char * title)250 void show_info(wxWindow* parent, const char* message, const char* title)
251 {
252 	assert(message);
253 	show_info(parent, wxString::FromUTF8(message), title ? wxString::FromUTF8(title) : wxString());
254 }
255 
warning_catcher(wxWindow * parent,const wxString & message)256 void warning_catcher(wxWindow* parent, const wxString& message)
257 {
258 	wxMessageDialog msg(parent, message, _L("Warning"), wxOK | wxICON_WARNING);
259 	msg.ShowModal();
260 }
261 
bold(const wxString & str)262 static wxString bold(const wxString& str)
263 {
264 	return wxString::Format("<b>%s</b>", str);
265 };
266 
bold_string(const wxString & str)267 static wxString bold_string(const wxString& str)
268 {
269 	return wxString::Format("<b>\"%s\"</b>", str);
270 };
271 
add_config_substitutions(const ConfigSubstitutions & conf_substitutions,wxString & changes)272 static void add_config_substitutions(const ConfigSubstitutions& conf_substitutions, wxString& changes)
273 {
274 	changes += "<table>";
275 	for (const ConfigSubstitution& conf_substitution : conf_substitutions) {
276 		wxString new_val;
277 		const ConfigOptionDef* def = conf_substitution.opt_def;
278 		if (!def)
279 			continue;
280 		switch (def->type) {
281 		case coEnum:
282 		{
283 			const std::vector<std::string>& labels = def->enum_labels;
284 			const std::vector<std::string>& values = def->enum_values;
285 			int val = conf_substitution.new_value->getInt();
286 
287 			bool is_infill = def->opt_key == "top_fill_pattern"	   ||
288 							 def->opt_key == "bottom_fill_pattern" ||
289 							 def->opt_key == "fill_pattern";
290 
291 			// Each infill doesn't use all list of infill declared in PrintConfig.hpp.
292 			// So we should "convert" val to the correct one
293 			if (is_infill) {
294 				for (const auto& key_val : *def->enum_keys_map)
295 					if ((int)key_val.second == val) {
296 						auto it = std::find(values.begin(), values.end(), key_val.first);
297 						if (it == values.end())
298 							break;
299 						auto idx = it - values.begin();
300 						new_val = wxString("\"") + values[idx] + "\"" + " (" + from_u8(_utf8(labels[idx])) + ")";
301 						break;
302 					}
303 				if (new_val.IsEmpty()) {
304 					assert(false);
305 					new_val = _L("Undefined");
306 				}
307 			}
308 			else
309 				new_val = wxString("\"") + values[val] + "\"" + " (" + from_u8(_utf8(labels[val])) + ")";
310 			break;
311 		}
312 		case coBool:
313 			new_val = conf_substitution.new_value->getBool() ? "true" : "false";
314 			break;
315 		case coBools:
316 			if (conf_substitution.new_value->nullable())
317 				for (const char v : static_cast<const ConfigOptionBoolsNullable*>(conf_substitution.new_value.get())->values)
318 					new_val += std::string(v == ConfigOptionBoolsNullable::nil_value() ? "nil" : v ? "true" : "false") + ", ";
319 			else
320 				for (const char v : static_cast<const ConfigOptionBools*>(conf_substitution.new_value.get())->values)
321 					new_val += std::string(v ? "true" : "false") + ", ";
322 			if (! new_val.empty())
323 				new_val.erase(new_val.begin() + new_val.size() - 2, new_val.end());
324 			break;
325 		default:
326 			assert(false);
327 		}
328 
329 		changes += format_wxstr("<tr><td><b>\"%1%\" (%2%)</b></td><td>: ", def->opt_key, _(def->label)) +
330 				   format_wxstr(_L("%1% was substituted with %2%"), bold_string(conf_substitution.old_value), bold(new_val)) +
331 				   "</td></tr>";
332 	}
333 	changes += "</table>";
334 }
335 
substitution_message(const wxString & changes)336 static wxString substitution_message(const wxString& changes)
337 {
338 	return
339 		_L("Most likely the configuration was produced by a newer version of PrusaSlicer or by some PrusaSlicer fork.") + " " +
340 		_L("The following values were substituted:") + "\n" + changes + "\n\n" +
341 		_L("Review the substitutions and adjust them if needed.");
342 }
343 
show_substitutions_info(const PresetsConfigSubstitutions & presets_config_substitutions)344 void show_substitutions_info(const PresetsConfigSubstitutions& presets_config_substitutions)
345 {
346 	wxString changes;
347 
348 	auto preset_type_name = [](Preset::Type type) {
349 		switch (type) {
350 			case Preset::TYPE_PRINT:			return _L("Print settings");
351 			case Preset::TYPE_SLA_PRINT:		return _L("SLA print settings");
352 			case Preset::TYPE_FILAMENT:			return _L("Filament");
353 			case Preset::TYPE_SLA_MATERIAL:		return _L("SLA material");
354 			case Preset::TYPE_PRINTER: 			return _L("Printer");
355 			case Preset::TYPE_PHYSICAL_PRINTER:	return _L("Physical Printer");
356 			default: assert(false);				return wxString();
357 		}
358 	};
359 
360 	for (const PresetConfigSubstitutions& substitution : presets_config_substitutions) {
361 		changes += "\n\n" + format_wxstr("%1% : %2%", preset_type_name(substitution.preset_type), bold_string(substitution.preset_name));
362 		if (!substitution.preset_file.empty())
363 			changes += format_wxstr(" (%1%)", substitution.preset_file);
364 
365 		add_config_substitutions(substitution.substitutions, changes);
366 	}
367 
368 	InfoDialog msg(nullptr, _L("Configuration bundle was loaded, however some configuration values were not recognized."), substitution_message(changes));
369 	msg.ShowModal();
370 }
371 
show_substitutions_info(const ConfigSubstitutions & config_substitutions,const std::string & filename)372 void show_substitutions_info(const ConfigSubstitutions& config_substitutions, const std::string& filename)
373 {
374 	wxString changes = "\n";
375 	add_config_substitutions(config_substitutions, changes);
376 
377 	InfoDialog msg(nullptr,
378 		format_wxstr(_L("Configuration file \"%1%\" was loaded, however some configuration values were not recognized."), from_u8(filename)),
379 		substitution_message(changes));
380 	msg.ShowModal();
381 }
382 
create_combochecklist(wxComboCtrl * comboCtrl,const std::string & text,const std::string & items)383 void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items)
384 {
385     if (comboCtrl == nullptr)
386         return;
387 
388     wxCheckListBoxComboPopup* popup = new wxCheckListBoxComboPopup;
389     if (popup != nullptr) {
390 		// FIXME If the following line is removed, the combo box popup list will not react to mouse clicks.
391         //  On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10.
392         comboCtrl->UseAltPopupWindow();
393 
394 		int max_width = 0;
395 
396 		// the following line messes up the popup size the first time it is shown on wxWidgets 3.1.3
397 //		comboCtrl->EnablePopupAnimation(false);
398 		comboCtrl->SetPopupControl(popup);
399 		wxString title = from_u8(text);
400 		max_width = std::max(max_width, 60 + comboCtrl->GetTextExtent(title).x);
401 		popup->SetStringValue(title);
402 		popup->Bind(wxEVT_CHECKLISTBOX, [popup](wxCommandEvent& evt) { popup->OnCheckListBox(evt); });
403 		popup->Bind(wxEVT_LISTBOX, [popup](wxCommandEvent& evt) { popup->OnListBoxSelection(evt); });
404         popup->Bind(wxEVT_KEY_DOWN, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); });
405         popup->Bind(wxEVT_KEY_UP, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); });
406 
407         std::vector<std::string> items_str;
408         boost::split(items_str, items, boost::is_any_of("|"), boost::token_compress_off);
409 
410 		// each item must be composed by 2 parts
411 		assert(items_str.size() %2 == 0);
412 
413 		for (size_t i = 0; i < items_str.size(); i += 2) {
414 			wxString label = from_u8(items_str[i]);
415 			max_width = std::max(max_width, 60 + popup->GetTextExtent(label).x);
416 			popup->Append(label);
417 			popup->Check(i / 2, items_str[i + 1] == "1");
418 		}
419 
420 		comboCtrl->SetMinClientSize(wxSize(max_width, -1));
421 	}
422 }
423 
combochecklist_get_flags(wxComboCtrl * comboCtrl)424 unsigned int combochecklist_get_flags(wxComboCtrl* comboCtrl)
425 {
426 	unsigned int flags = 0;
427 
428 	wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup);
429 	if (popup != nullptr) {
430 		for (unsigned int i = 0; i < popup->GetCount(); ++i) {
431 			if (popup->IsChecked(i))
432 				flags |= 1 << i;
433 		}
434 	}
435 
436 	return flags;
437 }
438 
combochecklist_set_flags(wxComboCtrl * comboCtrl,unsigned int flags)439 void combochecklist_set_flags(wxComboCtrl* comboCtrl, unsigned int flags)
440 {
441 	wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup);
442 	if (popup != nullptr) {
443 		for (unsigned int i = 0; i < popup->GetCount(); ++i) {
444 			popup->Check(i, (flags & (1 << i)) != 0);
445 		}
446 	}
447 }
448 
get_app_config()449 AppConfig* get_app_config()
450 {
451     return wxGetApp().app_config;
452 }
453 
from_u8(const std::string & str)454 wxString from_u8(const std::string &str)
455 {
456 	return wxString::FromUTF8(str.c_str());
457 }
458 
into_u8(const wxString & str)459 std::string into_u8(const wxString &str)
460 {
461 	auto buffer_utf8 = str.utf8_str();
462 	return std::string(buffer_utf8.data());
463 }
464 
from_path(const boost::filesystem::path & path)465 wxString from_path(const boost::filesystem::path &path)
466 {
467 #ifdef _WIN32
468 	return wxString(path.string<std::wstring>());
469 #else
470 	return from_u8(path.string<std::string>());
471 #endif
472 }
473 
into_path(const wxString & str)474 boost::filesystem::path into_path(const wxString &str)
475 {
476 	return boost::filesystem::path(str.wx_str());
477 }
478 
about()479 void about()
480 {
481     AboutDialog dlg;
482     dlg.ShowModal();
483 }
484 
desktop_open_datadir_folder()485 void desktop_open_datadir_folder()
486 {
487 	// Execute command to open a file explorer, platform dependent.
488 	// FIXME: The const_casts aren't needed in wxWidgets 3.1, remove them when we upgrade.
489 
490 	const auto path = data_dir();
491 #ifdef _WIN32
492 		const wxString widepath = from_u8(path);
493 		const wchar_t *argv[] = { L"explorer", widepath.GetData(), nullptr };
494 		::wxExecute(const_cast<wchar_t**>(argv), wxEXEC_ASYNC, nullptr);
495 #elif __APPLE__
496 		const char *argv[] = { "open", path.data(), nullptr };
497 		::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr);
498 #else
499 		const char *argv[] = { "xdg-open", path.data(), nullptr };
500 
501 		// Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars,
502 		// because they may mess up the environment expected by the file manager.
503 		// Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure.
504 		if (wxGetEnv("APPIMAGE", nullptr)) {
505 			// We're running from AppImage
506 			wxEnvVariableHashMap env_vars;
507 			wxGetEnvMap(&env_vars);
508 
509 			env_vars.erase("APPIMAGE");
510 			env_vars.erase("APPDIR");
511 			env_vars.erase("LD_LIBRARY_PATH");
512 			env_vars.erase("LD_PRELOAD");
513 			env_vars.erase("UNION_PRELOAD");
514 
515 			wxExecuteEnv exec_env;
516 			exec_env.env = std::move(env_vars);
517 
518 			wxString owd;
519 			if (wxGetEnv("OWD", &owd)) {
520 				// This is the original work directory from which the AppImage image was run,
521 				// set it as CWD for the child process:
522 				exec_env.cwd = std::move(owd);
523 			}
524 
525 			::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, &exec_env);
526 		} else {
527 			// Looks like we're NOT running from AppImage, we'll make no changes to the environment.
528 			::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, nullptr);
529 		}
530 #endif
531 }
532 
533 } }
534