Code refactoring for better maintainability

This commit is contained in:
Lam Wei Lun
2026-09-11 17:15:57 +08:00
parent c57b74731e
commit 966e029b95
36 changed files with 938 additions and 517 deletions
+2
View File
@@ -478,6 +478,8 @@ set(SLIC3R_GUI_SOURCES
GUI/SkipPartCanvas.hpp
GUI/Search.cpp
GUI/Search.hpp
GUI/SettingsIndex.cpp
GUI/SettingsIndex.hpp
GUI/Selection.cpp
GUI/Selection.hpp
GUI/SelectMachine.cpp
+4 -4
View File
@@ -8,7 +8,7 @@
#include "Notebook.hpp"
#include "OptionsGroup.hpp"
#include "Plater.hpp"
#include "Search.hpp"
#include "SettingsIndex.hpp"
#include "Tab.hpp"
#include "slic3r/plugin/PluginManager.hpp"
@@ -526,11 +526,11 @@ void ActionRegistry::materialize_setting_actions()
{
assert(wxThread::IsMain());
// Reuse the Sidebar's live searcher: it's the only OptionsSearcher whose groups_and_categories
// map is populated (Tab::add_key feeds it at build time), and it already mirrors the current
// Reuse the Sidebar's live settings index: it's the only catalog whose group/category map is
// populated (Tab::add_key feeds it at build time), and it already mirrors the current
// configs/printer-technology. Use the all-modes view so the Speed Dial lists every setting,
// including those above the user's current mode, and can prompt to switch before jumping.
const std::vector<Search::Option>& options = wxGetApp().sidebar().get_searcher().all_modes_options();
const std::vector<Search::Option>& options = wxGetApp().sidebar().settings_index().all_options();
// Load the persisted per-action state ONCE (not per-option) so a re-materialised setting keeps
// its recency/favourite; mirroring seed_state but amortised over the whole option set.
+2 -2
View File
@@ -248,7 +248,7 @@ void OptionsGroup::append_line(const Line& line)
// affordance for it. Settings tabs only; the searcher already exists by the time tabs are built.
if (m_use_custom_ctrl && !line.label_path.empty())
for (const auto& opt : line.get_options())
wxGetApp().sidebar().get_searcher().set_path(opt.opt_id, static_cast<Preset::Type>(config_type()), line.label_path);
wxGetApp().sidebar().settings_index().set_path(opt.opt_id, static_cast<Preset::Type>(config_type()), line.label_path);
if (line.full_width && (line.widget != nullptr || !line.get_extra_widgets().empty()))
return;
@@ -656,7 +656,7 @@ Option ConfigOptionsGroup::get_option(const std::string& opt_key, int opt_index
m_opt_map.emplace(opt_id, pair);
if (m_use_custom_ctrl) // fill group and category values just for options from Settings Tab
wxGetApp().sidebar().get_searcher().add_key(opt_id, static_cast<Preset::Type>(this->config_type()), title, this->config_category(), this->icon);
wxGetApp().sidebar().settings_index().add_key(opt_id, static_cast<Preset::Type>(this->config_type()), title, this->config_category(), this->icon);
return Option(*m_config->def()->get(opt_key), opt_id);
}
+5
View File
@@ -6419,6 +6419,11 @@ Search::OptionsSearcher& Sidebar::get_searcher()
return p->searcher;
}
Search::SettingsIndex& Sidebar::settings_index()
{
return p->searcher.index();
}
std::string& Sidebar::get_search_line()
{
return p->searcher.search_string();
+1
View File
@@ -282,6 +282,7 @@ public:
std::vector<std::string>& types,
std::vector<size_t>* config_indices = nullptr);
Search::OptionsSearcher& get_searcher();
Search::SettingsIndex& settings_index();
std::string& get_search_line();
void update_printer_thumbnail();
+6 -213
View File
@@ -62,107 +62,12 @@ static char marker_by_type(Preset::Type type, PrinterTechnology pt)
}
}
std::string Option::opt_key() const { return into_u8(key).substr(2); }
void FoundOption::get_marked_label_and_tooltip(const char **label_, const char **tooltip_) const
{
*label_ = marked_label.c_str();
*tooltip_ = tooltip.c_str();
}
template<class T>
// void change_opt_key(std::string& opt_key, DynamicPrintConfig* config)
void change_opt_key(std::string &opt_key, DynamicPrintConfig *config, int &cnt)
{
T *opt_cur = static_cast<T *>(config->option(opt_key));
cnt = opt_cur->values.size();
return;
if (opt_cur->values.size() > 0) opt_key += "#" + std::to_string(0);
}
static std::string get_key(const std::string &opt_key, Preset::Type type) { return std::to_string(int(type)) + ";" + opt_key; }
void OptionsSearcher::append_options(DynamicPrintConfig *config, Preset::Type type, ConfigOptionMode mode)
{
auto emplace = [this, type](std::vector<Option> &dst, const std::string &key, const wxString &label, ConfigOptionMode opt_mode, std::string tooltip) {
const GroupAndCategory &gc = groups_and_categories[key];
if (gc.group.IsEmpty() || gc.category.IsEmpty()) return;
wxString suffix;
wxString suffix_local;
if (gc.category == "Machine limits") {
//suffix = key.back() == '1' ? L("Stealth") : L("Normal");
suffix = key.back() == '1' ? wxEmptyString : wxEmptyString;
suffix_local = " " + _(suffix);
suffix = " " + suffix;
}
if (!label.IsEmpty())
dst.emplace_back(Option{boost::nowide::widen(key), type, (label + suffix).ToStdWstring(), (_(label) + suffix_local).ToStdWstring(), gc.group.ToStdWstring(),
_(gc.group).ToStdWstring(), into_u8(gc.icon), gc.category.ToStdWstring(), GUI::Tab::translate_category(gc.category, type).ToStdWstring(),
false, opt_mode, std::move(tooltip), gc.path});
};
for (std::string opt_key : config->keys()) {
const ConfigOptionDef &opt = config->def()->options.at(opt_key);
const bool in_filtered = opt.mode <= mode;
int cnt = 0;
if ((type == Preset::TYPE_SLA_MATERIAL || type == Preset::TYPE_PRINTER || type == Preset::TYPE_PRINT) && opt_key != "printable_area")
switch (config->option(opt_key)->type()) {
case coInts: change_opt_key<ConfigOptionInts>(opt_key, config, cnt); break;
case coBools: change_opt_key<ConfigOptionBools>(opt_key, config, cnt); break;
case coFloats: change_opt_key<ConfigOptionFloats>(opt_key, config, cnt); break;
case coStrings: change_opt_key<ConfigOptionStrings>(opt_key, config, cnt); break;
case coPercents: change_opt_key<ConfigOptionPercents>(opt_key, config, cnt); break;
case coPoints: change_opt_key<ConfigOptionPoints>(opt_key, config, cnt); break;
// BBS
case coEnums: change_opt_key<ConfigOptionInts>(opt_key, config, cnt); break;
default: break;
}
if (type == Preset::TYPE_FILAMENT && filament_options_with_variant.find(opt_key) != filament_options_with_variant.end())
opt_key += "#0";
wxString label = opt.full_label.empty() ? opt.label : opt.full_label;
std::string key = get_key(opt_key, type);
auto add = [&](const std::string &k) {
if (in_filtered)
emplace(options, k, label, opt.mode, into_u8(_(opt.tooltip)));
emplace(options_all_modes, k, label, opt.mode, into_u8(_(opt.tooltip)));
};
if (cnt == 0)
add(key);
else
for (int i = 0; i < cnt; ++i)
// ! It's very important to use "#". opt_key#n is a real option key used in GroupAndCategory
add(key + "#" + std::to_string(i));
}
}
void OptionsSearcher::sort_options()
{
// Both views are label-sorted and multi_category-marked. They are separate consumers (the sidebar
// search and the Speed Dial); keeping them in sync here prevents the all-modes view from silently
// diverging in order or flags.
auto sort_and_mark = [](std::vector<Option> &v) {
std::sort(v.begin(), v.end(), [](const Option &o1, const Option &o2) { return o1.label < o2.label; });
Option *last = nullptr;
for (auto &opt : v) {
if (last && last->label == opt.label && last->group == opt.group && last->type == opt.type && last->category != opt.category) {
last->multi_category = true;
opt.multi_category = true;
}
last = &opt;
}
};
sort_and_mark(options);
sort_and_mark(options_all_modes);
}
// Mark a string using ColorMarkerStart and ColorMarkerEnd symbols
static std::wstring mark_string(const std::wstring &str, const std::vector<uint16_t> &matches, Preset::Type type, PrinterTechnology pt)
{
@@ -247,7 +152,8 @@ bool OptionsSearcher::search(const std::string &search, bool force /* = false*/,
return wxString(marker_by_type(opt.type, printer_technology)) + opt.category_local + sep + opt.group_local + sep + opt.label_local;
};
std::vector<uint16_t> matches, matches2;
std::vector<uint16_t> matches, matches2;
const std::vector<Option> &options = m_index.options();
for (size_t i = 0; i < options.size(); i++) {
const Option &opt = options[i];
if (full_list) {
@@ -319,117 +225,21 @@ OptionsSearcher::~OptionsSearcher() {}
void OptionsSearcher::init(std::vector<InputInfo> input_values)
{
options.clear();
options_all_modes.clear();
for (auto i : input_values) append_options(i.config, i.type, i.mode);
sort_options();
m_index.init(std::move(input_values));
search(search_line, true, search_type);
}
void OptionsSearcher::apply(DynamicPrintConfig *config, Preset::Type type, ConfigOptionMode mode)
{
// options_all_modes is a separate consumer (the Speed Dial), so "nothing initialised yet" means
// both views are empty - the mode-filtered options can be empty while the all-modes view is not.
if (options.empty() && options_all_modes.empty()) return;
options.erase(std::remove_if(options.begin(), options.end(), [type](Option opt) { return opt.type == type; }), options.end());
options_all_modes.erase(std::remove_if(options_all_modes.begin(), options_all_modes.end(), [type](Option opt) { return opt.type == type; }),
options_all_modes.end());
append_options(config, type, mode);
sort_options();
search(search_line, true, search_type);
if (m_index.apply(config, type, mode))
search(search_line, true, search_type);
}
const Option &OptionsSearcher::get_option(size_t pos_in_filter) const
{
assert(pos_in_filter != size_t(-1) && found[pos_in_filter].option_idx != size_t(-1));
return options[found[pos_in_filter].option_idx];
}
const Option &OptionsSearcher::get_option(const std::string &opt_key, Preset::Type type, int &variant_index) const
{
std::string opt_key2 = opt_key;
if (auto n = opt_key.find('#'); n != std::string::npos) {
variant_index = std::atoi(opt_key.c_str() + n + 1);
opt_key2 = opt_key.substr(0, n);
}
auto it = std::lower_bound(options.begin(), options.end(), Option({boost::nowide::widen(get_key(opt_key2, type))}));
// BBS: return the 0th option when not found in searcher caused by mode difference
// assert(it != options.end());
if (it == options.end()) { variant_index = -2 ; return options[0]; }
if (it->opt_key() == opt_key2) {
variant_index = -1;
} else {
const std::string opt_key3 = opt_key2 + "#";
it = std::lower_bound(it, options.end(), Option({boost::nowide::widen(get_key(opt_key3, type))}));
if (it == options.end() || it->opt_key().compare(0, opt_key3.length(), opt_key3) != 0) {
variant_index = -2; // Not found
return options[0];
}
auto it2 = it;
++it2;
if (it2 != options.end() && it2->opt_key().compare(0, opt_key3.length(), opt_key3) == 0
&& printer_options_with_variant_1.find(opt_key2) == printer_options_with_variant_1.end())
variant_index = -2;
}
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 = opt_key.back() == '1' ? wxEmptyString : wxEmptyString;
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(get_key(opt_key, type)),
type,
(label + suffix).ToStdWstring(),
(_(label) + suffix_local).ToStdWstring(),
gc.group.ToStdWstring(),
_(gc.group).ToStdWstring(),
into_u8(gc.icon),
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
{
std::string key = get_key(opt_key, type);
auto it = std::lower_bound(options.begin(), options.end(), Option({boost::nowide::widen(key)}));
// BBS: return the 0th option when not found in searcher caused by mode difference
if (it == options.end()) return options[0];
if (it->key == boost::nowide::widen(key)) return options[it - options.begin()];
if (groups_and_categories.find(key) == groups_and_categories.end()) {
size_t pos = key.find('#');
if (pos == std::string::npos) return options[it - options.begin()];
std::string zero_opt_key = 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(key);
if (gc.group.IsEmpty() || gc.category.IsEmpty()) return options[it - options.begin()];
return create_option(opt_key, label, type, gc);
return m_index.option_at(found[pos_in_filter].option_idx);
}
void OptionsSearcher::show_dialog(Preset::Type type, wxWindow *parent, TextInput *input, wxWindow* ssearch_btn)
@@ -456,23 +266,6 @@ void OptionsSearcher::dlg_msw_rescale()
{
if (search_dialog) search_dialog->msw_rescale();
}
void OptionsSearcher::add_key(const std::string &opt_key, Preset::Type type, const wxString &group, const wxString &category, const wxString &icon)
{
// Update fields in place so a page rebuild (get_option after set_path) doesn't drop the
// previously recorded wiki path.
GroupAndCategory &gc = groups_and_categories[get_key(opt_key, type)];
gc.group = group;
gc.category = category;
gc.icon = icon;
}
void OptionsSearcher::set_path(const std::string &opt_key, Preset::Type type, const std::string &path)
{
if (path.empty())
return;
groups_and_categories[get_key(opt_key, type)].path = path;
}
//------------------------------------------
// SearchItem
//------------------------------------------
+10 -72
View File
@@ -19,6 +19,7 @@
#include "wxExtensions.hpp"
#include "GUI_Utils.hpp"
#include "libslic3r/Preset.hpp"
#include "SettingsIndex.hpp"
#include "Widgets/ScrolledWindow.hpp"
#include "Widgets/TextInput.hpp"
#include "Widgets/PopupWindow.hpp"
@@ -34,45 +35,6 @@ namespace Search {
class SearchDialog;
struct InputInfo
{
DynamicPrintConfig *config{nullptr};
Preset::Type type{Preset::TYPE_INVALID};
ConfigOptionMode mode{comSimple};
};
struct GroupAndCategory
{
wxString group;
wxString category;
wxString icon; // icon of the group's own header, or empty
std::string path; // wiki path (Line::label_path) of the option's line, or empty
};
struct Option
{
// bool operator<(const Option& other) const { return other.label > this->label; }
bool operator<(const Option &other) const { return other.key > this->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.
std::wstring key;
Preset::Type type{Preset::TYPE_INVALID};
std::wstring label;
std::wstring label_local;
std::wstring group;
std::wstring group_local;
std::string group_icon; // SVG base name of the group's own header icon, or empty
std::wstring category;
std::wstring category_local;
bool multi_category { false };
ConfigOptionMode mode{comSimple}; // option's visibility threshold; drives the Speed Dial's mode prompt
std::string tooltip; // localized ConfigOptionDef::tooltip, or empty
std::string wiki_path; // Line::label_path for the option's row, or empty
std::string opt_key() const;
};
struct FoundOption
{
// UTF8 encoding, to be consumed by ImGUI by reference.
@@ -96,28 +58,19 @@ struct OptionViewParameters
class OptionsSearcher
{
std::string search_line;
Preset::Type search_type = Preset::TYPE_INVALID;
SettingsIndex m_index;
std::map<std::string, GroupAndCategory> groups_and_categories;
PrinterTechnology printer_technology;
std::vector<Option> options{};
// Every option regardless of the current UI mode (Simple/Advanced/Expert/Developer), for the
// Speed Dial. The sidebar search keeps using the mode-filtered `options`.
std::vector<Option> options_all_modes{};
std::string search_line;
Preset::Type search_type = Preset::TYPE_INVALID;
PrinterTechnology printer_technology;
std::vector<FoundOption> found{};
void append_options(DynamicPrintConfig *config, Preset::Type type, ConfigOptionMode mode);
void sort_options();
void sort_found()
{
std::sort(found.begin(), found.end(),
[](const FoundOption &f1, const FoundOption &f2) { return f1.outScore > f2.outScore || (f1.outScore == f2.outScore && f1.label < f2.label); });
};
size_t options_size() const { return options.size(); }
size_t found_size() const { return found.size(); }
public:
@@ -128,44 +81,29 @@ public:
OptionsSearcher();
~OptionsSearcher();
SettingsIndex & index() { return m_index; }
const SettingsIndex &index() const { return m_index; }
// Rebuild the catalog and re-run the current query so the cached results track it.
void init(std::vector<InputInfo> input_values);
void apply(DynamicPrintConfig *config, Preset::Type type, ConfigOptionMode mode);
bool search();
bool search(const std::string &search, bool force = false, Preset::Type type = Preset::TYPE_INVALID);
void add_key(const std::string &opt_key, Preset::Type type, const wxString &group, const wxString &category,
const wxString &icon = wxEmptyString);
// Records the wiki path of an option's row (Line::label_path) so the Speed Dial can offer a
// "open wiki" affordance. Empty paths are ignored.
void set_path(const std::string &opt_key, Preset::Type type, const std::string &path);
size_t size() const { return found_size(); }
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, Preset::Type type, int &variant_index) const;
Option get_option(const std::string &opt_key, const wxString &label, Preset::Type type) const;
const std::vector<FoundOption> &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_key()
{
std::sort(options.begin(), options.end(), [](const Option &o1, const Option &o2) { return o1.key < o2.key; });
}
void sort_options_by_label() { sort_options(); }
void show_dialog(Preset::Type type, wxWindow *parent, TextInput *input, wxWindow *ssearch_btn);
void dlg_sys_color_changed();
void dlg_msw_rescale();
// Every option across all UI modes (Developer included), regardless of the current mode.
// Used by the Speed Dial so it can list settings the user would have to switch mode to edit.
const std::vector<Option>& all_modes_options() const { return options_all_modes; }
};
//------------------------------------------
+229
View File
@@ -0,0 +1,229 @@
#include "SettingsIndex.hpp"
#include <algorithm>
#include <cstddef>
#include <cstdlib>
#include <string>
#include <vector>
#include <boost/nowide/convert.hpp>
#include "GUI.hpp"
#include "I18N.hpp"
#include "Tab.hpp"
#include "libslic3r/PrintConfig.hpp"
namespace Slic3r {
using GUI::into_u8;
namespace Search {
static std::string get_key(const std::string &opt_key, Preset::Type type) { return std::to_string(int(type)) + ";" + opt_key; }
std::string Option::opt_key() const { return into_u8(key).substr(2); }
template<class T>
// void change_opt_key(std::string& opt_key, DynamicPrintConfig* config)
void change_opt_key(std::string &opt_key, DynamicPrintConfig *config, int &cnt)
{
T *opt_cur = static_cast<T *>(config->option(opt_key));
cnt = opt_cur->values.size();
return;
if (opt_cur->values.size() > 0) opt_key += "#" + std::to_string(0);
}
// Single assembler for an indexed Option, shared by append_options() and create_option(), so a new
// Option field is only wired up in one place.
static Option make_option(const std::string &key, Preset::Type type, const wxString &label, const GroupAndCategory &gc,
ConfigOptionMode mode, const std::string &tooltip, bool rewrite_extruder_category)
{
wxString suffix;
wxString suffix_local;
if (gc.category == "Machine limits") {
//suffix = key.back() == '1' ? L("Stealth") : L("Normal");
suffix = key.back() == '1' ? wxEmptyString : wxEmptyString;
suffix_local = " " + _(suffix);
suffix = " " + suffix;
}
wxString category = gc.category;
if (rewrite_extruder_category && type == Preset::TYPE_PRINTER && category.Contains("Extruder ")) {
std::string opt_idx = key.substr(key.find("#") + 1);
category = wxString::Format("%s %d", "Extruder", atoi(opt_idx.c_str()) + 1);
}
return Option{boost::nowide::widen(key), type, (label + suffix).ToStdWstring(), (_(label) + suffix_local).ToStdWstring(),
gc.group.ToStdWstring(), _(gc.group).ToStdWstring(), into_u8(gc.icon), gc.category.ToStdWstring(),
GUI::Tab::translate_category(category, type).ToStdWstring(), false, mode, tooltip, gc.path};
}
void SettingsIndex::append_options(DynamicPrintConfig *config, Preset::Type type, ConfigOptionMode mode)
{
for (std::string opt_key : config->keys()) {
const ConfigOptionDef &opt = config->def()->options.at(opt_key);
const bool in_filtered = opt.mode <= mode;
int cnt = 0;
if ((type == Preset::TYPE_SLA_MATERIAL || type == Preset::TYPE_PRINTER || type == Preset::TYPE_PRINT) && opt_key != "printable_area")
switch (config->option(opt_key)->type()) {
case coInts: change_opt_key<ConfigOptionInts>(opt_key, config, cnt); break;
case coBools: change_opt_key<ConfigOptionBools>(opt_key, config, cnt); break;
case coFloats: change_opt_key<ConfigOptionFloats>(opt_key, config, cnt); break;
case coStrings: change_opt_key<ConfigOptionStrings>(opt_key, config, cnt); break;
case coPercents: change_opt_key<ConfigOptionPercents>(opt_key, config, cnt); break;
case coPoints: change_opt_key<ConfigOptionPoints>(opt_key, config, cnt); break;
// BBS
case coEnums: change_opt_key<ConfigOptionInts>(opt_key, config, cnt); break;
default: break;
}
if (type == Preset::TYPE_FILAMENT && filament_options_with_variant.find(opt_key) != filament_options_with_variant.end())
opt_key += "#0";
wxString label = opt.full_label.empty() ? opt.label : opt.full_label;
std::string key = get_key(opt_key, type);
auto add = [&](const std::string &k) {
const GroupAndCategory &gc = m_groups_and_categories[k];
if (gc.group.IsEmpty() || gc.category.IsEmpty() || label.IsEmpty()) return;
const std::string tooltip = into_u8(_(opt.tooltip));
if (in_filtered)
m_options.emplace_back(make_option(k, type, label, gc, opt.mode, tooltip, false));
m_all_modes.emplace_back(make_option(k, type, label, gc, opt.mode, tooltip, false));
};
if (cnt == 0)
add(key);
else
for (int i = 0; i < cnt; ++i)
// ! It's very important to use "#". opt_key#n is a real option key used in GroupAndCategory
add(key + "#" + std::to_string(i));
}
}
void SettingsIndex::sort_options()
{
// Both views are label-sorted and multi_category-marked. They are separate consumers (the sidebar
// search and the Speed Dial); keeping them in sync here prevents the all-modes view from silently
// diverging in order or flags.
auto sort_and_mark = [](std::vector<Option> &v) {
std::sort(v.begin(), v.end(), [](const Option &o1, const Option &o2) { return o1.label < o2.label; });
Option *last = nullptr;
for (auto &opt : v) {
if (last && last->label == opt.label && last->group == opt.group && last->type == opt.type && last->category != opt.category) {
last->multi_category = true;
opt.multi_category = true;
}
last = &opt;
}
};
sort_and_mark(m_options);
sort_and_mark(m_all_modes);
}
void SettingsIndex::init(std::vector<InputInfo> input_values)
{
m_options.clear();
m_all_modes.clear();
for (auto i : input_values) append_options(i.config, i.type, i.mode);
sort_options();
}
bool SettingsIndex::apply(DynamicPrintConfig *config, Preset::Type type, ConfigOptionMode mode)
{
// m_all_modes is a separate consumer (the Speed Dial), so "nothing initialised yet" means both
// views are empty - the mode-filtered m_options can be empty while m_all_modes is not.
if (m_options.empty() && m_all_modes.empty()) return false;
m_options.erase(std::remove_if(m_options.begin(), m_options.end(), [type](Option opt) { return opt.type == type; }), m_options.end());
m_all_modes.erase(std::remove_if(m_all_modes.begin(), m_all_modes.end(), [type](Option opt) { return opt.type == type; }), m_all_modes.end());
append_options(config, type, mode);
sort_options();
return true;
}
const Option &SettingsIndex::get_option(const std::string &opt_key, Preset::Type type, int &variant_index) const
{
std::string opt_key2 = opt_key;
if (auto n = opt_key.find('#'); n != std::string::npos) {
variant_index = std::atoi(opt_key.c_str() + n + 1);
opt_key2 = opt_key.substr(0, n);
}
auto it = std::lower_bound(m_options.begin(), m_options.end(), Option({boost::nowide::widen(get_key(opt_key2, type))}));
// BBS: return the 0th option when not found in searcher caused by mode difference
// assert(it != options.end());
if (it == m_options.end()) { variant_index = -2 ; return m_options[0]; }
if (it->opt_key() == opt_key2) {
variant_index = -1;
} else {
const std::string opt_key3 = opt_key2 + "#";
it = std::lower_bound(it, m_options.end(), Option({boost::nowide::widen(get_key(opt_key3, type))}));
if (it == m_options.end() || it->opt_key().compare(0, opt_key3.length(), opt_key3) != 0) {
variant_index = -2; // Not found
return m_options[0];
}
auto it2 = it;
++it2;
if (it2 != m_options.end() && it2->opt_key().compare(0, opt_key3.length(), opt_key3) == 0
&& printer_options_with_variant_1.find(opt_key2) == printer_options_with_variant_1.end())
variant_index = -2;
}
return m_options[it - m_options.begin()];
}
static Option create_option(const std::string &opt_key, const wxString &label, Preset::Type type, const GroupAndCategory &gc)
{
return make_option(get_key(opt_key, type), type, label, gc, comSimple, std::string(), true);
}
Option SettingsIndex::get_option(const std::string &opt_key, const wxString &label, Preset::Type type) const
{
std::string key = get_key(opt_key, type);
auto it = std::lower_bound(m_options.begin(), m_options.end(), Option({boost::nowide::widen(key)}));
// BBS: return the 0th option when not found in searcher caused by mode difference
if (it == m_options.end()) return m_options[0];
if (it->key == boost::nowide::widen(key)) return m_options[it - m_options.begin()];
if (m_groups_and_categories.find(key) == m_groups_and_categories.end()) {
size_t pos = key.find('#');
if (pos == std::string::npos) return m_options[it - m_options.begin()];
std::string zero_opt_key = key.substr(0, pos + 1) + "0";
if (m_groups_and_categories.find(zero_opt_key) == m_groups_and_categories.end()) return m_options[it - m_options.begin()];
return create_option(opt_key, label, type, m_groups_and_categories.at(zero_opt_key));
}
const GroupAndCategory &gc = m_groups_and_categories.at(key);
if (gc.group.IsEmpty() || gc.category.IsEmpty()) return m_options[it - m_options.begin()];
return create_option(opt_key, label, type, gc);
}
void SettingsIndex::add_key(const std::string &opt_key, Preset::Type type, const wxString &group, const wxString &category, const wxString &icon)
{
// Update fields in place so a page rebuild (get_option after set_path) doesn't drop the
// previously recorded wiki path.
GroupAndCategory &gc = m_groups_and_categories[get_key(opt_key, type)];
gc.group = group;
gc.category = category;
gc.icon = icon;
}
void SettingsIndex::set_path(const std::string &opt_key, Preset::Type type, const std::string &path)
{
if (path.empty())
return;
m_groups_and_categories[get_key(opt_key, type)].path = path;
}
} // namespace Search
} // namespace Slic3r
+98
View File
@@ -0,0 +1,98 @@
#ifndef slic3r_SettingsIndex_hpp_
#define slic3r_SettingsIndex_hpp_
#include <algorithm>
#include <cstddef>
#include <map>
#include <string>
#include <vector>
#include <wx/string.h>
#include <libslic3r/Config.hpp>
#include <libslic3r/Preset.hpp>
namespace Slic3r {
namespace Search {
struct InputInfo
{
DynamicPrintConfig *config{nullptr};
Preset::Type type{Preset::TYPE_INVALID};
ConfigOptionMode mode{comSimple};
};
struct GroupAndCategory
{
wxString group;
wxString category;
wxString icon; // icon of the group's own header, or empty
std::string path; // wiki path (Line::label_path) of the option's line, or empty
};
struct Option
{
// bool operator<(const Option& other) const { return other.label > this->label; }
bool operator<(const Option &other) const { return other.key > this->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.
std::wstring key;
Preset::Type type{Preset::TYPE_INVALID};
std::wstring label;
std::wstring label_local;
std::wstring group;
std::wstring group_local;
std::string group_icon; // SVG base name of the group's own header icon, or empty
std::wstring category;
std::wstring category_local;
bool multi_category { false };
ConfigOptionMode mode{comSimple}; // option's visibility threshold; drives the Speed Dial's mode prompt
std::string tooltip; // localized ConfigOptionDef::tooltip, or empty
std::string wiki_path; // Line::label_path for the option's row, or empty
std::string opt_key() const;
};
// Catalog of settings and their metadata. Owns the group/category registry populated by the
// settings pages, plus two views of the options: the mode-filtered view the sidebar search
// queries, and every option regardless of mode for the Speed Dial.
class SettingsIndex
{
std::map<std::string, GroupAndCategory> m_groups_and_categories;
std::vector<Option> m_options; // mode-filtered view used by the sidebar search
std::vector<Option> m_all_modes; // every option regardless of mode, for the Speed Dial
void append_options(DynamicPrintConfig *config, Preset::Type type, ConfigOptionMode mode);
void sort_options();
public:
void init(std::vector<InputInfo> input_values);
// Rebuild the given type's options; returns false when the index was never initialised.
bool apply(DynamicPrintConfig *config, Preset::Type type, ConfigOptionMode mode);
void add_key(const std::string &opt_key, Preset::Type type, const wxString &group, const wxString &category,
const wxString &icon = wxEmptyString);
void set_path(const std::string &opt_key, Preset::Type type, const std::string &path);
const std::vector<Option> &options() const { return m_options; }
const std::vector<Option> &all_options() const { return m_all_modes; }
const Option & option_at(size_t pos) const { return m_options[pos]; }
const Option &get_option(const std::string &opt_key, Preset::Type type, int &variant_index) const;
Option get_option(const std::string &opt_key, const wxString &label, Preset::Type type) const;
const GroupAndCategory &get_group_and_category(const std::string &opt_key) { return m_groups_and_categories[opt_key]; }
void sort_options_by_key()
{
std::sort(m_options.begin(), m_options.end(), [](const Option &o1, const Option &o2) { return o1.key < o2.key; });
}
void sort_options_by_label() { sort_options(); }
};
} // namespace Search
} // namespace Slic3r
#endif // slic3r_SettingsIndex_hpp_
+8 -8
View File
@@ -5049,8 +5049,8 @@ void TabPrinter::build_fff()
}
// Register by hand so the UnsavedChanges dialog can render a row for it.
wxGetApp().sidebar().get_searcher().add_key("printer_agent", m_type, optgroup->title,
optgroup->config_category(), optgroup->icon);
wxGetApp().sidebar().settings_index().add_key("printer_agent", m_type, optgroup->title,
optgroup->config_category(), optgroup->icon);
}
}
@@ -5761,11 +5761,11 @@ if (is_marlin_flavor)
} else if (m_extruders_count_old == 1) {
first_extruder_title = wxString::Format("Extruder %d", 1);
}
auto & searcher = wxGetApp().sidebar().get_searcher();
auto & index = wxGetApp().sidebar().settings_index();
for (auto &group : m_pages[n_before_extruders]->m_optgroups) {
group->set_config_category_and_type(first_extruder_title, m_type);
for (auto &opt : group->opt_map())
searcher.add_key(opt.first + "#0", m_type, group->title, first_extruder_title, group->icon);
index.add_key(opt.first + "#0", m_type, group->title, first_extruder_title, group->icon);
}
Thaw();
@@ -7867,10 +7867,10 @@ wxSizer* TabPrinter::create_bed_shape_widget(wxWindow* parent)
}));
{
Search::OptionsSearcher& searcher = wxGetApp().sidebar().get_searcher();
const Search::GroupAndCategory& gc = searcher.get_group_and_category("printable_area");
searcher.add_key("bed_custom_texture", m_type, gc.group, gc.category, gc.icon);
searcher.add_key("bed_custom_model", m_type, gc.group, gc.category, gc.icon);
Search::SettingsIndex& index = wxGetApp().sidebar().settings_index();
const Search::GroupAndCategory& gc = index.get_group_and_category("printable_area");
index.add_key("bed_custom_texture", m_type, gc.group, gc.category, gc.icon);
index.add_key("bed_custom_model", m_type, gc.group, gc.category, gc.icon);
}
return sizer;
+16 -16
View File
@@ -1485,12 +1485,12 @@ std::string UnsavedChangesDialog::subreplace(std::string resource_str, std::stri
void UnsavedChangesDialog::update_tree(Preset::Type type, DynamicConfig * config, int from, int to)
{
Search::OptionsSearcher &searcher = wxGetApp().sidebar().get_searcher();
searcher.sort_options_by_key();
Search::SettingsIndex &index = wxGetApp().sidebar().settings_index();
index.sort_options_by_key();
for (const std::string &opt_key : config->keys()) {
int variant_index = -2;
const Search::Option &option = searcher.get_option(opt_key, type, variant_index);
const Search::Option &option = index.get_option(opt_key, type, variant_index);
auto category = option.category_local;
auto opt = dynamic_cast<ConfigOptionVectorBase*>(config->option(opt_key));
std::string value_from = opt->vserialize()[from];
@@ -1502,8 +1502,8 @@ void UnsavedChangesDialog::update_tree(Preset::Type type, DynamicConfig * config
void UnsavedChangesDialog::update_tree(Preset::Type type, PresetCollection* presets_)
{
Search::OptionsSearcher& searcher = wxGetApp().sidebar().get_searcher();
searcher.sort_options_by_key();
Search::SettingsIndex& index = wxGetApp().sidebar().settings_index();
index.sort_options_by_key();
// list of the presets with unsaved changes
std::vector<PresetCollection*> presets_list;
@@ -1558,11 +1558,11 @@ void UnsavedChangesDialog::update_tree(Preset::Type type, PresetCollection* pres
for (const std::string& opt_key : dirty_options) {
int variant_index = -2;
const Search::Option &option = searcher.get_option(opt_key, type, variant_index);
const Search::Option &option = index.get_option(opt_key, type, variant_index);
if (option.opt_key() != opt_key && variant_index < -1) {
// 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
// because of they don't exist in the index
continue;
}
auto category = option.category_local;
@@ -1590,8 +1590,8 @@ void UnsavedChangesDialog::update_tree(Preset::Type type, PresetCollection* pres
}
}
// Revert sort of searcher back
searcher.sort_options_by_label();
// Revert sort of index back
index.sort_options_by_label();
}
void UnsavedChangesDialog::on_dpi_changed(const wxRect& suggested_rect)
@@ -2043,8 +2043,8 @@ void DiffPresetDialog::update_bottom_info(wxString bottom_info)
void DiffPresetDialog::update_tree()
{
Search::OptionsSearcher& searcher = wxGetApp().sidebar().get_searcher();
searcher.sort_options_by_key();
Search::SettingsIndex& index = wxGetApp().sidebar().settings_index();
index.sort_options_by_key();
m_tree->Clear();
wxString bottom_info = "";
@@ -2124,14 +2124,14 @@ void DiffPresetDialog::update_tree()
wxString right_val = get_string_value(opt_key, right_congig);
const std::string lookup_key = get_pure_opt_key(opt_key);
Search::Option option = searcher.get_option(lookup_key, get_full_label(lookup_key, left_config), type);
Search::Option option = index.get_option(lookup_key, get_full_label(lookup_key, left_config), type);
if (get_pure_opt_key(option.opt_key()) != lookup_key)
option = searcher.get_option(opt_key, get_full_label(opt_key, left_config), type);
option = index.get_option(opt_key, get_full_label(opt_key, left_config), type);
if (get_pure_opt_key(option.opt_key()) != lookup_key) {
// When the found option is not the requested one.
// This can happen for dirty_options such as:
// "default_print_profile", "printer_model", "printer_settings_id",
// because they do not exist in the searcher.
// because they do not exist in the index.
continue;
}
m_tree->Append(opt_key, type, option.category_local, option.group_local, option.label_local,
@@ -2155,8 +2155,8 @@ void DiffPresetDialog::update_tree()
Refresh();
}
// Revert sort of searcher back
searcher.sort_options_by_label();
// Revert sort of index back
index.sort_options_by_label();
}
void DiffPresetDialog::on_dpi_changed(const wxRect&)