Support searching the Speed Dial by English names in any UI language (#16369)

This commit is contained in:
SoftFever
2026-10-11 21:29:06 +08:00
committed by GitHub
parent 7369674d8f
commit bd125d32da
16 changed files with 517 additions and 319 deletions
+103 -56
View File
@@ -206,19 +206,52 @@ constexpr const char* kPlateGotoPrefix = "orca_plate_goto";
constexpr const char* kRecentProjectPrefix = "orca_recent_project";
// Display context for a setting action's eyebrow, e.g. the "Process" in "Process : Quality : Layers".
// Keyed by the option's preset type so the palette reads like the settings sidebar tabs.
std::string setting_type_context(Preset::Type type)
// Keyed by the option's preset type so the palette reads like the settings sidebar tabs. Untranslated:
// the caller translates it for the displayed breadcrumb and keeps it for the English one.
const char* setting_type_context(Preset::Type type)
{
switch (type) {
case Preset::TYPE_FILAMENT:
case Preset::TYPE_SLA_MATERIAL: return _u8L("Filament");
case Preset::TYPE_PRINTER: return _u8L("Printer");
case Preset::TYPE_SLA_MATERIAL: return L("Filament");
case Preset::TYPE_PRINTER: return L("Printer");
case Preset::TYPE_PRINT:
case Preset::TYPE_SLA_PRINT:
default: return _u8L("Process");
default: return L("Process");
}
}
// A setting's text in one language. The displayed text and its English search aliases both come from
// here, so in an English UI the two are equal and no alias is sent.
struct SettingText
{
std::string title; // the label the settings row draws
std::string full_label; // the descriptive ConfigOptionDef name when it differs from title: a
// search-only alias ("overhang reversal" still finds "Reverse on even")
std::string source; // the full settings path "Process : Quality : Layers". The JS renders
// group || source and searches every field, so putting the whole path in
// source both displays it and makes it matchable by any segment.
};
SettingText setting_text(const std::wstring& def_label, const std::string& display_label, const wxString& row_label, bool row_multi,
const std::string& type_name, const std::wstring& category, const std::wstring& group)
{
SettingText text;
const std::string def = boost::nowide::narrow(def_label);
text.title = into_u8(Search::resolve_setting_title(from_u8(display_label), row_label, row_multi));
if (text.title.empty())
text.title = def;
if (text.title != def)
text.full_label = def;
std::wstring path = boost::nowide::widen(type_name);
if (!category.empty())
path += L" : " + category;
if (!group.empty())
path += L" : " + group;
text.source = boost::nowide::narrow(path);
return text;
}
// Stable, non-localized mode token for the webview, which maps it to a badge ("Developer" etc.).
const char* mode_key(ConfigOptionMode mode)
{
@@ -305,8 +338,9 @@ struct PlateAction : AppAction
PlateAction(int index, std::string title, std::string source_name)
: AppAction(AppActionId{id_for(index)}, std::move(title), kOrcaSourceKey, std::move(source_name)), plate_index(index)
{
this->kind = AppActionKind::Command;
this->group = _u8L("Plate");
this->kind = AppActionKind::Command;
this->group_en = L("Plate");
this->group = I18N::translate_utf8(this->group_en);
}
AppActionRunResult run(const std::string& /*param*/) const override
@@ -328,8 +362,9 @@ struct RecentProjectAction : AppAction
RecentProjectAction(std::string path, std::string title, std::string source)
: AppAction(AppActionId{id_for(path)}, std::move(title), kOrcaSourceKey, std::move(source)), file_path(std::move(path))
{
this->kind = AppActionKind::Command;
this->group = _u8L("Recent Projects");
this->kind = AppActionKind::Command;
this->group_en = L("Recent Projects");
this->group = I18N::translate_utf8(this->group_en);
}
AppActionRunResult run(const std::string& /*param*/) const override
@@ -342,6 +377,30 @@ struct RecentProjectAction : AppAction
} // namespace
nlohmann::json action_json(const AppAction& a)
{
nlohmann::json j({{"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"},
{"input", a.input},
{"icon", a.icon},
{"mode", mode_key(a.required_mode)},
{"desc", a.tooltip},
{"wiki", !a.help_url.empty()}});
auto add_alias = [&j](const char* key, const std::string& english, const std::string& local) {
if (!english.empty() && english != local)
j[key] = english;
};
add_alias("title_en", a.title_en, a.title());
add_alias("group_en", a.group_en, a.group);
add_alias("source_en", a.source_en, a.source_name());
add_alias("full_label_en", a.full_label_en, a.full_label);
return j;
}
ActionRegistry::~ActionRegistry() = default;
void ActionRegistry::init()
@@ -430,7 +489,8 @@ void ActionRegistry::relocalize_builtins()
// Drop only the built-in commands; plugins and the dynamically materialised families are
// either unlocalized or rebuilt per snapshot. remove() only erases the map entry, and upsert()
// re-seeds favourites/stats from config, so key-based ids keep their pinned state.
// re-seeds favourites/stats from config, so key-based ids keep their pinned state. make_action()
// translates in the new locale.
std::vector<std::string> stale;
for (const auto& [id, action] : m_actions)
if (action->source_key() == kOrcaSourceKey && action->kind == AppActionKind::Command)
@@ -438,7 +498,6 @@ void ActionRegistry::relocalize_builtins()
for (const std::string& id : stale)
remove(id);
NativeCommands::rebuild_catalog();
for (const NativeCommand& c : NativeCommands::catalog())
upsert(NativeCommands::make_action(c));
}
@@ -672,28 +731,22 @@ void ActionRegistry::materialize_setting_actions()
const std::string id = SettingAction::id_for(opt.opt_key(), opt.type);
seen.insert(id);
// 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;
// The localized text the page shows, and its English search aliases from the untranslated row
// label, def name and breadcrumb.
const char* type_context = setting_type_context(opt.type);
SettingText local = setting_text(opt.label_local.empty() ? opt.label : opt.label_local, opt.display_label, row.label, row.multi,
I18N::translate_utf8(type_context), opt.category_local, opt.group_local);
SettingText english = setting_text(opt.label, opt.display_label_en, row.label_en, row.multi, type_context,
Tab::translate_category(opt.category, opt.type, false).ToStdWstring(), opt.group);
// 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
// source both displays it and makes it matchable by any segment (e.g. a "quality" query).
std::wstring path = boost::nowide::widen(setting_type_context(opt.type));
if (!opt.category_local.empty())
path += L" : " + opt.category_local;
if (!opt.group_local.empty())
path += L" : " + opt.group_local;
// 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;
// group stays empty so the source path is the single display/search breadcrumb rather than
// being duplicated.
auto action = std::make_unique<SettingAction>(opt.opt_key(), opt.type, local.title, std::string(), opt.category, local.source,
opt.mode);
action->full_label = std::move(local.full_label);
action->title_en = std::move(english.title);
action->full_label_en = std::move(english.full_label);
action->source_en = std::move(english.source);
// 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
@@ -751,13 +804,14 @@ void ActionRegistry::materialize_plate_actions()
seen.insert(id);
// "Go to Plate N" + " (name)" when the plate is named, matching the object-list label.
std::string title(_u8L("Go to Plate"));
title += " " + std::to_string(i + 1);
const std::string name = plate->get_plate_name();
const char* go_to_plate = L("Go to Plate");
std::string suffix = " " + std::to_string(i + 1);
const std::string name = plate->get_plate_name();
if (!name.empty())
title += " (" + name + ")";
suffix += " (" + name + ")";
auto action = std::make_unique<PlateAction>(int(i), title, kOrcaSourceName);
auto action = std::make_unique<PlateAction>(int(i), I18N::translate_utf8(go_to_plate) + suffix, kOrcaSourceName);
action->title_en = go_to_plate + suffix;
seed_from(stats, favs, id, *action);
auto const action_id = action->id();
auto const app_action = std::shared_ptr<AppAction>(std::move(action));
@@ -868,23 +922,9 @@ nlohmann::json ActionRegistry::snapshot()
return a->id() < b->id();
});
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"},
{"input", a->input},
{"icon", a->icon},
{"mode", mode_key(a->required_mode)},
{"desc", a->tooltip},
{"wiki", !a->help_url.empty()}});
};
nlohmann::json actions = nlohmann::json::array();
for (const AppAction* a : sorted)
actions.push_back(action_to_json(a));
actions.push_back(action_json(*a));
// why: favourites is the ORDERED pin list - it must come from favourite_actions
// as stored, not be re-derived from the frecency-sorted actions (that would
@@ -917,7 +957,7 @@ nlohmann::json ActionRegistry::snapshot()
recent.resize(recent_limit);
nlohmann::json recent_json = nlohmann::json::array();
for (const AppAction* a : recent)
recent_json.push_back(action_to_json(a));
recent_json.push_back(action_json(*a));
return {{"actions", std::move(actions)},
{"favourites", std::move(favourites)},
@@ -937,14 +977,21 @@ nlohmann::json ActionRegistry::tab_options() const
MainFrame* mf = wxGetApp().mainframe;
if (!mf || !mf->m_tabpanel)
return out;
// English labels of the built-in pages whose id is not already their English name, so the tab
// picker can be searched in English in any UI language.
static const std::unordered_map<std::string, const char*> english_labels{
{TAB_ID_MONITOR, L("Device")}, {TAB_ID_MONITOR_WEB, L("Device (Web)")}, {TAB_ID_MULTI_DEVICE, L("Multi-device")}};
Notebook* notebook = mf->m_tabpanel;
for (size_t i = 0; i < notebook->GetPageCount(); ++i) {
const wxString id = notebook->GetPageName(i);
if (id.empty())
continue;
out.push_back({{"id", id.ToStdString()},
{"title", notebook->GetPageLabel(i).ToStdString()},
{"icon", notebook->GetPageIcon(i)}});
const std::string title = notebook->GetPageLabel(i).ToStdString();
nlohmann::json tab({{"id", id.ToStdString()}, {"title", title}, {"icon", notebook->GetPageIcon(i)}});
if (auto it = english_labels.find(id.ToStdString()); it != english_labels.end() && title != it->second)
tab["title_en"] = it->second;
out.push_back(std::move(tab));
}
return out;
}
+13
View File
@@ -88,6 +88,14 @@ struct AppAction
// 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;
// Search-only English counterparts of title(), group, source_name() and full_label, so a
// localized UI can still be searched in English. Each is derived exactly like the localized
// text it mirrors, minus the translation, so in an English UI it equals that text. Empty when
// there is no translated counterpart.
std::string title_en;
std::string group_en;
std::string source_en;
std::string full_label_en;
// Full wiki URL, when the action has one (SettingActions whose row declared a label_path).
std::string help_url;
@@ -138,6 +146,11 @@ inline bool requires_mode_switch(ConfigOptionMode setting_mode, ConfigOptionMode
// hand-edited config must never grow the quick-launch bar past `limit`, and a duplicated id must collapse to its first pin.
std::vector<std::string> cap_favourites(const std::vector<std::string>& ids, size_t limit);
// One action as the Speed Dial page receives it. Each English alias (title_en, group_en, source_en,
// full_label_en) is sent only when it is non-empty and differs from its localized counterpart, so an
// English UI receives none and searches exactly the displayed text.
nlohmann::json action_json(const AppAction& a);
// Self-contained sink and single owner of runnable actions for the app session.
//
// Workflow:
+1 -1
View File
@@ -1020,7 +1020,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
const BrimType brim_type = config->opt_enum<BrimType>("brim_type");
const bool have_auto_brim_ear = brim_type == btEar && !is_belt_tilted;
const bool have_painted_brim_ear = brim_type == btPainted && !is_belt_tilted;
set_option_label("brim_width", have_auto_brim_ear ? _L("Brim ear radius") : _L("Brim width"));
set_option_label("brim_width", have_auto_brim_ear ? L("Brim ear radius") : L("Brim width"));
const auto brim_width = config->opt_float("brim_width");
// Automatic brim ear settings require a non-zero brim width.
toggle_field("brim_ears_max_angle", brim_width > 0.0f);
+2
View File
@@ -80,6 +80,8 @@ public:
t_config_option_keys const &applying_keys() const;
void toggle_field(const std::string& field_key, const bool toggle, int opt_index = -1);
void toggle_line(const std::string& field_key, const bool toggle, int opt_index = -1);
// Renames an option's row. `label` is the untranslated English msgid (an L("...") string); the
// row translates it for display.
void set_option_label(const std::string& field_key, const wxString& label, int opt_index = -1);
// FFF print
+106 -113
View File
@@ -142,19 +142,24 @@ struct CommandAction : AppAction
AppActionRunResult run(const std::string& param) const override { return NativeCommands::run(command_key, param); }
explicit CommandAction(const NativeCommand& c)
: AppAction(AppActionId{AppAction::compose_id(kCommandPrefix, c.key, kOrcaSourceKey)}, c.title, kOrcaSourceKey, kOrcaSourceName)
: AppAction(AppActionId{AppAction::compose_id(kCommandPrefix, c.key, kOrcaSourceKey)}, I18N::translate_utf8(c.title), kOrcaSourceKey,
kOrcaSourceName)
, command_key(c.key)
{
this->kind = AppActionKind::Command;
this->group = c.group;
this->input = c.input;
this->icon = c.icon;
this->kind = AppActionKind::Command;
this->group = I18N::translate_utf8(c.group);
this->title_en = c.title;
this->group_en = c.group;
this->input = c.input;
this->icon = c.icon;
}
};
std::vector<NativeCommand> build_command_catalog()
{
std::vector<NativeCommand> out;
// `title` and `group` are the untranslated English msgids, L("...")-marked at each call so xgettext
// extracts them.
auto add = [&](std::string key, std::string title, std::string group, std::function<AppActionRunResult(const std::string&)> runner,
std::string input = {}, std::string icon = {}) {
out.push_back({std::move(key), std::move(title), std::move(group), std::move(input), std::move(icon), std::move(runner)});
@@ -166,7 +171,7 @@ std::vector<NativeCommand> build_command_catalog()
};
// ---- Slice & Export ----
add_with_icon("slice_and_preview", _u8L("Slice and Preview"), _u8L("Slice & Export"), "media_play", [](const std::string&) {
add_with_icon("slice_and_preview", L("Slice and Preview"), L("Slice & Export"), "media_play", [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (plater) {
plater->reslice();
@@ -178,7 +183,7 @@ std::vector<NativeCommand> build_command_catalog()
});
add_with_icon(
"go_to_layer", _u8L("Go to layer (percent)"), _u8L("Commands"), "height_range_layer",
"go_to_layer", L("Go to layer (percent)"), L("Commands"), "height_range_layer",
[](const std::string& param) {
Plater* plater = wxGetApp().plater();
if (plater) {
@@ -194,7 +199,7 @@ std::vector<NativeCommand> build_command_catalog()
// "go_to_tab" is two-phase: the palette collects the tab after activating it, then hands the tab
// id back as `param` (same contract as go_to_layer's percent).
add(
"go_to_tab", _u8L("Go to tab..."), _u8L("Commands"),
"go_to_tab", L("Go to tab..."), L("Commands"),
[](const std::string& param) {
if (MainFrame* mf = wxGetApp().mainframe; mf && !param.empty())
mf->select_tab(from_u8(param));
@@ -202,41 +207,41 @@ std::vector<NativeCommand> build_command_catalog()
},
"tab");
add_with_icon("load_project", _u8L("Load Project"), _u8L("Commands"), "menu_open", [](const std::string&) {
add_with_icon("load_project", L("Load Project"), L("Commands"), "menu_open", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->load_project();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("save_project", _u8L("Save Project"), _u8L("Commands"), "menu_save", [](const std::string&) {
add_with_icon("save_project", L("Save Project"), L("Commands"), "menu_save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->save_project(false);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("save_project_as", _u8L("Save Project As"), _u8L("Commands"), "menu_save", [](const std::string&) {
add_with_icon("save_project_as", L("Save Project As"), L("Commands"), "menu_save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->save_project(true);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("open_preferences", _u8L("Preferences"), _u8L("Commands"), "cog", [](const std::string&) {
add_with_icon("open_preferences", L("Preferences"), L("Commands"), "cog", [](const std::string&) {
wxGetApp().open_preferences();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
// ---- Mode ----
add_with_icon("mode_simple", _u8L("Mode: Simple"), _u8L("Mode"), "advanced", [](const std::string&) {
add_with_icon("mode_simple", L("Mode: Simple"), L("Mode"), "advanced", [](const std::string&) {
wxGetApp().set_mode(comSimple);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("mode_advanced", _u8L("Mode: Advanced"), _u8L("Mode"), "advanced", [](const std::string&) {
add_with_icon("mode_advanced", L("Mode: Advanced"), L("Mode"), "advanced", [](const std::string&) {
wxGetApp().set_mode(comAdvanced);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("mode_expert", _u8L("Mode: Expert"), _u8L("Mode"), "advanced", [](const std::string&) {
add_with_icon("mode_expert", L("Mode: Expert"), L("Mode"), "advanced", [](const std::string&) {
wxGetApp().set_mode(comExpert);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
// Mirrors Preferences > Developer > Developer mode: flip the flag, persist, refresh the UI.
add_with_icon("toggle_developer_mode", _u8L("Toggle Developer Mode"), _u8L("Mode"), "advanced", [](const std::string&) {
add_with_icon("toggle_developer_mode", L("Toggle Developer Mode"), L("Mode"), "advanced", [](const std::string&) {
GUI_App& app = wxGetApp();
const bool on = !app.app_config->get_bool("developer_mode");
app.app_config->set_bool("developer_mode", on);
@@ -246,27 +251,27 @@ std::vector<NativeCommand> build_command_catalog()
});
// ---- Export pipeline ----
add_with_icon("export_gcode", _u8L("Export G-code"), _u8L("Slice & Export"), "custom-gcode_gcode", [](const std::string&) {
add_with_icon("export_gcode", L("Export G-code"), L("Slice & Export"), "custom-gcode_gcode", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_gcode(false);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("export_stl", _u8L("Export STL"), _u8L("Slice & Export"), "save", [](const std::string&) {
add_with_icon("export_stl", L("Export STL"), L("Slice & Export"), "save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_stl();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("export_3mf", _u8L("Export 3MF"), _u8L("Slice & Export"), "menu_save", [](const std::string&) {
add_with_icon("export_3mf", L("Export 3MF"), L("Slice & Export"), "menu_save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_core_3mf();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("export_sliced_file", _u8L("Export Sliced File"), _u8L("Slice & Export"), "save", [](const std::string&) {
add_with_icon("export_sliced_file", L("Export Sliced File"), L("Slice & Export"), "save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_gcode_3mf(false);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("export_all_sliced_file", _u8L("Export All Sliced Files"), _u8L("Slice & Export"), "save", [](const std::string&) {
add_with_icon("export_all_sliced_file", L("Export All Sliced Files"), L("Slice & Export"), "save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_gcode_3mf(true);
return AppActionRunResult{AppActionRunResult::Level::Success};
@@ -275,39 +280,39 @@ std::vector<NativeCommand> build_command_catalog()
// ---- Calibration ----
// The tab-strip calib_sf glyph is drawn white for the dark tab bar and vanishes on the palette's
// light tile, so each wizard borrows the matching settings-group icon instead (gray + accent green).
add_with_icon("calib_temperature", _u8L("Temperature Calibration"), _u8L("Calibration"), "param_temperature",
add_with_icon("calib_temperature", L("Temperature Calibration"), L("Calibration"), "param_temperature",
[](const std::string&) { return calib_command(CalibKind::Temperature); });
add_with_icon("calib_max_volumetric", _u8L("Max Volumetric Speed Calibration"), _u8L("Calibration"), "param_volumetric_speed",
add_with_icon("calib_max_volumetric", L("Max Volumetric Speed Calibration"), L("Calibration"), "param_volumetric_speed",
[](const std::string&) { return calib_command(CalibKind::MaxVolumetric); });
add_with_icon("calib_pressure_advance", _u8L("Pressure Advance Calibration"), _u8L("Calibration"), "param_flow_ratio_and_pressure_advance",
add_with_icon("calib_pressure_advance", L("Pressure Advance Calibration"), L("Calibration"), "param_flow_ratio_and_pressure_advance",
[](const std::string&) { return calib_command(CalibKind::PressureAdvance); });
add_with_icon("calib_flow_ratio", _u8L("Flow Ratio Calibration"), _u8L("Calibration"), "param_flow_ratio_and_pressure_advance",
add_with_icon("calib_flow_ratio", L("Flow Ratio Calibration"), L("Calibration"), "param_flow_ratio_and_pressure_advance",
[](const std::string&) { return calib_command(CalibKind::FlowRatio); });
add_with_icon("calib_retraction", _u8L("Retraction Calibration"), _u8L("Calibration"), "param_retraction",
add_with_icon("calib_retraction", L("Retraction Calibration"), L("Calibration"), "param_retraction",
[](const std::string&) { return calib_command(CalibKind::Retraction); });
add_with_icon("calib_cornering", _u8L("Cornering Calibration"), _u8L("Calibration"), "param_precision",
add_with_icon("calib_cornering", L("Cornering Calibration"), L("Calibration"), "param_precision",
[](const std::string&) { return calib_command(CalibKind::Cornering); });
add_with_icon("calib_input_shaping_freq", _u8L("Input Shaping Frequency Calibration"), _u8L("Calibration"), "param_resonance_avoidance",
add_with_icon("calib_input_shaping_freq", L("Input Shaping Frequency Calibration"), L("Calibration"), "param_resonance_avoidance",
[](const std::string&) { return calib_command(CalibKind::InputShapingFreq); });
add_with_icon("calib_input_shaping_damp", _u8L("Input Shaping Damping Calibration"), _u8L("Calibration"), "param_resonance_avoidance",
add_with_icon("calib_input_shaping_damp", L("Input Shaping Damping Calibration"), L("Calibration"), "param_resonance_avoidance",
[](const std::string&) { return calib_command(CalibKind::InputShapingDamp); });
add_with_icon("calib_vfa", _u8L("VFA Calibration"), _u8L("Calibration"), "param_speed", [](const std::string&) { return calib_command(CalibKind::VFA); });
add_with_icon("calib_vfa", L("VFA Calibration"), L("Calibration"), "param_speed", [](const std::string&) { return calib_command(CalibKind::VFA); });
// ---- View ----
// Titles are built with _u8L here (not via a variable) so xgettext can extract them.
// Titles are L("...")-marked here (not via a variable) so xgettext can extract them.
for (auto [key, dir, title] :
std::initializer_list<std::tuple<const char*, const char*, std::string>>{{"view_top", "top", _u8L("View: Top")},
{"view_bottom", "bottom", _u8L("View: Bottom")},
{"view_front", "front", _u8L("View: Front")},
{"view_rear", "rear", _u8L("View: Rear")},
{"view_left", "left", _u8L("View: Left")},
{"view_right", "right", _u8L("View: Right")},
{"view_iso", "iso", _u8L("View: Isometric")}}) {
std::initializer_list<std::tuple<const char*, const char*, std::string>>{{"view_top", "top", L("View: Top")},
{"view_bottom", "bottom", L("View: Bottom")},
{"view_front", "front", L("View: Front")},
{"view_rear", "rear", L("View: Rear")},
{"view_left", "left", L("View: Left")},
{"view_right", "right", L("View: Right")},
{"view_iso", "iso", L("View: Isometric")}}) {
std::string k = key, d = dir;
add(k, title, _u8L("View"),
add(k, title, L("View"),
[d](const std::string&) { return view_command(wxGetApp().plater(), d); });
}
add("view_default", _u8L("View: Default"), _u8L("View"), [](const std::string&) {
add("view_default", L("View: Default"), L("View"), [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (plater) {
plater->select_view("plate");
@@ -316,75 +321,75 @@ std::vector<NativeCommand> build_command_catalog()
}
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("view_fit_bed", _u8L("Fit Bed to View"), _u8L("View"), [](const std::string&) {
add("view_fit_bed", L("Fit Bed to View"), L("View"), [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
if (GLCanvas3D* canvas = plater->get_current_canvas3D())
canvas->zoom_to_bed();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("view_toggle_perspective", _u8L("Toggle Perspective"), _u8L("View"), [](const std::string&) {
add("view_toggle_perspective", L("Toggle Perspective"), L("View"), [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->get_camera().select_next_type();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("reset_window_layout", _u8L("Reset Window Layout"), _u8L("View"), "toolbar_reset", [](const std::string&) {
add_with_icon("reset_window_layout", L("Reset Window Layout"), L("View"), "toolbar_reset", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->reset_window_layout();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
// ---- Object ----
add_with_icon("obj_delete", _u8L("Delete Selected"), _u8L("Object"), "delete", [](const std::string&) {
add_with_icon("obj_delete", L("Delete Selected"), L("Object"), "delete", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return !p->is_selection_empty(); }, [](Plater* p) { p->remove_selected(); });
});
add_with_icon("obj_delete_all", _u8L("Delete All Objects"), _u8L("Object"), "delete", [](const std::string&) {
add_with_icon("obj_delete_all", L("Delete All Objects"), L("Object"), "delete", [](const std::string&) {
return object_op(
wxGetApp().plater(), [](Plater* p) { return p->can_delete_all(); }, [](Plater* p) { p->delete_all_objects_from_model(); });
});
add_with_icon("obj_mirror_x", _u8L("Mirror X"), _u8L("Object"), "menu_mirror_x", [](const std::string&) {
add_with_icon("obj_mirror_x", L("Mirror X"), L("Object"), "menu_mirror_x", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return p->can_mirror(); }, [](Plater* p) { p->mirror(Axis::X); });
});
add_with_icon("obj_mirror_y", _u8L("Mirror Y"), _u8L("Object"), "menu_mirror_y", [](const std::string&) {
add_with_icon("obj_mirror_y", L("Mirror Y"), L("Object"), "menu_mirror_y", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return p->can_mirror(); }, [](Plater* p) { p->mirror(Axis::Y); });
});
add_with_icon("obj_mirror_z", _u8L("Mirror Z"), _u8L("Object"), "menu_mirror_z", [](const std::string&) {
add_with_icon("obj_mirror_z", L("Mirror Z"), L("Object"), "menu_mirror_z", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return p->can_mirror(); }, [](Plater* p) { p->mirror(Axis::Z); });
});
add_with_icon("obj_split_objects", _u8L("Split to Objects"), _u8L("Object"), "menu_split_objects", [](const std::string&) {
add_with_icon("obj_split_objects", L("Split to Objects"), L("Object"), "menu_split_objects", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return p->can_split_to_objects(); }, [](Plater* p) { p->split_object(true); });
});
add_with_icon("obj_split_parts", _u8L("Split to Parts"), _u8L("Object"), "menu_split_parts", [](const std::string&) {
add_with_icon("obj_split_parts", L("Split to Parts"), L("Object"), "menu_split_parts", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return p->can_split_to_volumes(); }, [](Plater* p) { p->split_volume(); });
});
add("obj_center", _u8L("Center Selected on Plate"), _u8L("Object"), [](const std::string&) {
add("obj_center", L("Center Selected on Plate"), L("Object"), [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return !p->is_selection_empty(); }, [](Plater* p) { p->center_selection(); });
});
add_with_icon("obj_drop", _u8L("Drop to Bed"), _u8L("Object"), "toolbar_flatten", [](const std::string&) {
add_with_icon("obj_drop", L("Drop to Bed"), L("Object"), "toolbar_flatten", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return !p->is_selection_empty(); }, [](Plater* p) { p->drop_selection(); });
});
add("obj_fit_volume", _u8L("Scale to Fit Print Volume"), _u8L("Object"), [](const std::string&) {
add("obj_fit_volume", L("Scale to Fit Print Volume"), L("Object"), [](const std::string&) {
return object_op(
wxGetApp().plater(), [](Plater* p) { return p->can_scale_to_print_volume(); },
[](Plater* p) { p->scale_selection_to_fit_print_volume(); });
});
add_with_icon("obj_instances_up", _u8L("Increase Instances"), _u8L("Object"), "instance_add", [](const std::string&) {
add_with_icon("obj_instances_up", L("Increase Instances"), L("Object"), "instance_add", [](const std::string&) {
return object_op(
wxGetApp().plater(), [](Plater* p) { return p->can_increase_instances(); }, [](Plater* p) { p->increase_instances(); });
});
add_with_icon("obj_instances_down", _u8L("Decrease Instances"), _u8L("Object"), "instance_remove", [](const std::string&) {
add_with_icon("obj_instances_down", L("Decrease Instances"), L("Object"), "instance_remove", [](const std::string&) {
return object_op(
wxGetApp().plater(), [](Plater* p) { return p->can_decrease_instances(); }, [](Plater* p) { p->decrease_instances(); });
});
add_with_icon("obj_arrange", _u8L("Auto-Arrange"), _u8L("Object"), "toolbar_arrange", [](const std::string&) {
add_with_icon("obj_arrange", L("Auto-Arrange"), L("Object"), "toolbar_arrange", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return p->can_arrange(); }, [](Plater* p) { p->arrange(); });
});
add_with_icon("obj_orient", _u8L("Auto-Orient"), _u8L("Object"), "toolbar_orient", [](const std::string&) {
add_with_icon("obj_orient", L("Auto-Orient"), L("Object"), "toolbar_orient", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return p->can_arrange(); }, [](Plater* p) { p->orient(); });
});
// ---- Add Primitive ---- (the Add > Add Primitive submenu; creates a new object)
auto add_primitive = [&](std::string key, std::string title, std::string icon, const char* type_name) {
add_with_icon(std::move(key), std::move(title), _u8L("Add Primitive"), std::move(icon), [type_name](const std::string&) {
add_with_icon(std::move(key), std::move(title), L("Add Primitive"), std::move(icon), [type_name](const std::string&) {
Plater* plater = wxGetApp().plater();
if (plater) {
ensure_3d_view(plater);
@@ -394,13 +399,13 @@ std::vector<NativeCommand> build_command_catalog()
return AppActionRunResult{AppActionRunResult::Level::Success};
});
};
add_primitive("add_primitive_cube", _u8L("Cube"), "menu_obj_cube", "Cube");
add_primitive("add_primitive_cylinder", _u8L("Cylinder"), "menu_obj_cylinder", "Cylinder");
add_primitive("add_primitive_sphere", _u8L("Sphere"), "menu_obj_sphere", "Sphere");
add_primitive("add_primitive_cone", _u8L("Cone"), "menu_obj_cone", "Cone");
add_primitive("add_primitive_disc", _u8L("Disc"), "menu_obj_disc", "Disc");
add_primitive("add_primitive_torus", _u8L("Torus"), "menu_obj_torus", "Torus");
add_with_icon("add_primitive_text", _u8L("Text"), _u8L("Add Primitive"), "menu_obj_text", [](const std::string&) {
add_primitive("add_primitive_cube", L("Cube"), "menu_obj_cube", "Cube");
add_primitive("add_primitive_cylinder", L("Cylinder"), "menu_obj_cylinder", "Cylinder");
add_primitive("add_primitive_sphere", L("Sphere"), "menu_obj_sphere", "Sphere");
add_primitive("add_primitive_cone", L("Cone"), "menu_obj_cone", "Cone");
add_primitive("add_primitive_disc", L("Disc"), "menu_obj_disc", "Disc");
add_primitive("add_primitive_torus", L("Torus"), "menu_obj_torus", "Torus");
add_with_icon("add_primitive_text", L("Text"), L("Add Primitive"), "menu_obj_text", [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (plater) {
ensure_3d_view(plater);
@@ -410,7 +415,7 @@ std::vector<NativeCommand> build_command_catalog()
}
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("add_primitive_svg", _u8L("SVG"), _u8L("Add Primitive"), "menu_obj_svg", [](const std::string&) {
add_with_icon("add_primitive_svg", L("SVG"), L("Add Primitive"), "menu_obj_svg", [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (plater) {
ensure_3d_view(plater);
@@ -424,7 +429,7 @@ std::vector<NativeCommand> build_command_catalog()
// ---- Add Handy models ---- (the Add > Add Handy models submenu)
const std::vector<MenuFactory::HandyModel>& handy = MenuFactory::handy_models();
for (std::size_t i = 0; i < handy.size(); ++i) {
add("add_handy_" + std::string(handy[i].key), Slic3r::GUI::I18N::translate_utf8(handy[i].label), _u8L("Add Handy models"),
add("add_handy_" + std::string(handy[i].key), handy[i].label, L("Add Handy models"),
[i](const std::string&) {
if (Plater* plater = wxGetApp().plater())
ensure_3d_view(plater);
@@ -434,7 +439,7 @@ std::vector<NativeCommand> build_command_catalog()
}
// ---- Plate ----
add_with_icon("plate_add", _u8L("Add Plate"), _u8L("Plate"), "toolbar_add_plate", [](const std::string&) {
add_with_icon("plate_add", L("Add Plate"), L("Plate"), "toolbar_add_plate", [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (!is_fff_plater(plater))
return plate_unavailable();
@@ -443,7 +448,7 @@ std::vector<NativeCommand> build_command_catalog()
plater->add_plate();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("plate_duplicate", _u8L("Duplicate Plate"), _u8L("Plate"), "menu_copy", [](const std::string&) {
add_with_icon("plate_duplicate", L("Duplicate Plate"), L("Plate"), "menu_copy", [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (!is_fff_plater(plater))
return plate_unavailable();
@@ -452,7 +457,7 @@ std::vector<NativeCommand> build_command_catalog()
plater->duplicate_plate();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("plate_delete", _u8L("Delete Plate"), _u8L("Plate"), "delete", [](const std::string&) {
add_with_icon("plate_delete", L("Delete Plate"), L("Plate"), "delete", [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (!is_fff_plater(plater))
return plate_unavailable();
@@ -461,7 +466,7 @@ std::vector<NativeCommand> build_command_catalog()
plater->delete_plate();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("plate_rename", _u8L("Rename Plate"), _u8L("Plate"), "plate_name_edit", [](const std::string&) {
add_with_icon("plate_rename", L("Rename Plate"), L("Plate"), "plate_name_edit", [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (!is_fff_plater(plater))
return plate_unavailable();
@@ -472,7 +477,7 @@ std::vector<NativeCommand> build_command_catalog()
curr->set_plate_name(dlg.get_plate_name().ToUTF8().data());
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("plate_toggle_lock", _u8L("Toggle Plate Lock"), _u8L("Plate"), "lock_normal", [](const std::string&) {
add_with_icon("plate_toggle_lock", L("Toggle Plate Lock"), L("Plate"), "lock_normal", [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (!is_fff_plater(plater))
return plate_unavailable();
@@ -482,7 +487,7 @@ std::vector<NativeCommand> build_command_catalog()
plates.lock_plate(index, !plates.is_locked(index));
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("plate_goto", _u8L("Go to Plate"), _u8L("Plate"), "go_next_plate", [](const std::string& param) {
add_with_icon("plate_goto", L("Go to Plate"), L("Plate"), "go_next_plate", [](const std::string& param) {
Plater* plater = wxGetApp().plater();
if (!is_fff_plater(plater))
return plate_unavailable();
@@ -500,7 +505,7 @@ std::vector<NativeCommand> build_command_catalog()
});
// ---- Printer / device connection ----
add_with_icon("sync_ams", _u8L("Synchronize Filament List from AMS"), _u8L("Printer"), "ams_fila_sync", [](const std::string&) {
add_with_icon("sync_ams", L("Synchronize Filament List from AMS"), L("Printer"), "ams_fila_sync", [](const std::string&) {
Plater* plater = wxGetApp().plater();
DeviceManager* dev = wxGetApp().getDeviceManager();
if (dev && dev->get_selected_machine() && plater) {
@@ -511,11 +516,11 @@ std::vector<NativeCommand> build_command_catalog()
});
// ---- Presets / cloud ----
add_with_icon("preset_bundle", _u8L("Open Preset Bundle"), _u8L("Presets"), "menu_edit_preset", [](const std::string&) {
add_with_icon("preset_bundle", L("Open Preset Bundle"), L("Presets"), "menu_edit_preset", [](const std::string&) {
wxGetApp().open_presetbundledialog();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("sync_presets", _u8L("Sync Presets"), _u8L("Presets"), "printer_sync_ok", [](const std::string&) {
add_with_icon("sync_presets", L("Sync Presets"), L("Presets"), "printer_sync_ok", [](const std::string&) {
if (!wxGetApp().is_user_login())
return AppActionRunResult{AppActionRunResult::Level::Info, _L("Sign in to sync presets.")};
wxGetApp().restart_sync_user_preset();
@@ -523,7 +528,7 @@ std::vector<NativeCommand> build_command_catalog()
});
// ---- Import ----
add_with_icon("import_file", _u8L("Import 3MF/STL/STEP/SVG/OBJ/AMF"), _u8L("Import"), "menu_open", [](const std::string&) {
add_with_icon("import_file", L("Import 3MF/STL/STEP/SVG/OBJ/AMF"), L("Import"), "menu_open", [](const std::string&) {
if (Plater* plater = wxGetApp().plater()) {
#ifdef __APPLE__
plater->add_model();
@@ -533,71 +538,71 @@ std::vector<NativeCommand> build_command_catalog()
}
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("import_zip_archive", _u8L("Import ZIP Archive"), _u8L("Import"), "menu_open", [](const std::string&) {
add_with_icon("import_zip_archive", L("Import ZIP Archive"), L("Import"), "menu_open", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->import_zip_archive();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("import_configs", _u8L("Import Configs"), _u8L("Import"), "menu_open", [](const std::string&) {
add_with_icon("import_configs", L("Import Configs"), L("Import"), "menu_open", [](const std::string&) {
if (MainFrame* mf = wxGetApp().mainframe)
mf->load_config_file();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
// ---- Export extras ----
add_with_icon("export_stl_multi", _u8L("Export All Objects as STLs"), _u8L("Export"), "save", [](const std::string&) {
add_with_icon("export_stl_multi", L("Export All Objects as STLs"), L("Export"), "save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_stl(false, false, true);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("export_drc_single", _u8L("Export All Objects as DRC (one file)"), _u8L("Export"), "save", [](const std::string&) {
add_with_icon("export_drc_single", L("Export All Objects as DRC (one file)"), L("Export"), "save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_stl(false, false, false, FT_DRC);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("export_drc_multi", _u8L("Export All Objects as DRCs"), _u8L("Export"), "save", [](const std::string&) {
add_with_icon("export_drc_multi", L("Export All Objects as DRCs"), L("Export"), "save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_stl(false, false, true, FT_DRC);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("export_toolpaths_obj", _u8L("Export Toolpaths as OBJ"), _u8L("Export"), "custom-gcode_gcode", [](const std::string&) {
add_with_icon("export_toolpaths_obj", L("Export Toolpaths as OBJ"), L("Export"), "custom-gcode_gcode", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_toolpaths_to_obj();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("export_config", _u8L("Export Preset Bundle"), _u8L("Export"), "save", [](const std::string&) {
add_with_icon("export_config", L("Export Preset Bundle"), L("Export"), "save", [](const std::string&) {
if (MainFrame* mf = wxGetApp().mainframe)
mf->export_config();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
// ---- Help ---- (mirrors the top-bar Help menu, plus the wiki/YouTube links)
add("help_keyboard_shortcuts", _u8L("Keyboard Shortcuts"), _u8L("Help"), [](const std::string&) {
add("help_keyboard_shortcuts", L("Keyboard Shortcuts"), L("Help"), [](const std::string&) {
wxGetApp().keyboard_shortcuts(ShortcutContext::Global);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("help_setup_wizard", _u8L("Setup Wizard"), _u8L("Help"), [](const std::string&) {
add("help_setup_wizard", L("Setup Wizard"), L("Help"), [](const std::string&) {
wxGetApp().ShowUserGuide();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("help_open_config_folder", _u8L("Show Configuration Folder"), _u8L("Help"), "open_project", [](const std::string&) {
add_with_icon("help_open_config_folder", L("Show Configuration Folder"), L("Help"), "open_project", [](const std::string&) {
Slic3r::GUI::desktop_open_datadir_folder();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("help_troubleshoot", _u8L("Troubleshoot Center"), _u8L("Help"), [](const std::string&) {
add("help_troubleshoot", L("Troubleshoot Center"), L("Help"), [](const std::string&) {
wxGetApp().troubleshoot();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("help_network_test", _u8L("Open Network Test"), _u8L("Help"), [](const std::string&) {
add("help_network_test", L("Open Network Test"), L("Help"), [](const std::string&) {
NetworkTestDialog dlg(wxGetApp().mainframe);
dlg.ShowModal();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("help_benchmark_3d_scene", _u8L("Benchmark 3D Scene"), _u8L("Help"), [](const std::string&) {
add("help_benchmark_3d_scene", L("Benchmark 3D Scene"), L("Help"), [](const std::string&) {
run_scene_benchmark();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("help_tip_of_the_day", _u8L("Show Tip of the Day"), _u8L("Help"), "info", [](const std::string&) {
add_with_icon("help_tip_of_the_day", L("Show Tip of the Day"), L("Help"), "info", [](const std::string&) {
if (Plater* plater = wxGetApp().plater()) {
plater->get_dailytips()->open();
if (GLCanvas3D* canvas = plater->get_current_canvas3D())
@@ -605,37 +610,37 @@ std::vector<NativeCommand> build_command_catalog()
}
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("help_check_updates", _u8L("Check for Updates"), _u8L("Help"), "refresh", [](const std::string&) {
add_with_icon("help_check_updates", L("Check for Updates"), L("Help"), "refresh", [](const std::string&) {
wxGetApp().check_new_version_sf(true, 1);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("help_about", _u8L("About OrcaSlicer"), _u8L("Help"), "OrcaSlicer_gradient_circle", [](const std::string&) {
add_with_icon("help_about", L("About OrcaSlicer"), L("Help"), "OrcaSlicer_gradient_circle", [](const std::string&) {
Slic3r::GUI::about();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("open_wiki", _u8L("Open Wiki"), _u8L("Help"), "link_wiki_img", [](const std::string&) {
add_with_icon("open_wiki", L("Open Wiki"), L("Help"), "link_wiki_img", [](const std::string&) {
wxLaunchDefaultBrowser("https://www.orcaslicer.com/wiki/", wxBROWSER_NEW_WINDOW);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("open_youtube", _u8L("Open YouTube Channel"), _u8L("Help"), [](const std::string&) {
add("open_youtube", L("Open YouTube Channel"), L("Help"), [](const std::string&) {
wxLaunchDefaultBrowser("https://www.youtube.com/@OfficialOrcaSlicer/videos", wxBROWSER_NEW_WINDOW);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
// ---- Plugins ----
add("open_plugins", _u8L("Open Plugins"), _u8L("Plugins"), [](const std::string&) {
add("open_plugins", L("Open Plugins"), L("Plugins"), [](const std::string&) {
wxGetApp().open_plugins_dialog();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("refresh_plugins", _u8L("Refresh Plugins"), _u8L("Plugins"), [](const std::string&) {
add("refresh_plugins", L("Refresh Plugins"), L("Plugins"), [](const std::string&) {
wxGetApp().refresh_plugins();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("install_plugin", _u8L("Install Plugin"), _u8L("Plugins"), [](const std::string&) {
add("install_plugin", L("Install Plugin"), L("Plugins"), [](const std::string&) {
open_plugin_hub();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("install_local_plugin", _u8L("Install Local Plugin"), _u8L("Plugins"), [](const std::string&) {
add("install_local_plugin", L("Install Local Plugin"), L("Plugins"), [](const std::string&) {
wxGetApp().install_local_plugin();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
@@ -643,24 +648,12 @@ std::vector<NativeCommand> build_command_catalog()
return out;
}
std::vector<NativeCommand>& catalog_storage()
{
static std::vector<NativeCommand> commands = build_command_catalog();
return commands;
}
} // namespace
const std::vector<NativeCommand>& NativeCommands::catalog()
{
return catalog_storage();
}
void NativeCommands::rebuild_catalog()
{
// build_command_catalog() re-runs _u8L under the current locale, so replacing the storage
// refreshes every translated title/group after a language switch.
catalog_storage() = build_command_catalog();
static const std::vector<NativeCommand> commands = build_command_catalog();
return commands;
}
std::unique_ptr<AppAction> NativeCommands::make_action(const NativeCommand& command)
+8 -10
View File
@@ -16,27 +16,25 @@ namespace Slic3r { namespace GUI {
struct NativeCommand
{
std::string key;
std::string title;
std::string group;
std::string title; // untranslated English msgid; make_action() translates it for display
std::string group; // likewise
std::string input; // "percent"/"tab" or "" for immediate run
std::string icon; // SVG base name, or "" to render a blank tile
std::function<AppActionRunResult(const std::string& param)> runner;
};
namespace NativeCommands {
// The full built-in command catalog. Built on first use and reused; call rebuild_catalog() after a
// live UI language switch so the translated titles/groups match the new locale. UI thread only.
// The full built-in command catalog. Built on first use and reused; it holds no translated text, so
// it stays valid across a UI language switch. UI thread only.
const std::vector<NativeCommand>& catalog();
// Rebuilds the catalog in the current locale. UI thread only.
void rebuild_catalog();
// Dispatches `key` to its runner (unknown keys return a quiet Info). UI thread only.
AppActionRunResult run(const std::string& key, const std::string& param = {});
// Materialises one catalog entry as a runnable AppAction. Keeps the catalog's identity,
// presentation and behaviour as the single source of truth; ActionRegistry only stores and
// dispatches the result. UI thread only.
// Materialises one catalog entry as a runnable AppAction, titled in the current UI language with the
// English title/group kept as search aliases. Keeps the catalog's identity, presentation and
// behaviour as the single source of truth; ActionRegistry only stores and dispatches the result.
// UI thread only.
std::unique_ptr<AppAction> make_action(const NativeCommand& command);
} // namespace NativeCommands
+3 -2
View File
@@ -275,7 +275,8 @@ void OptionsGroup::append_line(const Line& line)
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));
index.set_line_label(opt.opt_id, type, Search::compose_display_label(line.label, leaf, multi),
Search::compose_display_label(line.label_en, from_u8(leaf_src), multi));
}
}
@@ -650,7 +651,7 @@ Line OptionsGroup::create_single_option_line(const Option& option, const std::st
{
wxString tooltip = _(get_formatted_tooltip_text(option.opt, option.opt_id));
Line retval{_(option.opt.label), tooltip};
Line retval{from_u8(option.opt.label), tooltip};
retval.label_path = path;
retval.append_option(option);
+7 -2
View File
@@ -61,6 +61,7 @@ class Line : public UndoValueUIManager
bool m_is_separator{ false };
public:
wxString label;
wxString label_en; // untranslated English of `label`, searchable by the Speed Dial in any UI language
wxString label_tooltip;
std::string label_path;
bool undo_to_sys{false}; // BBS: object config
@@ -86,12 +87,16 @@ public:
void append_widget(const widget_t widget) {
m_extra_widgets.push_back(widget);
}
// `label` and `tooltip` are the untranslated English msgids; they are translated for display.
Line(wxString label, wxString tooltip) :
label(_(label)), label_tooltip(_(tooltip)) {}
label(_(label)), label_en(label), label_tooltip(_(tooltip)) {}
Line() : m_is_separator(true) {}
// `new_label` is the untranslated English msgid (an L("...") string), like the ctor's label:
// it is translated for display and kept as label_en.
void set_label(const wxString& new_label) {
label = new_label;
label = _(new_label);
label_en = new_label;
if (label_widget != nullptr) {
label_widget->SetLabel(label + (label.IsEmpty() ? "" : ": "));
label_widget->Refresh();
+5 -3
View File
@@ -63,7 +63,7 @@ static Option make_option(const std::string &key, Preset::Type type, const wxStr
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)};
into_u8(gc.line_label), into_u8(gc.line_label_en)};
return option;
}
@@ -250,11 +250,13 @@ 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)
void SettingsIndex::set_line_label(const std::string &opt_key, Preset::Type type, const wxString &label, const wxString &label_en)
{
if (label.IsEmpty())
return;
m_groups_and_categories[get_key(opt_key, type)].line_label = label;
GroupAndCategory &gc = m_groups_and_categories[get_key(opt_key, type)];
gc.line_label = label;
gc.line_label_en = label_en;
}
} // namespace Search
+9 -6
View File
@@ -28,12 +28,14 @@ struct GroupAndCategory
wxString group;
wxString category;
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
wxString line_label; // label the settings row actually draws (Line::label), or empty
wxString line_label_en; // untranslated English of 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.
// options (e.g. "Cool Plate \u2013 First layer"). Pure; both inputs are in the same language (the
// localized title, or its English counterpart).
inline wxString compose_display_label(const wxString& line_label, const wxString& leaf_label, bool multi)
{
if (line_label.empty())
@@ -73,6 +75,7 @@ struct Option
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 display_label_en; // untranslated English of display_label, or empty
std::string opt_key() const;
};
@@ -98,9 +101,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);
// Record the label the option's row draws and its untranslated English, 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 wxString &label_en);
const std::vector<Option> &options() const { return m_options; }
const std::vector<Option> &all_options() const { return m_all_modes; }
+9 -8
View File
@@ -817,23 +817,24 @@ Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::str
// Names of categories is save in English always. We translate them only for UI.
// But category "Extruder n" can't be translated regularly (using _()), so
// just for this category we should splite the title and translate "Extruder" word separately
wxString Tab::translate_category(const wxString& title, Preset::Type preset_type)
wxString Tab::translate_category(const wxString& title, Preset::Type preset_type, bool translate)
{
auto tr = [translate](const wxString& s) { return translate ? _(s) : s; };
if (preset_type == Preset::TYPE_PRINTER && title.Contains("Extruder ")) {
auto preset = wxGetApp().preset_bundle;
if (preset && preset->is_bbl_vendor()) {
if (title == "Extruder 1") return _("Left Extruder");
if (title == "Extruder 2") return _("Right Extruder");
if (title == "Extruder 1") return tr(L("Left Extruder"));
if (title == "Extruder 2") return tr(L("Right Extruder"));
}
return _("Extruder") + title.SubString(8, title.Last());
return tr(L("Extruder")) + title.SubString(8, title.Last());
}
// Orca: one "Extruder" page serves all extruders; name it "Extruders" when there are several.
if (preset_type == Preset::TYPE_PRINTER && title == "Extruder") {
auto preset = wxGetApp().preset_bundle;
if (preset && preset->get_printer_extruder_count() > 1)
return _("Extruders");
return tr(L("Extruders"));
}
return _(title);
return tr(title);
}
void Tab::OnActivate()
@@ -1865,14 +1866,14 @@ Tab::SettingRowState Tab::setting_row_state(const std::string &opt_id) const
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};
return {true, line.label, line.label_en, line.get_options().size() > 1};
found = true;
}
}
}
}
// Never registered on a page -> visible, but with no row label to contribute.
return {!found, wxString(), false};
return {!found, wxString(), wxString(), false};
}
// To be called by custom widgets, load a value into a config,
+8 -1
View File
@@ -405,7 +405,11 @@ public:
void on_roll_back_value(const bool to_sys = false);
PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false);
static wxString translate_category(const wxString& title, Preset::Type preset_type);
// The name a category is displayed under. A few printer pages are renamed for display (BBL
// "Extruder 1/2" -> "Left/Right Extruder", the shared "Extruder" page -> "Extruders" when there are
// several). With `translate` false the same name is returned untranslated, i.e. as an English UI
// displays it.
static wxString translate_category(const wxString& title, Preset::Type preset_type, bool translate = true);
virtual void OnActivate();
virtual void on_preset_loaded() {}
@@ -432,6 +436,8 @@ public:
Field* get_field(const t_config_option_key &opt_key, Page** selected_page, int opt_index = -1);
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
// Renames the row that owns the option. `label` is the untranslated English msgid (an L("...")
// string); it is translated for display and kept as the row's English search alias.
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.
@@ -439,6 +445,7 @@ public:
{
bool visible{true}; // false when ConfigManipulation hides the row
wxString label; // Line::label the row draws (may change at runtime)
wxString label_en; // Line::label_en, the untranslated English of label
bool multi{false}; // row packs several options, so label is precomposed
};
SettingRowState setting_row_state(const std::string &opt_id) const;