diff --git a/bbl/i18n/list.txt b/bbl/i18n/list.txt
index d56545aef8..1c153da5c0 100644
--- a/bbl/i18n/list.txt
+++ b/bbl/i18n/list.txt
@@ -168,6 +168,7 @@ src/slic3r/GUI/ObjectDataViewModel.cpp
src/slic3r/GUI/OpenGLManager.cpp
src/slic3r/GUI/OptionsGroup.cpp
src/slic3r/GUI/PrintOptionsDialog.cpp
+src/slic3r/GUI/PrintStatusFrame.cpp
src/slic3r/GUI/SafetyOptionsDialog.cpp
src/slic3r/GUI/ParamsPanel.cpp
src/slic3r/GUI/PartPlate.cpp
@@ -287,4 +288,4 @@ src/slic3r/GUI/UxProgramTermsDialog.hpp
src/slic3r/GUI/HelioHistoryDialog.hpp
src/slic3r/GUI/HelioHistoryDialog.cpp
src/slic3r/GUI/MixedFilamentDialog.cpp
-src/slic3r/GUI/MixedFilamentDialog.hpp
\ No newline at end of file
+src/slic3r/GUI/MixedFilamentDialog.hpp
diff --git a/resources/images/print_status_window.svg b/resources/images/print_status_window.svg
new file mode 100644
index 0000000000..b4174843cc
--- /dev/null
+++ b/resources/images/print_status_window.svg
@@ -0,0 +1,61 @@
+
+
diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp
index cfd11bf26c..e1cb526cf2 100644
--- a/src/libslic3r/AppConfig.cpp
+++ b/src/libslic3r/AppConfig.cpp
@@ -340,6 +340,27 @@ void AppConfig::set_defaults()
set_bool("show_print_history", true);
}
+ if (get("print_status_window_auto_show_on_minimize").empty())
+ set_bool("print_status_window_auto_show_on_minimize", false);
+ if (get("print_status_window_close_to_tray").empty())
+ set_bool("print_status_window_close_to_tray", false);
+ if (get("print_status_window_always_on_top").empty())
+ set_bool("print_status_window_always_on_top", true);
+ if (get("print_status_window_remember_position").empty())
+ set_bool("print_status_window_remember_position", true);
+ if (get("print_status_window_theme").empty())
+ set("print_status_window_theme", "follow_app");
+ if (get("print_status_window_opacity").empty())
+ set("print_status_window_opacity", "100");
+ if (get("print_status_window_pos_x").empty())
+ set("print_status_window_pos_x", "");
+ if (get("print_status_window_pos_y").empty())
+ set("print_status_window_pos_y", "");
+ if (get("print_status_window_width").empty())
+ set("print_status_window_width", "");
+ if (get("print_status_window_height").empty())
+ set("print_status_window_height", "");
+
if (get("show_printable_box").empty()) {
set_bool("show_printable_box", true);
}
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 25a4313212..016fb90081 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -196,6 +196,8 @@ set(SLIC3R_GUI_SOURCES
GUI/ImageDPIFrame.hpp
GUI/Preferences.cpp
GUI/Preferences.hpp
+ GUI/PrintStatusFrame.cpp
+ GUI/PrintStatusFrame.hpp
GUI/AMSSetting.cpp
GUI/AMSSetting.hpp
GUI/AMSDryControl.cpp
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 729d8784d3..65fe3ab17d 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -2908,10 +2908,12 @@ bool GUI_App::on_init_inner()
wxGetApp().Bind(wxEVT_QUERY_END_SESSION, [this](auto & e) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< "received wxEVT_QUERY_END_SESSION";
if (mainframe) {
+ mainframe->set_real_shutdown_requested(true);
wxCloseEvent e2(wxEVT_CLOSE_WINDOW);
e2.SetCanVeto(true);
mainframe->GetEventHandler()->ProcessEvent(e2);
if (e2.GetVeto()) {
+ mainframe->set_real_shutdown_requested(false);
e.Veto();
return;
}
@@ -3193,10 +3195,10 @@ bool GUI_App::on_init_inner()
wxLaunchDefaultBrowser(download_url);
break;
case wxID_NO:
- wxGetApp().mainframe->Close(true);
+ wxGetApp().mainframe->request_app_exit(true);
break;
default:
- wxGetApp().mainframe->Close(true);
+ wxGetApp().mainframe->request_app_exit(true);
}
});
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index 1a37b30945..cef7ff0064 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -8,6 +8,9 @@
#include
#include
#include
+#ifndef __APPLE__
+#include
+#endif
#include
//#include
#include
@@ -42,6 +45,7 @@
// BBS
#include "PartPlate.hpp"
#include "Preferences.hpp"
+#include "PrintStatusFrame.hpp"
#include "Widgets/ProgressDialog.hpp"
#include "BindDialog.hpp"
#include "../Utils/MacDarkMode.hpp"
@@ -135,6 +139,34 @@ class BambuStudioTaskBarIcon : public wxTaskBarIcon
};*/
#endif // __APPLE__
+// Generic Windows/Linux tray icon used only for close-to-tray.
+#ifndef __APPLE__
+class BambuStudioCloseToTrayIcon : public wxTaskBarIcon
+{
+public:
+ explicit BambuStudioCloseToTrayIcon(MainFrame* frame) : m_frame(frame) {}
+
+ wxMenu* CreatePopupMenu() override
+ {
+ auto* menu = new wxMenu;
+ if (m_frame == nullptr)
+ return menu;
+
+ append_menu_item(menu, wxID_ANY, _L("Show Bambu Studio"), _L("Show Bambu Studio"),
+ [this](wxCommandEvent&) { if (m_frame) m_frame->restore_from_tray(); }, "", this);
+ append_menu_item(menu, wxID_ANY, _L("Show Print Status Window"), _L("Show Print Status Window"),
+ [this](wxCommandEvent&) { if (m_frame) m_frame->show_print_status_frame(); }, "", this);
+ menu->AppendSeparator();
+ append_menu_item(menu, wxID_EXIT, _L("Quit"), _L("Quit"),
+ [this](wxCommandEvent&) { if (m_frame) m_frame->request_app_exit(false); }, "", this);
+ return menu;
+ }
+
+private:
+ MainFrame* m_frame { nullptr };
+};
+#endif
+
// Load the icon either from the exe, or from the ico file.
static wxIcon main_frame_icon(GUI_App::EAppMode app_mode)
{
@@ -460,10 +492,18 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
// declare events
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": mainframe received close_widow event";
+ if (should_close_to_tray(event) && ensure_close_to_tray_icon()) {
+ minimize_to_tray();
+ event.Veto();
+ return;
+ }
+
if (event.CanVeto() && m_plater->get_view3D_canvas3D()->get_gizmos_manager().is_in_editing_mode(true)) {
// prevents to open the save dirty project dialog
event.Veto();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< "cancelled by gizmo in editing";
+ if (m_real_shutdown_requested)
+ m_real_shutdown_requested = false;
return;
}
@@ -483,10 +523,14 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
if (event.CanVeto() && ((result = m_plater->close_with_confirm(check)) == wxID_CANCEL)) {
event.Veto();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< "cancelled by close_with_confirm selection";
+ if (m_real_shutdown_requested)
+ m_real_shutdown_requested = false;
return;
}
if (event.CanVeto() && !wxGetApp().check_print_host_queue()) {
event.Veto();
+ if (m_real_shutdown_requested)
+ m_real_shutdown_requested = false;
return;
}
@@ -588,6 +632,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
MarkdownTip::ExitTip();
m_plater->reset();
+ remove_close_to_tray_icon();
this->shutdown();
// propagate event
@@ -596,6 +641,26 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": mainframe finished process close_widow event";
});
+ Bind(wxEVT_ICONIZE, [this](wxIconizeEvent& event) {
+ if (!event.IsIconized()) {
+ event.Skip();
+ return;
+ }
+
+ if (m_real_shutdown_requested || IsBeingDeleted() || wxGetApp().app_config == nullptr) {
+ event.Skip();
+ return;
+ }
+
+ if (wxGetApp().app_config->get("print_status_window_auto_show_on_minimize") != "true") {
+ event.Skip();
+ return;
+ }
+
+ CallAfter([this]() { show_print_status_frame_safe_on_minimize(); });
+ event.Skip();
+ });
+
//FIXME it seems this method is not called on application start-up, at least not on Windows. Why?
// The same applies to wxEVT_CREATE, it is not being called on startup on Windows.
Bind(wxEVT_ACTIVATE, [this](wxActivateEvent& event) {
@@ -655,7 +720,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
this->Iconize();
return;
}
- if (evt.CmdDown() && evt.GetKeyCode() == 'Q') { wxPostEvent(this, wxCloseEvent(wxEVT_CLOSE_WINDOW)); return;}
+ if (evt.CmdDown() && evt.GetKeyCode() == 'Q') { request_app_exit(false); return;}
if (evt.CmdDown() && evt.RawControlDown() && evt.GetKeyCode() == 'F') {
EnableFullScreenView(true);
if (IsFullScreen()) {
@@ -748,6 +813,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
wxGetApp().persist_window_geometry(&m_settings_dialog, true);
}
+MainFrame::~MainFrame() = default;
+
#ifdef __WIN32__
// Orca: Fix maximized window overlaps taskbar when taskbar auto hide is enabled (#8085)
// Adopted from https://gist.github.com/MortenChristiansen/6463580
@@ -872,6 +939,251 @@ void MainFrame::show_log_window()
m_log_window->Show();
}
+void MainFrame::request_app_exit(bool force)
+{
+ m_real_shutdown_requested = true;
+ if (!Close(force))
+ m_real_shutdown_requested = false;
+}
+
+#ifndef __APPLE__
+bool MainFrame::should_close_to_tray(const wxCloseEvent& event) const
+{
+ return event.CanVeto() &&
+ !m_real_shutdown_requested &&
+ !wxGetApp().is_gcode_viewer() &&
+ IsShown() &&
+ wxGetApp().app_config != nullptr &&
+ wxGetApp().app_config->get("print_status_window_close_to_tray") == "true";
+}
+
+bool MainFrame::ensure_close_to_tray_icon()
+{
+ if (m_close_to_tray_icon)
+ return true;
+
+ auto tray_icon = std::make_unique(this);
+ wxIcon icon = main_frame_icon(wxGetApp().get_app_mode());
+ if (!icon.IsOk())
+ icon = wxIcon(Slic3r::var("BambuStudio.ico"), wxBITMAP_TYPE_ICO);
+ if (!icon.IsOk())
+ return false;
+ if (!tray_icon->SetIcon(icon, "BambuStudio"))
+ return false;
+
+ tray_icon->Bind(wxEVT_TASKBAR_LEFT_DCLICK, [this](wxTaskBarIconEvent&) { restore_from_tray(); });
+ m_close_to_tray_icon = std::move(tray_icon);
+ return true;
+}
+
+void MainFrame::minimize_to_tray()
+{
+ if (wxGetApp().app_config &&
+ wxGetApp().app_config->get("print_status_window_auto_show_on_minimize") == "true") {
+ show_print_status_frame();
+ }
+
+ if (m_settings_dialog.IsShown())
+ m_settings_dialog.Hide();
+
+ Iconize(false);
+ Show(false);
+}
+
+void MainFrame::restore_from_tray()
+{
+ Show(true);
+ Iconize(false);
+ Raise();
+ remove_close_to_tray_icon();
+}
+
+void MainFrame::remove_close_to_tray_icon()
+{
+ if (!m_close_to_tray_icon)
+ return;
+
+ m_close_to_tray_icon->RemoveIcon();
+ m_close_to_tray_icon.reset();
+}
+#else
+bool MainFrame::should_close_to_tray(const wxCloseEvent& /*event*/) const { return false; }
+bool MainFrame::ensure_close_to_tray_icon() { return false; }
+void MainFrame::minimize_to_tray() {}
+void MainFrame::restore_from_tray() {}
+void MainFrame::remove_close_to_tray_icon() {}
+#endif
+
+PrintStatusFrame* MainFrame::ensure_primary_print_status_frame()
+{
+ if (!m_primary_print_status_frame)
+ m_primary_print_status_frame = std::make_unique(this, std::string(), true);
+ return m_primary_print_status_frame.get();
+}
+
+PrintStatusFrame* MainFrame::find_print_status_frame_for_device(const std::string& dev_id) const
+{
+ if (dev_id.empty())
+ return nullptr;
+
+ if (m_primary_print_status_frame && m_primary_print_status_frame->selected_device_id() == dev_id)
+ return m_primary_print_status_frame.get();
+
+ for (const auto& frame : m_secondary_print_status_frames) {
+ if (frame && frame->selected_device_id() == dev_id)
+ return frame.get();
+ }
+
+ return nullptr;
+}
+
+void MainFrame::refresh_print_status_frames()
+{
+ if (m_primary_print_status_frame)
+ m_primary_print_status_frame->refresh_from_preferences();
+
+ for (const auto& frame : m_secondary_print_status_frames) {
+ if (frame)
+ frame->refresh_from_preferences();
+ }
+}
+
+std::string MainFrame::pick_additional_print_status_device(const std::string& preferred_dev_id) const
+{
+ auto* dev = wxGetApp().getDeviceManager();
+ if (dev == nullptr)
+ return preferred_dev_id;
+
+ std::vector machine_ids;
+ const auto machines = dev->get_my_machine_list();
+ machine_ids.reserve(machines.size());
+ for (const auto& item : machines) {
+ if (item.second != nullptr)
+ machine_ids.emplace_back(item.first);
+ }
+
+ std::vector occupied_ids;
+ occupied_ids.reserve(1 + m_secondary_print_status_frames.size());
+ if (m_primary_print_status_frame && m_primary_print_status_frame->IsShown()) {
+ const auto primary_id = m_primary_print_status_frame->selected_device_id();
+ if (!primary_id.empty())
+ occupied_ids.emplace_back(primary_id);
+ }
+ for (const auto& frame : m_secondary_print_status_frames) {
+ if (!frame || !frame->IsShown())
+ continue;
+ const auto frame_id = frame->selected_device_id();
+ if (!frame_id.empty())
+ occupied_ids.emplace_back(frame_id);
+ }
+
+ for (const auto& machine_id : machine_ids) {
+ if (std::find(occupied_ids.begin(), occupied_ids.end(), machine_id) == occupied_ids.end())
+ return machine_id;
+ }
+
+ if (!preferred_dev_id.empty())
+ return preferred_dev_id;
+
+ if (auto* selected = dev->get_selected_machine())
+ return selected->get_dev_id();
+
+ if (!machine_ids.empty())
+ return machine_ids.front();
+
+ return {};
+}
+
+void MainFrame::place_additional_print_status_frame(PrintStatusFrame* frame) const
+{
+ if (frame == nullptr)
+ return;
+
+ wxPoint base_position = GetPosition() + wxPoint(FromDIP(40), FromDIP(80));
+ if (!m_secondary_print_status_frames.empty()) {
+ for (auto it = m_secondary_print_status_frames.rbegin(); it != m_secondary_print_status_frames.rend(); ++it) {
+ if (*it) {
+ base_position = (*it)->GetPosition();
+ break;
+ }
+ }
+ } else if (m_primary_print_status_frame) {
+ base_position = m_primary_print_status_frame->GetPosition();
+ }
+
+ frame->SetPosition(base_position + wxPoint(FromDIP(24), FromDIP(24)));
+}
+
+void MainFrame::show_print_status_frame()
+{
+ if (auto* frame = ensure_primary_print_status_frame())
+ frame->show_window();
+}
+
+void MainFrame::show_print_status_frame_safe_on_minimize()
+{
+ if (m_real_shutdown_requested || IsBeingDeleted() || wxGetApp().app_config == nullptr)
+ return;
+
+ if (wxGetApp().app_config->get("print_status_window_auto_show_on_minimize") != "true") {
+ return;
+ }
+
+ if (auto* frame = ensure_primary_print_status_frame())
+ frame->show_window_safe_on_minimize();
+}
+
+void MainFrame::open_print_status_frame_for_device(const std::string& dev_id)
+{
+ if (dev_id.empty()) {
+ show_print_status_frame();
+ return;
+ }
+
+ if (auto* frame = find_print_status_frame_for_device(dev_id)) {
+ frame->show_window();
+ return;
+ }
+
+ auto frame = std::make_unique(this, dev_id, false);
+ auto* raw_frame = frame.get();
+ place_additional_print_status_frame(raw_frame);
+ m_secondary_print_status_frames.emplace_back(std::move(frame));
+ raw_frame->show_window();
+}
+
+void MainFrame::open_additional_print_status_frame(const std::string& preferred_dev_id)
+{
+ const std::string target_dev_id = pick_additional_print_status_device(preferred_dev_id);
+ if (auto* existing_frame = find_print_status_frame_for_device(target_dev_id)) {
+ if (!existing_frame->IsShown()) {
+ existing_frame->show_window();
+ return;
+ }
+ }
+
+ auto frame = std::make_unique(this, target_dev_id, false);
+ auto* raw_frame = frame.get();
+ place_additional_print_status_frame(raw_frame);
+ m_secondary_print_status_frames.emplace_back(std::move(frame));
+ raw_frame->show_window();
+}
+
+void MainFrame::destroy_print_status_frame()
+{
+ if (m_primary_print_status_frame) {
+ m_primary_print_status_frame->destroy_for_shutdown();
+ m_primary_print_status_frame.release();
+ }
+
+ for (auto& frame : m_secondary_print_status_frames) {
+ if (frame)
+ frame->destroy_for_shutdown();
+ frame.release();
+ }
+ m_secondary_print_status_frames.clear();
+}
+
//BBS GUI refactor: remove unused layout new/dlg
void MainFrame::update_layout()
{
@@ -1004,6 +1316,7 @@ void MainFrame::update_layout()
void MainFrame::shutdown()
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "MainFrame::shutdown enter";
+ m_real_shutdown_requested = true;
// BBS: backup
Slic3r::set_backup_callback(nullptr);
#ifdef _WIN32
@@ -1034,6 +1347,8 @@ void MainFrame::shutdown()
// Avoid the Paint messages by hiding the main window.
// Also the application closes much faster without these unnecessary screen refreshes.
// In addition, there were some crashes due to the Paint events sent to already destructed windows.
+ remove_close_to_tray_icon();
+ destroy_print_status_frame();
this->Show(false);
if (m_settings_dialog.IsShown())
@@ -2584,6 +2899,8 @@ void MainFrame::on_sys_color_changed()
WebView::RecreateAll();
+ refresh_print_status_frames();
+
this->Refresh();
}
@@ -2888,10 +3205,10 @@ void MainFrame::init_menubar_as_editor()
#ifndef __APPLE__
append_menu_item(fileMenu, wxID_EXIT, _L("Quit"), wxString::Format(_L("Quit")),
- [this](wxCommandEvent&) { Close(false); }, "menu_exit", nullptr);
+ [this](wxCommandEvent&) { request_app_exit(false); }, "menu_exit", nullptr);
#else
append_menu_item(fileMenu, wxID_EXIT, _L("Quit"), wxString::Format(_L("Quit")),
- [this](wxCommandEvent&) { Close(false); }, "", nullptr);
+ [this](wxCommandEvent&) { request_app_exit(false); }, "", nullptr);
#endif
}
@@ -3139,6 +3456,9 @@ void MainFrame::init_menubar_as_editor()
[this](wxCommandEvent &) { m_plater->reset_window_layout(); }, "", this,
[this]() { return (m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview) && m_plater->is_sidebar_enabled(); },
this);
+ append_menu_item(
+ viewMenu, wxID_ANY, _L("Show Print Status Window"), _L("Show Print Status Window"),
+ [this](wxCommandEvent &) { show_print_status_frame(); }, "", this);
viewMenu->AppendSeparator();
append_menu_check_item(viewMenu, wxID_ANY, _L("Show Labels by Layer") + "\t" + ctrl + "E", _L("Show Labels of printing by layer in 3D scene"),
[this](wxCommandEvent&) { m_plater->show_view3D_layer_labels(!m_plater->are_view3D_layer_labels_shown()); m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT)); }, this,
@@ -3608,7 +3928,7 @@ void MainFrame::init_menubar_as_editor()
wxMenu* apple_menu = m_menubar->OSXGetAppleMenu();
if (apple_menu != nullptr) {
apple_menu->Bind(wxEVT_MENU, [this](wxCommandEvent &) {
- Close();
+ request_app_exit(false);
}, wxID_EXIT);
}
#endif // __APPLE__
@@ -3685,7 +4005,7 @@ void MainFrame::init_menubar_as_gcodeviewer()
[]() {return true; }, this);
fileMenu->AppendSeparator();
append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME),
- [this](wxCommandEvent&) { Close(false); });
+ [this](wxCommandEvent&) { request_app_exit(false); });
}
// View menu
@@ -3712,7 +4032,7 @@ void MainFrame::init_menubar_as_gcodeviewer()
wxMenu* apple_menu = m_menubar->OSXGetAppleMenu();
if (apple_menu != nullptr) {
apple_menu->Bind(wxEVT_MENU, [this](wxCommandEvent&) {
- Close();
+ request_app_exit(false);
}, wxID_EXIT);
}
#endif // __APPLE__
@@ -4364,6 +4684,7 @@ void MainFrame::update_ui_from_settings()
m_plater->update_ui_from_settings();
for (auto tab: wxGetApp().tabs_list)
tab->update_ui_from_settings();
+ refresh_print_status_frames();
}
diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp
index bb91a740ae..960b5eaf43 100644
--- a/src/slic3r/GUI/MainFrame.hpp
+++ b/src/slic3r/GUI/MainFrame.hpp
@@ -15,6 +15,7 @@
#include
#include