diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 336475d2e..badaa7a04 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -9,6 +9,12 @@ #include #include +#include "wx/generic/private/markuptext.h" +#include "wx/generic/private/rowheightcache.h" +#include "wx/generic/private/widthcalc.h" +#if wxUSE_ACCESSIBILITY +#include "wx/private/markupparser.h" +#endif // wxUSE_ACCESSIBILITY namespace Slic3r { @@ -1575,9 +1581,44 @@ wxDataViewRenderer(wxT("PrusaDataViewBitmapText"), mode, align) } #endif // ENABLE_NONCUSTOM_DATA_VIEW_RENDERING +BitmapTextRenderer::~BitmapTextRenderer() +{ +#ifdef SUPPORTS_MARKUP + if (m_markupText) + delete m_markupText; +#endif // SUPPORTS_MARKUP +} + +#ifdef SUPPORTS_MARKUP +void BitmapTextRenderer::EnableMarkup(bool enable) +{ + if (enable) + { + if (!m_markupText) + { + m_markupText = new wxItemMarkupText(wxString()); + } + } + else + { + if (m_markupText) + { + delete m_markupText; + m_markupText = nullptr; + } + } +} +#endif // SUPPORTS_MARKUP + bool BitmapTextRenderer::SetValue(const wxVariant &value) { m_value << value; + +#ifdef SUPPORTS_MARKUP + if (m_markupText) + m_markupText->SetMarkup(m_value.GetText()); +#endif // SUPPORTS_MARKUP + return true; } @@ -1589,6 +1630,11 @@ bool BitmapTextRenderer::GetValue(wxVariant& WXUNUSED(value)) const #if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY wxString BitmapTextRenderer::GetAccessibleDescription() const { +#ifdef SUPPORTS_MARKUP + if (m_markupText) + return wxMarkupParser::Strip(m_text); +#endif // SUPPORTS_MARKUP + return m_value.GetText(); } #endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING @@ -1609,7 +1655,17 @@ bool BitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state) xoffset = icon_sz.x + 4; } - RenderText(m_value.GetText(), xoffset, rect, dc, state); +#ifdef SUPPORTS_MARKUP + if (m_markupText) + { + int flags = 0; + + rect.x += xoffset; + m_markupText->Render(GetView(), *dc, rect, flags, GetEllipsizeMode()); + } + else +#endif // SUPPORTS_MARKUP + RenderText(m_value.GetText(), xoffset, rect, dc, state); return true; } @@ -1618,7 +1674,23 @@ wxSize BitmapTextRenderer::GetSize() const { if (!m_value.GetText().empty()) { - wxSize size = GetTextExtent(m_value.GetText()); + wxSize size; +#ifdef SUPPORTS_MARKUP + if (m_markupText) + { + wxDataViewCtrl* const view = GetView(); + wxClientDC dc(view); + if (GetAttr().HasFont()) + dc.SetFont(GetAttr().GetEffectiveFont(view->GetFont())); + + size = m_markupText->Measure(dc); + } + else +#endif // SUPPORTS_MARKUP + size = GetTextExtent(m_value.GetText()); + + int lines = m_value.GetText().Freq('\n') + 1; + size.SetHeight(size.GetHeight() * lines); if (m_value.GetBitmap().IsOk()) size.x += m_value.GetBitmap().GetWidth() + 4; diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index c18484266..c8545e4a4 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -2,9 +2,10 @@ #define slic3r_GUI_ObjectDataViewModel_hpp_ #include - #include +#include "GUI_App.hpp" + namespace Slic3r { enum class ModelVolumeType : int; @@ -83,9 +84,19 @@ public: ) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align), m_parent(parent) - {} + { +#ifdef SUPPORTS_MARKUP + m_markupText = nullptr; +#endif // SUPPORTS_MARKUP + } #endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + ~BitmapTextRenderer(); + +#ifdef SUPPORTS_MARKUP + void EnableMarkup(bool enable = true); +#endif // SUPPORTS_MARKUP + bool SetValue(const wxVariant& value); bool GetValue(wxVariant& value) const; #if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY @@ -114,6 +125,10 @@ private: DataViewBitmapText m_value; bool m_was_unusable_symbol{ false }; wxWindow* m_parent{ nullptr }; + +#ifdef SUPPORTS_MARKUP + class wxItemMarkupText* m_markupText; +#endif // SUPPORTS_MARKUP }; diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 6eab6b72a..d13ef469f 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -321,6 +321,14 @@ const Option& OptionsSearcher::get_option(size_t pos_in_filter) const return options[found[pos_in_filter].option_idx]; } +const Option& OptionsSearcher::get_option(const std::string& opt_key) const +{ + auto it = std::upper_bound(options.begin(), options.end(), Option({ boost::nowide::widen(opt_key) })); + assert(it != options.end()); + + return options[it - options.begin()]; +} + 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 8202222e9..a57e0d015 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -37,8 +37,8 @@ struct GroupAndCategory { }; struct Option { - bool operator<(const Option& other) const { return other.label > this->label; } - bool operator>(const Option& other) const { return other.label < this->label; } +// bool operator<(const Option& other) const { return other.label > this->label; } + bool operator<(const Option& other) const { return other.opt_key > this->opt_key; } // Fuzzy matching works at a character level. Thus matching with wide characters is a safer bet than with short characters, // though for some languages (Chinese?) it may not work correctly. @@ -116,12 +116,18 @@ 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; const std::vector& found_options() { return found; } const GroupAndCategory& get_group_and_category (const std::string& opt_key) { return groups_and_categories[opt_key]; } std::string& search_string() { return search_line; } void set_printer_technology(PrinterTechnology pt) { printer_technology = pt; } + + void sort_options_by_opt_key() { + std::sort(options.begin(), options.end(), [](const Option& o1, const Option& o2) { + return o1.opt_key < o2.opt_key; }); + } }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 49317f802..dbb8761d3 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3133,8 +3133,16 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr*/, const std::string& new_printer_name /*= ""*/) { UnsavedChangesDialog dlg(m_type); - dlg.ShowModal(); - + if (dlg.ShowModal() == wxID_CANCEL) + return false; + if (dlg.just_continue()) + return true; + if (dlg.save_preset()) + // save selected changes + return false; + if (dlg.move_preset()) + // move selected changes + return false; if (presets == nullptr) presets = m_presets; // Display a dialog showing the dirty options in a human readable form. diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 21da295d4..46f086765 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -13,11 +13,12 @@ #include "GUI_App.hpp" #include "Plater.hpp" #include "Tab.hpp" +#include "ObjectDataViewModel.hpp" #define FTS_FUZZY_MATCH_IMPLEMENTATION #include "fts_fuzzy_match.h" -#include "imgui/imconfig.h" +#include "BitmapCache.hpp" using boost::optional; @@ -29,12 +30,39 @@ namespace GUI { // ModelNode: a node inside UnsavedChangesModel // ---------------------------------------------------------------------------- +static const std::map type_icon_names = { + {Preset::TYPE_PRINT, "cog" }, + {Preset::TYPE_SLA_PRINT, "cog" }, + {Preset::TYPE_FILAMENT, "spool" }, + {Preset::TYPE_SLA_MATERIAL, "resin" }, + {Preset::TYPE_PRINTER, "sla_printer" }, +}; + +static std::string black = "#000000"; +static std::string grey = "#808080"; +static std::string orange = "#ed6b21"; + +static void color_string(wxString& str, const std::string& color) +{ +#ifdef SUPPORTS_MARKUP + str = from_u8((boost::format("%2%") % color % into_u8(str)).str()); +#endif +} + +static void make_string_bold(wxString& str) +{ +#ifdef SUPPORTS_MARKUP + str = from_u8((boost::format("%1%") % into_u8(str)).str()); +#endif +} + // preset(root) node -ModelNode::ModelNode(const wxString& text, Preset::Type preset_type) : +ModelNode::ModelNode(Preset::Type preset_type, const wxString& text) : m_parent(nullptr), m_preset_type(preset_type), m_text(text) { + m_icon = create_scaled_bitmap(type_icon_names.at(preset_type)); } // group node @@ -42,38 +70,230 @@ ModelNode::ModelNode(ModelNode* parent, const wxString& text, const std::string& m_parent(parent), m_text(text) { + m_icon = create_scaled_bitmap(icon_name); } -// group node -ModelNode::ModelNode(ModelNode* parent, const wxString& text, bool is_option) : +// category node +ModelNode::ModelNode(ModelNode* parent, const wxString& text) : m_parent(parent), - m_text(text), - m_container(!is_option) + m_text(text) { } +wxBitmap ModelNode::get_bitmap(const wxString& color) +{ + /* It's supposed that standard size of an icon is 48px*16px for 100% scaled display. + * So set sizes for solid_colored icons used for filament preset + * and scale them in respect to em_unit value + */ + const double em = em_unit(m_parent_win); + const int icon_width = lround(6.4 * em); + const int icon_height = lround(1.6 * em); + + BitmapCache bmp_cache; + unsigned char rgb[3]; + BitmapCache::parse_color(into_u8(color), rgb); + // there is no need to scale created solid bitmap + return bmp_cache.mksolid(icon_width, icon_height, rgb, true); +} + +// option node +ModelNode::ModelNode(ModelNode* parent, const wxString& text, const wxString& old_value, const wxString& new_value) : + m_parent(parent), + m_old_color(old_value.StartsWith("#") ? old_value : ""), + m_new_color(new_value.StartsWith("#") ? new_value : ""), + m_container(false), + m_text(text), + m_old_value(old_value), + m_new_value(new_value) +{ + // check if old/new_value is color + if (m_old_color.IsEmpty()) { + if (!m_new_color.IsEmpty()) + m_old_value = _L("Undef"); + } + else { + m_old_color_bmp = get_bitmap(m_old_color); + m_old_value.Clear(); + } + + if (m_new_color.IsEmpty()) { + if (!m_old_color.IsEmpty()) + m_new_value = _L("Undef"); + } + else { + m_new_color_bmp = get_bitmap(m_new_color); + m_new_value.Clear(); + } + + // "color" strings + color_string(m_old_value, black); + color_string(m_new_value, orange); +} + +void ModelNode::UpdateEnabling() +{ +#ifdef SUPPORTS_MARKUP + auto change_text_color = [](wxString& str, const std::string& clr_from, const std::string& clr_to) + { + std::string old_val = into_u8(str); + boost::replace_all(old_val, clr_from, clr_to); + str = from_u8(old_val); + }; + + if (!m_toggle) { + change_text_color(m_text, black, grey); + change_text_color(m_old_value, black, grey); + change_text_color(m_new_value, orange,grey); + } + else { + change_text_color(m_text, grey, black); + change_text_color(m_old_value, grey, black); + change_text_color(m_new_value, grey, orange); + } +#endif + // update icons for the colors + if (!m_old_color.IsEmpty()) + m_old_color_bmp = get_bitmap(m_toggle? m_old_color : grey); + if (!m_new_color.IsEmpty()) + m_new_color_bmp = get_bitmap(m_toggle? m_new_color : grey); +} + // ---------------------------------------------------------------------------- // UnsavedChangesModel // ---------------------------------------------------------------------------- -UnsavedChangesModel::UnsavedChangesModel(wxWindow* parent) +UnsavedChangesModel::UnsavedChangesModel(wxWindow* parent) : + m_parent_win(parent) { - int icon_id = 0; - for (const std::string& icon : { "cog", "printer", "sla_printer", "spool", "resin" }) - m_icon[icon_id++] = ScalableBitmap(parent, icon); - - m_root = new ModelNode("Preset", Preset::TYPE_INVALID); } UnsavedChangesModel::~UnsavedChangesModel() { - delete m_root; + for (ModelNode* preset_node : m_preset_nodes) + delete preset_node; +} + +wxDataViewItem UnsavedChangesModel::AddPreset(Preset::Type type, wxString preset_name) +{ + // "color" strings + color_string(preset_name, black); + make_string_bold(preset_name); + + auto preset = new ModelNode(type, preset_name); + m_preset_nodes.emplace_back(preset); + + wxDataViewItem child((void*)preset); + wxDataViewItem parent(nullptr); + + ItemAdded(parent, child); + return child; +} + +ModelNode* UnsavedChangesModel::AddOption(ModelNode* group_node, wxString option_name, wxString old_value, wxString new_value) +{ + ModelNode* option = new ModelNode(group_node, option_name, old_value, new_value); + group_node->Append(option); + ItemAdded(wxDataViewItem((void*)group_node), wxDataViewItem((void*)option)); + + return option; +} + +ModelNode* UnsavedChangesModel::AddOptionWithGroup(ModelNode* category_node, wxString group_name, wxString option_name, wxString old_value, wxString new_value) +{ + ModelNode* group_node = new ModelNode(category_node, group_name); + category_node->Append(group_node); + wxDataViewItem group_item = wxDataViewItem((void*)group_node); + ItemAdded(wxDataViewItem((void*)category_node), group_item); + m_ctrl->Expand(group_item); + + return AddOption(group_node, option_name, old_value, new_value); +} + +ModelNode* UnsavedChangesModel::AddOptionWithGroupAndCategory(ModelNode* preset_node, wxString category_name, wxString group_name, wxString option_name, wxString old_value, wxString new_value) +{ + ModelNode* category_node = new ModelNode(preset_node, category_name, "cog"); + preset_node->Append(category_node); + ItemAdded(wxDataViewItem((void*)preset_node), wxDataViewItem((void*)category_node)); + + 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, + wxString old_value, wxString new_value) +{ + // "color" strings + color_string(category_name, black); + color_string(group_name, black); + color_string(option_name, black); + + // "make" strings bold + make_string_bold(category_name); + make_string_bold(group_name); + + // add items + for (ModelNode* preset : m_preset_nodes) + if (preset->type() == type) + { + for (ModelNode* category : preset->GetChildren()) + if (category->text() == category_name) + { + for (ModelNode* group : category->GetChildren()) + if (group->text() == group_name) + return wxDataViewItem((void*)AddOption(group, option_name, old_value, new_value)); + + return wxDataViewItem((void*)AddOptionWithGroup(category, group_name, option_name, old_value, new_value)); + } + + return wxDataViewItem((void*)AddOptionWithGroupAndCategory(preset, category_name, group_name, option_name, old_value, new_value)); + } + + return wxDataViewItem(nullptr); +} + +static void update_children(ModelNode* parent) +{ + if (parent->IsContainer()) { + bool toggle = parent->IsToggled(); + for (ModelNode* child : parent->GetChildren()) { + child->Toggle(toggle); + child->UpdateEnabling(); + update_children(child); + } + } +} + +static void update_parents(ModelNode* node) +{ + ModelNode* parent = node->GetParent(); + if (parent) { + bool toggle = false; + for (ModelNode* child : parent->GetChildren()) { + if (child->IsToggled()) { + toggle = true; + break; + } + } + parent->Toggle(toggle); + parent->UpdateEnabling(); + update_parents(parent); + } +} + +void UnsavedChangesModel::UpdateItemEnabling(wxDataViewItem item) +{ + assert(item.IsOk()); + ModelNode* node = (ModelNode*)item.GetID(); + node->UpdateEnabling(); + + update_children(node); + update_parents(node); } void UnsavedChangesModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const { - wxASSERT(item.IsOk()); + assert(item.IsOk()); ModelNode* node = (ModelNode*)item.GetID(); switch (col) @@ -81,20 +301,14 @@ void UnsavedChangesModel::GetValue(wxVariant& variant, const wxDataViewItem& ite case colToggle: variant = node->m_toggle; break; - case colTypeIcon: - variant << node->m_type_icon; - break; - case colGroupIcon: - variant << node->m_group_icon; - break; - case colMarkedText: - variant =node->m_text; + case colIconText: + variant << DataViewBitmapText(node->m_text, node->m_icon); break; case colOldValue: - variant =node->m_text; + variant << DataViewBitmapText(node->m_old_value, node->m_old_color_bmp); break; case colNewValue: - variant =node->m_text; + variant << DataViewBitmapText(node->m_new_value, node->m_new_color_bmp); break; default: @@ -109,24 +323,27 @@ bool UnsavedChangesModel::SetValue(const wxVariant& variant, const wxDataViewIte ModelNode* node = (ModelNode*)item.GetID(); switch (col) { + case colIconText: { + DataViewBitmapText data; + data << variant; + node->m_icon = data.GetBitmap(); + node->m_text = data.GetText(); + return true; } case colToggle: node->m_toggle = variant.GetBool(); return true; - case colTypeIcon: - node->m_type_icon << variant; - return true; - case colGroupIcon: - node->m_group_icon << variant; - return true; - case colMarkedText: - node->m_text = variant.GetString(); - return true; - case colOldValue: - node->m_text = variant.GetString(); - return true; - case colNewValue: - node->m_text = variant.GetString(); - return true; + case colOldValue: { + DataViewBitmapText data; + data << variant; + node->m_old_color_bmp = data.GetBitmap(); + node->m_old_value = data.GetText(); + return true; } + case colNewValue: { + DataViewBitmapText data; + data << variant; + node->m_new_color_bmp = data.GetBitmap(); + node->m_new_value = data.GetText(); + return true; } default: wxLogError("UnsavedChangesModel::SetValue: wrong column"); } @@ -136,11 +353,11 @@ bool UnsavedChangesModel::SetValue(const wxVariant& variant, const wxDataViewIte bool UnsavedChangesModel::IsEnabled(const wxDataViewItem& item, unsigned int col) const { assert(item.IsOk()); - - ModelNode* node = (ModelNode*)item.GetID(); + if (col == colToggle) + return true; // disable unchecked nodes - return !node->IsToggle(); + return ((ModelNode*)item.GetID())->IsToggled(); } wxDataViewItem UnsavedChangesModel::GetParent(const wxDataViewItem& item) const @@ -152,7 +369,7 @@ wxDataViewItem UnsavedChangesModel::GetParent(const wxDataViewItem& item) const ModelNode* node = (ModelNode*)item.GetID(); // "MyMusic" also has no parent - if (node == m_root) + if (node->IsRoot()) return wxDataViewItem(nullptr); return wxDataViewItem((void*)node->GetParent()); @@ -172,8 +389,9 @@ unsigned int UnsavedChangesModel::GetChildren(const wxDataViewItem& parent, wxDa { ModelNode* node = (ModelNode*)parent.GetID(); if (!node) { - array.Add(wxDataViewItem((void*)m_root)); - return 1; + for (auto preset_node : m_preset_nodes) + array.Add(wxDataViewItem((void*)preset_node)); + return m_preset_nodes.size(); } if (node->GetChildCount() == 0) @@ -191,13 +409,16 @@ unsigned int UnsavedChangesModel::GetChildren(const wxDataViewItem& parent, wxDa wxString UnsavedChangesModel::GetColumnType(unsigned int col) const { - if (col == colToggle) + switch (col) + { + case colToggle: return "bool"; - - if (col < colMarkedText) - return "wxBitmap"; - - return "string"; + case colIconText: + case colOldValue: + case colNewValue: + default: + return "DataViewBitmapText";//"string"; + } } @@ -214,43 +435,215 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type) int border = 10; int em = em_unit(); - changes_tree = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(em * 80, em * 60), wxBORDER_SIMPLE); - changes_tree_model = new UnsavedChangesModel(this); - changes_tree->AssociateModel(changes_tree_model); + m_tree = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(em * 80, em * 30), wxBORDER_SIMPLE | wxDV_VARIABLE_LINE_HEIGHT); + m_tree_model = new UnsavedChangesModel(this); + m_tree->AssociateModel(m_tree_model); + m_tree_model->SetAssociatedControl(m_tree); - changes_tree->AppendToggleColumn(L"\u2610", UnsavedChangesModel::colToggle);//2610,11,12 //2714 - changes_tree->AppendBitmapColumn("", UnsavedChangesModel::colTypeIcon); - changes_tree->AppendBitmapColumn("", UnsavedChangesModel::colGroupIcon); - - wxDataViewTextRenderer* const markupRenderer = new wxDataViewTextRenderer(); + m_tree->AppendToggleColumn(/*L"\u2714"*/"", UnsavedChangesModel::colToggle, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em, wxALIGN_NOT);//2610,11,12 //2714 + BitmapTextRenderer* renderer = new BitmapTextRenderer(m_tree); #ifdef SUPPORTS_MARKUP - markupRenderer->EnableMarkup(); + renderer->EnableMarkup(); #endif + m_tree->AppendColumn(new wxDataViewColumn("", renderer, UnsavedChangesModel::colIconText, 30 * em, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE)); + m_tree->AppendColumn(new wxDataViewColumn("Old value", renderer, UnsavedChangesModel::colOldValue, 20 * em, wxALIGN_TOP)); + m_tree->AppendColumn(new wxDataViewColumn("New value", renderer, UnsavedChangesModel::colNewValue, 20 * em, wxALIGN_TOP)); - changes_tree->AppendColumn(new wxDataViewColumn("", markupRenderer, UnsavedChangesModel::colMarkedText, wxCOL_WIDTH_AUTOSIZE, wxALIGN_LEFT)); - changes_tree->AppendColumn(new wxDataViewColumn("Old value", markupRenderer, UnsavedChangesModel::colOldValue, wxCOL_WIDTH_AUTOSIZE, wxALIGN_LEFT)); - changes_tree->AppendColumn(new wxDataViewColumn("New value", markupRenderer, UnsavedChangesModel::colNewValue, wxCOL_WIDTH_AUTOSIZE, wxALIGN_LEFT)); + m_tree->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &UnsavedChangesDialog::item_value_changed, this); - wxStdDialogButtonSizer* cancel_btn = this->CreateStdDialogButtonSizer(wxCANCEL); + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCANCEL); + + Tab* tab = wxGetApp().get_tab(type); + assert(tab); + + PresetCollection* presets = tab->get_presets(); + + wxString label= from_u8((boost::format(_u8L("Save selected to preset:%1%"))% ("\"" + presets->get_selected_preset().name + "\"")).str()); + auto save_btn = new wxButton(this, m_save_btn_id = NewControlId(), label); + save_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Save); }); + buttons->Insert(0, save_btn, 0, wxLEFT, 5); + + label = from_u8((boost::format(_u8L("Move selected to preset:%1%"))% ("\"NewSelectedPreset\"")).str()); + auto move_btn = new wxButton(this, m_move_btn_id = NewControlId(), label); + move_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Move); }); + buttons->Insert(1, move_btn, 0, wxLEFT, 5); + + auto continue_btn = new wxButton(this, m_continue_btn_id = NewControlId(), _L("Continue without changes")); + continue_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Continue); }); + buttons->Insert(2, continue_btn, 0, wxLEFT, 5); wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); - topSizer->Add(new wxStaticText(this, wxID_ANY, _L("There is unsaved changes for the current preset") + ":"), 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(changes_tree, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(cancel_btn, 0, wxEXPAND | wxALL, border); + topSizer->Add(new wxStaticText(this, wxID_ANY, _L("There is unsaved changes for") + (": \"" + tab->title() + "\"")), 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(m_tree, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(buttons, 0, wxEXPAND | wxALL, border); + + update(type); SetSizer(topSizer); topSizer->SetSizeHints(this); } +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(); +} + +void UnsavedChangesDialog::close(Action action) +{ + m_action = action; + this->EndModal(wxID_CLOSE); +} + +template +wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config) +{ + const std::vector& names = config.def()->options.at(opt_key).enum_labels;//ConfigOptionEnum::get_enum_names(); + T val = config.option>(opt_key)->value; + return from_u8(_u8L(names[static_cast(val)])); +} + +static wxString get_string_value(const std::string& opt_key, const DynamicPrintConfig& config) +{ + wxString out; + + // FIXME controll, if opt_key has index + int opt_idx = 0; + + ConfigOptionType type = config.def()->options.at(opt_key).type; + + switch (type) { + case coInt: + return from_u8((boost::format("%1%") % config.opt_int(opt_key)).str()); + case coInts: { + const ConfigOptionInts* opt = config.opt(opt_key); + if (opt) + return from_u8((boost::format("%1%") % opt->get_at(opt_idx)).str()); + break; + } + case coBool: + return config.opt_bool(opt_key) ? "true" : "false"; + case coBools: { + const ConfigOptionBools* opt = config.opt(opt_key); + if (opt) + return opt->get_at(opt_idx) ? "true" : "false"; + break; + } + case coPercent: + return from_u8((boost::format("%1%%%") % int(config.optptr(opt_key)->getFloat())).str()); + case coPercents: { + const ConfigOptionPercents* opt = config.opt(opt_key); + if (opt) + return from_u8((boost::format("%1%%%") % int(opt->get_at(opt_idx))).str()); + break; + } + case coFloat: + return double_to_string(config.opt_float(opt_key)); + case coFloats: { + const ConfigOptionFloats* opt = config.opt(opt_key); + if (opt) + return double_to_string(opt->get_at(opt_idx)); + break; + } + case coString: + return from_u8(config.opt_string(opt_key)); + case coStrings: { + const ConfigOptionStrings* strings = config.opt(opt_key); + if (strings) { + if (opt_key == "compatible_printers" || opt_key == "compatible_prints") { + if (strings->empty()) + return _L("All"); + for (size_t id = 0; id < strings->size(); id++) + out += from_u8(strings->get_at(id)) + "\n"; + out.RemoveLast(1); + return out; + } + if (!strings->empty()) + return from_u8(strings->get_at(opt_idx)); + } + break; + } + case coFloatOrPercent: { + const ConfigOptionFloatOrPercent* opt = config.opt(opt_key); + if (opt) + out = double_to_string(opt->value) + (opt->percent ? "%" : ""); + return out; + } + case coEnum: { + if (opt_key == "top_fill_pattern" || + opt_key == "bottom_fill_pattern" || + opt_key == "fill_pattern") + return get_string_from_enum(opt_key, config); + if (opt_key == "gcode_flavor") + return get_string_from_enum(opt_key, config); + if (opt_key == "ironing_type") + return get_string_from_enum(opt_key, config); + if (opt_key == "support_material_pattern") + return get_string_from_enum(opt_key, config); + if (opt_key == "seam_position") + return get_string_from_enum(opt_key, config); + if (opt_key == "display_orientation") + return get_string_from_enum(opt_key, config); + if (opt_key == "support_pillar_connection_mode") + return get_string_from_enum(opt_key, config); + break; + } + case coPoints: { + /* + if (opt_key == "bed_shape") { + config.option(opt_key)->values = boost::any_cast>(value); + break; + } + ConfigOptionPoints* vec_new = new ConfigOptionPoints{ boost::any_cast(value) }; + config.option(opt_key)->set_at(vec_new, opt_index, 0); + */ + return "Points"; + } + default: + break; + } + return out; +} + +void UnsavedChangesDialog::update(Preset::Type type) +{ + Tab* tab = wxGetApp().get_tab(type); + assert(tab); + + PresetCollection* presets = tab->get_presets(); + // Display a dialog showing the dirty options in a human readable form. + const DynamicPrintConfig& old_config = presets->get_selected_preset().config; + const DynamicPrintConfig& new_config = presets->get_edited_preset().config; + + m_tree_model->AddPreset(type, from_u8(presets->get_edited_preset().name)); + + Search::OptionsSearcher& searcher = wxGetApp().sidebar().get_searcher(); + searcher.sort_options_by_opt_key(); + + // Collect dirty options. + for (const std::string& opt_key : presets->current_dirty_options()) { + const Search::Option& option = searcher.get_option(opt_key); + + m_tree_model->AddOption(type, option.category_local, option.group_local, option.label_local, + get_string_value(opt_key, old_config), get_string_value(opt_key, new_config)); + } +} + + void UnsavedChangesDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); - msw_buttons_rescale(this, em, { wxID_CANCEL }); + msw_buttons_rescale(this, em, { wxID_CANCEL, m_save_btn_id, m_move_btn_id, m_continue_btn_id }); - const wxSize& size = wxSize(80 * em, 60 * em); + const wxSize& size = wxSize(80 * em, 30 * em); SetMinSize(size); Fit(); @@ -260,7 +653,7 @@ void UnsavedChangesDialog::on_dpi_changed(const wxRect& suggested_rect) void UnsavedChangesDialog::on_sys_color_changed() { // msw_rescale updates just icons, so use it -// changes_tree_model->msw_rescale(); +// m_tree_model->msw_rescale(); Refresh(); } diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index a3ee7d984..c4a02d7bc 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -31,11 +31,17 @@ WX_DEFINE_ARRAY_PTR(ModelNode*, ModelNodePtrArray); class ModelNode { + wxWindow* m_parent_win{ nullptr }; + ModelNode* m_parent; ModelNodePtrArray m_children; wxBitmap m_empty_bmp; Preset::Type m_preset_type {Preset::TYPE_INVALID}; + // saved values for colors if they exist + 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. @@ -47,23 +53,29 @@ class ModelNode // would be added to the control) bool m_container {true}; + wxBitmap get_bitmap(const wxString& color); + public: bool m_toggle {true}; - wxBitmap m_type_icon; - wxBitmap m_group_icon; + wxBitmap m_icon; + wxBitmap m_old_color_bmp; + wxBitmap m_new_color_bmp; wxString m_text; wxString m_old_value; wxString m_new_value; // preset(root) node - ModelNode(const wxString& text, Preset::Type preset_type); + ModelNode(Preset::Type preset_type, const wxString& text); - // group node + // category node ModelNode(ModelNode* parent, const wxString& text, const std::string& icon_name); // group node - ModelNode(ModelNode* parent, const wxString& text, bool is_option); + ModelNode(ModelNode* parent, const wxString& text); + + // option node + ModelNode(ModelNode* parent, const wxString& text, const wxString& old_value, const wxString& new_value); ~ModelNode() { // free all our children nodes @@ -75,15 +87,21 @@ public: } bool IsContainer() const { return m_container; } - bool IsToggle() const { return m_toggle; } + bool IsToggled() const { return m_toggle; } + void Toggle(bool toggle = true) { m_toggle = toggle; } + bool IsRoot() const { return m_parent == nullptr; } + Preset::Type type() const { return m_preset_type; } + const wxString& text() const { return m_text; } ModelNode* GetParent() { return m_parent; } ModelNodePtrArray& GetChildren() { return m_children; } ModelNode* GetNthChild(unsigned int n) { return m_children.Item(n); } unsigned int GetChildCount() const { return m_children.GetCount(); } - void Insert(ModelNode* child, unsigned int n) { m_children.Insert(child, n); } - void Append(ModelNode* child) { m_children.Add(child); } + void Insert(ModelNode* child, unsigned int n) { m_children.Insert(child, n); } + void Append(ModelNode* child) { m_children.Add(child); } + + void UpdateEnabling(); }; @@ -93,15 +111,31 @@ public: class UnsavedChangesModel : public wxDataViewModel { - ModelNode* m_root; - ScalableBitmap m_icon[5]; + wxWindow* m_parent_win {nullptr}; + std::vector m_preset_nodes; + + wxDataViewCtrl* m_ctrl{ nullptr }; + + ModelNode *AddOption(ModelNode *group_node, + wxString option_name, + wxString old_value, + wxString new_value); + ModelNode *AddOptionWithGroup(ModelNode *category_node, + wxString group_name, + wxString option_name, + wxString old_value, + wxString new_value); + ModelNode *AddOptionWithGroupAndCategory(ModelNode *preset_node, + wxString category_name, + wxString group_name, + wxString option_name, + wxString old_value, + wxString new_value); public: enum { colToggle, - colTypeIcon, - colGroupIcon, - colMarkedText, + colIconText, colOldValue, colNewValue, colMax @@ -110,18 +144,28 @@ public: UnsavedChangesModel(wxWindow* parent); ~UnsavedChangesModel(); - virtual unsigned int GetColumnCount() const override { return colMax; } - virtual wxString GetColumnType(unsigned int col) const override; + void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } - virtual wxDataViewItem GetParent(const wxDataViewItem& item) const override; - virtual unsigned int GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const override; + wxDataViewItem AddPreset(Preset::Type type, wxString preset_name); + wxDataViewItem AddOption(Preset::Type type, wxString category_name, wxString group_name, wxString option_name, + wxString old_value, wxString new_value); - virtual void GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const override; - virtual bool SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) override; + void UpdateItemEnabling(wxDataViewItem item); - virtual bool IsEnabled(const wxDataViewItem& item, unsigned int col) const override; - virtual bool IsContainer(const wxDataViewItem& item) const override; + unsigned int GetColumnCount() const override { return colMax; } + wxString GetColumnType(unsigned int col) const override; + wxDataViewItem GetParent(const wxDataViewItem& item) const override; + unsigned int GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const override; + + void GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const override; + bool SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) override; + + bool IsEnabled(const wxDataViewItem& item, unsigned int col) const override; + bool IsContainer(const wxDataViewItem& item) const override; + // Is the container just a header or an item with all columns + // In our case it is an item with all columns + bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } }; @@ -130,14 +174,30 @@ public: //------------------------------------------ class UnsavedChangesDialog : public DPIDialog { - wxDataViewCtrl* changes_tree{ nullptr }; - UnsavedChangesModel* changes_tree_model{ nullptr }; + wxDataViewCtrl* m_tree { nullptr }; + UnsavedChangesModel* m_tree_model { nullptr }; + + int m_save_btn_id { wxID_ANY }; + int m_move_btn_id { wxID_ANY }; + int m_continue_btn_id { wxID_ANY }; + + enum class Action { + Save, + Move, + Continue + } m_action; public: UnsavedChangesDialog(Preset::Type type); ~UnsavedChangesDialog() {} - void ProcessSelection(wxDataViewItem selection); + void update(Preset::Type type); + void item_value_changed(wxDataViewEvent &event); + void close(Action action); + + bool save_preset() const { return m_action == Action::Save; } + bool move_preset() const { return m_action == Action::Move; } + bool just_continue() const { return m_action == Action::Continue; } protected: void on_dpi_changed(const wxRect& suggested_rect) override;