From 4cdcfe600598b5cb725fa346be6b7aa9e8bd7906 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 4 Feb 2021 09:42:32 +0100 Subject: [PATCH] DiffPresetDialog, non modal to compare two presets. Added menu item "Window->Compare presets" to compare more than one type of presets. Code refactoring for PresetComboBox: Use suffix "modified" just for PlaterPresetComboBox and TabPresetComboBox Code refactoring. wxDataViewCtrl and related functions extracted from DiffPresetDialog and UnsavedChangesDialog to the separate class DiffViewCtrl Code refactoring, Tab.cpp: Build all unregular pages on a first build of the Printer Settings Squashed commit of the following: commit 91f45a8a2d97d252337c7306dd02db607fd79c00 Author: Vojtech Bubnik Date: Thu Feb 4 09:34:29 2021 +0100 Fixed after merge commit 09aa502498b08c059ecdf334f5db1567739b4520 Merge: 6df8e83e1 0baa92fab Author: Vojtech Bubnik Date: Thu Feb 4 09:31:43 2021 +0100 Merge remote-tracking branch 'remotes/origin/master' into ys_diff_dlg commit 6df8e83e1af3ba096ac39d25985b6deb2e0a245b Author: Vojtech Bubnik Date: Thu Feb 4 09:29:53 2021 +0100 Tiny beautification. commit e781ceaaf7c39bc073476409cc02918d9f182930 Author: Vojtech Bubnik Date: Wed Feb 3 18:05:19 2021 +0100 Reworked copying of PresetBundles: 1) Simplified by using the default copy constructors and copy operators. 2) Made safer by not allowing PresetCollection and PhysicalPrinterPresetCollection to be copied or instantiated outside of PresetBundle. 3) Fixed Preset::vendor pointers after copying PresetBundle. commit a8f0b7aa5156465cdb3e3dbb86feb66a64bcebc0 Author: YuSanka Date: Mon Jan 25 16:13:53 2021 +0100 DiffPresetsDialog: Show compatibility to print/printer presets selected in DiffPresetDialog commit 597451f930a4330376966d8cf3350056d4f85a73 Merge: cce5b5710 59017a7da Author: YuSanka Date: Thu Jan 21 12:14:48 2021 +0100 Merge remote-tracking branch 'origin/master' into ys_diff_dlg commit cce5b5710d1225c7eaa52d0abb4b3f66fbd3b6a1 Author: YuSanka Date: Thu Jan 21 12:11:40 2021 +0100 DiffPresetDialog: Fixed crash on ASAN build commit ad8d728d89d7a75c2ac136ea8cdbb730ab774cd9 Author: YuSanka Date: Wed Jan 20 16:38:28 2021 +0100 FullCompareDialog improvements commit 316bafbeec9bb63f731b653d296062d9e22dd02f Author: YuSanka Date: Wed Jan 20 09:25:46 2021 +0100 DiffPresetDialog improvements: * Update presets lists after save changes in edited preset or change printer_technology * Tab.cpp: Build all unregular pages on a first build of the Printer Settings commit 01171c8ef50389d0801b0ccc5746ab0c3b731b11 Author: YuSanka Date: Tue Jan 19 17:26:28 2021 +0100 DiffPresetDialog : improvements * This dialog is non-modal now * "Compare presets" menu item is moved from "View" to the "Window" menu group * Added info tooltips for the bitmap buttons between presets * Added "Show all presets" checkbox * Show full difference between printer presets with different count of extruders commit 3d38b62f3c9de8aab352ed3bcff297d5c0239b2e Merge: e50fe6de9 9a2310ae9 Author: YuSanka Date: Mon Jan 18 10:00:18 2021 +0100 Merge remote-tracking branch 'origin/master' into ys_diff_dlg commit e50fe6de984decadfef3d4b952f0eec8ab9e6670 Author: YuSanka Date: Sat Jan 16 02:27:24 2021 +0100 DiffPresetDialog improvements Added menu item "View->Compare presets" to compare more than one type of presets commit bf08b7c8c969ad0ca631b9eeac1bc231f474e72b Author: YuSanka Date: Sat Jan 16 00:28:39 2021 +0100 Code refactoring. wxDataViewCtrl and related functions extracted from DiffPresetDialog and UnsavedChangesDialog to the separate class DiffViewCtrl commit 7aeb63032b0bc2bd06e60990c060008660a5c44f Author: YuSanka Date: Fri Jan 15 21:35:43 2021 +0100 DiffPresetDialog: first implementation commit 2f23d5af9d6b5dd670dfb7d06eedbe0e6d2960c6 Author: YuSanka Date: Thu Jan 14 16:28:43 2021 +0100 Code refactoring for PresetComboBox. Use suffix "modified" just for PlaterPresetComboBox and TabPresetComboBox --- resources/icons/compare.svg | 30 + resources/icons/equal.svg | 15 + resources/icons/not_equal.svg | 16 + src/libslic3r/Preset.cpp | 16 +- src/libslic3r/Preset.hpp | 40 +- src/libslic3r/PresetBundle.cpp | 27 +- src/libslic3r/PresetBundle.hpp | 3 +- src/slic3r/GUI/MainFrame.cpp | 5 + src/slic3r/GUI/MainFrame.hpp | 2 + src/slic3r/GUI/PresetComboBoxes.cpp | 77 +- src/slic3r/GUI/PresetComboBoxes.hpp | 10 +- src/slic3r/GUI/Search.cpp | 47 ++ src/slic3r/GUI/Search.hpp | 1 + src/slic3r/GUI/Tab.cpp | 82 ++- src/slic3r/GUI/Tab.hpp | 4 +- src/slic3r/GUI/UnsavedChangesDialog.cpp | 934 +++++++++++++++++++----- src/slic3r/GUI/UnsavedChangesDialog.hpp | 156 ++-- 17 files changed, 1150 insertions(+), 315 deletions(-) create mode 100644 resources/icons/compare.svg create mode 100644 resources/icons/equal.svg create mode 100644 resources/icons/not_equal.svg diff --git a/resources/icons/compare.svg b/resources/icons/compare.svg new file mode 100644 index 000000000..fcb458f7c --- /dev/null +++ b/resources/icons/compare.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + diff --git a/resources/icons/equal.svg b/resources/icons/equal.svg new file mode 100644 index 000000000..bce4a24f7 --- /dev/null +++ b/resources/icons/equal.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/resources/icons/not_equal.svg b/resources/icons/not_equal.svg new file mode 100644 index 000000000..bc8814435 --- /dev/null +++ b/resources/icons/not_equal.svg @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index f535555d0..b7e966c9e 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -616,10 +616,6 @@ PresetCollection::PresetCollection(Preset::Type type, const std::vector m_num_default_presets) { @@ -1278,6 +1274,18 @@ std::vector PresetCollection::merge_presets(PresetCollection &&othe return duplicates; } +void PresetCollection::update_vendor_ptrs_after_copy(const VendorMap &new_vendors) +{ + for (Preset &preset : m_presets) + if (preset.vendor != nullptr) { + assert(! preset.is_default && ! preset.is_external); + // Re-assign a pointer to the vendor structure in the new PresetBundle. + auto it = new_vendors.find(preset.vendor->id); + assert(it != new_vendors.end()); + preset.vendor = &it->second; + } +} + void PresetCollection::update_map_alias_to_profile_name() { m_map_alias_to_profile_name.clear(); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index b6d44d58f..d81717f0e 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -115,13 +115,11 @@ public: TYPE_COUNT, }; - Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {} - Type type = TYPE_INVALID; // The preset represents a "default" set of properties, // pulled from the default values of the PrintConfig (see PrintConfigDef for their definitions). - bool is_default; + bool is_default = false; // External preset points to a configuration, which has been loaded but not imported // into the Slic3r default configuration location. bool is_external = false; @@ -233,6 +231,9 @@ public: static std::string remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config); protected: + Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {} + Preset() = default; + friend class PresetCollection; friend class PresetBundle; }; @@ -256,7 +257,6 @@ class PresetCollection public: // Initialize the PresetCollection with the "- default -" preset. PresetCollection(Preset::Type type, const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name = "- default -"); - ~PresetCollection(); typedef std::deque::iterator Iterator; typedef std::deque::const_iterator ConstIterator; @@ -460,6 +460,15 @@ public: size_t num_default_presets() { return m_num_default_presets; } protected: + PresetCollection() = default; + // Copy constructor and copy operators are not to be used from outside PresetBundle, + // as the Profile::vendor points to an instance of VendorProfile stored at parent PresetBundle! + PresetCollection(const PresetCollection &other) = default; + PresetCollection& operator=(const PresetCollection &other) = default; + // After copying a collection with the default operators above, call this function + // to adjust Profile::vendor pointers. + void update_vendor_ptrs_after_copy(const VendorMap &vendors); + // Select a preset, if it exists. If it does not exist, select an invalid (-1) index. // This is a temporary state, which shall be fixed immediately by the following step. bool select_preset_by_name_strict(const std::string &name); @@ -474,10 +483,6 @@ protected: void update_map_system_profile_renamed(); private: - PresetCollection(); - PresetCollection(const PresetCollection &other); - PresetCollection& operator=(const PresetCollection &other); - // Find a preset position in the sorted list of presets. // The "-- default -- " preset is always the first, so it needs // to be handled differently. @@ -507,9 +512,9 @@ private: { return const_cast(this)->find_preset_renamed(name); } size_t update_compatible_internal(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, PresetSelectCompatibleType unselect_if_incompatible); - +public: static std::vector dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type = false); - +private: // Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER. Preset::Type m_type; // List of presets, starting with the "- default -" preset. @@ -531,7 +536,7 @@ private: // Path to the directory to store the config files into. std::string m_dir_path; - // to access select_preset_by_name_strict() + // to access select_preset_by_name_strict() and the default & copy constructors. friend class PresetBundle; }; @@ -542,9 +547,17 @@ class PrinterPresetCollection : public PresetCollection public: PrinterPresetCollection(Preset::Type type, const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name = "- default -") : PresetCollection(type, keys, defaults, default_name) {} + const Preset& default_preset_for(const DynamicPrintConfig &config) const override; const Preset* find_by_model_id(const std::string &model_id) const; + +private: + PrinterPresetCollection() = default; + PrinterPresetCollection(const PrinterPresetCollection &other) = default; + PrinterPresetCollection& operator=(const PrinterPresetCollection &other) = default; + + friend class PresetBundle; }; namespace PresetUtils { @@ -634,7 +647,6 @@ class PhysicalPrinterCollection { public: PhysicalPrinterCollection(const std::vector& keys); - ~PhysicalPrinterCollection() {} typedef std::deque::iterator Iterator; typedef std::deque::const_iterator ConstIterator; @@ -725,7 +737,9 @@ public: const DynamicPrintConfig& default_config() const { return m_default_config; } private: - PhysicalPrinterCollection& operator=(const PhysicalPrinterCollection& other); + friend class PresetBundle; + PhysicalPrinterCollection() = default; + PhysicalPrinterCollection& operator=(const PhysicalPrinterCollection& other) = default; // Find a physical printer position in the sorted list of printers. // The name of a printer should be unique and case insensitive diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index d9b1ed76e..bfc1e222c 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -105,8 +105,33 @@ PresetBundle::PresetBundle() : this->project_config.apply_only(FullPrintConfig::defaults(), s_project_options); } -PresetBundle::~PresetBundle() +PresetBundle::PresetBundle(const PresetBundle &rhs) { + *this = rhs; +} + +PresetBundle& PresetBundle::operator=(const PresetBundle &rhs) +{ + prints = rhs.prints; + sla_prints = rhs.sla_prints; + filaments = rhs.filaments; + sla_materials = rhs.sla_materials; + printers = rhs.printers; + physical_printers = rhs.physical_printers; + + filament_presets = rhs.filament_presets; + project_config = rhs.project_config; + vendors = rhs.vendors; + obsolete_presets = rhs.obsolete_presets; + + // Adjust Preset::vendor pointers to point to the copied vendors map. + prints .update_vendor_ptrs_after_copy(this->vendors); + sla_prints .update_vendor_ptrs_after_copy(this->vendors); + filaments .update_vendor_ptrs_after_copy(this->vendors); + sla_materials.update_vendor_ptrs_after_copy(this->vendors); + printers .update_vendor_ptrs_after_copy(this->vendors); + + return *this; } void PresetBundle::reset(bool delete_files) diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 5d7cc84ba..f98b96533 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -15,7 +15,8 @@ class PresetBundle { public: PresetBundle(); - ~PresetBundle(); + PresetBundle(const PresetBundle &rhs); + PresetBundle& operator=(const PresetBundle &rhs); // Remove all the presets but the "-- default --". // Optionally remove all the files referenced by the presets from the user profile directory. diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 8e6b1c5ef..9c30ac330 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -37,6 +37,7 @@ #include #include "GUI_App.hpp" +#include "UnsavedChangesDialog.hpp" #ifdef _WIN32 #include @@ -1190,6 +1191,10 @@ void MainFrame::init_menubar_as_editor() windowMenu->AppendSeparator(); append_menu_item(windowMenu, wxID_ANY, _L("Open new instance") + "\tCtrl+Shift+I", _L("Open a new PrusaSlicer instance"), [this](wxCommandEvent&) { start_new_slicer(); }, "", nullptr, [this]() {return m_plater != nullptr && wxGetApp().app_config->get("single_instance") != "1"; }, this); + + windowMenu->AppendSeparator(); + append_menu_item(windowMenu, wxID_ANY, _L("Compare presets")/* + "\tCtrl+F"*/, _L("Compare presets"), + [this](wxCommandEvent&) { diff_dialog.show();}, "compare", nullptr, []() {return true; }, this); } // View menu diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 9504376b4..c39527409 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -16,6 +16,7 @@ #include "GUI_Utils.hpp" #include "Event.hpp" +#include "UnsavedChangesDialog.hpp" class wxNotebook; class wxProgressDialog; @@ -190,6 +191,7 @@ public: Plater* m_plater { nullptr }; wxNotebook* m_tabpanel { nullptr }; SettingsDialog m_settings_dialog; + DiffPresetDialog diff_dialog; wxWindow* m_plater_page{ nullptr }; wxProgressDialog* m_progress_dialog { nullptr }; PrintHostQueueDialog* m_printhost_queue_dlg; diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 60bb9a5ae..38d45f172 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -62,12 +62,12 @@ namespace GUI { * control size calculation methods (virtual) are overridden. **/ -PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxSize& size) : +PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxSize& size, PresetBundle* preset_bundle/* = nullptr*/) : wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, size, 0, nullptr, wxCB_READONLY), m_type(preset_type), m_last_selected(wxNOT_FOUND), m_em_unit(em_unit(this)), - m_preset_bundle(wxGetApp().preset_bundle) + m_preset_bundle(preset_bundle ? preset_bundle : wxGetApp().preset_bundle) { SetFont(wxGetApp().normal_font()); #ifdef _WIN32 @@ -208,6 +208,21 @@ void PresetComboBox::update_selection() #endif } +static std::string suffix(const Preset& preset) +{ + return (preset.is_dirty ? Preset::suffix_modified() : ""); +} + +static std::string suffix(Preset* preset) +{ + return (preset->is_dirty ? Preset::suffix_modified() : ""); +} + +wxString PresetComboBox::get_preset_name(const Preset & preset) +{ + return from_u8(preset.name/* + suffix(preset)*/); +} + void PresetComboBox::update(std::string select_preset_name) { Freeze(); @@ -226,7 +241,7 @@ void PresetComboBox::update(std::string select_preset_name) for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) { const Preset& preset = presets[i]; - if (!preset.is_visible || !preset.is_compatible) + if (!m_show_all && (!preset.is_visible || !preset.is_compatible)) continue; // marker used for disable incompatible printer models for the selected physical printer @@ -246,17 +261,17 @@ void PresetComboBox::update(std::string select_preset_name) assert(bmp); if (!is_enabled) - incomp_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), bmp); + incomp_presets.emplace(get_preset_name(preset), bmp); else if (preset.is_default || preset.is_system) { - Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); + Append(get_preset_name(preset), *bmp); validate_selection(preset.name == select_preset_name); } else { - nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), std::pair(bmp, is_enabled)); + nonsys_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); if (preset.name == select_preset_name || (select_preset_name.empty() && is_enabled)) - selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); + selected = get_preset_name(preset); } if (i + 1 == m_collection->num_default_presets()) set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); @@ -329,11 +344,22 @@ bool PresetComboBox::del_physical_printer(const wxString& note_string/* = wxEmpt return true; } +void PresetComboBox::show_all(bool show_all) +{ + m_show_all = show_all; + update(); +} + void PresetComboBox::update() { this->update(into_u8(this->GetString(this->GetSelection()))); } +void PresetComboBox::update_from_bundle() +{ + this->update(m_collection->get_selected_preset().name); +} + void PresetComboBox::msw_rescale() { m_em_unit = em_unit(this); @@ -745,6 +771,12 @@ void PlaterPresetComboBox::show_edit_menu() wxGetApp().plater()->PopupMenu(menu); } +wxString PlaterPresetComboBox::get_preset_name(const Preset& preset) +{ + std::string name = preset.alias.empty() ? preset.name : preset.alias; + return from_u8(name + suffix(preset)); +} + // Only the compatible presets are shown. // If an incompatible preset is selected, it is shown as well. void PlaterPresetComboBox::update() @@ -821,17 +853,17 @@ void PlaterPresetComboBox::update() const std::string name = preset.alias.empty() ? preset.name : preset.alias; if (preset.is_default || preset.is_system) { - Append(wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); + Append(get_preset_name(preset), *bmp); validate_selection(is_selected); if (is_selected) - tooltip = wxString::FromUTF8(preset.name.c_str()); + tooltip = from_u8(preset.name); } else { - nonsys_presets.emplace(wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), bmp); + nonsys_presets.emplace(get_preset_name(preset), bmp); if (is_selected) { - selected_user_preset = wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); - tooltip = wxString::FromUTF8(preset.name.c_str()); + selected_user_preset = get_preset_name(preset); + tooltip = from_u8(preset.name); } } if (i + 1 == m_collection->num_default_presets()) @@ -862,7 +894,7 @@ void PlaterPresetComboBox::update() wxBitmap* bmp = get_bmp(main_icon_name, wide_icons, main_icon_name); assert(bmp); - set_label_marker(Append(wxString::FromUTF8((it->get_full_name(preset_name) + (preset->is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); + set_label_marker(Append(from_u8(it->get_full_name(preset_name) + suffix(preset)), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); validate_selection(ph_printers.is_selected(it, preset_name)); } } @@ -946,6 +978,11 @@ TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type) }); } +wxString TabPresetComboBox::get_preset_name(const Preset& preset) +{ + return from_u8(preset.name + suffix(preset)); +} + // Update the choice UI from the list of presets. // If show_incompatible, all presets are shown, otherwise only the compatible presets are shown. // If an incompatible preset is selected, it is shown as well. @@ -991,7 +1028,7 @@ void TabPresetComboBox::update() assert(bmp); if (preset.is_default || preset.is_system) { - int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); + int item_id = Append(get_preset_name(preset), *bmp); if (!is_enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); validate_selection(i == idx_selected); @@ -999,9 +1036,9 @@ void TabPresetComboBox::update() else { std::pair pair(bmp, is_enabled); - nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), std::pair(bmp, is_enabled)); + nonsys_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); if (i == idx_selected) - selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); + selected = get_preset_name(preset); } if (i + 1 == m_collection->num_default_presets()) set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); @@ -1035,7 +1072,7 @@ void TabPresetComboBox::update() wxBitmap* bmp = get_bmp(main_icon_name, main_icon_name, "", true, true, false); assert(bmp); - set_label_marker(Append(wxString::FromUTF8((it->get_full_name(preset_name) + (preset->is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); + set_label_marker(Append(from_u8(it->get_full_name(preset_name) + suffix(preset)), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); validate_selection(ph_printers.is_selected(it, preset_name)); } } @@ -1082,15 +1119,15 @@ void TabPresetComboBox::update_dirty() preset_name = PhysicalPrinter::get_preset_name(preset_name); } - const Preset* preset = m_collection->find_preset(preset_name, false); + Preset* preset = m_collection->find_preset(preset_name, false); if (preset) { - std::string new_label = preset->is_dirty ? preset->name + Preset::suffix_modified() : preset->name; + std::string new_label = preset->name + suffix(preset); if (marker == LABEL_ITEM_PHYSICAL_PRINTER) new_label = ph_printer_name + PhysicalPrinter::separator() + new_label; if (old_label != new_label) - SetString(ui_id, wxString::FromUTF8(new_label.c_str())); + SetString(ui_id, from_u8(new_label)); } } #ifdef __APPLE__ diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index d3cc6277d..6f41c95f4 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -30,8 +30,9 @@ class BitmapCache; // BitmapComboBox used to presets list on Sidebar and Tabs class PresetComboBox : public wxBitmapComboBox { + bool m_show_all { false }; public: - PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxSize& size = wxDefaultSize); + PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxSize& size = wxDefaultSize, PresetBundle* preset_bundle = nullptr); ~PresetComboBox(); enum LabelItemType { @@ -58,11 +59,16 @@ public: bool selection_is_changed_according_to_physical_printers(); void update(std::string select_preset); + // select preset which is selected in PreseBundle + void update_from_bundle(); void edit_physical_printer(); void add_physical_printer(); bool del_physical_printer(const wxString& note_string = wxEmptyString); + virtual wxString get_preset_name(const Preset& preset); + Preset::Type get_type() { return m_type; } + void show_all(bool show_all); virtual void update(); virtual void msw_rescale(); @@ -158,6 +164,7 @@ public: void show_add_menu(); void show_edit_menu(); + wxString get_preset_name(const Preset& preset) override; void update() override; void msw_rescale() override; @@ -182,6 +189,7 @@ public: show_incompatible = show_incompatible_presets; } + wxString get_preset_name(const Preset& preset) override; void update() override; void update_dirty(); void msw_rescale() override; diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 2b7b3f3d9..03aa11eb6 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -326,6 +326,53 @@ const Option& OptionsSearcher::get_option(const std::string& opt_key) const return options[it - options.begin()]; } +static Option create_option(const std::string& opt_key, const wxString& label, Preset::Type type, const GroupAndCategory& gc) +{ + wxString suffix; + wxString suffix_local; + if (gc.category == "Machine limits") { + suffix = opt_key.back() == '1' ? L("Stealth") : L("Normal"); + suffix_local = " " + _(suffix); + suffix = " " + suffix; + } + + wxString category = gc.category; + if (type == Preset::TYPE_PRINTER && category.Contains("Extruder ")) { + std::string opt_idx = opt_key.substr(opt_key.find("#") + 1); + category = wxString::Format("%s %d", "Extruder", atoi(opt_idx.c_str()) + 1); + } + + return Option{ boost::nowide::widen(opt_key), type, + (label + suffix).ToStdWstring(), (_(label) + suffix_local).ToStdWstring(), + gc.group.ToStdWstring(), _(gc.group).ToStdWstring(), + gc.category.ToStdWstring(), GUI::Tab::translate_category(category, type).ToStdWstring() }; +} + +Option OptionsSearcher::get_option(const std::string& opt_key, const wxString& label, Preset::Type type) const +{ + auto it = std::lower_bound(options.begin(), options.end(), Option({ boost::nowide::widen(opt_key) })); + if(it->opt_key == boost::nowide::widen(opt_key)) + return options[it - options.begin()]; + if (groups_and_categories.find(opt_key) == groups_and_categories.end()) { + size_t pos = opt_key.find('#'); + if (pos == std::string::npos) + return options[it - options.begin()]; + + std::string zero_opt_key = opt_key.substr(0, pos + 1) + "0"; + + if(groups_and_categories.find(zero_opt_key) == groups_and_categories.end()) + return options[it - options.begin()]; + + return create_option(opt_key, label, type, groups_and_categories.at(zero_opt_key)); + } + + const GroupAndCategory& gc = groups_and_categories.at(opt_key); + if (gc.group.IsEmpty() || gc.category.IsEmpty()) + return options[it - options.begin()]; + + return create_option(opt_key, label, type, gc); +} + void OptionsSearcher::add_key(const std::string& opt_key, const wxString& group, const wxString& category) { groups_and_categories[opt_key] = GroupAndCategory{group, category}; diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index f8c9dffa6..1f2909564 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -117,6 +117,7 @@ public: const FoundOption& operator[](const size_t pos) const noexcept { return found[pos]; } const Option& get_option(size_t pos_in_filter) const; const Option& get_option(const std::string& opt_key) const; + Option get_option(const std::string& opt_key, const wxString& label, Preset::Type type) const; const std::vector& found_options() { return found; } const GroupAndCategory& get_group_and_category (const std::string& opt_key) { return groups_and_categories[opt_key]; } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 4243595a0..cdcc98d06 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -196,6 +196,7 @@ void Tab::create_preset_tab() m_scaled_buttons.reserve(6); m_scaled_buttons.reserve(2); + add_scaled_button(panel, &m_btn_compare_preset, "compare"); add_scaled_button(panel, &m_btn_save_preset, "save"); add_scaled_button(panel, &m_btn_delete_preset, "cross"); if (m_type == Preset::Type::TYPE_PRINTER) @@ -207,6 +208,7 @@ void Tab::create_preset_tab() add_scaled_button(panel, &m_btn_hide_incompatible_presets, m_bmp_hide_incompatible_presets.name()); + m_btn_compare_preset->SetToolTip(_L("Compare this preset with some another")); // TRN "Save current Settings" m_btn_save_preset->SetToolTip(from_u8((boost::format(_utf8(L("Save current %s"))) % m_title).str())); m_btn_delete_preset->SetToolTip(_(L("Delete this preset"))); @@ -271,6 +273,9 @@ void Tab::create_preset_tab() m_hsizer->Add(m_undo_btn, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->AddSpacer(int(32 * scale_factor)); m_hsizer->Add(m_search_btn, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->AddSpacer(int(8*scale_factor)); + m_hsizer->Add(m_btn_compare_preset, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->AddSpacer(int(16*scale_factor)); // m_hsizer->AddStretchSpacer(32); // StretchSpacer has a strange behavior under OSX, so // There is used just additional sizer for m_mode_sizer with right alignment @@ -338,6 +343,7 @@ void Tab::create_preset_tab() m_page_view->SetScrollbars(1, 20, 1, 2); m_hsizer->Add(m_page_view, 1, wxEXPAND | wxLEFT, 5); + m_btn_compare_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { compare_preset(); })); m_btn_save_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { save_preset(); })); m_btn_delete_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { delete_preset(); })); m_btn_hide_incompatible_presets->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { @@ -2066,11 +2072,18 @@ bool Tab::current_preset_is_dirty() void TabPrinter::build() { m_presets = &m_preset_bundle->printers; - load_initial_data(); - m_printer_technology = m_presets->get_selected_preset().printer_technology(); - m_presets->get_selected_preset().printer_technology() == ptSLA ? build_sla() : build_fff(); + // For DiffPresetDialog we use options list which is saved in Searcher class. + // Options for the Searcher is added in the moment of pages creation. + // So, build first of all printer pages for non-selected printer technology... + std::string def_preset_name = "- default " + std::string(m_printer_technology == ptSLA ? "FFF" : "SLA") + " -"; + m_config = &m_presets->find_preset(def_preset_name)->config; + m_printer_technology == ptSLA ? build_fff() : build_sla(); + + // ... and than for selected printer technology + load_initial_data(); + m_printer_technology == ptSLA ? build_sla() : build_fff(); } void TabPrinter::build_print_host_upload_group(Page* page) @@ -2105,7 +2118,8 @@ void TabPrinter::build_fff() m_initial_extruders_count = m_extruders_count = nozzle_diameter->values.size(); wxGetApp().sidebar().update_objects_list_extruder_column(m_initial_extruders_count); - const Preset* parent_preset = m_presets->get_selected_preset_parent(); + const Preset* parent_preset = m_printer_technology == ptSLA ? nullptr // just for first build, if SLA printer preset is selected + : m_presets->get_selected_preset_parent(); m_sys_extruders_count = parent_preset == nullptr ? 0 : static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); @@ -2289,7 +2303,7 @@ void TabPrinter::build_fff() build_preset_description_line(optgroup.get()); - build_unregular_pages(); + build_unregular_pages(true); } void TabPrinter::build_sla() @@ -2395,7 +2409,9 @@ void TabPrinter::append_option_line(ConfigOptionsGroupShp optgroup, const std::s auto option = optgroup->get_option(opt_key, 0); auto line = Line{ option.opt.full_label, "" }; line.append_option(option); - if (m_use_silent_mode) + if (m_use_silent_mode + || m_printer_technology == ptSLA // just for first build, if SLA printer preset is selected + ) line.append_option(optgroup->get_option(opt_key, 1)); optgroup->append_line(line); } @@ -2470,7 +2486,7 @@ PageShp TabPrinter::build_kinematics_page() * but "Machine limits" and "Single extruder MM setup" too * (These pages can changes according to the another values of a current preset) * */ -void TabPrinter::build_unregular_pages() +void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/) { size_t n_before_extruders = 2; // Count of pages before Extruder pages bool is_marlin_flavor = m_config->option>("gcode_flavor")->value == gcfMarlin; @@ -2481,18 +2497,6 @@ void TabPrinter::build_unregular_pages() * */ Freeze(); -#ifdef __WXMSW__ - /* Workaround for correct layout of controls inside the created page: - * In some _strange_ way we should we should imitate page resizing. - */ -/* auto layout_page = [this](PageShp page) - { - const wxSize& sz = page->GetSize(); - page->SetSize(sz.x + 1, sz.y + 1); - page->SetSize(sz); - };*/ -#endif //__WXMSW__ - // Add/delete Kinematics page according to is_marlin_flavor size_t existed_page = 0; for (size_t i = n_before_extruders; i < m_pages.size(); ++i) // first make sure it's not there already @@ -2504,12 +2508,12 @@ void TabPrinter::build_unregular_pages() break; } - if (existed_page < n_before_extruders && is_marlin_flavor) { + if (existed_page < n_before_extruders && (is_marlin_flavor || from_initial_build)) { auto page = build_kinematics_page(); -#ifdef __WXMSW__ -// layout_page(page); -#endif - m_pages.insert(m_pages.begin() + n_before_extruders, page); + if (from_initial_build) + page->clear(); + else + m_pages.insert(m_pages.begin() + n_before_extruders, page); } if (is_marlin_flavor) @@ -2527,7 +2531,8 @@ void TabPrinter::build_unregular_pages() } m_has_single_extruder_MM_page = false; } - if (m_extruders_count > 1 && m_config->opt_bool("single_extruder_multi_material") && !m_has_single_extruder_MM_page) { + if (from_initial_build || + (m_extruders_count > 1 && m_config->opt_bool("single_extruder_multi_material") && !m_has_single_extruder_MM_page)) { // create a page, but pretend it's an extruder page, so we can add it to m_pages ourselves auto page = add_options_page(L("Single extruder MM setup"), "printer", true); auto optgroup = page->new_optgroup(L("Single extruder multimaterial parameters")); @@ -2536,8 +2541,12 @@ void TabPrinter::build_unregular_pages() optgroup->append_single_option_line("parking_pos_retraction"); optgroup->append_single_option_line("extra_loading_move"); optgroup->append_single_option_line("high_current_on_filament_swap"); - m_pages.insert(m_pages.end() - n_after_single_extruder_MM, page); - m_has_single_extruder_MM_page = true; + if (from_initial_build) + page->clear(); + else { + m_pages.insert(m_pages.end() - n_after_single_extruder_MM, page); + m_has_single_extruder_MM_page = true; + } } // Build missed extruder pages @@ -2642,10 +2651,6 @@ void TabPrinter::build_unregular_pages() line = optgroup->create_single_option_line("extruder_colour", wxEmptyString, extruder_idx); line.append_widget(reset_to_filament_color); optgroup->append_line(line); - -#ifdef __WXMSW__ -// layout_page(page); -#endif } // # remove extra pages @@ -2656,6 +2661,10 @@ void TabPrinter::build_unregular_pages() Thaw(); m_extruders_count_old = m_extruders_count; + + if (m_printer_technology == ptSLA/*from_initial_build*/) + return; // next part of code is no needed to execute at this moment + rebuild_page_tree(); // Reload preset pages with current configuration values @@ -3187,6 +3196,9 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, load_current_preset(); } + + if (technology_changed) + wxGetApp().mainframe->diff_dialog.update_presets(); } // If the current preset is dirty, the user is asked whether the changes may be discarded. @@ -3374,6 +3386,11 @@ void Tab::OnKeyDown(wxKeyEvent& event) event.Skip(); } +void Tab::compare_preset() +{ + wxGetApp().mainframe->diff_dialog.show(m_type); +} + // Save the current preset into file. // This removes the "dirty" flag of the preset, possibly creates a new preset under a new name, // and activates the new preset. @@ -3443,6 +3460,9 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) for (Preset::Type preset_type : dependent) wxGetApp().get_tab(preset_type)->update_tab_ui(); } + + // update preset comboboxes in DiffPresetDlg + wxGetApp().mainframe->diff_dialog.update_presets(m_type); } // Called for a currently selected preset. diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 29588ba20..0358fd315 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -112,6 +112,7 @@ protected: const wxString m_title; TabPresetComboBox* m_presets_choice; ScalableButton* m_search_btn; + ScalableButton* m_btn_compare_preset; ScalableButton* m_btn_save_preset; ScalableButton* m_btn_delete_preset; ScalableButton* m_btn_edit_ph_printer {nullptr}; @@ -290,6 +291,7 @@ public: void OnTreeSelChange(wxTreeEvent& event); void OnKeyDown(wxKeyEvent& event); + void compare_preset(); void save_preset(std::string name = std::string(), bool detach = false); void delete_preset(); void toggle_show_hide_incompatible(); @@ -456,7 +458,7 @@ public: void update_pages(); // update m_pages according to printer technology void extruders_count_changed(size_t extruders_count); PageShp build_kinematics_page(); - void build_unregular_pages(); + void build_unregular_pages(bool from_initial_build = false); void on_preset_loaded() override; void init_options_list() override; void msw_rescale() override; diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 68487921d..2727a2982 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include "libslic3r/PrintConfig.hpp" #include "libslic3r/PresetBundle.hpp" #include "format.hpp" @@ -22,6 +24,7 @@ //#include "fts_fuzzy_match.h" #include "BitmapCache.hpp" +#include "PresetComboBoxes.hpp" using boost::optional; @@ -36,7 +39,7 @@ namespace Slic3r { namespace GUI { // ---------------------------------------------------------------------------- -// ModelNode: a node inside UnsavedChangesModel +// ModelNode: a node inside DiffModel // ---------------------------------------------------------------------------- static const std::map type_icon_names = { @@ -215,21 +218,15 @@ void ModelNode::UpdateIcons() // ---------------------------------------------------------------------------- -// UnsavedChangesModel +// DiffModel // ---------------------------------------------------------------------------- -UnsavedChangesModel::UnsavedChangesModel(wxWindow* parent) : +DiffModel::DiffModel(wxWindow* parent) : m_parent_win(parent) { } -UnsavedChangesModel::~UnsavedChangesModel() -{ - for (ModelNode* preset_node : m_preset_nodes) - delete preset_node; -} - -wxDataViewItem UnsavedChangesModel::AddPreset(Preset::Type type, wxString preset_name, PrinterTechnology pt) +wxDataViewItem DiffModel::AddPreset(Preset::Type type, wxString preset_name, PrinterTechnology pt) { // "color" strings color_string(preset_name, def_text_color()); @@ -245,7 +242,7 @@ wxDataViewItem UnsavedChangesModel::AddPreset(Preset::Type type, wxString preset return child; } -ModelNode* UnsavedChangesModel::AddOption(ModelNode* group_node, wxString option_name, wxString old_value, wxString new_value) +ModelNode* DiffModel::AddOption(ModelNode* group_node, wxString option_name, wxString old_value, wxString new_value) { group_node->Append(std::make_unique(group_node, option_name, old_value, new_value)); ModelNode* option = group_node->GetChildren().back().get(); @@ -256,7 +253,7 @@ ModelNode* UnsavedChangesModel::AddOption(ModelNode* group_node, wxString option return option; } -ModelNode* UnsavedChangesModel::AddOptionWithGroup(ModelNode* category_node, wxString group_name, wxString option_name, wxString old_value, wxString new_value) +ModelNode* DiffModel::AddOptionWithGroup(ModelNode* category_node, wxString group_name, wxString option_name, wxString old_value, wxString new_value) { category_node->Append(std::make_unique(category_node, group_name)); ModelNode* group_node = category_node->GetChildren().back().get(); @@ -265,7 +262,7 @@ ModelNode* UnsavedChangesModel::AddOptionWithGroup(ModelNode* category_node, wxS return AddOption(group_node, option_name, old_value, new_value); } -ModelNode* UnsavedChangesModel::AddOptionWithGroupAndCategory(ModelNode* preset_node, wxString category_name, wxString group_name, +ModelNode* DiffModel::AddOptionWithGroupAndCategory(ModelNode* preset_node, wxString category_name, wxString group_name, wxString option_name, wxString old_value, wxString new_value, const std::string category_icon_name) { preset_node->Append(std::make_unique(preset_node, category_name, category_icon_name)); @@ -275,7 +272,7 @@ ModelNode* UnsavedChangesModel::AddOptionWithGroupAndCategory(ModelNode* preset_ return AddOptionWithGroup(category_node, group_name, option_name, old_value, new_value); } -wxDataViewItem UnsavedChangesModel::AddOption(Preset::Type type, wxString category_name, wxString group_name, wxString option_name, +wxDataViewItem DiffModel::AddOption(Preset::Type type, wxString category_name, wxString group_name, wxString option_name, wxString old_value, wxString new_value, const std::string category_icon_name) { // "color" strings @@ -288,7 +285,7 @@ wxDataViewItem UnsavedChangesModel::AddOption(Preset::Type type, wxString catego make_string_bold(group_name); // add items - for (ModelNode* preset : m_preset_nodes) + for (std::unique_ptr& preset : m_preset_nodes) if (preset->type() == type) { for (std::unique_ptr &category : preset->GetChildren()) @@ -301,7 +298,7 @@ wxDataViewItem UnsavedChangesModel::AddOption(Preset::Type type, wxString catego return wxDataViewItem((void*)AddOptionWithGroup(category.get(), group_name, option_name, old_value, new_value)); } - return wxDataViewItem((void*)AddOptionWithGroupAndCategory(preset, category_name, group_name, option_name, old_value, new_value, category_icon_name)); + return wxDataViewItem((void*)AddOptionWithGroupAndCategory(preset.get(), category_name, group_name, option_name, old_value, new_value, category_icon_name)); } return wxDataViewItem(nullptr); @@ -336,7 +333,7 @@ static void update_parents(ModelNode* node) } } -void UnsavedChangesModel::UpdateItemEnabling(wxDataViewItem item) +void DiffModel::UpdateItemEnabling(wxDataViewItem item) { assert(item.IsOk()); ModelNode* node = static_cast(item.GetID()); @@ -346,14 +343,14 @@ void UnsavedChangesModel::UpdateItemEnabling(wxDataViewItem item) update_parents(node); } -bool UnsavedChangesModel::IsEnabledItem(const wxDataViewItem& item) +bool DiffModel::IsEnabledItem(const wxDataViewItem& item) { assert(item.IsOk()); ModelNode* node = static_cast(item.GetID()); return node->IsToggled(); } -void UnsavedChangesModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const +void DiffModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const { assert(item.IsOk()); @@ -386,11 +383,11 @@ void UnsavedChangesModel::GetValue(wxVariant& variant, const wxDataViewItem& ite #endif //__linux__ default: - wxLogError("UnsavedChangesModel::GetValue: wrong column %d", col); + wxLogError("DiffModel::GetValue: wrong column %d", col); } } -bool UnsavedChangesModel::SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) +bool DiffModel::SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) { assert(item.IsOk()); @@ -440,12 +437,12 @@ bool UnsavedChangesModel::SetValue(const wxVariant& variant, const wxDataViewIte return true; } #endif //__linux__ default: - wxLogError("UnsavedChangesModel::SetValue: wrong column"); + wxLogError("DiffModel::SetValue: wrong column"); } return false; } -bool UnsavedChangesModel::IsEnabled(const wxDataViewItem& item, unsigned int col) const +bool DiffModel::IsEnabled(const wxDataViewItem& item, unsigned int col) const { assert(item.IsOk()); if (col == colToggle) @@ -455,7 +452,7 @@ bool UnsavedChangesModel::IsEnabled(const wxDataViewItem& item, unsigned int col return (static_cast(item.GetID()))->IsToggled(); } -wxDataViewItem UnsavedChangesModel::GetParent(const wxDataViewItem& item) const +wxDataViewItem DiffModel::GetParent(const wxDataViewItem& item) const { // the invisible root node has no parent if (!item.IsOk()) @@ -463,14 +460,13 @@ wxDataViewItem UnsavedChangesModel::GetParent(const wxDataViewItem& item) const ModelNode* node = static_cast(item.GetID()); - // "MyMusic" also has no parent if (node->IsRoot()) return wxDataViewItem(nullptr); return wxDataViewItem((void*)node->GetParent()); } -bool UnsavedChangesModel::IsContainer(const wxDataViewItem& item) const +bool DiffModel::IsContainer(const wxDataViewItem& item) const { // the invisble root node can have children if (!item.IsOk()) @@ -480,23 +476,19 @@ bool UnsavedChangesModel::IsContainer(const wxDataViewItem& item) const return node->IsContainer(); } -unsigned int UnsavedChangesModel::GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const +unsigned int DiffModel::GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const { - ModelNode* node = (ModelNode*)parent.GetID(); - if (!node) { - for (auto preset_node : m_preset_nodes) - array.Add(wxDataViewItem((void*)preset_node)); - return m_preset_nodes.size(); - } + ModelNode* parent_node = (ModelNode*)parent.GetID(); - for (std::unique_ptr &child : node->GetChildren()) + const ModelNodePtrArray& children = parent_node ? parent_node->GetChildren() : m_preset_nodes; + for (const std::unique_ptr& child : children) array.Add(wxDataViewItem((void*)child.get())); - return node->GetChildCount(); + return array.size(); } -wxString UnsavedChangesModel::GetColumnType(unsigned int col) const +wxString DiffModel::GetColumnType(unsigned int col) const { switch (col) { @@ -520,14 +512,234 @@ static void rescale_children(ModelNode* parent) } } -void UnsavedChangesModel::Rescale() +void DiffModel::Rescale() { - for (ModelNode* node : m_preset_nodes) { + for (std::unique_ptr &node : m_preset_nodes) { node->UpdateIcons(); - rescale_children(node); + rescale_children(node.get()); } } +wxDataViewItem DiffModel::Delete(const wxDataViewItem& item) +{ + auto ret_item = wxDataViewItem(nullptr); + ModelNode* node = static_cast(item.GetID()); + if (!node) // happens if item.IsOk()==false + return ret_item; + + // first remove the node from the parent's array of children; + // NOTE: m_preset_nodes is only a vector of _pointers_ + // thus removing the node from it doesn't result in freeing it + ModelNodePtrArray& children = node->GetChildren(); + // Delete all children + while (!children.empty()) + Delete(wxDataViewItem(children.back().get())); + + auto node_parent = node->GetParent(); + wxDataViewItem parent(node_parent); + + ModelNodePtrArray& parents_children = node_parent ? node_parent->GetChildren() : m_preset_nodes; + auto it = find_if(parents_children.begin(), parents_children.end(), + [node](std::unique_ptr& child) { return child.get() == node; }); + assert(it != parents_children.end()); + it = parents_children.erase(it); + + if (it != parents_children.end()) + ret_item = wxDataViewItem(it->get()); + + // set m_container to FALSE if parent has no child + if (node_parent) { +#ifndef __WXGTK__ + if (node_parent->GetChildCount() == 0) + node_parent->m_container = false; +#endif //__WXGTK__ + ret_item = parent; + } + + // notify control + ItemDeleted(parent, item); + return ret_item; +} + +void DiffModel::Clear() +{ + while (!m_preset_nodes.empty()) + Delete(wxDataViewItem(m_preset_nodes.back().get())); +} + + +static std::string get_pure_opt_key(std::string opt_key) +{ + int pos = opt_key.find("#"); + if (pos > 0) + boost::erase_tail(opt_key, opt_key.size() - pos); + return opt_key; +} + +// ---------------------------------------------------------------------------- +// DiffViewCtrl +// ---------------------------------------------------------------------------- + +DiffViewCtrl::DiffViewCtrl(wxWindow* parent, wxSize size) + : wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, size, wxBORDER_SIMPLE | wxDV_VARIABLE_LINE_HEIGHT | wxDV_ROW_LINES), + m_em_unit(em_unit(parent)) +{ + model = new DiffModel(parent); + this->AssociateModel(model); + model->SetAssociatedControl(this); + + this->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &DiffViewCtrl::context_menu, this); + this->Bind(wxEVT_DATAVIEW_ITEM_ACTIVATED, &DiffViewCtrl::context_menu, this); + this->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &DiffViewCtrl::item_value_changed, this); +} + +void DiffViewCtrl::AppendBmpTextColumn(const wxString& label, unsigned model_column, int width, bool set_expander/* = false*/) +{ + m_columns_width.emplace(this->GetColumnCount(), width); +#ifdef __linux__ + wxDataViewIconTextRenderer* rd = new wxDataViewIconTextRenderer(); +#ifdef SUPPORTS_MARKUP + rd->EnableMarkup(true); +#endif + wxDataViewColumn* column = new wxDataViewColumn(label, rd, model_column, width, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_CELL_INERT); +#else + wxDataViewColumn* column = new wxDataViewColumn(label, new BitmapTextRenderer(true, wxDATAVIEW_CELL_INERT), model_column, width * m_em_unit, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE); +#endif //__linux__ + this->AppendColumn(column); + if (set_expander) + this->SetExpanderColumn(column); + +} + +void DiffViewCtrl::AppendToggleColumn_(const wxString& label, unsigned model_column, int width) +{ + m_columns_width.emplace(this->GetColumnCount(), width); + AppendToggleColumn(label, model_column, wxDATAVIEW_CELL_ACTIVATABLE, width * m_em_unit); +} + +void DiffViewCtrl::Rescale(int em /*= 0*/) +{ + if (em > 0) { + for (auto item : m_columns_width) + GetColumn(item.first)->SetWidth(item.second * em); + m_em_unit = em; + } + + model->Rescale(); + Refresh(); +} + + +void DiffViewCtrl::Append( const std::string& opt_key, Preset::Type type, + wxString category_name, wxString group_name, wxString option_name, + wxString old_value, wxString new_value, const std::string category_icon_name) +{ + ItemData item_data = { opt_key, option_name, old_value, new_value, type }; + + wxString old_val = get_short_string(item_data.old_val); + wxString new_val = get_short_string(item_data.new_val); + if (old_val != item_data.old_val || new_val != item_data.new_val) + item_data.is_long = true; + + m_items_map.emplace(model->AddOption(type, category_name, group_name, option_name, old_val, new_val, category_icon_name), item_data); + +} + +void DiffViewCtrl::Clear() +{ + model->Clear(); + m_items_map.clear(); +} + +wxString DiffViewCtrl::get_short_string(wxString full_string) +{ + int max_len = 30; + if (full_string.IsEmpty() || full_string.StartsWith("#") || + (full_string.Find("\n") == wxNOT_FOUND && full_string.Length() < max_len)) + return full_string; + + m_has_long_strings = true; + + int n_pos = full_string.Find("\n"); + if (n_pos != wxNOT_FOUND && n_pos < max_len) + max_len = n_pos; + + full_string.Truncate(max_len); + return full_string + dots; +} + +void DiffViewCtrl::context_menu(wxDataViewEvent& event) +{ + if (!m_has_long_strings) + return; + + wxDataViewItem item = event.GetItem(); + if (!item) { + wxPoint mouse_pos = wxGetMousePosition() - this->GetScreenPosition(); + wxDataViewColumn* col = nullptr; + this->HitTest(mouse_pos, item, col); + + if (!item) + item = this->GetSelection(); + + if (!item) + return; + } + + auto it = m_items_map.find(item); + if (it == m_items_map.end() || !it->second.is_long) + return; + FullCompareDialog(it->second.opt_name, it->second.old_val, it->second.new_val).ShowModal(); + +#ifdef __WXOSX__ + wxWindow* parent = this->GetParent(); + if (parent && parent->IsShown()) { + // if this dialog is shown it have to be Hide and show again to be placed on the very Top of windows + parent->Hide(); + parent->Show(); + } +#endif // __WXOSX__ +} + +void DiffViewCtrl::item_value_changed(wxDataViewEvent& event) +{ + if (event.GetColumn() != DiffModel::colToggle) + return; + + wxDataViewItem item = event.GetItem(); + + model->UpdateItemEnabling(item); + Refresh(); + + // update an enabling of the "save/move" buttons + m_empty_selection = selected_options().empty(); +} + +std::vector DiffViewCtrl::unselected_options(Preset::Type type) +{ + std::vector ret; + + for (auto item : m_items_map) { + if (item.second.opt_key == "extruders_count") + continue; + if (item.second.type == type && !model->IsEnabledItem(item.first)) + ret.emplace_back(get_pure_opt_key(item.second.opt_key)); + } + + return ret; +} + +std::vector DiffViewCtrl::selected_options() +{ + std::vector ret; + + for (auto item : m_items_map) + if (model->IsEnabledItem(item.first)) + ret.emplace_back(get_pure_opt_key(item.second.opt_key)); + + return ret; +} + //------------------------------------------ // UnsavedChangesDialog @@ -593,35 +805,11 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ m_action_line = new wxStaticText(this, wxID_ANY, ""); m_action_line->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold()); - m_tree = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(em * 60, em * 30), wxBORDER_SIMPLE | wxDV_VARIABLE_LINE_HEIGHT | wxDV_ROW_LINES); - m_tree_model = new UnsavedChangesModel(this); - m_tree->AssociateModel(m_tree_model); - m_tree_model->SetAssociatedControl(m_tree); - - m_tree->AppendToggleColumn(L"\u2714", UnsavedChangesModel::colToggle, wxDATAVIEW_CELL_ACTIVATABLE, (wxLinux ? 8 : 6) * em); - - auto append_bmp_text_column = [this](const wxString& label, unsigned model_column, int width, bool set_expander = false) - { -#ifdef __linux__ - wxDataViewIconTextRenderer* rd = new wxDataViewIconTextRenderer(); -#ifdef SUPPORTS_MARKUP - rd->EnableMarkup(true); -#endif - wxDataViewColumn* column = new wxDataViewColumn(label, rd, model_column, width, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_CELL_INERT); -#else - wxDataViewColumn* column = new wxDataViewColumn(label, new BitmapTextRenderer(true, wxDATAVIEW_CELL_INERT), model_column, width, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE); -#endif //__linux__ - m_tree->AppendColumn(column); - if (set_expander) - m_tree->SetExpanderColumn(column); - }; - - append_bmp_text_column("" , UnsavedChangesModel::colIconText, 28 * em); - append_bmp_text_column(_L("Old Value"), UnsavedChangesModel::colOldValue, 12 * em); - append_bmp_text_column(_L("New Value"), UnsavedChangesModel::colNewValue, 12 * em); - - m_tree->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &UnsavedChangesDialog::item_value_changed, this); - m_tree->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &UnsavedChangesDialog::context_menu, this); + m_tree = new DiffViewCtrl(this, wxSize(em * 60, em * 30)); + m_tree->AppendToggleColumn_(L"\u2714" , DiffModel::colToggle, wxLinux ? 8 : 6); + m_tree->AppendBmpTextColumn("" , DiffModel::colIconText, 28); + m_tree->AppendBmpTextColumn(_L("Old Value"), DiffModel::colOldValue, 12); + m_tree->AppendBmpTextColumn(_L("New Value"), DiffModel::colNewValue, 12); // Add Buttons wxFont btn_font = this->GetFont().Scaled(1.4f); @@ -641,7 +829,7 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ close(close_act); }); if (process_enable) - (*btn)->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_empty_selection); }); + (*btn)->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_tree->has_selection()); }); (*btn)->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& e) { show_info_line(Action::Undef); e.Skip(); }); }; @@ -698,45 +886,6 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ show_info_line(Action::Undef); } -void UnsavedChangesDialog::item_value_changed(wxDataViewEvent& event) -{ - if (event.GetColumn() != UnsavedChangesModel::colToggle) - return; - - wxDataViewItem item = event.GetItem(); - - m_tree_model->UpdateItemEnabling(item); - m_tree->Refresh(); - - // update an enabling of the "save/move" buttons - m_empty_selection = get_selected_options().empty(); -} - -void UnsavedChangesDialog::context_menu(wxDataViewEvent& event) -{ - if (!m_has_long_strings) - return; - - wxDataViewItem item = event.GetItem(); - if (!item) - { - wxPoint mouse_pos = wxGetMousePosition() - m_tree->GetScreenPosition(); - wxDataViewColumn* col = nullptr; - m_tree->HitTest(mouse_pos, item, col); - - if (!item) - item = m_tree->GetSelection(); - - if (!item) - return; - } - - auto it = m_items_map.find(item); - if (it == m_items_map.end() || !it->second.is_long) - return; - FullCompareDialog(it->second.opt_name, it->second.old_val, it->second.new_val).ShowModal(); -} - void UnsavedChangesDialog::show_info_line(Action action, std::string preset_name) { if (action == Action::Undef && !m_has_long_strings) @@ -869,12 +1018,15 @@ static int get_id_from_opt_key(std::string opt_key) return 0; } -static std::string get_pure_opt_key(std::string opt_key) +static wxString get_full_label(std::string opt_key, const DynamicPrintConfig& config) { - int pos = opt_key.find("#"); - if (pos > 0) - boost::erase_tail(opt_key, opt_key.size() - pos); - return opt_key; + opt_key = get_pure_opt_key(opt_key); + + if (config.option(opt_key)->is_nil()) + return _L("N/A"); + + const ConfigOptionDef* opt = config.def()->get(opt_key); + return opt->full_label.empty() ? opt->label : opt->full_label; } static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& config) @@ -894,34 +1046,62 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& case coInt: return from_u8((boost::format("%1%") % config.opt_int(opt_key)).str()); case coInts: { - int val = is_nullable ? - config.opt(opt_key)->get_at(opt_idx) : - config.opt(opt_key)->get_at(opt_idx); - return from_u8((boost::format("%1%") % val).str()); + if (is_nullable) { + auto values = config.opt(opt_key); + if (opt_idx < values->size()) + return from_u8((boost::format("%1%") % values->get_at(opt_idx)).str()); + } + else { + auto values = config.opt(opt_key); + if (opt_idx < values->size()) + return from_u8((boost::format("%1%") % values->get_at(opt_idx)).str()); + } + return _L("Undef"); } case coBool: return config.opt_bool(opt_key) ? "true" : "false"; case coBools: { - bool val = is_nullable ? - config.opt(opt_key)->get_at(opt_idx) : - config.opt(opt_key)->get_at(opt_idx); - return val ? "true" : "false"; + if (is_nullable) { + auto values = config.opt(opt_key); + if (opt_idx < values->size()) + return values->get_at(opt_idx) ? "true" : "false"; + } + else { + auto values = config.opt(opt_key); + if (opt_idx < values->size()) + return values->get_at(opt_idx) ? "true" : "false"; + } + return _L("Undef"); } case coPercent: return from_u8((boost::format("%1%%%") % int(config.optptr(opt_key)->getFloat())).str()); case coPercents: { - double val = is_nullable ? - config.opt(opt_key)->get_at(opt_idx) : - config.opt(opt_key)->get_at(opt_idx); - return from_u8((boost::format("%1%%%") % int(val)).str()); + if (is_nullable) { + auto values = config.opt(opt_key); + if (opt_idx < values->size()) + return from_u8((boost::format("%1%%%") % values->get_at(opt_idx)).str()); + } + else { + auto values = config.opt(opt_key); + if (opt_idx < values->size()) + return from_u8((boost::format("%1%%%") % values->get_at(opt_idx)).str()); + } + return _L("Undef"); } case coFloat: return double_to_string(config.opt_float(opt_key)); case coFloats: { - double val = is_nullable ? - config.opt(opt_key)->get_at(opt_idx) : - config.opt(opt_key)->get_at(opt_idx); - return double_to_string(val); + if (is_nullable) { + auto values = config.opt(opt_key); + if (opt_idx < values->size()) + return double_to_string(values->get_at(opt_idx)); + } + else { + auto values = config.opt(opt_key); + if (opt_idx < values->size()) + return double_to_string(values->get_at(opt_idx)); + } + return _L("Undef"); } case coString: return from_u8(config.opt_string(opt_key)); @@ -936,7 +1116,7 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& out.RemoveLast(1); return out; } - if (!strings->empty()) + if (!strings->empty() && opt_idx < (int)strings->values.size()) return from_u8(strings->get_at(opt_idx)); } break; @@ -987,23 +1167,6 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& return out; } -wxString UnsavedChangesDialog::get_short_string(wxString full_string) -{ - int max_len = 30; - if (full_string.IsEmpty() || full_string.StartsWith("#") || - (full_string.Find("\n") == wxNOT_FOUND && full_string.Length() < size_t(max_len))) - return full_string; - - m_has_long_strings = true; - - int n_pos = full_string.Find("\n"); - if (n_pos != wxNOT_FOUND && n_pos < max_len) - max_len = n_pos; - - full_string.Truncate(max_len); - return full_string + dots; -} - void UnsavedChangesDialog::update(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header) { PresetCollection* presets = dependent_presets; @@ -1065,7 +1228,7 @@ void UnsavedChangesDialog::update_tree(Preset::Type type, PresetCollection* pres const std::map& category_icon_map = wxGetApp().get_tab(type)->get_category_icon_map(); - m_tree_model->AddPreset(type, from_u8(presets->get_edited_preset().name), old_pt); + m_tree->model->AddPreset(type, from_u8(presets->get_edited_preset().name), old_pt); // Collect dirty options. const bool deep_compare = (type == Preset::TYPE_PRINTER || type == Preset::TYPE_SLA_MATERIAL); @@ -1078,9 +1241,7 @@ void UnsavedChangesDialog::update_tree(Preset::Type type, PresetCollection* pres wxString old_val = from_u8((boost::format("%1%") % old_config.opt("extruder_colour")->values.size()).str()); wxString new_val = from_u8((boost::format("%1%") % new_config.opt("extruder_colour")->values.size()).str()); - ItemData item_data = { "extruders_count", local_label, old_val, new_val, type }; - m_items_map.emplace(m_tree_model->AddOption(type, _L("General"), _L("Capabilities"), local_label, old_val, new_val, category_icon_map.at("General")), item_data); - + m_tree->Append("extruders_count", type, _L("General"), _L("Capabilities"), local_label, old_val, new_val, category_icon_map.at("General")); } for (const std::string& opt_key : dirty_options) { @@ -1092,43 +1253,12 @@ void UnsavedChangesDialog::update_tree(Preset::Type type, PresetCollection* pres continue; } - ItemData item_data = { opt_key, option.label_local, get_string_value(opt_key, old_config), get_string_value(opt_key, new_config), type }; - - wxString old_val = get_short_string(item_data.old_val); - wxString new_val = get_short_string(item_data.new_val); - if (old_val != item_data.old_val || new_val != item_data.new_val) - item_data.is_long = true; - - m_items_map.emplace(m_tree_model->AddOption(type, option.category_local, option.group_local, option.label_local, old_val, new_val, category_icon_map.at(option.category)), item_data); + m_tree->Append(opt_key, type, option.category_local, option.group_local, option.label_local, + get_string_value(opt_key, old_config), get_string_value(opt_key, new_config), category_icon_map.at(option.category)); } } } -std::vector UnsavedChangesDialog::get_unselected_options(Preset::Type type) -{ - std::vector ret; - - for (auto item : m_items_map) { - if (item.second.opt_key == "extruders_count") - continue; - if (item.second.type == type && !m_tree_model->IsEnabledItem(item.first)) - ret.emplace_back(get_pure_opt_key(item.second.opt_key)); - } - - return ret; -} - -std::vector UnsavedChangesDialog::get_selected_options() -{ - std::vector ret; - - for (auto item : m_items_map) - if (m_tree_model->IsEnabledItem(item.first)) - ret.emplace_back(get_pure_opt_key(item.second.opt_key)); - - return ret; -} - void UnsavedChangesDialog::on_dpi_changed(const wxRect& suggested_rect) { int em = em_unit(); @@ -1137,16 +1267,10 @@ void UnsavedChangesDialog::on_dpi_changed(const wxRect& suggested_rect) for (auto btn : { m_save_btn, m_transfer_btn, m_discard_btn } ) if (btn) btn->msw_rescale(); - const wxSize& size = wxSize(80 * em, 30 * em); + const wxSize& size = wxSize(70 * em, 30 * em); SetMinSize(size); - m_tree->GetColumn(UnsavedChangesModel::colToggle )->SetWidth(6 * em); - m_tree->GetColumn(UnsavedChangesModel::colIconText)->SetWidth(30 * em); - m_tree->GetColumn(UnsavedChangesModel::colOldValue)->SetWidth(20 * em); - m_tree->GetColumn(UnsavedChangesModel::colNewValue)->SetWidth(20 * em); - - m_tree_model->Rescale(); - m_tree->Refresh(); + m_tree->Rescale(em); Fit(); Refresh(); @@ -1157,8 +1281,7 @@ void UnsavedChangesDialog::on_sys_color_changed() for (auto btn : { m_save_btn, m_transfer_btn, m_discard_btn } ) btn->msw_rescale(); // msw_rescale updates just icons, so use it - m_tree_model->Rescale(); - m_tree->Refresh(); + m_tree->Rescale(); Refresh(); } @@ -1190,16 +1313,45 @@ FullCompareDialog::FullCompareDialog(const wxString& option_name, const wxString grid_sizer->Add(text, 0, wxALL, border); }; - auto add_value = [grid_sizer, border, this](wxString label, bool is_colored = false) { - wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, label, wxDefaultPosition, wxSize(300, -1), wxTE_MULTILINE | wxTE_READONLY | wxBORDER_NONE | wxTE_RICH); - text->SetStyle(0, label.Len(), wxTextAttr(is_colored ? wxColour(orange) : wxNullColour, wxNullColour, this->GetFont())); - grid_sizer->Add(text, 1, wxALL | wxEXPAND, border); - }; - add_header(_L("Old value")); add_header(_L("New value")); - add_value(old_value); - add_value(new_value, true); + + auto get_set_from_val = [](wxString str) { + if (str.Find("\n") == wxNOT_FOUND) + str.Replace(" ", "\n"); + + std::set str_set; + + wxStringTokenizer strings(str, "\n"); + while (strings.HasMoreTokens()) + str_set.emplace(strings.GetNextToken()); + + return str_set; + }; + + std::set old_set = get_set_from_val(old_value); + std::set new_set = get_set_from_val(new_value); + std::set old_new_diff_set; + std::set new_old_diff_set; + + std::set_difference(old_set.begin(), old_set.end(), new_set.begin(), new_set.end(), std::inserter(old_new_diff_set, old_new_diff_set.begin())); + std::set_difference(new_set.begin(), new_set.end(), old_set.begin(), old_set.end(), std::inserter(new_old_diff_set, new_old_diff_set.begin())); + + auto add_value = [grid_sizer, border, this](wxString label, const std::set& diff_set, bool is_colored = false) { + wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, label, wxDefaultPosition, wxSize(400, 400), wxTE_MULTILINE | wxTE_READONLY | wxBORDER_NONE | wxTE_RICH); + text->SetStyle(0, label.Len(), wxTextAttr(is_colored ? wxColour(orange) : wxNullColour, wxNullColour, this->GetFont())); + + for (const wxString& str : diff_set) { + int pos = label.First(str); + if (pos == wxNOT_FOUND) + continue; + text->SetStyle(pos, pos + (int)str.Len(), wxTextAttr(is_colored ? wxColour(orange) : wxNullColour, wxNullColour, this->GetFont().Bold())); + } + + grid_sizer->Add(text, 1, wxALL | wxEXPAND, border); + }; + add_value(old_value, old_new_diff_set); + add_value(new_value, new_old_diff_set, true); sizer->Add(grid_sizer, 1, wxEXPAND); @@ -1215,6 +1367,384 @@ FullCompareDialog::FullCompareDialog(const wxString& option_name, const wxString } +static PresetCollection* get_preset_collection(Preset::Type type, PresetBundle* preset_bundle = nullptr) { + if (!preset_bundle) + preset_bundle = wxGetApp().preset_bundle; + return type == Preset::Type::TYPE_PRINT ? &preset_bundle->prints : + type == Preset::Type::TYPE_SLA_PRINT ? &preset_bundle->sla_prints : + type == Preset::Type::TYPE_FILAMENT ? &preset_bundle->filaments : + type == Preset::Type::TYPE_SLA_MATERIAL ? &preset_bundle->sla_materials : + type == Preset::Type::TYPE_PRINTER ? &preset_bundle->printers : + nullptr; +} + +//------------------------------------------ +// DiffPresetDialog +//------------------------------------------ +static std::string get_selection(PresetComboBox* preset_combo) +{ + return into_u8(preset_combo->GetString(preset_combo->GetSelection())); +} + +DiffPresetDialog::DiffPresetDialog() + : DPIDialog(static_cast(wxGetApp().mainframe), wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), + m_pr_technology(wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology()) +{ + wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + SetBackgroundColour(bgr_clr); + +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && defined(__WXMSW__) + // ys_FIXME! temporary workaround for correct font scaling + // Because of from wxWidgets 3.1.3 auto rescaling is implemented for the Fonts, + // From the very beginning set dialog font to the wxSYS_DEFAULT_GUI_FONT + this->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + + int border = 10; + int em = em_unit(); + + assert(wxGetApp().preset_bundle); + + m_preset_bundle_left = std::make_unique(*wxGetApp().preset_bundle); + m_preset_bundle_right = std::make_unique(*wxGetApp().preset_bundle); + + m_top_info_line = new wxStaticText(this, wxID_ANY, "Select presets to compare"); + m_top_info_line->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold()); + + m_bottom_info_line = new wxStaticText(this, wxID_ANY, ""); + m_bottom_info_line->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold()); + + wxBoxSizer* presets_sizer = new wxBoxSizer(wxVERTICAL); + + for (auto new_type : { Preset::TYPE_PRINT, Preset::TYPE_FILAMENT, Preset::TYPE_SLA_PRINT, Preset::TYPE_SLA_MATERIAL, Preset::TYPE_PRINTER }) + { + const PresetCollection* collection = get_preset_collection(new_type); + wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); + PresetComboBox* presets_left; + PresetComboBox* presets_right; + ScalableButton* equal_bmp = new ScalableButton(this, wxID_ANY, "equal"); + + auto add_preset_combobox = [collection, sizer, new_type, em, this](PresetComboBox** cb_, PresetBundle* preset_bundle) { + *cb_ = new PresetComboBox(this, new_type, wxSize(em * 35, -1), preset_bundle); + PresetComboBox* cb = (*cb_); + cb->set_selection_changed_function([this, new_type, preset_bundle, cb](int selection) { + if (m_view_type == Preset::TYPE_INVALID) { + std::string preset_name = cb->GetString(selection).ToUTF8().data(); + update_compatibility(Preset::remove_suffix_modified(preset_name), new_type, preset_bundle); + } + update_tree(); + }); + if (collection->get_selected_idx() != (size_t)-1) + cb->update(collection->get_selected_preset().name); + + sizer->Add(cb, 1); + cb->Show(new_type == Preset::TYPE_PRINTER); + }; + add_preset_combobox(&presets_left, m_preset_bundle_left.get()); + sizer->Add(equal_bmp, 0, wxRIGHT | wxLEFT | wxALIGN_CENTER_VERTICAL, 5); + add_preset_combobox(&presets_right, m_preset_bundle_right.get()); + presets_sizer->Add(sizer, 1, wxTOP, 5); + equal_bmp->Show(new_type == Preset::TYPE_PRINTER); + + m_preset_combos.push_back({ presets_left, equal_bmp, presets_right }); + + equal_bmp->Bind(wxEVT_BUTTON, [presets_left, presets_right, this](wxEvent&) { + std::string preset_name = get_selection(presets_left); + presets_right->update(preset_name); + if (m_view_type == Preset::TYPE_INVALID) + update_compatibility(Preset::remove_suffix_modified(preset_name), presets_right->get_type(), m_preset_bundle_right.get()); + update_tree(); + }); + } + + m_show_all_presets = new wxCheckBox(this, wxID_ANY, _L("Show all preset (including incompatible)")); + m_show_all_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { + bool show_all = m_show_all_presets->GetValue(); + for (auto preset_combos : m_preset_combos) { + if (preset_combos.presets_left->get_type() == Preset::TYPE_PRINTER) + continue; + preset_combos.presets_left->show_all(show_all); + preset_combos.presets_right->show_all(show_all); + } + if (m_view_type == Preset::TYPE_INVALID) + update_tree(); + }); + + m_tree = new DiffViewCtrl(this, wxSize(em * 65, em * 40)); + m_tree->AppendBmpTextColumn("", DiffModel::colIconText, 35); + m_tree->AppendBmpTextColumn(_L("Left Preset Value"), DiffModel::colOldValue, 15); + m_tree->AppendBmpTextColumn(_L("Right Preset Value"),DiffModel::colNewValue, 15); + m_tree->Hide(); + + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + topSizer->Add(m_top_info_line, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 2 * border); + topSizer->Add(presets_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(m_show_all_presets, 0, wxEXPAND | wxALL, border); + topSizer->Add(m_bottom_info_line, 0, wxEXPAND | wxALL, 2 * border); + topSizer->Add(m_tree, 1, wxEXPAND | wxALL, border); + + this->SetMinSize(wxSize(80 * em, 30 * em)); + this->SetSizer(topSizer); + topSizer->SetSizeHints(this); +} + +void DiffPresetDialog::update_controls_visibility(Preset::Type type /* = Preset::TYPE_INVALID*/) +{ + for (auto preset_combos : m_preset_combos) { + Preset::Type cb_type = preset_combos.presets_left->get_type(); + bool show = type != Preset::TYPE_INVALID ? type == cb_type : + cb_type == Preset::TYPE_PRINTER ? true : + m_pr_technology == ptFFF ? cb_type == Preset::TYPE_PRINT || cb_type == Preset::TYPE_FILAMENT : + cb_type == Preset::TYPE_SLA_PRINT || cb_type == Preset::TYPE_SLA_MATERIAL; + preset_combos.presets_left->Show(show); + preset_combos.equal_bmp->Show(show); + preset_combos.presets_right->Show(show); + + if (show) { + preset_combos.presets_left->update_from_bundle(); + preset_combos.presets_right->update_from_bundle(); + } + } + + m_show_all_presets->Show(type != Preset::TYPE_PRINTER); +} + +void DiffPresetDialog::update_bundles_from_app() +{ + *m_preset_bundle_left = *wxGetApp().preset_bundle; + *m_preset_bundle_right = *wxGetApp().preset_bundle; +} + +void DiffPresetDialog::show(Preset::Type type /* = Preset::TYPE_INVALID*/) +{ + this->SetTitle(type == Preset::TYPE_INVALID ? _L("Compare Presets") : format_wxstr(_L("Compare %1% Presets"), wxGetApp().get_tab(type)->name())); + m_view_type = type; + + update_bundles_from_app(); + update_controls_visibility(type); + if (type == Preset::TYPE_INVALID) + Fit(); + + update_tree(); + + // if this dialog is shown it have to be Hide and show again to be placed on the very Top of windows + if (IsShown()) + Hide(); + Show(); +} + +void DiffPresetDialog::update_presets(Preset::Type type) +{ + m_pr_technology = m_preset_bundle_left.get()->printers.get_edited_preset().printer_technology(); + + update_bundles_from_app(); + update_controls_visibility(type); + + if (type == Preset::TYPE_INVALID) + for (auto preset_combos : m_preset_combos) { + if (preset_combos.presets_left->get_type() == Preset::TYPE_PRINTER) { + preset_combos.presets_left->update_from_bundle (); + preset_combos.presets_right->update_from_bundle(); + break; + } + } + else + for (auto preset_combos : m_preset_combos) { + if (preset_combos.presets_left->get_type() == type) { + preset_combos.presets_left->update(); + preset_combos.presets_right->update(); + break; + } + } + + update_tree(); +} + +void DiffPresetDialog::update_tree() +{ + Search::OptionsSearcher& searcher = wxGetApp().sidebar().get_searcher(); + searcher.sort_options_by_opt_key(); + + m_tree->Clear(); + wxString bottom_info = ""; + bool show_tree = false; + + for (auto preset_combos : m_preset_combos) + { + if (!preset_combos.presets_left->IsShown()) + continue; + Preset::Type type = preset_combos.presets_left->get_type(); + + const PresetCollection* presets = get_preset_collection(type); + const Preset* left_preset = presets->find_preset(get_selection(preset_combos.presets_left)); + const Preset* right_preset = presets->find_preset(get_selection(preset_combos.presets_right)); + if (!left_preset || !right_preset) { + bottom_info = _L("One of the presets doesn't found"); + preset_combos.equal_bmp->SetBitmap_(ScalableBitmap(this, "question")); + preset_combos.equal_bmp->SetToolTip(bottom_info); + continue; + } + + const DynamicPrintConfig& left_config = left_preset->config; + const PrinterTechnology& left_pt = left_preset->printer_technology(); + const DynamicPrintConfig& right_congig = right_preset->config; + + if (left_pt != right_preset->printer_technology()) { + bottom_info = _L("Comparable printer presets has different printer technology"); + preset_combos.equal_bmp->SetBitmap_(ScalableBitmap(this, "question")); + preset_combos.equal_bmp->SetToolTip(bottom_info); + continue; + } + + // Collect dirty options. + const bool deep_compare = (type == Preset::TYPE_PRINTER || type == Preset::TYPE_SLA_MATERIAL); + auto dirty_options = type == Preset::TYPE_PRINTER && left_pt == ptFFF && + left_config.opt("extruder_colour")->values.size() < right_congig.opt("extruder_colour")->values.size() ? + presets->dirty_options(right_preset, left_preset, deep_compare) : + presets->dirty_options(left_preset, right_preset, deep_compare); + + if (dirty_options.empty()) { + bottom_info = _L("Presets are the same"); + preset_combos.equal_bmp->SetBitmap_(ScalableBitmap(this, "equal")); + preset_combos.equal_bmp->SetToolTip(bottom_info); + continue; + } + + show_tree = true; + preset_combos.equal_bmp->SetBitmap_(ScalableBitmap(this, "not_equal")); + preset_combos.equal_bmp->SetToolTip(_L("Presets are different.\n" + "Click this button to select the same as left preset for the right preset.")); + + m_tree->model->AddPreset(type, "\"" + from_u8(left_preset->name) + "\" vs \"" + from_u8(right_preset->name) + "\"", left_pt); + + const std::map& category_icon_map = wxGetApp().get_tab(type)->get_category_icon_map(); + + // process changes of extruders count + if (type == Preset::TYPE_PRINTER && left_pt == ptFFF && + left_config.opt("extruder_colour")->values.size() != right_congig.opt("extruder_colour")->values.size()) { + wxString local_label = _L("Extruders count"); + wxString left_val = from_u8((boost::format("%1%") % left_config.opt("extruder_colour")->values.size()).str()); + wxString right_val = from_u8((boost::format("%1%") % right_congig.opt("extruder_colour")->values.size()).str()); + + m_tree->Append("extruders_count", type, _L("General"), _L("Capabilities"), local_label, left_val, right_val, category_icon_map.at("General")); + } + + for (const std::string& opt_key : dirty_options) { + wxString left_val = get_string_value(opt_key, left_config); + wxString right_val = get_string_value(opt_key, right_congig); + + Search::Option option = searcher.get_option(opt_key, get_full_label(opt_key, left_config), type); + if (option.opt_key != boost::nowide::widen(opt_key)) { + // temporary solution, just for testing + m_tree->Append(opt_key, type, _L("Undef category"), _L("Undef group"), opt_key, left_val, right_val, "question"); + // When founded option isn't the correct one. + // It can be for dirty_options: "default_print_profile", "printer_model", "printer_settings_id", + // because of they don't exist in searcher + continue; + } + m_tree->Append(opt_key, type, option.category_local, option.group_local, option.label_local, + left_val, right_val, category_icon_map.at(option.category)); + } + } + + bool tree_was_shown = m_tree->IsShown(); + m_tree->Show(show_tree); + if (!show_tree) + m_bottom_info_line->SetLabel(bottom_info); + m_bottom_info_line->Show(!show_tree); + + if (tree_was_shown == m_tree->IsShown()) + Layout(); + else { + Fit(); + Refresh(); + } +} + +void DiffPresetDialog::on_dpi_changed(const wxRect&) +{ + int em = em_unit(); + + msw_buttons_rescale(this, em, { wxID_CANCEL}); + + const wxSize& size = wxSize(80 * em, 30 * em); + SetMinSize(size); + + for (auto preset_combos : m_preset_combos) { + preset_combos.presets_left->msw_rescale(); + preset_combos.equal_bmp->msw_rescale(); + preset_combos.presets_right->msw_rescale(); + } + + m_tree->Rescale(em); + + Fit(); + Refresh(); +} + +void DiffPresetDialog::on_sys_color_changed() +{ + // msw_rescale updates just icons, so use it + m_tree->Rescale(); + Refresh(); +} + +void DiffPresetDialog::update_compatibility(const std::string& preset_name, Preset::Type type, PresetBundle* preset_bundle) +{ + PresetCollection* presets = get_preset_collection(type, preset_bundle); + + bool print_tab = type == Preset::TYPE_PRINT || type == Preset::TYPE_SLA_PRINT; + bool printer_tab = type == Preset::TYPE_PRINTER; + bool technology_changed = false; + + if (printer_tab) { + const Preset& new_printer_preset = *presets->find_preset(preset_name, true); + const PresetWithVendorProfile new_printer_preset_with_vendor_profile = presets->get_preset_with_vendor_profile(new_printer_preset); + PrinterTechnology old_printer_technology = presets->get_selected_preset().printer_technology(); + PrinterTechnology new_printer_technology = new_printer_preset.printer_technology(); + + technology_changed = old_printer_technology != new_printer_technology; + } + + bool is_selected = presets->select_preset_by_name(preset_name, false); + + // Mark the print & filament enabled if they are compatible with the currently selected preset. + // The following method should not discard changes of current print or filament presets on change of a printer profile, + // if they are compatible with the current printer. + auto update_compatible_type = [](bool technology_changed, bool on_page, bool show_incompatible_presets) { + return technology_changed ? PresetSelectCompatibleType::Always : + on_page ? PresetSelectCompatibleType::Never : + show_incompatible_presets ? PresetSelectCompatibleType::OnlyIfWasCompatible : PresetSelectCompatibleType::Always; + }; + if (print_tab || printer_tab) + preset_bundle->update_compatible( + update_compatible_type(technology_changed, print_tab, true), + update_compatible_type(technology_changed, false, true)); + + bool is_left_presets = preset_bundle == m_preset_bundle_left.get(); + PrinterTechnology pr_tech = preset_bundle->printers.get_selected_preset().printer_technology(); + + // update preset comboboxes + for (auto preset_combos : m_preset_combos) + { + PresetComboBox* cb = is_left_presets ? preset_combos.presets_left : preset_combos.presets_right; + Preset::Type presets_type = cb->get_type(); + if (print_tab && (pr_tech == ptFFF && presets_type == Preset::TYPE_FILAMENT || + pr_tech == ptSLA && presets_type == Preset::TYPE_SLA_MATERIAL) || + printer_tab && (pr_tech == ptFFF && (presets_type == Preset::TYPE_PRINT || presets_type == Preset::TYPE_FILAMENT) || + pr_tech == ptSLA && (presets_type == Preset::TYPE_SLA_PRINT || presets_type == Preset::TYPE_SLA_MATERIAL))) + cb->update(); + } + + if (technology_changed && + m_preset_bundle_left.get()->printers.get_selected_preset().printer_technology() == + m_preset_bundle_right.get()->printers.get_selected_preset().printer_technology()) + { + m_pr_technology = m_preset_bundle_left.get()->printers.get_edited_preset().printer_technology(); + update_controls_visibility(); + } +} } diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index 232802b66..12c215e56 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -7,7 +7,7 @@ #include "GUI_Utils.hpp" #include "wxExtensions.hpp" -#include "libslic3r/Preset.hpp" +#include "libslic3r/PresetBundle.hpp" class ScalableButton; class wxStaticText; @@ -16,10 +16,11 @@ namespace Slic3r { namespace GUI{ // ---------------------------------------------------------------------------- -// ModelNode: a node inside UnsavedChangesModel +// ModelNode: a node inside DiffModel // ---------------------------------------------------------------------------- class ModelNode; +class PresetComboBox; using ModelNodePtrArray = std::vector>; // On all of 3 different platforms Bitmap+Text icon column looks different @@ -42,17 +43,6 @@ class ModelNode wxString m_old_color; wxString m_new_color; - // TODO/FIXME: - // the GTK version of wxDVC (in particular wxDataViewCtrlInternal::ItemAdded) - // needs to know in advance if a node is or _will be_ a container. - // Thus implementing: - // bool IsContainer() const - // { return m_children.size()>0; } - // doesn't work with wxGTK when UnsavedChangesModel::AddToClassical is called - // AND the classical node was removed (a new node temporary without children - // would be added to the control) - bool m_container {true}; - #ifdef __linux__ wxIcon get_bitmap(const wxString& color); #else @@ -75,6 +65,17 @@ public: wxString m_old_value; wxString m_new_value; + // TODO/FIXME: + // the GTK version of wxDVC (in particular wxDataViewCtrlInternal::ItemAdded) + // needs to know in advance if a node is or _will be_ a container. + // Thus implementing: + // bool IsContainer() const + // { return m_children.size()>0; } + // doesn't work with wxGTK when DiffModel::AddToClassical is called + // AND the classical node was removed (a new node temporary without children + // would be added to the control) + bool m_container {true}; + // preset(root) node ModelNode(Preset::Type preset_type, wxWindow* parent_win, const wxString& text, const std::string& icon_name); @@ -107,13 +108,13 @@ public: // ---------------------------------------------------------------------------- -// UnsavedChangesModel +// DiffModel // ---------------------------------------------------------------------------- -class UnsavedChangesModel : public wxDataViewModel +class DiffModel : public wxDataViewModel { wxWindow* m_parent_win { nullptr }; - std::vector m_preset_nodes; + ModelNodePtrArray m_preset_nodes; wxDataViewCtrl* m_ctrl{ nullptr }; @@ -143,8 +144,8 @@ public: colMax }; - UnsavedChangesModel(wxWindow* parent); - ~UnsavedChangesModel(); + DiffModel(wxWindow* parent); + ~DiffModel() {} void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } @@ -159,6 +160,9 @@ public: wxString GetColumnType(unsigned int col) const override; void Rescale(); + wxDataViewItem Delete(const wxDataViewItem& item); + void Clear(); + wxDataViewItem GetParent(const wxDataViewItem& item) const override; unsigned int GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const override; @@ -173,14 +177,60 @@ public: }; +// ---------------------------------------------------------------------------- +// DiffViewCtrl +// ---------------------------------------------------------------------------- + +class DiffViewCtrl : public wxDataViewCtrl +{ + bool m_has_long_strings{ false }; + bool m_empty_selection { false }; + int m_em_unit; + + struct ItemData + { + std::string opt_key; + wxString opt_name; + wxString old_val; + wxString new_val; + Preset::Type type; + bool is_long{ false }; + }; + + // tree items related to the options + std::map m_items_map; + std::map m_columns_width; + +public: + DiffViewCtrl(wxWindow* parent, wxSize size); + ~DiffViewCtrl() {} + + DiffModel* model{ nullptr }; + + void AppendBmpTextColumn(const wxString& label, unsigned model_column, int width, bool set_expander = false); + void AppendToggleColumn_(const wxString& label, unsigned model_column, int width); + void Rescale(int em = 0); + void Append(const std::string& opt_key, Preset::Type type, wxString category_name, wxString group_name, wxString option_name, + wxString old_value, wxString new_value, const std::string category_icon_name); + void Clear(); + + wxString get_short_string(wxString full_string); + bool has_selection() { return !m_empty_selection; } + void context_menu(wxDataViewEvent& event); + void item_value_changed(wxDataViewEvent& event); + void set_em_unit(int em) { m_em_unit = em; } + + std::vector unselected_options(Preset::Type type); + std::vector selected_options(); +}; + + //------------------------------------------ // UnsavedChangesDialog //------------------------------------------ class UnsavedChangesDialog : public DPIDialog { - wxDataViewCtrl* m_tree { nullptr }; - UnsavedChangesModel* m_tree_model { nullptr }; - + DiffViewCtrl* m_tree { nullptr }; ScalableButton* m_save_btn { nullptr }; ScalableButton* m_transfer_btn { nullptr }; ScalableButton* m_discard_btn { nullptr }; @@ -188,7 +238,6 @@ class UnsavedChangesDialog : public DPIDialog wxStaticText* m_info_line { nullptr }; wxCheckBox* m_remember_choice { nullptr }; - bool m_empty_selection { false }; bool m_has_long_strings { false }; int m_save_btn_id { wxID_ANY }; int m_move_btn_id { wxID_ANY }; @@ -209,19 +258,6 @@ class UnsavedChangesDialog : public DPIDialog // selected action after Dialog closing Action m_exit_action {Action::Undef}; - - struct ItemData - { - std::string opt_key; - wxString opt_name; - wxString old_val; - wxString new_val; - Preset::Type type; - bool is_long {false}; - }; - // tree items related to the options - std::map m_items_map; - // preset names which are modified in SavePresetDialog and related types std::vector> names_and_types; @@ -230,13 +266,9 @@ public: UnsavedChangesDialog(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset); ~UnsavedChangesDialog() {} - wxString get_short_string(wxString full_string); - void build(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header = ""); void update(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header); void update_tree(Preset::Type type, PresetCollection *presets); - void item_value_changed(wxDataViewEvent &event); - void context_menu(wxDataViewEvent &event); void show_info_line(Action action, std::string preset_name = ""); void update_config(Action action); void close(Action action); @@ -251,8 +283,8 @@ public: // short version of the previous function, for the case, when just one preset is modified std::string get_preset_name() { return names_and_types[0].first; } - std::vector get_unselected_options(Preset::Type type); - std::vector get_selected_options(); + std::vector get_unselected_options(Preset::Type type) { return m_tree->unselected_options(type); } + std::vector get_selected_options() { return m_tree->selected_options(); } protected: void on_dpi_changed(const wxRect& suggested_rect) override; @@ -270,6 +302,48 @@ public: ~FullCompareDialog() {} }; + +//------------------------------------------ +// DiffPresetDialog +//------------------------------------------ +class DiffPresetDialog : public DPIDialog +{ + DiffViewCtrl* m_tree { nullptr }; + wxStaticText* m_top_info_line { nullptr }; + wxStaticText* m_bottom_info_line { nullptr }; + wxCheckBox* m_show_all_presets { nullptr }; + + Preset::Type m_view_type { Preset::TYPE_INVALID }; + PrinterTechnology m_pr_technology; + std::unique_ptr m_preset_bundle_left; + std::unique_ptr m_preset_bundle_right; + + void update_tree(); + void update_bundles_from_app(); + void update_controls_visibility(Preset::Type type = Preset::TYPE_INVALID); + void update_compatibility(const std::string& preset_name, Preset::Type type, PresetBundle* preset_bundle); + + struct DiffPresets + { + PresetComboBox* presets_left { nullptr }; + ScalableButton* equal_bmp { nullptr }; + PresetComboBox* presets_right { nullptr }; + }; + + std::vector m_preset_combos; + +public: + DiffPresetDialog(); + ~DiffPresetDialog() {} + + void show(Preset::Type type = Preset::TYPE_INVALID); + void update_presets(Preset::Type type = Preset::TYPE_INVALID); + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; + void on_sys_color_changed() override; +}; + } }