Skip to content

Commit 45cd64b

Browse files
committed
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.
1 parent 477c010 commit 45cd64b

File tree

7 files changed

+163
-115
lines changed

7 files changed

+163
-115
lines changed

src/rime/gear/switch_translator.cc

+73-86
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
//
55
// 2013-05-26 GONG Chen <[email protected]>
66
//
7-
#include <utf8.h>
7+
#include <boost/algorithm/string.hpp>
88
#include <rime/candidate.h>
99
#include <rime/common.h>
1010
#include <rime/config.h>
1111
#include <rime/context.h>
1212
#include <rime/schema.h>
1313
#include <rime/switcher.h>
14+
#include <rime/switches.h>
1415
#include <rime/translation.h>
1516
#include <rime/gear/switch_translator.h>
1617

@@ -20,20 +21,30 @@ static const char* kRadioSelected = " \xe2\x9c\x93"; // U+2713 CHECK MARK
2021

2122
namespace rime {
2223

24+
using SwitchOption = Switches::SwitchOption;
25+
26+
inline static string get_state_label(const SwitchOption& option,
27+
size_t state_index,
28+
bool abbreviate = false) {
29+
return string(Switches::GetStateLabel(option.the_switch,
30+
state_index,
31+
abbreviate));
32+
}
33+
2334
class Switch : public SimpleCandidate, public SwitcherCommand {
2435
public:
25-
Switch(const string& current_state_label,
26-
const string& next_state_label,
27-
const string& option_name,
36+
Switch(const SwitchOption& option,
2837
bool current_state,
2938
bool auto_save)
3039
: SimpleCandidate("switch", 0, 0,
31-
current_state_label, kRightArrow + next_state_label),
32-
SwitcherCommand(option_name),
40+
get_state_label(option, current_state),
41+
kRightArrow +
42+
get_state_label(option, 1 - current_state)),
43+
SwitcherCommand(option.option_name),
3344
target_state_(!current_state),
3445
auto_save_(auto_save) {
3546
}
36-
virtual void Apply(Switcher* switcher);
47+
void Apply(Switcher* switcher) override;
3748

3849
protected:
3950
bool target_state_;
@@ -59,8 +70,8 @@ class RadioGroup : public std::enable_shared_from_this<RadioGroup> {
5970
RadioGroup(Context* context, Switcher* switcher)
6071
: context_(context), switcher_(switcher) {
6172
}
62-
an<RadioOption> CreateOption(const string& state_label,
63-
const string& option_name);
73+
an<RadioOption> CreateOption(const SwitchOption& option,
74+
size_t option_index);
6475
void SelectOption(RadioOption* option);
6576
RadioOption* GetSelectedOption() const;
6677

@@ -79,7 +90,7 @@ class RadioOption : public SimpleCandidate, public SwitcherCommand {
7990
SwitcherCommand(option_name),
8091
group_(group) {
8192
}
82-
virtual void Apply(Switcher* switcher);
93+
void Apply(Switcher* switcher) override;
8394
void UpdateState(bool selected);
8495
bool selected() const { return selected_; }
8596

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

101112
an<RadioOption>
102-
RadioGroup::CreateOption(const string& state_label,
103-
const string& option_name) {
104-
auto option = New<RadioOption>(shared_from_this(),
105-
state_label,
106-
option_name);
107-
options_.push_back(option.get());
108-
return option;
113+
RadioGroup::CreateOption(const SwitchOption& option, size_t option_index) {
114+
auto radio_option = New<RadioOption>(shared_from_this(),
115+
get_state_label(option, option_index),
116+
option.option_name);
117+
options_.push_back(radio_option.get());
118+
return radio_option;
109119
}
110120

111121
void RadioGroup::SelectOption(RadioOption* option) {
@@ -142,14 +152,13 @@ class FoldedOptions : public SimpleCandidate, public SwitcherCommand {
142152
SwitcherCommand("_fold_options") {
143153
LoadConfig(config);
144154
}
145-
virtual void Apply(Switcher* switcher);
146-
void Append(const string& label) {
147-
labels_.push_back(label);
148-
}
155+
void Apply(Switcher* switcher) override;
156+
void Append(const SwitchOption& option, size_t state_index);
157+
void Finish();
158+
149159
size_t size() const {
150160
return labels_.size();
151161
}
152-
void Finish();
153162

154163
private:
155164
void LoadConfig(Config* config);
@@ -178,30 +187,13 @@ void FoldedOptions::Apply(Switcher* switcher) {
178187
switcher->RefreshMenu();
179188
}
180189

181-
static string FirstCharOf(const string& str) {
182-
if (str.empty()) {
183-
return str;
184-
}
185-
string first_char;
186-
const char* start = str.c_str();
187-
const char* end = start;
188-
utf8::unchecked::next(end);
189-
return string(start, end - start);
190+
void FoldedOptions::Append(const SwitchOption& option, size_t state_index) {
191+
labels_.push_back(
192+
get_state_label(option, state_index, abbreviate_options_));
190193
}
191194

192195
void FoldedOptions::Finish() {
193-
text_ = prefix_;
194-
bool first = true;
195-
for (const auto& label : labels_) {
196-
if (first) {
197-
first = false;
198-
}
199-
else {
200-
text_ += separator_;
201-
}
202-
text_ += abbreviate_options_ ? FirstCharOf(label) : label;
203-
}
204-
text_ += suffix_;
196+
text_ = prefix_ + boost::algorithm::join(labels_, separator_) + suffix_;
205197
}
206198

207199
class SwitchTranslation : public FifoTranslation {
@@ -220,54 +212,49 @@ void SwitchTranslation::LoadSwitches(Switcher* switcher) {
220212
Config* config = engine->schema()->config();
221213
if (!config)
222214
return;
223-
auto switches = config->GetList("switches");
224-
if (!switches)
225-
return;
226215
Context* context = engine->context();
227-
for (size_t i = 0; i < switches->size(); ++i) {
228-
auto item = As<ConfigMap>(switches->GetAt(i));
229-
if (!item)
230-
continue;
231-
auto states = As<ConfigList>(item->Get("states"));
232-
if (!states)
233-
continue;
234-
if (auto option_name = item->GetValue("name")) {
235-
// toggle
236-
if (states->size() != 2)
237-
continue;
238-
bool current_state = context->get_option(option_name->str());
239-
Append(New<Switch>(
240-
states->GetValueAt(current_state)->str(),
241-
states->GetValueAt(1 - current_state)->str(),
242-
option_name->str(),
243-
current_state,
244-
switcher->IsAutoSave(option_name->str())));
245-
}
246-
else if (auto options = As<ConfigList>(item->Get("options"))) {
247-
// radio
248-
if (states->size() < 2)
249-
continue;
250-
if (states->size() != options->size())
251-
continue;
252-
auto group = New<RadioGroup>(context, switcher);
253-
for (size_t i = 0; i < options->size(); ++i) {
254-
auto option_name = options->GetValueAt(i);
255-
auto state_label = states->GetValueAt(i);
256-
if (!option_name || !state_label)
257-
continue;
258-
Append(group->CreateOption(state_label->str(), option_name->str()));
216+
vector<an<RadioGroup>> groups;
217+
Switches switches(config);
218+
switches.FindOption(
219+
[this, switcher, context, &groups]
220+
(Switches::SwitchOption option) -> Switches::FindResult {
221+
if (option.type == Switches::kToggleOption) {
222+
bool current_state = context->get_option(option.option_name);
223+
Append(
224+
New<Switch>(option,
225+
current_state,
226+
switcher->IsAutoSave(option.option_name)));
227+
} else if (option.type == Switches::kRadioGroup) {
228+
an<RadioGroup> group;
229+
if (option.option_index == 0) {
230+
group = New<RadioGroup>(context, switcher);
231+
groups.push_back(group);
232+
} else {
233+
group = groups.back();
234+
}
235+
Append(
236+
group->CreateOption(option, option.option_index));
259237
}
260-
group->SelectOption(group->GetSelectedOption());
261-
}
238+
return Switches::kContinue;
239+
});
240+
for (auto& group : groups) {
241+
group->SelectOption(group->GetSelectedOption());
262242
}
263243
if (switcher->context()->get_option("_fold_options")) {
264244
auto folded_options = New<FoldedOptions>(switcher->schema()->config());
265-
for (auto x : candies_) {
266-
if (Is<Switch>(x) ||
267-
(Is<RadioOption>(x) && As<RadioOption>(x)->selected())) {
268-
folded_options->Append(x->text());
269-
}
270-
}
245+
switches.FindOption(
246+
[context, &folded_options]
247+
(Switches::SwitchOption option) -> Switches::FindResult {
248+
bool current_state = context->get_option(option.option_name);
249+
if (option.type == Switches::kToggleOption) {
250+
folded_options->Append(option, current_state);
251+
} else if (option.type == Switches::kRadioGroup) {
252+
if (current_state) {
253+
folded_options->Append(option, option.option_index);
254+
}
255+
}
256+
return Switches::kContinue;
257+
});
271258
if (folded_options->size() > 1) {
272259
folded_options->Finish();
273260
candies_.clear();

src/rime/gear/switch_translator.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ class SwitchTranslator : public Translator {
1515
public:
1616
SwitchTranslator(const Ticket& ticket);
1717

18-
virtual an<Translation> Query(const string& input,
19-
const Segment& segment);
18+
an<Translation> Query(const string& input, const Segment& segment) override;
2019
};
2120

2221
} // namespace rime

src/rime/switcher.cc

+3-3
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,14 @@ void Switcher::HighlightNextSchema() {
117117
schema: wubi_pinyin
118118
- case: [mode/wubi]
119119
schema: wubi86
120-
- case: [mode/default]
120+
- case: [mode/default]
121121
schema: pinyin
122122
123-
mode:
123+
mode:
124124
wubi: false
125125
wubi_pinyin: false
126126
default: true
127-
```
127+
```
128128
*/
129129

130130
static an<ConfigValue> ParseSchemaListEntry(Config* config,

src/rime/switches.cc

+45-14
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include <utf8.h>
12
#include <rime/config.h>
23
#include <rime/switches.h>
34

@@ -26,7 +27,8 @@ Switches::SwitchOption Switches::FindOptionFromConfigItem(
2627
if (callback(option) == kFound)
2728
return option;
2829
} else if (options.IsList()) {
29-
for (size_t option_index = 0; option_index < options.size();
30+
for (size_t option_index = 0;
31+
option_index < options.size();
3032
++option_index) {
3133
SwitchOption option{
3234
the_switch,
@@ -137,32 +139,61 @@ Switches::SwitchOption Switches::FindRadioGroupOption(
137139
return {};
138140
}
139141

140-
an<ConfigValue> Switches::GetStateLabel(an<ConfigMap> the_switch,
141-
size_t state_index) {
142+
inline static size_t first_unicode_byte_length(const string& str) {
143+
if (str.empty()) {
144+
return 0;
145+
}
146+
const char* start = str.c_str();
147+
const char* end = start;
148+
utf8::unchecked::next(end);
149+
return end - start;
150+
}
151+
152+
StringSlice Switches::GetStateLabel(an<ConfigMap> the_switch,
153+
size_t state_index,
154+
bool abbreviated) {
142155
if (!the_switch)
143-
return nullptr;
156+
return {nullptr, 0};
144157
auto states = As<ConfigList>(the_switch->Get("states"));
145-
if (!states)
146-
return nullptr;
147-
return states->GetValueAt(state_index);
158+
if (!states || states->size() <= state_index) {
159+
return {nullptr, 0};
160+
}
161+
if (abbreviated) {
162+
auto abbrev = As<ConfigList>(the_switch->Get("abbrev"));
163+
if (abbrev && abbrev->size() > state_index) {
164+
auto value = abbrev->GetValueAt(state_index);
165+
return {value->str().c_str(), value->str().length()};
166+
} else {
167+
auto value = states->GetValueAt(state_index);
168+
return {value->str().c_str(), first_unicode_byte_length(value->str())};
169+
}
170+
} else {
171+
auto value = states->GetValueAt(state_index);
172+
return {value->str().c_str(), value->str().length()};
173+
}
148174
}
149175

150-
an<ConfigValue> Switches::GetStateLabel(const string& option_name, int state) {
176+
StringSlice Switches::GetStateLabel(const string& option_name,
177+
int state,
178+
bool abbreviated) {
151179
auto the_option = OptionByName(option_name);
152-
if (!the_option.found())
153-
return nullptr;
180+
if (!the_option.found()) {
181+
return {nullptr, 0};
182+
}
154183
if (the_option.type == kToggleOption) {
155184
size_t state_index = static_cast<size_t>(state);
156-
return GetStateLabel(the_option.the_switch, state_index);
185+
return GetStateLabel(the_option.the_switch, state_index, abbreviated);
157186
}
158187
if (the_option.type == kRadioGroup) {
159188
// if the query is a deselected option among the radio group, do not
160189
// display its state label; only show the selected option.
161190
return state
162-
? GetStateLabel(the_option.the_switch, the_option.option_index)
163-
: nullptr;
191+
? GetStateLabel(the_option.the_switch,
192+
the_option.option_index,
193+
abbreviated)
194+
: StringSlice{nullptr, 0};
164195
}
165-
return nullptr;
196+
return {nullptr, 0};
166197
}
167198

168199
} // namespace rime

src/rime/switches.h

+14-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ class ConfigItemRef;
1010
class ConfigMap;
1111
class ConfigValue;
1212

13+
struct StringSlice {
14+
const char* str;
15+
size_t length;
16+
17+
operator string() const {
18+
return str && length ? string(str, length) : string();
19+
}
20+
};
21+
1322
class Switches {
1423
public:
1524
explicit Switches(Config* config) : config_(config) {}
@@ -56,10 +65,12 @@ class Switches {
5665
an<ConfigMap> the_switch,
5766
function<FindResult (SwitchOption option)> callback);
5867

59-
static an<ConfigValue> GetStateLabel(
60-
an<ConfigMap> the_switch, size_t state_index);
68+
static StringSlice GetStateLabel(
69+
an<ConfigMap> the_switch, size_t state_index, bool abbreviated);
6170

62-
an<ConfigValue> GetStateLabel(const string& option_name, int state);
71+
StringSlice GetStateLabel(const string& option_name,
72+
int state,
73+
bool abbreviated);
6374

6475
private:
6576
SwitchOption FindOptionFromConfigItem(

0 commit comments

Comments
 (0)