diff --git a/resources/icons/compare.svg b/resources/icons/compare.svg new file mode 100644 index 00000000000..fcb458f7c46 --- /dev/null +++ b/resources/icons/compare.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index b6d44d58ff5..d35a32d2aa6 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -507,9 +507,9 @@ class PresetCollection { 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. diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index d61fc05e02f..82496da3896 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -329,6 +329,31 @@ const Option& OptionsSearcher::get_option(const std::string& opt_key) const return options[it - options.begin()]; } +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) || + groups_and_categories.find(opt_key) == groups_and_categories.end()) + return options[it - options.begin()]; + + const GroupAndCategory& gc = groups_and_categories.at(opt_key); + if (gc.group.IsEmpty() || gc.category.IsEmpty()) + return options[it - options.begin()]; + + wxString suffix; + wxString suffix_local; + if (gc.category == "Machine limits") { + suffix = opt_key.back() == '1' ? L("Stealth") : L("Normal"); + suffix_local = " " + _(suffix); + suffix = " " + suffix; + } + + 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(gc.category, type).ToStdWstring() }; +} + 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 f8c9dffa6aa..1f2909564db 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -117,6 +117,7 @@ class OptionsSearcher 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 aec2f87542b..a0e0e554b4a 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) { @@ -2057,11 +2063,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) @@ -2096,7 +2109,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(); @@ -2386,7 +2400,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); } @@ -2466,24 +2482,15 @@ void TabPrinter::build_unregular_pages() size_t n_before_extruders = 2; // Count of pages before Extruder pages bool is_marlin_flavor = m_config->option>("gcode_flavor")->value == gcfMarlin; + // just for first build, if SLA printer preset is selected + bool just_initial_build = m_printer_technology == ptSLA; + /* ! Freeze/Thaw in this function is needed to avoid call OnPaint() for erased pages * and be cause of application crash, when try to change Preset in moment, * when one of unregular pages is selected. * */ 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 @@ -2495,12 +2502,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 || just_initial_build)) { auto page = build_kinematics_page(); -#ifdef __WXMSW__ -// layout_page(page); -#endif - m_pages.insert(m_pages.begin() + n_before_extruders, page); + if (just_initial_build) + page->clear(); + else + m_pages.insert(m_pages.begin() + n_before_extruders, page); } if (is_marlin_flavor) @@ -2518,7 +2525,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 (just_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")); @@ -2527,8 +2535,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 (just_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 @@ -2633,10 +2645,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 @@ -2647,6 +2655,10 @@ void TabPrinter::build_unregular_pages() Thaw(); m_extruders_count_old = m_extruders_count; + + if (just_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 @@ -3365,6 +3377,12 @@ void Tab::OnKeyDown(wxKeyEvent& event) event.Skip(); } +void Tab::compare_preset() +{ + DiffPresetDialog dlg(m_type); + dlg.ShowModal(); +} + // 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. diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 29588ba20e0..4b0a77cb96e 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -112,6 +112,7 @@ class Tab: public wxPanel 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 @@ class Tab: public wxPanel 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(); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index b4b38b4bd2a..9cc71ae68f8 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -22,6 +22,7 @@ //#include "fts_fuzzy_match.h" #include "BitmapCache.hpp" +#include "PresetComboBoxes.hpp" using boost::optional; @@ -223,12 +224,6 @@ UnsavedChangesModel::UnsavedChangesModel(wxWindow* parent) : { } -UnsavedChangesModel::~UnsavedChangesModel() -{ - for (ModelNode* preset_node : m_preset_nodes) - delete preset_node; -} - wxDataViewItem UnsavedChangesModel::AddPreset(Preset::Type type, wxString preset_name, PrinterTechnology pt) { // "color" strings @@ -288,7 +283,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 +296,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); @@ -463,7 +458,6 @@ wxDataViewItem UnsavedChangesModel::GetParent(const wxDataViewItem& item) const ModelNode* node = static_cast(item.GetID()); - // "MyMusic" also has no parent if (node->IsRoot()) return wxDataViewItem(nullptr); @@ -482,17 +476,13 @@ bool UnsavedChangesModel::IsContainer(const wxDataViewItem& item) const unsigned int UnsavedChangesModel::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(); } @@ -522,10 +512,57 @@ static void rescale_children(ModelNode* parent) void UnsavedChangesModel::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 UnsavedChangesModel::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 UnsavedChangesModel::Clear() +{ + while (!m_preset_nodes.empty()) + Delete(wxDataViewItem(m_preset_nodes.back().get())); } @@ -877,6 +914,17 @@ static std::string get_pure_opt_key(std::string opt_key) return opt_key; } +static wxString get_full_label(std::string opt_key, const DynamicPrintConfig& config) +{ + 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) { int opt_idx = get_id_from_opt_key(opt_key); @@ -1213,6 +1261,225 @@ FullCompareDialog::FullCompareDialog(const wxString& option_name, const wxString } +static const PresetCollection* get_preset_collection(Preset::Type type_) { + return type_ == Preset::Type::TYPE_PRINT ? &wxGetApp().preset_bundle->prints : + type_ == Preset::Type::TYPE_SLA_PRINT ? &wxGetApp().preset_bundle->sla_prints : + type_ == Preset::Type::TYPE_FILAMENT ? &wxGetApp().preset_bundle->filaments : + type_ == Preset::Type::TYPE_SLA_MATERIAL ? &wxGetApp().preset_bundle->sla_materials : + type_ == Preset::Type::TYPE_PRINTER ? &wxGetApp().preset_bundle->printers : + nullptr; +} + +//------------------------------------------ +// DiffPresetDialog +//------------------------------------------ +DiffPresetDialog::DiffPresetDialog(Preset::Type type/* = Preset::Type::TYPE_INVALID*/) + : DPIDialog(static_cast(wxGetApp().mainframe), wxID_ANY, format_wxstr(_L("Compare %1% Presets"), type), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), + m_type(type) +{ + 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(); + + 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()); + + const PresetCollection* collection = get_preset_collection(type); + wxBoxSizer* presets_sizer = new wxBoxSizer(wxHORIZONTAL); + + auto add_preset_combobox = [collection, presets_sizer, type, em, this](PresetComboBox** cb) { + *cb = new PresetComboBox(this, type, wxSize(em * 35, -1)); + (*cb)->set_selection_changed_function([this](int selection) { update_tree(); }); + (*cb)->update(collection->get_selected_preset().name); + + presets_sizer->Add(*cb, 1, wxRIGHT|wxLEFT, 5); + }; + + add_preset_combobox(&m_presets_left); + add_preset_combobox(&m_presets_right); + + m_tree = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(em * 65, em * 40), 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); + + 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, 35 * em); + append_bmp_text_column(_L("Left Preset Value"), UnsavedChangesModel::colOldValue, 15 * em); + append_bmp_text_column(_L("Right Preset Value"), UnsavedChangesModel::colNewValue, 15 * 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); + + 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_bottom_info_line, 0, wxEXPAND | wxALL, 2 * border); + topSizer->Add(m_tree, 1, wxEXPAND | wxALL, border); + + update_tree(); + + this->SetMinSize(wxSize(80 * em, 30 * em)); + this->SetSizer(topSizer); + topSizer->SetSizeHints(this); +} + +void DiffPresetDialog::update_tree() +{ + Search::OptionsSearcher& searcher = wxGetApp().sidebar().get_searcher(); + searcher.sort_options_by_opt_key(); + + m_tree_model->Clear(); + wxString bottom_info = ""; + + // list of the presets with unsaved changes + std::vector presets_list; + if (m_type == Preset::TYPE_INVALID) + { + PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology(); + + for (Tab* tab : wxGetApp().tabs_list) + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) + presets_list.emplace_back(tab->get_presets()); + } + else + presets_list.emplace_back(get_preset_collection(m_type)); + + // Display a dialog showing the dirty options in a human readable form. + for (const PresetCollection* presets : presets_list) + { + const Preset* left_preset = presets->find_preset(into_u8(m_presets_left ->GetString(m_presets_left ->GetSelection()))); + const Preset* right_preset = presets->find_preset(into_u8(m_presets_right->GetString(m_presets_right->GetSelection()))); + if (!left_preset || !right_preset) { + bottom_info = _L("One of the presets doesn't found"); + continue; + } + + const DynamicPrintConfig& left_config = left_preset->config; + const PrinterTechnology& left_pt = left_preset->printer_technology(); + const DynamicPrintConfig& right_congig = right_preset->config; + + Preset::Type type = presets->type(); + + if (left_pt != right_preset->printer_technology()) { + bottom_info = _L("Comparable presets has different printer technology"); + continue; + } + + // Collect dirty options. + const bool deep_compare = (type == Preset::TYPE_PRINTER || type == Preset::TYPE_SLA_MATERIAL); + auto dirty_options = presets->dirty_options(left_preset, right_preset, deep_compare); + + if (dirty_options.empty()) { + bottom_info = _L("Presets are the same"); + continue; + } + + 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_model->AddOption(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_model->AddOption(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_model->AddOption(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(); + bool show_tree = bottom_info.IsEmpty(); + 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); + + m_tree->GetColumn(UnsavedChangesModel::colIconText)->SetWidth(35 * em); + m_tree->GetColumn(UnsavedChangesModel::colOldValue)->SetWidth(15 * em); + m_tree->GetColumn(UnsavedChangesModel::colNewValue)->SetWidth(15 * em); + + m_tree_model->Rescale(); + m_tree->Refresh(); + + Fit(); + Refresh(); +} + +void DiffPresetDialog::on_sys_color_changed() +{ + // msw_rescale updates just icons, so use it + m_tree_model->Rescale(); + m_tree->Refresh(); + + Refresh(); +} + + + } diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index 232802b661a..b5d5e0e5a14 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -20,6 +20,7 @@ namespace GUI{ // ---------------------------------------------------------------------------- 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 @@ class ModelNode 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 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}; + // preset(root) node ModelNode(Preset::Type preset_type, wxWindow* parent_win, const wxString& text, const std::string& icon_name); @@ -113,7 +114,7 @@ class ModelNode class UnsavedChangesModel : public wxDataViewModel { wxWindow* m_parent_win { nullptr }; - std::vector m_preset_nodes; + ModelNodePtrArray m_preset_nodes; wxDataViewCtrl* m_ctrl{ nullptr }; @@ -144,7 +145,7 @@ class UnsavedChangesModel : public wxDataViewModel }; UnsavedChangesModel(wxWindow* parent); - ~UnsavedChangesModel(); + ~UnsavedChangesModel() {} void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } @@ -159,6 +160,9 @@ class UnsavedChangesModel : public wxDataViewModel 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; @@ -270,6 +274,34 @@ class FullCompareDialog : public wxDialog ~FullCompareDialog() {} }; + +//------------------------------------------ +// DiffPresetDialog +//------------------------------------------ +class DiffPresetDialog : public DPIDialog +{ + wxDataViewCtrl* m_tree{ nullptr }; + UnsavedChangesModel* m_tree_model{ nullptr }; + + PresetComboBox* m_presets_left { nullptr }; + PresetComboBox* m_presets_right { nullptr }; + + wxStaticText* m_top_info_line { nullptr }; + wxStaticText* m_bottom_info_line{ nullptr }; + + Preset::Type m_type; + + void update_tree(); + +public: + DiffPresetDialog(Preset::Type type = Preset::Type::TYPE_INVALID/**/); + ~DiffPresetDialog() {} + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; + void on_sys_color_changed() override; +}; + } }