fixes selectCandidate
[platform/upstream/ibus-libpinyin.git] / src / PYPPhoneticEditor.cc
1 /* vim:set et ts=4 sts=4:
2  *
3  * ibus-libpinyin - Intelligent Pinyin engine based on libpinyin for IBus
4  *
5  * Copyright (c) 2011 Peng Wu <alexepico@gmail.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20  */
21 #include "PYPPhoneticEditor.h"
22 #include "PYConfig.h"
23 #include "PYPinyinProperties.h"
24 #include "PYSimpTradConverter.h"
25
26 using namespace PY;
27
28 /* init static members */
29 LibPinyinPhoneticEditor::LibPinyinPhoneticEditor (PinyinProperties &props,
30                                                   Config &config):
31     Editor (props, config),
32     m_pinyin_len (0),
33     m_lookup_table (m_config.pageSize ())
34 {
35     m_candidates = g_array_new(FALSE, TRUE, sizeof(lookup_candidate_t));
36 }
37
38 LibPinyinPhoneticEditor::~LibPinyinPhoneticEditor (){
39     pinyin_free_candidates (m_instance, m_candidates);
40     g_array_free (m_candidates, TRUE);
41     m_candidates = NULL;
42 }
43
44 gboolean
45 LibPinyinPhoneticEditor::processSpace (guint keyval, guint keycode,
46                                        guint modifiers)
47 {
48     if (!m_text)
49         return FALSE;
50     if (cmshm_filter (modifiers) != 0)
51         return TRUE;
52
53     if (m_lookup_table.size () != 0) {
54         selectCandidate (m_lookup_table.cursorPos ());
55         update ();
56     }
57     else {
58         commit ();
59     }
60
61     return TRUE;
62 }
63
64 gboolean
65 LibPinyinPhoneticEditor::processFunctionKey (guint keyval, guint keycode, guint modifiers)
66 {
67     if (m_text.empty ())
68         return FALSE;
69
70     /* ignore numlock */
71     modifiers = cmshm_filter (modifiers);
72
73     if (modifiers != 0 && modifiers != IBUS_CONTROL_MASK)
74         return TRUE;
75
76     /* process some cursor control keys */
77     if (modifiers == 0) {  /* no modifiers. */
78         switch (keyval) {
79         case IBUS_Return:
80         case IBUS_KP_Enter:
81             commit (m_text.c_str ());
82             reset ();
83             return TRUE;
84
85         case IBUS_BackSpace:
86             removeCharBefore ();
87             return TRUE;
88
89         case IBUS_Delete:
90         case IBUS_KP_Delete:
91             removeCharAfter ();
92             return TRUE;
93
94         case IBUS_Left:
95         case IBUS_KP_Left:
96             moveCursorLeft ();
97             return TRUE;
98
99         case IBUS_Right:
100         case IBUS_KP_Right:
101             moveCursorRight ();
102             return TRUE;
103
104         case IBUS_Home:
105         case IBUS_KP_Home:
106             moveCursorToBegin ();
107             return TRUE;
108
109         case IBUS_End:
110         case IBUS_KP_End:
111             moveCursorToEnd ();
112             return TRUE;
113
114         case IBUS_Up:
115         case IBUS_KP_Up:
116             cursorUp ();
117             return TRUE;
118
119         case IBUS_Down:
120         case IBUS_KP_Down:
121             cursorDown ();
122             return TRUE;
123
124         case IBUS_Page_Up:
125         case IBUS_KP_Page_Up:
126             pageUp ();
127             return TRUE;
128
129         case IBUS_Page_Down:
130         case IBUS_KP_Page_Down:
131         case IBUS_Tab:
132             pageDown ();
133             return TRUE;
134
135         case IBUS_Escape:
136             reset ();
137             return TRUE;
138         default:
139             return TRUE;
140         }
141     } else { /* ctrl key pressed. */
142         switch (keyval) {
143         case IBUS_BackSpace:
144             removeWordBefore ();
145             return TRUE;
146
147         case IBUS_Delete:
148         case IBUS_KP_Delete:
149             removeWordAfter ();
150             return TRUE;
151
152         case IBUS_Left:
153         case IBUS_KP_Left:
154             moveCursorLeftByWord ();
155             return TRUE;
156
157         case IBUS_Right:
158         case IBUS_KP_Right:
159             moveCursorToEnd ();
160             return TRUE;
161
162         default:
163             return TRUE;
164         }
165     }
166     return TRUE;
167 }
168
169 gboolean
170 LibPinyinPhoneticEditor::processKeyEvent (guint keyval, guint keycode, guint modifiers)
171 {
172     return FALSE;
173 }
174
175 void
176 LibPinyinPhoneticEditor::updateLookupTableFast (void)
177 {
178     Editor::updateLookupTableFast (m_lookup_table, TRUE);
179 }
180
181 void
182 LibPinyinPhoneticEditor::updateLookupTable (void)
183 {
184     m_lookup_table.clear ();
185
186     fillLookupTableByPage ();
187     if (m_lookup_table.size()) {
188         Editor::updateLookupTable (m_lookup_table, TRUE);
189     } else {
190         hideLookupTable ();
191     }
192 }
193
194 gboolean
195 LibPinyinPhoneticEditor::fillLookupTableByPage (void)
196 {
197
198     guint filled_nr = m_lookup_table.size ();
199     guint page_size = m_lookup_table.pageSize ();
200
201     /* fill lookup table by libpinyin get candidates. */
202     guint need_nr = MIN (page_size, m_candidates->len - filled_nr);
203     g_assert (need_nr >=0);
204     if (need_nr == 0)
205         return FALSE;
206
207     String word;
208     for (guint i = filled_nr; i < filled_nr + need_nr; i++) {
209         if (i >= m_candidates->len)  /* no more candidates */
210             break;
211
212         lookup_candidate_t * candidate = &g_array_index
213             (m_candidates, lookup_candidate_t, i);
214
215         const gchar * phrase_string = candidate->m_phrase_string;
216
217         /* show get candidates. */
218         if (G_LIKELY (m_props.modeSimp ())) {
219             word = phrase_string;
220         } else { /* Traditional Chinese */
221             word.truncate (0);
222             SimpTradConverter::simpToTrad (phrase_string, word);
223         }
224
225         Text text (word);
226         m_lookup_table.appendCandidate (text);
227     }
228
229     return TRUE;
230 }
231
232 void
233 LibPinyinPhoneticEditor::pageUp (void)
234 {
235     if (G_LIKELY (m_lookup_table.pageUp ())) {
236         updateLookupTableFast ();
237         updatePreeditText ();
238         updateAuxiliaryText ();
239     }
240 }
241
242 void
243 LibPinyinPhoneticEditor::pageDown (void)
244 {
245     if (G_LIKELY((m_lookup_table.pageDown ()) ||
246                  (fillLookupTableByPage () && m_lookup_table.pageDown()))) {
247         updateLookupTableFast ();
248         updatePreeditText ();
249         updateAuxiliaryText ();
250     }
251 }
252
253 void
254 LibPinyinPhoneticEditor::cursorUp (void)
255 {
256     if (G_LIKELY (m_lookup_table.cursorUp ())) {
257         updateLookupTableFast ();
258         updatePreeditText ();
259         updateAuxiliaryText ();
260     }
261 }
262
263 void
264 LibPinyinPhoneticEditor::cursorDown (void)
265 {
266     if (G_LIKELY ((m_lookup_table.cursorPos () == m_lookup_table.size() - 1) &&
267                   (fillLookupTableByPage () == FALSE))) {
268         return;
269     }
270
271     if (G_LIKELY (m_lookup_table.cursorDown ())) {
272         updateLookupTableFast ();
273         updatePreeditText ();
274         updateAuxiliaryText ();
275     }
276 }
277
278 void
279 LibPinyinPhoneticEditor::candidateClicked (guint index, guint button, guint state)
280 {
281     selectCandidateInPage (index);
282 }
283
284 void
285 LibPinyinPhoneticEditor::reset (void)
286 {
287     m_pinyin_len = 0;
288     m_lookup_table.clear ();
289
290     pinyin_free_candidates (m_instance, m_candidates);
291     pinyin_reset (m_instance);
292
293     Editor::reset ();
294 }
295
296 void
297 LibPinyinPhoneticEditor::update (void)
298 {
299     guint lookup_cursor = getLookupCursor ();
300     pinyin_get_candidates (m_instance, lookup_cursor, m_candidates);
301
302     updateLookupTable ();
303     updatePreeditText ();
304     updateAuxiliaryText ();
305 }
306
307 void
308 LibPinyinPhoneticEditor::commit (const gchar *str)
309 {
310     StaticText text(str);
311     commitText (text);
312 }
313
314 guint
315 LibPinyinPhoneticEditor::getPinyinCursor ()
316 {
317     /* Translate cursor position to pinyin position. */
318     PinyinKeyPosVector & pinyin_poses = m_instance->m_pinyin_key_rests;
319     guint pinyin_cursor = pinyin_poses->len;
320
321     guint16 prev_end = 0, cur_end;
322     for (size_t i = 0; i < pinyin_poses->len; ++i) {
323         PinyinKeyPos *pos = &g_array_index
324             (pinyin_poses, PinyinKeyPos, i);
325         cur_end = pos->m_raw_end;
326
327         if (prev_end <= m_cursor && m_cursor < cur_end)
328             pinyin_cursor = i;
329
330         prev_end = cur_end;
331     }
332
333     g_assert (pinyin_cursor >= 0);
334     return pinyin_cursor;
335 }
336
337 guint
338 LibPinyinPhoneticEditor::getLookupCursor (void)
339 {
340     PinyinKeyVector & pinyins = m_instance->m_pinyin_keys;
341     guint lookup_cursor = getPinyinCursor ();
342     /* show candidates when pinyin cursor is at end. */
343     if (lookup_cursor == pinyins->len && m_pinyin_len == m_text.length())
344         lookup_cursor = 0;
345     return lookup_cursor;
346 }
347
348 gboolean
349 LibPinyinPhoneticEditor::selectCandidate (guint i)
350 {
351
352     if (G_UNLIKELY (i >= m_candidates->len))
353         return FALSE;
354
355     guint lookup_cursor = getLookupCursor ();
356
357     lookup_candidate_t * candidate = &g_array_index
358         (m_candidates, lookup_candidate_t, i);
359     if (BEST_MATCH_CANDIDATE == candidate->m_candidate_type) {
360         commit ();
361         return TRUE;
362     }
363
364     lookup_cursor = pinyin_choose_candidate
365         (m_instance, lookup_cursor, candidate);
366     if (DIVIDED_CANDIDATE == candidate->m_candidate_type ||
367         RESPLIT_CANDIDATE == candidate->m_candidate_type) {
368         m_text = m_instance->m_raw_full_pinyin;
369     }
370     pinyin_guess_sentence (m_instance);
371
372     PinyinKeyPosVector & pinyin_poses = m_instance->m_pinyin_key_rests;
373     if (lookup_cursor == pinyin_poses->len) {
374         pinyin_train(m_instance);
375         commit();
376         return TRUE;
377     }
378     PinyinKeyPos *pos = &g_array_index
379         (pinyin_poses, PinyinKeyPos, lookup_cursor);
380     m_cursor = pos->m_raw_begin;
381
382     return TRUE;
383 }
384
385 gboolean
386 LibPinyinPhoneticEditor::selectCandidateInPage (guint i)
387 {
388     guint page_size = m_lookup_table.pageSize ();
389     guint cursor_pos = m_lookup_table.cursorPos ();
390
391     if (G_UNLIKELY (i >= page_size))
392         return FALSE;
393     i += (cursor_pos / page_size) * page_size;
394
395     return selectCandidate (i);
396 }
397
398 gboolean
399 LibPinyinPhoneticEditor::removeCharBefore (void)
400 {
401     if (G_UNLIKELY (m_cursor == 0))
402         return FALSE;
403
404     m_cursor --;
405     m_text.erase (m_cursor, 1);
406
407     updatePinyin ();
408     update ();
409
410     return TRUE;
411 }
412
413 gboolean
414 LibPinyinPhoneticEditor::removeCharAfter (void)
415 {
416     if (G_UNLIKELY (m_cursor == m_text.length ()))
417         return FALSE;
418
419     m_text.erase (m_cursor, 1);
420
421     updatePinyin ();
422     update ();
423
424     return TRUE;
425 }
426
427 gboolean
428 LibPinyinPhoneticEditor::moveCursorLeft (void)
429 {
430     if (G_UNLIKELY (m_cursor == 0))
431         return FALSE;
432
433     m_cursor --;
434     update ();
435     return TRUE;
436 }
437
438 gboolean
439 LibPinyinPhoneticEditor::moveCursorRight (void)
440 {
441     if (G_UNLIKELY (m_cursor == m_text.length ()))
442         return FALSE;
443
444     m_cursor ++;
445     update ();
446     return TRUE;
447 }
448
449 gboolean
450 LibPinyinPhoneticEditor::moveCursorToBegin (void)
451 {
452     if (G_UNLIKELY (m_cursor == 0))
453         return TRUE;
454
455     m_cursor = 0;
456     update ();
457     return TRUE;
458 }
459
460 gboolean
461 LibPinyinPhoneticEditor::moveCursorToEnd (void)
462 {
463     if (G_UNLIKELY (m_cursor == m_text.length ()))
464         return FALSE;
465
466     m_cursor = m_text.length ();
467     update ();
468     return TRUE;
469 }
470
471
472 /* move cursor by word functions */
473
474 guint
475 LibPinyinPhoneticEditor::getCursorLeftByWord (void)
476 {
477     guint cursor;
478
479     if (G_UNLIKELY (m_cursor > m_pinyin_len)) {
480         cursor = m_pinyin_len;
481     } else {
482         PinyinKeyPosVector & pinyin_poses = m_instance->m_pinyin_key_rests;
483         guint pinyin_cursor = getPinyinCursor ();
484         PinyinKeyPos *pos = &g_array_index
485             (pinyin_poses, PinyinKeyPos, pinyin_cursor);
486         cursor = pos->m_raw_begin;
487
488         /* cursor at the begin of one pinyin */
489         g_return_val_if_fail (pinyin_cursor > 0, 0);
490         if ( cursor == m_cursor) {
491             pos = &g_array_index
492                 (pinyin_poses, PinyinKeyPos, pinyin_cursor - 1);
493             cursor = pos->m_raw_begin;
494         }
495     }
496
497     return cursor;
498 }
499
500 guint
501 LibPinyinPhoneticEditor::getCursorRightByWord (void)
502 {
503     guint cursor;
504
505     if (G_UNLIKELY (m_cursor > m_pinyin_len)) {
506         cursor = m_text.length ();
507     } else {
508         guint pinyin_cursor = getPinyinCursor ();
509         PinyinKeyPos *pos = &g_array_index
510             (m_instance->m_pinyin_key_rests, PinyinKeyPos, pinyin_cursor);
511         cursor = pos->m_raw_end;
512     }
513
514     return cursor;
515 }
516
517 gboolean
518 LibPinyinPhoneticEditor::removeWordBefore (void)
519 {
520     if (G_UNLIKELY (m_cursor == 0))
521         return FALSE;
522
523     guint cursor = getCursorLeftByWord ();
524     m_text.erase (cursor, m_cursor - cursor);
525     m_cursor = cursor;
526     updatePinyin ();
527     update ();
528     return TRUE;
529 }
530
531 gboolean
532 LibPinyinPhoneticEditor::removeWordAfter (void)
533 {
534     if (G_UNLIKELY (m_cursor == m_text.length ()))
535         return FALSE;
536
537     guint cursor = getCursorRightByWord ();
538     m_text.erase (m_cursor, cursor - m_cursor);
539     updatePinyin ();
540     update ();
541     return TRUE;
542 }
543
544 gboolean
545 LibPinyinPhoneticEditor::moveCursorLeftByWord (void)
546 {
547     if (G_UNLIKELY (m_cursor == 0))
548         return FALSE;
549
550     guint cursor = getCursorLeftByWord ();
551
552     m_cursor = cursor;
553     update ();
554     return TRUE;
555 }
556
557 gboolean
558 LibPinyinPhoneticEditor::moveCursorRightByWord (void)
559 {
560     if (G_UNLIKELY (m_cursor == m_text.length ()))
561         return FALSE;
562
563     guint cursor = getCursorRightByWord ();
564
565     m_cursor = cursor;
566     update ();
567     return TRUE;
568 }