diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 970faeb620..4dc838172c 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -280,19 +280,8 @@ void AppConfig::set_defaults() set(SETTING_OPENGL_FPS_CAP, std::to_string(fps_cap)); } - if (get(SETTING_PLUGIN_PAGES_VISIBLE_COUNT).empty()) - set(SETTING_PLUGIN_PAGES_VISIBLE_COUNT, std::to_string(PLUGIN_PAGES_VISIBLE_COUNT_DEFAULT)); - else { - int visible_count = PLUGIN_PAGES_VISIBLE_COUNT_DEFAULT; - try { - visible_count = std::stoi(get(SETTING_PLUGIN_PAGES_VISIBLE_COUNT)); - } - catch (...) { - visible_count = PLUGIN_PAGES_VISIBLE_COUNT_DEFAULT; - } - visible_count = std::max(PLUGIN_PAGES_VISIBLE_COUNT_MIN, std::min(visible_count, PLUGIN_PAGES_VISIBLE_COUNT_MAX)); - set(SETTING_PLUGIN_PAGES_VISIBLE_COUNT, std::to_string(visible_count)); - } + // The getter already defaults, parses and clamps; write back what it resolves to. + set(SETTING_PLUGIN_PAGES_VISIBLE_COUNT, std::to_string(get_plugin_pages_visible_count())); if (get(SETTING_OPENGL_SHOW_FPS_OVERLAY).empty()) set_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY, false); @@ -1657,13 +1646,7 @@ int AppConfig::get_plugin_pages_visible_count() const catch (...) { return PLUGIN_PAGES_VISIBLE_COUNT_DEFAULT; } - return std::max(PLUGIN_PAGES_VISIBLE_COUNT_MIN, std::min(visible_count, PLUGIN_PAGES_VISIBLE_COUNT_MAX)); -} - -void AppConfig::set_plugin_pages_visible_count(int count) -{ - count = std::max(PLUGIN_PAGES_VISIBLE_COUNT_MIN, std::min(count, PLUGIN_PAGES_VISIBLE_COUNT_MAX)); - set(SETTING_PLUGIN_PAGES_VISIBLE_COUNT, std::to_string(count)); + return std::clamp(visible_count, PLUGIN_PAGES_VISIBLE_COUNT_MIN, PLUGIN_PAGES_VISIBLE_COUNT_MAX); } std::vector AppConfig::get_skipped_network_versions() const diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index de5c3a442f..65c57cdb30 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -382,7 +382,6 @@ public: // Number of plugin pages shown as fixed tabs before the rest are collapsed into a // dropdown on the last tab. int get_plugin_pages_visible_count() const; - void set_plugin_pages_visible_count(int count); std::vector get_skipped_network_versions() const; void add_skipped_network_version(const std::string& version); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 9e3d40994b..3b1cf1dffd 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -722,7 +722,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'S') { if (can_save_as()) m_plater->save_project(true); return;} else if (evt.CmdDown() && evt.GetKeyCode() == 'S') { if (can_save()) m_plater->save_project(); return;} if (evt.CmdDown() && evt.GetKeyCode() == 'F') { - if (m_plater && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE || m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW)) { + if (m_plater && is_prepare_or_preview_tab()) { m_plater->sidebar().can_search(); } } @@ -1015,12 +1015,12 @@ void MainFrame::update_layout() case ESettingsLayout::Old: { m_plater->Reparent(m_tabpanel); - { - const int home_idx = m_tabpanel->FindPageByName(TAB_ID_HOME); - const size_t prepare_pos = (home_idx == wxNOT_FOUND) ? 0 : static_cast(home_idx) + 1; - m_tabpanel->InsertPage(prepare_pos, m_plater, _L("Prepare"), false, Notebook::PAGE_PREPARE); - m_tabpanel->InsertPage(prepare_pos + 1, m_plater, _L("Preview"), false, Notebook::PAGE_PREVIEW); - } + // Right after Home — or first, when there is no Home tab (PositionAfter() would + // append instead, and by now the other built-in tabs are already in place). + const int home_idx = m_tabpanel->FindPageByName(TAB_ID_HOME); + const size_t prepare_pos = (home_idx == wxNOT_FOUND) ? 0 : static_cast(home_idx) + 1; + m_tabpanel->InsertPage(prepare_pos, TAB_ID_PREPARE, m_plater, _L("Prepare"), "tab_3d_active"); + m_tabpanel->InsertPage(prepare_pos + 1, TAB_ID_PREVIEW, m_plater, _L("Preview"), "tab_preview_active"); m_main_sizer->Add(m_tabpanel, 1, wxEXPAND | wxTOP, 0); m_tabpanel->Bind(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, [this](wxCommandEvent& evt) @@ -1290,24 +1290,6 @@ void MainFrame::init_tabpanel() { if (panel) panel->SetFocus(); - - /*switch (sel) { - case TabPosition::tpHome: - show_option(false); - break; - case TabPosition::tp3DEditor: - show_option(true); - break; - case TabPosition::tpPreview: - show_option(true); - break; - case TabPosition::tpMonitor: - show_option(false); - break; - default: - show_option(false); - break; - }*/ }); if (wxGetApp().is_editor()) { @@ -1317,7 +1299,7 @@ void MainFrame::init_tabpanel() { select_tab(TAB_ID_HOME); m_webview->load_url(url); }); - m_tabpanel->AddPage(m_webview, "", false, Notebook::PAGE_HOME); + m_tabpanel->AddPage(TAB_ID_HOME, m_webview, "", "tab_home_active"); m_param_panel = new ParamsPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); } @@ -1332,7 +1314,7 @@ void MainFrame::init_tabpanel() { //BBS add pages m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize); m_monitor->SetBackgroundColour(*wxWHITE); - m_tabpanel->AddPage(m_monitor, _L("Device"), false, Notebook::PAGE_MONITOR); + m_tabpanel->AddPage(TAB_ID_MONITOR, m_monitor, _L("Device"), "tab_monitor_active"); m_printer_view = new PrinterWebView(m_tabpanel); Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent &evt) { @@ -1347,16 +1329,16 @@ void MainFrame::init_tabpanel() { m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize); m_multi_machine->SetBackgroundColour(*wxWHITE); // TODO: change the bitmap - m_tabpanel->AddPage(m_multi_machine, _L("Multi-device"), false, Notebook::PAGE_MULTI_DEVICE); + m_tabpanel->AddPage(TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), "tab_multi_active"); } m_project = new ProjectPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize); m_project->SetBackgroundColour(*wxWHITE); - m_tabpanel->AddPage(m_project, _L("Project"), false, Notebook::PAGE_PROJECT); + m_tabpanel->AddPage(TAB_ID_PROJECT, m_project, _L("Project"), "tab_auxiliary_active"); m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize); m_calibration->SetBackgroundColour(*wxWHITE); - m_tabpanel->AddPage(m_calibration, _L("Calibration"), false, Notebook::PAGE_CALIBRATION); + m_tabpanel->AddPage(TAB_ID_CALIBRATION, m_calibration, _L("Calibration"), "tab_calibration_active"); // Plugin pages are appended after the built-in tabs; their ids are namespaced // (plugin..) so they can't collide with the built-in TAB_ID_* constants. @@ -1382,9 +1364,14 @@ void MainFrame::show_device(bool should_use_native) { const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents"); - // The web page is appended when printer agents are enabled. Remove that - // extra page before switching back to the normal native/Web layout. - if (!use_printer_agents) { + // The web Device page is the extra tab printer-agents mode shows alongside the native one. + // Printers that drive the native Bambu device tab have nothing to put in it, so they don't + // get it — otherwise a Bambu user sees two Device tabs, one of them permanently empty. + const bool want_web_device_tab = use_printer_agents && wxGetApp().preset_bundle != nullptr && + !wxGetApp().preset_bundle->use_bbl_device_tab(); + + // Remove the extra page before switching to any layout that shouldn't have it. + if (!want_web_device_tab) { if ((idx = m_tabpanel->FindPageByName(TAB_ID_MONITOR_WEB)) != wxNOT_FOUND) { m_printer_view->Show(false); m_tabpanel->RemovePage(idx); @@ -1403,10 +1390,8 @@ void MainFrame::show_device(bool should_use_native) { m_tabpanel->RemovePage(idx); } m_monitor->Show(false); - const int preview_idx = m_tabpanel->FindPageByName(TAB_ID_PREVIEW); - const size_t monitor_pos = - (preview_idx == wxNOT_FOUND) ? m_tabpanel->GetPageCount() : static_cast(preview_idx) + 1; - m_tabpanel->InsertPage(monitor_pos, TAB_ID_MONITOR, m_monitor, _L("Device"), "tab_monitor_active", false); + m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor, + _L("Device"), "tab_monitor_active"); } if (m_printer_view == nullptr) { @@ -1427,30 +1412,31 @@ void MainFrame::show_device(bool should_use_native) { // TODO: change the bitmap if (m_tabpanel->FindPage(m_multi_machine) == wxNOT_FOUND) { m_multi_machine->Show(false); - const int monitor_idx = m_tabpanel->FindPageByName(TAB_ID_MONITOR); - const size_t multi_pos = - (monitor_idx == wxNOT_FOUND) ? m_tabpanel->GetPageCount() : static_cast(monitor_idx) + 1; - m_tabpanel->InsertPage(multi_pos, TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), - "tab_multi_active", false); + // Past the web Device tab when it is already there, so enabling multi-machine + // later can't wedge this page between the two Device tabs. + m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR_WEB, TAB_ID_MONITOR}), + TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), "tab_multi_active"); } } if (!m_calibration) { m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize); m_calibration->SetBackgroundColour(*wxWHITE); } - // Calibration is always the last page, so don't use InsertPage here. Otherwise, if multi_machine page is not enabled, - // the calibration tab won't be properly added as well, due to the TabPosition::tpCalibration no longer matches the real tab position. if (m_tabpanel->FindPage(m_calibration) == wxNOT_FOUND) { m_calibration->Show(false); - m_tabpanel->AddPage(m_calibration, _L("Calibration"), false, Notebook::PAGE_CALIBRATION); + m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration, + _L("Calibration"), "tab_calibration_active"); } - if ((idx = m_tabpanel->FindPage(m_printer_view)) == wxNOT_FOUND) { - m_printer_view->Show(false); - m_tabpanel->InsertPage(m_tabpanel->GetPageCount(), TAB_ID_MONITOR_WEB, m_printer_view, - _L("Device (Web)"), "tab_monitor_active", false); - } else { - m_tabpanel->SetPageText(idx, _L("Device (Web)")); + if (want_web_device_tab) { + if ((idx = m_tabpanel->FindPage(m_printer_view)) == wxNOT_FOUND) { + m_printer_view->Show(false); + // Immediately right of the native Device tab, not at the end of the tab bar. + m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MONITOR_WEB, + m_printer_view, _L("Device (Web)"), "tab_monitor_active"); + } else { + m_tabpanel->SetPageText(idx, _L("Device (Web)")); + } } #ifdef _MSW_DARK_MODE @@ -1458,7 +1444,7 @@ void MainFrame::show_device(bool should_use_native) { #endif // _MSW_DARK_MODE fit_tab_labels(); // ORCA on printer change - m_plugin_pages.relayout(); // keep plugin tabs after the native tabs just mutated above + m_plugin_pages.relayout(); // re-sync plugin tabs against the native tabs just mutated above return; } @@ -1480,11 +1466,8 @@ void MainFrame::show_device(bool should_use_native) { m_monitor->SetBackgroundColour(*wxWHITE); } m_monitor->Show(false); - { - const int preview_idx = m_tabpanel->FindPageByName(TAB_ID_PREVIEW); - const size_t monitor_pos = (preview_idx == wxNOT_FOUND) ? m_tabpanel->GetPageCount() : static_cast(preview_idx) + 1; - m_tabpanel->InsertPage(monitor_pos, m_monitor, _L("Device"), false, Notebook::PAGE_MONITOR); - } + m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor, + _L("Device"), "tab_monitor_active"); if (wxGetApp().is_enable_multi_machine()) { if (!m_multi_machine) { @@ -1493,21 +1476,18 @@ void MainFrame::show_device(bool should_use_native) { } // TODO: change the bitmap m_multi_machine->Show(false); - { - const int monitor_idx = m_tabpanel->FindPageByName(TAB_ID_MONITOR); - const size_t multi_pos = (monitor_idx == wxNOT_FOUND) ? m_tabpanel->GetPageCount() : static_cast(monitor_idx) + 1; - m_tabpanel->InsertPage(multi_pos, m_multi_machine, _L("Multi-device"), false, Notebook::PAGE_MULTI_DEVICE); - } + m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MULTI_DEVICE, m_multi_machine, + _L("Multi-device"), "tab_multi_active"); } if (!m_calibration) { m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize); m_calibration->SetBackgroundColour(*wxWHITE); } m_calibration->Show(false); - // Calibration is always appended last (AddPage), so it lands after whichever of Monitor/Multi-device - // actually got inserted above — no longer position-sensitive now that insertion position is computed - // from FindPageByName rather than a fixed TabPosition index. - m_tabpanel->AddPage(m_calibration, _L("Calibration"), false, Notebook::PAGE_CALIBRATION); + // Last of the built-in tabs, but plugin tabs already sit past it — anchor rather than + // append, so its position doesn't depend on the relayout() below running afterwards. + m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration, + _L("Calibration"), "tab_calibration_active"); #ifdef _MSW_DARK_MODE wxGetApp().UpdateDarkUIWin(this); @@ -1540,14 +1520,17 @@ void MainFrame::show_device(bool should_use_native) { }); } m_printer_view->Show(false); - { - const int preview_idx = m_tabpanel->FindPageByName(TAB_ID_PREVIEW); - const size_t monitor_pos = (preview_idx == wxNOT_FOUND) ? m_tabpanel->GetPageCount() : static_cast(preview_idx) + 1; - m_tabpanel->InsertPage(monitor_pos, m_printer_view, _L("Device"), false, Notebook::PAGE_MONITOR); - } + m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_printer_view, + _L("Device"), "tab_monitor_active"); } fit_tab_labels(); // ORCA on printer change - m_plugin_pages.relayout(); // keep plugin tabs after the native tabs just mutated above + m_plugin_pages.relayout(); // re-sync plugin tabs against the native tabs just mutated above +} + +bool MainFrame::is_prepare_or_preview_tab() const +{ + const wxString tab = m_tabpanel->GetSelectedPageName(); + return tab == TAB_ID_PREPARE || tab == TAB_ID_PREVIEW; } void MainFrame::fit_tab_labels() @@ -3168,7 +3151,7 @@ void MainFrame::init_menubar_as_editor() wxGetApp().app_config->set_bool("auto_perspective", !wxGetApp().app_config->get_bool("auto_perspective")); m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT)); }, - this, [this]() { return m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE || m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW; }, + this, [this]() { return is_prepare_or_preview_tab(); }, [this]() { return wxGetApp().app_config->get_bool("auto_perspective"); }, this); viewMenu->AppendSeparator(); @@ -3186,7 +3169,7 @@ void MainFrame::init_menubar_as_editor() wxGetApp().toggle_show_3d_navigator(); m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT)); }, - this, [this]() { return m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE || m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW; }, + this, [this]() { return is_prepare_or_preview_tab(); }, [this]() { return wxGetApp().show_3d_navigator(); }, this); append_menu_check_item(viewMenu, wxID_ANY, _L("Show Gridlines"), _L("Show Gridlines on plate"), @@ -3194,15 +3177,14 @@ void MainFrame::init_menubar_as_editor() wxGetApp().toggle_show_plate_gridlines(); m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT)); }, this, - [this]() { return m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE || m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW; }, + [this]() { return is_prepare_or_preview_tab(); }, [this]() { return wxGetApp().show_plate_gridlines(); }, this); append_menu_item( viewMenu, wxID_ANY, _L("Reset Window Layout"), _L("Reset to default window layout"), [this](wxCommandEvent&) { m_plater->reset_window_layout(); }, "", this, [this]() { - return (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE || m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW) && - m_plater->is_sidebar_enabled(); + return is_prepare_or_preview_tab() && m_plater->is_sidebar_enabled(); }, this); @@ -4025,10 +4007,8 @@ void MainFrame::select_tab(wxPanel* panel) wxGetApp().params_dialog()->Popup(); return; } - // page_name cannot be resolved via panel->GetName() — Prepare and Preview - // share the single m_plater window, so the window itself has no single correct - // name (see Global Constraints). Resolve via Notebook's per-slot m_pageNames - // instead, via the index -> id lookup, which works for any page (built-in or not). + // Not panel->GetName(): Prepare and Preview share the single m_plater window, so the + // window has no one correct name. The slot -> id lookup is the only correct resolution. int page_idx = m_tabpanel->FindPage(panel); wxString page_name = (page_idx == wxNOT_FOUND) ? wxString() : m_tabpanel->GetPageName(static_cast(page_idx)); if (page_name == TAB_ID_PREPARE && m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW) @@ -4082,10 +4062,8 @@ void MainFrame::select_tab(const wxString& id/* = wxString()*/) m_plater->get_current_canvas3D()->render(); }*/ #endif - // NOTE: this checks the ORIGINAL parameter (id), not the resolved new_selection — - // preserving that the fallback-to-last-tab path never triggers this render call - // even if the last selected tab happened to be Prepare. Do not "simplify" to - // new_selection == TAB_ID_PREPARE, that changes behavior. + // Intentionally `id`, not `new_selection`: the fallback-to-last-tab path must not + // trigger this render even when the last selected tab was Prepare. if (id == TAB_ID_PREPARE && m_layout == ESettingsLayout::Old) m_plater->canvas3D()->render(); else if (was_hidden) { diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index d6e8173288..2052860d80 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -362,6 +362,8 @@ public: //SoftFever void show_device(bool should_use_native); void fit_tab_labels(); // ORCA + // True while either of the two tabs backed by m_plater is selected. + bool is_prepare_or_preview_tab() const; PluginPages& plugin_pages() { return m_plugin_pages; } PA_Calibration_Dlg* m_pa_calib_dlg{ nullptr }; diff --git a/src/slic3r/GUI/Notebook.cpp b/src/slic3r/GUI/Notebook.cpp index f9db689faa..673508454a 100644 --- a/src/slic3r/GUI/Notebook.cpp +++ b/src/slic3r/GUI/Notebook.cpp @@ -156,14 +156,13 @@ void ButtonsListCtrl::SetSelection(int sel) Refresh(); } -bool ButtonsListCtrl::InsertPage(size_t n, const wxString &text, bool bSelect /* = false*/, const std::string &bmp_name /* = ""*/, int imageId /* = wxBookCtrlBase::NO_IMAGE */) +bool ButtonsListCtrl::InsertPage(size_t n, const wxString &text, bool bSelect /* = false*/, const std::string &bmp_name /* = ""*/, const wxBitmap &bmp /* = wxNullBitmap */) { Button * btn = new Button(this, text.empty() ? text : " " + text, bmp_name, wxNO_BORDER); btn->SetCornerRadius(0); - if (bmp_name.empty() && m_imageList != nullptr && imageId != wxBookCtrlBase::NO_IMAGE && imageId >= 0 && - imageId < m_imageList->GetImageCount()) - btn->SetIcon(m_imageList->GetBitmap(imageId)); + if (bmp_name.empty() && bmp.IsOk()) + btn->SetIcon(bmp); int em = em_unit(this); //BBS set size for button @@ -232,23 +231,6 @@ bool ButtonsListCtrl::SetPageImage(size_t n, const std::string& bmp_name) const return true; } -bool ButtonsListCtrl::SetPageImage(size_t n, int imageId) -{ - if (n >= m_pageButtons.size()) - return false; - - if (imageId == wxBookCtrlBase::NO_IMAGE) { - m_pageButtons[n]->SetIcon(wxBitmap()); - return true; - } - - if (m_imageList == nullptr || imageId < 0 || imageId >= m_imageList->GetImageCount()) - return false; - - m_pageButtons[n]->SetIcon(m_imageList->GetBitmap(imageId)); - return true; -} - void ButtonsListCtrl::SetPageText(size_t n, const wxString& strText) { Button* btn = m_pageButtons[n]; diff --git a/src/slic3r/GUI/Notebook.hpp b/src/slic3r/GUI/Notebook.hpp index da90535481..4734122b18 100644 --- a/src/slic3r/GUI/Notebook.hpp +++ b/src/slic3r/GUI/Notebook.hpp @@ -3,11 +3,11 @@ //#ifdef _WIN32 +#include #include #include -#include #include -#include +#include #include class ScalableButton; @@ -27,11 +27,9 @@ public: void SetSelection(int sel); void UpdateMode(); void Rescale(); - bool InsertPage(size_t n, const wxString &text, bool bSelect = false, const std::string &bmp_name = "", int imageId = wxBookCtrlBase::NO_IMAGE); + bool InsertPage(size_t n, const wxString &text, bool bSelect = false, const std::string &bmp_name = "", const wxBitmap &bmp = wxNullBitmap); void RemovePage(size_t n); bool SetPageImage(size_t n, const std::string& bmp_name) const; - bool SetPageImage(size_t n, int imageId); - void SetImageList(wxImageList* imageList) { m_imageList = imageList; } void SetPageText(size_t n, const wxString& strText); void SetCompact(size_t n, bool compact); // ORCA wxString GetPageText(size_t n) const; @@ -49,23 +47,12 @@ private: int m_btn_margin; int m_line_margin; std::vector m_pageLabels; // ORCA - wxImageList* m_imageList{nullptr}; wxWindow* m_overflow_button{nullptr}; // ORCA }; class Notebook : public wxBookCtrlBase { public: - // Negative values below wxBookCtrlBase::NO_IMAGE are reserved for the built-in - // tabs. Nonnegative values are wxImageList indices supplied by plugin pages. - static constexpr int PAGE_HOME = -2; - static constexpr int PAGE_PREPARE = -3; - static constexpr int PAGE_PREVIEW = -4; - static constexpr int PAGE_MONITOR = -5; - static constexpr int PAGE_MULTI_DEVICE = -6; - static constexpr int PAGE_PROJECT = -7; - static constexpr int PAGE_CALIBRATION = -8; - Notebook(wxWindow * parent, wxWindowID winid = wxID_ANY, const wxPoint & pos = wxDefaultPosition, @@ -156,83 +143,58 @@ public: // Implement base class pure virtual methods. + // Page management. Every insertion funnels through the InsertPage() below; `id` is the + // stable page name FindPageByName() resolves. Built-in tabs name a resource bitmap, + // plugin pages hand over a ready wxBitmap; wx's own imageId overloads carry neither. + bool AddPage(const wxString& id, + wxWindow* page, + const wxString& text, + const std::string& bmp_name = "", + bool bSelect = false) + { + DoInvalidateBestSize(); + return InsertPage(GetPageCount(), id, page, text, bmp_name, bSelect); + } + bool AddPage(wxWindow* page, const wxString& text, bool bSelect = false, int imageId = NO_IMAGE) override { DoInvalidateBestSize(); return InsertPage(GetPageCount(), page, text, bSelect, imageId); } - // Page management - virtual bool InsertPage(size_t n, - wxWindow * page, - const wxString & text, - bool bSelect = false, - int imageId = NO_IMAGE) override - { - wxString page_name; - std::string bmp_name; - const bool is_fixed_page = get_fixed_page_info(imageId, page_name, bmp_name); - const int stored_image_id = is_fixed_page ? NO_IMAGE : imageId; - - if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect, stored_image_id)) - return false; - - m_pageNames.insert(m_pageNames.begin() + n, page_name); - m_pageImageIds.insert(m_pageImageIds.begin() + n, stored_image_id); - GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name, stored_image_id); - - if (!DoSetSelectionAfterInsertion(n, bSelect)) - page->Hide(); - - return true; - } - - bool InsertPage(size_t n, - const wxString& id, - wxWindow* page, - const wxString& text, - int imageId, - bool bSelect = false) - { - if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect, imageId)) - return false; - - m_pageNames.insert(m_pageNames.begin() + n, id); - m_pageImageIds.insert(m_pageImageIds.begin() + n, imageId); - GetBtnsListCtrl()->InsertPage(n, text, bSelect, "", imageId); - - if (!DoSetSelectionAfterInsertion(n, bSelect)) - page->Hide(); - - return true; - } - bool InsertPage(size_t n, const wxString& id, wxWindow * page, const wxString & text, const std::string& bmp_name = "", - bool bSelect = false) + bool bSelect = false, + const wxBitmap& bmp = wxNullBitmap) { if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect)) return false; m_pageNames.insert(m_pageNames.begin() + n, id); - m_pageImageIds.insert(m_pageImageIds.begin() + n, NO_IMAGE); - GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name); + GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name, bmp); - // wxBookCtrlBase::InsertPage() only inserts into the page list and sizes the - // new page to the current page's rect — it never touches visibility. A freshly - // constructed page defaults to shown, so without this it renders on top of - // whatever page is currently selected until the next SetSelection() call hides - // it. Mirrors the pure-virtual InsertPage() override above, which already does - // this correctly. + // wxBookCtrlBase::InsertPage() only inserts into the page list and sizes the new + // page to the current page's rect — it never touches visibility, and a freshly + // constructed page defaults to shown. Without this it renders on top of whatever + // page is currently selected until the next SetSelection() call hides it. if (!DoSetSelectionAfterInsertion(n, bSelect)) page->Hide(); return true; } + virtual bool InsertPage(size_t n, + wxWindow * page, + const wxString & text, + bool bSelect = false, + int WXUNUSED(imageId) = NO_IMAGE) override + { + return InsertPage(n, wxString(), page, text, "", bSelect); + } + virtual int SetSelection(size_t n) override { int ret = DoSetSelection(n, SetSelection_SendEvent); @@ -262,7 +224,8 @@ public: return DoSetSelection(n); } - // Labels are stored by the custom button list; page images use the wx image-list IDs below. + // Labels are stored by the custom button list; wx's image-list API is unused — tab icons + // are set directly on the buttons, either from a resource name or a ready wxBitmap. virtual bool SetPageText(size_t n, const wxString & strText) override { wxCHECK_MSG(n < GetPageCount(), false, wxS("Invalid page")); @@ -278,27 +241,14 @@ public: return GetBtnsListCtrl()->GetPageText(n); } - virtual bool SetPageImage(size_t n, int imageId) override + virtual bool SetPageImage(size_t WXUNUSED(n), int WXUNUSED(imageId)) override { - if (n >= m_pageImageIds.size()) - return false; - - if (!GetBtnsListCtrl()->SetPageImage(n, imageId)) - return false; - - m_pageImageIds[n] = imageId; - return true; + return false; } - virtual int GetPageImage(size_t n) const override + virtual int GetPageImage(size_t WXUNUSED(n)) const override { - return n < m_pageImageIds.size() ? m_pageImageIds[n] : NO_IMAGE; - } - - void SetImageList(wxImageList* imageList) - { - m_imageList = imageList; - GetBtnsListCtrl()->SetImageList(imageList); + return NO_IMAGE; } bool SetPageImage(size_t n, const std::string& bmp_name) @@ -314,22 +264,27 @@ public: page->SetFocus(); } - // wxBookCtrlBase::DeleteAllPages() clears its page list directly rather than - // going through DoRemovePage() per page, so it would otherwise leave - // m_pageNames desynchronized (a mutation path outside the four this class - // already keeps in sync). Not currently called on a Notebook anywhere in - // this codebase, but kept correct for the same reason the rest of this - // bookkeeping exists. + // The base clears its page list directly instead of calling DoRemovePage() per page, + // which would leave m_pageNames behind. No caller today; kept in sync regardless. virtual bool DeleteAllPages() override { m_pageNames.clear(); - m_pageImageIds.clear(); return wxBookCtrlBase::DeleteAllPages(); } ButtonsListCtrl* GetBtnsListCtrl() const { return static_cast(m_bookctrl); } void SetOverflowButton(wxWindow* button) { GetBtnsListCtrl()->SetOverflowButton(button); } + // Insertion index just past the first of `ids` that is present, or the end of the bar + // if none is — lets call sites state tab order as "after X" instead of re-deriving it. + size_t PositionAfter(std::initializer_list ids) const + { + for (const char* id : ids) + if (const int idx = FindPageByName(id); idx != wxNOT_FOUND) + return static_cast(idx) + 1; + return GetPageCount(); + } + int FindPageByName(const wxString& id) const { if (id.empty()) @@ -485,7 +440,6 @@ protected: if (win) { m_pageNames.erase(m_pageNames.begin() + page); - m_pageImageIds.erase(m_pageImageIds.begin() + page); GetBtnsListCtrl()->RemovePage(page); DoSetSelectionAfterRemoval(page); } @@ -509,47 +463,9 @@ protected: } private: - static bool get_fixed_page_info(int imageId, wxString& page_name, std::string& bmp_name) - { - switch (imageId) { - case PAGE_HOME: - page_name = wxS("home"); - bmp_name = "tab_home_active"; - return true; - case PAGE_PREPARE: - page_name = wxS("prepare"); - bmp_name = "tab_3d_active"; - return true; - case PAGE_PREVIEW: - page_name = wxS("preview"); - bmp_name = "tab_preview_active"; - return true; - case PAGE_MONITOR: - page_name = wxS("monitor"); - bmp_name = "tab_monitor_active"; - return true; - case PAGE_MULTI_DEVICE: - page_name = wxS("multi_device"); - bmp_name = "tab_multi_active"; - return true; - case PAGE_PROJECT: - page_name = wxS("project"); - bmp_name = "tab_auxiliary_active"; - return true; - case PAGE_CALIBRATION: - page_name = wxS("calibration"); - bmp_name = "tab_calibration_active"; - return true; - default: - return false; - } - } - void Init(); - std::vector m_pageNames; // index-parallel to wxBookCtrlBase::m_pages - std::vector m_pageImageIds; // index-parallel to wxBookCtrlBase::m_pages - wxImageList* m_imageList{nullptr}; + std::vector m_pageNames; // index-parallel to wxBookCtrlBase::m_pages wxShowEffect m_showEffect, m_hideEffect; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5dd77f4389..aba0910630 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -11221,19 +11221,18 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e) const int new_sel = e.GetSelection(); if (new_sel == wxNOT_FOUND) { - // Guards against new_sel matching FindPageByName's own wxNOT_FOUND sentinel - // below when a TAB_ID_* isn't currently present in the tabpanel. + // GetPage(new_sel) below needs a valid index. e.Skip(); return; } - sidebar_layout.show = new_sel == main_frame->m_tabpanel->FindPageByName(TAB_ID_PREPARE) || - new_sel == main_frame->m_tabpanel->FindPageByName(TAB_ID_PREVIEW); + const wxString new_name = main_frame->m_tabpanel->GetPageName(new_sel); + sidebar_layout.show = new_name == TAB_ID_PREPARE || new_name == TAB_ID_PREVIEW; update_sidebar(); int old_sel = e.GetOldSelection(); const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents"); const bool use_native_device_tab = wxGetApp().preset_bundle && (wxGetApp().preset_bundle->use_bbl_device_tab() || use_printer_agents); - if (use_native_device_tab && new_sel == main_frame->m_tabpanel->FindPageByName(TAB_ID_MONITOR)) { + if (use_native_device_tab && new_name == TAB_ID_MONITOR) { // BBL network module is only required for BBL-vendor printers. // Non-BBL Python plugins (e.g. moonraker) drive the Device tab without it. if (!use_printer_agents && wxGetApp().preset_bundle->is_bbl_vendor() && !Slic3r::NetworkAgent::is_network_module_loaded()) { @@ -11253,7 +11252,7 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e) if (selecting_web_device_tab) { // Use the selected discovered machine when the preset has no host. main_frame->load_printer_url(); - } else if (new_sel == main_frame->m_tabpanel->FindPageByName(TAB_ID_MONITOR) && wxGetApp().preset_bundle != nullptr) { + } else if (new_name == TAB_ID_MONITOR && wxGetApp().preset_bundle != nullptr) { auto cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config; wxString url = from_u8(PrintHost::get_print_host_webui(&cfg)); if (main_frame->m_printer_view && url.empty()) { diff --git a/src/slic3r/GUI/Widgets/Button.cpp b/src/slic3r/GUI/Widgets/Button.cpp index 94be6ce301..74ed2cbadd 100644 --- a/src/slic3r/GUI/Widgets/Button.cpp +++ b/src/slic3r/GUI/Widgets/Button.cpp @@ -288,7 +288,7 @@ void Button::render(wxDC& dc) wxSize szIcon; wxSize textSize = this->textSize.GetSize(); - ScalableBitmap icon = active_icon; + const ScalableBitmap& icon = active_icon; wxSize padding = this->paddingSize; int spacing = 5; // Wrap text diff --git a/src/slic3r/plugin/host/PluginPages.cpp b/src/slic3r/plugin/host/PluginPages.cpp index 90e1282dd9..fed16ee416 100644 --- a/src/slic3r/plugin/host/PluginPages.cpp +++ b/src/slic3r/plugin/host/PluginPages.cpp @@ -171,9 +171,11 @@ void PluginPage::on_script_message(wxWebViewEvent& event) root.value("kind", std::string()) != "message") return; - const nlohmann::json data = root.contains("data") ? root["data"] : nlohmann::json(); + const auto data = root.find("data"); try { - m_cap->on_message(data.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace)); + m_cap->on_message(data == root.end() + ? "null" + : data->dump(-1, ' ', false, nlohmann::json::error_handler_t::replace)); } catch (const std::exception& error) { BOOST_LOG_TRIVIAL(error) << "Plugin page message handler failed for '" << m_cap->name() << "': " << error.what(); } catch (...) { @@ -186,28 +188,23 @@ void PluginPage::push_message(const std::string& message) if (m_browser == nullptr) return; - nlohmann::json data = nlohmann::json::parse(message, nullptr, false); - if (data.is_discarded()) - data = message; + // PagesPluginCapability::post_message() already dumps JSON, so accept it as-is; only a + // non-JSON payload needs wrapping as a string literal. + const std::string payload = nlohmann::json::accept(message) + ? message + : nlohmann::json(message).dump(-1, ' ', false, nlohmann::json::error_handler_t::replace); - const wxString script = wxString("(function dispatch(payload, attempts) {\n") + - wxString(" if (typeof window.__orcaDispatch === 'function') { window.__orcaDispatch(payload); return; }\n") + - wxString(" if (attempts < 100) window.setTimeout(function() { dispatch(payload, attempts + 1); }, 25);\n") + - wxString("})({data: ") + - wxString::FromUTF8(data.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace)) + - wxString("}, 0);"); - WebView::RunScript(m_browser, script); + WebView::RunScript(m_browser, wxString::Format( + "(function dispatch(payload, attempts) {\n" + " if (typeof window.__orcaDispatch === 'function') { window.__orcaDispatch(payload); return; }\n" + " if (attempts < 100) window.setTimeout(function() { dispatch(payload, attempts + 1); }, 25);\n" + "})({data: %s}, 0);", + wxString::FromUTF8(payload))); } PluginPages::~PluginPages() { shutdown(); - // try { - // } catch (const std::exception& error) { - // BOOST_LOG_TRIVIAL(error) << "PluginPages::~PluginPages: shutdown() threw: " << error.what(); - // } catch (...) { - // BOOST_LOG_TRIVIAL(error) << "PluginPages::~PluginPages: shutdown() threw a non-standard exception"; - // } } void PluginPages::initialize(Notebook* parent) @@ -219,9 +216,6 @@ void PluginPages::initialize(Notebook* parent) m_visible_page_count = GUI::wxGetApp().app_config->get_plugin_pages_visible_count(); - m_image_list = std::make_unique(20, 20, true, 0); - m_parent->SetImageList(m_image_list.get()); - for (const auto& capability : PluginManager::instance().get_plugin_capabilities("", PluginCapabilityType::Pages)) { if (capability) create_page(capability->identity()); @@ -233,15 +227,12 @@ void PluginPages::shutdown() { while (!m_pages.empty()) remove_page(m_pages.begin()->first); - if (m_parent != nullptr) - m_parent->SetImageList(nullptr); - m_image_list.reset(); m_parent = nullptr; } void PluginPages::set_visible_page_count(int count) { - const int clamped = std::max(PLUGIN_PAGES_VISIBLE_COUNT_MIN, std::min(count, PLUGIN_PAGES_VISIBLE_COUNT_MAX)); + const int clamped = std::clamp(count, PLUGIN_PAGES_VISIBLE_COUNT_MIN, PLUGIN_PAGES_VISIBLE_COUNT_MAX); if (clamped == m_visible_page_count) return; @@ -282,17 +273,14 @@ bool PluginPages::create_page(const PluginCapabilityId& id) return false; } - int image_id = wxBookCtrlBase::NO_IMAGE; - if (!icon.empty() && m_image_list) { + if (!icon.empty()) { try { boost::filesystem::path icon_path(icon); const std::string extension = icon_path.extension().string(); if (extension == ".svg" || extension == ".png") icon_path.replace_extension(); - const wxBitmap bitmap = create_scaled_bitmap(icon_path.string(), m_parent, 20); - if (bitmap.IsOk()) - image_id = m_image_list->Add(bitmap); + page->set_icon(create_scaled_bitmap(icon_path.string(), m_parent, 20)); } catch (const std::exception& error) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to load icon for plugin " << id.plugin_key << ": " << error.what(); } catch (...) { @@ -300,7 +288,6 @@ bool PluginPages::create_page(const PluginCapabilityId& id) } } - page->set_icon_image_id(image_id); m_pages.emplace(id, page); m_order.push_back(id); return true; @@ -349,24 +336,11 @@ void PluginPages::remove_page(const PluginCapabilityId& id) return; PluginPage* page = it->second; - const int removed_image_id = page->get_icon_image_id(); page->detach_capability(); m_pages.erase(it); m_order.erase(std::remove(m_order.begin(), m_order.end(), id), m_order.end()); - if (m_image_list && removed_image_id != wxBookCtrlBase::NO_IMAGE && - removed_image_id >= 0 && removed_image_id < m_image_list->GetImageCount()) { - m_image_list->Remove(removed_image_id); - - // wxImageList IDs are positional. Removing one shifts all later images down by one. - for (auto& [other_id, other_page] : m_pages) { - const int other_image_id = other_page->get_icon_image_id(); - if (other_image_id > removed_image_id) - other_page->set_icon_image_id(other_image_id - 1); - } - } - const int idx = m_parent != nullptr ? m_parent->FindPage(page) : wxNOT_FOUND; if (idx != wxNOT_FOUND) m_parent->RemovePage(idx); @@ -394,14 +368,6 @@ void PluginPages::relayout() }), m_order.end()); - wxString id_to_reselect = m_parent->GetSelectedPageName(); - - for (const auto& [id, page] : m_pages) { - const int idx = m_parent->FindPage(page); - if (idx != wxNOT_FOUND) - m_parent->RemovePage(idx); - } - const int visible_slots = std::max(1, m_visible_page_count); const bool need_overflow = static_cast(m_order.size()) > visible_slots; @@ -421,9 +387,37 @@ void PluginPages::relayout() tab_ids.push_back(*m_swapped_in_id); } - for (const auto& id : tab_ids) { - PluginPage* page = m_pages.at(id); - m_parent->InsertPage(m_parent->GetPageCount(), page_tab_id(id), page, wxString::FromUTF8(id.name), page->get_icon_image_id()); + // MainFrame::show_device() relayouts on every printer change and most of those change + // nothing, so only touch the notebook when the trailing slots don't already spell out + // tab_ids — a rebuild destroys and recreates every tab button and rasterizes every icon. + const size_t page_count = m_parent->GetPageCount(); + bool up_to_date = page_count >= tab_ids.size(); + for (size_t i = 0; up_to_date && i < tab_ids.size(); ++i) + up_to_date = m_parent->GetPageName(page_count - tab_ids.size() + i) == page_tab_id(tab_ids[i]); + for (const auto& [id, page] : m_pages) { + if (!up_to_date) + break; + const bool wanted = std::find(tab_ids.begin(), tab_ids.end(), id) != tab_ids.end(); + up_to_date = (m_parent->FindPage(page) != wxNOT_FOUND) == wanted; + } + + if (!up_to_date) { + const wxString id_to_reselect = m_parent->GetSelectedPageName(); + + for (const auto& [id, page] : m_pages) { + const int idx = m_parent->FindPage(page); + if (idx != wxNOT_FOUND) + m_parent->RemovePage(idx); + } + + for (const auto& id : tab_ids) { + PluginPage* page = m_pages.at(id); + m_parent->InsertPage(m_parent->GetPageCount(), page_tab_id(id), page, wxString::FromUTF8(id.name), "", + false, page->icon()); + } + + if (!id_to_reselect.empty()) + m_parent->SelectPageByName(id_to_reselect); } if (need_overflow) { @@ -442,9 +436,6 @@ void PluginPages::relayout() m_overflow_button->Destroy(); m_overflow_button = nullptr; } - - if (!id_to_reselect.empty()) - m_parent->SelectPageByName(id_to_reselect); } void PluginPages::show_overflow_menu() diff --git a/src/slic3r/plugin/host/PluginPages.hpp b/src/slic3r/plugin/host/PluginPages.hpp index 3dc86b8aee..4d00de6df9 100644 --- a/src/slic3r/plugin/host/PluginPages.hpp +++ b/src/slic3r/plugin/host/PluginPages.hpp @@ -10,8 +10,7 @@ #include #include -#include -#include +#include #include #include @@ -33,8 +32,8 @@ public: void on_new_window(wxWebViewEvent& event); void on_script_message(wxWebViewEvent& event); void push_message(const std::string& message); - void set_icon_image_id(int id) { m_icon_image_id = id; } - int get_icon_image_id() const { return m_icon_image_id; } + void set_icon(const wxBitmap& icon) { m_icon = icon; } + const wxBitmap& icon() const { return m_icon; } private: void load_plugin_content(); @@ -45,8 +44,7 @@ private: std::shared_ptr m_cap; std::shared_ptr> m_lifetime; bool m_content_loaded{false}; - - int m_icon_image_id = wxBookCtrlBase::NO_IMAGE; + wxBitmap m_icon; }; class PluginPages @@ -66,7 +64,6 @@ public: void on_plugin_register(const std::string& plugin_key); void on_plugin_deregister(const std::string& plugin_key); - int get_visible_page_count() const { return m_visible_page_count; } void set_visible_page_count(int count); void relayout(); @@ -83,7 +80,6 @@ private: std::vector m_order; Notebook* m_parent{nullptr}; - std::unique_ptr m_image_list; int m_visible_page_count{0}; std::optional m_swapped_in_id;