Tizen 2.1 base
[platform/core/uifw/ise-engine-sunpinyin.git] / wrapper / scim / src / sunpinyin_imengine.cpp
1 /*
2  * Copyright (c) 2007 Kov Chai <tchaikov@gmail.com>
3  * Copyright (c) 2012-2013 Samsung Electronics Co., Ltd.
4  *
5  * The contents of this file are subject to the terms of either the GNU Lesser
6  * General Public License Version 2.1 only ("LGPL") or the Common Development and
7  * Distribution License ("CDDL")(collectively, the "License"). You may not use this
8  * file except in compliance with the License. You can obtain a copy of the CDDL at
9  * http://www.opensource.org/licenses/cddl1.php and a copy of the LGPLv2.1 at
10  * http://www.opensource.org/licenses/lgpl-license.php. See the License for the 
11  * specific language governing permissions and limitations under the License. When
12  * distributing the software, include this License Header Notice in each file and
13  * include the full text of the License in the License file as well as the
14  * following notice:
15  * 
16  * NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE
17  * (CDDL)
18  * For Covered Software in this distribution, this License shall be governed by the
19  * laws of the State of California (excluding conflict-of-law provisions).
20  * Any litigation relating to this License shall be subject to the jurisdiction of
21  * the Federal Courts of the Northern District of California and the state courts
22  * of the State of California, with venue lying in Santa Clara County, California.
23  * 
24  * Contributor(s):
25  * 
26  * If you wish your version of this file to be governed by only the CDDL or only
27  * the LGPL Version 2.1, indicate your decision by adding "[Contributor]" elects to
28  * include this software in this distribution under the [CDDL or LGPL Version 2.1]
29  * license." If you don't indicate a single choice of license, a recipient has the
30  * option to distribute your version of this file under either the CDDL or the LGPL
31  * Version 2.1, or to extend the choice of license to its licensees as provided
32  * above. However, if you add LGPL Version 2.1 code and therefore, elected the LGPL
33  * Version 2 license, then the option applies only if the new code is made subject
34  * to such option by the copyright holder. 
35  *
36  * Modifications by Samsung Electronics Co., Ltd.
37  *
38  * 1.Added always candidate show feature
39  * 2.Added auto commit feature for mobile user
40  */
41
42 #define Uses_STL_AUTOPTR
43 #define Uses_STL_FUNCTIONAL
44 #define Uses_STL_VECTOR
45 #define Uses_STL_IOSTREAM
46 #define Uses_STL_FSTREAM
47 #define Uses_STL_ALGORITHM
48 #define Uses_STL_MAP
49 #define Uses_STL_UTILITY
50 #define Uses_STL_IOMANIP
51 #define Uses_C_STDIO
52 #define Uses_SCIM_UTILITY
53 #define Uses_SCIM_IMENGINE
54 #define Uses_SCIM_ICONV
55 #define Uses_SCIM_CONFIG_BASE
56 #define Uses_SCIM_CONFIG_PATH
57 #define Uses_SCIM_LOOKUP_TABLE
58 #define Uses_SCIM_DEBUG
59
60 #include <sys/types.h>
61 #include <sys/stat.h>
62 #include <unistd.h>
63
64 #include <imi_options.h>
65 #include <imi_view.h>
66 #include <ic_history.h>
67
68 #include <scim.h>
69
70 #include "imi_scimwin.h"
71 #include "sunpinyin_utils.h"
72 #include "sunpinyin_keycode.h"
73 #include "sunpinyin_lookup_table.h"
74 #include "sunpinyin_imengine.h"
75 #include "sunpinyin_imengine_config_keys.h"
76 #include "sunpinyin_private.h"
77
78 #define SCIM_PROP_STATUS                  "/IMEngine/SunPinyin/Status"
79 #define SCIM_PROP_LETTER                  "/IMEngine/SunPinyin/Letter"
80 #define SCIM_PROP_PUNCT                   "/IMEngine/SunPinyin/Punct"
81
82 #ifndef SCIM_SUNPINYIN_DATADIR
83     #define SCIM_SUNPINYIN_DATADIR            "/usr/share/scim/sunpinyin"
84 #endif
85
86 #ifndef SCIM_ICONDIR
87     #define SCIM_ICONDIR                      "/usr/share/scim/icons"
88 #endif
89
90 #ifndef SCIM_SUNPINYIN_ICON_FILE
91     #define SCIM_SUNPINYIN_ICON_FILE       (SCIM_ICONDIR "/sunpinyin_logo.png")
92 #endif
93
94 #define SCIM_FULL_LETTER_ICON              (SCIM_ICONDIR "/full-letter.png")
95 #define SCIM_HALF_LETTER_ICON              (SCIM_ICONDIR "/half-letter.png")
96 #define SCIM_FULL_PUNCT_ICON               (SCIM_ICONDIR "/full-punct.png")
97 #define SCIM_HALF_PUNCT_ICON               (SCIM_ICONDIR "/half-punct.png")
98
99 using namespace scim;
100
101 static IMEngineFactoryPointer _scim_pinyin_factory (0); 
102
103 static ConfigPointer _scim_config (0);
104
105 static Property _status_property   (SCIM_PROP_STATUS, "");
106 static Property _letter_property   (SCIM_PROP_LETTER, "");
107 static Property _punct_property    (SCIM_PROP_PUNCT, "");
108
109 static char g_common_symbol[]={'#','$','%','^','&','*','@'};
110
111 extern "C" {
112     void scim_module_init (void)
113     {
114         SCIM_DEBUG_IMENGINE (3) << "scim_module_init\n";
115         bindtextdomain (GETTEXT_PACKAGE, SCIM_SUNPINYIN_LOCALEDIR);
116         bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
117     }
118
119     void scim_module_exit (void)
120     {
121         _scim_pinyin_factory.reset ();
122         _scim_config.reset ();
123     }
124
125     uint32 scim_imengine_module_init (const ConfigPointer &config)
126     {
127         SCIM_DEBUG_IMENGINE (3) << "module_init\n";
128         _status_property.set_tip (_("The status of the current input method. Click to change it."));
129         _status_property.set_label ("英");
130         
131         _letter_property.set_icon (SCIM_HALF_LETTER_ICON);
132         _letter_property.set_tip (_("The input mode of the letters. Click to toggle between half and full."));
133         _letter_property.set_label (_("Full/Half Letter"));
134
135         _punct_property.set_icon (SCIM_HALF_PUNCT_ICON);
136         _punct_property.set_tip (_("The input mode of the puncutations. Click to toggle between half and full."));
137         _punct_property.set_label (_("Full/Half Punct"));
138
139         _scim_config = config;
140         return 1;
141     }
142
143     IMEngineFactoryPointer scim_imengine_module_create_factory (uint32 engine)
144     {
145         SCIM_DEBUG_IMENGINE (3) << "entering scim_imengine_module_create_factory()\n";
146         if (engine != 0) return IMEngineFactoryPointer (0);
147         if (_scim_pinyin_factory.null ()) {
148             SunPyFactory *factory = new SunPyFactory (_scim_config); 
149             if (factory->valid ())
150                 _scim_pinyin_factory = factory;
151             else
152                 delete factory;
153         }
154         return _scim_pinyin_factory;
155     }
156 }
157
158 // implementation of SunPyFactory
159 SunPyFactory::SunPyFactory (const ConfigPointer &config)
160     : m_config (config),
161       m_valid (false)
162 {
163     SCIM_DEBUG_IMENGINE (3) << "SunPyFactory()\n";
164     set_languages ("zh_CN");
165     m_name = utf8_mbstowcs ("SunPinyin");
166     m_valid = init ();
167     m_reload_signal_connection = m_config->signal_connect_reload (slot (this, &SunPyFactory::reload_config));
168     m_hotkey_profile = new CHotkeyProfile();
169 }
170
171 bool
172 SunPyFactory::init ()
173 {
174     bool valid = true;
175     
176     if (m_config) {
177         valid = load_user_config();
178     }
179     
180     // postpone the load_user_data() to the ctor of SunPyInstance
181     return valid;
182 }
183
184 bool
185 SunPyFactory::load_user_config()
186 {
187     // Load configurations.
188
189     return true;
190 }
191
192 SunPyFactory::~SunPyFactory ()
193 {
194     SCIM_DEBUG_IMENGINE (3) << "~SunPyFactory()\n";
195     m_reload_signal_connection.disconnect ();
196     delete m_hotkey_profile;
197 }
198
199 WideString
200 SunPyFactory::get_name () const
201 {
202     return m_name;
203 }
204
205 WideString
206 SunPyFactory::get_authors () const
207 {
208     return utf8_mbstowcs (
209                 String (_("Lei Zhang, <Phill.Zhang@sun.com>; Shuguagn Yan, <Ervin.Yan@sun.com>")));
210 }
211
212 WideString
213 SunPyFactory::get_credits () const
214 {
215     return utf8_mbstowcs (
216         String (_("Ported by Kov Chai, <tchaikov@gmail.com>")));
217 }
218
219 WideString
220 SunPyFactory::get_help () const
221 {
222     String help =
223         String (_("Hot Keys:"
224                   "\n\n  Shift+Alt:\n"
225                   "    Switch between English/Chinese mode."
226                   "\n\n  Control+period:\n"
227                   "    Switch between full/half width punctuation mode."
228                   "\n\n  Shift+space:\n"
229                   "    Switch between full/half width letter mode."
230                   "\n\n  PageUp:\n"
231                   "    Page up in lookup table."
232                   "\n\n  PageDown:\n"
233                   "    Page down in lookup table."
234                   "\n\n  Esc:\n"
235                   "    Cancel current syllable.\n"));
236     return utf8_mbstowcs (help);
237 }
238
239 String
240 SunPyFactory::get_uuid () const
241 {
242     return String ("3240fe82-585a-4f4a-96b3-0cad779c3b51");
243 }
244
245 String
246 SunPyFactory::get_icon_file () const
247 {
248     return String (SCIM_SUNPINYIN_ICON_FILE);
249 }
250
251 IMEngineInstancePointer
252 SunPyFactory::create_instance (const String& encoding, int id)
253 {
254     SCIM_DEBUG_IMENGINE (3) <<  "SunPyFactory::create_instance(" << id << ")\n";    
255     return new SunPyInstance (this, m_hotkey_profile, encoding, id);
256 }
257
258 void
259 SunPyFactory::reload_config (const ConfigPointer &config)
260 {
261     m_config = config;
262     m_valid = init ();
263 }
264
265 // implementation of SunPyInstance
266 SunPyInstance::SunPyInstance (SunPyFactory *factory,
267                               CHotkeyProfile *hotkey_profile,
268                               const String& encoding,
269                               int id)
270     : IMEngineInstanceBase (factory, encoding, id),
271       m_factory (factory),
272       m_pv (0),
273       m_wh (0),
274       m_hotkey_profile (hotkey_profile),
275       m_lookup_table (0),
276       m_common_lookup_table(0),
277       m_focused (false)
278 {
279     SCIM_DEBUG_IMENGINE (3) << get_id() << ": SunPyInstance()\n";
280     create_session(hotkey_profile);
281     if (!m_pv) return;
282     m_reload_signal_connection = factory->m_config->signal_connect_reload (slot (this, &SunPyInstance::reload_config));
283     init_lookup_table_labels ();
284 }
285
286 SunPyInstance::~SunPyInstance ()
287 {
288     SCIM_DEBUG_IMENGINE (3) <<  get_id() << ": ~SunPyInstance()\n";
289     m_reload_signal_connection.disconnect ();
290     destroy_session();
291 }
292
293 static CKeyEvent
294 translate_key(const KeyEvent& key)
295 {
296     // XXX: may need to move this logic into CKeyEvent
297     if (!(key.code & 0xff00) && isprint(key.code) && !isspace(key.code) && !(key.mask & IM_CTRL_MASK)) {
298         // we only care about key_val here
299         return CKeyEvent(0, key.code, key.mask);
300     } else {
301         // what matters is key_code, but ibus sents me key_code as key_val
302         return CKeyEvent(key.code, 0, key.mask);
303     }
304 }
305
306 bool
307 SunPyInstance::process_key_event (const KeyEvent& key)
308 {
309     SCIM_DEBUG_IMENGINE (3) <<  get_id() << ": process_key_event(" << m_focused << ", "  <<
310         key.code << ", " <<
311         key.mask << ", " <<
312         key.layout << ")\n";
313         
314     if (!m_focused) return false;
315
316     CKeyEvent ev = translate_key(key);
317     
318     if ( !m_pv->getStatusAttrValue(CScimWinHandler::STATUS_ID_CN) ) {
319         // we are in English input mode
320         if ( !m_hotkey_profile->isModeSwitchKey(ev) ) {
321             m_hotkey_profile->rememberLastKey(ev);
322             return false;
323         }
324     }
325     return ( key.is_key_release() ||
326              m_pv->onKeyEvent(ev) );
327 }
328
329 void
330 SunPyInstance::select_candidate (unsigned int item)
331 {
332     if(m_lookup_table->number_of_candidates() == 0 && item < sizeof(g_common_symbol)/sizeof(char)) {
333         char _str[2]={g_common_symbol[item],0};
334         commit_string(utf8_mbstowcs(_str));
335         return;
336     }
337     m_pv->onCandidateSelectRequest(item);
338 //  m_pv->makeSelection(item);
339 }
340
341 void
342 SunPyInstance::update_lookup_table_page_size (unsigned int page_size)
343 {
344     if (page_size > 0) {
345         SCIM_DEBUG_IMENGINE (3) << ": update_lookup_table_page_size(" << page_size << ")\n";
346         m_pv->setCandiWindowSize(page_size);
347         m_lookup_table->set_page_size(page_size);
348     }
349 }
350
351 void
352 SunPyInstance::lookup_table_page_up ()
353 {
354     lookup_page_up();
355     m_pv->onCandidatePageRequest(-1, true);
356 }
357
358 void
359 SunPyInstance::lookup_page_up()
360 {
361     m_lookup_table->page_up();
362     //    m_lookup_table->set_page_size(m_pv->s_CandiWindowSize);
363 }
364
365 void
366 SunPyInstance::lookup_page_down()
367 {
368     m_lookup_table->page_down();
369     //    m_lookup_table->set_page_size(m_pv->s_CandiWindowSize);
370 }
371
372 void
373 SunPyInstance::lookup_table_page_down ()
374 {
375     // XXX, it would be great, if View class expose a page_up() method
376     //    m_pv->onKeyEvent(IM_VK_PAGE_DOWN, 0, 0);
377     // classic View overrides this method
378     // but modern View uses the default dummy implementation
379     lookup_page_down ();
380     m_pv->onCandidatePageRequest(1, true);
381 }
382
383 void
384 SunPyInstance::move_preedit_caret (unsigned int /*pos*/)
385 {
386 }
387
388 void
389 SunPyInstance::reset ()
390 {
391     SCIM_DEBUG_IMENGINE (3) << get_id() << ": reset()\n";
392     flush();
393     m_lookup_table->clear ();
394     hide_preedit_string ();
395     //hide_aux_string ();
396     //m_pv->updateWindows(m_pv->clearIC());
397     //refresh_all_properties ();
398     show_lookup_table ();
399     m_pv->updateWindows(CIMIView::PREEDIT_MASK | CIMIView::CANDIDATE_MASK);
400 }
401
402 void
403 SunPyInstance::focus_in ()
404 {
405     SCIM_DEBUG_IMENGINE(3) << get_id() << ": focus_in ()\n";
406     m_focused = true;
407     show_lookup_table ();
408     initialize_all_properties ();
409     
410     hide_preedit_string ();
411     //hide_aux_string ();
412     
413     init_lookup_table_labels ();
414     
415     //hide_aux_string ();
416
417     m_pv->updateWindows(CIMIView::PREEDIT_MASK | CIMIView::CANDIDATE_MASK);
418 }
419
420 void
421 SunPyInstance::focus_out ()
422 {
423     SCIM_DEBUG_IMENGINE(3) << get_id() << ": focus_out ()\n";
424     m_focused = false;
425 }
426
427 void
428 SunPyInstance::flush ()
429 {
430     SCIM_DEBUG_IMENGINE(3) << get_id() << ": flush ()\n";
431     m_pv->onCandidateSelectRequest(0);
432 }
433
434 void
435 SunPyInstance::trigger_property (const String &property)
436 {
437     SCIM_DEBUG_IMENGINE (3) << get_id() << ": trigger_property(" << property << ")\n";
438     
439     if (property == SCIM_PROP_STATUS) {
440         const int is_CN = m_pv->getStatusAttrValue(CIMIWinHandler::STATUS_ID_CN);
441         m_pv->setStatusAttrValue(CIMIWinHandler::STATUS_ID_CN, is_CN?0:1);
442     } else if (property == SCIM_PROP_LETTER) {
443         const int is_fullsymbol = m_pv->getStatusAttrValue(CIMIWinHandler::STATUS_ID_FULLSYMBOL);
444         m_pv->setStatusAttrValue(CIMIWinHandler::STATUS_ID_FULLSYMBOL, is_fullsymbol?0:1);
445     } else if (property == SCIM_PROP_PUNCT) {
446         const int is_fullpunc = m_pv->getStatusAttrValue(CIMIWinHandler::STATUS_ID_FULLPUNC);
447         m_pv->setStatusAttrValue(CIMIWinHandler::STATUS_ID_FULLPUNC, is_fullpunc?0:1);
448     }
449 }
450
451
452 void
453 SunPyInstance::init_lookup_table_labels ()
454 {
455     m_pv->setCandiWindowSize(10);
456     m_lookup_table->set_page_size (10);
457     m_lookup_table->show_cursor ();
458 }
459
460 void
461 SunPyInstance::initialize_all_properties ()
462 {
463     PropertyList proplist;
464
465     proplist.push_back (_status_property);
466     proplist.push_back (_letter_property);
467     proplist.push_back (_punct_property);
468
469     register_properties (proplist);
470     refresh_all_properties ();
471 }
472
473 void
474 SunPyInstance::refresh_all_properties ()
475 {
476     SCIM_DEBUG_IMENGINE (3) << get_id() << ": refresh_all_properties()\n";
477     m_wh->updateStatus(CIMIWinHandler::STATUS_ID_CN,
478                        m_pv->getStatusAttrValue(CIMIWinHandler::STATUS_ID_CN));
479     m_wh->updateStatus(CIMIWinHandler::STATUS_ID_FULLPUNC,
480                        m_pv->getStatusAttrValue(CIMIWinHandler::STATUS_ID_FULLPUNC));
481     m_wh->updateStatus(CIMIWinHandler::STATUS_ID_FULLSYMBOL, 
482                        m_pv->getStatusAttrValue(CIMIWinHandler::STATUS_ID_FULLSYMBOL));
483 }
484
485
486 void
487 SunPyInstance::refresh_status_property(bool cn)
488 {
489     SCIM_DEBUG_IMENGINE (3) << get_id() << ": refresh_status_property(" << cn << ")\n";
490     if (!cn) {
491         reset();
492     }
493     _status_property.set_label(cn ? "中" : "英");
494     update_property(_status_property);
495 }
496
497 void
498 SunPyInstance::refresh_fullsymbol_property(bool full)
499 {
500     SCIM_DEBUG_IMENGINE (3) << get_id() << ": refresh_fullsimbol_property(" << full << ")\n";
501     _letter_property.set_icon(
502         full ? SCIM_FULL_LETTER_ICON : SCIM_HALF_LETTER_ICON);
503     update_property(_letter_property);
504 }
505
506 void
507 SunPyInstance::refresh_fullpunc_property(bool full)
508 {
509     _punct_property.set_icon(
510         full ? SCIM_FULL_PUNCT_ICON : SCIM_HALF_PUNCT_ICON);
511     update_property(_punct_property);
512 }
513
514 void
515 SunPyInstance::create_session(CHotkeyProfile *hotkey_profile)
516 {
517     SCIM_DEBUG_IMENGINE (3) << get_id() <<  ": create_session()\n";
518     AttributeList attrs;
519     CSunpinyinSessionFactory& factory = CSunpinyinSessionFactory::getFactory();
520     factory.setPinyinScheme(CSunpinyinSessionFactory::QUANPIN);
521     factory.setCandiWindowSize(10);
522     m_pv = factory.createSession();
523     if (!m_pv) {
524         SCIM_DEBUG_IMENGINE (3) << get_id() <<  " factory.createSession() failed\n";
525         return;
526     }
527     
528     m_pv->setHotkeyProfile(hotkey_profile);
529
530     m_lookup_table = new SunLookupTable();
531     m_common_lookup_table = new CommonLookupTable();
532     for(int i = 0;i < sizeof(g_common_symbol)/sizeof(char);i++) {
533         char _str[2]={g_common_symbol[i],0};
534         m_common_lookup_table->append_candidate (utf8_mbstowcs ((const char*)_str),attrs);
535     }
536     m_wh = new CScimWinHandler(this, m_lookup_table);
537     m_pv->attachWinHandler(m_wh);
538 }
539
540 void
541 SunPyInstance::destroy_session()
542 {
543     SCIM_DEBUG_IMENGINE (3) << get_id() <<  ": destroy_session()\n";
544     
545     // wh and ic are not pointers, I don't think it's necessary to delete them
546     // either
547     delete m_pv;
548     delete m_wh;
549     delete m_lookup_table;
550     delete m_common_lookup_table;
551     
552     m_pv = 0;
553     m_wh = 0;
554     m_lookup_table = 0;
555 }
556
557 AttributeList
558 SunPyInstance::build_preedit_attribs (const IPreeditString* ppd)
559 {
560     AttributeList attrs;
561     const int sz = ppd->charTypeSize();
562     for (int i = 0; i < sz; ) {
563         const int ct = ppd->charTypeAt(i);
564         if (ct & IPreeditString::ILLEGAL) {
565             const int start = i;
566             for (++i; (i<sz) && (ppd->charTypeAt(i) & IPreeditString::ILLEGAL); ++i) ;
567             attrs.push_back( Attribute(start, i-start,
568                                        SCIM_ATTR_DECORATE, SCIM_ATTR_DECORATE_REVERSE));
569         } else if (ct & IPreeditString::NORMAL_CHAR) {
570             if (ct & IPreeditString::USER_CHOICE) {
571                 const int start = i;
572                 for (++i; (i<sz) && (ppd->charTypeAt(i) & IPreeditString::USER_CHOICE); ++i) ;
573                 attrs.push_back( Attribute(start, i-start,
574                                            SCIM_ATTR_DECORATE, SCIM_ATTR_DECORATE_UNDERLINE));
575             } else {
576                 ++i;
577             }
578         } else {
579             ++i;
580         }
581     }
582     return attrs;
583 }
584
585 void
586 SunPyInstance::redraw_preedit_string (const IPreeditString* ppd)
587 {
588     SCIM_DEBUG_IMENGINE (3) << get_id() <<  ": redraw_preedit_string()\n";
589     if (ppd->size() != 0) {
590         AttributeList attrs;
591         const int caret = ppd->caret();
592         if (caret > 0 && caret <= ppd->size()) {
593             attrs.push_back( Attribute(ppd->candi_start(),
594                                        ppd->charTypeSize(),
595                                        SCIM_ATTR_DECORATE, SCIM_ATTR_DECORATE_REVERSE));
596         }
597         update_preedit_string( wstr_to_widestr(ppd->string(), ppd->size()) );
598         show_preedit_string ();
599         update_preedit_caret (caret);
600     } else {
601         hide_preedit_string ();
602     }
603 }
604
605 void
606 SunPyInstance::redraw_lookup_table(const ICandidateList* pcl)
607 {
608     SCIM_DEBUG_IMENGINE (3) << get_id() << ": redraw_lookup_table()\n";
609     
610     m_lookup_table->update(*pcl);
611     if (m_lookup_table->number_of_candidates()) {
612         update_lookup_table(*m_lookup_table);
613     } else {
614         update_lookup_table(*m_common_lookup_table);
615     }
616 }
617
618 void
619 SunPyInstance::reload_config(const ConfigPointer &config)
620 {
621     SCIM_DEBUG_IMENGINE (3) << get_id() << ": reload_config()\n";
622     reset();
623     if (m_factory->valid()) {
624         m_factory->load_user_config();
625     }
626 }