OrcaSlicer/src/slic3r/GUI/GUI_App.cpp
Ocraftyone 5c9b82d6ec
Update Web Link Association in Preferences (#5791)
* Fix check_url_association return value

* Update Web Link Association Preferences

Uses a checkbox in place of button to show if the current instance is registered at a quick look
New function is added to build the link association item
Current association line shows "None" for no app associated, "Current Instance" if the current instance is associated, and the formatted path to the registered app (Removes quotes and other extra chars)

* Update to use localization

* Fix Linux Build

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2024-06-21 21:06:12 +08:00

6666 lines
247 KiB
C++

#include "libslic3r/Technologies.hpp"
#include "GUI_App.hpp"
#include "GUI_Init.hpp"
#include "GUI_ObjectList.hpp"
#include "GUI_Factories.hpp"
#include "slic3r/GUI/UserManager.hpp"
#include "slic3r/GUI/TaskManager.hpp"
#include "format.hpp"
#include "libslic3r_version.h"
#include "Downloader.hpp"
// Localization headers: include libslic3r version first so everything in this file
// uses the slic3r/GUI version (the macros will take precedence over the functions).
// Also, there is a check that the former is not included from slic3r module.
// This is the only place where we want to allow that, so define an override macro.
#define SLIC3R_ALLOW_LIBSLIC3R_I18N_IN_SLIC3R
#include "libslic3r/I18N.hpp"
#undef SLIC3R_ALLOW_LIBSLIC3R_I18N_IN_SLIC3R
#include "slic3r/GUI/I18N.hpp"
#include <algorithm>
#include <iterator>
#include <exception>
#include <cstdlib>
#include <regex>
#include <thread>
#include <string_view>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/log/trivial.hpp>
#include <boost/nowide/convert.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <wx/stdpaths.h>
#include <wx/imagpng.h>
#include <wx/display.h>
#include <wx/menu.h>
#include <wx/menuitem.h>
#include <wx/filedlg.h>
#include <wx/progdlg.h>
#include <wx/dir.h>
#include <wx/wupdlock.h>
#include <wx/filefn.h>
#include <wx/sysopt.h>
#include <wx/richmsgdlg.h>
#include <wx/log.h>
#include <wx/intl.h>
#include <wx/dialog.h>
#include <wx/textctrl.h>
#include <wx/splash.h>
#include <wx/fontutil.h>
#include <wx/glcanvas.h>
#include "libslic3r/Utils.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/I18N.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/Thread.hpp"
#include "libslic3r/miniz_extension.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Color.hpp"
#include "GUI.hpp"
#include "GUI_Utils.hpp"
#include "3DScene.hpp"
#include "MainFrame.hpp"
#include "Plater.hpp"
#include "GLCanvas3D.hpp"
#include "../Utils/PresetUpdater.hpp"
#include "../Utils/PrintHost.hpp"
#include "../Utils/Process.hpp"
#include "../Utils/MacDarkMode.hpp"
#include "../Utils/Http.hpp"
#include "../Utils/UndoRedo.hpp"
#include "slic3r/Config/Snapshot.hpp"
#include "Preferences.hpp"
#include "Tab.hpp"
#include "SysInfoDialog.hpp"
#include "UpdateDialogs.hpp"
#include "Mouse3DController.hpp"
#include "RemovableDriveManager.hpp"
#include "InstanceCheck.hpp"
#include "NotificationManager.hpp"
#include "UnsavedChangesDialog.hpp"
#include "SavePresetDialog.hpp"
#include "PrintHostDialogs.hpp"
#include "DesktopIntegrationDialog.hpp"
#include "SendSystemInfoDialog.hpp"
#include "ParamsDialog.hpp"
#include "KBShortcutsDialog.hpp"
#include "DownloadProgressDialog.hpp"
#include "BitmapCache.hpp"
#include "Notebook.hpp"
#include "Widgets/Label.hpp"
#include "Widgets/ProgressDialog.hpp"
//BBS: DailyTip and UserGuide Dialog
#include "WebDownPluginDlg.hpp"
#include "WebGuideDialog.hpp"
#include "ReleaseNote.hpp"
#include "PrivacyUpdateDialog.hpp"
#include "ModelMall.hpp"
#include "HintNotification.hpp"
//#ifdef WIN32
//#include "BaseException.h"
//#endif
#ifdef __WXMSW__
#include <dbt.h>
#include <shlobj.h>
#ifdef __WINDOWS__
#ifdef _MSW_DARK_MODE
#include "dark_mode.hpp"
#include "wx/headerctrl.h"
#include "wx/msw/headerctrl.h"
#endif // _MSW_DARK_MODE
#endif // __WINDOWS__
#endif
#ifdef _WIN32
#include <boost/dll/runtime_symbol_info.hpp>
#endif
#ifdef WIN32
#include "BaseException.h"
#endif
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
#include <boost/beast/core/detail/base64.hpp>
#include <boost/nowide/fstream.hpp>
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
// Needed for forcing menu icons back under gtk2 and gtk3
#if defined(__WXGTK20__) || defined(__WXGTK3__)
#include <gtk/gtk.h>
#endif
using namespace std::literals;
namespace pt = boost::property_tree;
namespace Slic3r {
namespace GUI {
class MainFrame;
void start_ping_test()
{
return;
wxArrayString output;
wxExecute("ping www.amazon.com", output, wxEXEC_NODISABLE);
wxString output_i;
std::string output_temp;
for (int i = 0; i < output.size(); i++) {
output_i = output[i].To8BitData();
output_temp = output_i.ToStdString(wxConvUTF8);
BOOST_LOG_TRIVIAL(info) << "ping amazon:" << output_temp;
}
wxExecute("ping www.apple.com", output, wxEXEC_NODISABLE);
for (int i = 0; i < output.size(); i++) {
output_i = output[i].To8BitData();
output_temp = output_i.ToStdString(wxConvUTF8);
BOOST_LOG_TRIVIAL(info) << "ping www.apple.com:" << output_temp;
}
wxExecute("ping www.bambulab.com", output, wxEXEC_NODISABLE);
for (int i = 0; i < output.size(); i++) {
output_i = output[i].To8BitData();
output_temp = output_i.ToStdString(wxConvUTF8);
BOOST_LOG_TRIVIAL(info) << "ping bambulab:" << output_temp;
}
//Get GateWay IP
wxExecute("ping 192.168.0.1", output, wxEXEC_NODISABLE);
for (int i = 0; i < output.size(); i++) {
output_i = output[i].To8BitData();
output_temp = output_i.ToStdString(wxConvUTF8);
BOOST_LOG_TRIVIAL(info) << "ping 192.168.0.1:" << output_temp;
}
}
std::string VersionInfo::convert_full_version(std::string short_version)
{
std::string result = "";
std::vector<std::string> items;
boost::split(items, short_version, boost::is_any_of("."));
if (items.size() == VERSION_LEN) {
for (int i = 0; i < VERSION_LEN; i++) {
std::stringstream ss;
ss << std::setw(2) << std::setfill('0') << items[i];
result += ss.str();
if (i != VERSION_LEN - 1)
result += ".";
}
return result;
}
return result;
}
std::string VersionInfo::convert_short_version(std::string full_version)
{
full_version.erase(std::remove(full_version.begin(), full_version.end(), '0'), full_version.end());
return full_version;
}
static std::string convert_studio_language_to_api(std::string lang_code)
{
boost::replace_all(lang_code, "_", "-");
return lang_code;
/*if (lang_code == "zh_CN")
return "zh-hans";
else if (lang_code == "zh_TW")
return "zh-hant";
else
return "en";*/
}
#ifdef _WIN32
bool is_associate_files(std::wstring extend)
{
wchar_t app_path[MAX_PATH];
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
std::wstring prog_id = L" Orca.Slicer.1";
std::wstring reg_base = L"Software\\Classes";
std::wstring reg_extension = reg_base + L"\\." + extend;
wchar_t szValueCurrent[1000];
DWORD dwType;
DWORD dwSize = sizeof(szValueCurrent);
int iRC = ::RegGetValueW(HKEY_CURRENT_USER, reg_extension.c_str(), nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize);
bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND;
if (!bDidntExist && ::wcscmp(szValueCurrent, prog_id.c_str()) == 0)
return true;
return false;
}
#endif
class SplashScreen : public wxSplashScreen
{
public:
SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxPoint pos = wxDefaultPosition)
: wxSplashScreen(bitmap, splashStyle, milliseconds, static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, wxDefaultPosition, wxDefaultSize,
#ifdef __APPLE__
wxBORDER_NONE | wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP
#else
wxBORDER_NONE | wxFRAME_NO_TASKBAR
#endif // !__APPLE__
)
{
int init_dpi = get_dpi_for_window(this);
this->SetPosition(pos);
this->CenterOnScreen();
int new_dpi = get_dpi_for_window(this);
m_scale = (float)(new_dpi) / (float)(init_dpi);
m_main_bitmap = bitmap;
scale_bitmap(m_main_bitmap, m_scale);
// init constant texts and scale fonts
m_constant_text.init(Label::Body_16);
// ORCA scale all fonts with monitor scale
scale_font(m_constant_text.version_font, m_scale * 2);
scale_font(m_constant_text.based_on_font, m_scale * 1.5f);
scale_font(m_constant_text.credits_font, m_scale * 2);
// this font will be used for the action string
m_action_font = m_constant_text.credits_font;
// draw logo and constant info text
Decorate(m_main_bitmap);
wxGetApp().UpdateFrameDarkUI(this);
}
void SetText(const wxString& text)
{
set_bitmap(m_main_bitmap);
if (!text.empty()) {
wxBitmap bitmap(m_main_bitmap);
wxMemoryDC memDC;
memDC.SelectObject(bitmap);
memDC.SetFont(m_action_font);
memDC.SetTextForeground(StateColor::darkModeColorFor(wxColour(144, 144, 144)));
int width = bitmap.GetWidth();
int text_height = memDC.GetTextExtent(text).GetHeight();
int text_width = memDC.GetTextExtent(text).GetWidth();
wxRect text_rect(wxPoint(0, m_action_line_y_position), wxPoint(width, m_action_line_y_position + text_height));
memDC.DrawLabel(text, text_rect, wxALIGN_CENTER);
memDC.SelectObject(wxNullBitmap);
set_bitmap(bitmap);
#ifdef __WXOSX__
// without this code splash screen wouldn't be updated under OSX
wxYield();
#endif
}
}
void Decorate(wxBitmap& bmp)
{
if (!bmp.IsOk())
return;
bool is_dark = wxGetApp().app_config->get("dark_color_mode") == "1";
// use a memory DC to draw directly onto the bitmap
wxMemoryDC memDc(bmp);
int width = bmp.GetWidth();
int height = bmp.GetHeight();
// Logo
BitmapCache bmp_cache;
wxBitmap logo_bmp = *bmp_cache.load_svg(is_dark ? "splash_logo_dark" : "splash_logo", width, height); // use with full width & height
memDc.DrawBitmap(logo_bmp, 0, 0, true);
// Version
memDc.SetFont(m_constant_text.version_font);
memDc.SetTextForeground(StateColor::darkModeColorFor(wxColor(134, 134, 134)));
wxSize version_ext = memDc.GetTextExtent(m_constant_text.version);
wxRect version_rect(
wxPoint(0, int(height * 0.70)),
wxPoint(width, int(height * 0.70) + version_ext.GetHeight())
);
memDc.DrawLabel(m_constant_text.version, version_rect, wxALIGN_CENTER);
// Dynamic Text
m_action_line_y_position = int(height * 0.83);
// Based on Text
memDc.SetFont(m_constant_text.based_on_font);
auto bs_version = wxString::Format("Based on PrusaSlicer and BambuStudio").ToStdString();
wxSize based_on_ext = memDc.GetTextExtent(bs_version);
wxRect based_on_rect(
wxPoint(0, height - based_on_ext.GetHeight() * 2),
wxPoint(width, height - based_on_ext.GetHeight())
);
memDc.DrawLabel(bs_version, based_on_rect, wxALIGN_CENTER);
}
static wxBitmap MakeBitmap()
{
int width = FromDIP(480, nullptr);
int height = FromDIP(480, nullptr);
wxImage image(width, height);
wxBitmap new_bmp(image);
wxMemoryDC memDC;
memDC.SelectObject(new_bmp);
memDC.SetBrush(StateColor::darkModeColorFor(*wxWHITE));
memDC.DrawRectangle(-1, -1, width + 2, height + 2);
memDC.DrawBitmap(new_bmp, 0, 0, true);
return new_bmp;
}
void set_bitmap(wxBitmap& bmp)
{
m_window->SetBitmap(bmp);
m_window->Refresh();
m_window->Update();
}
void scale_bitmap(wxBitmap& bmp, float scale)
{
if (scale == 1.0)
return;
wxImage image = bmp.ConvertToImage();
if (!image.IsOk() || image.GetWidth() == 0 || image.GetHeight() == 0)
return;
int width = int(scale * image.GetWidth());
int height = int(scale * image.GetHeight());
image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR);
bmp = wxBitmap(std::move(image));
}
void scale_font(wxFont& font, float scale)
{
#ifdef __WXMSW__
// Workaround for the font scaling in respect to the current active display,
// not for the primary display, as it's implemented in Font.cpp
// See https://github.com/wxWidgets/wxWidgets/blob/master/src/msw/font.cpp
// void wxNativeFontInfo::SetFractionalPointSize(float pointSizeNew)
wxNativeFontInfo nfi= *font.GetNativeFontInfo();
float pointSizeNew = scale * font.GetPointSize();
nfi.lf.lfHeight = nfi.GetLogFontHeightAtPPI(pointSizeNew, get_dpi_for_window(this));
nfi.pointSize = pointSizeNew;
font = wxFont(nfi);
#else
font.Scale(scale);
#endif //__WXMSW__
}
private:
wxStaticText* m_staticText_slicer_name;
wxStaticText* m_staticText_slicer_version;
wxStaticBitmap* m_bitmap;
wxStaticText* m_staticText_loading;
wxBitmap m_main_bitmap;
wxFont m_action_font;
int m_action_line_y_position;
float m_scale {1.0};
struct ConstantText
{
wxString title;
wxString version;
wxString credits;
wxFont title_font;
wxFont version_font;
wxFont credits_font;
wxFont based_on_font;
void init(wxFont init_font)
{
// title
//title = wxGetApp().is_editor() ? SLIC3R_APP_FULL_NAME : GCODEVIEWER_APP_NAME;
// dynamically get the version to display
version = GUI_App::format_display_version();
// credits infornation
credits = "";
//title_font = Label::Head_16;
version_font = Label::Body_13;
based_on_font = Label::Body_8;
credits_font = Label::Body_8;
}
}
m_constant_text;
};
#ifdef __linux__
bool static check_old_linux_datadir(const wxString& app_name) {
// If we are on Linux and the datadir does not exist yet, look into the old
// location where the datadir was before version 2.3. If we find it there,
// tell the user that he might wanna migrate to the new location.
// (https://github.com/prusa3d/PrusaSlicer/issues/2911)
// To be precise, the datadir should exist, it is created when single instance
// lock happens. Instead of checking for existence, check the contents.
namespace fs = boost::filesystem;
std::string new_path = Slic3r::data_dir();
wxString dir;
if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() )
dir = wxFileName::GetHomeDir() + wxS("/.config");
std::string default_path = (dir + "/" + app_name).ToUTF8().data();
if (new_path != default_path) {
// This happens when the user specifies a custom --datadir.
// Do not show anything in that case.
return true;
}
fs::path data_dir = fs::path(new_path);
if (! fs::is_directory(data_dir))
return true; // This should not happen.
int file_count = std::distance(fs::directory_iterator(data_dir), fs::directory_iterator());
if (file_count <= 1) { // just cache dir with an instance lock
// BBS
} else {
// If the new directory exists, be silent. The user likely already saw the message.
}
return true;
}
#endif
struct FileWildcards {
std::string_view title;
std::vector<std::string_view> file_extensions;
};
static const FileWildcards file_wildcards_by_type[FT_SIZE] = {
/* FT_STEP */ { "STEP files"sv, { ".stp"sv, ".step"sv } },
/* FT_STL */ { "STL files"sv, { ".stl"sv } },
/* FT_OBJ */ { "OBJ files"sv, { ".obj"sv } },
/* FT_AMF */ { "AMF files"sv, { ".amf"sv, ".zip.amf"sv, ".xml"sv } },
/* FT_3MF */ { "3MF files"sv, { ".3mf"sv } },
/* FT_GCODE */ { "G-code files"sv, { ".gcode"sv, ".3mf"sv } },
#ifdef __APPLE__
/* FT_MODEL */
{"Supported files"sv, {".3mf"sv, ".stl"sv, ".oltp"sv, ".stp"sv, ".step"sv, ".svg"sv, ".amf"sv, ".obj"sv, ".usd"sv, ".usda"sv, ".usdc"sv, ".usdz"sv, ".abc"sv, ".ply"sv}},
#else
/* FT_MODEL */
{"Supported files"sv, {".3mf"sv, ".stl"sv, ".oltp"sv, ".stp"sv, ".step"sv, ".svg"sv, ".amf"sv, ".obj"sv}},
#endif
/* FT_ZIP */ { "ZIP files"sv, { ".zip"sv } },
/* FT_PROJECT */ { "Project files"sv, { ".3mf"sv} },
/* FT_GALLERY */ { "Known files"sv, { ".stl"sv, ".obj"sv } },
/* FT_INI */ { "INI files"sv, { ".ini"sv } },
/* FT_SVG */ { "SVG files"sv, { ".svg"sv } },
/* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } },
/* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv } },
};
// This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms.
// The function accepts a custom extension parameter. If the parameter is provided, the custom extension
// will be added as a fist to the list. This is important for a "file save" dialog on OSX, which strips
// an extension from the provided initial file name and substitutes it with the default extension (the first one in the template).
wxString file_wildcards(FileType file_type, const std::string &custom_extension)
{
const FileWildcards& data = file_wildcards_by_type[file_type];
std::string title;
std::string mask;
std::string custom_ext_lower;
if (! custom_extension.empty()) {
// Generate an extension into the title mask and into the list of extensions.
custom_ext_lower = boost::to_lower_copy(custom_extension);
const std::string custom_ext_upper = boost::to_upper_copy(custom_extension);
if (custom_ext_lower == custom_extension) {
// Add a lower case version.
title = std::string("*") + custom_ext_lower;
mask = title;
// Add an upper case version.
mask += ";*";
mask += custom_ext_upper;
} else if (custom_ext_upper == custom_extension) {
// Add an upper case version.
title = std::string("*") + custom_ext_upper;
mask = title;
// Add a lower case version.
mask += ";*";
mask += custom_ext_lower;
} else {
// Add the mixed case version only.
title = std::string("*") + custom_extension;
mask = title;
}
}
for (const std::string_view &ext : data.file_extensions)
// Only add an extension if it was not added first as the custom extension.
if (ext != custom_ext_lower) {
if (title.empty()) {
title = "*";
title += ext;
mask = title;
} else {
title += ", *";
title += ext;
mask += ";*";
mask += ext;
}
mask += ";*";
mask += boost::to_upper_copy(std::string(ext));
}
return GUI::format_wxstr("%s (%s)|%s", data.title, title, mask);
}
static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); }
#ifdef WIN32
#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
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;
});
}
#endif // !wxVERSION_EQUAL_OR_GREATER_THAN
static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 };
static void register_win32_device_notification_event()
{
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
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
/*#ifdef WIN32
//LPEXCEPTION_POINTERS exception_pointers = nullptr;
__try {
throw;
}
__except (CBaseException::UnhandledExceptionFilter2(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER) {
//__except (exception_pointers = GetExceptionInformation(), EXCEPTION_EXECUTE_HANDLER) {
// if (exception_pointers) {
// CBaseException::UnhandledExceptionFilter(exception_pointers);
// }
// else
throw;
}
#else*/
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.
BOOST_LOG_TRIVIAL(error) << boost::format("std::bad_alloc exception: %1%") % ex.what();
flush_logs();
wxString errmsg = wxString::Format(_L("OrcaSlicer will terminate because of running out of memory."
"It may be a bug. It will be appreciated if you report the issue to our team."));
wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Fatal error"), wxOK | wxICON_ERROR);
std::terminate();
//throw;
} catch (const boost::io::bad_format_string& ex) {
BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what();
flush_logs();
wxString errmsg = _L("OrcaSlicer will terminate because of a localization error. "
"It will be appreciated if you report the specific scenario this issue happened.");
wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Critical error"), wxOK | wxICON_ERROR);
std::terminate();
//throw;
} catch (const std::exception& ex) {
wxLogError(format_wxstr(_L("OrcaSlicer got an unhandled exception: %1%"), ex.what()));
BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what();
flush_logs();
throw;
}
//#endif
}
void GUI_App::toggle_show_gcode_window()
{
m_show_gcode_window = !m_show_gcode_window;
app_config->set_bool("show_gcode_window", m_show_gcode_window);
}
std::vector<std::string> GUI_App::split_str(std::string src, std::string separator)
{
std::string::size_type pos;
std::vector<std::string> result;
src += separator;
int size = src.size();
for (int i = 0; i < size; i++)
{
pos = src.find(separator, i);
if (pos < size)
{
std::string s = src.substr(i, pos - i);
result.push_back(s);
i = pos + separator.size() - 1;
}
}
return result;
}
void GUI_App::post_init()
{
assert(initialized());
if (! this->initialized())
throw Slic3r::RuntimeError("Calling post_init() while not yet initialized");
if (app_config->get("sync_user_preset") == "true") {
// BBS loading user preset
// Always async, not such startup step
// BOOST_LOG_TRIVIAL(info) << "Loading user presets...";
// scrn->SetText(_L("Loading user presets..."));
if (m_agent) { start_sync_user_preset(); }
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " sync_user_preset: true";
} else {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " sync_user_preset: false";
}
m_open_method = "double_click";
bool switch_to_3d = false;
if (!this->init_params->input_files.empty()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", init with input files, size %1%, input_gcode %2%")
%this->init_params->input_files.size() %this->init_params->input_gcode;
const auto first_url = this->init_params->input_files.front();
if (this->init_params->input_files.size() == 1 && is_supported_open_protocol(first_url)) {
switch_to_3d = true;
start_download(first_url);
m_open_method = "url";
} else {
switch_to_3d = true;
if (this->init_params->input_gcode) {
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
plater_->select_view_3D("3D");
this->plater()->load_gcode(from_u8(this->init_params->input_files.front()));
m_open_method = "gcode";
} else {
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
plater_->select_view_3D("3D");
wxArrayString input_files;
for (auto& file : this->init_params->input_files) {
input_files.push_back(wxString::FromUTF8(file));
}
this->plater()->set_project_filename(_L("Untitled"));
this->plater()->load_files(input_files);
try {
if (!input_files.empty()) {
std::string file_path = input_files.front().ToStdString();
std::filesystem::path path(file_path);
m_open_method = "file_" + path.extension().string();
}
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", file path exception!";
m_open_method = "file";
}
}
}
}
//#if BBL_HAS_FIRST_PAGE
bool slow_bootup = false;
if (app_config->get("slow_bootup") == "true") {
slow_bootup = true;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", slow bootup, won't render gl here.";
}
if (!switch_to_3d) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", begin load_gl_resources";
mainframe->Freeze();
plater_->canvas3D()->enable_render(false);
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
plater_->select_view_3D("3D");
//BBS init the opengl resource here
//#ifdef __linux__
if (plater_->canvas3D()->get_wxglcanvas()->IsShownOnScreen()&&plater_->canvas3D()->make_current_for_postinit()) {
//#endif
Size canvas_size = plater_->canvas3D()->get_canvas_size();
wxGetApp().imgui()->set_display_size(static_cast<float>(canvas_size.get_width()), static_cast<float>(canvas_size.get_height()));
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", start to init opengl";
wxGetApp().init_opengl();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished init opengl";
plater_->canvas3D()->init();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished init canvas3D";
wxGetApp().imgui()->new_frame();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished init imgui frame";
plater_->canvas3D()->enable_render(true);
if (!slow_bootup) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", start to render a first frame for test";
plater_->canvas3D()->render(false);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished rendering a first frame for test";
}
//#ifdef __linux__
}
else {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "Found glcontext not ready, postpone the init";
}
//#endif
if (is_editor())
mainframe->select_tab(size_t(0));
if (app_config->get("default_page") == "1")
mainframe->select_tab(size_t(1));
mainframe->Thaw();
plater_->trigger_restore_project(1);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", end load_gl_resources";
}
//#endif
//BBS: remove GCodeViewer as seperate APP logic
/*if (this->init_params->start_as_gcodeviewer) {
if (! this->init_params->input_files.empty())
this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str()));
}
else
{
if (! this->init_params->preset_substitutions.empty())
show_substitutions_info(this->init_params->preset_substitutions);
#if 0
// Load the cummulative config over the currently active profiles.
//FIXME if multiple configs are loaded, only the last one will have an effect.
// We need to decide what to do about loading of separate presets (just print preset, just filament preset etc).
// As of now only the full configs are supported here.
if (!m_print_config.empty())
this->gui->mainframe->load_config(m_print_config);
#endif
if (! this->init_params->load_configs.empty())
// Load the last config to give it a name at the UI. The name of the preset may be later
// changed by loading an AMF or 3MF.
//FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config.
this->mainframe->load_config_file(this->init_params->load_configs.back());
// If loading a 3MF file, the config is loaded from the last one.
if (!this->init_params->input_files.empty()) {
const std::vector<size_t> res = this->plater()->load_files(this->init_params->input_files);
if (!res.empty() && this->init_params->input_files.size() == 1) {
// Update application titlebar when opening a project file
const std::string& filename = this->init_params->input_files.front();
//BBS: remove amf logic as project
if (boost::algorithm::iends_with(filename, ".3mf"))
this->plater()->set_project_filename(filename);
}
}
if (! this->init_params->extra_config.empty())
this->mainframe->load_config(this->init_params->extra_config);
}*/
// BBS: to be checked
#if 1
// show "Did you know" notification
if (app_config->get("show_hints") == "true" && !is_gcode_viewer()) {
plater_->get_notification_manager()->push_hint_notification(false);
}
#endif
if (app_config->get("stealth_mode") == "false")
hms_query = new HMSQuery();
m_show_gcode_window = app_config->get_bool("show_gcode_window");
if (m_networking_need_update) {
//updating networking
int ret = updating_bambu_networking();
if (!ret) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<<":networking plugin updated successfully";
//restart_networking();
}
else {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<<":networking plugin updated failed";
}
}
// 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.
if (this->preset_updater) { // G-Code Viewer does not initialize preset_updater.
CallAfter([this] {
bool cw_showed = this->config_wizard_startup();
std::string http_url = get_http_url(app_config->get_country_code());
std::string language = GUI::into_u8(current_language_code());
std::string network_ver = Slic3r::NetworkAgent::get_version();
bool sys_preset = app_config->get("sync_system_preset") == "true";
this->preset_updater->sync(http_url, language, network_ver, sys_preset ? preset_bundle : nullptr);
this->check_new_version_sf();
if (is_user_login() && app_config->get("stealth_mode") == "false") {
// this->check_privacy_version(0);
request_user_handle(0);
}
});
}
if (is_user_login())
request_user_handle(0);
if(!m_networking_need_update && m_agent) {
m_agent->set_on_ssdp_msg_fn(
[this](std::string json_str) {
if (m_is_closing) {
return;
}
GUI::wxGetApp().CallAfter([this, json_str] {
if (m_device_manager) {
m_device_manager->on_machine_alive(json_str);
}
});
}
);
m_agent->set_on_http_error_fn([this](unsigned int status, std::string body) {
this->handle_http_error(status, body);
});
m_agent->start_discovery(true, false);
}
//update the plugin tips
CallAfter([this] {
mainframe->refresh_plugin_tips();
});
// update hms info
CallAfter([this] {
if (hms_query)
hms_query->check_hms_info();
});
DeviceManager::load_filaments_blacklist_config();
// remove old log files over LOG_FILES_MAX_NUM
std::string log_addr = data_dir();
if (!log_addr.empty()) {
auto log_folder = boost::filesystem::path(log_addr) / "log";
if (boost::filesystem::exists(log_folder)) {
std::vector<std::pair<time_t, std::string>> files_vec;
for (auto& it : boost::filesystem::directory_iterator(log_folder)) {
auto temp_path = it.path();
try {
std::time_t lw_t = boost::filesystem::last_write_time(temp_path) ;
files_vec.push_back({ lw_t, temp_path.filename().string() });
} catch (const std::exception &ex) {
}
}
std::sort(files_vec.begin(), files_vec.end(), [](
std::pair<time_t, std::string> &a, std::pair<time_t, std::string> &b) {
return a.first > b.first;
});
while (files_vec.size() > LOG_FILES_MAX_NUM) {
auto full_path = log_folder / boost::filesystem::path(files_vec[files_vec.size() - 1].second);
BOOST_LOG_TRIVIAL(info) << "delete log file over " << LOG_FILES_MAX_NUM << ", filename: "<< files_vec[files_vec.size() - 1].second;
try {
boost::filesystem::remove(full_path);
}
catch (const std::exception& ex) {
BOOST_LOG_TRIVIAL(error) << "failed to delete log file: "<< files_vec[files_vec.size() - 1].second << ". Error: " << ex.what();
}
files_vec.pop_back();
}
}
}
BOOST_LOG_TRIVIAL(info) << "finished post_init";
//BBS: remove the single instance currently
#ifdef _WIN32
// Sets window property to mainframe so other instances can indentify it.
OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int);
#endif //WIN32
}
wxDEFINE_EVENT(EVT_ENTER_FORCE_UPGRADE, wxCommandEvent);
wxDEFINE_EVENT(EVT_SHOW_NO_NEW_VERSION, wxCommandEvent);
wxDEFINE_EVENT(EVT_SHOW_DIALOG, wxCommandEvent);
wxDEFINE_EVENT(EVT_CONNECT_LAN_MODE_PRINT, wxCommandEvent);
IMPLEMENT_APP(GUI_App)
//BBS: remove GCodeViewer as seperate APP logic
//GUI_App::GUI_App(EAppMode mode)
GUI_App::GUI_App()
: wxApp()
//, m_app_mode(mode)
, m_app_mode(EAppMode::Editor)
, m_em_unit(10)
, m_imgui(new ImGuiWrapper())
, m_removable_drive_manager(std::make_unique<RemovableDriveManager>())
, m_downloader(std::make_unique<Downloader>())
, m_other_instance_message_handler(std::make_unique<OtherInstanceMessageHandler>())
{
//app config initializes early becasuse it is used in instance checking in OrcaSlicer.cpp
this->init_app_config();
this->init_download_path();
#if wxUSE_WEBVIEW_EDGE
this->init_webview_runtime();
#endif
reset_to_active();
}
void GUI_App::shutdown()
{
BOOST_LOG_TRIVIAL(info) << "GUI_App::shutdown enter";
if (m_removable_drive_manager) {
removable_drive_manager()->shutdown();
}
// destroy login dialog
if (login_dlg != nullptr) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": destroy login dialog");
delete login_dlg;
login_dlg = nullptr;
}
if (m_is_recreating_gui) return;
m_is_closing = true;
BOOST_LOG_TRIVIAL(info) << "GUI_App::shutdown exit";
}
std::string GUI_App::get_http_url(std::string country_code, std::string path)
{
std::string url;
if (country_code == "US") {
url = "https://api.bambulab.com/";
}
else if (country_code == "CN") {
url = "https://api.bambulab.cn/";
}
else if (country_code == "ENV_CN_DEV") {
url = "https://api-dev.bambu-lab.com/";
}
else if (country_code == "ENV_CN_QA") {
url = "https://api-qa.bambu-lab.com/";
}
else if (country_code == "ENV_CN_PRE") {
url = "https://api-pre.bambu-lab.com/";
}
else {
url = "https://api.bambulab.com/";
}
url += path.empty() ? "v1/iot-service/api/slicer/resource" : path;
return url;
}
std::string GUI_App::get_model_http_url(std::string country_code)
{
std::string url;
if (country_code == "US") {
url = "https://makerworld.com/";
}
else if (country_code == "CN") {
url = "https://makerworld.com/";
}
else if (country_code == "ENV_CN_DEV") {
url = "https://makerhub-dev.bambu-lab.com/";
}
else if (country_code == "ENV_CN_QA") {
url = "https://makerhub-qa.bambu-lab.com/";
}
else if (country_code == "ENV_CN_PRE") {
url = "https://makerhub-pre.bambu-lab.com/";
}
else {
url = "https://makerworld.com/";
}
return url;
}
std::string GUI_App::get_plugin_url(std::string name, std::string country_code)
{
std::string url = get_http_url(country_code);
std::string curr_version = SLIC3R_VERSION;
std::string using_version = curr_version.substr(0, 9) + "00";
if (name == "cameratools")
using_version = curr_version.substr(0, 6) + "00.00";
url += (boost::format("?slicer/%1%/cloud=%2%") % name % using_version).str();
//url += (boost::format("?slicer/plugins/cloud=%1%") % "01.01.00.00").str();
return url;
}
static std::string decode(std::string const& extra, std::string const& path = {}) {
char const* p = extra.data();
char const* e = p + extra.length();
while (p + 4 < e) {
boost::uint16_t len = ((boost::uint16_t)p[2]) | ((boost::uint16_t)p[3] << 8);
if (p[0] == '\x75' && p[1] == '\x70' && len >= 5 && p + 4 + len < e && p[4] == '\x01') {
return std::string(p + 9, p + 4 + len);
}
else {
p += 4 + len;
}
}
return Slic3r::decode_path(path.c_str());
}
int GUI_App::download_plugin(std::string name, std::string package_name, InstallProgressFn pro_fn, WasCancelledFn cancel_fn)
{
int result = 0;
json j;
std::string err_msg;
// get country_code
AppConfig* app_config = wxGetApp().app_config;
if (!app_config) {
j["result"] = "failed";
j["error_msg"] = "app_config is nullptr";
return -1;
}
BOOST_LOG_TRIVIAL(info) << "[download_plugin]: enter";
m_networking_cancel_update = false;
// get temp path
fs::path target_file_path = (fs::temp_directory_path() / package_name);
fs::path tmp_path = target_file_path;
tmp_path += format(".%1%%2%", get_current_pid(), ".tmp");
// get_url
std::string url = get_plugin_url(name, app_config->get_country_code());
std::string download_url;
Slic3r::Http http_url = Slic3r::Http::get(url);
BOOST_LOG_TRIVIAL(info) << "[download_plugin]: check the plugin from " << url;
http_url.timeout_connect(TIMEOUT_CONNECT)
.timeout_max(TIMEOUT_RESPONSE)
.on_complete(
[&download_url](std::string body, unsigned status) {
try {
json j = json::parse(body);
std::string message = j["message"].get<std::string>();
if (message == "success") {
json resource = j.at("resources");
if (resource.is_array()) {
for (auto iter = resource.begin(); iter != resource.end(); iter++) {
Semver version;
std::string url;
std::string type;
std::string vendor;
std::string description;
for (auto sub_iter = iter.value().begin(); sub_iter != iter.value().end(); sub_iter++) {
if (boost::iequals(sub_iter.key(), "type")) {
type = sub_iter.value();
BOOST_LOG_TRIVIAL(info) << "[download_plugin]: get version of settings's type, " << sub_iter.value();
}
else if (boost::iequals(sub_iter.key(), "version")) {
version = *(Semver::parse(sub_iter.value()));
}
else if (boost::iequals(sub_iter.key(), "description")) {
description = sub_iter.value();
}
else if (boost::iequals(sub_iter.key(), "url")) {
url = sub_iter.value();
}
}
BOOST_LOG_TRIVIAL(info) << "[download_plugin 1]: get type " << type << ", version " << version.to_string() << ", url " << url;
download_url = url;
}
}
}
else {
BOOST_LOG_TRIVIAL(info) << "[download_plugin 1]: get version of plugin failed, body=" << body;
}
}
catch (...) {
BOOST_LOG_TRIVIAL(error) << "[download_plugin 1]: catch unknown exception";
;
}
}).on_error(
[&result, &err_msg](std::string body, std::string error, unsigned int status) {
BOOST_LOG_TRIVIAL(error) << "[download_plugin 1] on_error: " << error<<", body = " << body;
err_msg += "[download_plugin 1] on_error: " + error + ", body = " + body;
result = -1;
}).perform_sync();
bool cancel = false;
if (result < 0) {
j["result"] = "failed";
j["error_msg"] = err_msg;
if (pro_fn) pro_fn(InstallStatusDownloadFailed, 0, cancel);
return result;
}
if (download_url.empty()) {
BOOST_LOG_TRIVIAL(info) << "[download_plugin 1]: no available plugin found for this app version: " << SLIC3R_VERSION;
if (pro_fn) pro_fn(InstallStatusDownloadFailed, 0, cancel);
j["result"] = "failed";
j["error_msg"] = "[download_plugin 1]: no available plugin found for this app version: " + std::string(SLIC3R_VERSION);
return -1;
}
else if (pro_fn) {
pro_fn(InstallStatusNormal, 5, cancel);
}
if (m_networking_cancel_update || cancel) {
BOOST_LOG_TRIVIAL(info) << boost::format("[download_plugin 1]: %1%, cancelled by user") % __LINE__;
j["result"] = "failed";
j["error_msg"] = (boost::format("[download_plugin 1]: %1%, cancelled by user") % __LINE__).str();
return -1;
}
BOOST_LOG_TRIVIAL(info) << "[download_plugin] get_url = " << download_url;
// download
Slic3r::Http http = Slic3r::Http::get(download_url);
int reported_percent = 0;
http.on_progress(
[this, &pro_fn, cancel_fn, &result, &reported_percent, &err_msg](Slic3r::Http::Progress progress, bool& cancel) {
int percent = 0;
if (progress.dltotal != 0)
percent = progress.dlnow * 50 / progress.dltotal;
bool was_cancel = false;
if (pro_fn && ((percent - reported_percent) >= 10)) {
pro_fn(InstallStatusNormal, percent, was_cancel);
reported_percent = percent;
BOOST_LOG_TRIVIAL(info) << "[download_plugin 2] progress: " << reported_percent;
}
cancel = m_networking_cancel_update || was_cancel;
if (cancel_fn)
if (cancel_fn())
cancel = true;
if (cancel) {
err_msg += "[download_plugin] cancel";
result = -1;
}
})
.on_complete([&pro_fn, tmp_path, target_file_path](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(info) << "[download_plugin 2] completed";
bool cancel = false;
int percent = 0;
fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc);
file.write(body.c_str(), body.size());
file.close();
fs::rename(tmp_path, target_file_path);
if (pro_fn) pro_fn(InstallStatusDownloadCompleted, 80, cancel);
})
.on_error([&pro_fn, &result, &err_msg](std::string body, std::string error, unsigned int status) {
bool cancel = false;
if (pro_fn) pro_fn(InstallStatusDownloadFailed, 0, cancel);
BOOST_LOG_TRIVIAL(error) << "[download_plugin 2] on_error: " << error<<", body = " << body;
err_msg += "[download_plugin 2] on_error: " + error + ", body = " + body;
result = -1;
});
http.perform_sync();
j["result"] = result < 0 ? "failed" : "success";
j["error_msg"] = err_msg;
return result;
}
int GUI_App::install_plugin(std::string name, std::string package_name, InstallProgressFn pro_fn, WasCancelledFn cancel_fn)
{
bool cancel = false;
std::string target_file_path = (fs::temp_directory_path() / package_name).string();
BOOST_LOG_TRIVIAL(info) << "[install_plugin] enter";
// get plugin folder
std::string data_dir_str = data_dir();
boost::filesystem::path data_dir_path(data_dir_str);
auto plugin_folder = data_dir_path / name;
//auto plugin_folder = boost::filesystem::path(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data()) / "plugins";
auto backup_folder = plugin_folder/"backup";
if (!boost::filesystem::exists(plugin_folder)) {
BOOST_LOG_TRIVIAL(info) << "[install_plugin] will create directory "<<plugin_folder.string();
boost::filesystem::create_directory(plugin_folder);
}
if (!boost::filesystem::exists(backup_folder)) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", will create directory %1%")%backup_folder.string();
boost::filesystem::create_directory(backup_folder);
}
if (m_networking_cancel_update) {
BOOST_LOG_TRIVIAL(info) << boost::format("[install_plugin]: %1%, cancelled by user")%__LINE__;
return -1;
}
if (pro_fn) {
pro_fn(InstallStatusNormal, 50, cancel);
}
// unzip
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
if (!open_zip_reader(&archive, target_file_path)) {
BOOST_LOG_TRIVIAL(error) << boost::format("[install_plugin]: %1%, open zip file failed")%__LINE__;
if (pro_fn) pro_fn(InstallStatusDownloadFailed, 0, cancel);
return InstallStatusUnzipFailed;
}
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
mz_zip_archive_file_stat stat;
BOOST_LOG_TRIVIAL(error) << boost::format("[install_plugin]: %1%, got %2% files")%__LINE__ %num_entries;
for (mz_uint i = 0; i < num_entries; i++) {
if (m_networking_cancel_update || cancel) {
BOOST_LOG_TRIVIAL(info) << boost::format("[install_plugin]: %1%, cancelled by user")%__LINE__;
return -1;
}
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
if (stat.m_uncomp_size > 0) {
std::string dest_file;
if (stat.m_is_utf8) {
dest_file = stat.m_filename;
}
else {
std::string extra(1024, 0);
size_t n = mz_zip_reader_get_extra(&archive, stat.m_file_index, extra.data(), extra.size());
dest_file = decode(extra.substr(0, n), stat.m_filename);
}
auto dest_file_path = boost::filesystem::path(dest_file);
dest_file = dest_file_path.filename().string();
auto dest_path = boost::filesystem::path(plugin_folder.string() + "/" + dest_file);
std::string dest_zip_file = encode_path(dest_path.string().c_str());
try {
if (fs::exists(dest_path))
fs::remove(dest_path);
mz_bool res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_zip_file.c_str(), 0);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", extract %1% from plugin zip %2%\n") % dest_file % stat.m_filename;
if (res == 0) {
#ifdef WIN32
std::wstring new_dest_zip_file = boost::locale::conv::utf_to_utf<wchar_t>(dest_path.generic_string());
res = mz_zip_reader_extract_to_file_w(&archive, stat.m_file_index, new_dest_zip_file.c_str(), 0);
#endif
if (res == 0) {
mz_zip_error zip_error = mz_zip_get_last_error(&archive);
BOOST_LOG_TRIVIAL(error) << "[install_plugin]Archive read error:" << mz_zip_get_error_string(zip_error) << std::endl;
close_zip_reader(&archive);
if (pro_fn) { pro_fn(InstallStatusUnzipFailed, 0, cancel); }
return InstallStatusUnzipFailed;
}
}
else {
if (pro_fn) {
pro_fn(InstallStatusNormal, 50 + i/num_entries, cancel);
}
try {
auto backup_path = boost::filesystem::path(backup_folder.string() + "/" + dest_file);
if (fs::exists(backup_path))
fs::remove(backup_path);
std::string error_message;
CopyFileResult cfr = copy_file(dest_path.string(), backup_path.string(), error_message, false);
if (cfr != CopyFileResult::SUCCESS) {
BOOST_LOG_TRIVIAL(error) << "Copying to backup failed(" << cfr << "): " << error_message;
}
}
catch (const std::exception& e)
{
BOOST_LOG_TRIVIAL(error) << "Copying to backup failed: " << e.what();
//continue
}
}
}
catch (const std::exception& e)
{
// ensure the zip archive is closed and rethrow the exception
close_zip_reader(&archive);
BOOST_LOG_TRIVIAL(error) << "[install_plugin]Archive read exception:"<<e.what();
if (pro_fn) {
pro_fn(InstallStatusUnzipFailed, 0, cancel);
}
return InstallStatusUnzipFailed;
}
}
}
else {
BOOST_LOG_TRIVIAL(error) << boost::format("[install_plugin]: %1%, mz_zip_reader_file_stat for file %2% failed")%__LINE__%i;
}
}
close_zip_reader(&archive);
if (pro_fn)
pro_fn(InstallStatusInstallCompleted, 100, cancel);
if (name == "plugins")
app_config->set_bool("installed_networking", true);
BOOST_LOG_TRIVIAL(info) << "[install_plugin] success";
return 0;
}
void GUI_App::restart_networking()
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(" enter, mainframe %1%")%mainframe;
on_init_network(true);
if(m_agent) {
init_networking_callbacks();
m_agent->set_on_ssdp_msg_fn(
[this](std::string json_str) {
if (m_is_closing) {
return;
}
GUI::wxGetApp().CallAfter([this, json_str] {
if (m_device_manager) {
m_device_manager->on_machine_alive(json_str);
}
});
}
);
m_agent->set_on_http_error_fn([this](unsigned int status, std::string body) {
this->handle_http_error(status, body);
});
m_agent->start_discovery(true, false);
if (mainframe)
mainframe->refresh_plugin_tips();
if (plater_)
plater_->get_notification_manager()->bbl_close_plugin_install_notification();
if (m_agent->is_user_login()) {
remove_user_presets();
enable_user_preset_folder(true);
preset_bundle->load_user_presets(m_agent->get_user_id(), ForwardCompatibilitySubstitutionRule::Enable);
mainframe->update_side_preset_ui();
}
if (app_config->get("sync_user_preset") == "true") {
start_sync_user_preset();
}
// if (mainframe && this->app_config->get("staff_pick_switch") == "true") {
// if (mainframe->m_webview) { mainframe->m_webview->SendDesignStaffpick(has_model_mall()); }
// }
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(" exit, m_agent=%1%")%m_agent;
}
void GUI_App::remove_old_networking_plugins()
{
std::string data_dir_str = data_dir();
boost::filesystem::path data_dir_path(data_dir_str);
auto plugin_folder = data_dir_path / "plugins";
//auto plugin_folder = boost::filesystem::path(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data()) / "plugins";
if (boost::filesystem::exists(plugin_folder)) {
BOOST_LOG_TRIVIAL(info) << "[remove_old_networking_plugins] remove the directory "<<plugin_folder.string();
try {
fs::remove_all(plugin_folder);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "Failed removing the plugins directory " << plugin_folder.string();
}
}
}
int GUI_App::updating_bambu_networking()
{
DownloadProgressDialog dlg(_L("Downloading Bambu Network Plug-in"));
dlg.ShowModal();
return 0;
}
bool GUI_App::check_networking_version()
{
std::string network_ver = Slic3r::NetworkAgent::get_version();
if (!network_ver.empty()) {
BOOST_LOG_TRIVIAL(info) << "get_network_agent_version=" << network_ver;
}
std::string studio_ver = SLIC3R_VERSION;
if (network_ver.length() >= 8) {
if (network_ver.substr(0,8) == studio_ver.substr(0,8)) {
m_networking_compatible = true;
return true;
}
}
m_networking_compatible = false;
return false;
}
bool GUI_App::is_compatibility_version()
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": m_networking_compatible=%1%")%m_networking_compatible;
return m_networking_compatible;
}
void GUI_App::cancel_networking_install()
{
m_networking_cancel_update = true;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": plugin install cancelled!");
}
void GUI_App::init_networking_callbacks()
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": enter, m_agent=%1%")%m_agent;
if (m_agent) {
//set callbacks
//m_agent->set_on_user_login_fn([this](int online_login, bool login) {
// GUI::wxGetApp().request_user_handle(online_login);
// });
m_agent->set_on_server_connected_fn([this](int return_code, int reason_code) {
if (m_is_closing) {
return;
}
if (return_code == 5) {
GUI::wxGetApp().CallAfter([this] {
this->request_user_logout();
MessageDialog msg_dlg(nullptr, _L("Login information expired. Please login again."), "", wxAPPLY | wxOK);
if (msg_dlg.ShowModal() == wxOK) {
return;
}
});
return;
}
GUI::wxGetApp().CallAfter([this] {
if (m_is_closing)
return;
BOOST_LOG_TRIVIAL(trace) << "static: server connected";
m_agent->set_user_selected_machine(m_agent->get_user_selected_machine());
if (this->is_enable_multi_machine()) {
auto evt = new wxCommandEvent(EVT_UPDATE_MACHINE_LIST);
wxQueueEvent(this, evt);
}
m_agent->set_user_selected_machine(m_agent->get_user_selected_machine());
//subscribe device
if (m_agent->is_user_login()) {
m_agent->start_device_subscribe();
/* resubscribe the cache dev list */
if (this->is_enable_multi_machine()) {
DeviceManager* dev = this->getDeviceManager();
if (dev && !dev->subscribe_list_cache.empty()) {
dev->subscribe_device_list(dev->subscribe_list_cache);
}
}
}
});
});
m_agent->set_on_printer_connected_fn([this](std::string dev_id) {
if (m_is_closing) {
return;
}
GUI::wxGetApp().CallAfter([this, dev_id] {
if (m_is_closing)
return;
bool tunnel = boost::algorithm::starts_with(dev_id, "tunnel/");
/* request_pushing */
MachineObject* obj = m_device_manager->get_my_machine(tunnel ? dev_id.substr(7) : dev_id);
if (obj) {
obj->is_tunnel_mqtt = tunnel;
obj->command_request_push_all(true);
obj->command_get_version();
obj->erase_user_access_code();
obj->command_get_access_code();
if (!is_enable_multi_machine()) {
GUI::wxGetApp().sidebar().load_ams_list(obj->dev_id, obj);
}
}
});
});
m_agent->set_get_country_code_fn([this]() {
if (app_config)
return app_config->get_country_code();
return std::string();
}
);
m_agent->set_on_subscribe_failure_fn([this](std::string dev_id) {
CallAfter([this, dev_id] {
on_start_subscribe_again(dev_id);
});
});
m_agent->set_on_local_connect_fn(
[this](int state, std::string dev_id, std::string msg) {
if (m_is_closing) {
return;
}
CallAfter([this, state, dev_id, msg] {
if (m_is_closing) {
return;
}
/* request_pushing */
MachineObject* obj = m_device_manager->get_my_machine(dev_id);
wxCommandEvent event(EVT_CONNECT_LAN_MODE_PRINT);
if (obj) {
if (obj->is_lan_mode_printer()) {
if (state == ConnectStatus::ConnectStatusOk) {
obj->command_request_push_all(true);
obj->command_get_version();
event.SetInt(0);
event.SetString(obj->dev_id);
GUI::wxGetApp().sidebar().load_ams_list(obj->dev_id, obj);
} else if (state == ConnectStatus::ConnectStatusFailed) {
obj->set_access_code("");
obj->erase_user_access_code();
m_device_manager->set_selected_machine("", true);
wxString text;
if (msg == "5") {
text = wxString::Format(_L("Incorrect password"));
wxGetApp().show_dialog(text);
} else {
text = wxString::Format(_L("Connect %s failed! [SN:%s, code=%s]"), from_u8(obj->dev_name), obj->dev_id, msg);
wxGetApp().show_dialog(text);
}
event.SetInt(-1);
} else if (state == ConnectStatus::ConnectStatusLost) {
obj->set_access_code("");
obj->erase_user_access_code();
m_device_manager->localMachineList.erase(obj->dev_id);
m_device_manager->set_selected_machine("", true);
event.SetInt(-1);
BOOST_LOG_TRIVIAL(info) << "set_on_local_connect_fn: state = lost";
} else {
event.SetInt(-1);
BOOST_LOG_TRIVIAL(info) << "set_on_local_connect_fn: state = " << state;
}
obj->set_lan_mode_connection_state(false);
}
else {
if (state == ConnectStatus::ConnectStatusOk) {
event.SetInt(1);
event.SetString(obj->dev_id);
}
else if(msg == "5") {
event.SetInt(5);
event.SetString(obj->dev_id);
}
else {
event.SetInt(-2);
event.SetString(obj->dev_id);
}
}
}
if (wxGetApp().plater()->get_select_machine_dialog()) {
wxPostEvent(wxGetApp().plater()->get_select_machine_dialog(), event);
}
});
}
);
auto message_arrive_fn = [this](std::string dev_id, std::string msg) {
if (m_is_closing) {
return;
}
CallAfter([this, dev_id, msg] {
if (m_is_closing)
return;
MachineObject* obj = this->m_device_manager->get_user_machine(dev_id);
if (obj) {
obj->is_ams_need_update = false;
auto sel = this->m_device_manager->get_selected_machine();
if (sel && sel->dev_id == dev_id) {
obj->parse_json(msg);
}
else {
obj->parse_json(msg, true);
}
if (!this->is_enable_multi_machine()) {
if ((sel == obj || sel == nullptr) && obj->is_ams_need_update) {
GUI::wxGetApp().sidebar().load_ams_list(obj->dev_id, obj);
}
}
}
});
};
m_agent->set_on_message_fn(message_arrive_fn);
auto user_message_arrive_fn = [this](std::string user_id, std::string msg) {
if (m_is_closing) {
return;
}
CallAfter([this, user_id, msg] {
if (m_is_closing)
return;
//check user
if (user_id == m_agent->get_user_id()) {
this->m_user_manager->parse_json(msg);
}
});
};
m_agent->set_on_user_message_fn(user_message_arrive_fn);
auto lan_message_arrive_fn = [this](std::string dev_id, std::string msg) {
if (m_is_closing) {
return;
}
CallAfter([this, dev_id, msg] {
if (m_is_closing)
return;
MachineObject* obj = m_device_manager->get_my_machine(dev_id);
if (!obj || !obj->is_lan_mode_printer()) {
obj = m_device_manager->get_local_machine(dev_id);
}
if (obj) {
obj->parse_json(msg, DeviceManager::key_field_only);
if (this->m_device_manager->get_selected_machine() == obj && obj->is_ams_need_update) {
GUI::wxGetApp().sidebar().load_ams_list(obj->dev_id, obj);
}
}
obj = m_device_manager->get_local_machine(dev_id);
if (obj) {
obj->parse_json(msg, DeviceManager::key_field_only);
}
});
};
m_agent->set_on_local_message_fn(lan_message_arrive_fn);
m_agent->set_queue_on_main_fn([this](std::function<void()> callback) {
CallAfter(callback);
});
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": exit, m_agent=%1%")%m_agent;
}
GUI_App::~GUI_App()
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": enter");
if (app_config != nullptr) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": destroy app_config");
delete app_config;
}
if (preset_bundle != nullptr) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": destroy preset_bundle");
delete preset_bundle;
}
if (preset_updater != nullptr) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": destroy preset updater");
delete preset_updater;
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": exit");
}
// If formatted for github, plaintext with OpenGL extensions enclosed into <details>.
// Otherwise HTML formatted for the system info dialog.
std::string GUI_App::get_gl_info(bool for_github)
{
return OpenGLManager::get_gl_info().to_string(for_github);
}
wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas)
{
return m_opengl_mgr.init_glcontext(canvas);
}
bool GUI_App::init_opengl()
{
#ifdef __linux__
bool status = m_opengl_mgr.init_gl();
m_opengl_initialized = true;
return status;
#else
return m_opengl_mgr.init_gl();
#endif
}
// gets path to PrusaSlicer.ini, returns semver from first line comment
static boost::optional<Semver> parse_semver_from_ini(std::string path)
{
std::ifstream stream(path);
std::stringstream buffer;
buffer << stream.rdbuf();
std::string body = buffer.str();
size_t start = body.find("OrcaSlicer ");
if (start == std::string::npos) {
start = body.find("OrcaSlicer ");
if (start == std::string::npos)
return boost::none;
}
body = body.substr(start + 12);
size_t end = body.find_first_of(" \n");
if (end < body.size())
body.resize(end);
return Semver::parse(body);
}
void GUI_App::init_download_path()
{
std::string down_path = app_config->get("download_path");
if (down_path.empty()) {
std::string user_down_path = wxStandardPaths::Get().GetUserDir(wxStandardPaths::Dir_Downloads).ToUTF8().data();
app_config->set("download_path", user_down_path);
}
else {
fs::path dp(down_path);
if (!fs::exists(dp)) {
std::string user_down_path = wxStandardPaths::Get().GetUserDir(wxStandardPaths::Dir_Downloads).ToUTF8().data();
app_config->set("download_path", user_down_path);
}
}
}
#if wxUSE_WEBVIEW_EDGE
void GUI_App::init_webview_runtime()
{
// Check WebView Runtime
if (!WebView::CheckWebViewRuntime()) {
int nRet = wxMessageBox(_L("Orca Slicer requires the Microsoft WebView2 Runtime to operate certain features.\nClick Yes to install it now."),
_L("WebView2 Runtime"), wxYES_NO);
if (nRet == wxYES) {
WebView::DownloadAndInstallWebViewRuntime();
}
}
}
#endif
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 "-alpha");
// 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()) {
boost::filesystem::path data_dir_path;
#ifndef __linux__
std::string data_dir = wxStandardPaths::Get().GetUserDataDir().ToUTF8().data();
//BBS create folder if not exists
data_dir_path = boost::filesystem::path(data_dir);
set_data_dir(data_dir);
#else
// Since version 2.3, config dir on Linux is in ${XDG_CONFIG_HOME}.
// https://github.com/prusa3d/PrusaSlicer/issues/2911
wxString dir;
if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() )
dir = wxFileName::GetHomeDir() + wxS("/.config");
set_data_dir((dir + "/" + GetAppName()).ToUTF8().data());
data_dir_path = boost::filesystem::path(data_dir());
#endif
if (!boost::filesystem::exists(data_dir_path)){
boost::filesystem::create_directory(data_dir_path);
}
// Change current dirtory of application
chdir(encode_path((Slic3r::data_dir() + "/log").c_str()).c_str());
} else {
m_datadir_redefined = true;
}
// start log here
std::time_t t = std::time(0);
std::tm * now_time = std::localtime(&t);
std::stringstream buf;
buf << std::put_time(now_time, "debug_%a_%b_%d_%H_%M_%S_");
buf << get_current_pid() << ".log";
std::string log_filename = buf.str();
#if !BBL_RELEASE_TO_PUBLIC
set_log_path_and_level(log_filename, 5);
#else
set_log_path_and_level(log_filename, 3);
#endif
//BBS: remove GCodeViewer as seperate APP logic
if (!app_config)
app_config = new AppConfig();
//app_config = new AppConfig(is_editor() ? AppConfig::EAppMode::Editor : AppConfig::EAppMode::GCodeViewer);
m_config_corrupted = false;
// load settings
m_app_conf_exists = app_config->exists();
if (m_app_conf_exists) {
std::string error = app_config->load();
if (!error.empty()) {
// Orca: if the config file is corrupted, we will show a error dialog and create a default config file.
m_config_corrupted = true;
}
// Save orig_version here, so its empty if no app_config existed before this run.
m_last_config_version = app_config->orig_version();//parse_semver_from_ini(app_config->config_path());
}
else {
#ifdef _WIN32
// update associate files from registry information
if (is_associate_files(L"3mf")) {
app_config->set("associate_3mf", "true");
}
if (is_associate_files(L"stl")) {
app_config->set("associate_stl", "true");
}
if (is_associate_files(L"step") && is_associate_files(L"stp")) {
app_config->set("associate_step", "true");
}
#endif // _WIN32
}
set_logging_level(Slic3r::level_string_to_boost(app_config->get("log_severity_level")));
}
// returns true if found newer version and user agreed to use it
bool GUI_App::check_older_app_config(Semver current_version, bool backup)
{
//BBS: current no need these logic
return false;
}
void GUI_App::copy_older_config()
{
preset_bundle->copy_files(m_older_data_dir_path);
}
std::map<std::string, std::string> GUI_App::get_extra_header()
{
std::map<std::string, std::string> extra_headers;
extra_headers.insert(std::make_pair("X-BBL-Client-Type", "slicer"));
extra_headers.insert(std::make_pair("X-BBL-Client-Name", SLIC3R_APP_NAME));
extra_headers.insert(std::make_pair("X-BBL-Client-Version", VersionInfo::convert_full_version(SLIC3R_VERSION)));
#if defined(__WINDOWS__)
extra_headers.insert(std::make_pair("X-BBL-OS-Type", "windows"));
#elif defined(__APPLE__)
extra_headers.insert(std::make_pair("X-BBL-OS-Type", "macos"));
#elif defined(__LINUX__)
extra_headers.insert(std::make_pair("X-BBL-OS-Type", "linux"));
#endif
int major = 0, minor = 0, micro = 0;
wxGetOsVersion(&major, &minor, &micro);
std::string os_version = (boost::format("%1%.%2%.%3%") % major % minor % micro).str();
extra_headers.insert(std::make_pair("X-BBL-OS-Version", os_version));
if (app_config)
extra_headers.insert(std::make_pair("X-BBL-Device-ID", app_config->get("slicer_uuid")));
extra_headers.insert(std::make_pair("X-BBL-Language", convert_studio_language_to_api(into_u8(current_language_code_safe()))));
return extra_headers;
}
//BBS
void GUI_App::init_http_extra_header()
{
std::map<std::string, std::string> extra_headers = get_extra_header();
if (m_agent)
m_agent->set_extra_http_header(extra_headers);
}
void GUI_App::update_http_extra_header()
{
std::map<std::string, std::string> extra_headers = get_extra_header();
Slic3r::Http::set_extra_headers(extra_headers);
if (m_agent)
m_agent->set_extra_http_header(extra_headers);
}
void GUI_App::on_start_subscribe_again(std::string dev_id)
{
auto start_subscribe_timer = new wxTimer(this, wxID_ANY);
Bind(wxEVT_TIMER, [this, start_subscribe_timer, dev_id](auto& e) {
start_subscribe_timer->Stop();
Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) return;
MachineObject* obj = dev->get_selected_machine();
if (!obj) return;
if ( (dev_id == obj->dev_id) && obj->is_connecting() && obj->subscribe_counter > 0) {
obj->subscribe_counter--;
if(wxGetApp().getAgent()) wxGetApp().getAgent()->set_user_selected_machine(dev_id);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": dev_id=" << obj->dev_id;
}
});
start_subscribe_timer->Start(4000, wxTIMER_ONE_SHOT);
}
std::string GUI_App::get_local_models_path()
{
std::string local_path = "";
if (data_dir().empty()) {
return local_path;
}
auto models_folder = (boost::filesystem::path(data_dir()) / "models");
local_path = models_folder.string();
if (!fs::exists(models_folder)) {
if (!fs::create_directory(models_folder)) {
local_path = "";
}
BOOST_LOG_TRIVIAL(info) << "create models folder:" << models_folder.string();
}
return local_path;
}
void GUI_App::init_single_instance_checker(const std::string &name, const std::string &path)
{
BOOST_LOG_TRIVIAL(debug) << "init wx instance checker " << name << " "<< path;
m_single_instance_checker = std::make_unique<wxSingleInstanceChecker>(boost::nowide::widen(name), boost::nowide::widen(path));
}
bool GUI_App::OnInit()
{
try {
return on_init_inner();
} catch (const std::exception&) {
generic_exception_handle();
return false;
}
}
int GUI_App::OnExit()
{
stop_sync_user_preset();
if (m_device_manager) {
delete m_device_manager;
m_device_manager = nullptr;
}
if (m_user_manager) {
delete m_user_manager;
m_user_manager = nullptr;
}
if (m_agent) {
// BBS avoid a crash on mac platform
#ifdef __WINDOWS__
m_agent->start_discovery(false, false);
#endif
delete m_agent;
m_agent = nullptr;
}
// Orca: clean up encrypted bbl network log file if plugin is used
// No point to keep them as they are encrypted and can't be used for debugging
try {
auto log_folder = boost::filesystem::path(data_dir()) / "log";
const std::string filePattern = R"(debug_network_.*\.log\.enc)";
std::regex pattern(filePattern);
if (boost::filesystem::exists(log_folder)) {
std::vector<boost::filesystem::path> network_logs;
for (auto& it : boost::filesystem::directory_iterator(log_folder)) {
auto temp_path = it.path();
if (boost::filesystem::is_regular_file(temp_path) && std::regex_match(temp_path.filename().string(), pattern)) {
network_logs.push_back(temp_path.filename());
}
}
for (auto f : network_logs) {
boost::filesystem::remove(f);
}
}
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "Failed to clean up encrypt bbl network log file";
}
return wxApp::OnExit();
}
class wxBoostLog : public wxLog
{
void DoLogText(const wxString &msg) override {
BOOST_LOG_TRIVIAL(warning) << msg.ToUTF8().data();
}
~wxBoostLog() override
{
// This is a hack. Prevent thread logs from going to wxGuiLog on app quit.
auto t = wxLog::SetActiveTarget(this);
wxLog::FlushActive();
wxLog::SetActiveTarget(t);
}
};
bool GUI_App::on_init_inner()
{
wxLog::SetActiveTarget(new wxBoostLog());
#if BBL_RELEASE_TO_PUBLIC
wxLog::SetLogLevel(wxLOG_Message);
#endif
// Set initialization of image handlers before any UI actions - See GH issue #7469
wxInitAllImageHandlers();
#ifdef NDEBUG
wxImage::SetDefaultLoadFlags(0); // ignore waring in release build
#endif
#if defined(_WIN32) && ! defined(_WIN64)
// BBS: remove 32bit build prompt
// Win32 32bit build.
#endif // _WIN64
// Forcing back menu icons under gtk2 and gtk3. Solution is based on:
// https://docs.gtk.org/gtk3/class.Settings.html
// see also https://docs.wxwidgets.org/3.0/classwx_menu_item.html#a2b5d6bcb820b992b1e4709facbf6d4fb
// TODO: Find workaround for GTK4
#if defined(__WXGTK20__) || defined(__WXGTK3__)
g_object_set (gtk_settings_get_default (), "gtk-menu-images", TRUE, NULL);
#endif
#ifdef WIN32
//BBS set crash log folder
CBaseException::set_log_folder(data_dir());
#endif
wxGetApp().Bind(wxEVT_QUERY_END_SESSION, [this](auto & e) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< "received wxEVT_QUERY_END_SESSION";
if (mainframe) {
wxCloseEvent e2(wxEVT_CLOSE_WINDOW);
e2.SetCanVeto(true);
mainframe->GetEventHandler()->ProcessEvent(e2);
if (e2.GetVeto()) {
e.Veto();
return;
}
}
for (auto d : dialogStack)
d->EndModal(wxID_ABORT);
});
// 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));
#ifdef __linux__
if (! check_old_linux_datadir(GetAppName())) {
std::cerr << "Quitting, user chose to move their data to new location." << std::endl;
return false;
}
#endif
BOOST_LOG_TRIVIAL(info) << boost::format("gui mode, Current OrcaSlicer Version %1%")%SoftFever_VERSION;
// 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;
if (is_editor()) {
std::string msg = Slic3r::Http::tls_global_init();
std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location");
bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Slic3r::Http::tls_system_cert_store();
if (!msg.empty() && !ssl_accept) {
RichMessageDialog
dlg(nullptr,
wxString::Format(_L("%s\nDo you want to continue?"), msg),
"OrcaSlicer", 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() ? Slic3r::Http::tls_system_cert_store() : "");
}
}
// !!! Initialization of UI settings as a language, application color mode, fonts... have to be done before first UI action.
// Like here, before the show InfoDialog in check_older_app_config()
// If load_language() fails, the application closes.
load_language(wxString(), true);
#ifdef _MSW_DARK_MODE
#ifndef __WINDOWS__
wxSystemAppearance app = wxSystemSettings::GetAppearance();
GUI::wxGetApp().app_config->set("dark_color_mode", app.IsDark() ? "1" : "0");
GUI::wxGetApp().app_config->save();
#endif // __APPLE__
bool init_dark_color_mode = dark_mode();
bool init_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1";
#ifdef __WINDOWS__
NppDarkMode::InitDarkMode(init_dark_color_mode, init_sys_menu_enabled);
#endif // __WINDOWS__
#endif
// initialize label colors and fonts
init_label_colours();
init_fonts();
wxGetApp().Update_dark_mode_flag();
#ifdef _MSW_DARK_MODE
// app_config can be updated in check_older_app_config(), so check if dark_color_mode and sys_menu_enabled was changed
if (bool new_dark_color_mode = dark_mode();
init_dark_color_mode != new_dark_color_mode) {
#ifdef __WINDOWS__
NppDarkMode::SetDarkMode(new_dark_color_mode);
#endif // __WINDOWS__
init_label_colours();
//update_label_colours_from_appconfig();
}
if (bool new_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1";
init_sys_menu_enabled != new_sys_menu_enabled)
#ifdef __WINDOWS__
NppDarkMode::SetSystemMenuForApp(new_sys_menu_enabled);
#endif
#endif
if (m_last_config_version) {
int last_major = m_last_config_version->maj();
int last_minor = m_last_config_version->min();
int last_patch = m_last_config_version->patch()/100;
std::string studio_ver = SLIC3R_VERSION;
int cur_major = atoi(studio_ver.substr(0,2).c_str());
int cur_minor = atoi(studio_ver.substr(3,2).c_str());
int cur_patch = atoi(studio_ver.substr(6,2).c_str());
BOOST_LOG_TRIVIAL(info) << boost::format("last app version {%1%.%2%.%3%}, current version {%4%.%5%.%6%}")
%last_major%last_minor%last_patch%cur_major%cur_minor%cur_patch;
if ((last_major != cur_major)
||(last_minor != cur_minor)
||(last_patch != cur_patch)) {
remove_old_networking_plugins();
}
}
if(app_config->get("version") != SLIC3R_VERSION) {
app_config->set("version", SLIC3R_VERSION);
}
SplashScreen * scrn = nullptr;
if (app_config->get("show_splash_screen") == "true") {
// make a bitmap with dark grey banner on the left side
//BBS make BBL splash screen bitmap
wxBitmap bmp = SplashScreen::MakeBitmap();
// Detect position (display) to show the splash screen
// Now this position is equal to the mainframe position
wxPoint splashscreen_pos = wxDefaultPosition;
if (app_config->has("window_mainframe")) {
auto metrics = WindowMetrics::deserialize(app_config->get("window_mainframe"));
if (metrics)
splashscreen_pos = metrics->get_rect().GetPosition();
}
BOOST_LOG_TRIVIAL(info) << "begin to show the splash screen...";
//BBS use BBL splashScreen
scrn = new SplashScreen(bmp, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 1500, splashscreen_pos);
wxYield();
scrn->SetText(_L("Loading configuration")+ dots);
}
BOOST_LOG_TRIVIAL(info) << "loading systen presets...";
preset_bundle = new PresetBundle();
// 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
preset_bundle->setup_directories();
if (m_init_app_config_from_older)
copy_older_config();
if (is_editor()) {
#ifdef __WXMSW__
if (app_config->get("associate_3mf") == "true")
associate_files(L"3mf");
if (app_config->get("associate_stl") == "true")
associate_files(L"stl");
if (app_config->get("associate_step") == "true") {
associate_files(L"step");
associate_files(L"stp");
}
associate_url(L"orcaslicer");
if (app_config->get("associate_gcode") == "true")
associate_files(L"gcode");
#endif // __WXMSW__
preset_updater = new PresetUpdater();
Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent& evt) {
if (this->plater_ != nullptr) {
// this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable);
//BBS show msg box to download new version
/* wxString tips = wxString::Format(_L("Click to download new version in default browser: %s"), version_info.version_str);
DownloadDialog dialog(this->mainframe,
tips,
_L("New version of Orca Slicer"),
false,
wxCENTER | wxICON_INFORMATION);
dialog.SetExtendedMessage(extmsg);*/
std::string skip_version_str = this->app_config->get("app", "skip_version");
bool skip_this_version = false;
if (!skip_version_str.empty()) {
BOOST_LOG_TRIVIAL(info) << "new version = " << version_info.version_str << ", skip version = " << skip_version_str;
if (version_info.version_str <= skip_version_str) {
skip_this_version = true;
} else {
app_config->set("skip_version", "");
skip_this_version = false;
}
}
if (!skip_this_version
|| evt.GetInt() != 0) {
UpdateVersionDialog dialog(this->mainframe);
wxString extmsg = wxString::FromUTF8(version_info.description);
dialog.update_version_info(extmsg, version_info.version_str);
//dialog.update_version_info(version_info.description);
if (evt.GetInt() != 0) {
dialog.m_button_skip_version->Hide();
}
switch (dialog.ShowModal())
{
case wxID_YES:
wxLaunchDefaultBrowser(version_info.url);
break;
case wxID_NO:
break;
default:
;
}
}
}
});
Bind(EVT_ENTER_FORCE_UPGRADE, [this](const wxCommandEvent& evt) {
wxString version_str = wxString::FromUTF8(this->app_config->get("upgrade", "version"));
wxString description_text = wxString::FromUTF8(this->app_config->get("upgrade", "description"));
std::string download_url = this->app_config->get("upgrade", "url");
wxString tips = wxString::Format(_L("Click to download new version in default browser: %s"), version_str);
DownloadDialog dialog(this->mainframe,
tips,
_L("The Orca Slicer needs an upgrade"),
false,
wxCENTER | wxICON_INFORMATION);
dialog.SetExtendedMessage(description_text);
int result = dialog.ShowModal();
switch (result)
{
case wxID_YES:
wxLaunchDefaultBrowser(download_url);
break;
case wxID_NO:
wxGetApp().mainframe->Close(true);
break;
default:
wxGetApp().mainframe->Close(true);
}
});
Bind(EVT_SHOW_NO_NEW_VERSION, [this](const wxCommandEvent& evt) {
wxString msg = _L("This is the newest version.");
InfoDialog dlg(nullptr, _L("Info"), msg);
dlg.ShowModal();
});
Bind(EVT_SHOW_DIALOG, [this](const wxCommandEvent& evt) {
wxString msg = evt.GetString();
InfoDialog dlg(this->mainframe, _L("Info"), msg);
dlg.Bind(wxEVT_DESTROY, [this](auto& e) {
m_info_dialog_content = wxEmptyString;
});
dlg.ShowModal();
});
}
else {
#ifdef __WXMSW__
if (app_config->get("associate_gcode") == "true")
associate_files(L"gcode");
#endif // __WXMSW__
}
// Suppress the '- default -' presets.
preset_bundle->set_default_suppressed(true);
Bind(EVT_SET_SELECTED_MACHINE, &GUI_App::on_set_selected_machine, this);
Bind(EVT_UPDATE_MACHINE_LIST, &GUI_App::on_update_machine_list, this);
Bind(EVT_USER_LOGIN, &GUI_App::on_user_login, this);
Bind(EVT_USER_LOGIN_HANDLE, &GUI_App::on_user_login_handle, this);
Bind(EVT_CHECK_PRIVACY_VER, &GUI_App::on_check_privacy_update, this);
Bind(EVT_CHECK_PRIVACY_SHOW, &GUI_App::show_check_privacy_dlg, this);
Bind(EVT_SHOW_IP_DIALOG, &GUI_App::show_ip_address_enter_dialog_handler, this);
std::map<std::string, std::string> extra_headers = get_extra_header();
Slic3r::Http::set_extra_headers(extra_headers);
copy_network_if_available();
on_init_network();
if (m_agent && m_agent->is_user_login()) {
enable_user_preset_folder(true);
} else {
enable_user_preset_folder(false);
}
// BBS if load user preset failed
//if (loaded_preset_result != 0) {
try {
// Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only.
// If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force
// installation of a compatible system preset, thus nullifying the system preset substitutions.
init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent);
}
catch (const std::exception& ex) {
show_error(nullptr, ex.what());
}
//}
#ifdef WIN32
#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
register_win32_dpi_event();
#endif // !wxVERSION_EQUAL_OR_GREATER_THAN
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);
BOOST_LOG_TRIVIAL(info) << "create the main window";
mainframe = new MainFrame();
// hide settings tabs after first Layout
if (is_editor()) {
mainframe->select_tab(size_t(0));
}
sidebar().obj_list()->init();
//sidebar().aux_list()->init_auxiliary();
mainframe->m_project->init_auxiliary();
// update_mode(); // !!! do that later
SetTopWindow(mainframe);
plater_->init_notification_manager();
m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg()));
if (is_gcode_viewer()) {
mainframe->update_layout();
if (plater_ != nullptr)
// ensure the selected technology is ptFFF
plater_->set_printer_technology(ptFFF);
}
else
load_current_presets();
if (plater_ != nullptr) {
plater_->reset_project_dirty_initial_presets();
plater_->update_project_dirty_from_presets();
}
// BBS:
#ifdef __WINDOWS__
mainframe->topbar()->SaveNormalRect();
#endif
mainframe->Show(true);
BOOST_LOG_TRIVIAL(info) << "main frame firstly shown";
//#if BBL_HAS_FIRST_PAGE
//BBS: set tp3DEditor firstly
/*plater_->canvas3D()->enable_render(false);
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
scrn->SetText(_L("Loading Opengl resourses..."));
plater_->select_view_3D("3D");
//BBS init the opengl resource here
Size canvas_size = plater_->canvas3D()->get_canvas_size();
wxGetApp().imgui()->set_display_size(static_cast<float>(canvas_size.get_width()), static_cast<float>(canvas_size.get_height()));
wxGetApp().init_opengl();
plater_->canvas3D()->init();
wxGetApp().imgui()->new_frame();
plater_->canvas3D()->enable_render(true);
plater_->canvas3D()->render();
if (is_editor())
mainframe->select_tab(size_t(0));*/
//#else
//plater_->trigger_restore_project(1);
//#endif
obj_list()->set_min_height();
update_mode(); // update view mode after fix of the object_list size
#ifdef __APPLE__
other_instance_message_handler()->bring_instance_forward();
#endif //__APPLE__
Bind(EVT_HTTP_ERROR, &GUI_App::on_http_error, this);
Bind(wxEVT_IDLE, [this](wxIdleEvent& event)
{
bool curr_studio_active = this->is_studio_active();
if (m_studio_active != curr_studio_active) {
if (curr_studio_active) {
BOOST_LOG_TRIVIAL(info) << "studio is active, start to subscribe";
if (m_agent) {
json j = json::object();
m_agent->start_subscribe("app");
}
} else {
BOOST_LOG_TRIVIAL(info) << "studio is inactive, stop to subscribe";
if (m_agent) {
json j = json::object();
m_agent->stop_subscribe("app");
}
}
m_studio_active = curr_studio_active;
}
if (! plater_)
return;
// BBS
//this->obj_manipul()->update_if_dirty();
//use m_post_initialized instead
//static bool update_gui_after_init = true;
// An ugly solution to GH #5537 in which GUI_App::init_opengl (normally called from events wxEVT_PAINT
// and wxEVT_SET_FOCUS before GUI_App::post_init is called) wasn't called before GUI_App::post_init and OpenGL wasn't initialized.
//#ifdef __linux__
// if (!m_post_initialized && m_opengl_initialized) {
//#else
if (!m_post_initialized && !m_adding_script_handler) {
//#endif
m_post_initialized = true;
#ifdef WIN32
this->mainframe->register_win32_callbacks();
#endif
this->post_init();
update_publish_status();
}
if (m_post_initialized && app_config->dirty())
app_config->save();
});
m_initialized = true;
flush_logs();
BOOST_LOG_TRIVIAL(info) << "finished the gui app init";
if (m_config_corrupted) {
m_config_corrupted = false;
show_error(nullptr,
_u8L(
"The OrcaSlicer configuration file may be corrupted and cannot be parsed.\nOrcaSlicer has attempted to recreate the "
"configuration file.\nPlease note, application settings will be lost, but printer profiles will not be affected."));
}
return true;
}
void GUI_App::copy_network_if_available()
{
if (app_config->get("update_network_plugin") != "true")
return;
std::string network_library, player_library, live555_library, network_library_dst, player_library_dst, live555_library_dst;
std::string data_dir_str = data_dir();
boost::filesystem::path data_dir_path(data_dir_str);
auto plugin_folder = data_dir_path / "plugins";
auto cache_folder = data_dir_path / "ota";
std::string changelog_file = cache_folder.string() + "/network_plugins.json";
#if defined(_MSC_VER) || defined(_WIN32)
network_library = cache_folder.string() + "/bambu_networking.dll";
player_library = cache_folder.string() + "/BambuSource.dll";
live555_library = cache_folder.string() + "/live555.dll";
network_library_dst = plugin_folder.string() + "/bambu_networking.dll";
player_library_dst = plugin_folder.string() + "/BambuSource.dll";
live555_library_dst = plugin_folder.string() + "/live555.dll";
#elif defined(__WXMAC__)
network_library = cache_folder.string() + "/libbambu_networking.dylib";
player_library = cache_folder.string() + "/libBambuSource.dylib";
live555_library = cache_folder.string() + "/liblive555.dylib";
network_library_dst = plugin_folder.string() + "/libbambu_networking.dylib";
player_library_dst = plugin_folder.string() + "/libBambuSource.dylib";
live555_library_dst = plugin_folder.string() + "/liblive555.dylib";
#else
network_library = cache_folder.string() + "/libbambu_networking.so";
player_library = cache_folder.string() + "/libBambuSource.so";
live555_library = cache_folder.string() + "/liblive555.so";
network_library_dst = plugin_folder.string() + "/libbambu_networking.so";
player_library_dst = plugin_folder.string() + "/libBambuSource.so";
live555_library_dst = plugin_folder.string() + "/liblive555.so";
#endif
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": checking network_library " << network_library << ", player_library " << player_library;
if (!boost::filesystem::exists(plugin_folder)) {
BOOST_LOG_TRIVIAL(info)<< __FUNCTION__ << ": create directory "<<plugin_folder.string();
boost::filesystem::create_directory(plugin_folder);
}
std::string error_message;
if (boost::filesystem::exists(network_library)) {
CopyFileResult cfr = copy_file(network_library, network_library_dst, error_message, false);
if (cfr != CopyFileResult::SUCCESS) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": Copying failed(" << cfr << "): " << error_message;
return;
}
static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;
fs::permissions(network_library_dst, perms);
fs::remove(network_library);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": Copying network library from" << network_library << " to " << network_library_dst<<" successfully.";
}
if (boost::filesystem::exists(player_library)) {
CopyFileResult cfr = copy_file(player_library, player_library_dst, error_message, false);
if (cfr != CopyFileResult::SUCCESS) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": Copying failed(" << cfr << "): " << error_message;
return;
}
static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;
fs::permissions(player_library_dst, perms);
fs::remove(player_library);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": Copying player library from" << player_library << " to " << player_library_dst<<" successfully.";
}
if (boost::filesystem::exists(live555_library)) {
CopyFileResult cfr = copy_file(live555_library, live555_library_dst, error_message, false);
if (cfr != CopyFileResult::SUCCESS) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": Copying failed(" << cfr << "): " << error_message;
return;
}
static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;
fs::permissions(live555_library_dst, perms);
fs::remove(live555_library);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": Copying live555 library from" << live555_library << " to " << live555_library_dst<<" successfully.";
}
if (boost::filesystem::exists(changelog_file))
fs::remove(changelog_file);
app_config->set("update_network_plugin", "false");
}
bool GUI_App::on_init_network(bool try_backup)
{
auto should_load_networking_plugin = app_config->get_bool("installed_networking");
if(!should_load_networking_plugin) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Don't load plugin as installed_networking is false";
return false;
}
int load_agent_dll = Slic3r::NetworkAgent::initialize_network_module();
bool create_network_agent = false;
__retry:
if (!load_agent_dll) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, load dll ok";
if (check_networking_version()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, compatibility version";
auto bambu_source = Slic3r::NetworkAgent::get_bambu_source_entry();
if (!bambu_source) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": can not get bambu source module!";
m_networking_compatible = false;
if (should_load_networking_plugin) {
m_networking_need_update = true;
}
}
else
create_network_agent = true;
} else {
if (try_backup) {
int result = Slic3r::NetworkAgent::unload_network_module();
BOOST_LOG_TRIVIAL(info) << "on_init_network, version mismatch, unload_network_module, result = " << result;
load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(true);
try_backup = false;
goto __retry;
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, version dismatch, need upload network module";
if (should_load_networking_plugin) {
m_networking_need_update = true;
}
}
} else {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, load dll failed";
if (should_load_networking_plugin) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, need upload network module";
m_networking_need_update = true;
}
}
if (create_network_agent) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", create network agent...");
//std::string data_dir = wxStandardPaths::Get().GetUserDataDir().ToUTF8().data();
std::string data_directory = data_dir();
m_agent = new Slic3r::NetworkAgent(data_directory);
if (!m_device_manager)
m_device_manager = new Slic3r::DeviceManager(m_agent);
else
m_device_manager->set_agent(m_agent);
if (!m_user_manager)
m_user_manager = new Slic3r::UserManager(m_agent);
else
m_user_manager->set_agent(m_agent);
if (this->is_enable_multi_machine()) {
if (!m_task_manager) {
m_task_manager = new Slic3r::TaskManager(m_agent);
m_task_manager->start();
}
m_agent->enable_multi_machine(true);
DeviceManager::EnableMultiMachine = true;
} else {
m_agent->enable_multi_machine(false);
DeviceManager::EnableMultiMachine = false;
}
//BBS set config dir
if (m_agent) {
m_agent->set_config_dir(data_directory);
}
//BBS start http log
if (m_agent) {
m_agent->init_log();
}
//BBS set cert dir
if (m_agent)
m_agent->set_cert_file(resources_dir() + "/cert", "slicer_base64.cer");
init_http_extra_header();
if (m_agent) {
init_networking_callbacks();
std::string country_code = app_config->get_country_code();
m_agent->set_country_code(country_code);
m_agent->start();
}
}
else {
int result = Slic3r::NetworkAgent::unload_network_module();
BOOST_LOG_TRIVIAL(info) << "on_init_network, unload_network_module, result = " << result;
if (!m_device_manager)
m_device_manager = new Slic3r::DeviceManager();
if (!m_user_manager)
m_user_manager = new Slic3r::UserManager();
}
return true;
}
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
));
}
bool GUI_App::dark_mode()
{
#ifdef SUPPORT_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();
#else
return wxGetApp().app_config->get("dark_color_mode") == "1" ? true : check_dark_mode();
//const unsigned luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
//return luma < 128;
#endif
#else
//BBS disable DarkUI mode
return false;
#endif
}
const wxColour GUI_App::get_label_default_clr_system()
{
return dark_mode() ? wxColour(115, 220, 103) : wxColour(26, 132, 57);
}
const wxColour GUI_App::get_label_default_clr_modified()
{
return dark_mode() ? wxColour(253, 111, 40) : wxColour(252, 77, 1);
}
void GUI_App::init_label_colours()
{
bool is_dark_mode = dark_mode();
m_color_label_modified = is_dark_mode ? wxColour("#F1754E") : wxColour("#F1754E");
m_color_label_sys = is_dark_mode ? wxColour("#B2B3B5") : wxColour("#363636");
#if defined(_WIN32) || defined(__linux__) || defined(__APPLE__)
m_color_label_default = is_dark_mode ? wxColour(250, 250, 250) : m_color_label_sys; // wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT);
m_color_highlight_default = is_dark_mode ? wxColour(78, 78, 78) : wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT);
m_color_hovered_btn_label = is_dark_mode ? wxColour(255, 255, 254) : wxColour(0,0,0);
m_color_default_btn_label = is_dark_mode ? wxColour(255, 255, 254): wxColour(0,0,0);
m_color_selected_btn_bg = is_dark_mode ? wxColour(84, 84, 91) : wxColour(206, 206, 206);
#else
m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
#endif
m_color_window_default = is_dark_mode ? wxColour(43, 43, 43) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
StateColor::SetDarkMode(is_dark_mode);
}
void GUI_App::update_label_colours_from_appconfig()
{
if (app_config->has("label_clr_sys")) {
auto str = app_config->get("label_clr_sys");
if (str != "")
m_color_label_sys = wxColour(str);
}
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::update_publish_status()
{
// mainframe->show_publish_button(has_model_mall());
// if (app_config->get("staff_pick_switch") == "true") {
// mainframe->m_webview->SendDesignStaffpick(has_model_mall());
// }
}
bool GUI_App::has_model_mall()
{
if (auto cc = app_config->get_region(); cc == "CNH" || cc == "China" || cc == "")
return false;
return true;
}
void GUI_App::update_label_colours()
{
for (Tab* tab : tabs_list)
tab->update_label_colours();
}
#ifdef _WIN32
static bool is_focused(HWND hWnd)
{
HWND hFocusedWnd = ::GetFocus();
return hFocusedWnd && hWnd == hFocusedWnd;
}
static bool is_default(wxWindow* win)
{
wxTopLevelWindow* tlw = find_toplevel_parent(win);
if (!tlw)
return false;
return win == tlw->GetDefaultItem();
}
#endif
void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool just_font/* = false*/)
{
if (wxButton *btn = dynamic_cast<wxButton*>(window)) {
if (btn->GetWindowStyleFlag() & wxBU_AUTODRAW)
return;
else {
#ifdef _WIN32
if (btn->GetId() == wxID_OK || btn->GetId() == wxID_CANCEL) {
bool is_focused_button = false;
bool is_default_button = false;
if (!(btn->GetWindowStyle() & wxNO_BORDER)) {
btn->SetWindowStyle(btn->GetWindowStyle() | wxNO_BORDER);
highlited = true;
}
auto mark_button = [this, btn, highlited](const bool mark) {
btn->SetBackgroundColour(mark ? m_color_selected_btn_bg : highlited ? m_color_highlight_default : m_color_window_default);
btn->SetForegroundColour(mark ? m_color_hovered_btn_label :m_color_default_btn_label);
btn->Refresh();
btn->Update();
};
// hovering
btn->Bind(wxEVT_ENTER_WINDOW, [mark_button](wxMouseEvent& event) { mark_button(true); event.Skip(); });
btn->Bind(wxEVT_LEAVE_WINDOW, [mark_button, btn](wxMouseEvent& event) { mark_button(is_focused(btn->GetHWND())); event.Skip(); });
// focusing
btn->Bind(wxEVT_SET_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(true); event.Skip(); });
btn->Bind(wxEVT_KILL_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(false); event.Skip(); });
is_focused_button = is_focused(btn->GetHWND());
is_default_button = is_default(btn);
mark_button(is_focused_button);
}
#endif
}
}
if (Button* btn = dynamic_cast<Button*>(window)) {
if (btn->GetWindowStyleFlag() & wxBU_AUTODRAW)
return;
}
/*if (m_is_dark_mode != dark_mode() )
m_is_dark_mode = dark_mode();*/
if (m_is_dark_mode) {
auto orig_col = window->GetBackgroundColour();
auto bg_col = StateColor::darkModeColorFor(orig_col);
// there are cases where the background color of an item is bright, specifically:
// * the background color of a button: #009688 -- 73
if (bg_col != orig_col) {
window->SetBackgroundColour(bg_col);
}
orig_col = window->GetForegroundColour();
auto fg_col = StateColor::darkModeColorFor(orig_col);
auto fg_l = StateColor::GetLightness(fg_col);
auto color_difference = StateColor::GetColorDifference(bg_col, fg_col);
// fallback and sanity check with LAB
// color difference of less than 2 or 3 is not normally visible, and even less than 30-40 doesn't stand out
if (color_difference < 10) {
fg_col = StateColor::SetLightness(fg_col, 90);
}
// some of the stock colors have a lightness of ~49
if (fg_l < 45) {
fg_col = StateColor::SetLightness(fg_col, 70);
}
// at this point it shouldn't be possible that fg_col is the same as bg_col, but let's be safe
if (fg_col == bg_col) {
fg_col = StateColor::SetLightness(fg_col, 70);
}
window->SetForegroundColour(fg_col);
}
else {
auto original_col = window->GetBackgroundColour();
auto bg_col = StateColor::lightModeColorFor(original_col);
if (bg_col != original_col) {
window->SetBackgroundColour(bg_col);
}
original_col = window->GetForegroundColour();
auto fg_col = StateColor::lightModeColorFor(original_col);
if (fg_col != original_col) {
window->SetForegroundColour(fg_col);
}
}
}
// recursive function for scaling fonts for all controls in Window
static void update_dark_children_ui(wxWindow* window, bool just_buttons_update = false)
{
/*bool is_btn = dynamic_cast<wxButton*>(window) != nullptr;
is_btn = false;*/
if (!window) return;
wxGetApp().UpdateDarkUI(window);
auto children = window->GetChildren();
for (auto child : children) {
update_dark_children_ui(child);
}
}
// Note: Don't use this function for Dialog contains ScalableButtons
void GUI_App::UpdateDarkUIWin(wxWindow* win)
{
update_dark_children_ui(win);
}
void GUI_App::Update_dark_mode_flag()
{
m_is_dark_mode = dark_mode();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": switch the current dark mode status to %1% ")%m_is_dark_mode;
}
void GUI_App::UpdateDlgDarkUI(wxDialog* dlg)
{
#ifdef __WINDOWS__
NppDarkMode::SetDarkExplorerTheme(dlg->GetHWND());
NppDarkMode::SetDarkTitleBar(dlg->GetHWND());
#endif
update_dark_children_ui(dlg);
}
void GUI_App::UpdateFrameDarkUI(wxFrame* dlg)
{
#ifdef __WINDOWS__
NppDarkMode::SetDarkExplorerTheme(dlg->GetHWND());
NppDarkMode::SetDarkTitleBar(dlg->GetHWND());
#endif
update_dark_children_ui(dlg);
}
void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/)
{
#ifdef __WINDOWS__
UpdateDarkUI(dvc, highlited ? dark_mode() : false);
#ifdef _MSW_DARK_MODE
//dvc->RefreshHeaderDarkMode(&m_normal_font);
HWND hwnd;
if (!dvc->HasFlag(wxDV_NO_HEADER)) {
hwnd = (HWND) dvc->GenericGetHeader()->GetHandle();
hwnd = GetWindow(hwnd, GW_CHILD);
if (hwnd != NULL)
NppDarkMode::SetDarkListViewHeader(hwnd);
wxItemAttr attr;
attr.SetTextColour(NppDarkMode::GetTextColor());
attr.SetFont(m_normal_font);
dvc->SetHeaderAttr(attr);
}
#endif //_MSW_DARK_MODE
if (dvc->HasFlag(wxDV_ROW_LINES))
dvc->SetAlternateRowColour(m_color_highlight_default);
if (dvc->GetBorder() != wxBORDER_SIMPLE)
dvc->SetWindowStyle(dvc->GetWindowStyle() | wxBORDER_SIMPLE);
#endif
}
void GUI_App::UpdateAllStaticTextDarkUI(wxWindow* parent)
{
#ifdef __WINDOWS__
wxGetApp().UpdateDarkUI(parent);
auto children = parent->GetChildren();
for (auto child : children) {
if (dynamic_cast<wxStaticText*>(child))
child->SetForegroundColour(m_color_label_default);
}
#endif
}
void GUI_App::init_fonts()
{
// BBS: modify font
m_small_font = Label::Body_10;
m_bold_font = Label::Body_10.Bold();
m_normal_font = Label::Body_10;
#ifdef __WXMAC__
m_small_font.SetPointSize(11);
m_bold_font.SetPointSize(13);
#endif /*__WXMAC__*/
// wxSYS_OEM_FIXED_FONT and wxSYS_ANSI_FIXED_FONT use the same as
// DEFAULT in wxGtk. Use the TELETYPE family as a work-around
m_code_font = wxFont(wxFontInfo().Family(wxFONTFAMILY_TELETYPE));
m_code_font.SetPointSize(m_small_font.GetPointSize());
}
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 = Label::Body_14; // BBS: larger font size
m_small_font = m_normal_font;
m_bold_font = m_normal_font.Bold();
m_link_font = m_bold_font.Underlined();
m_em_unit = main_frame->em_unit();
m_code_font.SetPointSize(m_small_font.GetPointSize());
}
void GUI_App::set_label_clr_modified(const wxColour& clr)
{
return;
//BBS
/*
if (m_color_label_modified == clr)
return;
m_color_label_modified = clr;
const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue()));
app_config->save();
*/
}
void GUI_App::set_label_clr_sys(const wxColour& clr)
{
return;
//BBS
/*
if (m_color_label_sys == clr)
return;
m_color_label_sys = clr;
const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue()));
app_config->save();
*/
}
bool GUI_App::get_side_menu_popup_status()
{
return m_side_popup_status;
}
void GUI_App::set_side_menu_popup_status(bool status)
{
m_side_popup_status = status;
}
void GUI_App::link_to_network_check()
{
std::string url;
std::string country_code = app_config->get_country_code();
if (country_code == "US") {
url = "https://status.bambulab.com";
}
else if (country_code == "CN") {
url = "https://status.bambulab.cn";
}
else {
url = "https://status.bambulab.com";
}
wxLaunchDefaultBrowser(url);
}
bool GUI_App::tabs_as_menu() const
{
return false;
}
wxSize GUI_App::get_min_size() const
{
return wxSize(76*m_em_unit, 49 * m_em_unit);
}
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__
//return icon_sc;
const std::string& auto_val = app_config->get("toolkit_size");
if (auto_val.empty())
return icon_sc;
int int_val = 100;
// correct value in respect to toolkit_size
int_val = std::min(atoi(auto_val.c_str()), int_val);
if (is_limited && int_val < 50)
int_val = 50;
return 0.01f * int_val * icon_sc;
}
void GUI_App::set_auto_toolbar_icon_scale(float scale) 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__
long int_val = std::min(int(std::lround(scale / icon_sc * 100)), 100);
std::string val = std::to_string(int_val);
app_config->set("toolkit_size", val);
}
// check user printer_presets for the containing information about "Print Host upload"
void GUI_App::check_printer_presets()
{
//BBS
#if 0
std::vector<std::string> preset_names = PhysicalPrinter::presets_with_print_host_information(preset_bundle->printers);
if (preset_names.empty())
return;
// BBS: remove "print host upload" message dialog
preset_bundle->physical_printers.load_printers_from_presets(preset_bundle->printers);
#endif
}
void switch_window_pools();
void release_window_pools();
void GUI_App::recreate_GUI(const wxString &msg_name)
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "recreate_GUI enter";
m_is_recreating_gui = true;
update_http_extra_header();
mainframe->shutdown();
ProgressDialog dlg(msg_name, msg_name, 100, nullptr, wxPD_AUTO_HIDE);
dlg.Pulse();
dlg.Update(10, _L("Rebuild") + dots);
MainFrame *old_main_frame = mainframe;
struct ClientData : wxClientData
{
~ClientData() { release_window_pools(); }
};
old_main_frame->SetClientObject(new ClientData);
switch_window_pools();
mainframe = new MainFrame();
if (is_editor())
// hide settings tabs after first Layout
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
// Propagate model objects to object list.
sidebar().obj_list()->init();
//sidebar().aux_list()->init_auxiliary();
//mainframe->m_auxiliary->init_auxiliary();
SetTopWindow(mainframe);
dlg.Update(30, _L("Rebuild") + dots);
old_main_frame->Destroy();
dlg.Update(80, _L("Loading current presets") + dots);
m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg()));
load_current_presets();
mainframe->Show(true);
//mainframe->refresh_plugin_tips();
dlg.Update(90, _L("Loading a mode view") + dots);
obj_list()->set_min_height();
update_mode();
//check hms info for different language
if (hms_query)
hms_query->check_hms_info();
//BBS: trigger restore project logic here, and skip confirm
plater_->trigger_restore_project(1);
// #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);
// });
update_publish_status();
m_is_recreating_gui = false;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "recreate_GUI exit";
}
void GUI_App::system_info()
{
//SysInfoDialog dlg;
//dlg.ShowModal();
}
void GUI_App::keyboard_shortcuts()
{
KBShortcutsDialog dlg;
dlg.ShowModal();
}
void GUI_App::ShowUserGuide() {
// BBS:Show NewUser Guide
try {
bool res = false;
GuideFrame GuideDlg(this);
//if (GuideDlg.IsFirstUse())
res = GuideDlg.run();
if (res) {
load_current_presets();
update_publish_status();
mainframe->refresh_plugin_tips();
// BBS: remove SLA related message
}
} catch (std::exception &e) {
// wxMessageBox(e.what(), "", MB_OK);
}
}
void GUI_App::ShowDownNetPluginDlg() {
try {
auto iter = std::find_if(dialogStack.begin(), dialogStack.end(), [](auto dialog) {
return dynamic_cast<DownloadProgressDialog *>(dialog) != nullptr;
});
if (iter != dialogStack.end())
return;
DownloadProgressDialog dlg(_L("Downloading Bambu Network Plug-in"));
dlg.ShowModal();
} catch (std::exception &e) {
;
}
}
void GUI_App::ShowUserLogin(bool show)
{
// BBS: User Login Dialog
if (show) {
try {
if (!login_dlg)
login_dlg = new ZUserLogin();
else {
delete login_dlg;
login_dlg = new ZUserLogin();
}
login_dlg->ShowModal();
} catch (std::exception &e) {
;
}
} else {
if (login_dlg)
login_dlg->EndModal(wxID_OK);
}
}
void GUI_App::ShowOnlyFilament() {
// BBS:Show NewUser Guide
try {
bool res = false;
GuideFrame GuideDlg(this);
GuideDlg.SetStartPage(GuideFrame::GuidePage::BBL_FILAMENT_ONLY);
res = GuideDlg.run();
if (res) {
load_current_presets();
// BBS: remove SLA related message
}
} catch (std::exception &e) {
// wxMessageBox(e.what(), "", MB_OK);
}
}
// static method accepting a wxWindow object as first parameter
bool GUI_App::catch_error(std::function<void()> cb,
// wxMessageDialog* message_dialog,
const std::string& err /*= ""*/)
{
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
void fatal_error(wxWindow* parent)
{
show_error(parent, "");
// exit 1; // #ys_FIXME
}
#ifdef __WINDOWS__
#ifdef _MSW_DARK_MODE
static void update_scrolls(wxWindow* window)
{
wxWindowList::compatibility_iterator node = window->GetChildren().GetFirst();
while (node)
{
wxWindow* win = node->GetData();
if (dynamic_cast<wxScrollHelper*>(win) ||
dynamic_cast<wxTreeCtrl*>(win) ||
dynamic_cast<wxTextCtrl*>(win))
NppDarkMode::SetDarkExplorerTheme(win->GetHWND());
update_scrolls(win);
node = node->GetNext();
}
}
#endif //_MSW_DARK_MODE
#ifdef _MSW_DARK_MODE
void GUI_App::force_menu_update()
{
NppDarkMode::SetSystemMenuForApp(app_config->get("sys_menu_enabled") == "1");
}
#endif //_MSW_DARK_MODE
#endif //__WINDOWS__
void GUI_App::force_colors_update()
{
#ifdef _MSW_DARK_MODE
#ifdef __WINDOWS__
NppDarkMode::SetDarkMode(dark_mode());
if (WXHWND wxHWND = wxToolTip::GetToolTipCtrl())
NppDarkMode::SetDarkExplorerTheme((HWND)wxHWND);
NppDarkMode::SetDarkTitleBar(mainframe->GetHWND());
//NppDarkMode::SetDarkExplorerTheme((HWND)mainframe->m_settings_dialog.GetHWND());
//NppDarkMode::SetDarkTitleBar(mainframe->m_settings_dialog.GetHWND());
#endif // __WINDOWS__
#endif //_MSW_DARK_MODE
m_force_colors_update = true;
}
// Called after the Preferences dialog is closed and the program settings are saved.
// Update the UI based on the current preferences.
void GUI_App::update_ui_from_settings()
{
update_label_colours();
// Upadte UI colors before Update UI from settings
if (m_force_colors_update) {
m_force_colors_update = false;
//UpdateDlgDarkUI(&mainframe->m_settings_dialog);
//mainframe->m_settings_dialog.Refresh();
//mainframe->m_settings_dialog.Update();
if (mainframe) {
#ifdef __WINDOWS__
mainframe->force_color_changed();
update_scrolls(mainframe);
update_scrolls(&mainframe->m_settings_dialog);
#endif //_MSW_DARK_MODE
update_dark_children_ui(mainframe);
}
}
if (mainframe) {mainframe->update_ui_from_settings();}
}
void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_maximized)
{
const std::string name = into_u8(window->GetName());
window->Bind(wxEVT_CLOSE_WINDOW, [=](wxCloseEvent &event) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": received wxEVT_CLOSE_WINDOW, trigger save for window_mainframe";
window_pos_save(window, "mainframe");
event.Skip();
});
if (window_pos_restore(window, "mainframe", default_maximized)) {
on_window_geometry(window, [=]() {
window_pos_sanitize(window);
});
} else {
on_window_geometry(window, [=]() {
window_pos_center(window);
});
}
}
void GUI_App::load_project(wxWindow *parent, wxString& input_file) const
{
input_file.Clear();
wxFileDialog dialog(parent ? parent : GetTopWindow(),
_L("Choose one file (3mf):"),
app_config->get_last_dir(), "",
file_wildcards(FT_PROJECT), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() == wxID_OK)
input_file = dialog.GetPath();
}
void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const
{
input_files.Clear();
wxFileDialog dialog(parent ? parent : GetTopWindow(),
#ifdef __APPLE__
_L("Choose one or more files (3mf/step/stl/svg/obj/amf/usd*/abc/ply):"),
#else
_L("Choose one or more files (3mf/step/stl/svg/obj/amf):"),
#endif
from_u8(app_config->get_last_dir()), "",
file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() == wxID_OK)
dialog.GetPaths(input_files);
}
void GUI_App::import_zip(wxWindow* parent, wxString& input_file) const
{
wxFileDialog dialog(parent ? parent : GetTopWindow(),
_L("Choose ZIP file") + ":",
from_u8(app_config->get_last_dir()), "",
file_wildcards(FT_ZIP), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() == wxID_OK)
input_file = dialog.GetPath();
}
void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const
{
input_file.Clear();
wxFileDialog dialog(parent ? parent : GetTopWindow(),
_L("Choose one file (gcode/3mf):"),
app_config->get_last_dir(), "",
file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() == wxID_OK)
input_file = dialog.GetPath();
}
wxString GUI_App::transition_tridid(int trid_id)
{
wxString maping_dict[8] = { "A", "B", "C", "D", "E", "F", "G" };
int id_index = ceil(trid_id / 4);
int id_suffix = (trid_id + 1) % 4 == 0 ? 4 : (trid_id + 1) % 4;
return wxString::Format("%s%d", maping_dict[id_index], id_suffix);
}
//BBS
void GUI_App::request_login(bool show_user_info)
{
ShowUserLogin();
if (show_user_info) {
get_login_info();
}
}
void GUI_App::get_login_info()
{
if (m_agent) {
if (m_agent->is_user_login()) {
std::string login_cmd = m_agent->build_login_cmd();
wxString strJS = wxString::Format("window.postMessage(%s)", login_cmd);
GUI::wxGetApp().run_script(strJS);
}
else {
m_agent->user_logout();
std::string logout_cmd = m_agent->build_logout_cmd();
wxString strJS = wxString::Format("window.postMessage(%s)", logout_cmd);
GUI::wxGetApp().run_script(strJS);
}
mainframe->m_webview->SetLoginPanelVisibility(true);
}
}
bool GUI_App::is_user_login()
{
if (m_agent) {
return m_agent->is_user_login();
}
return false;
}
bool GUI_App::check_login()
{
bool result = false;
if (m_agent) {
result = m_agent->is_user_login();
}
if (!result) {
ShowUserLogin();
}
return result;
}
void GUI_App::request_user_handle(int online_login)
{
auto evt = new wxCommandEvent(EVT_USER_LOGIN_HANDLE);
evt->SetInt(online_login);
wxQueueEvent(this, evt);
}
void GUI_App::request_user_login(int online_login)
{
auto evt = new wxCommandEvent(EVT_USER_LOGIN);
evt->SetInt(online_login);
wxQueueEvent(this, evt);
}
void GUI_App::request_user_logout()
{
if (m_agent && m_agent->is_user_login()) {
// Update data first before showing dialogs
m_agent->user_logout();
m_agent->set_user_selected_machine("");
/* delete old user settings */
bool transfer_preset_changes = false;
wxString header = _L("Some presets are modified.") + "\n" +
_L("You can keep the modifield presets to the new project, discard or save changes as new presets.");
wxGetApp().check_and_keep_current_preset_changes(_L("User logged out"), header, ActionButtons::KEEP | ActionButtons::SAVE, &transfer_preset_changes);
m_device_manager->clean_user_info();
GUI::wxGetApp().sidebar().load_ams_list({}, {});
remove_user_presets();
enable_user_preset_folder(false);
preset_bundle->load_user_presets(DEFAULT_USER_FOLDER_NAME, ForwardCompatibilitySubstitutionRule::Enable);
mainframe->update_side_preset_ui();
GUI::wxGetApp().stop_sync_user_preset();
}
}
int GUI_App::request_user_unbind(std::string dev_id)
{
int result = -1;
if (m_agent) {
result = m_agent->unbind(dev_id);
BOOST_LOG_TRIVIAL(info) << "request_user_unbind, dev_id = " << dev_id << ", result = " << result;
return result;
}
return result;
}
std::string GUI_App::handle_web_request(std::string cmd)
{
try {
//BBS use nlohmann json format
std::stringstream ss(cmd), oss;
pt::ptree root, response;
pt::read_json(ss, root);
if (root.empty())
return "";
boost::optional<std::string> sequence_id = root.get_optional<std::string>("sequence_id");
boost::optional<std::string> command = root.get_optional<std::string>("command");
if (command.has_value()) {
std::string command_str = command.value();
if (command_str.compare("request_project_download") == 0) {
if (root.get_child_optional("data") != boost::none) {
pt::ptree data_node = root.get_child("data");
boost::optional<std::string> project_id = data_node.get_optional<std::string>("project_id");
if (project_id.has_value()) {
this->request_project_download(project_id.value());
}
}
}
else if (command_str.compare("open_project") == 0) {
if (root.get_child_optional("data") != boost::none) {
pt::ptree data_node = root.get_child("data");
boost::optional<std::string> project_id = data_node.get_optional<std::string>("project_id");
if (project_id.has_value()) {
this->request_open_project(project_id.value());
}
}
}
else if (command_str.compare("get_login_info") == 0) {
CallAfter([this] {
get_login_info();
});
}
else if (command_str.compare("homepage_login_or_register") == 0) {
CallAfter([this] {
this->request_login(true);
});
}
else if (command_str.compare("homepage_logout") == 0) {
CallAfter([this] {
wxGetApp().request_user_logout();
});
}
else if (command_str.compare("homepage_modeldepot") == 0) {
CallAfter([this] {
wxGetApp().open_mall_page_dialog();
});
}
else if (command_str.compare("homepage_newproject") == 0) {
this->request_open_project("<new>");
}
else if (command_str.compare("homepage_openproject") == 0) {
this->request_open_project({});
}
else if (command_str.compare("get_recent_projects") == 0) {
if (mainframe) {
if (mainframe->m_webview) {
mainframe->m_webview->SendRecentList(INT_MAX);
}
}
}
// else if (command_str.compare("modelmall_model_advise_get") == 0) {
// if (mainframe && this->app_config->get("staff_pick_switch") == "true") {
// if (mainframe->m_webview) {
// mainframe->m_webview->SendDesignStaffpick(has_model_mall());
// }
// }
// }
// else if (command_str.compare("modelmall_model_open") == 0) {
// if (root.get_child_optional("data") != boost::none) {
// pt::ptree data_node = root.get_child("data");
// boost::optional<std::string> id = data_node.get_optional<std::string>("id");
// if (id.has_value() && mainframe->m_webview) {
// mainframe->m_webview->OpenModelDetail(id.value(), m_agent);
// }
// }
// }
else if (command_str.compare("homepage_open_recentfile") == 0) {
if (root.get_child_optional("data") != boost::none) {
pt::ptree data_node = root.get_child("data");
boost::optional<std::string> path = data_node.get_optional<std::string>("path");
if (path.has_value()) {
this->request_open_project(path.value());
}
}
}
else if (command_str.compare("homepage_delete_recentfile") == 0) {
if (root.get_child_optional("data") != boost::none) {
pt::ptree data_node = root.get_child("data");
boost::optional<std::string> path = data_node.get_optional<std::string>("path");
if (path.has_value()) {
this->request_remove_project(path.value());
}
}
}
else if (command_str.compare("homepage_delete_all_recentfile") == 0) {
this->request_remove_project("");
}
else if (command_str.compare("homepage_explore_recentfile") == 0) {
if (root.get_child_optional("data") != boost::none) {
pt::ptree data_node = root.get_child("data");
boost::optional<std::string> path = data_node.get_optional<std::string>("path");
if (path.has_value())
{
boost::filesystem::path NowFile(path.value());
std::string FilePath = NowFile.make_preferred().string();
desktop_open_any_folder(FilePath);
}
}
}
else if (command_str.compare("homepage_open_hotspot") == 0) {
if (root.get_child_optional("data") != boost::none) {
pt::ptree data_node = root.get_child("data");
boost::optional<std::string> url = data_node.get_optional<std::string>("url");
if (url.has_value()) {
this->request_open_project(url.value());
}
}
}
else if (command_str.compare("begin_network_plugin_download") == 0) {
CallAfter([this] { wxGetApp().ShowDownNetPluginDlg(); });
}
else if (command_str.compare("get_web_shortcut") == 0) {
if (root.get_child_optional("key_event") != boost::none) {
pt::ptree key_event_node = root.get_child("key_event");
auto keyCode = key_event_node.get<int>("key");
auto ctrlKey = key_event_node.get<bool>("ctrl");
auto shiftKey = key_event_node.get<bool>("shift");
auto cmdKey = key_event_node.get<bool>("cmd");
wxKeyEvent e(wxEVT_CHAR_HOOK);
#ifdef __APPLE__
e.SetControlDown(cmdKey);
e.SetRawControlDown(ctrlKey);
#else
e.SetControlDown(ctrlKey);
#endif
e.SetShiftDown(shiftKey);
keyCode = keyCode == 188 ? ',' : keyCode;
e.m_keyCode = keyCode;
e.SetEventObject(mainframe);
wxPostEvent(mainframe, e);
}
}
else if (command_str.compare("userguide_wiki_open") == 0) {
if (root.get_child_optional("data") != boost::none) {
pt::ptree data_node = root.get_child("data");
boost::optional<std::string> path = data_node.get_optional<std::string>("url");
if (path.has_value()) {
wxLaunchDefaultBrowser(path.value());
}
}
}
else if (command_str.compare("homepage_open_ccabin") == 0) {
if (root.get_child_optional("data") != boost::none) {
pt::ptree data_node = root.get_child("data");
boost::optional<std::string> path = data_node.get_optional<std::string>("file");
if (path.has_value()) {
std::string Fullpath = resources_dir() + "/web/homepage/model/" + path.value();
this->request_open_project(Fullpath);
}
}
}
else if (command_str.compare("common_openurl") == 0) {
boost::optional<std::string> path = root.get_optional<std::string>("url");
if (path.has_value()) {
wxLaunchDefaultBrowser(path.value());
}
}
else if (command_str.compare("homepage_makerlab_get") == 0) {
//if (mainframe->m_webview) { mainframe->m_webview->SendMakerlabList(); }
}
else if (command_str.compare("makerworld_model_open") == 0)
{
if (root.get_child_optional("model") != boost::none) {
pt::ptree data_node = root.get_child("model");
boost::optional<std::string> path = data_node.get_optional<std::string>("url");
if (path.has_value())
{
wxString realurl = from_u8(url_decode(path.value()));
wxGetApp().request_model_download(realurl);
}
}
}
}
}
catch (...) {
BOOST_LOG_TRIVIAL(trace) << "parse json cmd failed " << cmd;
return "";
}
return "";
}
void GUI_App::handle_script_message(std::string msg)
{
try {
json j = json::parse(msg);
if (j.contains("command")) {
wxString cmd = j["command"];
if (cmd == "user_login") {
if (m_agent) {
m_agent->change_user(j.dump());
if (m_agent->is_user_login()) {
request_user_login(1);
}
}
}
}
}
catch (...) {
;
}
}
void GUI_App::request_model_download(wxString url)
{
if (plater_) {
plater_->request_model_download(url);
}
}
//BBS download project by project id
void GUI_App::download_project(std::string project_id)
{
if (plater_) {
plater_->request_download_project(project_id);
}
}
void GUI_App::request_project_download(std::string project_id)
{
if (!check_login()) return;
download_project(project_id);
}
void GUI_App::request_open_project(std::string project_id)
{
if (plater()->is_background_process_slicing()) {
Slic3r::GUI::show_info(nullptr, _L("new or open project file is not allowed during the slicing process!"), _L("Open Project"));
return;
}
if (project_id == "<new>")
plater()->new_project();
else if (project_id.empty())
plater()->load_project();
else if (std::find_if_not(project_id.begin(), project_id.end(),
[](char c) { return std::isdigit(c); }) == project_id.end())
;
else if (boost::algorithm::starts_with(project_id, "http"))
;
else
CallAfter([this, project_id] { mainframe->open_recent_project(-1, wxString::FromUTF8(project_id)); });
}
void GUI_App::request_remove_project(std::string project_id)
{
mainframe->remove_recent_project(-1, wxString::FromUTF8(project_id));
}
void GUI_App::handle_http_error(unsigned int status, std::string body)
{
// tips body size must less than 1024
auto evt = new wxCommandEvent(EVT_HTTP_ERROR);
evt->SetInt(status);
evt->SetString(wxString(body));
wxQueueEvent(this, evt);
}
void GUI_App::on_http_error(wxCommandEvent &evt)
{
int status = evt.GetInt();
int code = 0;
std::string error;
wxString result;
if (status >= 400 && status < 500) {
try {
json j = json::parse(evt.GetString());
if (j.contains("code")) {
if (!j["code"].is_null())
code = j["code"].get<int>();
}
if (j.contains("error"))
if (!j["error"].is_null())
error = j["error"].get<std::string>();
}
catch (...) {}
}
// Version limit
if (code == HttpErrorVersionLimited) {
MessageDialog msg_dlg(nullptr, _L("The version of Orca Slicer is too low and needs to be updated to the latest version before it can be used normally"), "", wxAPPLY | wxOK);
if (msg_dlg.ShowModal() == wxOK) {
}
}
// request login
if (status == 401) {
if (m_agent) {
if (m_agent->is_user_login()) {
this->request_user_logout();
if (!m_show_http_errpr_msgdlg) {
MessageDialog msg_dlg(nullptr, _L("Login information expired. Please login again."), "", wxAPPLY | wxOK);
m_show_http_errpr_msgdlg = true;
auto modal_result = msg_dlg.ShowModal();
if (modal_result == wxOK || modal_result == wxCLOSE) {
m_show_http_errpr_msgdlg = false;
return;
}
}
}
}
return;
}
}
void GUI_App::enable_user_preset_folder(bool enable)
{
if (enable) {
std::string user_id = m_agent->get_user_id();
app_config->set("preset_folder", user_id);
GUI::wxGetApp().preset_bundle->update_user_presets_directory(user_id);
} else {
BOOST_LOG_TRIVIAL(info) << "preset_folder: set to empty";
app_config->set("preset_folder", "");
GUI::wxGetApp().preset_bundle->update_user_presets_directory(DEFAULT_USER_FOLDER_NAME);
}
}
void GUI_App::on_set_selected_machine(wxCommandEvent &evt)
{
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (dev) {
dev->set_selected_machine(m_agent->get_user_selected_machine());
}
}
void GUI_App::on_update_machine_list(wxCommandEvent &evt)
{
/* DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (dev) {
dev->add_user_subscribe();
}*/
}
void GUI_App::on_user_login_handle(wxCommandEvent &evt)
{
if (!m_agent) { return; }
int online_login = evt.GetInt();
m_agent->connect_server();
// get machine list
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) return;
boost::thread update_thread = boost::thread([this, dev] {
dev->update_user_machine_list_info();
auto evt = new wxCommandEvent(EVT_SET_SELECTED_MACHINE);
wxQueueEvent(this, evt);
});
if (online_login) {
remove_user_presets();
enable_user_preset_folder(true);
preset_bundle->load_user_presets(m_agent->get_user_id(), ForwardCompatibilitySubstitutionRule::Enable);
mainframe->update_side_preset_ui();
GUI::wxGetApp().mainframe->show_sync_dialog();
}
}
void GUI_App::check_track_enable()
{
// Orca: alaways disable track event
if (m_agent) {
m_agent->track_enable(false);
m_agent->track_remove_files();
}
}
void GUI_App::on_user_login(wxCommandEvent &evt)
{
if (!m_agent) { return; }
int online_login = evt.GetInt();
// check privacy before handle
check_privacy_version(online_login);
check_track_enable();
}
bool GUI_App::is_studio_active()
{
auto curr_time = std::chrono::system_clock::now();
auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(curr_time - last_active_point);
if (diff.count() < STUDIO_INACTIVE_TIMEOUT) {
return true;
}
return false;
}
void GUI_App::reset_to_active()
{
last_active_point = std::chrono::system_clock::now();
}
void GUI_App::check_update(bool show_tips, int by_user)
{
if (version_info.version_str.empty()) return;
if (version_info.url.empty()) return;
auto curr_version = Semver::parse(SLIC3R_VERSION);
auto remote_version = Semver::parse(version_info.version_str);
if (curr_version && remote_version && (*remote_version > *curr_version)) {
if (version_info.force_upgrade) {
wxGetApp().app_config->set_bool("force_upgrade", version_info.force_upgrade);
wxGetApp().app_config->set("upgrade", "force_upgrade", true);
wxGetApp().app_config->set("upgrade", "description", version_info.description);
wxGetApp().app_config->set("upgrade", "version", version_info.version_str);
wxGetApp().app_config->set("upgrade", "url", version_info.url);
GUI::wxGetApp().enter_force_upgrade();
}
else {
GUI::wxGetApp().request_new_version(by_user);
}
} else {
wxGetApp().app_config->set("upgrade", "force_upgrade", false);
if (show_tips)
this->no_new_version();
}
}
void GUI_App::check_new_version(bool show_tips, int by_user)
{
std::string platform = "windows";
#ifdef __WINDOWS__
platform = "windows";
#endif
#ifdef __APPLE__
platform = "macos";
#endif
#ifdef __LINUX__
platform = "linux";
#endif
std::string query_params = (boost::format("?name=slicer&version=%1%&guide_version=%2%")
% VersionInfo::convert_full_version(SLIC3R_VERSION)
% VersionInfo::convert_full_version("0.0.0.1")
).str();
std::string url = get_http_url(app_config->get_country_code()) + query_params;
Slic3r::Http http = Slic3r::Http::get(url);
http.header("accept", "application/json")
.timeout_connect(TIMEOUT_CONNECT)
.timeout_max(TIMEOUT_RESPONSE)
.on_complete([this, show_tips, by_user](std::string body, unsigned) {
try {
json j = json::parse(body);
if (j.contains("message")) {
if (j["message"].get<std::string>() == "success") {
if (j.contains("software")) {
if (j["software"].empty() && show_tips) {
this->no_new_version();
}
else {
if (j["software"].contains("url")
&& j["software"].contains("version")
&& j["software"].contains("description")) {
version_info.url = j["software"]["url"].get<std::string>();
version_info.version_str = j["software"]["version"].get<std::string>();
version_info.description = j["software"]["description"].get<std::string>();
}
if (j["software"].contains("force_update")) {
version_info.force_upgrade = j["software"]["force_update"].get<bool>();
}
CallAfter([this, show_tips, by_user](){
this->check_update(show_tips, by_user);
});
}
}
}
}
}
catch (...) {
;
}
})
.on_error([this](std::string body, std::string error, unsigned int status) {
handle_http_error(status, body);
BOOST_LOG_TRIVIAL(error) << "check new version error" << body;
}).perform();
}
//parse the string, if it doesn't contain a valid version string, return invalid version.
Semver get_version(const std::string& str, const std::regex& regexp) {
std::smatch match;
if (std::regex_match(str, match, regexp)) {
std::string version_cleaned = match[0];
const boost::optional<Semver> version = Semver::parse(version_cleaned);
if (version.has_value()) {
return *version;
}
}
return Semver::invalid();
}
void GUI_App::check_new_version_sf(bool show_tips, int by_user)
{
AppConfig* app_config = wxGetApp().app_config;
bool check_stable_only = app_config->get_bool("check_stable_update_only");
auto version_check_url = app_config->version_check_url(check_stable_only);
Http::get(version_check_url)
.on_error([&](std::string body, std::string error, unsigned http_status) {
(void)body;
BOOST_LOG_TRIVIAL(error) << format("Error getting: `%1%`: HTTP %2%, %3%", "check_new_version_sf", http_status,
error);
})
.timeout_connect(1)
.on_complete([this,by_user, check_stable_only](std::string body, unsigned http_status) {
// Http response OK
if (http_status != 200)
return;
try {
boost::trim(body);
// Orca: parse github release, inspired by SS
boost::property_tree::ptree root;
std::stringstream json_stream(body);
boost::property_tree::read_json(json_stream, root);
// at least two number, use '.' as separator. can be followed by -Az23 for prereleased and +Az42 for
// metadata
std::regex matcher("[0-9]+\\.[0-9]+(\\.[0-9]+)*(-[A-Za-z0-9]+)?(\\+[A-Za-z0-9]+)?");
Semver current_version = get_version(SoftFever_VERSION, matcher);
Semver best_pre(1, 0, 0);
Semver best_release(1, 0, 0);
std::string best_pre_url;
std::string best_release_url;
std::string best_release_content;
std::string best_pre_content;
const std::regex reg_num("([0-9]+)");
if (check_stable_only) {
std::string tag = root.get<std::string>("tag_name");
if (tag[0] == 'v')
tag.erase(0, 1);
for (std::regex_iterator it = std::sregex_iterator(tag.begin(), tag.end(), reg_num); it != std::sregex_iterator(); ++it) {}
Semver tag_version = get_version(tag, matcher);
if (root.get<bool>("prerelease")) {
if (best_pre < tag_version) {
best_pre = tag_version;
best_pre_url = root.get<std::string>("html_url");
best_pre_content = root.get<std::string>("body");
best_pre.set_prerelease("Preview");
}
} else {
if (best_release < tag_version) {
best_release = tag_version;
best_release_url = root.get<std::string>("html_url");
best_release_content = root.get<std::string>("body");
}
}
} else {
for (auto json_version : root) {
std::string tag = json_version.second.get<std::string>("tag_name");
if (tag[0] == 'v')
tag.erase(0, 1);
for (std::regex_iterator it = std::sregex_iterator(tag.begin(), tag.end(), reg_num); it != std::sregex_iterator();
++it) {}
Semver tag_version = get_version(tag, matcher);
if (json_version.second.get<bool>("prerelease")) {
if (best_pre < tag_version) {
best_pre = tag_version;
best_pre_url = json_version.second.get<std::string>("html_url");
best_pre_content = json_version.second.get<std::string>("body");
best_pre.set_prerelease("Preview");
}
} else {
if (best_release < tag_version) {
best_release = tag_version;
best_release_url = json_version.second.get<std::string>("html_url");
best_release_content = json_version.second.get<std::string>("body");
}
}
}
}
// if release is more recent than beta, use release anyway
if (best_pre < best_release) {
best_pre = best_release;
best_pre_url = best_release_url;
best_pre_content = best_release_content;
}
// if we're the most recent, don't do anything
if ((check_stable_only ? best_release : best_pre) <= current_version) {
if (by_user != 0)
this->no_new_version();
return;
}
version_info.url = check_stable_only ? best_release_url : best_pre_url;
version_info.version_str = check_stable_only ? best_release.to_string_sf() : best_pre.to_string();
version_info.description = check_stable_only ? best_release_content : best_pre_content;
version_info.force_upgrade = false;
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
evt->SetString((check_stable_only ? best_release : best_pre).to_string());
GUI::wxGetApp().QueueEvent(evt);
} catch (...) {}
})
.perform();
}
//BBS pop up a dialog and download files
void GUI_App::request_new_version(int by_user)
{
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version_info.version_str));
evt->SetInt(by_user);
GUI::wxGetApp().QueueEvent(evt);
}
void GUI_App::enter_force_upgrade()
{
wxCommandEvent *evt = new wxCommandEvent(EVT_ENTER_FORCE_UPGRADE);
GUI::wxGetApp().QueueEvent(evt);
}
void GUI_App::set_skip_version(bool skip)
{
BOOST_LOG_TRIVIAL(info) << "set_skip_version, skip = " << skip << ", version = " <<version_info.version_str;
if (skip) {
app_config->set("skip_version", version_info.version_str);
}else {
app_config->set("skip_version", "");
}
}
void GUI_App::show_check_privacy_dlg(wxCommandEvent& evt)
{
int online_login = evt.GetInt();
PrivacyUpdateDialog privacy_dlg(this->mainframe, wxID_ANY, _L("Privacy Policy Update"));
privacy_dlg.Bind(EVT_PRIVACY_UPDATE_CONFIRM, [this, online_login](wxCommandEvent &e) {
app_config->set("privacy_version", privacy_version_info.version_str);
app_config->set_bool("privacy_update_checked", true);
request_user_handle(online_login);
});
privacy_dlg.Bind(EVT_PRIVACY_UPDATE_CANCEL, [this](wxCommandEvent &e) {
app_config->set_bool("privacy_update_checked", false);
if (m_agent) {
m_agent->user_logout();
}
});
privacy_dlg.set_text(privacy_version_info.description);
privacy_dlg.on_show();
}
void GUI_App::on_show_check_privacy_dlg(int online_login)
{
auto evt = new wxCommandEvent(EVT_CHECK_PRIVACY_SHOW);
evt->SetInt(online_login);
wxQueueEvent(this, evt);
}
bool GUI_App::check_privacy_update()
{
if (privacy_version_info.version_str.empty() || privacy_version_info.description.empty()
|| privacy_version_info.url.empty()) {
return false;
}
std::string local_privacy_ver = app_config->get("privacy_version");
auto curr_version = Semver::parse(local_privacy_ver);
auto remote_version = Semver::parse(privacy_version_info.version_str);
if (curr_version && remote_version) {
if (*remote_version > *curr_version || app_config->get("privacy_update_checked") != "true") {
return true;
}
}
return false;
}
void GUI_App::on_check_privacy_update(wxCommandEvent& evt)
{
int online_login = evt.GetInt();
bool result = check_privacy_update();
if (result)
on_show_check_privacy_dlg(online_login);
else
request_user_handle(online_login);
}
void GUI_App::check_privacy_version(int online_login)
{
update_http_extra_header();
std::string query_params = "?policy/privacy=00.00.00.00";
std::string url = get_http_url(app_config->get_country_code()) + query_params;
Slic3r::Http http = Slic3r::Http::get(url);
http.header("accept", "application/json")
.timeout_connect(TIMEOUT_CONNECT)
.timeout_max(TIMEOUT_RESPONSE)
.on_complete([this, online_login](std::string body, unsigned) {
try {
json j = json::parse(body);
if (j.contains("message")) {
if (j["message"].get<std::string>() == "success") {
if (j.contains("resources")) {
for (auto it = j["resources"].begin(); it != j["resources"].end(); it++) {
if (it->contains("type")) {
if ((*it)["type"] == std::string("policy/privacy")
&& it->contains("version")
&& it->contains("description")
&& it->contains("url")
&& it->contains("force_update")) {
privacy_version_info.version_str = (*it)["version"].get<std::string>();
privacy_version_info.description = (*it)["description"].get<std::string>();
privacy_version_info.url = (*it)["url"].get<std::string>();
privacy_version_info.force_upgrade = (*it)["force_update"].get<bool>();
break;
}
}
}
CallAfter([this, online_login]() {
auto evt = new wxCommandEvent(EVT_CHECK_PRIVACY_VER);
evt->SetInt(online_login);
wxQueueEvent(this, evt);
});
}
}
}
}
catch (...) {
request_user_handle(online_login);
}
})
.on_error([this, online_login](std::string body, std::string error, unsigned int status) {
request_user_handle(online_login);
BOOST_LOG_TRIVIAL(error) << "check privacy version error" << body;
}).perform();
}
void GUI_App::no_new_version()
{
wxCommandEvent* evt = new wxCommandEvent(EVT_SHOW_NO_NEW_VERSION);
GUI::wxGetApp().QueueEvent(evt);
}
std::string GUI_App::version_display = "";
std::string GUI_App::format_display_version()
{
if (!version_display.empty()) return version_display;
version_display = SoftFever_VERSION;
return version_display;
}
std::string GUI_App::format_IP(const std::string& ip)
{
std::string format_ip = ip;
size_t pos_st = 0;
size_t pos_en = 0;
for (int i = 0; i < 2; i++) {
pos_en = format_ip.find('.', pos_st + 1);
if (pos_en == std::string::npos) {
return ip;
}
format_ip.replace(pos_st, pos_en - pos_st, "***");
pos_st = pos_en + 1;
}
return format_ip;
}
void GUI_App::show_dialog(wxString msg)
{
if (m_info_dialog_content.empty()) {
wxCommandEvent* evt = new wxCommandEvent(EVT_SHOW_DIALOG);
evt->SetString(msg);
GUI::wxGetApp().QueueEvent(evt);
m_info_dialog_content = msg;
}
}
void GUI_App::push_notification(wxString msg, wxString title, UserNotificationStyle style)
{
if (!this->is_enable_multi_machine()) {
if (style == UserNotificationStyle::UNS_NORMAL) {
if (m_info_dialog_content.empty()) {
wxCommandEvent* evt = new wxCommandEvent(EVT_SHOW_DIALOG);
evt->SetString(msg);
GUI::wxGetApp().QueueEvent(evt);
m_info_dialog_content = msg;
}
}
else if (style == UserNotificationStyle::UNS_WARNING_CONFIRM) {
GUI::wxGetApp().CallAfter([msg, title] {
GUI::MessageDialog msg_dlg(nullptr, msg, title, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
});
}
}
}
void GUI_App::reload_settings()
{
if (preset_bundle && m_agent) {
std::map<std::string, std::map<std::string, std::string>> user_presets;
m_agent->get_user_presets(&user_presets);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " cloud user preset number is: " << user_presets.size();
preset_bundle->load_user_presets(*app_config, user_presets, ForwardCompatibilitySubstitutionRule::Enable);
preset_bundle->save_user_presets(*app_config, get_delete_cache_presets());
mainframe->update_side_preset_ui();
}
}
//BBS reload when logout
void GUI_App::remove_user_presets()
{
if (preset_bundle && m_agent) {
preset_bundle->remove_users_preset(*app_config);
// Not remove user preset cache
//std::string user_id = m_agent->get_user_id();
//preset_bundle->remove_user_presets_directory(user_id);
//update ui
mainframe->update_side_preset_ui();
}
}
void GUI_App::sync_preset(Preset* preset)
{
int result = -1;
unsigned int http_code = 200;
std::string updated_info;
long long update_time = 0;
// only sync user's preset
if (!preset->is_user()) return;
if (preset->is_custom_defined()) return;
auto setting_id = preset->setting_id;
std::map<std::string, std::string> values_map;
if (setting_id.empty() && preset->sync_info.empty()) {
if (m_create_preset_blocked[preset->type])
return;
int ret = preset_bundle->get_differed_values_to_update(*preset, values_map);
if (!ret) {
std::string new_setting_id = m_agent->request_setting_id(preset->name, &values_map, &http_code);
if (!new_setting_id.empty()) {
setting_id = new_setting_id;
result = 0;
auto update_time_str = values_map[BBL_JSON_KEY_UPDATE_TIME];
if (!update_time_str.empty())
update_time = std::atoll(update_time_str.c_str());
}
else {
BOOST_LOG_TRIVIAL(trace) << "[sync_preset]init: request_setting_id failed, http code "<<http_code;
// do not post new preset this time if http code >= 400
if (http_code >= 400) {
result = 0;
updated_info = "hold";
} else
result = -1;
}
}
else {
BOOST_LOG_TRIVIAL(trace) << "[sync_preset]init: can not generate differed key-values";
result = 0;
updated_info = "hold";
}
}
else if (preset->sync_info.compare("create") == 0) {
if (m_create_preset_blocked[preset->type])
return;
int ret = preset_bundle->get_differed_values_to_update(*preset, values_map);
if (!ret) {
std::string new_setting_id = m_agent->request_setting_id(preset->name, &values_map, &http_code);
if (!new_setting_id.empty()) {
setting_id = new_setting_id;
result = 0;
auto update_time_str = values_map[BBL_JSON_KEY_UPDATE_TIME];
if (!update_time_str.empty())
update_time = std::atoll(update_time_str.c_str());
} else {
BOOST_LOG_TRIVIAL(trace) << "[sync_preset]create: request_setting_id failed, http code "<<http_code;
// do not post new preset this time if http code >= 400
if (http_code >= 400) {
result = 0;
updated_info = "hold";
}
else
result = -1;
}
} else {
BOOST_LOG_TRIVIAL(trace) << "[sync_preset]create: can not generate differed preset";
}
} else if (preset->sync_info.compare("update") == 0) {
if (!setting_id.empty()) {
int ret = preset_bundle->get_differed_values_to_update(*preset, values_map);
if (!ret) {
if (auto iter = values_map.find(BBL_JSON_KEY_BASE_ID); iter != values_map.end() && iter->second == setting_id) {
//clear the setting_id in this case ???
setting_id.clear();
result = 0;
}
else {
result = m_agent->put_setting(setting_id, preset->name, &values_map, &http_code);
if (http_code >= 400) {
result = 0;
updated_info = "hold";
BOOST_LOG_TRIVIAL(error) << "[sync_preset] put setting_id = " << setting_id << " failed, http_code = " << http_code;
} else {
auto update_time_str = values_map[BBL_JSON_KEY_UPDATE_TIME];
if (!update_time_str.empty())
update_time = std::atoll(update_time_str.c_str());
}
}
}
else {
BOOST_LOG_TRIVIAL(trace) << "[sync_preset]update: can not generate differed key-values, we need to skip this preset "<< preset->name;
result = 0;
}
}
else {
//clear the sync_info
result = 0;
}
}
if (http_code >= 400 && values_map["code"] == "14") { // Limit
m_create_preset_blocked[preset->type] = true;
CallAfter([this] {
plater()->get_notification_manager()->push_notification(NotificationType::BBLUserPresetExceedLimit);
static bool dialog_notified = false;
if (dialog_notified)
return;
dialog_notified = true;
if (mainframe == nullptr)
return;
auto msg = _L("The number of user presets cached in the cloud has exceeded the upper limit, newly created user presets can only be used locally.");
MessageDialog(mainframe, msg, _L("Sync user presets"), wxICON_WARNING | wxOK).ShowModal();
});
return; // this error not need hold, and should not hold
}
// update sync_info preset info in file
if (result == 0) {
//PresetBundle* preset_bundle = wxGetApp().preset_bundle;
if (!this->preset_bundle) return;
BOOST_LOG_TRIVIAL(trace) << "sync_preset: sync operation: " << preset->sync_info << " success! preset = " << preset->name;
if (preset->type == Preset::Type::TYPE_FILAMENT) {
preset_bundle->filaments.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time);
} else if (preset->type == Preset::Type::TYPE_PRINT) {
preset_bundle->prints.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time);
} else if (preset->type == Preset::Type::TYPE_PRINTER) {
preset_bundle->printers.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time);
}
}
}
void GUI_App::start_sync_user_preset(bool with_progress_dlg)
{
if (app_config->get("stealth_mode") == "true")
return;
if (!m_agent || !m_agent->is_user_login()) return;
// has already start sync
if (m_user_sync_token) return;
ProgressFn progressFn;
WasCancelledFn cancelFn;
std::function<void(bool)> finishFn;
BOOST_LOG_TRIVIAL(info) << "start_sync_service...";
// BBS
m_user_sync_token.reset(new int(0));
if (with_progress_dlg) {
auto dlg = new ProgressDialog(_L("Loading"), "", 100, this->mainframe, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
dlg->Update(0, _L("Loading user preset"));
progressFn = [this, dlg](int percent) {
CallAfter([=]{
dlg->Update(percent, _L("Loading user preset"));
});
};
cancelFn = [this, dlg]() {
return m_is_closing || dlg->WasCanceled();
};
finishFn = [this, userid = m_agent->get_user_id(), dlg, t = std::weak_ptr<int>(m_user_sync_token)](bool ok) {
CallAfter([=]{
dlg->Destroy();
if (ok && m_agent && t.lock() == m_user_sync_token && userid == m_agent->get_user_id()) reload_settings();
});
};
}
else {
finishFn = [this, userid = m_agent->get_user_id(), t = std::weak_ptr<int>(m_user_sync_token)](bool ok) {
CallAfter([=] {
if (ok && m_agent && t.lock() == m_user_sync_token && userid == m_agent->get_user_id()) reload_settings();
});
};
}
m_sync_update_thread = Slic3r::create_thread(
[this, progressFn, cancelFn, finishFn, t = std::weak_ptr<int>(m_user_sync_token)] {
// get setting list, update setting list
std::string version = preset_bundle->get_vendor_profile_version(PresetBundle::BBL_BUNDLE).to_string();
int ret = m_agent->get_setting_list2(version, [this](auto info) {
auto type = info[BBL_JSON_KEY_TYPE];
auto name = info[BBL_JSON_KEY_NAME];
auto setting_id = info[BBL_JSON_KEY_SETTING_ID];
auto update_time_str = info[BBL_JSON_KEY_UPDATE_TIME];
long long update_time = 0;
if (!update_time_str.empty())
update_time = std::atoll(update_time_str.c_str());
if (type == "filament") {
return preset_bundle->filaments.need_sync(name, setting_id, update_time);
} else if (type == "print") {
return preset_bundle->prints.need_sync(name, setting_id, update_time);
} else if (type == "printer") {
return preset_bundle->printers.need_sync(name, setting_id, update_time);
} else {
return true;
}
}, progressFn, cancelFn);
finishFn(ret == 0);
int count = 0, sync_count = 0;
std::vector<Preset> presets_to_sync;
while (!t.expired()) {
count++;
if (count % 20 == 0) {
if (m_agent) {
if (!m_agent->is_user_login()) {
continue;
}
//sync preset
if (!preset_bundle) continue;
int total_count = 0;
sync_count = preset_bundle->prints.get_user_presets(preset_bundle, presets_to_sync);
if (sync_count > 0) {
for (Preset& preset : presets_to_sync) {
sync_preset(&preset);
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
}
}
total_count += sync_count;
sync_count = preset_bundle->filaments.get_user_presets(preset_bundle, presets_to_sync);
if (sync_count > 0) {
for (Preset& preset : presets_to_sync) {
sync_preset(&preset);
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
}
}
total_count += sync_count;
sync_count = preset_bundle->printers.get_user_presets(preset_bundle, presets_to_sync);
if (sync_count > 0) {
for (Preset& preset : presets_to_sync) {
sync_preset(&preset);
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
}
}
total_count += sync_count;
if (total_count == 0) {
CallAfter([this] {
if (!m_is_closing)
plater()->get_notification_manager()->close_notification_of_type(NotificationType::BBLUserPresetExceedLimit);
});
}
unsigned int http_code = 200;
/* get list witch need to be deleted*/
std::vector<string> delete_cache_presets = get_delete_cache_presets_lock();
for (auto it = delete_cache_presets.begin(); it != delete_cache_presets.end();) {
if ((*it).empty()) continue;
std::string del_setting_id = *it;
int result = m_agent->delete_setting(del_setting_id);
if (result == 0) {
preset_deleted_from_cloud(del_setting_id);
it = delete_cache_presets.erase(it);
m_create_preset_blocked = { false, false, false, false, false, false };
BOOST_LOG_TRIVIAL(trace) << "sync_preset: sync operation: delete success! setting id = " << del_setting_id;
}
else {
BOOST_LOG_TRIVIAL(info) << "delete setting = " <<del_setting_id << " failed";
it++;
}
}
}
} else {
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
}
}
});
}
void GUI_App::stop_sync_user_preset()
{
if (!m_user_sync_token)
return;
m_user_sync_token.reset();
if (m_sync_update_thread.joinable()) {
if (m_is_closing)
m_sync_update_thread.join();
else
m_sync_update_thread.detach();
}
}
void GUI_App::start_http_server()
{
if (!m_http_server.is_started())
m_http_server.start();
}
void GUI_App::stop_http_server()
{
m_http_server.stop();
}
void GUI_App::switch_staff_pick(bool on)
{
mainframe->m_webview->SendDesignStaffpick(on);
}
bool GUI_App::switch_language()
{
if (select_language()) {
recreate_GUI(_L("Switching application language") + dots);
return true;
} else {
return false;
}
}
#ifdef __linux__
static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguageInfo* language,
const wxLanguageInfo* system_language)
{
constexpr size_t max_len = 50;
char path[max_len] = "";
std::vector<std::string> locales;
const std::string lang_prefix = into_u8(language->CanonicalName.BeforeFirst('_'));
// Call locale -a so we can parse the output to get the list of available locales
// We expect lines such as "en_US.utf8". Pick ones starting with the language code
// we are switching to. Lines with different formatting will be removed later.
FILE* fp = popen("locale -a", "r");
if (fp != NULL) {
while (fgets(path, max_len, fp) != NULL) {
std::string line(path);
line = line.substr(0, line.find('\n'));
if (boost::starts_with(line, lang_prefix))
locales.push_back(line);
}
pclose(fp);
}
// locales now contain all candidates for this language.
// Sort them so ones containing anything about UTF-8 are at the end.
std::sort(locales.begin(), locales.end(), [](const std::string& a, const std::string& b)
{
auto has_utf8 = [](const std::string & s) {
auto S = boost::to_upper_copy(s);
return S.find("UTF8") != std::string::npos || S.find("UTF-8") != std::string::npos;
};
return ! has_utf8(a) && has_utf8(b);
});
// Remove the suffix behind a dot, if there is one.
for (std::string& s : locales)
s = s.substr(0, s.find("."));
// We just hope that dear Linux "locale -a" returns country codes
// in ISO 3166-1 alpha-2 code (two letter) format.
// https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes
// To be sure, remove anything not looking as expected
// (any number of lowercase letters, underscore, two uppercase letters).
locales.erase(std::remove_if(locales.begin(),
locales.end(),
[](const std::string& s) {
return ! std::regex_match(s,
std::regex("^[a-z]+_[A-Z]{2}$"));
}),
locales.end());
// Is there a candidate matching a country code of a system language? Move it to the end,
// while maintaining the order of matches, so that the best match ends up at the very end.
std::string temp_local = into_u8(system_language->CanonicalName.AfterFirst('_'));
if (temp_local.size() >= 2) {
temp_local = temp_local.substr(0, 2);
}
std::string system_country = "_" + temp_local;
int cnt = locales.size();
for (int i=0; i<cnt; ++i)
if (locales[i].find(system_country) != std::string::npos) {
locales.emplace_back(std::move(locales[i]));
locales[i].clear();
}
// Now try them one by one.
for (auto it = locales.rbegin(); it != locales.rend(); ++ it)
if (! it->empty()) {
const std::string &locale = *it;
const wxLanguageInfo* lang = wxLocale::FindLanguageInfo(from_u8(locale));
if (wxLocale::IsAvailable(lang->Language))
return lang;
}
return language;
}
#endif
int GUI_App::GetSingleChoiceIndex(const wxString& message,
const wxString& caption,
const wxArrayString& choices,
int initialSelection)
{
#ifdef _WIN32
wxSingleChoiceDialog dialog(nullptr, message, caption, choices);
dialog.SetBackgroundColour(*wxWHITE);
wxGetApp().UpdateDlgDarkUI(&dialog);
dialog.SetSelection(initialSelection);
return dialog.ShowModal() == wxID_OK ? dialog.GetSelection() : -1;
#else
return wxGetSingleChoiceIndex(message, caption, choices, initialSelection);
#endif
}
// 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 = GetSingleChoiceIndex(_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("language", new_language_info->CanonicalName.ToUTF8().data());
return true;
}
}
return false;
}
// Load gettext translation files and activate them at the start of the application,
// based on the "language" key stored in the application config.
bool GUI_App::load_language(wxString language, bool initial)
{
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: language %2%, initial: %3%") %__FUNCTION__ %language %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("language");
if (! language.empty())
BOOST_LOG_TRIVIAL(info) << boost::format("language provided by OrcaSlicer.conf: %1%") % language;
else {
// Get the system language.
const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage());
if (lang_system != wxLANGUAGE_UNKNOWN) {
m_language_info_system = wxLocale::GetLanguageInfo(lang_system);
#ifdef __WXMSW__
WCHAR wszLanguagesBuffer[LOCALE_NAME_MAX_LENGTH];
::LCIDToLocaleName(LOCALE_USER_DEFAULT, wszLanguagesBuffer, LOCALE_NAME_MAX_LENGTH, 0);
wxString lang(wszLanguagesBuffer);
lang.Replace('-', '_');
if (auto info = wxLocale::FindLanguageInfo(lang))
m_language_info_system = info;
#endif
BOOST_LOG_TRIVIAL(info) << boost::format("System language detected (user locales and such): %1%") % m_language_info_system->CanonicalName.ToUTF8().data();
// BBS set language to app config
app_config->set("language", m_language_info_system->CanonicalName.ToUTF8().data());
} else {
{
// Allocating a temporary locale will switch the default wxTranslations to its internal wxTranslations instance.
wxLocale temp_locale;
temp_locale.Init();
// 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(info) << boost::format("Best translation language detected (may be different from user locales): %1%") %
m_language_info_best->CanonicalName.ToUTF8().data();
app_config->set("language", m_language_info_best->CanonicalName.ToUTF8().data());
}
#ifdef __linux__
wxString lc_all;
if (wxGetEnv("LC_ALL", &lc_all) && !lc_all.IsEmpty()) {
// Best language returned by wxWidgets on Linux apparently does not respect LC_ALL.
// Disregard the "best" suggestion in case LC_ALL is provided.
m_language_info_best = nullptr;
}
#endif
}
}
}
}
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 OrcaSlicer: %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();
// 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;
// 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(info) << "Using Czech dictionaries for Slovak language";
}
#ifdef __linux__
// If we can't find this locale , try to use different one for the language
// instead of just reporting that it is impossible to switch.
if (! wxLocale::IsAvailable(language_info->Language) && m_language_info_system) {
std::string original_lang = into_u8(language_info->CanonicalName);
language_info = linux_get_existing_locale_language(language_info, m_language_info_system);
BOOST_LOG_TRIVIAL(info) << boost::format("Can't switch language to %1% (missing locales). Using %2% instead.")
% original_lang % language_info->CanonicalName.ToUTF8().data();
}
#endif
if (! wxLocale::IsAvailable(language_info->Language)&&initial) {
language_info = wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH_UK);
app_config->set("language", language_info->CanonicalName.ToUTF8().data());
}
else if (initial) {
// bbs supported languages
//TODO: use a global one with Preference
//wxLanguage supported_languages[]{
// wxLANGUAGE_ENGLISH,
// wxLANGUAGE_CHINESE_SIMPLIFIED,
// wxLANGUAGE_GERMAN,
// wxLANGUAGE_FRENCH,
// wxLANGUAGE_SPANISH,
// wxLANGUAGE_SWEDISH,
// wxLANGUAGE_DUTCH,
// wxLANGUAGE_HUNGARIAN,
// wxLANGUAGE_JAPANESE,
// wxLANGUAGE_ITALIAN
//};
//std::string cur_language = app_config->get("language");
//if (cur_language != "") {
// //cleanup the language wrongly set before
// const wxLanguageInfo *langinfo = nullptr;
// bool embedded_language = false;
// int language_num = sizeof(supported_languages) / sizeof(supported_languages[0]);
// for (auto index = 0; index < language_num; index++) {
// langinfo = wxLocale::GetLanguageInfo(supported_languages[index]);
// std::string temp_lan = langinfo->CanonicalName.ToUTF8().data();
// if (cur_language == temp_lan) {
// embedded_language = true;
// break;
// }
// }
// if (!embedded_language)
// app_config->erase("app", "language");
//}
}
if (! wxLocale::IsAvailable(language_info->Language)) {
// Loading the language dictionary failed.
wxString message = "Switching Orca Slicer 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, "Orca Slicer - 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((_L("*") + " ").ToUTF8().data());
HintDatabase::get_instance().reinit();
return true;
}
Tab* GUI_App::get_tab(Preset::Type type)
{
for (Tab* tab: tabs_list)
if (tab->type() == type)
return tab->completed() ? tab : nullptr; // To avoid actions with no-completed Tab
return nullptr;
}
Tab* GUI_App::get_plate_tab()
{
return plate_tab;
}
Tab* GUI_App::get_model_tab(bool part)
{
return model_tabs_list[part ? 1 : 0];
}
Tab* GUI_App::get_layer_tab()
{
return model_tabs_list[2];
}
ConfigOptionMode GUI_App::get_mode()
{
if (!app_config->has("user_mode"))
return comSimple;
//BBS
const auto mode = app_config->get("user_mode");
return mode == "advanced" ? comAdvanced :
mode == "simple" ? comSimple :
mode == "develop" ? comDevelop : comSimple;
}
std::string GUI_App::get_mode_str()
{
if (!app_config->has("user_mode"))
return "simple";
return app_config->get("user_mode");
}
void GUI_App::save_mode(const /*ConfigOptionMode*/int mode)
{
//BBS
const std::string mode_str = mode == comAdvanced ? "advanced" :
mode == comSimple ? "simple" :
mode == comDevelop ? "develop" : "simple";
app_config->set("user_mode", mode_str);
update_mode();
}
// Update view mode according to selected menu
void GUI_App::update_mode()
{
sidebar().update_mode();
//BBS: GUI refactor
if (mainframe->m_param_panel)
mainframe->m_param_panel->update_mode();
if (mainframe->m_param_dialog)
mainframe->m_param_dialog->panel()->update_mode();
mainframe->m_webview->update_mode();
#ifdef _MSW_DARK_MODE
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(mainframe->m_tabpanel)->UpdateMode();
#endif
for (auto tab : tabs_list)
tab->update_mode();
for (auto tab : model_tabs_list)
tab->update_mode();
//BBS plater()->update_menus();
plater()->canvas3D()->update_gizmos_on_off_state();
}
void GUI_App::update_internal_development() {
mainframe->m_webview->update_mode();
}
void GUI_App::show_ip_address_enter_dialog(wxString title)
{
auto evt = new wxCommandEvent(EVT_SHOW_IP_DIALOG);
evt->SetString(title);
wxQueueEvent(this, evt);
}
bool GUI_App::show_modal_ip_address_enter_dialog(wxString title)
{
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) return false;
if (!dev->get_selected_machine()) return false;
auto obj = dev->get_selected_machine();
InputIpAddressDialog dlg(nullptr);
dlg.set_machine_obj(obj);
if (!title.empty()) dlg.update_title(title);
dlg.Bind(EVT_ENTER_IP_ADDRESS, [this, obj](wxCommandEvent& e) {
auto selection_data_arr = wxSplit(e.GetString().ToStdString(), '|');
if (selection_data_arr.size() == 2) {
auto ip_address = selection_data_arr[0];
auto access_code = selection_data_arr[1];
BOOST_LOG_TRIVIAL(info) << "User enter IP address is " << ip_address;
if (!ip_address.empty()) {
wxGetApp().app_config->set_str("ip_address", obj->dev_id, ip_address.ToStdString());
wxGetApp().app_config->save();
obj->dev_ip = ip_address.ToStdString();
obj->set_user_access_code(access_code.ToStdString());
}
}
});
if (dlg.ShowModal() == wxID_YES) {
return true;
}
return false;
}
void GUI_App::show_ip_address_enter_dialog_handler(wxCommandEvent& evt)
{
wxString title = evt.GetString();
show_modal_ip_address_enter_dialog(title);
}
//void GUI_App::add_config_menu(wxMenuBar *menu)
//void GUI_App::add_config_menu(wxMenu *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("Open %s"))) % config_wizard_name).str());
// // Cmd+, is standard on OS X - what about other operating systems?
// if (is_editor()) {
// local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip);
// local_menu->Append(config_id_base + ConfigMenuUpdate, _L("Check for Configuration 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"));
// wxMenu* mode_menu = nullptr;
// if (is_editor()) {
// local_menu->AppendSeparator();
// mode_menu = new wxMenu();
// mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _L("Simple"), _L("Simple Mode"));
// mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _L("Advanced"), _L("Advanced 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);
//
// local_menu->AppendSubMenu(mode_menu, _L("Mode"), wxString::Format(_L("%s Mode"), SLIC3R_APP_NAME));
// }
// local_menu->AppendSeparator();
// local_menu->Append(config_id_base + ConfigMenuLanguage, _L("Language"));
// if (is_editor()) {
// local_menu->AppendSeparator();
// }
//
// 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;
//#ifdef __linux__
// case ConfigMenuDesktopIntegration:
// show_desktop_integration_dialog();
// break;
//#endif
// case ConfigMenuSnapshots:
// //BBS do not support task snapshot
// break;
// case ConfigMenuPreferences:
// {
// //BBS GUI refactor: remove unuse layout logic
// //bool app_layout_changed = false;
// {
// // the dialog needs to be destroyed before the call to recreate_GUI()
// // or sometimes the application crashes into wxDialogBase() destructor
// // so we put it into an inner scope
// PreferencesDialog dlg(mainframe);
// dlg.ShowModal();
// //BBS GUI refactor: remove unuse layout logic
// //app_layout_changed = dlg.settings_layout_changed();
// if (dlg.seq_top_layer_only_changed())
// this->plater_->refresh_print();
//
// if (dlg.recreate_GUI()) {
// recreate_GUI(_L("Restart application") + dots);
// return;
// }
//#ifdef _WIN32
// if (is_editor()) {
// if (app_config->get("associate_3mf") == "true")
// associate_3mf_files();
// if (app_config->get("associate_stl") == "true")
// associate_stl_files();
// }
// else {
// if (app_config->get("associate_gcode") == "true")
// associate_gcode_files();
// }
//#endif // _WIN32
// }
// //BBS GUI refactor: remove unuse layout logic
// /*if (app_layout_changed) {
// // hide full main_sizer for mainFrame
// mainframe->GetSizer()->Show(false);
// mainframe->update_layout();
// mainframe->select_tab(size_t(0));
// }*/
// 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
// wxString title = is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME);
// title += " - " + _L("Choose language");
// //wxMessageDialog dialog(nullptr,
// MessageDialog dialog(nullptr,
// _L("Switching the language requires application restart.\n") + "\n\n" +
// _L("Do you want to continue?"),
// title,
// wxICON_QUESTION | wxOK | wxCANCEL);
// if (dialog.ShowModal() == wxID_CANCEL)
// return;
// }
//
// switch_language();
// break;
// }
// case ConfigMenuFlashFirmware:
// //BBS FirmwareDialog::run(mainframe);
// break;
// default:
// break;
// }
// });
//
// using std::placeholders::_1;
//
// if (mode_menu != nullptr) {
// 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);
// }
//
// // BBS
// //menu->Append(local_menu, _L("Configuration"));
// menu->AppendSubMenu(local_menu, _L("Configuration"));
//}
void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_option)
{
bool app_layout_changed = false;
{
// the dialog needs to be destroyed before the call to recreate_GUI()
// or sometimes the application crashes into wxDialogBase() destructor
// so we put it into an inner scope
PreferencesDialog dlg(mainframe, open_on_tab, highlight_option);
dlg.ShowModal();
this->plater_->get_current_canvas3D()->force_set_focus();
// BBS
//app_layout_changed = dlg.settings_layout_changed();
#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed())
#else
if (dlg.seq_top_layer_only_changed())
#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
this->plater_->refresh_print();
#ifdef _WIN32
if (is_editor()) {
if (app_config->get("associate_3mf") == "true")
associate_files(L"3mf");
if (app_config->get("associate_stl") == "true")
associate_files(L"stl");
if (app_config->get("associate_step") == "true") {
associate_files(L"step");
associate_files(L"stp");
}
associate_url(L"orcaslicer");
}
else {
if (app_config->get("associate_gcode") == "true")
associate_files(L"gcode");
}
#endif // _WIN32
}
// BBS
/*
if (app_layout_changed) {
// hide full main_sizer for mainFrame
mainframe->GetSizer()->Show(false);
mainframe->update_layout();
mainframe->select_tab(size_t(0));
}*/
}
bool GUI_App::has_unsaved_preset_changes() const
{
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
for (const Tab* const tab : tabs_list) {
if (tab->supports_printer_technology(printer_technology) && tab->saved_preset_is_dirty())
return true;
}
return false;
}
bool GUI_App::has_current_preset_changes() const
{
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
for (const Tab* const tab : tabs_list) {
if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty())
return true;
}
return false;
}
void GUI_App::update_saved_preset_from_current_preset()
{
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
for (Tab* tab : tabs_list) {
if (tab->supports_printer_technology(printer_technology))
tab->update_saved_preset_from_current_preset();
}
}
std::vector<std::pair<unsigned int, std::string>> GUI_App::get_selected_presets() const
{
std::vector<std::pair<unsigned int, std::string>> ret;
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
for (Tab* tab : tabs_list) {
if (tab->supports_printer_technology(printer_technology)) {
const PresetCollection* presets = tab->get_presets();
ret.push_back({ static_cast<unsigned int>(presets->type()), presets->get_selected_preset_name() });
}
}
return ret;
}
// To notify the user whether he is aware that some preset changes will be lost,
// UnsavedChangesDialog: "Discard / Save / Cancel"
// This is called when:
// - Close Application & Current project isn't saved
// - Load Project & Current project isn't saved
// - Undo / Redo with change of print technologie
// - Loading snapshot
// - Loading config_file/bundle
// UnsavedChangesDialog: "Don't save / Save / Cancel"
// This is called when:
// - Exporting config_bundle
// - Taking snapshot
bool GUI_App::check_and_save_current_preset_changes(const wxString& caption, const wxString& header, bool remember_choice/* = true*/, bool dont_save_insted_of_discard/* = false*/)
{
if (has_current_preset_changes()) {
int act_buttons = ActionButtons::SAVE;
if (dont_save_insted_of_discard)
act_buttons |= ActionButtons::DONT_SAVE;
if (remember_choice)
act_buttons |= ActionButtons::REMEMBER_CHOISE;
UnsavedChangesDialog dlg(caption, header, "", act_buttons);
if (dlg.ShowModal() == wxID_CANCEL)
return false;
if (dlg.save_preset()) // save selected changes
{
//BBS: add project embedded preset relate logic
for (const UnsavedChangesDialog::PresetData& nt : dlg.get_names_and_types())
preset_bundle->save_changes_for_preset(nt.name, nt.type, dlg.get_unselected_options(nt.type), nt.save_to_project);
//for (const std::pair<std::string, Preset::Type>& nt : dlg.get_names_and_types())
// preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second));
load_current_presets(false);
// if we saved changes to the new presets, we should to
// synchronize config.ini with the current selections.
preset_bundle->export_selections(*app_config);
//MessageDialog(nullptr, _L_PLURAL("Modifications to the preset have been saved",
// "Modifications to the presets have been saved", dlg.get_names_and_types().size())).ShowModal();
}
}
return true;
}
void GUI_App::apply_keeped_preset_modifications()
{
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
for (Tab* tab : tabs_list) {
if (tab->supports_printer_technology(printer_technology))
tab->apply_config_from_cache();
}
load_current_presets(false);
}
// This is called when creating new project or load another project
// OR close ConfigWizard
// to ask the user what should we do with unsaved changes for presets.
// New Project => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Cancel"
// => Current project isn't saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel"
// Close ConfigWizard => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel"
// Note: no_nullptr postponed_apply_of_keeped_changes indicates that thie function is called after ConfigWizard is closed
bool GUI_App::check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes/* = nullptr*/)
{
if (has_current_preset_changes()) {
bool is_called_from_configwizard = postponed_apply_of_keeped_changes != nullptr;
UnsavedChangesDialog dlg(caption, header, "", action_buttons);
if (dlg.ShowModal() == wxID_CANCEL)
return false;
auto reset_modifications = [this, is_called_from_configwizard]() {
//if (is_called_from_configwizard)
// return; // no need to discared changes. It will be done fromConfigWizard closing
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
for (const Tab* const tab : tabs_list) {
if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty())
tab->m_presets->discard_current_changes();
}
load_current_presets(false);
};
if (dlg.discard())
reset_modifications();
else // save selected changes
{
//BBS: add project embedded preset relate logic
const auto& preset_names_and_types = dlg.get_names_and_types();
if (dlg.save_preset()) {
for (const UnsavedChangesDialog::PresetData& nt : preset_names_and_types)
preset_bundle->save_changes_for_preset(nt.name, nt.type, dlg.get_unselected_options(nt.type), nt.save_to_project);
// if we saved changes to the new presets, we should to
// synchronize config.ini with the current selections.
preset_bundle->export_selections(*app_config);
//wxString text = _L_PLURAL("Modifications to the preset have been saved",
// "Modifications to the presets have been saved", preset_names_and_types.size());
//if (!is_called_from_configwizard)
// text += "\n\n" + _L("All modifications will be discarded for new project.");
//MessageDialog(nullptr, text).ShowModal();
reset_modifications();
}
else if (dlg.transfer_changes() && (dlg.has_unselected_options() || is_called_from_configwizard)) {
// execute this part of code only if not all modifications are keeping to the new project
// OR this function is called when ConfigWizard is closed and "Keep modifications" is selected
for (const UnsavedChangesDialog::PresetData& nt : preset_names_and_types) {
Preset::Type type = nt.type;
Tab* tab = get_tab(type);
std::vector<std::string> selected_options = dlg.get_selected_options(type);
if (type == Preset::TYPE_PRINTER) {
auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count");
if (it != selected_options.end()) {
// erase "extruders_count" option from the list
selected_options.erase(it);
// cache the extruders count
static_cast<TabPrinter*>(tab)->cache_extruder_cnt();
}
}
std::vector<std::string> selected_options2;
std::transform(selected_options.begin(), selected_options.end(), std::back_inserter(selected_options2), [](auto & o) {
auto i = o.find('#');
return i != std::string::npos ? o.substr(0, i) : o;
});
tab->cache_config_diff(selected_options2);
if (!is_called_from_configwizard)
tab->m_presets->discard_current_changes();
}
if (is_called_from_configwizard)
*postponed_apply_of_keeped_changes = true;
else
apply_keeped_preset_modifications();
}
}
}
return true;
}
bool GUI_App::can_load_project()
{
return true;
}
bool GUI_App::check_print_host_queue()
{
wxString dirty;
std::vector<std::pair<std::string, std::string>> jobs;
// Get ongoing jobs from dialog
mainframe->m_printhost_queue_dlg->get_active_jobs(jobs);
if (jobs.empty())
return true;
// Show dialog
wxString job_string = wxString();
for (const auto& job : jobs) {
job_string += format_wxstr(" %1% : %2% \n", job.first, job.second);
}
wxString message;
message += _(L("The uploads are still ongoing")) + ":\n\n" + job_string +"\n" + _(L("Stop them and continue anyway?"));
//wxMessageDialog dialog(mainframe,
MessageDialog dialog(mainframe,
message,
wxString(SLIC3R_APP_NAME) + " - " + _(L("Ongoing uploads")),
wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT);
if (dialog.ShowModal() == wxID_YES)
return true;
// TODO: If already shown, bring forward
mainframe->m_printhost_queue_dlg->Show();
return false;
}
bool GUI_App::checked_tab(Tab* tab)
{
bool ret = true;
if (find(tabs_list.begin(), tabs_list.end(), tab) == tabs_list.end() &&
find(model_tabs_list.begin(), model_tabs_list.end(), tab) == model_tabs_list.end())
ret = false;
return ret;
}
// Update UI / Tabs to reflect changes in the currently loaded presets
//BBS: add preset combo box re-activate logic
void GUI_App::load_current_presets(bool active_preset_combox/*= false*/, bool check_printer_presets_ /*= true*/)
{
// check printer_presets for the containing information about "Print Host upload"
// and create physical printer from it, if any exists
if (check_printer_presets_)
check_printer_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();
//BBS: add preset combox re-active logic
if (active_preset_combox)
tab->reactive_preset_combo_box();
}
// BBS: model config
for (Tab *tab : model_tabs_list)
if (tab->supports_printer_technology(printer_technology)) {
tab->rebuild_page_tree();
}
}
static std::mutex mutex_delete_cache_presets;
std::vector<std::string> & GUI_App::get_delete_cache_presets()
{
return need_delete_presets;
}
std::vector<std::string> GUI_App::get_delete_cache_presets_lock()
{
std::scoped_lock l(mutex_delete_cache_presets);
return need_delete_presets;
}
void GUI_App::delete_preset_from_cloud(std::string setting_id)
{
std::scoped_lock l(mutex_delete_cache_presets);
need_delete_presets.push_back(setting_id);
}
void GUI_App::preset_deleted_from_cloud(std::string setting_id)
{
std::scoped_lock l(mutex_delete_cache_presets);
need_delete_presets.erase(std::remove(need_delete_presets.begin(), need_delete_presets.end(), setting_id), need_delete_presets.end());
}
wxString GUI_App::filter_string(wxString str)
{
std::string result = str.utf8_string();
std::string input = str.utf8_string();
std::regex domainRegex(R"(([a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(?:\.[a-zA-Z]{2,})?))");
std::sregex_iterator it(input.begin(), input.end(), domainRegex);
std::sregex_iterator end;
while (it != end) {
std::smatch match = *it;
std::string domain = match.str();
result.replace(match.position(), domain.length(), "[***]");
++it;
}
return wxString::FromUTF8(result);
}
bool GUI_App::OnExceptionInMainLoop()
{
generic_exception_handle();
return false;
}
#ifdef __APPLE__
// This callback is called from wxEntry()->wxApp::CallOnInit()->NSApplication run
// that is, before GUI_App::OnInit(), so we have a chance to switch GUI_App
// to a G-code viewer.
void GUI_App::OSXStoreOpenFiles(const wxArrayString &fileNames)
{
//BBS: remove GCodeViewer as seperate APP logic
/*size_t num_gcodes = 0;
for (const wxString &filename : fileNames)
if (is_gcode_file(into_u8(filename)))
++ num_gcodes;
if (fileNames.size() == num_gcodes) {
// Opening PrusaSlicer by drag & dropping a G-Code onto OrcaSlicer icon in Finder,
// just G-codes were passed. Switch to G-code viewer mode.
m_app_mode = EAppMode::GCodeViewer;
unlock_lockfile(get_instance_hash_string() + ".lock", data_dir() + "/cache/");
if(app_config != nullptr)
delete app_config;
app_config = nullptr;
init_app_config();
}*/
wxApp::OSXStoreOpenFiles(fileNames);
}
void GUI_App::MacOpenURL(const wxString& url)
{
if (url.empty())
return;
start_download(boost::nowide::narrow(url));
}
// wxWidgets override to get an event on open files.
void GUI_App::MacOpenFiles(const wxArrayString &fileNames)
{
bool single_instance = app_config->get("app", "single_instance") == "true";
if (m_post_initialized && !single_instance) {
bool has3mf = false;
std::vector<wxString> names;
for (auto & n : fileNames) {
has3mf |= n.EndsWith(".3mf");
names.push_back(n);
}
if (has3mf) {
start_new_slicer(names);
return;
}
}
std::vector<std::string> files;
std::vector<wxString> gcode_files;
std::vector<wxString> non_gcode_files;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", open files, size " << fileNames.size();
for (const auto& filename : fileNames) {
if (is_gcode_file(into_u8(filename)))
gcode_files.emplace_back(filename);
else {
files.emplace_back(into_u8(filename));
non_gcode_files.emplace_back(filename);
}
}
//BBS: remove GCodeViewer as seperate APP logic
/*if (m_app_mode == EAppMode::GCodeViewer) {
// Running in G-code viewer.
// Load the first G-code into the G-code viewer.
// Or if no G-codes, send other files to slicer.
if (! gcode_files.empty())
this->plater()->load_gcode(gcode_files.front());
if (!non_gcode_files.empty())
start_new_slicer(non_gcode_files, true);
} else*/
{
if (! files.empty()) {
if (m_post_initialized) {
wxArrayString input_files;
for (size_t i = 0; i < non_gcode_files.size(); ++i) {
input_files.push_back(non_gcode_files[i]);
}
this->plater()->load_files(input_files);
} else {
for (size_t i = 0; i < files.size(); ++i) {
this->init_params->input_files.emplace_back(files[i]);
}
}
} else {
if (m_post_initialized) {
this->plater()->load_gcode(gcode_files.front());
} else {
this->init_params->input_gcode = true;
this->init_params->input_files = { into_u8(gcode_files.front()) };
}
}
/*for (const wxString &filename : gcode_files)
start_new_gcodeviewer(&filename);*/
}
}
#endif /* __APPLE */
Sidebar& GUI_App::sidebar()
{
return plater_->sidebar();
}
GizmoObjectManipulation *GUI_App::obj_manipul()
{
// If this method is called before plater_ has been initialized, return nullptr (to avoid a crash)
return (plater_ != nullptr) ? &plater_->get_view3D_canvas3D()->get_gizmos_manager().get_object_manipulation() : nullptr;
}
ObjectSettings* GUI_App::obj_settings()
{
return sidebar().obj_settings();
}
ObjectList* GUI_App::obj_list()
{
return sidebar().obj_list();
}
ObjectLayers* GUI_App::obj_layers()
{
return sidebar().obj_layers();
}
Plater* GUI_App::plater()
{
return plater_;
}
const Plater* GUI_App::plater() const
{
return plater_;
}
ParamsPanel* GUI_App::params_panel()
{
if (mainframe)
return mainframe->m_param_panel;
return nullptr;
}
ParamsDialog* GUI_App::params_dialog()
{
if (mainframe)
return mainframe->m_param_dialog;
return nullptr;
}
Model& GUI_App::model()
{
return plater_->model();
}
Downloader* GUI_App::downloader()
{
return m_downloader.get();
}
void GUI_App::load_url(wxString url)
{
if (mainframe)
return mainframe->load_url(url);
}
void GUI_App::open_mall_page_dialog()
{
std::string host_url;
std::string model_url;
std::string link_url;
int result = -1;
//model api url
host_url = get_model_http_url(app_config->get_country_code());
//model url
wxString language_code = this->current_language_code().BeforeFirst('_');
model_url = language_code.ToStdString();
if (getAgent() && mainframe) {
//login already
if (getAgent()->is_user_login()) {
std::string ticket;
result = getAgent()->request_bind_ticket(&ticket);
if(result == 0){
link_url = host_url + "api/sign-in/ticket?to=" + host_url + url_encode(model_url) + "&ticket=" + ticket;
}
}
}
if (result < 0) {
link_url = host_url + model_url;
}
if (link_url.find("?") != std::string::npos) {
link_url += "&from=orcaslicer";
} else {
link_url += "?from=orcaslicer";
}
wxLaunchDefaultBrowser(link_url);
}
void GUI_App::open_publish_page_dialog()
{
std::string host_url;
std::string model_url;
std::string link_url;
int result = -1;
//model api url
host_url = get_model_http_url(app_config->get_country_code());
//publish url
wxString language_code = this->current_language_code().BeforeFirst('_');
model_url += (language_code.ToStdString() + "/my/models/publish");
if (getAgent() && mainframe) {
//login already
if (getAgent()->is_user_login()) {
std::string ticket;
result = getAgent()->request_bind_ticket(&ticket);
if (result == 0) {
link_url = host_url + "api/sign-in/ticket?to=" + host_url + url_encode(model_url) + "&ticket=" + ticket;
}
}
}
if (result < 0) {
link_url = host_url + model_url;
}
wxLaunchDefaultBrowser(link_url);
}
char GUI_App::from_hex(char ch) {
return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
}
std::string GUI_App::url_decode(std::string value) {
return Http::url_decode(value);
}
std::string GUI_App::url_encode(std::string value) {
return Http::url_encode(value);
}
void GUI_App::popup_ping_bind_dialog()
{
if (m_ping_code_binding_dialog == nullptr) {
m_ping_code_binding_dialog = new PingCodeBindDialog();
m_ping_code_binding_dialog->ShowModal();
remove_ping_bind_dialog();
}
}
void GUI_App::remove_ping_bind_dialog()
{
if (m_ping_code_binding_dialog != nullptr) {
m_ping_code_binding_dialog->Destroy();
delete m_mall_publish_dialog;
m_ping_code_binding_dialog = nullptr;
}
}
void GUI_App::remove_mall_system_dialog()
{
if (m_mall_publish_dialog != nullptr) {
m_mall_publish_dialog->Destroy();
delete m_mall_publish_dialog;
}
}
void GUI_App::run_script(wxString js)
{
if (mainframe)
return mainframe->RunScript(js);
}
Notebook* GUI_App::tab_panel() const
{
if (mainframe)
return mainframe->m_tabpanel;
return nullptr;
}
NotificationManager * GUI_App::notification_manager()
{
if (plater_)
return plater_->get_notification_manager();
return nullptr;
}
// extruders count from selected printer preset
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();
}
// BBS
int GUI_App::filaments_cnt() const
{
return preset_bundle->filament_presets.size();
}
PrintSequence GUI_App::global_print_sequence() const
{
PrintSequence global_print_seq = PrintSequence::ByDefault;
auto curr_preset_config = preset_bundle->prints.get_edited_preset().config;
if (curr_preset_config.has("print_sequence"))
global_print_seq = curr_preset_config.option<ConfigOptionEnum<PrintSequence>>("print_sequence")->value;
return global_print_seq;
}
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", },
{ "nl", "nl_NL", },
{ "sv", "sv_SE", },
{ "es", "es_ES", },
{ "fr", "fr_FR", },
{ "it", "it_IT", },
{ "ja", "ja_JP", },
{ "ko", "ko_KR", },
{ "pl", "pl_PL", },
{ "uk", "uk_UA", },
{ "zh", "zh_CN", },
{ "ru", "ru_RU", },
{ "tr", "tr_TR", },
{ "pt", "pt_BR", },
};
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)
{
open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe());
}
// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s).
// Because of we can't to print the multi-part objects with SLA technology.
bool GUI_App::may_switch_to_SLA_preset(const wxString& caption)
{
if (model_has_multi_part_objects(model())) {
// BBS: remove SLA related message
return false;
}
return true;
}
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 (reason == ConfigWizard::RR_USER) {
// //TODO: turn off it currently, maybe need to turn on in the future
// if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED)
// return false;
//}
//auto wizard_t = new ConfigWizard(mainframe);
//const bool res = wizard_t->run(reason, start_page);
std::string strFinish = wxGetApp().app_config->get("firstguide", "finish");
long pStyle = wxCAPTION | wxCLOSE_BOX | wxSYSTEM_MENU;
if (strFinish == "false" || strFinish.empty())
pStyle = wxCAPTION | wxTAB_TRAVERSAL;
GuideFrame wizard(this, pStyle);
auto page = start_page == ConfigWizard::SP_WELCOME ? GuideFrame::BBL_WELCOME :
start_page == ConfigWizard::SP_FILAMENTS ? GuideFrame::BBL_FILAMENT_ONLY :
start_page == ConfigWizard::SP_PRINTERS ? GuideFrame::BBL_MODELS_ONLY :
GuideFrame::BBL_MODELS;
wizard.SetStartPage(page);
bool res = wizard.run();
if (res) {
load_current_presets();
update_publish_status();
mainframe->refresh_plugin_tips();
// BBS: remove SLA related message
}
return res;
}
void GUI_App::show_desktop_integration_dialog()
{
#ifdef __linux__
//wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
DesktopIntegrationDialog dialog(mainframe);
dialog.ShowModal();
#endif //__linux__
}
#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 G-code 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
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();
}
bool GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized)
{
if (name.empty()) { return false; }
const auto config_key = (boost::format("window_%1%") % name).str();
if (! app_config->has(config_key)) {
//window->Maximize(default_maximized);
return false;
}
auto metrics = WindowMetrics::deserialize(app_config->get(config_key));
if (! metrics) {
window->Maximize(default_maximized);
return true;
}
const wxRect& rect = metrics->get_rect();
window->SetPosition(rect.GetPosition());
window->SetSize(rect.GetSize());
window->Maximize(metrics->get_maximized());
return true;
}
void GUI_App::window_pos_sanitize(wxTopLevelWindow* window)
{
/*unsigned*/int display_idx = wxDisplay::GetFromWindow(window);
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());
}
}
void GUI_App::window_pos_center(wxTopLevelWindow *window)
{
/*unsigned*/int display_idx = wxDisplay::GetFromWindow(window);
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.center_for_display(display);
if (window->GetScreenRect() != metrics.get_rect()) {
window->SetSize(metrics.get_rect());
}
}
bool GUI_App::config_wizard_startup()
{
if (!m_app_conf_exists || preset_bundle->printers.only_default_printers()) {
BOOST_LOG_TRIVIAL(info) << "run wizard...";
run_wizard(ConfigWizard::RR_DATA_EMPTY);
BOOST_LOG_TRIVIAL(info) << "finished run wizard";
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(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION);
if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
mainframe->Close();
}
else if (updater_result == PresetUpdater::R_INCOMPAT_CONFIGURED) {
m_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());
}
}
bool GUI_App::open_browser_with_warning_dialog(const wxString& url, int flags/* = 0*/)
{
return wxLaunchDefaultBrowser(url, flags);
}
// 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???
// void GUI_App::notify(message) {
// 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 ?
// }
#ifdef __WXMSW__
static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue)
{
// see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association
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;
if (!bDidntExist) {
if (dwType != REG_SZ)
// invalid type
return false;
if (::wcscmp(szValueCurrent, pszValue) == 0)
// value already set
return false;
}
DWORD dwDisposition;
HKEY hkey;
iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition);
bool ret = false;
if (iRC == ERROR_SUCCESS) {
iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t));
if (iRC == ERROR_SUCCESS)
ret = true;
}
RegCloseKey(hkey);
return ret;
}
static bool del_win_registry(HKEY hkeyHive, const wchar_t *pszVar, const wchar_t *pszValue)
{
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)
return false;
if (!bDidntExist) {
DWORD dwDisposition;
HKEY hkey;
iRC = ::RegDeleteKeyExW(hkeyHive, pszVar, KEY_ALL_ACCESS, 0);
if (iRC == ERROR_SUCCESS) {
return true;
}
}
return false;
}
#endif // __WXMSW__
void GUI_App::associate_files(std::wstring extend)
{
#ifdef WIN32
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" Orca.Slicer.1";
std::wstring prog_desc = L"OrcaSlicer";
std::wstring prog_command = prog_path + L" \"%1\"";
std::wstring reg_base = L"Software\\Classes";
std::wstring reg_extension = reg_base + L"\\." + extend;
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 |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
if (is_new)
// notify Windows only when any of the values gets changed
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
#endif // WIN32
}
void GUI_App::disassociate_files(std::wstring extend)
{
#ifdef WIN32
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" Orca.Slicer.1";
std::wstring prog_desc = L"OrcaSlicer";
std::wstring prog_command = prog_path + L" \"%1\"";
std::wstring reg_base = L"Software\\Classes";
std::wstring reg_extension = reg_base + L"\\." + extend;
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 |= del_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
bool is_associate_3mf = app_config->get("associate_3mf") == "true";
bool is_associate_stl = app_config->get("associate_stl") == "true";
bool is_associate_step = app_config->get("associate_step") == "true";
if (!is_associate_3mf && !is_associate_stl && !is_associate_step)
{
is_new |= del_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
is_new |= del_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
}
if (is_new)
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
#endif // WIN32
}
bool GUI_App::check_url_association(std::wstring url_prefix, std::wstring& reg_bin)
{
reg_bin = L"";
#ifdef WIN32
wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
if (!key_full.Exists()) {
return false;
}
reg_bin = key_full.QueryDefaultValue().ToStdWstring();
boost::filesystem::path binary_path(boost::filesystem::canonical(boost::dll::program_location()));
std::wstring key_string = L"\"" + binary_path.wstring() + L"\" \"%1\"";
return key_string == reg_bin;
#else
return false;
#endif // WIN32
}
void GUI_App::associate_url(std::wstring url_prefix)
{
#ifdef WIN32
boost::filesystem::path binary_path(boost::filesystem::canonical(boost::dll::program_location()));
// the path to binary needs to be correctly saved in string with respect to localized characters
wxString wbinary = wxString::FromUTF8(binary_path.string());
std::string binary_string = (boost::format("%1%") % wbinary).str();
BOOST_LOG_TRIVIAL(info) << "Downloader registration: Path of binary: " << binary_string;
std::string key_string = "\"" + binary_string + "\" \"%1\"";
wxRegKey key_first(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix);
wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
if (!key_first.Exists()) {
key_first.Create(false);
}
key_first.SetValue("URL Protocol", "");
if (!key_full.Exists()) {
key_full.Create(false);
}
key_full = key_string;
#elif defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
DesktopIntegrationDialog::perform_downloader_desktop_integration(boost::nowide::narrow(url_prefix));
#endif // WIN32
}
void GUI_App::disassociate_url(std::wstring url_prefix)
{
#ifdef WIN32
wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
if (!key_full.Exists()) {
return;
}
key_full = "";
#endif // WIN32
}
void GUI_App::start_download(std::string url)
{
if (!plater_) {
BOOST_LOG_TRIVIAL(error) << "Could not start URL download: plater is nullptr.";
return;
}
//lets always init so if the download dest folder was changed, new dest is used
boost::filesystem::path dest_folder(app_config->get("download_path"));
if (dest_folder.empty() || !boost::filesystem::is_directory(dest_folder)) {
std::string msg = _u8L("Could not start URL download. Destination folder is not set. Please choose destination folder in Configuration Wizard.");
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return;
}
m_downloader->init(dest_folder);
m_downloader->start_download(url);
}
bool is_support_filament(int extruder_id)
{
auto &filament_presets = Slic3r::GUI::wxGetApp().preset_bundle->filament_presets;
auto &filaments = Slic3r::GUI::wxGetApp().preset_bundle->filaments;
if (extruder_id >= filament_presets.size()) return false;
Slic3r::Preset *filament = filaments.find_preset(filament_presets[extruder_id]);
if (filament == nullptr) return false;
Slic3r::ConfigOptionBools *support_option = dynamic_cast<Slic3r::ConfigOptionBools *>(filament->config.option("filament_is_support"));
if (support_option == nullptr) return false;
return support_option->get_at(0);
};
} // GUI
} //Slic3r