1 //
2 // Copyright RIME Developers
3 // Distributed under the BSD License
4 //
5 // 2013-05-26 GONG Chen <chen.sst@gmail.com>
6 //
7 #include <utf8.h>
8 #include <rime/candidate.h>
9 #include <rime/common.h>
10 #include <rime/config.h>
11 #include <rime/context.h>
12 #include <rime/schema.h>
13 #include <rime/switcher.h>
14 #include <rime/translation.h>
15 #include <rime/gear/switch_translator.h>
16
17 static const char* kRightArrow = "\xe2\x86\x92 ";
18 //static const char* kRadioSelected = " \xe2\x97\x89"; // U+25C9 FISHEYE
19 static const char* kRadioSelected = " \xe2\x9c\x93"; // U+2713 CHECK MARK
20
21 namespace rime {
22
23 class Switch : public SimpleCandidate, public SwitcherCommand {
24 public:
Switch(const string & current_state_label,const string & next_state_label,const string & option_name,bool current_state,bool auto_save)25 Switch(const string& current_state_label,
26 const string& next_state_label,
27 const string& option_name,
28 bool current_state,
29 bool auto_save)
30 : SimpleCandidate("switch", 0, 0,
31 current_state_label, kRightArrow + next_state_label),
32 SwitcherCommand(option_name),
33 target_state_(!current_state),
34 auto_save_(auto_save) {
35 }
36 virtual void Apply(Switcher* switcher);
37
38 protected:
39 bool target_state_;
40 bool auto_save_;
41 };
42
Apply(Switcher * switcher)43 void Switch::Apply(Switcher* switcher) {
44 if (Engine* engine = switcher->attached_engine()) {
45 engine->context()->set_option(keyword_, target_state_);
46 }
47 if (auto_save_) {
48 if (Config* user_config = switcher->user_config()) {
49 user_config->SetBool("var/option/" + keyword_, target_state_);
50 }
51 }
52 switcher->Deactivate();
53 }
54
55 class RadioOption;
56
57 class RadioGroup : public std::enable_shared_from_this<RadioGroup> {
58 public:
RadioGroup(Context * context,Switcher * switcher)59 RadioGroup(Context* context, Switcher* switcher)
60 : context_(context), switcher_(switcher) {
61 }
62 an<RadioOption> CreateOption(const string& state_label,
63 const string& option_name);
64 void SelectOption(RadioOption* option);
65 RadioOption* GetSelectedOption() const;
66
67 private:
68 Context* context_;
69 Switcher* switcher_;
70 vector<RadioOption*> options_;
71 };
72
73 class RadioOption : public SimpleCandidate, public SwitcherCommand {
74 public:
RadioOption(an<RadioGroup> group,const string & state_label,const string & option_name)75 RadioOption(an<RadioGroup> group,
76 const string& state_label,
77 const string& option_name)
78 : SimpleCandidate("switch", 0, 0, state_label),
79 SwitcherCommand(option_name),
80 group_(group) {
81 }
82 virtual void Apply(Switcher* switcher);
83 void UpdateState(bool selected);
selected() const84 bool selected() const { return selected_; }
85
86 protected:
87 an<RadioGroup> group_;
88 bool selected_ = false;
89 };
90
Apply(Switcher * switcher)91 void RadioOption::Apply(Switcher* switcher) {
92 group_->SelectOption(this);
93 switcher->Deactivate();
94 }
95
UpdateState(bool selected)96 void RadioOption::UpdateState(bool selected) {
97 selected_ = selected;
98 set_comment(selected ? kRadioSelected : "");
99 }
100
101 an<RadioOption>
CreateOption(const string & state_label,const string & option_name)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;
109 }
110
SelectOption(RadioOption * option)111 void RadioGroup::SelectOption(RadioOption* option) {
112 if (!option)
113 return;
114 Config* user_config = switcher_->user_config();
115 for (auto it = options_.begin(); it != options_.end(); ++it) {
116 bool selected = (*it == option);
117 (*it)->UpdateState(selected);
118 const string& option_name((*it)->keyword());
119 if (context_->get_option(option_name) != selected) {
120 context_->set_option(option_name, selected);
121 if (user_config && switcher_->IsAutoSave(option_name)) {
122 user_config->SetBool("var/option/" + option_name, selected);
123 }
124 }
125 }
126 }
127
GetSelectedOption() const128 RadioOption* RadioGroup::GetSelectedOption() const {
129 if (options_.empty())
130 return NULL;
131 for (auto it = options_.begin(); it != options_.end(); ++it) {
132 if (context_->get_option((*it)->keyword()))
133 return *it;
134 }
135 return options_[0];
136 }
137
138 class FoldedOptions : public SimpleCandidate, public SwitcherCommand {
139 public:
FoldedOptions(Config * config)140 FoldedOptions(Config* config)
141 : SimpleCandidate("unfold", 0, 0, ""),
142 SwitcherCommand("_fold_options") {
143 LoadConfig(config);
144 }
145 virtual void Apply(Switcher* switcher);
Append(const string & label)146 void Append(const string& label) {
147 labels_.push_back(label);
148 }
size() const149 size_t size() const {
150 return labels_.size();
151 }
152 void Finish();
153
154 private:
155 void LoadConfig(Config* config);
156
157 string prefix_;
158 string suffix_;
159 string separator_ = " ";
160 bool abbreviate_options_ = false;
161
162 vector<string> labels_;
163 };
164
LoadConfig(Config * config)165 void FoldedOptions::LoadConfig(Config* config) {
166 if (!config) {
167 return;
168 }
169 config->GetString("switcher/option_list_prefix", &prefix_);
170 config->GetString("switcher/option_list_suffix", &suffix_);
171 config->GetString("switcher/option_list_separator", &separator_);
172 config->GetBool("switcher/abbreviate_options", &abbreviate_options_);
173 }
174
Apply(Switcher * switcher)175 void FoldedOptions::Apply(Switcher* switcher) {
176 // expand the list of options
177 switcher->context()->set_option(keyword_, false);
178 switcher->RefreshMenu();
179 }
180
FirstCharOf(const string & str)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 }
191
Finish()192 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_;
205 }
206
207 class SwitchTranslation : public FifoTranslation {
208 public:
SwitchTranslation(Switcher * switcher)209 SwitchTranslation(Switcher* switcher) {
210 LoadSwitches(switcher);
211 }
212 protected:
213 void LoadSwitches(Switcher* switcher);
214 };
215
LoadSwitches(Switcher * switcher)216 void SwitchTranslation::LoadSwitches(Switcher* switcher) {
217 Engine* engine = switcher->attached_engine();
218 if (!engine)
219 return;
220 Config* config = engine->schema()->config();
221 if (!config)
222 return;
223 auto switches = config->GetList("switches");
224 if (!switches)
225 return;
226 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()));
259 }
260 group->SelectOption(group->GetSelectedOption());
261 }
262 }
263 if (switcher->context()->get_option("_fold_options")) {
264 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 }
271 if (folded_options->size() > 1) {
272 folded_options->Finish();
273 candies_.clear();
274 Append(folded_options);
275 }
276 }
277 DLOG(INFO) << "num switches: " << candies_.size();
278 }
279
SwitchTranslator(const Ticket & ticket)280 SwitchTranslator::SwitchTranslator(const Ticket& ticket)
281 : Translator(ticket) {
282 }
283
Query(const string & input,const Segment & segment)284 an<Translation> SwitchTranslator::Query(const string& input,
285 const Segment& segment) {
286 auto switcher = dynamic_cast<Switcher*>(engine_);
287 if (!switcher) {
288 return nullptr;
289 }
290 return New<SwitchTranslation>(switcher);
291 }
292
293 } // namespace rime
294