Tizen 2.1 base
[platform/core/uifw/ise-engine-sunpinyin.git] / wrapper / ibus / src / sunpinyin_engine.cpp
1 /*
2  * Copyright (c) 2009 Kov Chai <tchaikov@gmail.com>
3  *
4  * The contents of this file are subject to the terms of either the GNU Lesser
5  * General Public License Version 2.1 only ("LGPL") or the Common Development and
6  * Distribution License ("CDDL")(collectively, the "License"). You may not use this
7  * file except in compliance with the License. You can obtain a copy of the CDDL at
8  * http://www.opensource.org/licenses/cddl1.php and a copy of the LGPLv2.1 at
9  * http://www.opensource.org/licenses/lgpl-license.php. See the License for the
10  * specific language governing permissions and limitations under the License. When
11  * distributing the software, include this License Header Notice in each file and
12  * include the full text of the License in the License file as well as the
13  * following notice:
14  *
15  * NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE
16  * (CDDL)
17  * For Covered Software in this distribution, this License shall be governed by the
18  * laws of the State of California (excluding conflict-of-law provisions).
19  * Any litigation relating to this License shall be subject to the jurisdiction of
20  * the Federal Courts of the Northern District of California and the state courts
21  * of the State of California, with venue lying in Santa Clara County, California.
22  *
23  * Contributor(s):
24  *
25  * If you wish your version of this file to be governed by only the CDDL or only
26  * the LGPL Version 2.1, indicate your decision by adding "[Contributor]" elects to
27  * include this software in this distribution under the [CDDL or LGPL Version 2.1]
28  * license." If you don't indicate a single choice of license, a recipient has the
29  * option to distribute your version of this file under either the CDDL or the LGPL
30  * Version 2.1, or to extend the choice of license to its licensees as provided
31  * above. However, if you add LGPL Version 2.1 code and therefore, elected the LGPL
32  * Version 2 license, then the option applies only if the new code is made subject
33  * to such option by the copyright holder.
34  */
35
36 #include <cassert>
37 #include <algorithm>
38 #include <sstream>
39
40 #include <sunpinyin.h>
41
42 #include "sunpinyin_property.h"
43 #include "sunpinyin_lookup_table.h"
44 #include "sunpinyin_config.h"
45 #include "sunpinyin_config_keys.h"
46 #include "imi_ibus_win.h"
47 #include "ibus_portable.h"
48 #include "sunpinyin_engine.h"
49
50 extern ibus::Config config;
51
52 SunPinyinEngine::SunPinyinEngine(IBusEngine *engine)
53     : m_engine(engine),
54       m_status_prop(SunPinyinProperty::create_status_prop(engine)),
55       m_letter_prop(SunPinyinProperty::create_letter_prop(engine)),
56       m_punct_prop(SunPinyinProperty::create_punct_prop(engine)),
57       m_wh(NULL),
58       m_pv(NULL),
59       m_hotkey_profile(NULL)
60 {
61     CSunpinyinSessionFactory& factory = CSunpinyinSessionFactory::getFactory();
62
63     CSunpinyinSessionFactory::EPyScheme pinyin_scheme =
64         m_config.get_py_scheme(CSunpinyinSessionFactory::QUANPIN);
65     factory.setPinyinScheme(pinyin_scheme);
66     if (pinyin_scheme == CSunpinyinSessionFactory::QUANPIN) {
67         update_fuzzy_pinyins();
68         update_correction_pinyins();
69         update_fuzzy_segs();
70     } else {
71         update_shuangpin_type();
72     }
73     update_user_data_dir();
74     update_punct_mappings();
75
76     factory.setCandiWindowSize(m_config.get(CONFIG_GENERAL_PAGE_SIZE, 10));
77
78     m_pv = factory.createSession();
79     if (!m_pv)
80         return;
81
82     m_hotkey_profile = new CHotkeyProfile();
83     m_pv->setHotkeyProfile(m_hotkey_profile);
84
85     m_wh = new CIBusWinHandler(this);
86     m_pv->attachWinHandler(m_wh);
87
88     m_prop_list = ibus_prop_list_new();
89
90     ibus_prop_list_append(m_prop_list, m_status_prop);
91     ibus_prop_list_append(m_prop_list, m_letter_prop);
92     ibus_prop_list_append(m_prop_list, m_punct_prop);
93     ibus_prop_list_append(m_prop_list, m_setup_prop);
94
95     update_config();
96 }
97
98 SunPinyinEngine::~SunPinyinEngine()
99 {
100     if (m_pv) {
101         CSunpinyinSessionFactory& factory =
102             CSunpinyinSessionFactory::getFactory();
103         factory.destroySession(m_pv);
104     }
105
106     delete m_wh;
107     delete m_hotkey_profile;
108 }
109
110 static CKeyEvent
111 translate_key(guint key_val, guint /*key_code*/, guint modifiers)
112 {
113     // XXX: may need to move this logic into CKeyEvent
114     if (key_val > 0x20 && key_val < 0x7f // isprint(key_val) && !isspace(key_val)
115         && !(modifiers & IM_CTRL_MASK)) {
116         // we only care about key_val here
117         return CKeyEvent(key_val, key_val, modifiers);
118     } else {
119         // what matters is key_code, but ibus sents me key_code as key_val
120         return CKeyEvent(key_val, 0, modifiers);
121     }
122 }
123
124 gboolean
125 SunPinyinEngine::process_key_event (guint key_val,
126                                     guint key_code,
127                                     guint modifiers)
128 {
129     CKeyEvent key = translate_key(key_val, key_code, modifiers);
130
131     if (!m_pv->getStatusAttrValue(CIBusWinHandler::STATUS_ID_CN)) {
132         // we are in English input mode
133         if (!m_hotkey_profile->isModeSwitchKey(key)) {
134             m_hotkey_profile->rememberLastKey(key);
135             return FALSE;
136         }
137     } else if (m_hotkey_profile->isModeSwitchKey(key)) {
138         m_pv->onKeyEvent(CKeyEvent(IM_VK_ENTER, 0, 0));
139         m_pv->setStatusAttrValue(CIMIWinHandler::STATUS_ID_CN, false);
140         return TRUE;
141     }
142
143     return m_pv->onKeyEvent(key);
144 }
145
146 void
147 SunPinyinEngine::focus_in ()
148 {
149     ibus_engine_register_properties(m_engine, m_prop_list);
150     m_pv->updateWindows(CIMIView::PREEDIT_MASK | CIMIView::CANDIDATE_MASK);
151 }
152
153 void
154 SunPinyinEngine::focus_out ()
155 {
156     reset();
157 }
158
159 void
160 SunPinyinEngine::reset ()
161 {
162     m_pv->updateWindows(m_pv->clearIC());
163 }
164
165 void
166 SunPinyinEngine::enable ()
167 {
168     bool is_cn = m_config.is_initial_mode_cn();
169     m_status_prop.update(is_cn);
170     m_pv->setStatusAttrValue(CIMIWinHandler::STATUS_ID_CN, is_cn);
171
172     bool is_letter_full = m_config.is_initial_letter_full();
173     m_letter_prop.update(is_letter_full);
174     m_pv->setStatusAttrValue(CIMIWinHandler::STATUS_ID_FULLSYMBOL, is_letter_full);
175
176     bool is_punct_full = m_config.is_initial_punct_full();
177     m_punct_prop.update(is_punct_full);
178     m_pv->setStatusAttrValue(CIMIWinHandler::STATUS_ID_FULLPUNC, is_punct_full);
179 }
180
181 void
182 SunPinyinEngine::disable ()
183 {
184 }
185
186 void
187 SunPinyinEngine::page_up ()
188 {
189     m_pv->onCandidatePageRequest(-1, true /* relative */);
190 }
191
192 void
193 SunPinyinEngine::page_down ()
194 {
195     m_pv->onCandidatePageRequest(1, true /* relative */);
196 }
197
198 void
199 SunPinyinEngine::property_activate (const std::string& property, unsigned /*state*/)
200 {
201     if (m_status_prop.toggle(property)) {
202         m_pv->setStatusAttrValue(CIMIWinHandler::STATUS_ID_CN,
203                                  m_status_prop.state());
204     } else if (m_letter_prop.toggle(property)) {
205         m_pv->setStatusAttrValue(CIMIWinHandler::STATUS_ID_FULLSYMBOL,
206                                  m_letter_prop.state());
207     } else if (m_punct_prop.toggle(property)) {
208         m_pv->setStatusAttrValue(CIMIWinHandler::STATUS_ID_FULLPUNC,
209                                  m_punct_prop.state());
210     } else {
211         // try to launch the setup UI
212         m_setup_prop.launch(property);
213     }
214 }
215
216 void
217 SunPinyinEngine::candidate_clicked (guint index)
218 {
219     m_pv->onCandidateSelectRequest(index);
220 }
221
222 void
223 SunPinyinEngine::cursor_up ()
224 {
225     if (m_lookup_table.cursor_up()) {
226         update_lookup_table();
227     }
228 }
229
230 void
231 SunPinyinEngine::cursor_down ()
232 {
233     if (m_lookup_table.cursor_down()) {
234         update_lookup_table();
235     }
236 }
237
238 bool
239 SunPinyinEngine::onConfigChanged(const COptionEvent& event)
240 {
241     if (event.name == CONFIG_GENERAL_MEMORY_POWER) {
242         update_history_power();
243     } else if (event.name == CONFIG_GENERAL_PAGE_SIZE) {
244         update_cand_window_size();
245     } else if (event.name == CONFIG_GENERAL_CHARSET_LEVEL) {
246         update_charset_level();
247     } else if (event.name == CONFIG_GENERAL_MAX_BEST) {
248         update_max_best();
249     } else if (event.name == CONFIG_GENERAL_MAX_TAIL_CANDIDATE) {
250         update_max_tail_candidate();
251     } else if (event.name == CONFIG_KEYBOARD_MODE_SWITCH) {
252         update_mode_key();
253     } else if (event.name == CONFIG_KEYBOARD_PUNCT_SWITCH) {
254         update_punct_key();
255     } else if (event.name == CONFIG_KEYBOARD_PAGE_COMMA) {
256         update_page_key_comma();
257     } else if (event.name == CONFIG_KEYBOARD_PAGE_MINUS) {
258         update_page_key_minus();
259     } else if (event.name == CONFIG_KEYBOARD_PAGE_BRACKET) {
260         update_page_key_bracket();
261     } else if (event.name == CONFIG_QUANPIN_FUZZYSEGS_ENABLED) {
262         update_fuzzy_segs();
263     } else if (event.name == CONFIG_KEYBOARD_CANCEL_BACKSPACE) {
264         update_cancel_with_backspace();
265     } else if (event.name == CONFIG_KEYBOARD_SMARK_PUNCT) {
266         update_smart_punc();
267     }
268
269     return false;
270 }
271
272 void
273 SunPinyinEngine::update_config()
274 {
275     update_history_power();
276     update_cand_window_size();
277     update_max_best();
278     update_max_tail_candidate();
279     update_charset_level();
280     update_page_key_minus();
281     update_page_key_comma();
282     update_page_key_bracket();
283     update_mode_key();
284     update_punct_key();
285     update_cancel_with_backspace();
286     update_smart_punc();
287     update_punct_mappings();
288     update_candi_delete_key();
289     // update_quanpin_config();
290     // update_shuangpin_config();
291 }
292
293 void
294 SunPinyinEngine::commit_string (const std::wstring& str)
295 {
296     IBusText *text;
297     text = ibus_text_new_from_ucs4((const gunichar*) str.c_str());
298     ibus_engine_commit_text(m_engine, text);
299 }
300
301 void
302 SunPinyinEngine::update_candidates(const ICandidateList& cl)
303 {
304     if (m_lookup_table.update_candidates(cl) > 0)
305         update_lookup_table();
306     else
307         ibus_engine_hide_lookup_table (m_engine);
308 }
309
310 void
311 SunPinyinEngine::update_lookup_table()
312 {
313     ibus_engine_update_lookup_table(m_engine, m_lookup_table, TRUE);
314 }
315
316 bool
317 SunPinyinEngine::is_valid() const
318 {
319     return m_pv != NULL;
320 }
321
322 static int
323 find_embed_preedit_pos(const IPreeditString& preedit)
324 {
325     int mask = IPreeditString::USER_CHOICE & IPreeditString::HANZI_CHAR;
326     for (size_t i = 0; i < preedit.charTypeSize(); i++) {
327         if ((preedit.charTypeAt(i) & mask) == 0) {
328             return i;
329         }
330     }
331     return preedit.charTypeSize();
332 }
333
334 enum {ORANGE = 0xE76F00, GRAY_BLUE = 0x35556B, WHITE = 0xFFFFFF,
335       BLACK = 0x000000};
336
337 void
338 SunPinyinEngine::update_preedit_string(const IPreeditString& preedit)
339 {
340     if (preedit.size() > 0) {
341         wstring wstr(preedit.string());
342         int embed_pos = find_embed_preedit_pos(preedit);
343         wstring embed_wstr = wstr.substr(0, embed_pos);
344         wstring preedit_wstr = wstr.substr(embed_pos);
345         const gunichar* embed_cstr = (const gunichar*) embed_wstr.c_str();
346         const gunichar* preedit_cstr = (const gunichar*) preedit_wstr.c_str();
347         IBusText* preedit_text = ibus_text_new_from_ucs4(preedit_cstr);
348         int caret = preedit.caret() - embed_pos;
349
350         if (preedit.caret() < preedit.size()) {
351             ibus_text_append_attribute(preedit_text, IBUS_ATTR_TYPE_FOREGROUND,
352                                        WHITE, caret, preedit_wstr.size());
353             ibus_text_append_attribute(preedit_text, IBUS_ATTR_TYPE_BACKGROUND,
354                                        GRAY_BLUE, caret, preedit_wstr.size());
355         }
356         ibus_engine_update_auxiliary_text(m_engine, preedit_text, TRUE);
357
358         ibus_engine_update_preedit_text(m_engine,
359                                         ibus_text_new_from_ucs4(embed_cstr),
360                                         preedit.caret(), TRUE);
361
362     } else {
363         ibus_engine_hide_auxiliary_text(m_engine);
364         ibus_engine_hide_preedit_text(m_engine);
365     }
366 }
367
368 void
369 SunPinyinEngine::update_status_property(bool cn)
370 {
371     m_status_prop.update(cn);
372 }
373
374 void
375 SunPinyinEngine::update_punct_property(bool full)
376 {
377     m_punct_prop.update(full);
378 }
379
380 void
381 SunPinyinEngine::update_letter_property(bool full)
382 {
383     m_letter_prop.update(full);
384 }
385
386 void
387 SunPinyinEngine::update_history_power()
388 {
389     unsigned power = m_config.get(CONFIG_GENERAL_MEMORY_POWER, 3);
390     CIMIContext* ic = m_pv->getIC();
391     assert(ic);
392     ic->setHistoryPower(power);
393 }
394
395 void
396 SunPinyinEngine::update_charset_level()
397 {
398     unsigned charset = m_config.get(CONFIG_GENERAL_CHARSET_LEVEL, GBK);
399     CIMIContext* ic = m_pv->getIC();
400     assert(ic);
401     charset &= 3;               // charset can only be 0,1,2 or 3
402     ic->setCharsetLevel(charset);
403 }
404
405 void
406 SunPinyinEngine::update_cand_window_size()
407 {
408     unsigned size = m_config.get(CONFIG_GENERAL_PAGE_SIZE, 10);
409     m_pv->setCandiWindowSize(size);
410 }
411
412 void
413 SunPinyinEngine::update_mode_key()
414 {
415     std::string mode_switch("Shift");
416     mode_switch = m_config.get(CONFIG_KEYBOARD_MODE_SWITCH, mode_switch);
417
418     CKeyEvent shift_l  (IM_VK_SHIFT_L, 0, IM_SHIFT_MASK|IM_RELEASE_MASK);
419     CKeyEvent shift_r  (IM_VK_SHIFT_R, 0, IM_SHIFT_MASK|IM_RELEASE_MASK);
420     CKeyEvent control_l(IM_VK_CONTROL_L, 0, IM_CTRL_MASK|IM_RELEASE_MASK);
421     CKeyEvent control_r(IM_VK_CONTROL_R, 0, IM_CTRL_MASK|IM_RELEASE_MASK);
422
423     if (mode_switch == "Shift") {
424         m_hotkey_profile->removeModeSwitchKey(control_l);
425         m_hotkey_profile->removeModeSwitchKey(control_r);
426         m_hotkey_profile->addModeSwitchKey(shift_l);
427         m_hotkey_profile->addModeSwitchKey(shift_r);
428     } else if (mode_switch == "Control") {
429         m_hotkey_profile->removeModeSwitchKey(shift_l);
430         m_hotkey_profile->removeModeSwitchKey(shift_r);
431         m_hotkey_profile->addModeSwitchKey(control_l);
432         m_hotkey_profile->addModeSwitchKey(control_r);
433     }
434 }
435
436 void
437 SunPinyinEngine::update_punct_key()
438 {
439     std::string punct_switch("ControlComma");
440     punct_switch = m_config.get(CONFIG_KEYBOARD_PUNCT_SWITCH, punct_switch);
441     if (punct_switch == "ControlComma") {
442         m_hotkey_profile->setPunctSwitchKey(CKeyEvent(IM_VK_COMMA, 0, IM_CTRL_MASK));
443     } else if (punct_switch == "ControlPeriod") {
444         m_hotkey_profile->setPunctSwitchKey(CKeyEvent(IM_VK_PERIOD, 0, IM_CTRL_MASK));
445     }
446 }
447
448 void
449 SunPinyinEngine::update_page_key_minus()
450 {
451     update_page_key(CONFIG_KEYBOARD_PAGE_MINUS, false,
452                     IM_VK_MINUS, IM_VK_EQUALS);
453 }
454
455 void
456 SunPinyinEngine::update_page_key_comma()
457 {
458     update_page_key(CONFIG_KEYBOARD_PAGE_COMMA, false,
459                     IM_VK_COMMA, IM_VK_PERIOD);
460 }
461
462 void
463 SunPinyinEngine::update_page_key_bracket()
464 {
465     update_page_key(CONFIG_KEYBOARD_PAGE_BRACKET, false,
466                     IM_VK_OPEN_BRACKET, IM_VK_CLOSE_BRACKET);
467 }
468
469 void
470 SunPinyinEngine::update_page_key(const char* conf_key, bool default_val,
471                             unsigned page_up, unsigned page_down)
472 {
473     bool enabled = m_config.get(conf_key, default_val);
474
475     if (enabled) {
476         m_hotkey_profile->addPageUpKey(CKeyEvent(page_up, 0));
477         m_hotkey_profile->addPageDownKey(CKeyEvent(page_down, 0));
478     } else {
479         m_hotkey_profile->removePageUpKey(CKeyEvent(page_up, 0));
480         m_hotkey_profile->removePageDownKey(CKeyEvent(page_down, 0));
481     }
482 }
483
484 void
485 SunPinyinEngine::update_cancel_with_backspace()
486 {
487     bool enabled = m_config.get(CONFIG_KEYBOARD_CANCEL_BACKSPACE, true);
488     m_pv->setCancelOnBackspace(enabled);
489 }
490
491 void
492 SunPinyinEngine::update_smart_punc()
493 {
494     m_pv->setSmartPunct(m_config.get(CONFIG_KEYBOARD_SMARK_PUNCT, true));
495 }
496
497 void
498 SunPinyinEngine::update_max_best()
499 {
500     if (m_pv->getIC() == NULL) {
501         return;
502     }
503     int oldval = (int) m_pv->getIC()->getMaxBest();
504     m_pv->getIC()->setMaxBest(m_config.get(CONFIG_GENERAL_MAX_BEST, oldval));
505 }
506
507 void
508 SunPinyinEngine::update_max_tail_candidate()
509 {
510     if (m_pv->getIC() == NULL) {
511         return;
512     }
513     int oldval = (int) m_pv->getIC()->getMaxTailCandidateNum();
514     m_pv->getIC()->setMaxTailCandidateNum(
515         m_config.get(CONFIG_GENERAL_MAX_TAIL_CANDIDATE, oldval));
516 }
517
518 string_pairs parse_pairs(const std::vector<std::string>& strings)
519 {
520     string_pairs pairs;
521     for (std::vector<std::string>::const_iterator pair = strings.begin();
522          pair != strings.end(); ++pair) {
523
524         std::string::size_type found = pair->find(':');
525         if (found == pair->npos || pair->length() < 3)
526             continue;
527         if (found == 0 && (*pair)[0] == ':')
528             found = 1;
529
530         pairs.push_back(make_pair(pair->substr(0, found),
531                                   pair->substr(found+1)));
532     }
533     return pairs;
534 }
535
536 // the mappings in default_pairs will override the ones in user_pairs
537 string_pairs merge_pairs(const string_pairs& default_pairs,
538                          const string_pairs& user_pairs)
539 {
540     typedef std::map<std::string, int> Indexes;
541     Indexes indexes;
542     int index = 0;
543     for (string_pairs::const_iterator it = default_pairs.begin();
544          it != default_pairs.end(); ++it, ++index) {
545         Indexes::iterator found = indexes.find(it->first);
546         if (found == indexes.end()) {
547             indexes[it->first] = index;
548         } else {
549             // it is a paired punct.
550             indexes[it->first] = -found->second;
551         }
552     }
553     string_pairs result(default_pairs);
554     for (string_pairs::const_iterator it = user_pairs.begin();
555          it != user_pairs.end(); ++it) {
556         Indexes::iterator found = indexes.find(it->first);
557         if (found == indexes.end()) {
558             result.push_back(*it);
559         } else if (found->second >= 0) {
560             result[found->second] = *it;
561         } else {
562             // it is a paired punct,
563             // but we don't support this kind of mapping yet,
564             // so quietly ignore it.
565         }
566     }
567     return result;
568 }
569
570 void
571 SunPinyinEngine::update_punct_mappings()
572 {
573     CSimplifiedChinesePolicy& policy = ASimplifiedChinesePolicy::instance();
574     if (m_config.get(PINYIN_PUNCTMAPPING_ENABLED, false)) {
575         std::vector<std::string> mappings;
576         mappings = m_config.get(PINYIN_PUNCTMAPPING_MAPPINGS, mappings);
577         string_pairs pairs(merge_pairs(policy.getDefaultPunctMapping(),
578                                        parse_pairs(mappings)));
579         policy.setPunctMapping(pairs);
580     }
581 }
582
583 void
584 SunPinyinEngine::update_user_data_dir()
585 {
586     std::stringstream user_data_dir;
587     user_data_dir << g_get_home_dir()
588                   << G_DIR_SEPARATOR_S << ".sunpinyin";
589     ASimplifiedChinesePolicy::instance().setUserDataDir(user_data_dir.str());
590 }
591
592 void
593 SunPinyinEngine::update_fuzzy_pinyins()
594 {
595     bool enabled = m_config.get(QUANPIN_FUZZY_ENABLED, false);
596     AQuanpinSchemePolicy::instance().setFuzzyForwarding(enabled);
597     AShuangpinSchemePolicy::instance().setFuzzyForwarding(enabled);
598     if (!enabled)
599         return;
600     std::vector<std::string> fuzzy_pinyins;
601     fuzzy_pinyins = m_config.get(QUANPIN_FUZZY_PINYINS, fuzzy_pinyins);
602     AQuanpinSchemePolicy::instance().setFuzzyPinyinPairs(parse_pairs(fuzzy_pinyins));
603     AShuangpinSchemePolicy::instance().setFuzzyPinyinPairs(parse_pairs(fuzzy_pinyins));
604 }
605
606 void
607 SunPinyinEngine::update_correction_pinyins()
608 {
609     bool enabled = m_config.get(QUANPIN_AUTOCORRECTION_ENABLED, false);
610     AQuanpinSchemePolicy::instance().setAutoCorrecting(enabled);
611     if (!enabled)
612         return;
613     std::vector<std::string> correction_pinyins;
614     correction_pinyins = m_config.get(QUANPIN_AUTOCORRECTION_PINYINS, correction_pinyins);
615     AQuanpinSchemePolicy::instance().setAutoCorrectionPairs(parse_pairs(correction_pinyins));
616 }
617
618 void
619 SunPinyinEngine::update_fuzzy_segs()
620 {
621     bool enable_fuzzy_segs = m_config.get(CONFIG_QUANPIN_FUZZYSEGS_ENABLED, false);
622     AQuanpinSchemePolicy::instance().setFuzzySegmentation(enable_fuzzy_segs);
623     bool enable_inner_fuzzy = m_config.get(CONFIG_QUANPIN_INNERFUZZY_ENABLED, false);
624     AQuanpinSchemePolicy::instance().setInnerFuzzySegmentation(CONFIG_QUANPIN_INNERFUZZY_ENABLED);
625 }
626
627 void
628 SunPinyinEngine::update_shuangpin_type()
629 {
630     EShuangpinType shuangpin_type = MS2003;
631     shuangpin_type = (EShuangpinType) m_config.get(SHUANGPIN_TYPE, (int) shuangpin_type);
632     AShuangpinSchemePolicy::instance().setShuangpinType(shuangpin_type);
633 }
634
635 void
636 SunPinyinEngine::update_candi_delete_key()
637 {
638     /* FIXME: need to get candi_delete_key from user's configuration */
639     m_hotkey_profile->setCandiDeleteKey(CKeyEvent(0, 0, IM_ALT_MASK));
640 }