1 /* vim:set et ts=4 sts=4:
2 *
3 * libpyzy - The Chinese PinYin and Bopomofo conversion library.
4 *
5 * Copyright (c) 2008-2010 Peng Huang <shawn.p.huang@gmail.com>
6 * Copyright (c) 2010 BYVoid <byvoid1@gmail.com>
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
21 * USA
22 */
23 #include "BopomofoContext.h"
24
25 #include "Config.h"
26 #include "PinyinParser.h"
27 #include "SimpTradConverter.h"
28
29 namespace PyZy {
30 #include "BopomofoKeyboard.h"
31
BopomofoContext(PhoneticContext::Observer * observer)32 BopomofoContext::BopomofoContext (PhoneticContext::Observer *observer)
33 : PhoneticContext (observer),
34 m_bopomofo_schema (BOPOMOFO_KEYBOARD_STANDARD)
35 {
36 }
37
~BopomofoContext(void)38 BopomofoContext::~BopomofoContext (void)
39 {
40 }
41
42 bool
insert(char ch)43 BopomofoContext::insert (char ch)
44 {
45 if (keyvalToBopomofo (ch) == BOPOMOFO_ZERO) {
46 return false;
47 }
48
49 /* is full */
50 if (G_UNLIKELY (m_text.length () >= MAX_PINYIN_LEN))
51 return true;
52
53 m_text.insert (m_cursor++, ch);
54 updateInputText ();
55 updateCursor ();
56
57 if (G_UNLIKELY (!(m_config.option & PINYIN_INCOMPLETE_PINYIN))) {
58 updateSpecialPhrases ();
59 updatePinyin ();
60 }
61 else if (G_LIKELY (m_cursor <= m_pinyin_len + 2)) {
62 updateSpecialPhrases ();
63 updatePinyin ();
64 }
65 else {
66 if (updateSpecialPhrases ()) {
67 update ();
68 }
69 else {
70 updatePreeditText ();
71 updateAuxiliaryText ();
72 }
73 }
74
75 return true;
76 }
77
78 bool
removeCharBefore(void)79 BopomofoContext::removeCharBefore (void)
80 {
81 if (G_UNLIKELY (m_cursor == 0))
82 return false;
83
84 m_cursor --;
85 m_text.erase (m_cursor, 1);
86 updateInputText ();
87 updateCursor ();
88 updateSpecialPhrases ();
89 updatePinyin ();
90
91 return true;
92 }
93
94 bool
removeCharAfter(void)95 BopomofoContext::removeCharAfter (void)
96 {
97 if (G_UNLIKELY (m_cursor == m_text.length ()))
98 return false;
99
100 m_text.erase (m_cursor, 1);
101 updateInputText ();
102 updatePreeditText ();
103 updateAuxiliaryText ();
104
105 return true;
106 }
107
108 bool
removeWordBefore(void)109 BopomofoContext::removeWordBefore (void)
110 {
111 if (G_UNLIKELY (m_cursor == 0))
112 return false;
113
114 size_t cursor;
115
116 if (G_UNLIKELY (m_cursor > m_pinyin_len)) {
117 cursor = m_pinyin_len;
118 }
119 else {
120 const Pinyin & p = *m_pinyin.back ();
121 cursor = m_cursor - p.len;
122 m_pinyin_len -= p.len;
123 m_pinyin.pop_back ();
124 }
125
126 m_text.erase (cursor, m_cursor - cursor);
127 m_cursor = cursor;
128 updateInputText ();
129 updateCursor ();
130 updateSpecialPhrases ();
131 updatePhraseEditor ();
132 update ();
133 return true;
134 }
135
136 bool
removeWordAfter(void)137 BopomofoContext::removeWordAfter (void)
138 {
139 if (G_UNLIKELY (m_cursor == m_text.length ()))
140 return false;
141
142 m_text.erase (m_cursor, -1);
143 updateInputText ();
144 updatePreeditText ();
145 updateAuxiliaryText ();
146 return true;
147 }
148
149 bool
moveCursorLeft(void)150 BopomofoContext::moveCursorLeft (void)
151 {
152 if (G_UNLIKELY (m_cursor == 0))
153 return false;
154
155 m_cursor --;
156 updateCursor ();
157 updateSpecialPhrases ();
158 updatePinyin ();
159
160 return true;
161 }
162
163 bool
moveCursorRight(void)164 BopomofoContext::moveCursorRight (void)
165 {
166 if (G_UNLIKELY (m_cursor == m_text.length ()))
167 return false;
168
169 m_cursor ++;
170 updateCursor ();
171 updateSpecialPhrases ();
172 updatePinyin ();
173
174 return true;
175 }
176
177 bool
moveCursorLeftByWord(void)178 BopomofoContext::moveCursorLeftByWord (void)
179 {
180 if (G_UNLIKELY (m_cursor == 0))
181 return false;
182
183 if (G_UNLIKELY (m_cursor > m_pinyin_len)) {
184 m_cursor = m_pinyin_len;
185 return true;
186 }
187
188 const Pinyin & p = *m_pinyin.back ();
189 m_cursor -= p.len;
190 m_pinyin_len -= p.len;
191 m_pinyin.pop_back ();
192
193 updateCursor ();
194 updateSpecialPhrases ();
195 updatePhraseEditor ();
196 update ();
197
198 return true;
199 }
200
201 bool
moveCursorRightByWord(void)202 BopomofoContext::moveCursorRightByWord (void)
203 {
204 return moveCursorToEnd ();
205 }
206
207 bool
moveCursorToBegin(void)208 BopomofoContext::moveCursorToBegin (void)
209 {
210 if (G_UNLIKELY (m_cursor == 0))
211 return false;
212
213 m_cursor = 0;
214 m_pinyin.clear ();
215 m_pinyin_len = 0;
216
217 updateCursor ();
218 updateSpecialPhrases ();
219 updatePhraseEditor ();
220 update ();
221
222 return true;
223 }
224
225 bool
moveCursorToEnd(void)226 BopomofoContext::moveCursorToEnd (void)
227 {
228 if (G_UNLIKELY (m_cursor == m_text.length ()))
229 return false;
230
231 m_cursor = m_text.length ();
232 updateCursor ();
233 updateSpecialPhrases ();
234 updatePinyin ();
235
236 return true;
237 }
238
239 void
updatePinyin(void)240 BopomofoContext::updatePinyin (void)
241 {
242 if (G_UNLIKELY (m_text.empty ())) {
243 m_pinyin.clear ();
244 m_pinyin_len = 0;
245 }
246 else {
247 std::wstring bopomofo;
248 for(String::iterator i = m_text.begin (); i != m_text.end (); ++i) {
249 bopomofo += bopomofo_char[keyvalToBopomofo (*i)];
250 }
251
252 m_pinyin_len = PinyinParser::parseBopomofo (
253 bopomofo, // bopomofo
254 m_cursor, // text length
255 m_config.option, // option
256 m_pinyin, // result
257 MAX_PHRASE_LEN); // max result length
258 }
259
260 updatePhraseEditor ();
261 update ();
262 }
263
264 void
updateAuxiliaryText(void)265 BopomofoContext::updateAuxiliaryText (void)
266 {
267 if (G_UNLIKELY (m_text.empty () || !hasCandidate (0))) {
268 m_auxiliary_text = "";
269 PhoneticContext::updateAuxiliaryText ();
270 return;
271 }
272
273 m_buffer.clear ();
274
275 if (m_selected_special_phrase.empty ()) {
276 size_t si = 0;
277 size_t m_text_len = m_text.length();
278 for (size_t i = m_phrase_editor.cursor (); i < m_pinyin.size (); ++i) {
279 if (G_LIKELY (i != m_phrase_editor.cursor ()))
280 m_buffer << ',';
281 m_buffer << (unichar *)m_pinyin[i]->bopomofo;
282 for (size_t sj = 0; m_pinyin[i]->bopomofo[sj] == bopomofo_char[keyvalToBopomofo(m_text.c_str()[si])] ; si++,sj++);
283
284 if (si < m_text_len) {
285 int ch = keyvalToBopomofo(m_text.c_str()[si]);
286 if (ch >= BOPOMOFO_TONE_2 && ch <= BOPOMOFO_TONE_5) {
287 m_buffer.appendUnichar(bopomofo_char[ch]);
288 ++si;
289 }
290 }
291 }
292
293 for (String::iterator i = m_text.begin () + m_pinyin_len; i != m_text.end (); i++) {
294 if (m_cursor == (size_t)(i - m_text.begin ()))
295 m_buffer << '|';
296 m_buffer.appendUnichar (bopomofo_char[keyvalToBopomofo (*i)]);
297 }
298 if (m_cursor == m_text.length ())
299 m_buffer << '|';
300 }
301 else {
302 if (m_cursor < m_text.size ()) {
303 m_buffer << '|' << textAfterCursor ();
304 }
305 }
306
307 m_auxiliary_text = m_buffer;
308 PhoneticContext::updateAuxiliaryText ();
309 }
310
311 void
commit(CommitType type)312 BopomofoContext::commit (CommitType type)
313 {
314 if (G_UNLIKELY (m_buffer.empty ()))
315 return;
316
317 m_buffer.clear ();
318
319 if (G_LIKELY (type == TYPE_CONVERTED)) {
320 m_buffer << m_phrase_editor.selectedString ();
321
322 const char *p;
323
324 if (m_selected_special_phrase.empty ()) {
325 p = textAfterPinyin (m_buffer.utf8Length ());
326 }
327 else {
328 m_buffer << m_selected_special_phrase;
329 p = textAfterCursor ();
330 }
331
332 while (*p != '\0') {
333 m_buffer.appendUnichar ((unichar)bopomofo_char[keyvalToBopomofo (*p++)]);
334 }
335
336 m_phrase_editor.commit ();
337 }
338 else if (type == TYPE_PHONETIC) {
339 const char *p = m_text;
340 while (*p != '\0') {
341 m_buffer.appendUnichar ((unichar)bopomofo_char[keyvalToBopomofo (*p++)]);
342 }
343 } else {
344 m_buffer = m_text;
345 m_phrase_editor.reset ();
346 }
347
348 resetContext ();
349 updateInputText ();
350 updateCursor ();
351 update ();
352 PhoneticContext::commitText (m_buffer);
353 }
354
355 void
updatePreeditText(void)356 BopomofoContext::updatePreeditText (void)
357 {
358 /* preedit text = selected phrases + highlight candidate + rest text */
359 if (G_UNLIKELY (m_phrase_editor.empty () && m_text.empty ())) {
360 m_preedit_text.clear ();
361 PhoneticContext::updatePreeditText ();
362 return;
363 }
364
365 size_t edit_begin_byte = 0;
366 size_t edit_end_byte = 0;
367
368 m_buffer.clear ();
369 m_preedit_text.clear ();
370
371 /* add selected phrases */
372 m_buffer << m_phrase_editor.selectedString ();
373
374 if (G_UNLIKELY (! m_selected_special_phrase.empty ())) {
375 /* add selected special phrase */
376 m_buffer << m_selected_special_phrase;
377 edit_begin_byte = edit_end_byte = m_buffer.size ();
378
379 /* append text after cursor */
380 m_buffer << textAfterCursor ();
381 }
382 else {
383 edit_begin_byte = m_buffer.size ();
384
385 if (hasCandidate (0)) {
386 size_t index = m_focused_candidate;
387
388 if (index < m_special_phrases.size ()) {
389 m_buffer << m_special_phrases[index].c_str ();
390 edit_end_byte = m_buffer.size ();
391
392 /* append text after cursor */
393 m_buffer << textAfterCursor ();
394 }
395 else {
396 const Phrase & candidate = m_phrase_editor.candidate (index - m_special_phrases.size ());
397 if (m_text.size () == m_cursor) {
398 /* cursor at end */
399 if (m_config.modeSimp)
400 m_buffer << candidate;
401 else
402 SimpTradConverter::simpToTrad (candidate, m_buffer);
403 edit_end_byte = m_buffer.size ();
404 /* append rest text */
405 for (const char *p=m_text.c_str() + m_pinyin_len; *p ;++p) {
406 m_buffer.appendUnichar(bopomofo_char[keyvalToBopomofo(*p)]);
407 }
408 }
409 else {
410 for (const char *p = m_text.c_str (); *p; ++p) {
411 if ((size_t) (p - m_text.c_str ()) == m_cursor)
412 m_buffer << ' ';
413 m_buffer.appendUnichar (bopomofo_char[keyvalToBopomofo (*p)]);
414 }
415 edit_end_byte = m_buffer.size ();
416 }
417 }
418 }
419 else {
420 edit_end_byte = m_buffer.size ();
421 for (const char *p=m_text.c_str () + m_pinyin_len; *p ; ++p) {
422 m_buffer.appendUnichar (bopomofo_char[keyvalToBopomofo (*p)]);
423 }
424 }
425 }
426
427 m_preedit_text.selected_text = m_buffer.substr (0, edit_begin_byte);
428 m_preedit_text.candidate_text = m_buffer.substr (edit_begin_byte, edit_end_byte - edit_begin_byte);
429 m_preedit_text.rest_text = m_buffer.substr (edit_end_byte);
430
431 PhoneticContext::updatePreeditText ();
432 }
433
434 Variant
getProperty(PropertyName name) const435 BopomofoContext::getProperty (PropertyName name) const
436 {
437 if (name == PROPERTY_BOPOMOFO_SCHEMA) {
438 return Variant::fromUnsignedInt(m_bopomofo_schema);
439 }
440 return PhoneticContext::getProperty (name);
441 }
442
443 bool
setProperty(PropertyName name,const Variant & variant)444 BopomofoContext::setProperty (PropertyName name, const Variant &variant)
445 {
446 if (name == PROPERTY_BOPOMOFO_SCHEMA) {
447 if (variant.getType () != Variant::TYPE_UNSIGNED_INT) {
448 return false;
449 }
450 const unsigned int schema = variant.getUnsignedInt ();
451 if (schema >= BOPOMOFO_KEYBOARD_LAST) {
452 return false;
453 }
454
455 m_bopomofo_schema = schema;
456 return true;
457 }
458
459 return PhoneticContext::setProperty (name, variant);
460 }
461
462 static int
keyboard_cmp(const void * p1,const void * p2)463 keyboard_cmp (const void * p1, const void * p2)
464 {
465 const int s1 = GPOINTER_TO_INT (p1);
466 const unsigned char *s2 = (const unsigned char *) p2;
467 return s1 - s2[0];
468 }
469
470 int
keyvalToBopomofo(int ch)471 BopomofoContext::keyvalToBopomofo(int ch)
472 {
473 const unsigned int keyboard = m_bopomofo_schema;
474 const unsigned char *brs;
475 brs = (const unsigned char *) std::bsearch (GINT_TO_POINTER (ch),
476 bopomofo_keyboard[keyboard],
477 G_N_ELEMENTS (bopomofo_keyboard[keyboard]),
478 sizeof(bopomofo_keyboard[keyboard][0]),
479 keyboard_cmp);
480 if (G_UNLIKELY (brs == NULL))
481 return BOPOMOFO_ZERO;
482 return brs[1];
483 }
484
485 }; // namespace PyZy
486