Skip to content

Commit

Permalink
feat(switches): abbreviate state labels
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lotem committed Feb 11, 2023
1 parent 477c010 commit 45cd64b
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 115 deletions.
159 changes: 73 additions & 86 deletions src/rime/gear/switch_translator.cc
Expand Up @@ -4,13 +4,14 @@
//
// 2013-05-26 GONG Chen <chen.sst@gmail.com>
//
#include <utf8.h>
#include <boost/algorithm/string.hpp>
#include <rime/candidate.h>
#include <rime/common.h>
#include <rime/config.h>
#include <rime/context.h>
#include <rime/schema.h>
#include <rime/switcher.h>
#include <rime/switches.h>
#include <rime/translation.h>
#include <rime/gear/switch_translator.h>

Expand All @@ -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_;
Expand All @@ -59,8 +70,8 @@ class RadioGroup : public std::enable_shared_from_this<RadioGroup> {
RadioGroup(Context* context, Switcher* switcher)
: context_(context), switcher_(switcher) {
}
an<RadioOption> CreateOption(const string& state_label,
const string& option_name);
an<RadioOption> CreateOption(const SwitchOption& option,
size_t option_index);
void SelectOption(RadioOption* option);
RadioOption* GetSelectedOption() const;

Expand All @@ -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_; }

Expand All @@ -99,13 +110,12 @@ void RadioOption::UpdateState(bool selected) {
}

an<RadioOption>
RadioGroup::CreateOption(const string& state_label,
const string& option_name) {
auto option = New<RadioOption>(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<RadioOption>(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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand All @@ -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<ConfigMap>(switches->GetAt(i));
if (!item)
continue;
auto states = As<ConfigList>(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<Switch>(
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<ConfigList>(item->Get("options"))) {
// radio
if (states->size() < 2)
continue;
if (states->size() != options->size())
continue;
auto group = New<RadioGroup>(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<an<RadioGroup>> 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<Switch>(option,
current_state,
switcher->IsAutoSave(option.option_name)));
} else if (option.type == Switches::kRadioGroup) {
an<RadioGroup> group;
if (option.option_index == 0) {
group = New<RadioGroup>(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<FoldedOptions>(switcher->schema()->config());
for (auto x : candies_) {
if (Is<Switch>(x) ||
(Is<RadioOption>(x) && As<RadioOption>(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();
Expand Down
3 changes: 1 addition & 2 deletions src/rime/gear/switch_translator.h
Expand Up @@ -15,8 +15,7 @@ class SwitchTranslator : public Translator {
public:
SwitchTranslator(const Ticket& ticket);

virtual an<Translation> Query(const string& input,
const Segment& segment);
an<Translation> Query(const string& input, const Segment& segment) override;
};

} // namespace rime
Expand Down
6 changes: 3 additions & 3 deletions src/rime/switcher.cc
Expand Up @@ -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<ConfigValue> ParseSchemaListEntry(Config* config,
Expand Down
59 changes: 45 additions & 14 deletions src/rime/switches.cc
@@ -1,3 +1,4 @@
#include <utf8.h>
#include <rime/config.h>
#include <rime/switches.h>

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -137,32 +139,61 @@ Switches::SwitchOption Switches::FindRadioGroupOption(
return {};
}

an<ConfigValue> Switches::GetStateLabel(an<ConfigMap> 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<ConfigMap> the_switch,
size_t state_index,
bool abbreviated) {
if (!the_switch)
return nullptr;
return {nullptr, 0};
auto states = As<ConfigList>(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<ConfigList>(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<ConfigValue> 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<size_t>(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
17 changes: 14 additions & 3 deletions src/rime/switches.h
Expand Up @@ -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) {}
Expand Down Expand Up @@ -56,10 +65,12 @@ class Switches {
an<ConfigMap> the_switch,
function<FindResult (SwitchOption option)> callback);

static an<ConfigValue> GetStateLabel(
an<ConfigMap> the_switch, size_t state_index);
static StringSlice GetStateLabel(
an<ConfigMap> the_switch, size_t state_index, bool abbreviated);

an<ConfigValue> GetStateLabel(const string& option_name, int state);
StringSlice GetStateLabel(const string& option_name,
int state,
bool abbreviated);

private:
SwitchOption FindOptionFromConfigItem(
Expand Down

0 comments on commit 45cd64b

Please sign in to comment.