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