clean up PYConfig.h/cc
[platform/upstream/ibus-libpinyin.git] / src / PYExtEditor.cc
1 /* vim:set et ts=4 sts=4:
2  *
3  * ibus-pinyin - The Chinese PinYin engine for IBus
4  *
5  * Copyright (c) 2008-2010 Peng Huang <shawn.p.huang@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., 675 Mass Ave, Cambridge, MA 02139, USA.
20  */
21 #include <cstring>
22 #include <string>
23
24 extern "C" {
25 #include "lua-plugin.h"
26 }
27
28 #include "PYConfig.h"
29 #include "PYPointer.h"
30 #include "PYLookupTable.h"
31
32 #include "PYDynamicSpecialPhrase.h"
33
34 #include "PYEditor.h"
35 #include "PYExtEditor.h"
36
37 namespace PY {
38
39
40 /* Write digit/alpha/none Label generator here.
41  * foreach (results): 1, from get_retval; 2..n from get_retvals.
42  */
43
44 ExtEditor::ExtEditor (PinyinProperties & props, Config & config)
45     : Editor (props, config),
46       m_mode (LABEL_NONE),
47       m_result_num (0),
48       m_candidate (NULL),
49       m_candidates (NULL)
50 {
51     m_lua_plugin = ibus_engine_plugin_new ();
52
53     gchar * path = g_build_filename (g_get_user_config_dir (),
54                                      ".ibus", "pinyin", "base.lua", NULL);
55     loadLuaScript ( ".." G_DIR_SEPARATOR_S "lua" G_DIR_SEPARATOR_S "base.lua")||
56         loadLuaScript (path) ||
57         loadLuaScript (PKGDATADIR G_DIR_SEPARATOR_S "base.lua");
58
59     g_free(path);
60 }
61
62 int
63 ExtEditor::loadLuaScript (std::string filename)
64 {
65     return !ibus_engine_plugin_load_lua_script (m_lua_plugin, filename.c_str ());
66 }
67
68 void
69 ExtEditor::resetLuaState ()
70 {
71   g_object_unref (m_lua_plugin);
72   m_lua_plugin = ibus_engine_plugin_new ();
73 }
74
75
76 gboolean
77 ExtEditor::processKeyEvent (guint keyval, guint keycode, guint modifiers)
78 {
79     //IBUS_SHIFT_MASK is removed.
80     modifiers &= (IBUS_CONTROL_MASK |
81                   IBUS_MOD1_MASK |
82                   IBUS_SUPER_MASK |
83                   IBUS_HYPER_MASK |
84                   IBUS_META_MASK |
85                   IBUS_LOCK_MASK);
86     if ( modifiers )
87         return FALSE;
88
89     //handle backspace/delete here.
90     if (processEditKey (keyval))
91         return TRUE;
92
93     //handle page/cursor up/down here.
94     if (processPageKey (keyval))
95         return TRUE;
96
97     //handle label key select here.
98     if (processLabelKey (keyval))
99         return TRUE;
100
101     if (processSpace (keyval))
102         return TRUE;
103
104     if (processEnter (keyval))
105         return TRUE;
106
107     m_cursor = std::min (m_cursor, (guint)m_text.length ());
108
109     /* Remember the input string. */
110     switch (m_cursor) {
111     case 0: //Empty input string.
112         {
113             g_return_val_if_fail ( 'i' == keyval, FALSE);
114             if ( 'i' == keyval ) {
115                 m_text.insert (m_cursor, keyval);
116                 m_cursor++;
117             }
118         }
119         break;
120     case 1 ... 2: // Only contains 'i' in input string.
121         {
122             g_return_val_if_fail ( 'i' == m_text[0], FALSE);
123             if ( isalnum (keyval) ) {
124                 m_text.insert (m_cursor, keyval);
125                 m_cursor++;
126             }
127         }
128         break;
129     default: //Here is the appended argment.
130         {
131             g_return_val_if_fail ( 'i' == m_text[0], FALSE);
132             if (isprint (keyval)) {
133                 m_text.insert (m_cursor, keyval);
134                 m_cursor++;
135             }
136         }
137         break;
138     }
139     /* Deal other staff with updateStateFromInput (). */
140     updateStateFromInput ();
141     update ();
142     return TRUE;
143 }
144
145 gboolean
146 ExtEditor::processEditKey (guint keyval)
147 {
148     switch (keyval) {
149     case IBUS_Delete:
150     case IBUS_KP_Delete:
151         removeCharAfter ();
152         updateStateFromInput ();
153         update ();
154         return TRUE;
155     case IBUS_BackSpace:
156         removeCharBefore ();
157         updateStateFromInput ();
158         update ();
159         return TRUE;
160     }
161     return FALSE;
162 }
163
164 gboolean
165 ExtEditor::processPageKey (guint keyval)
166 {
167     switch (keyval) {
168     //For 2000-10-10 16:30 input.
169     case IBUS_comma:
170         if (m_config.commaPeriodPage ()) {
171             pageUp ();
172             return TRUE;
173         }
174         break;
175 #if 0
176     case IBUS_minus:
177         if (m_config.minusEqualPage ()) {
178             pageUp ();
179             return TRUE;
180         }
181         break;
182 #endif
183     case IBUS_period:
184         if (m_config.commaPeriodPage ()) {
185             pageDown ();
186             return TRUE;
187         }
188         break;
189     case IBUS_equal:
190         if (m_config.minusEqualPage ()) {
191             pageDown ();
192             return TRUE;
193         }
194         break;
195     case IBUS_Up:
196     case IBUS_KP_Up:
197         cursorUp ();
198         return TRUE;
199
200     case IBUS_Down:
201     case IBUS_KP_Down:
202         cursorDown ();
203         return TRUE;
204
205     case IBUS_Page_Up:
206     case IBUS_KP_Page_Up:
207         pageUp ();
208         return TRUE;
209
210     case IBUS_Page_Down:
211     case IBUS_KP_Page_Down:
212         pageDown ();
213         return TRUE;
214
215     case IBUS_Escape:
216         reset ();
217         return TRUE;
218     }
219     return FALSE;
220 }
221
222 gboolean
223 ExtEditor::processLabelKey (guint keyval)
224 {
225     //According to enum ExtEditorLabelMode.
226
227     switch (m_mode) {
228     case LABEL_LIST_DIGIT:
229         switch (keyval) {
230         case '1' ... '9':
231             return selectCandidateInPage (keyval - '1');
232             break;
233         case '0':
234             return selectCandidateInPage (9);
235             break;
236         }
237         break;
238     case LABEL_LIST_NUMBERS:
239     case LABEL_LIST_ALPHA:
240         switch (keyval) {
241         case 'a' ... 'k':
242             return selectCandidateInPage (keyval - 'a');
243             break;
244         case 'A' ... 'K':
245             return selectCandidateInPage (keyval - 'A');
246             break;
247         }
248         break;
249     default:
250         break;
251     }
252     return FALSE;
253 }
254
255 gboolean
256 ExtEditor::processSpace (guint keyval)
257 {
258     if (!(keyval == IBUS_space || keyval == IBUS_KP_Space))
259         return FALSE;
260
261     guint cursor_pos = m_lookup_table.cursorPos ();
262
263     switch (m_mode) {
264     case LABEL_LIST_NUMBERS:
265         selectCandidate (cursor_pos);
266         break;
267     case LABEL_LIST_COMMANDS:
268     case LABEL_LIST_DIGIT:
269     case LABEL_LIST_ALPHA:
270         selectCandidate (cursor_pos);
271         break;
272     case LABEL_LIST_SINGLE:
273         g_return_val_if_fail (cursor_pos == 0 , FALSE);
274         selectCandidate (cursor_pos);
275         break;
276     default:
277         break;
278     }
279     return TRUE;
280 }
281
282 gboolean
283 ExtEditor::processEnter(guint keyval)
284 {
285     if (keyval != IBUS_Return)
286         return FALSE;
287
288     if (m_text.length () == 0)
289         return FALSE;
290
291     Text text(m_text);
292     commitText (text);
293     reset ();
294     return TRUE;
295 }
296
297 void
298 ExtEditor::pageUp (void)
299 {
300     if (G_LIKELY(m_lookup_table.pageUp ())) {
301         update ();
302     }
303 }
304
305 void
306 ExtEditor::pageDown (void)
307 {
308     if (G_LIKELY(m_lookup_table.pageDown ())) {
309         update ();
310     }
311 }
312
313 gboolean
314 ExtEditor::removeCharBefore (void)
315 {
316     if (G_UNLIKELY( m_cursor <= 0 )) {
317         m_cursor = 0;
318         return FALSE;
319     }
320
321     if (G_UNLIKELY( m_cursor > m_text.length () )) {
322         m_cursor = m_text.length ();
323         return FALSE;
324     }
325
326     m_text.erase (m_cursor - 1, 1);
327     m_cursor = std::max (0, static_cast<int>(m_cursor) - 1);
328     return TRUE;
329 }
330
331 gboolean
332 ExtEditor::removeCharAfter (void)
333 {
334     if (G_UNLIKELY( m_cursor < 0 )) {
335         m_cursor = 0;
336         return FALSE;
337     }
338
339     if (G_UNLIKELY( m_cursor >= m_text.length () )) {
340         m_cursor = m_text.length ();
341         return FALSE;
342     }
343     m_text.erase (m_cursor, 1);
344     m_cursor = std::min (m_cursor, (guint)m_text.length ());
345     return TRUE;
346 }
347
348 void
349 ExtEditor::cursorUp (void)
350 {
351     if (G_LIKELY (m_lookup_table.cursorUp ())) {
352         update ();
353     }
354 }
355
356 void
357 ExtEditor::cursorDown (void)
358 {
359     if (G_LIKELY (m_lookup_table.cursorDown ())) {
360         update ();
361     }
362 }
363
364 void
365 ExtEditor::update (void)
366 {
367     updateLookupTable ();
368     updatePreeditText ();
369     updateAuxiliaryText ();
370 }
371
372 void
373 ExtEditor::reset (void)
374 {
375     m_text = "";
376     updateStateFromInput ();
377     update ();
378 }
379
380 void
381 ExtEditor::candidateClicked (guint index, guint button, guint state)
382 {
383     selectCandidateInPage (index);
384 }
385
386 gboolean
387 ExtEditor::selectCandidateInPage (guint index)
388 {
389     guint page_size = m_lookup_table.pageSize ();
390     guint cursor_pos = m_lookup_table.cursorPos ();
391
392     if (G_UNLIKELY(index >= page_size))
393         return FALSE;
394     index += (cursor_pos / page_size) * page_size;
395
396     return selectCandidate (index);
397 }
398
399 gboolean
400 ExtEditor::selectCandidate (guint index)
401 {
402     switch (m_mode) {
403     case LABEL_LIST_NUMBERS:
404         {
405             if ( index >= m_lookup_table.size() )
406                 return FALSE;
407
408             IBusText * candidate = m_lookup_table.getCandidate(index);
409             Text text(candidate);
410             commitText(text);
411             reset();
412             return TRUE;
413         }
414         break;
415     case LABEL_LIST_COMMANDS:
416         {
417             std::string prefix = m_text.substr (1, 2);
418             int len = prefix.length ();
419             const char * prefix_str = prefix.c_str ();
420             const GArray * commands = ibus_engine_plugin_get_available_commands (m_lua_plugin);
421             int match_count = -1;
422             for (int i = 0; i < static_cast<int>(commands->len); ++i) {
423                 lua_command_t * command = &g_array_index (commands, lua_command_t, i);
424                 if ( strncmp (prefix_str, command->command_name, len) == 0 ) {
425                     match_count++;
426                 }
427                 if ( match_count == static_cast<int>(index) ) {
428                     m_text.clear ();
429                     m_text = "i";
430                     m_text += command->command_name;
431                     m_cursor = m_text.length ();
432                     break;
433                 }
434             }
435             updateStateFromInput ();
436             update ();
437         }
438         return TRUE;
439         break;
440     case LABEL_LIST_DIGIT:
441     case LABEL_LIST_ALPHA:
442         {
443             g_return_val_if_fail (m_result_num > 1, FALSE);
444             g_return_val_if_fail (static_cast<int>(index) < m_result_num, FALSE);
445
446             const lua_command_candidate_t * candidate = g_array_index (m_candidates, lua_command_candidate_t *, index);
447             if ( candidate->content ) {
448                 Text text (candidate->content);
449                 commitText (text);
450                 m_text.clear ();
451             } else if (candidate->suggest) {
452                 m_text += candidate->suggest;
453                 m_cursor += strlen(candidate->suggest);
454             }
455
456             updateStateFromInput ();
457             update ();
458         }
459         return TRUE;
460         break;
461     case LABEL_LIST_SINGLE:
462         {
463             g_return_val_if_fail (m_result_num == 1, FALSE);
464             g_return_val_if_fail (index == 0, FALSE);
465             if ( m_candidate->content ) {
466                 Text text (m_candidate->content);
467                 commitText (text);
468                 m_text.clear ();
469             } else if (m_candidate->suggest) {
470                 m_text += m_candidate->suggest;
471             }
472
473             updateStateFromInput ();
474             update ();
475             return TRUE;
476         }
477         break;
478     default:
479         break;
480     }
481     return FALSE;
482 }
483
484 bool
485 ExtEditor::updateStateFromInput (void)
486 {
487     /* Do parse and candidates update here. */
488     /* prefix i double check here. */
489     if ( !m_text.length () ) {
490         m_preedit_text = "";
491         m_auxiliary_text = "";
492         m_cursor = 0;
493         clearLookupTable ();
494         return FALSE;
495     }
496
497     if ( ! 'i' == m_text[0] ) {
498         g_warning ("i is expected in m_text string.\n");
499         return FALSE;
500     }
501
502     m_auxiliary_text = "i";
503
504     m_mode = LABEL_LIST_COMMANDS;
505     if ( 1 == m_text.length () ) {
506         fillCommandCandidates ();
507         return true;
508     }
509     /* Check m_text len, and update auxiliary string meanwhile.
510      * 1. only "i", dispatch to fillCommandCandidates (void).
511      * 2. "i" with one charactor,
512      *      dispatch to fillCommandCandidates (std::string).
513      * 3. "i" with two charactor or more,
514      *      dispatch to fillCommand (std::string, const char * argument).
515      */
516
517     if ( isalpha (m_text[1])) {
518         m_mode = LABEL_LIST_COMMANDS;
519         if ( m_text.length () == 2) {
520             fillCommandCandidates (m_text.substr (1,1).c_str ());
521
522             m_auxiliary_text += " ";
523             m_auxiliary_text += m_text.substr (1, 1);
524             return true;
525         } else if ( m_text.length () >= 3) {
526             std::string command_name = m_text.substr (1,2);
527
528             m_auxiliary_text += " ";
529             m_auxiliary_text += m_text.substr (1,2);
530
531             const char * argment = NULL;
532             std::string arg = "";
533             if (m_text.length () > 3) {
534                 arg = m_text.substr (3);
535                 argment = arg.c_str ();
536                 m_auxiliary_text += " ";
537                 m_auxiliary_text += argment;
538             }
539             /* finish auxiliary text computing here. */
540
541             const lua_command_t * command = ibus_engine_plugin_lookup_command (m_lua_plugin, command_name.c_str ());
542             if ( NULL == command) {
543                 m_mode = LABEL_NONE;
544                 clearLookupTable ();
545                 m_lookup_table.clear ();
546                 return FALSE;
547             }
548
549             if ( command->help ){
550                 int space_len = std::max ( 0, m_aux_text_len
551                                            - (int) g_utf8_strlen (command->help, -1)
552                                            - 2 /* length of "[...]" */);
553                 m_auxiliary_text.append(space_len, ' ');
554
555                 m_auxiliary_text += "[";
556                 m_auxiliary_text += command->help;
557                 m_auxiliary_text += "]";
558             }
559
560             std::string label = command->leading;
561
562             if ( "digit" == label )
563                 m_mode = LABEL_LIST_DIGIT;
564             else if ( "alpha" == label )
565                 m_mode = LABEL_LIST_ALPHA;
566             else
567                 m_mode = LABEL_LIST_NONE;
568
569             fillCommand (command_name, argment);
570         }
571     }
572     else if ( isdigit (m_text[1]) ) {
573         m_mode = LABEL_LIST_NUMBERS;
574         std::string number = m_text.substr(1);
575         m_auxiliary_text += " ";
576         m_auxiliary_text += number;
577
578         //Generate Chinese number.
579         gint64 num = atoll (number.c_str ());
580         fillChineseNumber (num);
581     }
582
583     return true;
584 }
585
586 bool
587 ExtEditor::fillCommandCandidates (void)
588 {
589     return fillCommandCandidates ("");
590 }
591
592 bool
593 ExtEditor::fillCommandCandidates (std::string prefix)
594 {
595     clearLookupTable ();
596
597     /* fill candidates here. */
598     int len = prefix.length ();
599     const char * prefix_str = prefix.c_str ();
600     const GArray * commands = ibus_engine_plugin_get_available_commands (m_lua_plugin);
601     int count = -1;
602     for ( int i = 0; i < static_cast<int>(commands->len); ++i) {
603         lua_command_t * command = &g_array_index (commands, lua_command_t, i);
604         if ( strncmp (prefix_str, command->command_name, len) == 0) {
605             count++;
606             std::string candidate = command->command_name;
607             candidate += ".";
608             candidate += command->description;
609             m_lookup_table.setLabel (count, Text (""));
610             m_lookup_table.appendCandidate (Text (candidate));
611         }
612     }
613
614     return true;
615 }
616
617 bool
618 ExtEditor::fillCommand (std::string command_name, const char * argument)
619 {
620     const lua_command_t * command = ibus_engine_plugin_lookup_command (m_lua_plugin, command_name.c_str ());
621     if ( NULL == command )
622         return false;
623
624     if ( m_result_num != 0) {
625         if ( m_result_num == 1) {
626             ibus_engine_plugin_free_candidate ((lua_command_candidate_t *)m_candidate);
627             m_candidate = NULL;
628         }else{
629             for ( int i = 0; i < m_result_num; ++i) {
630                 const lua_command_candidate_t * candidate = g_array_index (m_candidates, lua_command_candidate_t *, i);
631                 ibus_engine_plugin_free_candidate ((lua_command_candidate_t *)candidate);
632             }
633
634             g_array_free (m_candidates, TRUE);
635             m_candidates = NULL;
636         }
637         m_result_num = 0;
638         g_assert (m_candidates == NULL && m_candidate == NULL);
639     }
640
641     m_result_num = ibus_engine_plugin_call (m_lua_plugin, command->lua_function_name, argument);
642
643     if ( 1 == m_result_num )
644         m_mode = LABEL_LIST_SINGLE;
645
646     clearLookupTable ();
647
648     //Generate labels according to m_mode
649     if ( LABEL_LIST_DIGIT == m_mode ) {
650         for ( int i = 1; i <= 10; ++i )
651             m_lookup_table.setLabel ( i - 1, Text (i - 1 + '1') );
652     }
653
654     if ( LABEL_LIST_ALPHA == m_mode) {
655         for ( int i = 1; i <= 10; ++i )
656             m_lookup_table.setLabel ( i - 1, Text (i - 1 + 'a') );
657     }
658
659     if ( LABEL_LIST_NONE == m_mode || LABEL_LIST_SINGLE == m_mode) {
660         for ( int i = 1; i <= 10; ++i)
661             m_lookup_table.setLabel ( i - 1, Text (""));
662     }
663
664     //Generate candidates
665     std::string result;
666     if ( 1 == m_result_num ) {
667         m_candidate = ibus_engine_plugin_get_retval (m_lua_plugin);
668         result = "";
669         if ( m_candidate->content ) {
670             result = m_candidate->content;
671             if (strstr (result.c_str (), "\n"))
672                 result = "(字符画)";
673         }
674         if ( m_candidate->suggest && m_candidate-> help ) {
675             result += m_candidate->suggest;
676             result += " ";
677             result += "[";
678             result += m_candidate->help;
679             result += "]";
680         }
681
682         m_lookup_table.appendCandidate (Text (result));
683     }else if (m_result_num > 1) {
684         m_candidates = ibus_engine_plugin_get_retvals (m_lua_plugin);
685         for ( int i = 0; i < m_result_num; ++i) {
686             const lua_command_candidate_t * candidate = g_array_index (m_candidates, lua_command_candidate_t *, i);
687             result = "";
688             if ( candidate->content ) {
689                 result = candidate->content;
690                 if (strstr (result.c_str (), "\n"))
691                     result = "(字符画)";
692             }
693             if ( candidate->suggest && candidate-> help ) {
694                 result += candidate->suggest;
695                 result += " ";
696                 result += "[";
697                 result += candidate->help;
698                 result += "]";
699             }
700
701             m_lookup_table.appendCandidate (Text (result));
702         }
703     }
704
705     return true;
706 }
707
708 bool
709 ExtEditor::fillChineseNumber(gint64 num)
710 {
711     clearLookupTable();
712
713     DynamicSpecialPhrase phrase ("", 0);
714
715     if ( LABEL_LIST_NUMBERS == m_mode) {
716         for ( int i = 1; i <= 10; ++i )
717             m_lookup_table.setLabel ( i - 1, Text (i - 1 + 'a') );
718     }
719
720     std::string result = phrase.simplified_number(num);
721     if ( !result.empty() ){
722         Text text(result);
723         m_lookup_table.appendCandidate(text);
724     }
725
726     result = phrase.traditional_number(num);
727     if ( !result.empty() ){
728         Text text(result);
729         m_lookup_table.appendCandidate(text);
730     }
731
732     result = phrase.simplest_cn_number(num);
733     if ( !result.empty() ){
734         Text text(result);
735         m_lookup_table.appendCandidate(text);
736     }
737
738     return TRUE;
739 }
740
741 void
742 ExtEditor::clearLookupTable (void)
743 {
744     m_lookup_table.clear ();
745     m_lookup_table.setPageSize (m_config.pageSize ());
746     m_lookup_table.setOrientation (m_config.orientation ());
747 }
748
749 void
750 ExtEditor::updateLookupTable (void)
751 {
752     if (m_lookup_table.size ()) {
753         Editor::updateLookupTableFast (m_lookup_table, TRUE);
754     }
755     else {
756         hideLookupTable ();
757     }
758 }
759
760 void
761 ExtEditor::updatePreeditText (void)
762 {
763     if ( G_UNLIKELY(m_preedit_text.empty ()) ) {
764         hidePreeditText ();
765         return;
766     }
767
768     StaticText preedit_text (m_preedit_text);
769     Editor::updatePreeditText (preedit_text, m_cursor, TRUE);
770 }
771
772 void
773 ExtEditor::updateAuxiliaryText (void)
774 {
775     if ( G_UNLIKELY(m_auxiliary_text.empty ()) ) {
776         hideAuxiliaryText ();
777         return;
778     }
779
780     StaticText aux_text (m_auxiliary_text);
781     Editor::updateAuxiliaryText (aux_text, TRUE);
782 }
783
784 };
785
786