From b4aa070c40dd73ddef33b49b61e37feb9d67d7bf Mon Sep 17 00:00:00 2001 From: yw4z Date: Mon, 18 May 2026 14:46:47 +0300 Subject: [PATCH] MultiChooseDialog & CheckList class & improvements for Profile Dependencies (#9971) * init * fix * fix * update * update * update * Update Tab.cpp * Update CheckList.cpp --- resources/images/filter.svg | 1 + src/slic3r/CMakeLists.txt | 4 + src/slic3r/GUI/MultiChoiceDialog.cpp | 58 +++++++ src/slic3r/GUI/MultiChoiceDialog.hpp | 37 +++++ src/slic3r/GUI/Tab.cpp | 19 ++- src/slic3r/GUI/Widgets/CheckList.cpp | 225 +++++++++++++++++++++++++++ src/slic3r/GUI/Widgets/CheckList.hpp | 59 +++++++ 7 files changed, 399 insertions(+), 4 deletions(-) create mode 100644 resources/images/filter.svg create mode 100644 src/slic3r/GUI/MultiChoiceDialog.cpp create mode 100644 src/slic3r/GUI/MultiChoiceDialog.hpp create mode 100644 src/slic3r/GUI/Widgets/CheckList.cpp create mode 100644 src/slic3r/GUI/Widgets/CheckList.hpp diff --git a/resources/images/filter.svg b/resources/images/filter.svg new file mode 100644 index 0000000000..c48942073b --- /dev/null +++ b/resources/images/filter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index c22b9b64fd..5e502207ef 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -329,6 +329,8 @@ set(SLIC3R_GUI_SOURCES GUI/Mouse3DController.hpp GUI/MsgDialog.cpp GUI/MsgDialog.hpp + GUI/MultiChoiceDialog.hpp + GUI/MultiChoiceDialog.cpp GUI/MultiMachine.cpp GUI/MultiMachine.hpp GUI/MultiMachineManagerPage.cpp @@ -496,6 +498,8 @@ set(SLIC3R_GUI_SOURCES GUI/Widgets/Button.hpp GUI/Widgets/CheckBox.cpp GUI/Widgets/CheckBox.hpp + GUI/Widgets/CheckList.cpp + GUI/Widgets/CheckList.hpp GUI/Widgets/ComboBox.cpp GUI/Widgets/ComboBox.hpp GUI/Widgets/DialogButtons.cpp diff --git a/src/slic3r/GUI/MultiChoiceDialog.cpp b/src/slic3r/GUI/MultiChoiceDialog.cpp new file mode 100644 index 0000000000..6bd5412737 --- /dev/null +++ b/src/slic3r/GUI/MultiChoiceDialog.cpp @@ -0,0 +1,58 @@ +#include "MultiChoiceDialog.hpp" + +#include "GUI_App.hpp" +#include "MainFrame.hpp" + +namespace Slic3r { namespace GUI { + +MultiChoiceDialog::MultiChoiceDialog( + wxWindow* parent, + const wxString& message, + const wxString& caption, + const wxArrayString& choices +) + : DPIDialog(parent ? parent : static_cast(wxGetApp().mainframe), wxID_ANY, caption, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) +{ + SetBackgroundColour(*wxWHITE); + + wxBoxSizer* w_sizer = new wxBoxSizer(wxVERTICAL); + + if(!message.IsEmpty()){ + wxStaticText *msg = new wxStaticText(this, wxID_ANY, message); + msg->SetFont(Label::Body_13); + msg->Wrap(-1); + w_sizer->Add(msg, 0, wxRIGHT | wxLEFT | wxTOP, FromDIP(10)); + } + + m_check_list = new CheckList(this, choices); + + w_sizer->Add(m_check_list, 1, wxRIGHT | wxLEFT | wxTOP | wxEXPAND, FromDIP(10)); + + auto dlg_btns = new DialogButtons(this, {"OK", "Cancel"}); + + dlg_btns->GetOK()->Bind( wxEVT_BUTTON, [this](wxCommandEvent &e) {EndModal(wxID_OK);}); + dlg_btns->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) {EndModal(wxID_CANCEL);}); + + w_sizer->Add(dlg_btns, 0, wxEXPAND); + + SetSizer(w_sizer); + Layout(); + w_sizer->Fit(this); + wxGetApp().UpdateDlgDarkUI(this); +} + +wxArrayInt MultiChoiceDialog::GetSelections() const +{ + return m_check_list->GetSelections(); +} + +void MultiChoiceDialog::SetSelections(wxArrayInt sel_array) +{ + m_check_list->SetSelections(sel_array); +} + +MultiChoiceDialog::~MultiChoiceDialog() {} + +void MultiChoiceDialog::on_dpi_changed(const wxRect &suggested_rect) {} + +}} // namespace Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/MultiChoiceDialog.hpp b/src/slic3r/GUI/MultiChoiceDialog.hpp new file mode 100644 index 0000000000..f636bebd3b --- /dev/null +++ b/src/slic3r/GUI/MultiChoiceDialog.hpp @@ -0,0 +1,37 @@ +#ifndef slic3r_GUI_MultiChoiceDialog_hpp_ +#define slic3r_GUI_MultiChoiceDialog_hpp_ + +#include "Widgets/CheckList.hpp" +#include "Widgets/DialogButtons.hpp" + +#include +#include +#include + + +namespace Slic3r { namespace GUI { + +class MultiChoiceDialog : public DPIDialog +{ +public: + MultiChoiceDialog( + wxWindow* parent = nullptr, + const wxString& message = wxEmptyString, + const wxString& caption = wxEmptyString, + const wxArrayString& choices = wxArrayString() + ); + ~MultiChoiceDialog(); + + wxArrayInt GetSelections() const; + + void SetSelections(wxArrayInt sel_array); + +protected: + CheckList* m_check_list; + wxArrayInt m_selected_indices; + void on_dpi_changed(const wxRect &suggested_rect) override; +}; + +}} // namespace Slic3r::GUI + +#endif \ No newline at end of file diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index ccf595b22a..44b34e590e 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -40,7 +40,7 @@ #include "UnsavedChangesDialog.hpp" #include "SavePresetDialog.hpp" #include "EditGCodeDialog.hpp" - +#include "MultiChoiceDialog.hpp" #include "MsgDialog.hpp" #include "Notebook.hpp" @@ -7002,7 +7002,15 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep presets.Add(from_u8(preset.name)); } - wxMultiChoiceDialog dlg(parent, deps.dialog_title, deps.dialog_label, presets); + if(deps.type == Preset::TYPE_PRINTER){ + deps.dialog_title = "Compatible printers"; + deps.dialog_label = "Select printers"; + }else{ + deps.dialog_title = "Compatible process profiles"; + deps.dialog_label = "Select profiles"; + } + + MultiChoiceDialog dlg(parent, deps.dialog_label, deps.dialog_title, presets); wxGetApp().UpdateDlgDarkUI(&dlg); // Collect and set indices of depending_presets marked as compatible. wxArrayInt selections; @@ -7015,13 +7023,16 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep break; } dlg.SetSelections(selections); + dlg.SetSize(FromDIP(wxSize(360, 480))); std::vector value; // Show the dialog. if (dlg.ShowModal() == wxID_OK) { selections.Clear(); selections = dlg.GetSelections(); - for (auto idx : selections) - value.push_back(presets[idx].ToUTF8().data()); + // leave list empty if all items checked. this will check "All" checkbox automatically. also fixes unnecessary config change + if(selections.GetCount() != presets.GetCount()) + for (auto idx : selections) + value.push_back(presets[idx].ToUTF8().data()); if (value.empty()) { deps.checkbox->SetValue(1); deps.btn->Disable(); diff --git a/src/slic3r/GUI/Widgets/CheckList.cpp b/src/slic3r/GUI/Widgets/CheckList.cpp new file mode 100644 index 0000000000..cd0dcebff3 --- /dev/null +++ b/src/slic3r/GUI/Widgets/CheckList.cpp @@ -0,0 +1,225 @@ +#include "CheckList.hpp" + +#include "slic3r/GUI/GUI_App.hpp" + +CheckList::CheckList( + wxWindow* parent, + const wxArrayString& choices, + long scroll_style +) + : wxWindow(parent, wxID_ANY) + , m_search(this, "search", 16) + , m_menu(this, "filter", 16) + , m_first_load(true) +{ + Freeze(); + w_sizer = new wxBoxSizer(wxVERTICAL); + + f_sizer = new wxBoxSizer(wxHORIZONTAL); + f_bar = new wxPanel(this, wxID_ANY); + f_bar->SetBackgroundColour(parent->GetBackgroundColour()); + m_filter_box = new TextInput(f_bar, "", "", "", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); + m_filter_box->SetIcon(m_search.bmp()); + m_filter_box->SetMinSize(FromDIP(wxSize(200,24))); + m_filter_box->SetSize(FromDIP(wxSize(-1,24))); + m_filter_box->SetFocus(); + m_filter_ctrl = m_filter_box->GetTextCtrl(); + m_filter_ctrl->SetFont(Label::Body_13); + m_filter_ctrl->SetSize(wxSize(-1, FromDIP(16))); // Centers text vertically + m_filter_ctrl->SetHint(_L("Type to filter...")); + m_filter_ctrl->Bind(wxEVT_TEXT, [this](auto &e) {Filter(m_filter_ctrl->GetValue());}); + m_filter_ctrl->Bind(wxEVT_TEXT_ENTER, [this](auto &e) {Filter(m_filter_ctrl->GetValue());}); + m_filter_ctrl->Bind(wxEVT_SET_FOCUS, [this](auto &e) {Filter(m_filter_ctrl->GetValue());e.Skip();}); + m_filter_ctrl->Bind(wxEVT_KILL_FOCUS, [this](auto &e) {Filter(m_filter_ctrl->GetValue());e.Skip();}); + f_sizer->Add(m_filter_box, 1, wxEXPAND); + Bind(wxEVT_SET_FOCUS, [this](auto &e) {m_filter_box->SetFocus();}); + + fb_sizer = new wxBoxSizer(wxHORIZONTAL); + auto create_btn = [this] (wxString title, bool select){ + auto btn = new wxStaticText(f_bar, wxID_ANY, title); + btn->SetForegroundColour("#009687"); + btn->SetCursor(wxCURSOR_HAND); + btn->SetFont(Label::Body_13); + btn->Bind(wxEVT_LEFT_DOWN, [this, select](wxMouseEvent &e) {SelectAll(select);}); + fb_sizer->Add(btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + }; + f_sizer->Add(fb_sizer,0 ,wxALIGN_CENTER_VERTICAL); + create_btn(_L("All") , true); + create_btn(_L("None"), false); + + m_menu_button = new wxStaticBitmap(f_bar, wxID_ANY, m_menu.bmp()); + m_menu_button->SetCursor(wxCURSOR_HAND); + m_menu_button->Bind(wxEVT_LEFT_DOWN, &CheckList::ShowMenu, this); + f_sizer->Add(m_menu_button,0 ,wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + f_bar->SetSizerAndFit(f_sizer); + w_sizer->Add(f_bar, 0, wxEXPAND); + + auto spacer = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, FromDIP(3))); + spacer->SetBackgroundColour(parent->GetBackgroundColour()); + w_sizer->Add(spacer, 0, wxEXPAND); + + s_sizer = new wxBoxSizer(wxVERTICAL); + m_scroll_area = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, scroll_style); + m_scroll_area->SetScrollRate(0, 10); + m_scroll_area->SetSizer(s_sizer); + m_scroll_area->SetBackgroundColour(parent->GetBackgroundColour()); + m_scroll_area->Bind(wxEVT_RIGHT_DOWN, &CheckList::ShowMenu, this); + m_scroll_area->DisableFocusFromKeyboard(); + + m_info = new wxStaticText(m_scroll_area, wxID_ANY, ""); + m_info->SetFont(Label::Body_13); + s_sizer->Add(m_info, 1, wxALIGN_CENTER_HORIZONTAL | wxALL, FromDIP(10)); + m_info->Hide(); + + m_info_nonsel = _L("No selected items..."); + m_info_allsel = _L("All items selected..."); + m_info_empty = _L("No matching items..."); + + SetBackgroundColour(StateColor::darkModeColorFor("#DBDBDB")); // draws border on wxScrolledWindow + + w_sizer->Add(m_scroll_area, 1, wxEXPAND | wxALL, FromDIP(1)); // 1 for border + s_sizer->Layout(); + + m_list_size = choices.size(); + + m_checks.reserve(m_list_size); + + auto margin = FromDIP(2); + wxCheckBox* cb; + + for (size_t i = 0; i < m_list_size; ++i){ + cb = new wxCheckBox(m_scroll_area, wxID_ANY, choices[i]); + m_checks.emplace_back(cb); + s_sizer->Add(cb, 0, wxALL, margin); + } + + m_scroll_area->FitInside(); + s_sizer->Layout(); + + SetSizer(w_sizer); + Layout(); + Thaw(); +} + +void CheckList::SetSelections(wxArrayInt sel_array){ + if(!m_first_load){ + for (size_t i = 0; i < m_list_size; ++i) + Check(i, false); + m_first_load = false; + } + + for (int i : sel_array) + Check(i, true); +} + +wxArrayInt CheckList::GetSelections() +{ + wxArrayInt checks; + for (size_t i = 0; i < m_list_size; ++i) + if (m_checks[i]->GetValue()) + checks.push_back(i); + return checks; +} + +void CheckList::Check(int i, bool checked) +{ + if (i > -1 && i < m_list_size) + m_checks[i]->SetValue(checked); +} + +void CheckList::SelectAll(bool value) +{ + for (size_t i = 0; i < m_list_size; ++i) + Check(i, value); +} + +void CheckList::SelectVisible(bool value) +{ + auto filter = m_filter_ctrl->GetValue().Lower(); + if((!value && filter == "::unsel") || (value && filter == "::sel")) + m_filter_ctrl->SetValue(""); + for (size_t i = 0; i < m_list_size; ++i) + if(m_checks[i]->IsShown()) + Check(i, value); +} + +bool CheckList::IsChecked(int i) +{ + if (i > -1 && i < m_list_size) + return false; + return m_checks[i]->GetValue(); +} + +void CheckList::Filter(const wxString& filterText) +{ + Freeze(); + auto filter = filterText.Lower(); + auto c_text = m_filter_ctrl->GetValue().Lower(); + + if(filter == "::sel" || filter == "::nonsel"){ + if(c_text != filter){ // not text input + m_filter_ctrl->SetValue(filter); + m_filter_ctrl->SetSelection(0,-1); + } + fb_sizer->Show(false); + if (filter == "::sel") + for (auto& cb : m_checks) cb->Show(cb->GetValue()); + else + for (auto& cb : m_checks) cb->Show(!cb->GetValue()); + } + else{ + bool clear = filterText.IsEmpty(); + fb_sizer->Show(clear); + for (auto& cb : m_checks) + cb->Show(clear || cb->GetLabel().Lower().Contains(filter)); + } + + m_info->Show(); + for (size_t i = 0; i < m_list_size; ++i) { + if (m_checks[i]->IsShown()){ + m_info->Hide(); + break; + } + } + if (m_info->IsShown()) + m_info->SetLabel(filter == "::sel" ? m_info_nonsel : filter == "::nonsel" ? m_info_allsel : m_info_empty); + + m_scroll_area->FitInside(); + f_sizer->Layout(); + Thaw(); +} + +void CheckList::ShowMenu(wxMouseEvent &evt) +{ + bool filtering = !m_filter_ctrl->GetValue().IsEmpty(); + bool list_empty = m_info->IsShown(); + + wxMenu m; + m.Append(wxID_FILE1, _L("Select All" ))->Enable(!filtering); + m.Append(wxID_FILE2, _L("Deselect All"))->Enable(!filtering); + m.AppendSeparator(); + m.Append(wxID_FILE3, _L("Select visible" ))->Enable(!list_empty && filtering); + m.Append(wxID_FILE4, _L("Deselect visible"))->Enable(!list_empty && filtering); + m.AppendSeparator(); + m.Append(wxID_FILE5, _L("Filter selected" )); + m.Append(wxID_FILE6, _L("Filter nonSelected")); + + m.Bind(wxEVT_MENU, [this](wxCommandEvent& e) { + switch (e.GetId()){ + case wxID_FILE1: SelectAll(true) ; break; + case wxID_FILE2: SelectAll(false) ; break; + case wxID_FILE3: SelectVisible(true) ; break; + case wxID_FILE4: SelectVisible(false); break; + case wxID_FILE5: Filter("::sel") ; break; + case wxID_FILE6: Filter("::nonsel") ; break; + default: break; + } + },wxID_FILE1, wxID_FILE6); + + wxWindow* src = dynamic_cast(evt.GetEventObject()); + if (!src) return; + wxPoint screen_pos = src->ClientToScreen(evt.GetPosition()); + wxPoint local_pos = ScreenToClient(screen_pos); + PopupMenu(&m, local_pos); +} \ No newline at end of file diff --git a/src/slic3r/GUI/Widgets/CheckList.hpp b/src/slic3r/GUI/Widgets/CheckList.hpp new file mode 100644 index 0000000000..bd40e1ad84 --- /dev/null +++ b/src/slic3r/GUI/Widgets/CheckList.hpp @@ -0,0 +1,59 @@ +#ifndef slic3r_GUI_CHECKLIST_hpp_ +#define slic3r_GUI_CHECKLIST_hpp_ + +#include "../wxExtensions.hpp" + +#include "Label.hpp" +#include "TextInput.hpp" + +#include +#include +#include +#include + +class CheckList : public wxWindow +{ +public: + CheckList( + wxWindow* parent, + const wxArrayString& choices = wxArrayString(), + long scroll_style = wxVSCROLL + ); + + wxArrayInt GetSelections(); + void SetSelections(wxArrayInt sel_array); + void Check(int i, bool checked); + bool IsChecked(int i); + + void Filter(const wxString& filterText); + void SelectAll(bool value); + void SelectVisible(bool value); + +private: + void ShowMenu(wxMouseEvent &e); + + std::vector m_checks; + int m_list_size; + bool m_first_load; + wxBoxSizer* w_sizer; + + wxPanel* f_bar; + wxBoxSizer* f_sizer; + TextInput* m_filter_box; + wxTextCtrl* m_filter_ctrl; + wxBoxSizer* fb_sizer; + wxStaticBitmap* m_menu_button; + + wxScrolledWindow* m_scroll_area; + wxBoxSizer* s_sizer; + + wxStaticText* m_info; + wxString m_info_nonsel; + wxString m_info_allsel; + wxString m_info_empty; + + ScalableBitmap m_search; + ScalableBitmap m_menu; +}; + +#endif // !slic3r_GUI_CheckList_hpp_