1 //
2 // Copyright RIME Developers
3 // Distributed under the BSD License
4 //
5 // 2011-05-08 GONG Chen <chen.sst@gmail.com>
6 //
7 #include <utility>
8 #include <rime/candidate.h>
9 #include <rime/context.h>
10 #include <rime/menu.h>
11 #include <rime/segmentation.h>
12 
13 namespace rime {
14 
Commit()15 bool Context::Commit() {
16   if (!IsComposing())
17     return false;
18   // notify the engine and interesting components
19   commit_notifier_(this);
20   // start over
21   Clear();
22   return true;
23 }
24 
GetCommitText() const25 string Context::GetCommitText() const {
26   if (get_option("dumb"))
27     return string();
28   return composition_.GetCommitText();
29 }
30 
GetScriptText() const31 string Context::GetScriptText() const {
32   return composition_.GetScriptText();
33 }
34 
35 static const string kCaretSymbol("\xe2\x80\xb8");
36 
GetSoftCursor() const37 string Context::GetSoftCursor() const {
38   return get_option("soft_cursor") ? kCaretSymbol : string();
39 }
40 
GetPreedit() const41 Preedit Context::GetPreedit() const {
42   return composition_.GetPreedit(input_, caret_pos_, GetSoftCursor());
43 }
44 
IsComposing() const45 bool Context::IsComposing() const {
46   return !input_.empty();
47 }
48 
HasMenu() const49 bool Context::HasMenu() const {
50   if (composition_.empty())
51     return false;
52   const auto& menu(composition_.back().menu);
53   return menu && !menu->empty();
54 }
55 
GetSelectedCandidate() const56 an<Candidate> Context::GetSelectedCandidate() const {
57   if (composition_.empty())
58     return nullptr;
59   return composition_.back().GetSelectedCandidate();
60 }
61 
PushInput(char ch)62 bool Context::PushInput(char ch) {
63   if (caret_pos_ >= input_.length()) {
64     input_.push_back(ch);
65     caret_pos_ = input_.length();
66   }
67   else {
68     input_.insert(caret_pos_, 1, ch);
69     ++caret_pos_;
70   }
71   update_notifier_(this);
72   return true;
73 }
74 
PushInput(const string & str)75 bool Context::PushInput(const string& str) {
76   if (caret_pos_ >= input_.length()) {
77     input_ += str;
78     caret_pos_ = input_.length();
79   }
80   else {
81     input_.insert(caret_pos_, str);
82     caret_pos_ += str.length();
83   }
84   update_notifier_(this);
85   return true;
86 }
87 
PopInput(size_t len)88 bool Context::PopInput(size_t len) {
89   if (caret_pos_ < len)
90     return false;
91   caret_pos_ -= len;
92   input_.erase(caret_pos_, len);
93   update_notifier_(this);
94   return true;
95 }
96 
DeleteInput(size_t len)97 bool Context::DeleteInput(size_t len) {
98   if (caret_pos_ + len > input_.length())
99     return false;
100   input_.erase(caret_pos_, len);
101   update_notifier_(this);
102   return true;
103 }
104 
Clear()105 void Context::Clear() {
106   input_.clear();
107   caret_pos_ = 0;
108   composition_.clear();
109   update_notifier_(this);
110 }
111 
Select(size_t index)112 bool Context::Select(size_t index) {
113   if (composition_.empty())
114     return false;
115   Segment& seg(composition_.back());
116   if (auto cand = seg.GetCandidateAt(index)) {
117     seg.selected_index = index;
118     seg.status = Segment::kSelected;
119     DLOG(INFO) << "Selected: '" << cand->text() << "', index = " << index;
120     select_notifier_(this);
121     return true;
122   }
123   return false;
124 }
125 
DeleteCurrentSelection()126 bool Context::DeleteCurrentSelection() {
127   if (composition_.empty())
128     return false;
129   Segment& seg(composition_.back());
130   if (auto cand = seg.GetSelectedCandidate()) {
131     DLOG(INFO) << "Deleting: '" << cand->text()
132                << "', selected_index = " << seg.selected_index;
133     delete_notifier_(this);
134     return true;  // CAVEAT: this doesn't mean anything is deleted for sure
135   }
136   return false;
137 }
138 
ConfirmCurrentSelection()139 bool Context::ConfirmCurrentSelection() {
140   if (composition_.empty())
141     return false;
142   Segment& seg(composition_.back());
143   seg.status = Segment::kSelected;
144   if (auto cand = seg.GetSelectedCandidate()) {
145     DLOG(INFO) << "Confirmed: '" << cand->text()
146                << "', selected_index = " << seg.selected_index;
147   }
148   else {
149     if (seg.end == seg.start) {
150       // fluid_editor will confirm the whole sentence
151       return false;
152     }
153     // confirm raw input
154   }
155   select_notifier_(this);
156   return true;
157 }
158 
ConfirmPreviousSelection()159 bool Context::ConfirmPreviousSelection() {
160   for (auto it = composition_.rbegin(); it != composition_.rend(); ++it) {
161     if (it->status > Segment::kSelected) {
162       return false;
163     }
164     if (it->status == Segment::kSelected) {
165       it->status = Segment::kConfirmed;
166       return true;
167     }
168   }
169   return false;
170 }
171 
ReopenPreviousSegment()172 bool Context::ReopenPreviousSegment() {
173   if (composition_.Trim()) {
174     if (!composition_.empty() &&
175         composition_.back().status >= Segment::kSelected) {
176       composition_.back().Reopen(caret_pos());
177     }
178     update_notifier_(this);
179     return true;
180   }
181   return false;
182 }
183 
ClearPreviousSegment()184 bool Context::ClearPreviousSegment() {
185   if (composition_.empty())
186     return false;
187   size_t where = composition_.back().start;
188   if (where >= input_.length())
189     return false;
190   set_input(input_.substr(0, where));
191   return true;
192 }
193 
ReopenPreviousSelection()194 bool Context::ReopenPreviousSelection() {
195   for (auto it = composition_.rbegin(); it != composition_.rend(); ++it) {
196     if (it->status > Segment::kSelected)
197       return false;
198     if (it->status == Segment::kSelected) {
199       while (it != composition_.rbegin()) {
200         composition_.pop_back();
201       }
202       it->Reopen(caret_pos());
203       update_notifier_(this);
204       return true;
205     }
206   }
207   return false;
208 }
209 
ClearNonConfirmedComposition()210 bool Context::ClearNonConfirmedComposition() {
211   bool reverted = false;
212   while (!composition_.empty() &&
213          composition_.back().status < Segment::kSelected) {
214     composition_.pop_back();
215     reverted = true;
216   }
217   if (reverted) {
218     composition_.Forward();
219     DLOG(INFO) << "composition: " << composition_.GetDebugText();
220   }
221   return reverted;
222 }
223 
RefreshNonConfirmedComposition()224 bool Context::RefreshNonConfirmedComposition() {
225   if (ClearNonConfirmedComposition()) {
226     update_notifier_(this);
227     return true;
228   }
229   return false;
230 }
231 
set_caret_pos(size_t caret_pos)232 void Context::set_caret_pos(size_t caret_pos) {
233   if (caret_pos > input_.length())
234     caret_pos_ = input_.length();
235   else
236     caret_pos_ = caret_pos;
237   update_notifier_(this);
238 }
239 
set_composition(Composition && comp)240 void Context::set_composition(Composition&& comp) {
241   composition_ = std::move(comp);
242 }
243 
set_input(const string & value)244 void Context::set_input(const string& value) {
245   input_ = value;
246   caret_pos_ = input_.length();
247   update_notifier_(this);
248 }
249 
set_option(const string & name,bool value)250 void Context::set_option(const string& name, bool value) {
251   options_[name] = value;
252   option_update_notifier_(this, name);
253 }
254 
get_option(const string & name) const255 bool Context::get_option(const string& name) const {
256   auto it = options_.find(name);
257   if (it != options_.end())
258     return it->second;
259   else
260     return false;
261 }
262 
set_property(const string & name,const string & value)263 void Context::set_property(const string& name,
264                            const string& value) {
265   properties_[name] = value;
266   property_update_notifier_(this, name);
267 }
268 
get_property(const string & name) const269 string Context::get_property(const string& name) const {
270   auto it = properties_.find(name);
271   if (it != properties_.end())
272     return it->second;
273   else
274     return string();
275 }
276 
ClearTransientOptions()277 void Context::ClearTransientOptions() {
278   auto opt = options_.lower_bound("_");
279   while (opt != options_.end() &&
280          !opt->first.empty() && opt->first[0] == '_') {
281     options_.erase(opt++);
282   }
283   auto prop = properties_.lower_bound("_");
284   while (prop != properties_.end() &&
285          !prop->first.empty() && prop->first[0] == '_') {
286     properties_.erase(prop++);
287   }
288 }
289 
290 }  // namespace rime
291