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