From 45cd64b0306bc80d517ed8b78722cd51e4642b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=85=E6=88=8E=E6=B0=8F?= Date: Sun, 12 Feb 2023 00:50:18 +0800 Subject: [PATCH] feat(switches): abbreviate state labels YAML config: a switch can have optional string array `abbrev` to define short state labels which do not have to take the first character of the full state labels. (switch_translator): folded options now make use of the short labels defined in the `switches` configuration. (rime_api): add function `get_state_label_abbreviated`, which returns `RimeStringSlice` type since the short label can be the first character of the full label string. --- src/rime/gear/switch_translator.cc | 159 +++++++++++++---------------- src/rime/gear/switch_translator.h | 3 +- src/rime/switcher.cc | 6 +- src/rime/switches.cc | 59 ++++++++--- src/rime/switches.h | 17 ++- src/rime_api.cc | 23 +++-- src/rime_api.h | 11 ++ 7 files changed, 163 insertions(+), 115 deletions(-) diff --git a/src/rime/gear/switch_translator.cc b/src/rime/gear/switch_translator.cc index d5c7b81ce..c29cb2952 100644 --- a/src/rime/gear/switch_translator.cc +++ b/src/rime/gear/switch_translator.cc @@ -4,13 +4,14 @@ // // 2013-05-26 GONG Chen // -#include +#include #include #include #include #include #include #include +#include #include #include @@ -20,20 +21,30 @@ static const char* kRadioSelected = " \xe2\x9c\x93"; // U+2713 CHECK MARK namespace rime { +using SwitchOption = Switches::SwitchOption; + +inline static string get_state_label(const SwitchOption& option, + size_t state_index, + bool abbreviate = false) { + return string(Switches::GetStateLabel(option.the_switch, + state_index, + abbreviate)); +} + class Switch : public SimpleCandidate, public SwitcherCommand { public: - Switch(const string& current_state_label, - const string& next_state_label, - const string& option_name, + Switch(const SwitchOption& option, bool current_state, bool auto_save) : SimpleCandidate("switch", 0, 0, - current_state_label, kRightArrow + next_state_label), - SwitcherCommand(option_name), + get_state_label(option, current_state), + kRightArrow + + get_state_label(option, 1 - current_state)), + SwitcherCommand(option.option_name), target_state_(!current_state), auto_save_(auto_save) { } - virtual void Apply(Switcher* switcher); + void Apply(Switcher* switcher) override; protected: bool target_state_; @@ -59,8 +70,8 @@ class RadioGroup : public std::enable_shared_from_this { RadioGroup(Context* context, Switcher* switcher) : context_(context), switcher_(switcher) { } - an CreateOption(const string& state_label, - const string& option_name); + an CreateOption(const SwitchOption& option, + size_t option_index); void SelectOption(RadioOption* option); RadioOption* GetSelectedOption() const; @@ -79,7 +90,7 @@ class RadioOption : public SimpleCandidate, public SwitcherCommand { SwitcherCommand(option_name), group_(group) { } - virtual void Apply(Switcher* switcher); + void Apply(Switcher* switcher) override; void UpdateState(bool selected); bool selected() const { return selected_; } @@ -99,13 +110,12 @@ void RadioOption::UpdateState(bool selected) { } an -RadioGroup::CreateOption(const string& state_label, - const string& option_name) { - auto option = New(shared_from_this(), - state_label, - option_name); - options_.push_back(option.get()); - return option; +RadioGroup::CreateOption(const SwitchOption& option, size_t option_index) { + auto radio_option = New(shared_from_this(), + get_state_label(option, option_index), + option.option_name); + options_.push_back(radio_option.get()); + return radio_option; } void RadioGroup::SelectOption(RadioOption* option) { @@ -142,14 +152,13 @@ class FoldedOptions : public SimpleCandidate, public SwitcherCommand { SwitcherCommand("_fold_options") { LoadConfig(config); } - virtual void Apply(Switcher* switcher); - void Append(const string& label) { - labels_.push_back(label); - } + void Apply(Switcher* switcher) override; + void Append(const SwitchOption& option, size_t state_index); + void Finish(); + size_t size() const { return labels_.size(); } - void Finish(); private: void LoadConfig(Config* config); @@ -178,30 +187,13 @@ void FoldedOptions::Apply(Switcher* switcher) { switcher->RefreshMenu(); } -static string FirstCharOf(const string& str) { - if (str.empty()) { - return str; - } - string first_char; - const char* start = str.c_str(); - const char* end = start; - utf8::unchecked::next(end); - return string(start, end - start); +void FoldedOptions::Append(const SwitchOption& option, size_t state_index) { + labels_.push_back( + get_state_label(option, state_index, abbreviate_options_)); } void FoldedOptions::Finish() { - text_ = prefix_; - bool first = true; - for (const auto& label : labels_) { - if (first) { - first = false; - } - else { - text_ += separator_; - } - text_ += abbreviate_options_ ? FirstCharOf(label) : label; - } - text_ += suffix_; + text_ = prefix_ + boost::algorithm::join(labels_, separator_) + suffix_; } class SwitchTranslation : public FifoTranslation { @@ -220,54 +212,49 @@ void SwitchTranslation::LoadSwitches(Switcher* switcher) { Config* config = engine->schema()->config(); if (!config) return; - auto switches = config->GetList("switches"); - if (!switches) - return; Context* context = engine->context(); - for (size_t i = 0; i < switches->size(); ++i) { - auto item = As(switches->GetAt(i)); - if (!item) - continue; - auto states = As(item->Get("states")); - if (!states) - continue; - if (auto option_name = item->GetValue("name")) { - // toggle - if (states->size() != 2) - continue; - bool current_state = context->get_option(option_name->str()); - Append(New( - states->GetValueAt(current_state)->str(), - states->GetValueAt(1 - current_state)->str(), - option_name->str(), - current_state, - switcher->IsAutoSave(option_name->str()))); - } - else if (auto options = As(item->Get("options"))) { - // radio - if (states->size() < 2) - continue; - if (states->size() != options->size()) - continue; - auto group = New(context, switcher); - for (size_t i = 0; i < options->size(); ++i) { - auto option_name = options->GetValueAt(i); - auto state_label = states->GetValueAt(i); - if (!option_name || !state_label) - continue; - Append(group->CreateOption(state_label->str(), option_name->str())); + vector> groups; + Switches switches(config); + switches.FindOption( + [this, switcher, context, &groups] + (Switches::SwitchOption option) -> Switches::FindResult { + if (option.type == Switches::kToggleOption) { + bool current_state = context->get_option(option.option_name); + Append( + New(option, + current_state, + switcher->IsAutoSave(option.option_name))); + } else if (option.type == Switches::kRadioGroup) { + an group; + if (option.option_index == 0) { + group = New(context, switcher); + groups.push_back(group); + } else { + group = groups.back(); + } + Append( + group->CreateOption(option, option.option_index)); } - group->SelectOption(group->GetSelectedOption()); - } + return Switches::kContinue; + }); + for (auto& group : groups) { + group->SelectOption(group->GetSelectedOption()); } if (switcher->context()->get_option("_fold_options")) { auto folded_options = New(switcher->schema()->config()); - for (auto x : candies_) { - if (Is(x) || - (Is(x) && As(x)->selected())) { - folded_options->Append(x->text()); - } - } + switches.FindOption( + [context, &folded_options] + (Switches::SwitchOption option) -> Switches::FindResult { + bool current_state = context->get_option(option.option_name); + if (option.type == Switches::kToggleOption) { + folded_options->Append(option, current_state); + } else if (option.type == Switches::kRadioGroup) { + if (current_state) { + folded_options->Append(option, option.option_index); + } + } + return Switches::kContinue; + }); if (folded_options->size() > 1) { folded_options->Finish(); candies_.clear(); diff --git a/src/rime/gear/switch_translator.h b/src/rime/gear/switch_translator.h index b259deaff..8185a06d2 100644 --- a/src/rime/gear/switch_translator.h +++ b/src/rime/gear/switch_translator.h @@ -15,8 +15,7 @@ class SwitchTranslator : public Translator { public: SwitchTranslator(const Ticket& ticket); - virtual an Query(const string& input, - const Segment& segment); + an Query(const string& input, const Segment& segment) override; }; } // namespace rime diff --git a/src/rime/switcher.cc b/src/rime/switcher.cc index 6659ed33b..15a35b41a 100644 --- a/src/rime/switcher.cc +++ b/src/rime/switcher.cc @@ -117,14 +117,14 @@ void Switcher::HighlightNextSchema() { schema: wubi_pinyin - case: [mode/wubi] schema: wubi86 - - case: [mode/default] + - case: [mode/default] schema: pinyin - mode: + mode: wubi: false wubi_pinyin: false default: true - ``` + ``` */ static an ParseSchemaListEntry(Config* config, diff --git a/src/rime/switches.cc b/src/rime/switches.cc index e6ab00795..d35692c4a 100644 --- a/src/rime/switches.cc +++ b/src/rime/switches.cc @@ -1,3 +1,4 @@ +#include #include #include @@ -26,7 +27,8 @@ Switches::SwitchOption Switches::FindOptionFromConfigItem( if (callback(option) == kFound) return option; } else if (options.IsList()) { - for (size_t option_index = 0; option_index < options.size(); + for (size_t option_index = 0; + option_index < options.size(); ++option_index) { SwitchOption option{ the_switch, @@ -137,32 +139,61 @@ Switches::SwitchOption Switches::FindRadioGroupOption( return {}; } -an Switches::GetStateLabel(an the_switch, - size_t state_index) { +inline static size_t first_unicode_byte_length(const string& str) { + if (str.empty()) { + return 0; + } + const char* start = str.c_str(); + const char* end = start; + utf8::unchecked::next(end); + return end - start; +} + +StringSlice Switches::GetStateLabel(an the_switch, + size_t state_index, + bool abbreviated) { if (!the_switch) - return nullptr; + return {nullptr, 0}; auto states = As(the_switch->Get("states")); - if (!states) - return nullptr; - return states->GetValueAt(state_index); + if (!states || states->size() <= state_index) { + return {nullptr, 0}; + } + if (abbreviated) { + auto abbrev = As(the_switch->Get("abbrev")); + if (abbrev && abbrev->size() > state_index) { + auto value = abbrev->GetValueAt(state_index); + return {value->str().c_str(), value->str().length()}; + } else { + auto value = states->GetValueAt(state_index); + return {value->str().c_str(), first_unicode_byte_length(value->str())}; + } + } else { + auto value = states->GetValueAt(state_index); + return {value->str().c_str(), value->str().length()}; + } } -an Switches::GetStateLabel(const string& option_name, int state) { +StringSlice Switches::GetStateLabel(const string& option_name, + int state, + bool abbreviated) { auto the_option = OptionByName(option_name); - if (!the_option.found()) - return nullptr; + if (!the_option.found()) { + return {nullptr, 0}; + } if (the_option.type == kToggleOption) { size_t state_index = static_cast(state); - return GetStateLabel(the_option.the_switch, state_index); + return GetStateLabel(the_option.the_switch, state_index, abbreviated); } if (the_option.type == kRadioGroup) { // if the query is a deselected option among the radio group, do not // display its state label; only show the selected option. return state - ? GetStateLabel(the_option.the_switch, the_option.option_index) - : nullptr; + ? GetStateLabel(the_option.the_switch, + the_option.option_index, + abbreviated) + : StringSlice{nullptr, 0}; } - return nullptr; + return {nullptr, 0}; } } // namespace rime diff --git a/src/rime/switches.h b/src/rime/switches.h index 8b1dda5df..792775e40 100644 --- a/src/rime/switches.h +++ b/src/rime/switches.h @@ -10,6 +10,15 @@ class ConfigItemRef; class ConfigMap; class ConfigValue; +struct StringSlice { + const char* str; + size_t length; + + operator string() const { + return str && length ? string(str, length) : string(); + } +}; + class Switches { public: explicit Switches(Config* config) : config_(config) {} @@ -56,10 +65,12 @@ class Switches { an the_switch, function callback); - static an GetStateLabel( - an the_switch, size_t state_index); + static StringSlice GetStateLabel( + an the_switch, size_t state_index, bool abbreviated); - an GetStateLabel(const string& option_name, int state); + StringSlice GetStateLabel(const string& option_name, + int state, + bool abbreviated); private: SwitchOption FindOptionFromConfigItem( diff --git a/src/rime_api.cc b/src/rime_api.cc index 211b9f80f..10c99f58f 100644 --- a/src/rime_api.cc +++ b/src/rime_api.cc @@ -1002,18 +1002,26 @@ void RimeSetCaretPos(RimeSessionId session_id, size_t caret_pos) { return ctx->set_caret_pos(caret_pos); } -const char* RimeGetStateLabel(RimeSessionId session_id, - const char* option_name, - Bool state) { +RimeStringSlice RimeGetStateLabelAbbreviated(RimeSessionId session_id, + const char* option_name, + Bool state, + Bool abbreviated) { an session(Service::instance().GetSession(session_id)); if (!session) - return nullptr; + return {nullptr, 0}; Config* config = session->schema()->config(); if (!config) - return nullptr; + return {nullptr, 0}; Switches switches(config); - an label = switches.GetStateLabel(option_name, state); - return label ? label->str().c_str() : nullptr; + StringSlice label = + switches.GetStateLabel(option_name, state, abbreviated); + return {label.str, label.length}; +} + +const char* RimeGetStateLabel(RimeSessionId session_id, + const char* option_name, + Bool state) { + return RimeGetStateLabelAbbreviated(session_id, option_name, state, False).str; } RIME_API RimeApi* rime_get_api() { @@ -1108,6 +1116,7 @@ RIME_API RimeApi* rime_get_api() { s_api.get_state_label = &RimeGetStateLabel; s_api.delete_candidate = &RimeDeleteCandidate; s_api.delete_candidate_on_current_page = &RimeDeleteCandidateOnCurrentPage; + s_api.get_state_label_abbreviated = &RimeGetStateLabelAbbreviated; } return &s_api; } diff --git a/src/rime_api.h b/src/rime_api.h index 24c4404e9..671a1c160 100644 --- a/src/rime_api.h +++ b/src/rime_api.h @@ -193,6 +193,11 @@ typedef void (*RimeNotificationHandler)(void* context_object, const char* message_type, const char* message_value); +typedef struct rime_string_slice_t { + const char* str; + size_t length; +} RimeStringSlice; + // Setup /*! @@ -552,10 +557,16 @@ typedef struct rime_api_t { void (*status_proto)(RimeSessionId session_id, RIME_PROTO_BUILDER* status_builder); const char* (*get_state_label)(RimeSessionId session_id, const char *option_name, Bool state); + //! delete a candidate at the given index in candidate list. Bool (*delete_candidate)(RimeSessionId session_id, size_t index); //! delete a candidate from current page. Bool (*delete_candidate_on_current_page)(RimeSessionId session_id, size_t index); + + RimeStringSlice (*get_state_label_abbreviated)(RimeSessionId session_id, + const char *option_name, + Bool state, + Bool abbreviated); } RimeApi; //! API entry