1 /***************************************************************************
2  *   Copyright (C) 2010~2011 by CSSlayer                                   *
3  *   wengxt@gmail.com                                                      *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.              *
19  ***************************************************************************/
20 
21 #include <memory>
22 #include <sstream>
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <pinyin.h>
28 #include <fcitx/ime.h>
29 #include <fcitx-config/hotkey.h>
30 #include <fcitx-config/xdg.h>
31 #include <fcitx-utils/log.h>
32 #include <fcitx-config/fcitx-config.h>
33 #include <fcitx-utils/utils.h>
34 #include <fcitx/instance.h>
35 #include <fcitx/keys.h>
36 #include <fcitx/module.h>
37 #include <fcitx/context.h>
38 #include <fcitx/module/punc/fcitx-punc.h>
39 #include <string>
40 #include <libintl.h>
41 
42 #include "config.h"
43 #include "eim.h"
44 #include "enummap.h"
45 #include "bus.h"
46 #include "common.h"
47 #include "utils.h"
48 
49 #define FCITX_LIBPINYIN_MAX(x, y) ((x) > (y)? (x) : (y))
50 #define FCITX_LIBPINYIN_MIN(x, y) ((x) < (y)? (x) : (y))
51 extern "C" {
52     FCITX_DEFINE_PLUGIN(fcitx_libpinyin, ime2, FcitxIMClass2) = {
53         FcitxLibPinyinCreate,
54         FcitxLibPinyinDestroy,
55         FcitxLibPinyinReloadConfig,
56         NULL,
57         NULL,
58         NULL,
59         NULL,
60         NULL,
61     };
62 }
63 
64 typedef struct _FcitxLibPinyinCandWord {
65     boolean ispunc;
66     int idx;
67 } FcitxLibPinyinCandWord;
68 
69 CONFIG_DEFINE_LOAD_AND_SAVE(FcitxLibPinyinConfig, FcitxLibPinyinConfig, "fcitx-libpinyin")
70 
71 static void FcitxLibPinyinReconfigure(FcitxLibPinyinAddonInstance* libpinyin);
72 static void FcitxLibPinyinSave(void* arg);
73 static bool LibPinyinCheckZhuyinKey(FcitxKeySym sym, FCITX_ZHUYIN_LAYOUT layout, boolean useTone) ;
74 
75 static const char* input_keys [] = {
76     "1qaz2wsxedcrfv5tgbyhnujm8ik,9ol.0p;/-",       /* standard kb */
77     "1234567890-qwertyuiopasdfghjkl;zxcvbn",       /* IBM */
78     "2wsx3edcrfvtgb6yhnujm8ik,9ol.0p;/-['=",       /* Gin-yieh */
79     "bpmfdtnlvkhg7c,./j;'sexuaorwiqzy890-=",       /* ET  */
80     0
81 };
82 
83 static const char* tone_keys [] = {
84     "7634 ",       /* standard kb */
85     "/m,. ",       /* IBM */
86     "1qaz ",       /* Gin-yieh */
87     "1234 ",       /* ET  */
88     0
89 };
90 
91 static const FcitxKeyState candidateModifierMap[] = {
92     FcitxKeyState_Ctrl,
93     FcitxKeyState_Alt,
94     FcitxKeyState_Shift,
95 };
96 
97 static const FcitxHotkey FCITX_LIBPINYIN_SHIFT_ENTER[2] = {
98     {NULL, FcitxKey_Return, FcitxKeyState_Shift},
99     {NULL, FcitxKey_None, FcitxKeyState_None}
100 };
101 
LibPinyinCheckZhuyinKey(FcitxKeySym sym,FCITX_ZHUYIN_LAYOUT layout,boolean useTone)102 bool LibPinyinCheckZhuyinKey(FcitxKeySym sym, FCITX_ZHUYIN_LAYOUT layout, boolean useTone)
103 {
104     char key = sym & 0xff;
105     const char* keys = input_keys[layout];
106     const char* tones = tone_keys[layout];
107     if (strchr(keys, key))
108         return true;
109 
110     if (useTone && strchr(tones, key))
111         return true;
112 
113     return false;
114 }
115 
offset() const116 int FcitxLibPinyin::offset() const
117 {
118     if (!m_fixedString.empty()) {
119         return m_fixedString.back().first;
120     }
121     return 0;
122 }
123 
pinyinOffset() const124 int FcitxLibPinyin::pinyinOffset() const
125 {
126     if (!m_fixedString.empty()) {
127         return m_fixedString.back().second;
128     }
129     return 0;
130 }
131 
132 /**
133  * @brief Reset the status.
134  *
135  **/
FcitxLibPinyinReset(void * arg)136 void FcitxLibPinyinReset(void* arg)
137 {
138     FcitxLibPinyin* libpinyin = (FcitxLibPinyin*) arg;
139     libpinyin->reset();
140 }
141 
reset()142 void FcitxLibPinyin::reset() {
143     m_buf.clear();
144     m_cursorPos = 0;
145     m_parsedLen = 0;
146     m_fixedString.clear();
147     if (m_inst) {
148         pinyin_reset(m_inst);
149     }
150 }
151 
parse(const char * str)152 size_t FcitxLibPinyin::parse(const char* str)
153 {
154     switch (m_type) {
155     case LPT_Pinyin:
156         return pinyin_parse_more_full_pinyins(m_inst, str);
157     case LPT_Shuangpin:
158         return pinyin_parse_more_double_pinyins(m_inst, str);
159     case LPT_Zhuyin:
160         return pinyin_parse_more_chewings(m_inst, str);
161     }
162     return 0;
163 }
164 
165 /**
166  * @brief Process Key Input and return the status
167  *
168  * @param keycode keycode from XKeyEvent
169  * @param state state from XKeyEvent
170  * @param count count from XKeyEvent
171  * @return INPUT_RETURN_VALUE
172  **/
FcitxLibPinyinDoInput(void * arg,FcitxKeySym sym,unsigned int state)173 INPUT_RETURN_VALUE FcitxLibPinyinDoInput(void* arg, FcitxKeySym sym, unsigned int state)
174 {
175     FcitxLibPinyin* libpinyin = (FcitxLibPinyin*) arg;
176     return libpinyin->doInput(sym, state);
177 }
178 
doInput(FcitxKeySym sym,unsigned int state)179 INPUT_RETURN_VALUE FcitxLibPinyin::doInput(FcitxKeySym sym, unsigned int state) {
180     FcitxLibPinyinConfig* config = &m_owner->config;
181     FcitxInstance* instance = m_owner->owner;
182     FcitxInputState* input = FcitxInstanceGetInputState(instance);
183 
184     if (FcitxHotkeyIsHotKeySimple(sym, state)) {
185         /* there is some special case that ';' is used */
186         if (FcitxHotkeyIsHotKeyLAZ(sym, state)
187                 || sym == '\''
188                 || (FcitxHotkeyIsHotKey(sym, state, FCITX_SEMICOLON) && m_type == LPT_Shuangpin && (config->spScheme == FCITX_SHUANG_PIN_MS || config->spScheme == FCITX_SHUANG_PIN_ZIGUANG))
189                 || (m_type == LPT_Zhuyin && LibPinyinCheckZhuyinKey(sym, config->zhuyinLayout, config->useTone))
190            ) {
191             if (m_buf.size() == 0 && (sym == '\'' || sym == ';'))
192                 return IRV_TO_PROCESS;
193 
194             if (m_buf.size() < MAX_PINYIN_INPUT) {
195                 m_buf.insert(m_cursorPos, 1, (char)(sym & 0xff));
196                 m_cursorPos ++;
197 
198                 m_parsedLen =parse(m_buf.c_str());
199 
200                 if (pinyin_get_parsed_input_length(m_inst) == 0 && m_buf.size() == 1 && m_type != LPT_Shuangpin
201                         && !(m_type == LPT_Pinyin && !m_owner->config.incomplete)
202                         && !(m_type == LPT_Zhuyin && !m_owner->config.zhuyinIncomplete)) {
203                     reset();
204                     return IRV_TO_PROCESS;
205                 }
206                 return IRV_DISPLAY_CANDWORDS;
207             } else
208                 return IRV_DO_NOTHING;
209         }
210     }
211 
212     if (FcitxHotkeyIsHotKey(sym, state, FCITX_SPACE)
213             || (m_type == LPT_Zhuyin && FcitxHotkeyIsHotKey(sym, state, FCITX_ENTER))) {
214         size_t len = m_buf.size();
215         if (len == 0)
216             return IRV_TO_PROCESS;
217 
218         return FcitxCandidateWordChooseByIndex(FcitxInputStateGetCandidateList(input), 0);
219     }
220 
221     if ((m_type != LPT_Zhuyin && FcitxHotkeyIsHotKey(sym, state, FCITX_ENTER))
222         || (m_type == LPT_Zhuyin && FcitxHotkeyIsHotKey(sym, state, FCITX_LIBPINYIN_SHIFT_ENTER))) {
223         if (m_buf[0] == 0)
224             return IRV_TO_PROCESS;
225 
226         const std::string sentence = this->sentence(0);
227         if (!sentence.empty()) {
228             int offset = this->offset();
229             int hzlen = 0;
230             if (fcitx_utf8_strlen(sentence.c_str()) > offset) {
231                 hzlen = fcitx_utf8_get_nth_char(sentence.c_str(), offset) - sentence.c_str();
232             } else {
233                 hzlen = sentence.size();
234             }
235 
236             int start = pinyinOffset();
237             int pylen = m_buf.size() - start;
238 
239             if (pylen < 0) {
240                 pylen = 0;
241             }
242 
243             char* buf = (char*) fcitx_utils_malloc0((hzlen + pylen + 1) * sizeof(char));
244             strncpy(buf, sentence.c_str(), hzlen);
245             if (pylen) {
246                 strcpy(&buf[hzlen], &m_buf[start]);
247             }
248             buf[hzlen + pylen] = '\0';
249             FcitxInstanceCommitString(instance, FcitxInstanceGetCurrentIC(instance), buf);
250             free(buf);
251         } else {
252             FcitxInstanceCommitString(instance, FcitxInstanceGetCurrentIC(instance), m_buf.c_str());
253         }
254 
255         return IRV_CLEAN;
256     }
257 
258     if (FcitxHotkeyIsHotKey(sym, state, FCITX_BACKSPACE) || FcitxHotkeyIsHotKey(sym, state, FCITX_DELETE)) {
259         if (m_buf.size() > 0) {
260             if (offset() != 0 && FcitxHotkeyIsHotKey(sym, state, FCITX_BACKSPACE)) {
261                 m_fixedString.pop_back();
262                 pinyin_clear_constraint(m_inst, pinyinOffset());
263             } else {
264                 if (FcitxHotkeyIsHotKey(sym, state, FCITX_BACKSPACE)) {
265                     if (m_cursorPos > 0) {
266                         m_cursorPos -- ;
267                     } else {
268                         return IRV_DO_NOTHING;
269                     }
270                 }
271                 size_t len = m_buf.size();
272                 if (m_cursorPos == (int)len)
273                     return IRV_DO_NOTHING;
274                 m_buf.erase(m_cursorPos, 1);
275                 if (m_buf.empty())
276                     return IRV_CLEAN;
277                 else
278                     m_parsedLen = parse(m_buf.c_str());
279             }
280             return IRV_DISPLAY_CANDWORDS;
281         } else
282             return IRV_TO_PROCESS;
283     } else {
284         if (m_buf.size() > 0) {
285             if (FcitxHotkeyIsHotKey(sym, state, FCITX_LEFT)) {
286                 if (m_cursorPos > 0) {
287                     if (m_cursorPos == pinyinOffset()) {
288                         m_fixedString.pop_back();
289                         pinyin_clear_constraint(m_inst, offset());
290                         return IRV_DISPLAY_CANDWORDS;
291                     } else {
292                         m_cursorPos--;
293                         return IRV_DISPLAY_CANDWORDS;
294                     }
295                 }
296 
297                 return IRV_DO_NOTHING;
298             } else if (FcitxHotkeyIsHotKey(sym, state, FCITX_RIGHT)) {
299                 size_t len = m_buf.size();
300                 if (m_cursorPos < (int) len) {
301                     m_cursorPos ++ ;
302                     return IRV_DISPLAY_CANDWORDS;
303                 }
304                 return IRV_DO_NOTHING;
305             } else if (FcitxHotkeyIsHotKey(sym, state, FCITX_HOME)) {
306                 int offset = pinyinOffset();
307                 if (m_cursorPos != offset) {
308                     m_cursorPos = offset;
309                     return IRV_DISPLAY_CANDWORDS;
310                 }
311                 return IRV_DO_NOTHING;
312             } else if (FcitxHotkeyIsHotKey(sym, state, FCITX_END)) {
313                 size_t len = m_buf.size();
314                 if (m_cursorPos != (int) len) {
315                     m_cursorPos = len ;
316                     return IRV_DISPLAY_CANDWORDS;
317                 }
318                 return IRV_DO_NOTHING;
319             }
320         } else {
321             return IRV_TO_PROCESS;
322         }
323     }
324     return IRV_TO_PROCESS;
325 }
326 
load()327 void FcitxLibPinyin::load()
328 {
329     if (m_inst != NULL)
330         return;
331 
332     FcitxLibPinyinAddonInstance* libpinyinaddon = m_owner;
333 
334     if (m_type == LPT_Zhuyin && m_owner->zhuyin_context == NULL) {
335         char* user_path = FcitxLibPinyinGetUserPath(libpinyinaddon->config.bSimplifiedDataForZhuyin ? LPLT_Simplified : LPLT_Traditional);
336         char* syspath = FcitxLibPinyinGetSysPath(libpinyinaddon->config.bSimplifiedDataForZhuyin ? LPLT_Simplified : LPLT_Traditional);
337         libpinyinaddon->zhuyin_context = pinyin_init(syspath, user_path);
338         free(user_path);
339         free(syspath);
340     }
341 
342     if (m_type != LPT_Zhuyin && m_owner->pinyin_context == NULL) {
343         char* user_path = FcitxLibPinyinGetUserPath(libpinyinaddon->config.bTraditionalDataForPinyin ? LPLT_Traditional : LPLT_Simplified);
344         char* syspath = FcitxLibPinyinGetSysPath(libpinyinaddon->config.bTraditionalDataForPinyin ? LPLT_Traditional : LPLT_Simplified);
345         libpinyinaddon->pinyin_context = pinyin_init(syspath, user_path);
346         free(user_path);
347         free(syspath);
348     }
349 
350     if (m_type == LPT_Zhuyin)
351         m_inst = pinyin_alloc_instance(libpinyinaddon->zhuyin_context);
352     else
353         m_inst = pinyin_alloc_instance(libpinyinaddon->pinyin_context);
354 
355     FcitxLibPinyinReconfigure(libpinyinaddon);
356 }
357 
FcitxLibPinyinInit(void * arg)358 boolean FcitxLibPinyinInit(void* arg)
359 {
360     FcitxLibPinyin* libpinyin = (FcitxLibPinyin*) arg;
361     libpinyin->init();
362     return true;
363 }
364 
init()365 void FcitxLibPinyin::init() {
366     FcitxInstanceSetContext(m_owner->owner, CONTEXT_IM_KEYBOARD_LAYOUT, "us");
367     if (m_type == LPT_Zhuyin) {
368         FcitxInstanceSetContext(m_owner->owner, CONTEXT_ALTERNATIVE_PREVPAGE_KEY, m_owner->config.hkPrevPage);
369         FcitxInstanceSetContext(m_owner->owner, CONTEXT_ALTERNATIVE_NEXTPAGE_KEY, m_owner->config.hkNextPage);
370     }
371 
372     load();
373 }
374 
updatePreedit(const std::string & sentence)375 void FcitxLibPinyin::updatePreedit(const std::string &sentence)
376 {
377     FcitxInstance* instance = m_owner->owner;
378     FcitxInputState* input = FcitxInstanceGetInputState(instance);
379     int offset = this->offset();
380 
381 #if 0
382     if (m_type == LPT_Pinyin) {
383         const gchar* raw_full_pinyin;
384         pinyin_get_raw_full_pinyin(m_inst, &raw_full_pinyin);
385         int libpinyinLen = strlen(raw_full_pinyin);
386         int fcitxLen = m_buf.size();
387         if (fcitxLen != libpinyinLen) {
388             strcpy(m_buf, raw_full_pinyin);
389             m_cursorPos += libpinyinLen - fcitxLen;
390         }
391     }
392 #endif
393 
394     int pyoffset = pinyinOffset();
395     if (pyoffset > m_cursorPos)
396         m_cursorPos = pyoffset;
397 
398     int hzlen = 0;
399     if (fcitx_utf8_strlen(sentence.c_str()) > offset) {
400         hzlen = fcitx_utf8_get_nth_char(sentence.c_str(), offset) - sentence.c_str();
401     } else {
402         hzlen = sentence.size();
403     }
404 
405     if (hzlen > 0) {
406         char* buf = (char*) fcitx_utils_malloc0((hzlen + 1) * sizeof(char));
407         strncpy(buf, sentence.c_str(), hzlen);
408         buf[hzlen] = 0;
409         FcitxMessagesAddMessageAtLast(FcitxInputStateGetPreedit(input), MSG_INPUT, "%s", buf);
410         free(buf);
411     }
412 
413     int charcurpos = hzlen;
414 
415     int lastpos = pyoffset;
416     int curoffset = pyoffset;
417 
418     PinyinKey* pykey = NULL;
419     PinyinKeyPos* pykeypos = NULL;
420     FcitxMessagesAddMessageAtLast(FcitxInputStateGetPreedit(input), MSG_CODE, "");
421     for (int i = pinyinOffset(); i < m_parsedLen; ) {
422         if (!pinyin_get_pinyin_key(m_inst, i, &pykey)) {
423             break;
424         }
425         pinyin_get_pinyin_key_rest(m_inst, i, &pykeypos);
426 
427         guint16 rawBegin = 0, rawEnd = 0;
428         pinyin_get_pinyin_key_rest_positions(m_inst, pykeypos, &rawBegin, &rawEnd);
429         if (lastpos > 0) {
430             FcitxMessagesMessageConcatLast(FcitxInputStateGetPreedit(input), " ");
431             if (curoffset < m_cursorPos)
432                 charcurpos ++;
433             for (int j = lastpos; j < rawBegin; j ++) {
434                 char temp[2] = {'\0', '\0'};
435                 temp[0] = m_buf[j];
436                 FcitxMessagesMessageConcatLast(FcitxInputStateGetPreedit(input), temp);
437                 if (curoffset < m_cursorPos) {
438                     curoffset ++;
439                     charcurpos ++;
440                 }
441             }
442             if (lastpos < rawBegin) {
443                 FcitxMessagesMessageConcatLast(FcitxInputStateGetPreedit(input), " ");
444                 if (curoffset < m_cursorPos)
445                     charcurpos ++;
446             }
447         }
448         lastpos = rawEnd;
449 
450         bool breakAhead = false;
451         switch (m_type) {
452         case LPT_Pinyin: {
453             gchar* pystring;
454             pinyin_get_pinyin_string(m_inst, pykey, &pystring);
455             if (!pystring) {
456                 breakAhead = true;
457                 break;
458             }
459             FcitxMessagesMessageConcatLast(FcitxInputStateGetPreedit(input), pystring);
460             size_t pylen = strlen(pystring);
461             if (curoffset + pylen < m_cursorPos) {
462                 curoffset += pylen;
463                 charcurpos += pylen;
464             } else {
465                 charcurpos += m_cursorPos - curoffset;
466                 curoffset = m_cursorPos;
467             }
468             g_free(pystring);
469             break;
470         }
471         case LPT_Shuangpin: {
472             guint16 pykeyposLen = 0;
473             pinyin_get_pinyin_key_rest_length(m_inst, pykeypos, &pykeyposLen);
474             if (pykeyposLen == 2) {
475                 const gchar* initial = NULL;
476                 gchar* middle_final = NULL, *_initial = NULL;
477                 pinyin_get_pinyin_strings(m_inst, pykey, &_initial, &middle_final);
478                 if (!_initial[0])
479                     initial = "'";
480                 else
481                     initial = _initial;
482                 if (curoffset + 1 <= m_cursorPos) {
483                     curoffset += 1;
484                     charcurpos += strlen(initial);
485                 }
486                 FcitxMessagesMessageConcatLast(FcitxInputStateGetPreedit(input), initial);
487 
488                 if (curoffset + 1 <= m_cursorPos) {
489                     curoffset += 1;
490                     charcurpos += strlen(middle_final);
491                 }
492                 FcitxMessagesMessageConcatLast(FcitxInputStateGetPreedit(input), middle_final);
493                 g_free(_initial);
494                 g_free(middle_final);
495             } else if (pykeyposLen == 1) {
496                 gchar* pystring;
497                 pinyin_get_pinyin_string(m_inst, pykey, &pystring);
498                 if (curoffset + 1 <= m_cursorPos) {
499                     curoffset += 1;
500                     charcurpos += strlen(pystring);
501                 }
502                 FcitxMessagesMessageConcatLast(FcitxInputStateGetPreedit(input), pystring);
503                 g_free(pystring);
504             }
505             break;
506         }
507         case LPT_Zhuyin: {
508             guint16 pykeyposLen = 0;
509             pinyin_get_pinyin_key_rest_length(m_inst, pykeypos, &pykeyposLen);
510             gchar* pystring;
511             pinyin_get_zhuyin_string(m_inst, pykey, &pystring);
512             // libpinyin would give us null when it is something like "xi'"
513             if (!pystring) {
514                 breakAhead = true;
515                 break;
516             }
517             FcitxMessagesMessageConcatLast(FcitxInputStateGetPreedit(input), pystring);
518 
519             if (curoffset + pykeyposLen <= m_cursorPos) {
520                 curoffset += pykeyposLen;
521                 charcurpos += strlen(pystring);
522             } else {
523                 int diff = m_cursorPos - curoffset;
524                 curoffset = m_cursorPos;
525                 size_t len = fcitx_utf8_strlen(pystring);
526 
527                 if (diff > len)
528                     charcurpos += strlen(pystring);
529                 else {
530                     charcurpos += fcitx_utf8_get_nth_char(pystring, diff) - pystring;
531                 }
532             }
533             g_free(pystring);
534             break;
535         }
536         }
537         if (breakAhead) {
538             break;
539         }
540         // assertion would happen if pinyin string is sth like "xi'"
541         // so we use breakAhead to track such case. It's ugly but also works.
542         size_t nexti;
543         if (pinyin_get_right_pinyin_offset(m_inst, i, &nexti)) {
544             i = nexti;
545         } else {
546             break;
547         }
548     }
549 
550     int buflen = m_buf.size();
551 
552     if (lastpos < buflen) {
553         if (FcitxMessagesGetMessageCount(FcitxInputStateGetPreedit(input))) {
554             FcitxMessagesMessageConcatLast(FcitxInputStateGetPreedit(input), " ");
555             if (lastpos < m_cursorPos)
556                 charcurpos ++;
557         }
558 
559         for (int i = lastpos; i < buflen; i ++) {
560             char temp[2] = {'\0', '\0'};
561             temp[0] = m_buf[i];
562             FcitxMessagesMessageConcatLast(FcitxInputStateGetPreedit(input), temp);
563             if (lastpos < m_cursorPos) {
564                 charcurpos ++;
565                 lastpos++;
566             }
567         }
568     }
569     FcitxInputStateSetCursorPos(input, charcurpos);
570 }
571 
sentence(guint8 index)572 std::string FcitxLibPinyin::sentence(guint8 index)
573 {
574     char* sentence = NULL;
575     pinyin_get_sentence(m_inst, index, &sentence);
576     std::string result = sentence ? sentence : "";
577     g_free(sentence);
578     return result;
579 }
580 
581 /**
582  * @brief function DoInput has done everything for us.
583  *
584  * @param searchMode
585  * @return INPUT_RETURN_VALUE
586  **/
FcitxLibPinyinGetCandWords(void * arg)587 INPUT_RETURN_VALUE FcitxLibPinyinGetCandWords(void* arg)
588 {
589     FcitxLibPinyin* libpinyin = (FcitxLibPinyin*)arg;
590     return libpinyin->getCandWords();
591 }
592 
getCandWords()593 INPUT_RETURN_VALUE FcitxLibPinyin::getCandWords() {
594     FcitxInstance* instance = m_owner->owner;
595     FcitxInputState* input = FcitxInstanceGetInputState(instance);
596     FcitxGlobalConfig* config = FcitxInstanceGetGlobalConfig(m_owner->owner);
597     FcitxLibPinyinConfig* pyConfig = &m_owner->config;
598     struct _FcitxCandidateWordList* candList = FcitxInputStateGetCandidateList(input);
599     FcitxCandidateWordSetPageSize(candList, config->iMaxCandWord);
600     FcitxUICloseInputWindow(instance);
601     strcpy(FcitxInputStateGetRawInputBuffer(input), m_buf.c_str());
602     FcitxInputStateSetRawInputBufferSize(input, m_buf.size());
603     FcitxInputStateSetShowCursor(input, true);
604     FcitxInputStateSetClientCursorPos(input, 0);
605 
606     if (m_type == LPT_Zhuyin) {
607         FcitxKeyState state = candidateModifierMap[pyConfig->candidateModifiers];
608         FcitxCandidateWordSetChooseAndModifier(candList, "1234567890", state);
609     } else
610         FcitxCandidateWordSetChoose(candList, "1234567890");
611 
612     /* add punc */
613     if (m_type == LPT_Zhuyin
614             && m_buf.size() == 1
615             && LibPinyinCheckZhuyinKey((FcitxKeySym) m_buf[0], pyConfig->zhuyinLayout, pyConfig->useTone)
616             && (m_buf[0] >= ' ' && m_buf[0] <= '\x7e') /* simple */
617             && !(m_buf[0] >= 'a' && m_buf[0] <= 'z') /* not a-z */
618             && !(m_buf[0] >= 'A' && m_buf[0] <= 'Z') /* not A-Z /*/
619             && !(m_buf[0] >= '0' && m_buf[0] <= '9') /* not digit */
620        ) {
621         int c = m_buf[0];
622         char* result = FcitxPuncGetPunc(instance, &c);
623         if (result) {
624             FcitxCandidateWord candWord;
625             FcitxLibPinyinCandWord* pyCand = (FcitxLibPinyinCandWord*) fcitx_utils_malloc0(sizeof(FcitxLibPinyinCandWord));
626             pyCand->ispunc = true;
627             candWord.callback = FcitxLibPinyinGetCandWord;
628             candWord.extraType = MSG_OTHER;
629             candWord.owner = this;
630             candWord.priv = pyCand;
631             candWord.strExtra = NULL;
632             candWord.strWord = strdup(result);
633             candWord.wordType = MSG_OTHER;
634 
635             FcitxCandidateWordAppend(FcitxInputStateGetCandidateList(input), &candWord);
636         }
637     }
638     pinyin_guess_sentence(m_inst);
639     const std::string sentence = this->sentence(0);
640     if (!sentence.empty()) {
641         updatePreedit(sentence.c_str());
642 
643         FcitxMessagesAddMessageAtLast(FcitxInputStateGetClientPreedit(input), MSG_INPUT, "%s", sentence.c_str());
644         if (m_buf.size() >= m_parsedLen) {
645             FcitxMessagesAddMessageAtLast(FcitxInputStateGetClientPreedit(input), MSG_INPUT, "%s", m_buf.substr(m_parsedLen).c_str());
646         }
647     } else {
648         FcitxInputStateSetCursorPos(input, m_cursorPos);
649         FcitxMessagesAddMessageAtLast(FcitxInputStateGetClientPreedit(input), MSG_INPUT, "%s", m_buf.c_str());
650         FcitxMessagesAddMessageAtLast(FcitxInputStateGetPreedit(input), MSG_INPUT, "%s", m_buf.c_str());
651     }
652 
653     if (pinyinOffset() < m_parsedLen) {
654         pinyin_guess_candidates(m_inst, pinyinOffset(), FcitxLibPinyinTransSortOption(pyConfig->sort));
655         guint candidateLen = 0;
656         pinyin_get_n_candidate(m_inst, &candidateLen);
657         int i = 0;
658         for (i = 0 ; i < candidateLen; i ++) {
659             lookup_candidate_t* token = NULL;
660             pinyin_get_candidate(m_inst, i, &token);
661             FcitxCandidateWord candWord;
662             FcitxLibPinyinCandWord* pyCand = (FcitxLibPinyinCandWord*) fcitx_utils_malloc0(sizeof(FcitxLibPinyinCandWord));
663             pyCand->ispunc = false;
664             pyCand->idx = i;
665             candWord.callback = FcitxLibPinyinGetCandWord;
666             candWord.extraType = MSG_OTHER;
667             candWord.owner = this;
668             candWord.priv = pyCand;
669             candWord.strExtra = NULL;
670 
671             const gchar* phrase_string = NULL;
672             pinyin_get_candidate_string(m_inst, token, &phrase_string);
673             candWord.strWord = strdup(phrase_string);
674             candWord.wordType = MSG_OTHER;
675 
676             FcitxCandidateWordAppend(FcitxInputStateGetCandidateList(input), &candWord);
677         }
678     } else {
679         FcitxCandidateWord candWord;
680         FcitxLibPinyinCandWord* pyCand = (FcitxLibPinyinCandWord*) fcitx_utils_malloc0(sizeof(FcitxLibPinyinCandWord));
681         pyCand->ispunc = false;
682         pyCand->idx = -1;
683         candWord.callback = FcitxLibPinyinGetCandWord;
684         candWord.extraType = MSG_OTHER;
685         candWord.owner = this;
686         candWord.priv = pyCand;
687         candWord.strExtra = NULL;
688         std::string cand;
689         if (m_buf.size() >= m_parsedLen) {
690             cand += m_buf.substr(m_parsedLen);
691         }
692 
693         candWord.strWord = strdup(cand.c_str());
694         candWord.wordType = MSG_OTHER;
695 
696         FcitxCandidateWordAppend(FcitxInputStateGetCandidateList(input), &candWord);
697     }
698 
699 
700     return IRV_DISPLAY_CANDWORDS;
701 }
702 
703 /**
704  * @brief get the candidate word by index
705  *
706  * @param iIndex index of candidate word
707  * @return the string of canidate word
708  **/
FcitxLibPinyinGetCandWord(void * arg,FcitxCandidateWord * candWord)709 INPUT_RETURN_VALUE FcitxLibPinyinGetCandWord(void* arg, FcitxCandidateWord* candWord)
710 {
711     FcitxLibPinyin* libpinyin = (FcitxLibPinyin*)arg;
712     return libpinyin->getCandWord(candWord);
713 }
714 
getCandWord(FcitxCandidateWord * candWord)715 INPUT_RETURN_VALUE FcitxLibPinyin::getCandWord(FcitxCandidateWord* candWord) {
716     FcitxLibPinyinCandWord* pyCand = (FcitxLibPinyinCandWord*) candWord->priv;
717     FcitxInstance* instance = m_owner->owner;
718     FcitxInputState* input = FcitxInstanceGetInputState(instance);
719 
720     if (pyCand->ispunc) {
721         strcpy(FcitxInputStateGetOutputString(input), candWord->strWord);
722         return IRV_COMMIT_STRING;
723     } else if (pyCand->idx < 0) {
724         strcpy(FcitxInputStateGetOutputString(input), (sentence(0) + candWord->strWord).c_str());
725         return IRV_COMMIT_STRING;
726     } else {
727         guint candidateLen = 0;
728         pinyin_get_n_candidate(m_inst, &candidateLen);
729         if (candidateLen <= pyCand->idx)
730             return IRV_TO_PROCESS;
731         lookup_candidate_t* cand = NULL;
732         pinyin_get_candidate(m_inst, pyCand->idx, &cand);
733         int newOffset = pinyin_choose_candidate(m_inst, pinyinOffset(), cand);
734         if (newOffset != pinyinOffset()) {
735             const gchar* phrase_string = NULL;
736             pinyin_get_candidate_string(m_inst, cand, &phrase_string);
737             m_fixedString.push_back(std::make_pair(offset() + fcitx_utf8_strlen(phrase_string), newOffset));
738         }
739 
740         if (pinyinOffset() == m_parsedLen) {
741             if (m_parsedLen == m_buf.size()) {
742                 pinyin_guess_sentence(m_inst);
743                 const std::string sentence = this->sentence(0);
744                 if (!sentence.empty()) {
745                     strcpy(FcitxInputStateGetOutputString(input), sentence.c_str());
746                     pinyin_train(m_inst, 0);
747                 } else
748                     strcpy(FcitxInputStateGetOutputString(input), "");
749 
750                 return IRV_COMMIT_STRING;
751             }
752         }
753 
754         int pyoffset = pinyinOffset();
755         if (pyoffset > m_cursorPos)
756             m_cursorPos = pyoffset;
757 
758         return IRV_DISPLAY_CANDWORDS;
759     }
760     return IRV_TO_PROCESS;
761 }
762 
FcitxLibPinyin(FcitxLibPinyinAddonInstance * libpinyinaddon,LIBPINYIN_TYPE type)763 FcitxLibPinyin::FcitxLibPinyin(FcitxLibPinyinAddonInstance* libpinyinaddon, LIBPINYIN_TYPE type) :
764     m_inst(NULL), m_type(type), m_owner(libpinyinaddon)
765 {
766 }
767 
~FcitxLibPinyin()768 FcitxLibPinyin::~FcitxLibPinyin()
769 {
770     if (m_inst)
771         pinyin_free_instance(m_inst);
772 }
773 
LibPinyinSavePinyinWord(void * arg,FcitxModuleFunctionArg args)774 void* LibPinyinSavePinyinWord(void* arg, FcitxModuleFunctionArg args)
775 {
776     FcitxLibPinyinAddonInstance* libpinyinaddon = (FcitxLibPinyinAddonInstance*) arg;
777     FcitxIM* im = FcitxInstanceGetCurrentIM(libpinyinaddon->owner);
778     pinyin_context_t* context = NULL;
779     if (strcmp(im->uniqueName, "pinyin-libpinyin") == 0 ||
780             strcmp(im->uniqueName, "shuangpin-libpinyin") == 0) {
781         context = libpinyinaddon->pinyin_context;
782     }
783     if (!context)
784         return NULL;
785 
786     FcitxLibPinyin* libpinyin = (FcitxLibPinyin*) im->klass;
787     libpinyin->savePinyinWord(static_cast<const char*>(args.args[0]));
788 
789     return NULL;
790 }
791 
savePinyinWord(const char * str)792 void FcitxLibPinyin::savePinyinWord(const char* str) {
793     // valid non empty utf8 string
794     if (fcitx_utf8_check_string(str) && *str) {
795         const char *s = str;
796         while (*s) {
797             uint32_t chr;
798 
799             s = fcitx_utf8_get_char(s, &chr);
800             // ok ok, let's filter out ascii
801             if (chr < 256) {
802                 return;
803             }
804         }
805         pinyin_remember_user_input(m_inst, str, -1);
806     }
807 }
808 
809 /**
810  * @brief initialize the extra input method
811  *
812  * @param arg
813  * @return successful or not
814  **/
FcitxLibPinyinCreate(FcitxInstance * instance)815 void* FcitxLibPinyinCreate(FcitxInstance* instance)
816 {
817     FcitxLibPinyinAddonInstance* libpinyinaddon = (FcitxLibPinyinAddonInstance*) fcitx_utils_malloc0(sizeof(FcitxLibPinyinAddonInstance));
818     bindtextdomain("fcitx-libpinyin", LOCALEDIR);
819     bind_textdomain_codeset("fcitx-libpinyin", "UTF-8");
820     libpinyinaddon->owner = instance;
821     FcitxAddon* addon = FcitxAddonsGetAddonByName(FcitxInstanceGetAddons(instance), "fcitx-libpinyin");
822 
823     if (!FcitxLibPinyinConfigLoadConfig(&libpinyinaddon->config)) {
824         free(libpinyinaddon);
825         return NULL;
826     }
827 
828     libpinyinaddon->pinyin = new FcitxLibPinyin(libpinyinaddon, LPT_Pinyin);
829     libpinyinaddon->shuangpin = new FcitxLibPinyin(libpinyinaddon, LPT_Shuangpin);
830     libpinyinaddon->zhuyin = new FcitxLibPinyin(libpinyinaddon, LPT_Zhuyin);
831 
832     FcitxLibPinyinReconfigure(libpinyinaddon);
833 
834     FcitxInstanceRegisterIM(instance,
835                             libpinyinaddon->pinyin,
836                             "pinyin-libpinyin",
837                             _("Pinyin (LibPinyin)"),
838                             "pinyin-libpinyin",
839                             FcitxLibPinyinInit,
840                             FcitxLibPinyinReset,
841                             FcitxLibPinyinDoInput,
842                             FcitxLibPinyinGetCandWords,
843                             NULL,
844                             FcitxLibPinyinSave,
845                             NULL,
846                             NULL,
847                             5,
848                             libpinyinaddon->config.bTraditionalDataForPinyin ? "zh_TW" : "zh_CN"
849                            );
850 
851     FcitxInstanceRegisterIM(instance,
852                             libpinyinaddon->shuangpin,
853                             "shuangpin-libpinyin",
854                             _("Shuangpin (LibPinyin)"),
855                             "shuangpin-libpinyin",
856                             FcitxLibPinyinInit,
857                             FcitxLibPinyinReset,
858                             FcitxLibPinyinDoInput,
859                             FcitxLibPinyinGetCandWords,
860                             NULL,
861                             FcitxLibPinyinSave,
862                             NULL,
863                             NULL,
864                             5,
865                             libpinyinaddon->config.bTraditionalDataForPinyin ? "zh_TW" : "zh_CN"
866                            );
867 
868     FcitxInstanceRegisterIM(instance,
869                             libpinyinaddon->zhuyin,
870                             "zhuyin-libpinyin",
871                             _("Bopomofo (LibPinyin)"),
872                             "bopomofo",
873                             FcitxLibPinyinInit,
874                             FcitxLibPinyinReset,
875                             FcitxLibPinyinDoInput,
876                             FcitxLibPinyinGetCandWords,
877                             NULL,
878                             FcitxLibPinyinSave,
879                             NULL,
880                             NULL,
881                             5,
882                             libpinyinaddon->config.bSimplifiedDataForZhuyin ? "zh_CN" : "zh_TW"
883                            );
884 
885     FcitxModuleAddFunction(addon, LibPinyinSavePinyinWord);
886 
887     libpinyinaddon->bus = new FcitxLibPinyinBus(libpinyinaddon);
888 
889     return libpinyinaddon;
890 }
891 
892 /**
893  * @brief Destroy the input method while unload it.
894  *
895  * @return int
896  **/
FcitxLibPinyinDestroy(void * arg)897 void FcitxLibPinyinDestroy(void* arg)
898 {
899     FcitxLibPinyinAddonInstance* libpinyin = (FcitxLibPinyinAddonInstance*) arg;
900     delete libpinyin->pinyin;
901     delete libpinyin->zhuyin;
902     delete libpinyin->shuangpin;
903 
904     if (libpinyin->pinyin_context)
905         pinyin_fini(libpinyin->pinyin_context);
906     if (libpinyin->zhuyin_context)
907         pinyin_fini(libpinyin->zhuyin_context);
908 
909     delete libpinyin->bus;
910 
911     free(libpinyin);
912 }
913 
FcitxLibPinyinReconfigure(FcitxLibPinyinAddonInstance * libpinyinaddon)914 void FcitxLibPinyinReconfigure(FcitxLibPinyinAddonInstance* libpinyinaddon)
915 {
916     FcitxLibPinyinConfig* config = &libpinyinaddon->config;
917 
918     if (libpinyinaddon->zhuyin_context) {
919         pinyin_set_zhuyin_scheme(libpinyinaddon->zhuyin_context, FcitxLibPinyinTransZhuyinLayout(config->zhuyinLayout));
920 
921         for (int i = 0; i <= FCITX_ZHUYIN_DICT_LAST; i++) {
922             if (config->dict_zhuyin[i]) {
923                 pinyin_load_addon_phrase_library(libpinyinaddon->zhuyin_context, FcitxLibPinyinTransZhuyinDictionary(static_cast<FCITX_ZHUYIN_DICTIONARY>(i)));
924             } else {
925                 pinyin_unload_addon_phrase_library(libpinyinaddon->zhuyin_context, FcitxLibPinyinTransZhuyinDictionary(static_cast<FCITX_ZHUYIN_DICTIONARY>(i)));
926             }
927         }
928     }
929     if (libpinyinaddon->pinyin_context) {
930         pinyin_set_double_pinyin_scheme(libpinyinaddon->pinyin_context, FcitxLibPinyinTransShuangpinScheme(config->spScheme));
931         for (int i = 0; i <= FCITX_DICT_LAST; i++) {
932             if (config->dict[i]) {
933                 pinyin_load_addon_phrase_library(libpinyinaddon->pinyin_context, FcitxLibPinyinTransDictionary(static_cast<FCITX_DICTIONARY>(i)));
934             } else {
935                 pinyin_unload_addon_phrase_library(libpinyinaddon->pinyin_context, FcitxLibPinyinTransDictionary(static_cast<FCITX_DICTIONARY>(i)));
936             }
937         }
938     }
939     pinyin_option_t settings = 0;
940     settings |= DYNAMIC_ADJUST;
941     settings |= USE_DIVIDED_TABLE | USE_RESPLIT_TABLE;
942     for (int i = 0; i <= FCITX_CR_LAST; i ++) {
943         if (config->cr[i])
944             settings |= FcitxLibPinyinTransCorrection((FCITX_CORRECTION) i);
945     }
946     for (int i = 0; i <= FCITX_AMB_LAST; i ++) {
947         if (config->amb[i])
948             settings |= FcitxLibPinyinTransAmbiguity((FCITX_AMBIGUITY) i);
949     }
950 
951     if (config->incomplete) {
952         settings |= PINYIN_INCOMPLETE;
953     }
954 
955     if (config->zhuyinIncomplete) {
956         settings |= ZHUYIN_INCOMPLETE;
957     }
958 
959     if (config->useTone) {
960         settings |= USE_TONE;
961     }
962     settings |= IS_PINYIN;
963     settings |= IS_ZHUYIN;
964     if (libpinyinaddon->pinyin_context)
965         pinyin_set_options(libpinyinaddon->pinyin_context, settings);
966     if (libpinyinaddon->zhuyin_context)
967         pinyin_set_options(libpinyinaddon->zhuyin_context, settings);
968 }
969 
FcitxLibPinyinReloadConfig(void * arg)970 void FcitxLibPinyinReloadConfig(void* arg)
971 {
972     FcitxLibPinyinAddonInstance* libpinyinaddon = (FcitxLibPinyinAddonInstance*) arg;
973     FcitxLibPinyinConfigLoadConfig(&libpinyinaddon->config);
974     FcitxLibPinyinReconfigure(libpinyinaddon);
975 }
976 
FcitxLibPinyinSave(void * arg)977 void FcitxLibPinyinSave(void* arg)
978 {
979     FcitxLibPinyin* libpinyin = (FcitxLibPinyin*) arg;
980     libpinyin->save();
981 }
982 
save()983 void FcitxLibPinyin::save() {
984     if (m_owner->zhuyin_context)
985         pinyin_save(m_owner->zhuyin_context);
986     if (m_owner->pinyin_context)
987         pinyin_save(m_owner->pinyin_context);
988 }
989 
clearData(int type)990 void FcitxLibPinyin::clearData(int type)
991 {
992     FcitxLibPinyinAddonInstance* libpinyinaddon = m_owner;
993     reset();
994 
995     pinyin_context_t* context = m_type != LPT_Zhuyin ? libpinyinaddon->pinyin_context : libpinyinaddon->zhuyin_context;
996     if (!context) {
997         return;
998     }
999 
1000     switch (type) {
1001     case 0:
1002         pinyin_mask_out(context, PHRASE_INDEX_LIBRARY_MASK, PHRASE_INDEX_MAKE_TOKEN(USER_DICTIONARY, null_token));
1003         pinyin_mask_out(context, PHRASE_INDEX_LIBRARY_MASK, PHRASE_INDEX_MAKE_TOKEN(ADDON_DICTIONARY, null_token));
1004         break;
1005     case 1:
1006         pinyin_mask_out(context, PHRASE_INDEX_LIBRARY_MASK, PHRASE_INDEX_MAKE_TOKEN(NETWORK_DICTIONARY, null_token));
1007         break;
1008     case 2:
1009         pinyin_mask_out(context, 0, 0);
1010         break;
1011     }
1012 
1013     pinyin_train(m_inst, 0);
1014     pinyin_save(context);
1015 }
1016 
import()1017 void FcitxLibPinyin::import()
1018 {
1019     FcitxLibPinyinAddonInstance* libpinyinaddon = m_owner;
1020     reset();
1021     load();
1022 
1023     LIBPINYIN_LANGUAGE_TYPE langType = m_type == LPT_Zhuyin ?
1024                                        (libpinyinaddon->config.bSimplifiedDataForZhuyin ? LPLT_Simplified : LPLT_Traditional)
1025                                            : (libpinyinaddon->config.bTraditionalDataForPinyin ? LPLT_Traditional : LPLT_Simplified);
1026 
1027     pinyin_context_t* context = m_type != LPT_Zhuyin ? libpinyinaddon->pinyin_context : libpinyinaddon->zhuyin_context;
1028     if (!context)
1029         return;
1030 
1031     const char* path = langType == LPLT_Simplified ? "libpinyin/importdict" : "libpinyin/importdict_zhuyin";
1032 
1033     pinyin_mask_out(context, PHRASE_INDEX_LIBRARY_MASK, PHRASE_INDEX_MAKE_TOKEN(NETWORK_DICTIONARY, null_token));
1034 
1035     import_iterator_t* iter = pinyin_begin_add_phrases(context, NETWORK_DICTIONARY);
1036     if (iter == NULL) {
1037         return;
1038     }
1039 
1040     FcitxStringHashSet* sset = FcitxXDGGetFiles(path, NULL, ".txt");
1041     HASH_FOREACH(str, sset, FcitxStringHashSet) {
1042         /* user phrase library should be already loaded here. */
1043         FILE* dictfile = FcitxXDGGetFileWithPrefix(path, str->name, "r", NULL);
1044         if (NULL == dictfile) {
1045             continue;
1046         }
1047 
1048         char* linebuf = NULL;
1049         size_t size = 0;
1050         while (getline(&linebuf, &size, dictfile) != -1) {
1051             if (0 == strlen(linebuf))
1052                 continue;
1053 
1054             if ('\n' == linebuf[strlen(linebuf) - 1]) {
1055                 linebuf[strlen(linebuf) - 1] = '\0';
1056             }
1057 
1058             gchar** items = g_strsplit_set(linebuf, " \t", 3);
1059             guint len = g_strv_length(items);
1060             do {
1061 
1062                 gchar* phrase = NULL, * pinyin = NULL;
1063                 gint count = -1;
1064                 if (2 == len || 3 == len) {
1065                     phrase = items[0];
1066                     pinyin = items[1];
1067                     if (3 == len)
1068                         count = atoi(items[2]);
1069                 } else {
1070                     break;
1071                 }
1072 
1073                 if (!fcitx_utf8_check_string(phrase)) {
1074                     continue;
1075                 }
1076 
1077                 pinyin_iterator_add_phrase(iter, phrase, pinyin, count);
1078             } while (0);
1079 
1080             g_strfreev(items);
1081         }
1082         free(linebuf);
1083         fclose(dictfile);
1084     }
1085 
1086     pinyin_end_add_phrases(iter);
1087 
1088     if (m_inst) {
1089         pinyin_train(m_inst, 0);
1090     }
1091     pinyin_save(context);
1092 }
1093 
1094 // kate: indent-mode cstyle; indent-width 4; replace-tabs on;
1095