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