///|/ Copyright (c) Prusa Research 2016 - 2023 Tomáš Mészáros @tamasmeszaros, Oleksandra Iushchenko @YuSanka, Vojtěch Bubník @bubnikv, Lukáš Matěna @lukasmatena, Lukáš Hejl @hejllukas, David Kocík @kocikdav, Enrico Turri @enricoturri1966, Vojtěch Král @vojtechkral ///|/ Copyright (c) 2018 Martin Loidl @LoidlM ///|/ Copyright (c) Slic3r 2015 Alessandro Ranellucci @alranel ///|/ ///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher ///|/ #include "GUI.hpp" #include "GUI_App.hpp" #include "format.hpp" #include "I18N.hpp" #include "libslic3r/LocalesUtils.hpp" #include #include #include #include #include #if __APPLE__ #import #elif _WIN32 #define WIN32_LEAN_AND_MEAN #ifndef NOMINMAX #define NOMINMAX #endif #include #include "boost/nowide/convert.hpp" #endif #include "AboutDialog.hpp" #include "MsgDialog.hpp" #include "format.hpp" #include "libslic3r/Print.hpp" namespace Slic3r { class AppConfig; namespace GUI { #if __APPLE__ IOPMAssertionID assertionID; #endif void disable_screensaver() { #if __APPLE__ CFStringRef reasonForActivity = CFSTR("Slic3r"); [[maybe_unused]]IOReturn success = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, reasonForActivity, &assertionID); // ignore result: success == kIOReturnSuccess #elif _WIN32 SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_CONTINUOUS); #endif } void enable_screensaver() { #if __APPLE__ IOPMAssertionRelease(assertionID); #elif _WIN32 SetThreadExecutionState(ES_CONTINUOUS); #endif } bool debugged() { #ifdef _WIN32 return IsDebuggerPresent() == TRUE; #else return false; #endif /* _WIN32 */ } void break_to_debugger() { #ifdef _WIN32 if (IsDebuggerPresent()) DebugBreak(); #endif /* _WIN32 */ } const std::string& shortkey_ctrl_prefix() { static const std::string str = #ifdef __APPLE__ "⌘" #else "Ctrl+" #endif ; return str; } const std::string& shortkey_alt_prefix() { static const std::string str = #ifdef __APPLE__ "⌥" #else "Alt+" #endif ; return str; } void show_error(wxWindow* parent, const wxString& message, bool monospaced_font) { ErrorDialog msg(parent, message, monospaced_font); msg.ShowModal(); } void show_error(wxWindow* parent, const char* message, bool monospaced_font) { assert(message); show_error(parent, wxString::FromUTF8(message), monospaced_font); } void show_error_id(int id, const std::string& message) { auto *parent = id != 0 ? wxWindow::FindWindowById(id) : nullptr; show_error(parent, message); } void show_info(wxWindow* parent, const wxString& message, const wxString& title) { //wxMessageDialog msg_wingow(parent, message, wxString(SLIC3R_APP_NAME " - ") + (title.empty() ? _L("Notice") : title), wxOK | wxICON_INFORMATION); MessageDialog msg_wingow(parent, message, wxString(SLIC3R_APP_NAME " - ") + (title.empty() ? _L("Notice") : title), wxOK | wxICON_INFORMATION); msg_wingow.ShowModal(); } void show_info(wxWindow* parent, const char* message, const char* title) { assert(message); show_info(parent, wxString::FromUTF8(message), title ? wxString::FromUTF8(title) : wxString()); } void warning_catcher(wxWindow* parent, const wxString& message) { //wxMessageDialog msg(parent, message, _L("Warning"), wxOK | wxICON_WARNING); MessageDialog msg(parent, message, _L("Warning"), wxOK | wxICON_WARNING); msg.ShowModal(); } static wxString bold(const wxString& str) { return wxString::Format("%s", str); }; static wxString bold_string(const wxString& str) { return wxString::Format("\"%s\"", str); }; static void add_config_substitutions(const ConfigSubstitutions& conf_substitutions, wxString& changes) { changes += ""; for (const ConfigSubstitution& conf_substitution : conf_substitutions) { wxString new_val; const ConfigOptionDef* def = conf_substitution.opt_def; if (!def) continue; switch (def->type) { case coEnum: { auto opt = def->enum_def->enum_to_index(conf_substitution.new_value->getInt()); new_val = opt.has_value() ? wxString("\"") + def->enum_def->value(*opt) + "\"" + " (" + _(from_u8(def->enum_def->label(*opt))) + ")" : _L("Undefined"); break; } case coBool: new_val = conf_substitution.new_value->getBool() ? "true" : "false"; break; case coBools: if (conf_substitution.new_value->nullable()) for (const char v : static_cast(conf_substitution.new_value.get())->values) new_val += std::string(v == ConfigOptionBoolsNullable::nil_value() ? "nil" : v ? "true" : "false") + ", "; else for (const char v : static_cast(conf_substitution.new_value.get())->values) new_val += std::string(v ? "true" : "false") + ", "; if (! new_val.empty()) new_val.erase(new_val.begin() + new_val.size() - 2, new_val.end()); break; default: assert(false); } changes += format_wxstr(""; } changes += "
\"%1%\" (%2%): ", def->opt_key, _(def->label)) + format_wxstr(_L("%1% was substituted with %2%"), bold_string(conf_substitution.old_value), bold(new_val)) + "
"; } static wxString substitution_message(const wxString& changes) { return _L("Most likely the configuration was produced by a newer version of PrusaSlicer or by some PrusaSlicer fork.") + " " + _L("The following values were substituted:") + "\n" + changes + "\n\n" + _L("Review the substitutions and adjust them if needed."); } void show_substitutions_info(const PresetsConfigSubstitutions& presets_config_substitutions) { wxString changes; auto preset_type_name = [](Preset::Type type) { switch (type) { case Preset::TYPE_PRINT: return _L("Print settings"); case Preset::TYPE_SLA_PRINT: return _L("SLA print settings"); case Preset::TYPE_FILAMENT: return _L("Filament"); case Preset::TYPE_SLA_MATERIAL: return _L("SLA material"); case Preset::TYPE_PRINTER: return _L("Printer"); case Preset::TYPE_PHYSICAL_PRINTER: return _L("Physical Printer"); default: assert(false); return wxString(); } }; for (const PresetConfigSubstitutions& substitution : presets_config_substitutions) { changes += "\n\n" + format_wxstr("%1% : %2%", preset_type_name(substitution.preset_type), bold_string(from_u8(substitution.preset_name))); if (!substitution.preset_file.empty()) changes += format_wxstr(" (%1%)", substitution.preset_file); add_config_substitutions(substitution.substitutions, changes); } InfoDialog msg(nullptr, _L("Configuration bundle was loaded, however some configuration values were not recognized."), substitution_message(changes), true); msg.ShowModal(); } void show_substitutions_info(const ConfigSubstitutions& config_substitutions, const std::string& filename) { wxString changes = "\n"; add_config_substitutions(config_substitutions, changes); InfoDialog msg(nullptr, format_wxstr(_L("Configuration file \"%1%\" was loaded, however some configuration values were not recognized."), from_u8(filename)), substitution_message(changes), true); msg.ShowModal(); } void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items) { if (comboCtrl == nullptr) return; wxGetApp().UpdateDarkUI(comboCtrl); wxCheckListBoxComboPopup* popup = new wxCheckListBoxComboPopup; if (popup != nullptr) { // FIXME If the following line is removed, the combo box popup list will not react to mouse clicks. // On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10. comboCtrl->UseAltPopupWindow(); int max_width = 0; // the following line messes up the popup size the first time it is shown on wxWidgets 3.1.3 // comboCtrl->EnablePopupAnimation(false); #ifdef _WIN32 popup->SetFont(comboCtrl->GetFont()); #endif // _WIN32 comboCtrl->SetPopupControl(popup); wxString title = from_u8(text); max_width = std::max(max_width, 60 + comboCtrl->GetTextExtent(title).x); popup->SetStringValue(title); popup->Bind(wxEVT_CHECKLISTBOX, [popup](wxCommandEvent& evt) { popup->OnCheckListBox(evt); }); popup->Bind(wxEVT_LISTBOX, [popup](wxCommandEvent& evt) { popup->OnListBoxSelection(evt); }); popup->Bind(wxEVT_KEY_DOWN, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); }); popup->Bind(wxEVT_KEY_UP, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); }); std::vector items_str; boost::split(items_str, items, boost::is_any_of("|"), boost::token_compress_off); // each item must be composed by 2 parts assert(items_str.size() %2 == 0); for (size_t i = 0; i < items_str.size(); i += 2) { wxString label = from_u8(items_str[i]); max_width = std::max(max_width, 60 + popup->GetTextExtent(label).x); popup->Append(label); popup->Check(i / 2, items_str[i + 1] == "1"); } comboCtrl->SetMinClientSize(wxSize(max_width, -1)); wxGetApp().UpdateDarkUI(popup); } } unsigned int combochecklist_get_flags(wxComboCtrl* comboCtrl) { unsigned int flags = 0; wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup); if (popup != nullptr) { for (unsigned int i = 0; i < popup->GetCount(); ++i) { if (popup->IsChecked(i)) flags |= 1 << i; } } return flags; } void combochecklist_set_flags(wxComboCtrl* comboCtrl, unsigned int flags) { wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup); if (popup != nullptr) { for (unsigned int i = 0; i < popup->GetCount(); ++i) { popup->Check(i, (flags & (1 << i)) != 0); } } } AppConfig* get_app_config() { return wxGetApp().app_config; } wxString from_u8(const std::string &str) { return wxString::FromUTF8(str.c_str()); } std::string into_u8(const wxString &str) { auto buffer_utf8 = str.utf8_str(); return std::string(buffer_utf8.data()); } wxString from_path(const boost::filesystem::path &path) { #ifdef _WIN32 return wxString(path.string()); #else return from_u8(path.string()); #endif } boost::filesystem::path into_path(const wxString &str) { return boost::filesystem::path(str.wx_str()); } void about() { AboutDialog dlg; dlg.ShowModal(); } void desktop_open_datadir_folder() { boost::filesystem::path path(data_dir()); desktop_open_folder(std::move(path)); } void desktop_open_folder(const boost::filesystem::path& path) { if (!boost::filesystem::is_directory(path)) return; // Execute command to open a file explorer, platform dependent. #ifdef _WIN32 const wxString widepath = path.wstring(); const wchar_t* argv[] = { L"explorer", widepath.GetData(), nullptr }; ::wxExecute(const_cast(argv), wxEXEC_ASYNC, nullptr); #elif __APPLE__ const char* argv[] = { "open", path.string().c_str(), nullptr }; ::wxExecute(const_cast(argv), wxEXEC_ASYNC, nullptr); #else const char* argv[] = { "xdg-open", path.string().c_str(), nullptr }; desktop_execute(argv); #endif } #ifdef __linux__ namespace { wxExecuteEnv get_appimage_exec_env() { // If we're running in an AppImage container, we need to remove AppImage's env vars, // because they may mess up the environment expected by the file manager. // Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure. wxEnvVariableHashMap env_vars; wxGetEnvMap(&env_vars); env_vars.erase("APPIMAGE"); env_vars.erase("APPDIR"); env_vars.erase("LD_LIBRARY_PATH"); env_vars.erase("LD_PRELOAD"); env_vars.erase("UNION_PRELOAD"); wxExecuteEnv exec_env; exec_env.env = std::move(env_vars); wxString owd; if (wxGetEnv("OWD", &owd)) { // This is the original work directory from which the AppImage image was run, // set it as CWD for the child process: exec_env.cwd = std::move(owd); } return exec_env; } } // namespace void desktop_execute(const char* argv[]) { // Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars, // because they may mess up the environment expected by the file manager. // Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure. if (wxGetEnv("APPIMAGE", nullptr)) { // We're running from AppImage wxExecuteEnv exec_env = get_appimage_exec_env(); ::wxExecute(const_cast(argv), wxEXEC_ASYNC, nullptr, &exec_env); } else { // Looks like we're NOT running from AppImage, we'll make no changes to the environment. ::wxExecute(const_cast(argv), wxEXEC_ASYNC, nullptr, nullptr); } } void desktop_execute_get_result(wxString command, wxArrayString& output) { output.Clear(); //Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars, // because they may mess up the environment expected by the file manager. // Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure. if (wxGetEnv("APPIMAGE", nullptr)) { // We're running from AppImage wxExecuteEnv exec_env = get_appimage_exec_env(); ::wxExecute(command, output, wxEXEC_SYNC | wxEXEC_NOEVENTS, &exec_env); } else { // Looks like we're NOT running from AppImage, we'll make no changes to the environment. ::wxExecute(command, output, wxEXEC_SYNC | wxEXEC_NOEVENTS); } } #endif // __linux__ #ifdef _WIN32 bool create_process(const boost::filesystem::path& path, const std::wstring& cmd_opt, std::string& error_msg) { // find updater exe if (boost::filesystem::exists(path)) { // Using quoted string as mentioned in CreateProcessW docs. std::wstring wcmd = L"\"" + path.wstring() + L"\""; if (!cmd_opt.empty()) wcmd += L" " + cmd_opt; // additional information STARTUPINFOW si; PROCESS_INFORMATION pi; // set the size of the structures ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); // start the program up if (CreateProcessW(NULL, // the path wcmd.data(), // Command line NULL, // Process handle not inheritable NULL, // Thread handle not inheritable FALSE, // Set handle inheritance to FALSE 0, // No creation flags NULL, // Use parent's environment block NULL, // Use parent's starting directory &si, // Pointer to STARTUPINFO structure &pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses) )) { // Close process and thread handles. CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return true; } else error_msg = "CreateProcessW failed to create process " + into_u8(path.wstring()); } else error_msg = "Executable doesn't exists. Path: " + into_u8(path.wstring()); return false; } #endif //_WIN32 bool has_illegal_characters(const wxString& wxs_name) { const std::string name = into_u8(wxs_name); return has_illegal_characters(name); } bool has_illegal_characters(const std::string& name) { for (size_t i = 0; i < std::strlen(illegal_characters); i++) if (name.find_first_of(illegal_characters[i]) != std::string::npos) return true; return false; } void show_illegal_characters_warning(wxWindow* parent) { show_error(parent, format_wxstr("%1%\n%2% %3%", _L("The provided name is not valid;"), _L("the following characters are not allowed:"), illegal_characters)); } } } // namespaces GUI / Slic3r