mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 22:42:37 +00:00
Fixes issue with showing hidden settings in speed dial.
This commit is contained in:
@@ -114,6 +114,13 @@ function sourceNorm(a) {
|
||||
a._sn = NormText(a.source || "", false);
|
||||
return a._sn;
|
||||
}
|
||||
// Search-only alias for the descriptive name when the title differs (e.g. "Reverse on even" vs
|
||||
// "Overhang reversal"). Never rendered, so no highlight ranges.
|
||||
function fullNorm(a) {
|
||||
if (a._fn === undefined)
|
||||
a._fn = NormText(a.full_label || "", false);
|
||||
return a._fn;
|
||||
}
|
||||
|
||||
// Match one pre-normalized field vs one pre-normalized needle. Returns {score, ranges, contiguous}
|
||||
// when the needle is present, else null. wwRe is a compiled whole-word (\b-bounded) regex for the
|
||||
@@ -152,19 +159,19 @@ function tokenWordRe(token) {
|
||||
return new RegExp("\\b" + EscapeRegExp(token) + "\\b");
|
||||
}
|
||||
|
||||
// One token's best match across an action's three fields, keeping the per-field ranges so the caller
|
||||
// can highlight each matched word. Returns {score, title, group, source} (ranges or null per field), or
|
||||
// null when no field contains the token. Score mirrors scoreFields' tiers: contiguous > fuzzy, then
|
||||
// title > group > source.
|
||||
// One token's best match across an action's searchable fields, keeping per-field ranges for
|
||||
// highlighting. Returns {score, title, group, source}, or null if no field matches.
|
||||
function tokenMatch(a, token, wwRe) {
|
||||
var t = fieldMatchScore(titleNorm(a), token, wwRe);
|
||||
var g = fieldMatchScore(groupNorm(a), token, wwRe);
|
||||
var s = fieldMatchScore(sourceNorm(a), token, wwRe);
|
||||
if (!t && !g && !s) return null;
|
||||
var f = fieldMatchScore(fullNorm(a), token, wwRe);
|
||||
if (!t && !g && !s && !f) return null;
|
||||
var score = Math.max(
|
||||
t ? (t.contiguous ? SCORE_CONTIGUOUS : 0) + SCORE_TITLE + t.score : -Infinity,
|
||||
g ? (g.contiguous ? SCORE_CONTIGUOUS : 0) + SCORE_GROUP + g.score : -Infinity,
|
||||
s ? (s.contiguous ? SCORE_CONTIGUOUS : 0) + s.score : -Infinity
|
||||
s ? (s.contiguous ? SCORE_CONTIGUOUS : 0) + s.score : -Infinity,
|
||||
f ? (f.contiguous ? SCORE_CONTIGUOUS : 0) + f.score : -Infinity
|
||||
);
|
||||
return { score: score, title: t ? t.ranges : null, group: g ? g.ranges : null, source: s ? s.ranges : null };
|
||||
}
|
||||
@@ -189,11 +196,9 @@ function mergeRanges(ranges) {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Combine the per-field match scores into one comparable value, or null when no field matched.
|
||||
// Ranking tiers, strongest first:
|
||||
// tier (contiguous/perfect vs fuzzy) > field (title > group > source) > start/gaps.
|
||||
// The additive weights keep every contiguous match above every fuzzy one regardless of field.
|
||||
function scoreFields(t, g, s) {
|
||||
// Combine per-field scores into one value, or null when nothing matched.
|
||||
// Ranking: contiguous > fuzzy, then title > group > source/full alias, then start/gaps.
|
||||
function scoreFields(t, g, s, f) {
|
||||
var best = null;
|
||||
function consider(m, weight) {
|
||||
if (!m) return;
|
||||
@@ -203,6 +208,7 @@ function scoreFields(t, g, s) {
|
||||
consider(t, SCORE_TITLE);
|
||||
consider(g, SCORE_GROUP);
|
||||
consider(s, 0);
|
||||
consider(f, 0);
|
||||
return best;
|
||||
}
|
||||
|
||||
@@ -216,6 +222,7 @@ function scoreFields(t, g, s) {
|
||||
// - tokens: every whitespace-separated word must match SOME field, but different words may match
|
||||
// different fields. This is what lets "speed acceleration inner" find "Inner wall" whose path is
|
||||
// "Process : Speed : Acceleration" (title + source breadcrumb together).
|
||||
// full_label is searchable too but never highlighted, since it is not rendered.
|
||||
// A phrase match always outranks a distributed token match.
|
||||
function searchActions(actions, query) {
|
||||
var q = (query || "").trim();
|
||||
@@ -239,7 +246,8 @@ function searchActions(actions, query) {
|
||||
var t = fieldMatchScore(titleNorm(a), searchNeedle, wwRe);
|
||||
var g = fieldMatchScore(groupNorm(a), searchNeedle, wwRe);
|
||||
var s = fieldMatchScore(sourceNorm(a), searchNeedle, wwRe);
|
||||
var phrase = scoreFields(t, g, s);
|
||||
var f = fieldMatchScore(fullNorm(a), searchNeedle, wwRe);
|
||||
var phrase = scoreFields(t, g, s, f);
|
||||
var score, ranges;
|
||||
if (phrase !== null) {
|
||||
score = phrase + SCORE_PHRASE;
|
||||
@@ -312,7 +320,7 @@ function completionFor(query, list) {
|
||||
var top = (list || []).slice(0, 10);
|
||||
for (var i = 0; i < top.length; i++) {
|
||||
var a = top[i];
|
||||
var fields = [a.title, a.group, a.source];
|
||||
var fields = [a.title, a.group, a.source, a.full_label];
|
||||
for (var f = 0; f < fields.length; f++) {
|
||||
var words = completionWords(fields[f]);
|
||||
for (var w = 0; w < words.length; w++) {
|
||||
|
||||
@@ -117,6 +117,18 @@ const negativePool = [
|
||||
assert.equal(ctx.searchActions(negativePool, "ornt").length, 1,
|
||||
"a low-score fuzzy match is not mistaken for no match");
|
||||
|
||||
// A setting whose displayed title is the page row label keeps the descriptive ConfigOptionDef name as
|
||||
// a search-only alias, so the old wording still finds it without being shown.
|
||||
const aliasPool = [
|
||||
{ id: "rev", title: "Reverse on even", full_label: "Overhang reversal", source: "Process : Quality : Overhangs", group: "", input: "" }
|
||||
];
|
||||
assert.deepEqual(ctx.searchActions(aliasPool, "overhang reversal").map(function (a) { return a.id; }), ["rev"],
|
||||
"the descriptive full_label is searchable even though the title shows the row label");
|
||||
assert.equal(ctx.matchIndex.rev.title, null,
|
||||
"an alias-only match does not highlight the displayed title");
|
||||
assert.deepEqual(ctx.searchActions(aliasPool, "reversal").map(function (a) { return a.id; }), ["rev"],
|
||||
"a token that exists only in the full_label still matches");
|
||||
|
||||
// Multi-token cross-field search: each whitespace-separated word must match SOME searchable field,
|
||||
// but different words may match different fields. "inner" is the title while "speed" and
|
||||
// "acceleration" live in the source breadcrumb, so the query as a whole is never contiguous in one
|
||||
|
||||
@@ -561,10 +561,24 @@ void ActionRegistry::materialize_setting_actions()
|
||||
|
||||
std::unordered_set<std::string> seen;
|
||||
for (const Search::Option& opt : options) {
|
||||
// The row's live state drives both the hidden filter and the title (labels can change at
|
||||
// runtime, e.g. brim_width -> "Brim ear radius"). Hidden rows are skipped, not marked seen.
|
||||
Tab* tab = wxGetApp().get_tab(opt.type);
|
||||
Tab::SettingRowState row;
|
||||
if (tab)
|
||||
row = tab->setting_row_state(opt.opt_key());
|
||||
if (!row.visible)
|
||||
continue;
|
||||
|
||||
const std::string id = SettingAction::id_for(opt.opt_key(), opt.type);
|
||||
seen.insert(id);
|
||||
|
||||
const std::wstring label_w = opt.label_local.empty() ? opt.label : opt.label_local;
|
||||
// The page draws Line::label; the descriptive ConfigOptionDef name stays a search-only alias
|
||||
// ("overhang reversal" still finds "Reverse on even").
|
||||
const std::string search_label = boost::nowide::narrow(opt.label_local.empty() ? opt.label : opt.label_local);
|
||||
std::string title = into_u8(Search::resolve_setting_title(from_u8(opt.display_label), row.label, row.multi));
|
||||
if (title.empty())
|
||||
title = search_label;
|
||||
|
||||
// Eyebrow/source = the full settings path "Process : Quality : Layers" (localized). The JS
|
||||
// renders group || source and searches source + " " + group, so putting the whole path in
|
||||
@@ -575,22 +589,22 @@ void ActionRegistry::materialize_setting_actions()
|
||||
if (!opt.group_local.empty())
|
||||
path += L" : " + opt.group_local;
|
||||
|
||||
// title = the option leaf name (last label segment); group stays empty so the source path
|
||||
// (above) is the single display/search breadcrumb rather than being duplicated.
|
||||
auto action = std::make_unique<SettingAction>(opt.opt_key(), opt.type, boost::nowide::narrow(label_w), std::string(),
|
||||
opt.category, boost::nowide::narrow(path), opt.mode);
|
||||
// title = the label the settings row draws; group stays empty so the source path (above) is
|
||||
// the single display/search breadcrumb rather than being duplicated.
|
||||
auto action = std::make_unique<SettingAction>(opt.opt_key(), opt.type, title, std::string(), opt.category,
|
||||
boost::nowide::narrow(path), opt.mode);
|
||||
if (title != search_label)
|
||||
action->full_label = search_label;
|
||||
|
||||
// Tile pictogram = the icon of the setting's own group header (e.g. Advanced -> param_advanced),
|
||||
// the one shown next to it in the page. Fall back to the page/category icon for groups
|
||||
// without one. Keys are the English titles the GUI registers.
|
||||
action->icon = opt.group_icon;
|
||||
if (action->icon.empty() && !opt.category.empty()) {
|
||||
if (Tab* tab = wxGetApp().get_tab(opt.type); tab) {
|
||||
const auto& icons = tab->get_category_icon_map();
|
||||
auto it = icons.find(wxString(opt.category));
|
||||
if (it != icons.end())
|
||||
action->icon = it->second;
|
||||
}
|
||||
if (action->icon.empty() && !opt.category.empty() && tab) {
|
||||
const auto& icons = tab->get_category_icon_map();
|
||||
auto it = icons.find(wxString(opt.category));
|
||||
if (it != icons.end())
|
||||
action->icon = it->second;
|
||||
}
|
||||
|
||||
// Footer description + wiki affordance; only settings whose row declared a wiki path have one.
|
||||
@@ -745,6 +759,7 @@ nlohmann::json ActionRegistry::snapshot()
|
||||
auto action_to_json = [](const AppAction* a) {
|
||||
return nlohmann::json({{"id", a->id()},
|
||||
{"title", a->title()},
|
||||
{"full_label", a->full_label},
|
||||
{"source", a->source_name()},
|
||||
{"group", a->group},
|
||||
{"kind", a->kind == AppActionKind::Plugin ? "plugin" : "command"},
|
||||
|
||||
@@ -85,6 +85,9 @@ struct AppAction
|
||||
ConfigOptionMode required_mode = comSimple;
|
||||
// Description shown in the Speed Dial's footer strip (SettingActions: the localized tooltip).
|
||||
std::string tooltip;
|
||||
// Search-only alias when the title differs from the descriptive ConfigOptionDef name (e.g. title
|
||||
// "Reverse on even", full_label "Overhang reversal"). Empty when the two agree.
|
||||
std::string full_label;
|
||||
// Full wiki URL, when the action has one (SettingActions whose row declared a label_path).
|
||||
std::string help_url;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "OptionsGroup.hpp"
|
||||
#include "ConfigExceptions.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "SettingsIndex.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "OG_CustomCtrl.hpp"
|
||||
@@ -244,11 +245,24 @@ void OptionsGroup::append_line(const Line& line)
|
||||
{
|
||||
m_lines.emplace_back(line);
|
||||
|
||||
// Record each option's wiki path (Line::label_path) so the Speed Dial can offer an "open wiki"
|
||||
// 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().settings_index().set_path(opt.opt_id, static_cast<Preset::Type>(config_type()), line.label_path);
|
||||
// Feed the searcher the row's wiki path (Line::label_path, for the Speed Dial's "open wiki"
|
||||
// affordance) and the label the row actually draws, so a setting action is named like the page.
|
||||
if (m_use_custom_ctrl) {
|
||||
Search::SettingsIndex& index = wxGetApp().sidebar().settings_index();
|
||||
const Preset::Type type = static_cast<Preset::Type>(config_type());
|
||||
const bool multi = line.get_options().size() > 1;
|
||||
for (const auto& opt : line.get_options()) {
|
||||
if (!line.label_path.empty())
|
||||
index.set_path(opt.opt_id, type, line.label_path);
|
||||
// Mirror the sub-label OG_CustomCtrl draws for a multi-option row, so the palette
|
||||
// names each field like the page does.
|
||||
const std::string& leaf_src = opt.opt.label;
|
||||
const wxString leaf = (leaf_src == L_CONTEXT("Top", "Layers") || leaf_src == L_CONTEXT("Bottom", "Layers")) ?
|
||||
_L_CONTEXT(leaf_src, "Layers") :
|
||||
_(leaf_src);
|
||||
index.set_line_label(opt.opt_id, type, Search::compose_display_label(line.label, leaf, multi));
|
||||
}
|
||||
}
|
||||
|
||||
if (line.full_width && (line.widget != nullptr || !line.get_extra_widgets().empty()))
|
||||
return;
|
||||
|
||||
@@ -55,9 +55,13 @@ static Option make_option(const std::string &key, Preset::Type type, const wxStr
|
||||
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(),
|
||||
Option 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};
|
||||
GUI::Tab::translate_category(category, type).ToStdWstring(), false, mode, tooltip, gc.path,
|
||||
// The settings page draws Line::label; carrying it lets the Speed Dial name a setting
|
||||
// the way the page does. `label`/`label_local` stay the search-oriented name.
|
||||
into_u8(gc.line_label)};
|
||||
return option;
|
||||
}
|
||||
|
||||
void SettingsIndex::append_options(DynamicPrintConfig *config, Preset::Type type, ConfigOptionMode mode)
|
||||
@@ -225,5 +229,12 @@ void SettingsIndex::set_path(const std::string &opt_key, Preset::Type type, cons
|
||||
m_groups_and_categories[get_key(opt_key, type)].path = path;
|
||||
}
|
||||
|
||||
void SettingsIndex::set_line_label(const std::string &opt_key, Preset::Type type, const wxString &label)
|
||||
{
|
||||
if (label.IsEmpty())
|
||||
return;
|
||||
m_groups_and_categories[get_key(opt_key, type)].line_label = label;
|
||||
}
|
||||
|
||||
} // namespace Search
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -26,10 +26,31 @@ 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
|
||||
wxString icon; // icon of the group's own header, or empty
|
||||
wxString line_label; // label the settings row actually draws (Line::label), or empty
|
||||
std::string path; // wiki path (Line::label_path) of the option's line, or empty
|
||||
};
|
||||
|
||||
// Title for a setting: the row label, qualified with the field leaf when the row packs several
|
||||
// options (e.g. "Cool Plate \u2013 First layer"). Pure; inputs are already localized.
|
||||
inline wxString compose_display_label(const wxString& line_label, const wxString& leaf_label, bool multi)
|
||||
{
|
||||
if (line_label.empty())
|
||||
return leaf_label;
|
||||
if (!multi || leaf_label.empty() || leaf_label == line_label)
|
||||
return line_label;
|
||||
return line_label + L" \u2013 " + leaf_label; // en dash separator
|
||||
}
|
||||
|
||||
// Title to show. A single-option row uses its live label, which can be renamed at runtime
|
||||
// (brim_width -> "Brim ear radius"); otherwise fall back to the precomposed label.
|
||||
inline wxString resolve_setting_title(const wxString& precomposed, const wxString& live_label, bool live_multi)
|
||||
{
|
||||
if (!live_multi && !live_label.empty())
|
||||
return live_label;
|
||||
return precomposed;
|
||||
}
|
||||
|
||||
struct Option
|
||||
{
|
||||
// bool operator<(const Option& other) const { return other.label > this->label; }
|
||||
@@ -50,6 +71,7 @@ struct Option
|
||||
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 display_label; // label the settings row draws (localized); empty falls back to label
|
||||
|
||||
std::string opt_key() const;
|
||||
};
|
||||
@@ -75,6 +97,9 @@ public:
|
||||
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);
|
||||
// Record the label the option's row draws, so the Speed Dial names a setting like the page
|
||||
// (ConfigOptionDef::label/full_label is a search name, not the row text).
|
||||
void set_line_label(const std::string &opt_key, Preset::Type type, const wxString &label);
|
||||
|
||||
const std::vector<Option> &options() const { return m_options; }
|
||||
const std::vector<Option> &all_options() const { return m_all_modes; }
|
||||
|
||||
+36
-6
@@ -1736,16 +1736,46 @@ void Tab::toggle_option(const std::string& opt_key, bool toggle, int opt_index/*
|
||||
|
||||
void Tab::toggle_line(const std::string &opt_key, bool toggle, int opt_index)
|
||||
{
|
||||
if (!m_active_page) return;
|
||||
Line *line = m_active_page->get_line(opt_key, opt_index);
|
||||
if (line) line->toggle_visible = toggle;
|
||||
// Apply to every page that owns the option, not just m_active_page. ConfigManipulation runs while
|
||||
// each tab updates at preset load, so the Speed Dial sees the same visibility regardless of page.
|
||||
for (const PageShp& page : m_pages) {
|
||||
if (!page) continue;
|
||||
if (Line *line = page->get_line(opt_key, opt_index))
|
||||
line->toggle_visible = toggle;
|
||||
}
|
||||
};
|
||||
|
||||
void Tab::set_option_label(const std::string &opt_key, const wxString &label, int opt_index)
|
||||
{
|
||||
if (!m_active_page) return;
|
||||
Line *line = m_active_page->get_line(opt_key, opt_index);
|
||||
if (line) line->set_label(label);
|
||||
// Same as toggle_line: a runtime rename (brim_width -> "Brim ear radius") must reach every page
|
||||
// so the Speed Dial titles the setting before the page has been shown.
|
||||
for (const PageShp& page : m_pages) {
|
||||
if (!page) continue;
|
||||
if (Line *line = page->get_line(opt_key, opt_index))
|
||||
line->set_label(label);
|
||||
}
|
||||
}
|
||||
|
||||
Tab::SettingRowState Tab::setting_row_state(const std::string &opt_id) const
|
||||
{
|
||||
bool found = false;
|
||||
for (const PageShp& page : m_pages) {
|
||||
if (!page) continue;
|
||||
for (const ConfigOptionsGroupShp& group : page->m_optgroups) {
|
||||
if (!group) continue;
|
||||
for (const Line& line : group->get_lines()) {
|
||||
for (const Option& opt : line.get_options()) {
|
||||
if (opt.opt_id != opt_id)
|
||||
continue;
|
||||
if (line.toggle_visible) // shown on any owning page is enough
|
||||
return {true, line.label, line.get_options().size() > 1};
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Never registered on a page -> visible, but with no row label to contribute.
|
||||
return {!found, wxString(), false};
|
||||
}
|
||||
|
||||
// To be called by custom widgets, load a value into a config,
|
||||
|
||||
@@ -401,6 +401,16 @@ public:
|
||||
void toggle_option(const std::string &opt_key, bool toggle, int opt_index = -1);
|
||||
void toggle_line(const std::string &opt_key, bool toggle, int opt_index = -1); // BBS: hide some line
|
||||
void set_option_label(const std::string &opt_key, const wxString &label, int opt_index = -1);
|
||||
|
||||
// Live state of the settings row that owns an option, read from the built pages.
|
||||
struct SettingRowState
|
||||
{
|
||||
bool visible{true}; // false when ConfigManipulation hides the row
|
||||
wxString label; // Line::label the row draws (may change at runtime)
|
||||
bool multi{false}; // row packs several options, so label is precomposed
|
||||
};
|
||||
SettingRowState setting_row_state(const std::string &opt_id) const;
|
||||
|
||||
wxSizer* description_line_widget(wxWindow* parent, ogStaticText** StaticText, wxString text = wxEmptyString);
|
||||
bool current_preset_is_dirty() const;
|
||||
bool saved_preset_is_dirty() const;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "slic3r/GUI/ActionRegistry.hpp"
|
||||
#include "slic3r/GUI/NativeCommands.hpp"
|
||||
#include "slic3r/GUI/SettingsIndex.hpp"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
@@ -284,6 +285,35 @@ TEST_CASE("Native command catalog covers the Add menus", "[ActionSource][SpeedDi
|
||||
}
|
||||
}
|
||||
|
||||
// A setting action is named like its settings row, not the ConfigOptionDef label: the row's
|
||||
// Line::label, plus the field leaf when the row packs several options.
|
||||
TEST_CASE("Setting display labels mirror the settings row", "[ActionSource][SpeedDial]")
|
||||
{
|
||||
using Slic3r::Search::compose_display_label;
|
||||
|
||||
// Single-option row: the row label is the whole title.
|
||||
CHECK(compose_display_label(L"Reverse on even", L"Reverse on even", false) == L"Reverse on even");
|
||||
// No recorded row label falls back to the field leaf.
|
||||
CHECK(compose_display_label(L"", L"Outer wall", false) == L"Outer wall");
|
||||
// Multi-option row: qualify with the leaf so the plate-temperature fields are distinct.
|
||||
CHECK(compose_display_label(L"Cool Plate", L"First layer", true) == wxString(L"Cool Plate \u2013 First layer"));
|
||||
CHECK(compose_display_label(L"Cool Plate", L"Other layers", true) == wxString(L"Cool Plate \u2013 Other layers"));
|
||||
// A leaf equal to the row label is not repeated.
|
||||
CHECK(compose_display_label(L"Skirt loops", L"Skirt loops", true) == L"Skirt loops");
|
||||
|
||||
using Slic3r::Search::resolve_setting_title;
|
||||
|
||||
// A single-option row's live label wins, so a runtime rename is reflected.
|
||||
CHECK(resolve_setting_title(L"Brim width", L"Brim ear radius", false) == L"Brim ear radius");
|
||||
// Multi-option rows keep their precomposed "row – field" label (the leaf disambiguates them).
|
||||
CHECK(resolve_setting_title(L"Cool Plate \u2013 First layer", L"Cool Plate", true) ==
|
||||
wxString(L"Cool Plate \u2013 First layer"));
|
||||
// No live row label (option not on a built page) keeps the precomposed label.
|
||||
CHECK(resolve_setting_title(L"Reverse on even", L"", false) == L"Reverse on even");
|
||||
// Neither present: empty, so the caller falls back to the descriptive label.
|
||||
CHECK(resolve_setting_title(L"", L"", false).IsEmpty());
|
||||
}
|
||||
|
||||
// A setting whose mode is above the user's current mode must be prompted before it can be edited.
|
||||
// Developer settings (comDevelop) are above every non-developer mode, so they always prompt then.
|
||||
TEST_CASE("Settings above the current mode require a switch", "[ActionSource][SpeedDial]")
|
||||
|
||||
Reference in New Issue
Block a user