a9b81ce52045746571868d5d773140c2e320a293
[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     pinyin_guess_sentence (m_instance);
367
368     PinyinKeyPosVector & pinyin_poses = m_instance->m_pinyin_key_rests;
369     if (lookup_cursor == pinyin_poses->len) {
370         commit();
371         return TRUE;
372     }
373     PinyinKeyPos *pos = &g_array_index
374         (pinyin_poses, PinyinKeyPos, lookup_cursor);
375     m_cursor = pos->m_raw_begin;
376
377     return TRUE;
378 }
379
380 gboolean
381 LibPinyinPhoneticEditor::selectCandidateInPage (guint i)
382 {
383     guint page_size = m_lookup_table.pageSize ();
384     guint cursor_pos = m_lookup_table.cursorPos ();
385
386     if (G_UNLIKELY (i >= page_size))
387         return FALSE;
388     i += (cursor_pos / page_size) * page_size;
389
390     return selectCandidate (i);
391 }
392
393 gboolean
394 LibPinyinPhoneticEditor::removeCharBefore (void)
395 {
396     if (G_UNLIKELY (m_cursor == 0))
397         return FALSE;
398
399     m_cursor --;
400     m_text.erase (m_cursor, 1);
401
402     updatePinyin ();
403     update ();
404
405     return TRUE;
406 }
407
408 gboolean
409 LibPinyinPhoneticEditor::removeCharAfter (void)
410 {
411     if (G_UNLIKELY (m_cursor == m_text.length ()))
412         return FALSE;
413
414     m_text.erase (m_cursor, 1);
415
416     updatePinyin ();
417     update ();
418
419     return TRUE;
420 }
421
422 gboolean
423 LibPinyinPhoneticEditor::moveCursorLeft (void)
424 {
425     if (G_UNLIKELY (m_cursor == 0))
426         return FALSE;
427
428     m_cursor --;
429     update ();
430     return TRUE;
431 }
432
433 gboolean
434 LibPinyinPhoneticEditor::moveCursorRight (void)
435 {
436     if (G_UNLIKELY (m_cursor == m_text.length ()))
437         return FALSE;
438
439     m_cursor ++;
440     update ();
441     return TRUE;
442 }
443
444 gboolean
445 LibPinyinPhoneticEditor::moveCursorToBegin (void)
446 {
447     if (G_UNLIKELY (m_cursor == 0))
448         return TRUE;
449
450     m_cursor = 0;
451     update ();
452     return TRUE;
453 }
454
455 gboolean
456 LibPinyinPhoneticEditor::moveCursorToEnd (void)
457 {
458     if (G_UNLIKELY (m_cursor == m_text.length ()))
459         return FALSE;
460
461     m_cursor = m_text.length ();
462     update ();
463     return TRUE;
464 }
465
466
467 /* move cursor by word functions */
468
469 guint
470 LibPinyinPhoneticEditor::getCursorLeftByWord (void)
471 {
472     guint cursor;
473
474     if (G_UNLIKELY (m_cursor > m_pinyin_len)) {
475         cursor = m_pinyin_len;
476     } else {
477         PinyinKeyPosVector & pinyin_poses = m_instance->m_pinyin_key_rests;
478         guint pinyin_cursor = getPinyinCursor ();
479         PinyinKeyPos *pos = &g_array_index
480             (pinyin_poses, PinyinKeyPos, pinyin_cursor);
481         cursor = pos->m_raw_begin;
482
483         /* cursor at the begin of one pinyin */
484         g_return_val_if_fail (pinyin_cursor > 0, 0);
485         if ( cursor == m_cursor) {
486             pos = &g_array_index
487                 (pinyin_poses, PinyinKeyPos, pinyin_cursor - 1);
488             cursor = pos->m_raw_begin;
489         }
490     }
491
492     return cursor;
493 }
494
495 guint
496 LibPinyinPhoneticEditor::getCursorRightByWord (void)
497 {
498     guint cursor;
499
500     if (G_UNLIKELY (m_cursor > m_pinyin_len)) {
501         cursor = m_text.length ();
502     } else {
503         guint pinyin_cursor = getPinyinCursor ();
504         PinyinKeyPos *pos = &g_array_index
505             (m_instance->m_pinyin_key_rests, PinyinKeyPos, pinyin_cursor);
506         cursor = pos->m_raw_end;
507     }
508
509     return cursor;
510 }
511
512 gboolean
513 LibPinyinPhoneticEditor::removeWordBefore (void)
514 {
515     if (G_UNLIKELY (m_cursor == 0))
516         return FALSE;
517
518     guint cursor = getCursorLeftByWord ();
519     m_text.erase (cursor, m_cursor - cursor);
520     m_cursor = cursor;
521     updatePinyin ();
522     update ();
523     return TRUE;
524 }
525
526 gboolean
527 LibPinyinPhoneticEditor::removeWordAfter (void)
528 {
529     if (G_UNLIKELY (m_cursor == m_text.length ()))
530         return FALSE;
531
532     guint cursor = getCursorRightByWord ();
533     m_text.erase (m_cursor, cursor - m_cursor);
534     updatePinyin ();
535     update ();
536     return TRUE;
537 }
538
539 gboolean
540 LibPinyinPhoneticEditor::moveCursorLeftByWord (void)
541 {
542     if (G_UNLIKELY (m_cursor == 0))
543         return FALSE;
544
545     guint cursor = getCursorLeftByWord ();
546
547     m_cursor = cursor;
548     update ();
549     return TRUE;
550 }
551
552 gboolean
553 LibPinyinPhoneticEditor::moveCursorRightByWord (void)
554 {
555     if (G_UNLIKELY (m_cursor == m_text.length ()))
556         return FALSE;
557
558     guint cursor = getCursorRightByWord ();
559
560     m_cursor = cursor;
561     update ();
562     return TRUE;
563 }