PrusaSlicer-NonPlainar/src/slic3r/GUI/GUI_App.cpp

1509 lines
57 KiB
C++
Raw Normal View History

2018-09-20 06:40:22 +00:00
#include "GUI_App.hpp"
2018-10-05 21:29:15 +00:00
#include "GUI_ObjectList.hpp"
#include "GUI_ObjectManipulation.hpp"
#include "I18N.hpp"
2018-09-20 06:40:22 +00:00
#include <algorithm>
#include <iterator>
#include <exception>
#include <cstdlib>
2018-09-20 06:40:22 +00:00
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <boost/nowide/convert.hpp>
2018-09-20 06:40:22 +00:00
#include <wx/stdpaths.h>
#include <wx/imagpng.h>
#include <wx/display.h>
#include <wx/menu.h>
#include <wx/menuitem.h>
2018-09-20 11:12:35 +00:00
#include <wx/filedlg.h>
#include <wx/progdlg.h>
#include <wx/dir.h>
#include <wx/wupdlock.h>
#include <wx/filefn.h>
#include <wx/sysopt.h>
2019-04-09 09:28:11 +00:00
#include <wx/msgdlg.h>
#include <wx/richmsgdlg.h>
2019-04-09 09:28:11 +00:00
#include <wx/log.h>
#include <wx/intl.h>
#include "libslic3r/Utils.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/I18N.hpp"
2018-09-20 06:40:22 +00:00
#include "GUI.hpp"
#include "GUI_Utils.hpp"
2018-09-20 06:40:22 +00:00
#include "AppConfig.hpp"
#include "PresetBundle.hpp"
#include "../Utils/PresetUpdater.hpp"
2018-12-11 09:33:11 +00:00
#include "../Utils/PrintHost.hpp"
2019-04-26 14:59:14 +00:00
#include "../Utils/MacDarkMode.hpp"
#include "slic3r/Config/Snapshot.hpp"
#include "ConfigSnapshotDialog.hpp"
#include "FirmwareDialog.hpp"
#include "Preferences.hpp"
#include "Tab.hpp"
2018-10-25 10:48:19 +00:00
#include "SysInfoDialog.hpp"
2018-12-19 12:06:24 +00:00
#include "KBShortcutsDialog.hpp"
#include "UpdateDialogs.hpp"
#include "Mouse3DController.hpp"
2019-11-27 14:47:37 +00:00
#include "RemovableDriveManager.hpp"
#include "InstanceCheck.hpp"
2018-09-20 06:40:22 +00:00
2019-05-03 13:50:05 +00:00
#ifdef __WXMSW__
#include <dbt.h>
#include <shlobj.h>
2019-05-03 13:50:05 +00:00
#endif // __WXMSW__
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
#include <boost/beast/core/detail/base64.hpp>
#include <boost/nowide/fstream.hpp>
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
2018-09-20 06:40:22 +00:00
namespace Slic3r {
namespace GUI {
class MainFrame;
2018-10-04 09:12:55 +00:00
wxString file_wildcards(FileType file_type, const std::string &custom_extension)
{
static const std::string defaults[FT_SIZE] = {
/* FT_STL */ "STL files (*.stl)|*.stl;*.STL",
/* FT_OBJ */ "OBJ files (*.obj)|*.obj;*.OBJ",
/* FT_AMF */ "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML",
/* FT_3MF */ "3MF files (*.3mf)|*.3mf;*.3MF;",
/* FT_PRUSA */ "Prusa Control files (*.prusa)|*.prusa;*.PRUSA",
/* FT_GCODE */ "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC",
/* FT_MODEL */ "Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA",
/* FT_PROJECT */ "Project files (*.3mf, *.amf)|*.3mf;*.3MF;*.amf;*.AMF",
/* FT_INI */ "INI files (*.ini)|*.ini;*.INI",
/* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG",
/* FT_TEX */ "Texture (*.png, *.svg)|*.png;*.PNG;*.svg;*.SVG",
/* FT_PNGZIP */ "Masked SLA files (*.sl1)|*.sl1;*.SL1",
};
std::string out = defaults[file_type];
if (! custom_extension.empty()) {
// Find the custom extension in the template.
if (out.find(std::string("*") + custom_extension + ",") == std::string::npos && out.find(std::string("*") + custom_extension + ")") == std::string::npos) {
// The custom extension was not found in the template.
// Append the custom extension to the wildcards, so that the file dialog would not add the default extension to it.
boost::replace_first(out, ")|", std::string(", *") + custom_extension + ")|");
out += std::string(";*") + custom_extension;
}
}
return from_u8(out);
}
2018-10-04 09:12:55 +00:00
static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); }
#ifdef WIN32
static void register_win32_dpi_event()
{
enum { WM_DPICHANGED_ = 0x02e0 };
wxWindow::MSWRegisterMessageHandler(WM_DPICHANGED_, [](wxWindow *win, WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) {
const int dpi = wParam & 0xffff;
const auto rect = reinterpret_cast<PRECT>(lParam);
const wxRect wxrect(wxPoint(rect->top, rect->left), wxPoint(rect->bottom, rect->right));
DpiChangedEvent evt(EVT_DPI_CHANGED_SLICER, dpi, wxrect);
win->GetEventHandler()->AddPendingEvent(evt);
return true;
});
}
static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 };
static void register_win32_device_notification_event()
{
enum { WM_DPICHANGED_ = 0x02e0 };
wxWindow::MSWRegisterMessageHandler(WM_DEVICECHANGE, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
// Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only.
auto main_frame = dynamic_cast<MainFrame*>(win);
auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater();
if (plater == nullptr)
// Maybe some other top level window like a dialog or maybe a pop-up menu?
return true;
PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
switch (wParam) {
case DBT_DEVICEARRIVAL:
if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME)
plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED));
else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb;
// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) {
// printf("DBT_DEVICEARRIVAL %d - Media has arrived: %ws\n", msg_count, lpdbi->dbcc_name);
if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID)
plater->GetEventHandler()->AddPendingEvent(HIDDeviceAttachedEvent(EVT_HID_DEVICE_ATTACHED, boost::nowide::narrow(lpdbi->dbcc_name)));
}
break;
case DBT_DEVICEREMOVECOMPLETE:
if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME)
plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED));
else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb;
// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME)
// printf("DBT_DEVICEARRIVAL %d - Media was removed: %ws\n", msg_count, lpdbi->dbcc_name);
if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID)
plater->GetEventHandler()->AddPendingEvent(HIDDeviceDetachedEvent(EVT_HID_DEVICE_DETACHED, boost::nowide::narrow(lpdbi->dbcc_name)));
}
break;
default:
break;
}
return true;
});
wxWindow::MSWRegisterMessageHandler(MainFrame::WM_USER_MEDIACHANGED, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
// Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only.
auto main_frame = dynamic_cast<MainFrame*>(win);
auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater();
if (plater == nullptr)
// Maybe some other top level window like a dialog or maybe a pop-up menu?
return true;
wchar_t sPath[MAX_PATH];
if (lParam == SHCNE_MEDIAINSERTED || lParam == SHCNE_MEDIAREMOVED) {
struct _ITEMIDLIST* pidl = *reinterpret_cast<struct _ITEMIDLIST**>(wParam);
if (! SHGetPathFromIDList(pidl, sPath)) {
BOOST_LOG_TRIVIAL(error) << "MediaInserted: SHGetPathFromIDList failed";
return false;
}
}
switch (lParam) {
case SHCNE_MEDIAINSERTED:
{
//printf("SHCNE_MEDIAINSERTED %S\n", sPath);
plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED));
break;
}
case SHCNE_MEDIAREMOVED:
{
//printf("SHCNE_MEDIAREMOVED %S\n", sPath);
plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED));
break;
}
default:
// printf("Unknown\n");
break;
}
return true;
});
wxWindow::MSWRegisterMessageHandler(WM_INPUT, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
auto main_frame = dynamic_cast<MainFrame*>(Slic3r::GUI::find_toplevel_parent(win));
auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater();
// if (wParam == RIM_INPUTSINK && plater != nullptr && main_frame->IsActive()) {
if (wParam == RIM_INPUT && plater != nullptr && main_frame->IsActive()) {
RAWINPUT raw;
UINT rawSize = sizeof(RAWINPUT);
::GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER));
if (raw.header.dwType == RIM_TYPEHID && plater->get_mouse3d_controller().handle_raw_input_win32(raw.data.hid.bRawData, raw.data.hid.dwSizeHid))
return true;
}
return false;
});
wxWindow::MSWRegisterMessageHandler(WM_COPYDATA, [](wxWindow* win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
COPYDATASTRUCT* copy_data_structure = { 0 };
copy_data_structure = (COPYDATASTRUCT*)lParam;
if (copy_data_structure->dwData == 1) {
LPCWSTR arguments = (LPCWSTR)copy_data_structure->lpData;
Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(boost::nowide::narrow(arguments));
}
return true;
});
}
#endif // WIN32
2019-04-09 09:28:11 +00:00
static void generic_exception_handle()
{
// Note: Some wxWidgets APIs use wxLogError() to report errors, eg. wxImage
// - see https://docs.wxwidgets.org/3.1/classwx_image.html#aa249e657259fe6518d68a5208b9043d0
//
// wxLogError typically goes around exception handling and display an error dialog some time
// after an error is logged even if exception handling and OnExceptionInMainLoop() take place.
// This is why we use wxLogError() here as well instead of a custom dialog, because it accumulates
// errors if multiple have been collected and displays just one error message for all of them.
// Otherwise we would get multiple error messages for one missing png, for example.
//
// If a custom error message window (or some other solution) were to be used, it would be necessary
// to turn off wxLogError() usage in wx APIs, most notably in wxImage
// - see https://docs.wxwidgets.org/trunk/classwx_image.html#aa32e5d3507cc0f8c3330135bc0befc6a
try {
throw;
} catch (const std::bad_alloc& ex) {
// bad_alloc in main thread is most likely fatal. Report immediately to the user (wxLogError would be delayed)
// and terminate the app so it is at least certain to happen now.
wxString errmsg = wxString::Format(_(L("%s has encountered an error. It was likely caused by running out of memory. "
"If you are sure you have enough RAM on your system, this may also be a bug and we would "
"be glad if you reported it.\n\nThe application will now terminate.")), SLIC3R_APP_NAME);
wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _(L("Fatal error")), wxOK | wxICON_ERROR);
BOOST_LOG_TRIVIAL(error) << boost::format("std::bad_alloc exception: %1%") % ex.what();
std::terminate();
} catch (const std::exception& ex) {
2019-04-09 09:28:11 +00:00
wxLogError("Internal error: %s", ex.what());
BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what();
throw;
}
}
IMPLEMENT_APP(GUI_App)
2018-11-26 09:56:07 +00:00
GUI_App::GUI_App()
: wxApp()
, m_em_unit(10)
, m_imgui(new ImGuiWrapper())
, m_wizard(nullptr)
, m_removable_drive_manager(std::make_unique<RemovableDriveManager>())
, m_other_instance_message_handler(std::make_unique<OtherInstanceMessageHandler>())
{
//app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp
this->init_app_config();
}
2018-11-26 09:56:07 +00:00
GUI_App::~GUI_App()
{
if (app_config != nullptr)
delete app_config;
if (preset_bundle != nullptr)
delete preset_bundle;
if (preset_updater != nullptr)
delete preset_updater;
}
#if ENABLE_NON_STATIC_CANVAS_MANAGER
std::string GUI_App::get_gl_info(bool format_as_html, bool extensions)
{
return GLCanvas3DManager::get_gl_info().to_string(format_as_html, extensions);
}
wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas)
{
return m_canvas_mgr.init_glcontext(canvas);
}
bool GUI_App::init_opengl()
{
return m_canvas_mgr.init_gl();
}
#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
void GUI_App::init_app_config()
{
// Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
SetAppName(SLIC3R_APP_KEY);
//SetAppName(SLIC3R_APP_KEY "-beta");
SetAppDisplayName(SLIC3R_APP_NAME);
// Set the Slic3r data directory at the Slic3r XS module.
// Unix: ~/ .Slic3r
// Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r"
// Mac : "~/Library/Application Support/Slic3r"
if (data_dir().empty())
set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data());
if (!app_config)
app_config = new AppConfig();
// load settings
app_conf_exists = app_config->exists();
if (app_conf_exists) {
app_config->load();
}
}
2018-09-20 06:40:22 +00:00
bool GUI_App::OnInit()
2019-04-09 09:28:11 +00:00
{
try {
return on_init_inner();
} catch (const std::exception&) {
2019-04-09 09:28:11 +00:00
generic_exception_handle();
return false;
}
}
bool GUI_App::on_init_inner()
2018-09-20 06:40:22 +00:00
{
// Verify resources path
const wxString resources_dir = from_u8(Slic3r::resources_dir());
wxCHECK_MSG(wxDirExists(resources_dir), false,
wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir));
2018-10-31 09:19:44 +00:00
// Enable this to get the default Win32 COMCTRL32 behavior of static boxes.
// wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0);
// Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible
// performance when working on high resolution multi-display setups.
// wxSystemOptions::SetOption("msw.notebook.themed-background", 0);
// Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION;
std::string msg = Http::tls_global_init();
std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location");
2020-04-29 12:42:43 +00:00
bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store();
if (!msg.empty() && !ssl_accept) {
2020-04-29 12:42:43 +00:00
wxRichMessageDialog
dlg(nullptr,
wxString::Format(_(L("%s\nDo you want to continue?")), msg),
"PrusaSlicer", wxICON_QUESTION | wxYES_NO);
dlg.ShowCheckBox(_(L("Remember my choice")));
if (dlg.ShowModal() != wxID_YES) return false;
app_config->set("tls_cert_store_accepted",
dlg.IsCheckBoxChecked() ? "yes" : "no");
app_config->set("tls_accepted_cert_store_location",
dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : "");
}
app_config->set("version", SLIC3R_VERSION);
app_config->save();
preset_bundle = new PresetBundle();
2018-09-20 06:40:22 +00:00
// just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory
// supplied as argument to --datadir; in that case we should still run the wizard
2019-04-09 09:28:11 +00:00
preset_bundle->setup_directories();
2019-05-03 13:50:05 +00:00
#ifdef __WXMSW__
associate_3mf_files();
#endif // __WXMSW__
preset_updater = new PresetUpdater();
2019-02-14 15:51:41 +00:00
Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent &evt) {
app_config->set("version_online", into_u8(evt.GetString()));
app_config->save();
});
2018-09-20 06:40:22 +00:00
// initialize label colors and fonts
init_label_colours();
init_fonts();
// If load_language() fails, the application closes.
load_language(wxString(), true);
2018-09-20 06:40:22 +00:00
// Suppress the '- default -' presets.
preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1");
2019-04-09 09:28:11 +00:00
try {
preset_bundle->load_presets(*app_config);
} catch (const std::exception &ex) {
show_error(nullptr, ex.what());
2019-04-09 09:28:11 +00:00
}
2018-09-20 06:40:22 +00:00
#ifdef WIN32
register_win32_dpi_event();
register_win32_device_notification_event();
#endif // WIN32
// Let the libslic3r know the callback, which will translate messages on demand.
Slic3r::I18N::set_translate_callback(libslic3r_translate_callback);
2018-09-20 06:40:22 +00:00
// application frame
if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr)
wxImage::AddHandler(new wxPNGHandler());
mainframe = new MainFrame();
mainframe->switch_to(true); // hide settings tabs after first Layout
2018-10-05 21:29:15 +00:00
sidebar().obj_list()->init_objects(); // propagate model objects to object list
// update_mode(); // !!! do that later
2018-09-20 06:40:22 +00:00
SetTopWindow(mainframe);
m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg()));
2019-12-09 16:12:22 +00:00
Bind(wxEVT_IDLE, [this](wxIdleEvent& event)
{
2019-04-09 09:28:11 +00:00
if (! plater_)
return;
//m_other_instance_message_handler->report();
if (app_config->dirty() && app_config->get("autosave") == "1")
app_config->save();
this->obj_manipul()->update_if_dirty();
// Preset updating & Configwizard are done after the above initializations,
// and after MainFrame is created & shown.
// The extra CallAfter() is needed because of Mac, where this is the only way
// to popup a modal dialog on start without screwing combo boxes.
// This is ugly but I honestly found no better way to do it.
// Neither wxShowEvent nor wxWindowCreateEvent work reliably.
static bool once = true;
if (once) {
once = false;
check_updates(false);
CallAfter([this] {
config_wizard_startup();
preset_updater->slic3r_update_notify();
preset_updater->sync(preset_bundle);
});
}
});
load_current_presets();
2018-09-20 06:40:22 +00:00
mainframe->Show(true);
/* Temporary workaround for the correct behavior of the Scrolled sidebar panel:
* change min hight of object list to the normal min value (15 * wxGetApp().em_unit())
* after first whole Mainframe updating/layouting
*/
const int list_min_height = 15 * em_unit();
if (obj_list()->GetMinSize().GetY() > list_min_height)
obj_list()->SetMinSize(wxSize(-1, list_min_height));
update_mode(); // update view mode after fix of the object_list size
m_initialized = true;
return true;
2018-09-20 06:40:22 +00:00
}
unsigned GUI_App::get_colour_approx_luma(const wxColour &colour)
{
double r = colour.Red();
double g = colour.Green();
double b = colour.Blue();
return std::round(std::sqrt(
r * r * .241 +
g * g * .691 +
b * b * .068
));
}
2019-04-26 14:59:14 +00:00
bool GUI_App::dark_mode()
{
#if __APPLE__
// The check for dark mode returns false positive on 10.12 and 10.13,
// which allowed setting dark menu bar and dock area, which is
// is detected as dark mode. We must run on at least 10.14 where the
// proper dark mode was first introduced.
return wxPlatformInfo::Get().CheckOSVersion(10, 14) && mac_dark_mode();
2019-04-26 14:59:14 +00:00
#else
const unsigned luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
return luma < 128;
2019-04-26 14:59:14 +00:00
#endif
}
void GUI_App::init_label_colours()
{
2019-04-26 14:59:14 +00:00
if (dark_mode()) {
m_color_label_modified = wxColour(253, 111, 40);
m_color_label_sys = wxColour(115, 220, 103);
}
else {
m_color_label_modified = wxColour(252, 77, 1);
m_color_label_sys = wxColour(26, 132, 57);
}
m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
}
void GUI_App::update_label_colours_from_appconfig()
{
2018-10-31 11:56:08 +00:00
if (app_config->has("label_clr_sys")) {
auto str = app_config->get("label_clr_sys");
if (str != "")
m_color_label_sys = wxColour(str);
}
2018-10-31 11:56:08 +00:00
if (app_config->has("label_clr_modified")) {
auto str = app_config->get("label_clr_modified");
if (str != "")
m_color_label_modified = wxColour(str);
}
}
void GUI_App::init_fonts()
{
m_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
m_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold();
m_normal_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
#ifdef __WXMAC__
m_small_font.SetPointSize(11);
m_bold_font.SetPointSize(13);
#endif /*__WXMAC__*/
}
void GUI_App::update_fonts(const MainFrame *main_frame)
{
/* Only normal and bold fonts are used for an application rescale,
* because of under MSW small and normal fonts are the same.
* To avoid same rescaling twice, just fill this values
* from rescaled MainFrame
*/
if (main_frame == nullptr)
main_frame = this->mainframe;
m_normal_font = main_frame->normal_font();
m_small_font = m_normal_font;
m_bold_font = main_frame->normal_font().Bold();
m_em_unit = main_frame->em_unit();
}
void GUI_App::set_label_clr_modified(const wxColour& clr) {
m_color_label_modified = clr;
auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue());
std::string str = clr_str.ToStdString();
app_config->set("label_clr_modified", str);
app_config->save();
}
void GUI_App::set_label_clr_sys(const wxColour& clr) {
m_color_label_sys = clr;
auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue());
std::string str = clr_str.ToStdString();
app_config->set("label_clr_sys", str);
app_config->save();
}
float GUI_App::toolbar_icon_scale(const bool is_limited/* = false*/) const
{
#ifdef __APPLE__
const float icon_sc = 1.0f; // for Retina display will be used its own scale
#else
const float icon_sc = m_em_unit*0.1f;
#endif // __APPLE__
const std::string& use_val = app_config->get("use_custom_toolbar_size");
const std::string& val = app_config->get("custom_toolbar_size");
if (val.empty() || use_val.empty() || use_val == "0")
return icon_sc;
int int_val = atoi(val.c_str());
if (is_limited && int_val < 50)
int_val = 50;
return 0.01f * int_val * icon_sc;
}
2018-09-20 06:40:22 +00:00
void GUI_App::recreate_GUI()
{
mainframe->shutdown();
const auto msg_name = _(L("Changing of an application language")) + dots;
wxProgressDialog dlg(msg_name, msg_name);
dlg.Pulse();
dlg.Update(10, _(L("Recreating")) + dots);
MainFrame *old_main_frame = mainframe;
mainframe = new MainFrame();
// Propagate model objects to object list.
sidebar().obj_list()->init_objects();
SetTopWindow(mainframe);
dlg.Update(30, _(L("Recreating")) + dots);
old_main_frame->Destroy();
// For this moment ConfigWizard is deleted, invalidate it.
m_wizard = nullptr;
2018-09-20 06:40:22 +00:00
2019-04-15 14:14:19 +00:00
dlg.Update(80, _(L("Loading of current presets")) + dots);
m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg()));
load_current_presets();
mainframe->Show(true);
dlg.Update(90, _(L("Loading of a mode view")) + dots);
/* Temporary workaround for the correct behavior of the Scrolled sidebar panel:
* change min hight of object list to the normal min value (15 * wxGetApp().em_unit())
* after first whole Mainframe updating/layouting
*/
const int list_min_height = 15 * em_unit();
if (obj_list()->GetMinSize().GetY() > list_min_height)
obj_list()->SetMinSize(wxSize(-1, list_min_height));
update_mode();
// #ys_FIXME_delete_after_testing Do we still need this ?
// CallAfter([]() {
// // Run the config wizard, don't offer the "reset user profile" checkbox.
// config_wizard_startup(true);
// });
2018-09-20 06:40:22 +00:00
}
void GUI_App::system_info()
{
2018-10-25 10:48:19 +00:00
SysInfoDialog dlg;
dlg.ShowModal();
2018-09-20 06:40:22 +00:00
}
2018-12-19 12:06:24 +00:00
void GUI_App::keyboard_shortcuts()
{
KBShortcutsDialog dlg;
dlg.ShowModal();
}
2018-09-20 06:40:22 +00:00
// static method accepting a wxWindow object as first parameter
bool GUI_App::catch_error(std::function<void()> cb,
// wxMessageDialog* message_dialog,
2018-10-31 11:56:08 +00:00
const std::string& err /*= ""*/)
{
2018-09-20 06:40:22 +00:00
if (!err.empty()) {
if (cb)
cb();
// if (message_dialog)
// message_dialog->(err, "Error", wxOK | wxICON_ERROR);
show_error(/*this*/nullptr, err);
return true;
}
return false;
}
// static method accepting a wxWindow object as first parameter
2018-10-31 11:56:08 +00:00
void fatal_error(wxWindow* parent)
{
2018-09-20 06:40:22 +00:00
show_error(parent, "");
// exit 1; // #ys_FIXME
}
// Called after the Preferences dialog is closed and the program settings are saved.
// Update the UI based on the current preferences.
2018-10-31 11:56:08 +00:00
void GUI_App::update_ui_from_settings()
{
2018-09-20 06:40:22 +00:00
mainframe->update_ui_from_settings();
}
void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_maximized)
2019-01-11 17:09:21 +00:00
{
const std::string name = into_u8(window->GetName());
window->Bind(wxEVT_CLOSE_WINDOW, [=](wxCloseEvent &event) {
window_pos_save(window, name);
event.Skip();
});
window_pos_restore(window, name, default_maximized);
2019-02-07 14:55:47 +00:00
on_window_geometry(window, [=]() {
window_pos_sanitize(window);
2019-01-11 17:09:21 +00:00
});
}
void GUI_App::load_project(wxWindow *parent, wxString& input_file) const
{
input_file.Clear();
wxFileDialog dialog(parent ? parent : GetTopWindow(),
_(L("Choose one file (3MF/AMF):")),
app_config->get_last_dir(), "",
file_wildcards(FT_PROJECT), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
2018-09-20 11:12:35 +00:00
if (dialog.ShowModal() == wxID_OK)
input_file = dialog.GetPath();
}
void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const
2018-09-20 11:12:35 +00:00
{
#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI
if (this->plater_ != nullptr)
// hides the tooltip
plater_->get_current_canvas3D()->set_tooltip("");
#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI
input_files.Clear();
wxFileDialog dialog(parent ? parent : GetTopWindow(),
2018-09-20 11:12:35 +00:00
_(L("Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):")),
2019-01-02 14:11:05 +00:00
from_u8(app_config->get_last_dir()), "",
file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
2018-09-20 11:12:35 +00:00
if (dialog.ShowModal() == wxID_OK)
dialog.GetPaths(input_files);
2018-09-20 11:12:35 +00:00
}
2018-09-20 06:40:22 +00:00
bool GUI_App::switch_language()
{
if (select_language()) {
recreate_GUI();
return true;
} else {
return false;
}
}
// select language from the list of installed languages
bool GUI_App::select_language()
{
wxArrayString 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)
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; });
wxArrayString names;
names.Alloc(language_infos.size());
// Some valid language should be selected since the application start up.
const wxLanguage current_language = wxLanguage(m_wxLocale->GetLanguage());
int init_selection = -1;
int init_selection_alt = -1;
int init_selection_default = -1;
for (size_t i = 0; i < language_infos.size(); ++ i) {
if (wxLanguage(language_infos[i]->Language) == current_language)
// The dictionary matches the active language and country.
init_selection = i;
else if ((language_infos[i]->CanonicalName.BeforeFirst('_') == m_wxLocale->GetCanonicalName().BeforeFirst('_')) ||
// if the active language is Slovak, mark the Czech language as active.
(language_infos[i]->CanonicalName.BeforeFirst('_') == "cs" && m_wxLocale->GetCanonicalName().BeforeFirst('_') == "sk"))
// The dictionary matches the active language, it does not necessarily match the country.
init_selection_alt = i;
if (language_infos[i]->CanonicalName.BeforeFirst('_') == "en")
// This will be the default selection if the active language does not match any dictionary.
init_selection_default = i;
names.Add(language_infos[i]->Description);
}
if (init_selection == -1)
// This is the dictionary matching the active language.
init_selection = init_selection_alt;
if (init_selection != -1)
// This is the language to highlight in the choice dialog initially.
init_selection_default = init_selection;
const long index = wxGetSingleChoiceIndex(_(L("Select the language")), _(L("Language")), names, init_selection_default);
// Try to load a new language.
if (index != -1 && (init_selection == -1 || init_selection != index)) {
const wxLanguageInfo *new_language_info = language_infos[index];
if (this->load_language(new_language_info->CanonicalName, false)) {
// Save language at application config.
// Which language to save as the selected dictionary language?
// 1) Hopefully the language set to wxTranslations by this->load_language(), but that API is weird and we don't want to rely on its
// stability in the future:
// wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH);
// 2) Current locale language may not match the dictionary name, see GH issue #3901
// m_wxLocale->GetCanonicalName()
// 3) new_language_info->CanonicalName is a safe bet. It points to a valid dictionary name.
app_config->set("translation_language", new_language_info->CanonicalName.ToUTF8().data());
app_config->save();
return true;
}
}
return false;
}
// Load gettext translation files and activate them at the start of the application,
// based on the "translation_language" key stored in the application config.
bool GUI_App::load_language(wxString language, bool initial)
{
if (initial) {
// There is a static list of lookup path prefixes in wxWidgets. Add ours.
wxFileTranslationsLoader::AddCatalogLookupPathPrefix(from_u8(localization_dir()));
// Get the active language from PrusaSlicer.ini, or empty string if the key does not exist.
language = app_config->get("translation_language");
if (! language.empty())
BOOST_LOG_TRIVIAL(trace) << boost::format("translation_language provided by PrusaSlicer.ini: %1%") % language;
// Get the system language.
{
const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage());
if (lang_system != wxLANGUAGE_UNKNOWN) {
m_language_info_system = wxLocale::GetLanguageInfo(lang_system);
BOOST_LOG_TRIVIAL(trace) << boost::format("System language detected (user locales and such): %1%") % m_language_info_system->CanonicalName.ToUTF8().data();
}
}
{
// Allocating a temporary locale will switch the default wxTranslations to its internal wxTranslations instance.
wxLocale temp_locale;
// Set the current translation's language to default, otherwise GetBestTranslation() may not work (see the wxWidgets source code).
wxTranslations::Get()->SetLanguage(wxLANGUAGE_DEFAULT);
// Let the wxFileTranslationsLoader enumerate all translation dictionaries for PrusaSlicer
// and try to match them with the system specific "preferred languages".
// There seems to be a support for that on Windows and OSX, while on Linuxes the code just returns wxLocale::GetSystemLanguage().
// The last parameter gets added to the list of detected dictionaries. This is a workaround
// for not having the English dictionary. Let's hope wxWidgets of various versions process this call the same way.
wxString best_language = wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH);
if (! best_language.IsEmpty()) {
m_language_info_best = wxLocale::FindLanguageInfo(best_language);
BOOST_LOG_TRIVIAL(trace) << boost::format("Best translation language detected (may be different from user locales): %1%") % m_language_info_best->CanonicalName.ToUTF8().data();
}
}
}
const wxLanguageInfo *language_info = language.empty() ? nullptr : wxLocale::FindLanguageInfo(language);
if (! language.empty() && (language_info == nullptr || language_info->CanonicalName.empty())) {
// Fix for wxWidgets issue, where the FindLanguageInfo() returns locales with undefined ANSII code (wxLANGUAGE_KONKANI or wxLANGUAGE_MANIPURI).
language_info = nullptr;
BOOST_LOG_TRIVIAL(error) << boost::format("Language code \"%1%\" is not supported") % language.ToUTF8().data();
}
if (language_info != nullptr && language_info->LayoutDirection == wxLayout_RightToLeft) {
BOOST_LOG_TRIVIAL(trace) << boost::format("The following language code requires right to left layout, which is not supported by PrusaSlicer: %1%") % language_info->CanonicalName.ToUTF8().data();
language_info = nullptr;
}
if (language_info == nullptr) {
// PrusaSlicer does not support the Right to Left languages yet.
if (m_language_info_system != nullptr && m_language_info_system->LayoutDirection != wxLayout_RightToLeft)
language_info = m_language_info_system;
if (m_language_info_best != nullptr && m_language_info_best->LayoutDirection != wxLayout_RightToLeft)
language_info = m_language_info_best;
if (language_info == nullptr)
language_info = wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH_US);
}
BOOST_LOG_TRIVIAL(trace) << boost::format("Switching wxLocales to %1%") % language_info->CanonicalName.ToUTF8().data();
// Alternate language code.
wxLanguage language_dict = wxLanguage(language_info->Language);
if (language_info->CanonicalName.BeforeFirst('_') == "sk") {
// Slovaks understand Czech well. Give them the Czech translation.
language_dict = wxLANGUAGE_CZECH;
BOOST_LOG_TRIVIAL(trace) << "Using Czech dictionaries for Slovak language";
}
// Select language for locales. This language may be different from the language of the dictionary.
if (language_info == m_language_info_best || language_info == m_language_info_system) {
// The current language matches user's default profile exactly. That's great.
} else if (m_language_info_best != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_best->CanonicalName.BeforeFirst('_')) {
// Use whatever the operating system recommends, if it the language code of the dictionary matches the recommended language.
// This allows a Swiss guy to use a German dictionary without forcing him to German locales.
language_info = m_language_info_best;
} else if (m_language_info_system != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_system->CanonicalName.BeforeFirst('_'))
language_info = m_language_info_system;
if (! wxLocale::IsAvailable(language_info->Language)) {
// Loading the language dictionary failed.
wxString message = "Switching PrusaSlicer to language " + language_info->CanonicalName + " failed.";
#if !defined(_WIN32) && !defined(__APPLE__)
// likely some linux system
message += "\nYou may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n";
#endif
if (initial)
message + "\n\nApplication will close.";
wxMessageBox(message, "PrusaSlicer - Switching language failed", wxOK | wxICON_ERROR);
if (initial)
std::exit(EXIT_FAILURE);
else
return false;
}
// Release the old locales, create new locales.
//FIXME wxWidgets cause havoc if the current locale is deleted. We just forget it causing memory leaks for now.
m_wxLocale.release();
m_wxLocale = Slic3r::make_unique<wxLocale>();
m_wxLocale->Init(language_info->Language);
// Override language at the active wxTranslations class (which is stored in the active m_wxLocale)
// to load possibly different dictionary, for example, load Czech dictionary for Slovak language.
wxTranslations::Get()->SetLanguage(language_dict);
m_wxLocale->AddCatalog(SLIC3R_APP_KEY);
m_imgui->set_language(into_u8(language_info->CanonicalName));
//FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only.
wxSetlocale(LC_NUMERIC, "C");
Preset::update_suffix_modified();
return true;
}
Tab* GUI_App::get_tab(Preset::Type type)
{
for (Tab* tab: tabs_list)
if (tab->type() == type)
Various changes in handling of profile compatiblilities and the "show incompatible profiles" option. It was not able to select the incompatible Print profile, which is possible now. (see Cannot select incompatible printer profile #3715) When the Printer profile derived from the Prusa3D system profile was active or a system Prusa3D profile was active, and when the Print profile with the removed "inherits" field was active (or any other profile derived from the "-- default --" profile was active), then the filament selector offered just the profiles with the removed "inherits" field (or any other profile derived from the "-- default--") profile. This behavior has been now changed, so that in this scenario the Filament selector will offer the Prusa3D vendor profiles compatible with the active Print and Printer profile as well as the user profiles. Slicer was also changed to keep an incompatible preset selected at its respective tab if its respective "Red flag" is enabled. For example, if an incompatible Print preset is selected and a Printer profile is switched to another one which is not compatible with the active Print preset that was red already, the active Print preset is not switched if the Print "Red flag" is active. However, if the Print profile was compatible before the Printer profile is switched and now the Print profile becomes incompatible, another compatible Print profile is selected. A likely bug in wxWidgets was worked around when switching a Print preset on Plater, if the last item in the Print preset was active and incompatible, and another Print preset was selected by the user. On Windows, an CBN_EDITCHANGE is sent just after combo box selection change event and the CBN_EDITCHANGE holds an index of the combo box item, which will be removed by the 1st event, therefore leading to an assert in wxWidgets on CBN_EDITCHANGE. The workaround is to disable processing of CBN_EDITCHANGE on Windows for the Plater preset selection combo boxes.
2020-02-27 10:44:01 +00:00
return tab->completed() ? tab : nullptr; // To avoid actions with no-completed Tab
return nullptr;
}
ConfigOptionMode GUI_App::get_mode()
{
if (!app_config->has("view_mode"))
return comSimple;
const auto mode = app_config->get("view_mode");
return mode == "expert" ? comExpert :
mode == "simple" ? comSimple : comAdvanced;
}
void GUI_App::save_mode(const /*ConfigOptionMode*/int mode)
{
const std::string mode_str = mode == comExpert ? "expert" :
mode == comSimple ? "simple" : "advanced";
app_config->set("view_mode", mode_str);
app_config->save();
update_mode();
}
2018-10-03 13:14:52 +00:00
// Update view mode according to selected menu
void GUI_App::update_mode()
{
sidebar().update_mode();
for (auto tab : tabs_list)
2019-03-20 15:22:01 +00:00
tab->update_mode();
plater()->update_object_menu();
2018-10-03 13:14:52 +00:00
}
void GUI_App::add_config_menu(wxMenuBar *menu)
{
auto local_menu = new wxMenu();
wxWindowID config_id_base = wxWindow::NewControlId(int(ConfigMenuCnt));
const auto config_wizard_name = _(ConfigWizard::name(true));
const auto config_wizard_tooltip = from_u8((boost::format(_utf8(L("Run %s"))) % config_wizard_name).str());
// Cmd+, is standard on OS X - what about other operating systems?
local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip);
2019-01-21 11:34:28 +00:00
local_menu->Append(config_id_base + ConfigMenuSnapshots, _(L("&Configuration Snapshots")) + dots, _(L("Inspect / activate configuration snapshots")));
local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _(L("Take Configuration &Snapshot")), _(L("Capture a configuration snapshot")));
local_menu->Append(config_id_base + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates")));
local_menu->AppendSeparator();
local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("&Preferences")) + dots +
#ifdef __APPLE__
"\tCtrl+,",
#else
"\tCtrl+P",
#endif
_(L("Application preferences")));
local_menu->AppendSeparator();
auto mode_menu = new wxMenu();
2018-11-21 09:21:12 +00:00
mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _(L("Simple")), _(L("Simple View Mode")));
// mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _(L("Advanced")), _(L("Advanced View Mode")));
mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _CTX(L_CONTEXT("Advanced", "Mode"), "Mode"), _L("Advanced View Mode"));
2018-11-21 09:21:12 +00:00
mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _(L("Expert")), _(L("Expert View Mode")));
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if(get_mode() == comSimple) evt.Check(true); }, config_id_base + ConfigMenuModeSimple);
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if(get_mode() == comAdvanced) evt.Check(true); }, config_id_base + ConfigMenuModeAdvanced);
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if(get_mode() == comExpert) evt.Check(true); }, config_id_base + ConfigMenuModeExpert);
local_menu->AppendSubMenu(mode_menu, _(L("Mode")), wxString::Format(_(L("%s View Mode")), SLIC3R_APP_NAME));
local_menu->AppendSeparator();
2020-02-19 12:21:16 +00:00
local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("&Language")));
local_menu->AppendSeparator();
2019-01-21 11:34:28 +00:00
local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _(L("Flash printer &firmware")), _(L("Upload a firmware image into an Arduino based printer")));
// TODO: for when we're able to flash dictionaries
// local_menu->Append(config_id_base + FirmwareMenuDict, _(L("Flash language file")), _(L("Upload a language dictionary file into a Prusa printer")));
2018-10-31 11:56:08 +00:00
local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) {
switch (event.GetId() - config_id_base) {
case ConfigMenuWizard:
run_wizard(ConfigWizard::RR_USER);
break;
case ConfigMenuUpdate:
check_updates(true);
break;
case ConfigMenuTakeSnapshot:
// Take a configuration snapshot.
if (check_unsaved_changes()) {
wxTextEntryDialog dlg(nullptr, _(L("Taking configuration snapshot")), _(L("Snapshot name")));
// set current normal font for dialog children,
// because of just dlg.SetFont(normal_font()) has no result;
for (auto child : dlg.GetChildren())
child->SetFont(normal_font());
if (dlg.ShowModal() == wxID_OK)
app_config->set("on_snapshot",
Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot(
*app_config, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()).id);
}
break;
case ConfigMenuSnapshots:
if (check_unsaved_changes()) {
std::string on_snapshot;
if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config))
on_snapshot = app_config->get("on_snapshot");
ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot);
dlg.ShowModal();
if (!dlg.snapshot_to_activate().empty()) {
if (!Config::SnapshotDB::singleton().is_on_snapshot(*app_config))
Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK);
app_config->set("on_snapshot",
Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id);
preset_bundle->load_presets(*app_config);
// Load the currently selected preset into the GUI, update the preset selection box.
load_current_presets();
}
}
break;
case ConfigMenuPreferences:
{
PreferencesDialog dlg(mainframe);
dlg.ShowModal();
break;
}
case ConfigMenuLanguage:
{
/* Before change application language, let's check unsaved changes on 3D-Scene
* and draw user's attention to the application restarting after a language change
*/
{
// 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
wxMessageDialog dialog(nullptr,
_(L("Switching the language will trigger application restart.\n"
"You will lose content of the plater.")) + "\n\n" +
_(L("Do you want to proceed?")),
wxString(SLIC3R_APP_NAME) + " - " + _(L("Language selection")),
wxICON_QUESTION | wxOK | wxCANCEL);
if (dialog.ShowModal() == wxID_CANCEL)
return;
}
switch_language();
break;
}
case ConfigMenuFlashFirmware:
FirmwareDialog::run(mainframe);
break;
default:
break;
}
});
using std::placeholders::_1;
auto modfn = [this](int mode, wxCommandEvent&) { if(get_mode() != mode) save_mode(mode); };
mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comSimple, _1), config_id_base + ConfigMenuModeSimple);
mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comAdvanced, _1), config_id_base + ConfigMenuModeAdvanced);
mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comExpert, _1), config_id_base + ConfigMenuModeExpert);
menu->Append(local_menu, _(L("&Configuration")));
}
// This is called when closing the application, when loading a config file or when starting the config wizard
// to notify the user whether he is aware that some preset changes will be lost.
bool GUI_App::check_unsaved_changes(const wxString &header)
{
wxString dirty;
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
for (Tab *tab : tabs_list)
if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) {
if (dirty.empty())
dirty = tab->title();
else
dirty += wxString(", ") + tab->title();
}
if (dirty.empty())
// No changes, the application may close or reload presets.
return true;
// Ask the user.
wxString message;
if (! header.empty())
message = header + "\n\n";
message += _(L("The presets on the following tabs were modified")) + ": " + dirty + "\n\n" + _(L("Discard changes and continue anyway?"));
wxMessageDialog dialog(mainframe,
message,
2019-05-09 17:24:21 +00:00
wxString(SLIC3R_APP_NAME) + " - " + _(L("Unsaved Presets")),
wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT);
return dialog.ShowModal() == wxID_YES;
}
bool GUI_App::checked_tab(Tab* tab)
{
bool ret = true;
if (find(tabs_list.begin(), tabs_list.end(), tab) == tabs_list.end())
ret = false;
return ret;
}
// Update UI / Tabs to reflect changes in the currently loaded presets
void GUI_App::load_current_presets()
{
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
this->plater()->set_printer_technology(printer_technology);
for (Tab *tab : tabs_list)
if (tab->supports_printer_technology(printer_technology)) {
if (tab->type() == Preset::TYPE_PRINTER) {
static_cast<TabPrinter*>(tab)->update_pages();
// Mark the plater to update print bed by tab->load_current_preset() from Plater::on_config_change().
this->plater()->force_print_bed_update();
}
tab->load_current_preset();
}
}
bool GUI_App::OnExceptionInMainLoop()
{
2019-04-09 09:28:11 +00:00
generic_exception_handle();
return false;
}
#ifdef __APPLE__
// wxWidgets override to get an event on open files.
void GUI_App::MacOpenFiles(const wxArrayString &fileNames)
{
std::vector<std::string> files;
for (size_t i = 0; i < fileNames.GetCount(); ++ i)
files.emplace_back(fileNames[i].ToUTF8().data());
this->plater()->load_files(files, true, true);
}
#endif /* __APPLE */
Sidebar& GUI_App::sidebar()
{
return plater_->sidebar();
}
ObjectManipulation* GUI_App::obj_manipul()
{
2019-01-14 13:42:21 +00:00
// If this method is called before plater_ has been initialized, return nullptr (to avoid a crash)
return (plater_ != nullptr) ? sidebar().obj_manipul() : nullptr;
}
ObjectSettings* GUI_App::obj_settings()
{
return sidebar().obj_settings();
}
2018-10-05 21:29:15 +00:00
ObjectList* GUI_App::obj_list()
{
2018-10-05 21:29:15 +00:00
return sidebar().obj_list();
}
ObjectLayers* GUI_App::obj_layers()
{
return sidebar().obj_layers();
}
Plater* GUI_App::plater()
{
return plater_;
}
Model& GUI_App::model()
2018-10-05 21:29:15 +00:00
{
return plater_->model();
2018-10-05 21:29:15 +00:00
}
2018-10-05 21:29:15 +00:00
wxNotebook* GUI_App::tab_panel() const
{
return mainframe->m_tabpanel;
}
// extruders count from selected printer preset
2019-03-19 13:36:32 +00:00
int GUI_App::extruders_cnt() const
{
const Preset& preset = preset_bundle->printers.get_selected_preset();
return preset.printer_technology() == ptSLA ? 1 :
preset.config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
}
// extruders count from edited printer preset
int GUI_App::extruders_edited_cnt() const
{
const Preset& preset = preset_bundle->printers.get_edited_preset();
return preset.printer_technology() == ptSLA ? 1 :
preset.config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
}
wxString GUI_App::current_language_code_safe() const
{
// Translate the language code to a code, for which Prusa Research maintains translations.
const std::map<wxString, wxString> mapping {
{ "cs", "cs_CZ", },
{ "sk", "cs_CZ", },
{ "de", "de_DE", },
{ "es", "es_ES", },
{ "fr", "fr_FR", },
{ "it", "it_IT", },
{ "ja", "ja_JP", },
{ "ko", "ko_KR", },
{ "pl", "pl_PL", },
{ "uk", "uk_UA", },
{ "zh", "zh_CN", },
};
wxString language_code = this->current_language_code().BeforeFirst('_');
auto it = mapping.find(language_code);
if (it != mapping.end())
language_code = it->second;
else
language_code = "en_US";
return language_code;
}
void GUI_App::open_web_page_localized(const std::string &http_address)
{
wxLaunchDefaultBrowser(http_address + "&lng=" + this->current_language_code_safe());
}
bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page)
{
wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
if (! m_wizard) {
m_wizard = new ConfigWizard(mainframe);
}
const bool res = m_wizard->run(reason, start_page);
if (res) {
load_current_presets();
if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA
&& Slic3r::model_has_multi_part_objects(wxGetApp().model())) {
GUI::show_info(nullptr,
_(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" +
_(L("Please check and fix your object list.")),
_(L("Attention!")));
}
}
return res;
}
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
void GUI_App::gcode_thumbnails_debug()
{
const std::string BEGIN_MASK = "; thumbnail begin";
const std::string END_MASK = "; thumbnail end";
std::string gcode_line;
bool reading_image = false;
unsigned int width = 0;
unsigned int height = 0;
wxFileDialog dialog(GetTopWindow(), _(L("Select a gcode file:")), "", "", "G-code files (*.gcode)|*.gcode;*.GCODE;", wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() != wxID_OK)
return;
std::string in_filename = into_u8(dialog.GetPath());
std::string out_path = boost::filesystem::path(in_filename).remove_filename().append(L"thumbnail").string();
boost::nowide::ifstream in_file(in_filename.c_str());
std::vector<std::string> rows;
std::string row;
if (in_file.good())
{
while (std::getline(in_file, gcode_line))
{
if (in_file.good())
{
if (boost::starts_with(gcode_line, BEGIN_MASK))
{
reading_image = true;
gcode_line = gcode_line.substr(BEGIN_MASK.length() + 1);
std::string::size_type x_pos = gcode_line.find('x');
std::string width_str = gcode_line.substr(0, x_pos);
width = (unsigned int)::atoi(width_str.c_str());
std::string height_str = gcode_line.substr(x_pos + 1);
height = (unsigned int)::atoi(height_str.c_str());
row.clear();
}
else if (reading_image && boost::starts_with(gcode_line, END_MASK))
{
std::string out_filename = out_path + std::to_string(width) + "x" + std::to_string(height) + ".png";
boost::nowide::ofstream out_file(out_filename.c_str(), std::ios::binary);
if (out_file.good())
{
std::string decoded;
decoded.resize(boost::beast::detail::base64::decoded_size(row.size()));
decoded.resize(boost::beast::detail::base64::decode((void*)&decoded[0], row.data(), row.size()).first);
out_file.write(decoded.c_str(), decoded.size());
out_file.close();
}
reading_image = false;
width = 0;
height = 0;
rows.clear();
}
else if (reading_image)
row += gcode_line.substr(2);
}
}
in_file.close();
}
}
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
2019-01-11 17:09:21 +00:00
void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name)
{
if (name.empty()) { return; }
const auto config_key = (boost::format("window_%1%") % name).str();
WindowMetrics metrics = WindowMetrics::from_window(window);
app_config->set(config_key, metrics.serialize());
app_config->save();
}
void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized)
2019-01-11 17:09:21 +00:00
{
if (name.empty()) { return; }
const auto config_key = (boost::format("window_%1%") % name).str();
if (! app_config->has(config_key)) {
window->Maximize(default_maximized);
return;
}
2019-01-11 17:09:21 +00:00
auto metrics = WindowMetrics::deserialize(app_config->get(config_key));
if (! metrics) {
window->Maximize(default_maximized);
return;
}
2019-01-11 17:09:21 +00:00
window->SetSize(metrics->get_rect());
window->Maximize(metrics->get_maximized());
}
void GUI_App::window_pos_sanitize(wxTopLevelWindow* window)
{
/*unsigned*/int display_idx = wxDisplay::GetFromWindow(window);
2019-01-11 17:09:21 +00:00
wxRect display;
if (display_idx == wxNOT_FOUND) {
display = wxDisplay(0u).GetClientArea();
window->Move(display.GetTopLeft());
} else {
display = wxDisplay(display_idx).GetClientArea();
}
auto metrics = WindowMetrics::from_window(window);
metrics.sanitize_for_display(display);
if (window->GetScreenRect() != metrics.get_rect()) {
window->SetSize(metrics.get_rect());
}
}
bool GUI_App::config_wizard_startup()
{
if (!app_conf_exists || preset_bundle->printers.size() <= 1) {
run_wizard(ConfigWizard::RR_DATA_EMPTY);
return true;
} else if (get_app_config()->legacy_datadir()) {
// Looks like user has legacy pre-vendorbundle data directory,
// explain what this is and run the wizard
MsgDataLegacy dlg;
dlg.ShowModal();
run_wizard(ConfigWizard::RR_DATA_LEGACY);
return true;
}
return false;
}
void GUI_App::check_updates(const bool verbose)
{
PresetUpdater::UpdateResult updater_result;
try {
updater_result = preset_updater->config_update(app_config->orig_version());
if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
mainframe->Close();
}
else if (updater_result == PresetUpdater::R_INCOMPAT_CONFIGURED) {
app_conf_exists = true;
}
else if(verbose && updater_result == PresetUpdater::R_NOOP)
{
MsgNoUpdates dlg;
dlg.ShowModal();
}
}
catch (const std::exception & ex) {
show_error(nullptr, ex.what());
}
}
2018-09-20 06:40:22 +00:00
// static method accepting a wxWindow object as first parameter
// void warning_catcher{
// my($self, $message_dialog) = @_;
// return sub{
// my $message = shift;
// return if $message = ~/ GLUquadricObjPtr | Attempt to free unreferenced scalar / ;
// my @params = ($message, 'Warning', wxOK | wxICON_WARNING);
// $message_dialog
// ? $message_dialog->(@params)
// : Wx::MessageDialog->new($self, @params)->ShowModal;
// };
// }
// Do we need this function???
2018-10-31 11:56:08 +00:00
// void GUI_App::notify(message) {
2018-09-20 06:40:22 +00:00
// auto frame = GetTopWindow();
// // try harder to attract user attention on OS X
// if (!frame->IsActive())
// frame->RequestUserAttention(defined(__WXOSX__/*&Wx::wxMAC */)? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO);
//
// // There used to be notifier using a Growl application for OSX, but Growl is dead.
// // The notifier also supported the Linux X D - bus notifications, but that support was broken.
// //TODO use wxNotificationMessage ?
// }
2019-05-03 13:50:05 +00:00
#ifdef __WXMSW__
void GUI_App::associate_3mf_files()
{
// see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association
auto reg_set = [](HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue)->bool
2019-05-03 13:50:05 +00:00
{
wchar_t szValueCurrent[1000];
DWORD dwType;
DWORD dwSize = sizeof(szValueCurrent);
int iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize);
bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND;
if ((iRC != ERROR_SUCCESS) && !bDidntExist)
// an error occurred
return false;
2019-05-03 13:50:05 +00:00
if (!bDidntExist)
{
if (dwType != REG_SZ)
// invalid type
return false;
2019-05-03 13:50:05 +00:00
if (::wcscmp(szValueCurrent, pszValue) == 0)
// value already set
return false;
2019-05-03 13:50:05 +00:00
}
DWORD dwDisposition;
HKEY hkey;
iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition);
bool ret = false;
2019-05-03 13:50:05 +00:00
if (iRC == ERROR_SUCCESS)
{
2019-05-03 13:50:05 +00:00
iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t));
if (iRC == ERROR_SUCCESS)
ret = true;
}
2019-05-03 13:50:05 +00:00
RegCloseKey(hkey);
return ret;
2019-05-03 13:50:05 +00:00
};
wchar_t app_path[MAX_PATH];
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
std::wstring prog_id = L"Prusa.Slicer.1";
std::wstring prog_desc = L"PrusaSlicer";
std::wstring prog_command = prog_path + L" \"%1\"";
std::wstring reg_base = L"Software\\Classes";
std::wstring reg_extension = reg_base + L"\\.3mf";
std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
bool is_new = false;
is_new |= reg_set(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
is_new |= reg_set(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
is_new |= reg_set(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
2019-05-03 13:50:05 +00:00
if (is_new)
// notify Windows only when any of the values gets changed
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
2019-05-03 13:50:05 +00:00
}
#endif // __WXMSW__
2018-09-20 06:40:22 +00:00
} // GUI
} //Slic3r