1 /** @file scim_hangul_imengine.cpp
5 * Smart Common Input Method
7 * Copyright (C) 2004-2006 Choe Hwanjin
8 * Copyright (c) 2004-2006 James Su <suzhe@tsinghua.org.cn>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
24 * $Id: scim_hangul_imengine.cpp,v 1.34 2007/05/27 13:08:07 hwanjin Exp $
27 #define Uses_SCIM_UTILITY
28 #define Uses_SCIM_IMENGINE
29 #define Uses_SCIM_LOOKUP_TABLE
30 #define Uses_SCIM_CONFIG_BASE
39 #include "scim_hangul_imengine.h"
43 #define _(String) dgettext(GETTEXT_PACKAGE,String)
44 #define N_(String) (String)
46 #define _(String) (String)
47 #define N_(String) (String)
48 #define bindtextdomain(Package,Directory)
49 #define textdomain(domain)
50 #define bind_textdomain_codeset(domain,codeset)
53 #define scim_module_init hangul_LTX_scim_module_init
54 #define scim_module_exit hangul_LTX_scim_module_exit
55 #define scim_imengine_module_init hangul_LTX_scim_imengine_module_init
56 #define scim_imengine_module_create_factory hangul_LTX_scim_imengine_module_create_factory
58 #define SCIM_IMENGINE_HANGUL "/IMEngine/Hangul"
60 #define SCIM_CONFIG_PREFIX SCIM_IMENGINE_HANGUL
62 #define SCIM_CONFIG_SHOW_CANDIDATE_COMMENT SCIM_CONFIG_PREFIX "/ShowCandidateComment"
63 #define SCIM_CONFIG_HANGUL_KEY SCIM_CONFIG_PREFIX "/HangulKey"
64 #define SCIM_CONFIG_HANJA_KEY SCIM_CONFIG_PREFIX "/HanjaKey"
65 #define SCIM_CONFIG_HANJA_MODE_KEY SCIM_CONFIG_PREFIX "/HanjaModeKey"
66 #define SCIM_CONFIG_LAYOUT SCIM_CONFIG_PREFIX "/KeyboardLayout"
67 #define SCIM_CONFIG_USE_ASCII_MODE SCIM_CONFIG_PREFIX "/UseAsciiMode"
68 #define SCIM_CONFIG_COMMIT_BY_WORD SCIM_CONFIG_PREFIX "/CommitByWord"
69 #define SCIM_CONFIG_HANJA_MODE SCIM_CONFIG_PREFIX "/HanjaMode"
70 #define SCIM_CONFIG_AUTO_REORDER SCIM_CONFIG_PREFIX "/AutoReorder"
72 #define SCIM_CONFIG_PANEL_LOOKUP_TABLE_VERTICAL "/Panel/Gtk/LookupTableVertical"
74 #define SCIM_PROP_PREFIX "/IMEngine/Hangul"
75 #define SCIM_PROP_HANGUL_MODE SCIM_PROP_PREFIX "/HangulMode"
76 #define SCIM_PROP_HANJA_MODE SCIM_PROP_PREFIX "/HanjaMode"
78 #ifndef SCIM_HANGUL_ICON_FILE
79 #define SCIM_HANGUL_ICON_FILE (SCIM_ICONDIR "/scim-hangul.png")
82 #define SCIM_HANGUL_ICON_ON SCIM_ICONDIR "/scim-hangul-on.png"
83 #define SCIM_HANGUL_ICON_OFF SCIM_ICONDIR "/scim-hangul-off.png"
85 static ConfigPointer _scim_config (0);
87 static Property hangul_mode(SCIM_PROP_HANGUL_MODE, "");
88 static Property hanja_mode(SCIM_PROP_HANJA_MODE, "");
91 void scim_module_init (void)
93 bindtextdomain (GETTEXT_PACKAGE, SCIM_HANGUL_LOCALEDIR);
94 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
97 void scim_module_exit (void)
99 _scim_config.reset ();
102 uint32 scim_imengine_module_init (const ConfigPointer &config)
104 SCIM_DEBUG_IMENGINE(1) << "Initialize Hangul Engine\n";
106 _scim_config = config;
111 IMEngineFactoryPointer scim_imengine_module_create_factory (uint32 engine)
113 HangulFactory *factory = 0;
116 factory = new HangulFactory (_scim_config);
126 HangulFactory::HangulFactory (const ConfigPointer &config)
128 m_uuid = "d75857a5-4148-4745-89e2-1da7ddaf70a9";
129 m_name = _("Korean");
131 m_keyboard_layout = "2";
132 m_show_candidate_comment = true;
133 m_use_ascii_mode = false;
134 m_commit_by_word = false;
135 m_auto_reorder = true;
137 m_hanja_table = hanja_table_load(NULL);
138 m_symbol_table = NULL;
140 std::string symbol_file = getenv("HOME");
141 symbol_file += "/.scim/hangul/symbol.txt";
142 if (access(symbol_file.c_str(), R_OK) == 0)
143 m_symbol_table = hanja_table_load(symbol_file.c_str());
144 if (m_symbol_table == NULL) {
145 symbol_file = SCIM_HANGUL_DATADIR "/symbol.txt";
146 if (access(symbol_file.c_str(), R_OK) == 0)
147 m_symbol_table = hanja_table_load(symbol_file.c_str());
150 set_languages ("ko");
152 reload_config(m_config);
154 m_reload_signal_connection = m_config->signal_connect_reload(slot(this, &HangulFactory::reload_config));
157 HangulFactory::~HangulFactory ()
159 m_reload_signal_connection.disconnect();
160 if (m_hanja_table != NULL)
161 hanja_table_delete(m_hanja_table);
163 if (m_symbol_table != NULL)
164 hanja_table_delete(m_symbol_table);
168 HangulFactory::get_name () const
170 return utf8_mbstowcs (m_name);
174 HangulFactory::get_authors () const
176 return utf8_mbstowcs (String (_("Copyright (C) 2006 Choe Hwanjin <choe.hwanjin@gmail.com>")));
180 HangulFactory::get_credits () const
182 return WideString ();
186 HangulFactory::get_help () const
189 _("Key bindings:\n");
193 scim_key_list_to_string(hangul_keys, m_hangul_keys);
194 scim_key_list_to_string(hanja_keys, m_hanja_keys);
196 char paragraph1[512];
197 char paragraph2[512];
199 snprintf(paragraph1, sizeof(paragraph1),
200 _(" Hangul key: %s\n"
201 " This key binding is to switch the input mode between the ASCII input \n"
202 " mode and the hangul input mode.\n"), hangul_keys.c_str());
203 snprintf(paragraph2, sizeof(paragraph2),
205 " This key binding is to convert a hangul character to a hanja character.\n"), hanja_keys.c_str());
207 return utf8_mbstowcs (header)
208 + utf8_mbstowcs (paragraph1)
209 + utf8_mbstowcs (paragraph2);
213 HangulFactory::get_uuid () const
219 HangulFactory::get_icon_file () const
221 return String (SCIM_HANGUL_ICON_FILE);
225 HangulFactory::reload_config(const ConfigPointer &config)
230 m_show_candidate_comment = config->read(String(SCIM_CONFIG_SHOW_CANDIDATE_COMMENT),
231 m_show_candidate_comment);
233 m_keyboard_layout = config->read(String(SCIM_CONFIG_LAYOUT), String("2"));
235 m_use_ascii_mode = config->read(String(SCIM_CONFIG_USE_ASCII_MODE),
237 m_commit_by_word = config->read(String(SCIM_CONFIG_COMMIT_BY_WORD),
239 m_hanja_mode = config->read(String(SCIM_CONFIG_HANJA_MODE),
241 m_auto_reorder = config->read(String(SCIM_CONFIG_AUTO_REORDER),
245 str = config->read(String(SCIM_CONFIG_HANGUL_KEY),
246 String("Hangul,Shift+space"));
247 scim_string_to_key_list(m_hangul_keys, str);
249 str = config->read(String (SCIM_CONFIG_HANJA_KEY),
250 String ("Hangul_Hanja,F9"));
251 scim_string_to_key_list(m_hanja_keys, str);
253 str = config->read(String (SCIM_CONFIG_HANJA_MODE_KEY),
255 scim_string_to_key_list(m_hanja_mode_keys, str);
257 m_lookup_table_vertical = config->read(String(SCIM_CONFIG_PANEL_LOOKUP_TABLE_VERTICAL),
261 IMEngineInstancePointer
262 HangulFactory::create_instance (const String &encoding, int id)
264 SCIM_DEBUG_IMENGINE(1) << "create_instance: HangulInstance.\n";
265 return new HangulInstance (this, encoding, id);
268 HangulInstance::HangulInstance (HangulFactory *factory,
269 const String &encoding,
271 : IMEngineInstanceBase (factory, encoding, id),
274 m_output_mode (OUTPUT_MODE_SYLLABLE)
276 m_hic = hangul_ic_new(factory->m_keyboard_layout.c_str());
277 hangul_ic_connect_callback (m_hic, "transition", (void *)on_transition, this);
280 std::vector <WideString> labels;
282 for (int i = 1; i < 10; ++i) {
283 snprintf (label, sizeof(label), "%d", i);
284 labels.push_back (utf8_mbstowcs (label));
287 m_lookup_table.set_candidate_labels (labels);
289 m_hangul_mode = true;
292 HangulInstance::~HangulInstance ()
297 HangulInstance::candidate_key_event (const KeyEvent &key)
300 case SCIM_KEY_Return:
301 case SCIM_KEY_KP_Enter:
302 select_candidate (m_lookup_table.get_cursor_pos_in_current_page ());
304 case SCIM_KEY_KP_Subtract:
305 m_lookup_table.cursor_up ();
306 update_lookup_table (m_lookup_table);
307 hangul_update_aux_string ();
312 case SCIM_KEY_KP_Add:
313 m_lookup_table.cursor_down ();
314 update_lookup_table (m_lookup_table);
315 hangul_update_aux_string ();
317 case SCIM_KEY_Page_Up:
318 lookup_table_page_up();
320 case SCIM_KEY_Page_Down:
321 case SCIM_KEY_KP_Tab:
322 lookup_table_page_down();
328 if (m_factory->m_lookup_table_vertical) {
329 lookup_table_page_up();
331 m_lookup_table.cursor_up ();
332 update_lookup_table (m_lookup_table);
333 hangul_update_aux_string ();
340 if (m_factory->m_lookup_table_vertical) {
341 lookup_table_page_down();
343 m_lookup_table.cursor_down ();
344 update_lookup_table (m_lookup_table);
345 hangul_update_aux_string ();
352 if (m_factory->m_lookup_table_vertical) {
353 m_lookup_table.cursor_up ();
354 update_lookup_table (m_lookup_table);
355 hangul_update_aux_string ();
357 lookup_table_page_up();
364 if (m_factory->m_lookup_table_vertical) {
365 m_lookup_table.cursor_down ();
366 update_lookup_table (m_lookup_table);
367 hangul_update_aux_string ();
369 lookup_table_page_down();
372 case SCIM_KEY_Escape:
373 delete_candidates ();
384 select_candidate (key.code - SCIM_KEY_1);
387 return !is_hanja_mode();
394 HangulInstance::process_key_event (const KeyEvent& rawkey)
396 SCIM_DEBUG_IMENGINE(1) << "process_key_event.\n";
398 KeyEvent key = rawkey.map_to_layout(SCIM_KEYBOARD_Default);
402 if (use_ascii_mode() && !is_hangul_mode()) {
403 if (is_hangul_key(key)) {
404 toggle_hangul_mode();
411 /* ignore key release. */
412 if (key.is_key_release ())
416 if (use_ascii_mode() && is_hangul_key(key)) {
417 toggle_hangul_mode();
422 if (is_hanja_mode_key (key)) {
426 /* toggle candidate table */
427 if (is_hanja_key (key)) {
428 if (is_hanja_mode()) {
429 update_candidates ();
431 if (m_lookup_table.number_of_candidates ())
432 delete_candidates ();
434 update_candidates ();
440 /* ignore shift keys */
441 if (key.code == SCIM_KEY_Shift_L || key.code == SCIM_KEY_Shift_R)
444 /* flush on modifier-on keys */
445 if (key.is_control_down() || key.is_alt_down()) {
451 if (m_lookup_table.number_of_candidates ()) {
452 if (candidate_key_event(key))
456 /* change to ascii mode on ESCAPE key, for vi users.
457 * We should process this key after processing candidate keys,
458 * or input mode will be changed to non-hangul mode when the user presses
459 * escape key to close candidate window. */
460 if (use_ascii_mode() && !is_hanja_mode()) {
461 if (key.code == SCIM_KEY_Escape) {
462 toggle_hangul_mode();
467 if (is_backspace_key(key)) {
468 bool ret = hangul_ic_backspace(m_hic);
470 hangul_update_preedit_string ();
471 } else if (m_preedit.length() > 0) {
473 m_preedit.erase(m_preedit.length() - 1, 1);
474 hangul_update_preedit_string();
476 if (m_surrounding_text.length() > 0) {
477 m_surrounding_text.erase(m_surrounding_text.length() - 1, 1);
478 if (m_surrounding_text.empty()) {
485 if (is_hanja_mode() && m_lookup_table.number_of_candidates()) {
492 if (key.code >= SCIM_KEY_exclam && key.code <= SCIM_KEY_asciitilde) {
493 /* main hangul composing process */
494 int ascii = key.get_ascii_code();
495 if (key.is_caps_lock_down()) {
497 ascii = tolower(ascii);
498 else if (islower(ascii))
499 ascii = toupper(ascii);
502 bool ret = hangul_ic_process(m_hic, ascii);
505 wstr = get_commit_string ();
506 if (wstr.length ()) {
507 /* Before commit, we set preedit string to null to work arround
508 * some buggy IM implementation, ex) Qt, Evolution */
509 hide_preedit_string ();
510 if (is_hanja_mode() || m_factory->m_commit_by_word) {
517 if (is_hanja_mode() || m_factory->m_commit_by_word) {
518 if (hangul_ic_is_empty(m_hic)) {
523 hangul_update_preedit_string ();
525 if (is_hanja_mode()) {
537 HangulInstance::move_preedit_caret (unsigned int pos)
542 HangulInstance::select_candidate (unsigned int index)
544 SCIM_DEBUG_IMENGINE(2) << "select_candidate.\n";
546 if ((int)index >= m_lookup_table.get_current_page_size ())
549 WideString candidate = m_lookup_table.get_candidate_in_current_page(index);
551 WideString commit_str = candidate;
552 WideString preedit = get_preedit_string();
553 if (is_hanja_mode() || m_factory->m_commit_by_word) {
555 int len = m_surrounding_text.length();
557 delete_surrounding_text(-len, len);
558 if (candidate.length() <= m_surrounding_text.length()) {
559 len = m_surrounding_text.length() - candidate.length();
560 commit_str.append(m_surrounding_text, candidate.length(), len);
561 m_surrounding_text.erase(0, candidate.length());
562 } else if (candidate.length() <= m_surrounding_text.length() + preedit.length()) {
563 len = candidate.length() - m_surrounding_text.length();
564 if (len > (int)m_preedit.length()) {
566 hangul_ic_reset(m_hic);
568 m_preedit.erase(0, len);
570 m_surrounding_text.clear();
573 hangul_ic_reset(m_hic);
574 m_surrounding_text.clear();
578 if (candidate.length() > preedit.length()) {
579 int len = candidate.length() - preedit.length();
580 delete_surrounding_text(-len, len);
582 hangul_ic_reset(m_hic);
583 m_surrounding_text.clear();
586 commit_string(commit_str);
587 hangul_update_preedit_string ();
589 if (is_hanja_mode()) {
597 HangulInstance::update_lookup_table_page_size (unsigned int page_size)
599 SCIM_DEBUG_IMENGINE(2) << "update_lookup_table_page_size.\n";
601 m_lookup_table.set_page_size (page_size);
605 HangulInstance::lookup_table_page_up ()
607 if (!m_lookup_table.number_of_candidates () || !m_lookup_table.get_current_page_start ())
610 SCIM_DEBUG_IMENGINE(2) << "lookup_table_page_up.\n";
612 m_lookup_table.page_up ();
614 update_lookup_table (m_lookup_table);
616 hangul_update_aux_string ();
620 HangulInstance::lookup_table_page_down ()
622 if (m_lookup_table.number_of_candidates () <= 0 ||
623 m_lookup_table.get_current_page_start () + m_lookup_table.get_current_page_size () >=
624 (int)m_lookup_table.number_of_candidates ())
627 SCIM_DEBUG_IMENGINE(2) << "lookup_table_page_down.\n";
629 m_lookup_table.page_down ();
631 update_lookup_table (m_lookup_table);
633 hangul_update_aux_string ();
637 HangulInstance::reset()
639 SCIM_DEBUG_IMENGINE(2) << "reset.\n";
644 HangulInstance::flush()
646 SCIM_DEBUG_IMENGINE(2) << "flush.\n";
648 hide_preedit_string();
650 WideString wstr = m_preedit;
651 const ucschar *str = hangul_ic_flush(m_hic);
653 wstr.push_back (*str++);
658 delete_candidates ();
663 HangulInstance::focus_in ()
665 SCIM_DEBUG_IMENGINE(2) << "focus_in.\n";
667 register_all_properties();
669 hangul_ic_select_keyboard(m_hic, m_factory->m_keyboard_layout.c_str());
671 if (m_lookup_table.number_of_candidates ()) {
672 update_lookup_table (m_lookup_table);
673 show_lookup_table ();
675 hide_lookup_table ();
678 hangul_update_aux_string ();
682 HangulInstance::focus_out ()
684 SCIM_DEBUG_IMENGINE(2) << "focus_out.\n";
689 HangulInstance::trigger_property (const String &property)
691 SCIM_DEBUG_IMENGINE(2) << "trigger_property.\n";
692 if (property == SCIM_PROP_HANGUL_MODE) {
693 toggle_hangul_mode();
694 } else if (property == SCIM_PROP_HANJA_MODE) {
700 HangulInstance::get_candidate_string()
703 if (m_surrounding_text.empty())
704 get_surrounding_text(m_surrounding_text, cursor, 10, 0);
707 for (i = m_surrounding_text.length() - 1; i >= 0; i--) {
708 if (!hangul_is_syllable(m_surrounding_text[i]))
712 m_surrounding_text.erase(0, i + 1);
714 return utf8_wcstombs(m_surrounding_text + get_preedit_string());
718 HangulInstance::update_candidates ()
720 m_lookup_table.clear ();
721 m_candidate_comments.clear ();
723 HanjaList* list = NULL;
725 // search for symbol character
726 // key string for symbol character is like:
727 // 'ㄱ', 'ㄴ', 'ㄷ', etc
728 WideString preeditw = get_preedit_string();
729 if (preeditw.length() == 1) {
730 String key = utf8_wcstombs(preeditw);
731 list = hanja_table_match_suffix(m_factory->m_symbol_table, key.c_str());
736 String str = get_candidate_string();
737 SCIM_DEBUG_IMENGINE(1) << "candidate string: " << str << "\n";
739 if (str.length() > 0) {
740 if (is_hanja_mode() || m_factory->m_commit_by_word) {
741 list = hanja_table_match_prefix(m_factory->m_hanja_table,
744 list = hanja_table_match_suffix(m_factory->m_hanja_table,
751 int n = hanja_list_get_size(list);
752 for (int i = 0; i < n; ++i) {
753 const char* value = hanja_list_get_nth_value(list, i);
754 const char* comment = hanja_list_get_nth_comment(list, i);
755 WideString candidate = utf8_mbstowcs(value, -1);
756 m_lookup_table.append_candidate(candidate);
757 m_candidate_comments.push_back(String(comment));
760 m_lookup_table.set_page_size (9);
761 m_lookup_table.show_cursor ();
763 update_lookup_table (m_lookup_table);
764 show_lookup_table ();
766 hangul_update_aux_string ();
768 hanja_list_delete(list);
771 if (m_lookup_table.number_of_candidates() <= 0) {
777 HangulInstance::delete_candidates ()
779 m_surrounding_text.clear();
780 m_lookup_table.clear ();
781 m_candidate_comments.clear ();
782 hide_lookup_table ();
787 HangulInstance::hangul_update_aux_string ()
789 if (!m_factory->m_show_candidate_comment || !m_lookup_table.number_of_candidates ()) {
794 size_t cursor = m_lookup_table.get_cursor_pos ();
796 if (cursor >= m_candidate_comments.size ()) {
801 update_aux_string (m_lookup_table.get_candidate (cursor) + utf8_mbstowcs (String (" : ") + m_candidate_comments [cursor]));
806 HangulInstance::hangul_update_preedit_string ()
808 WideString wstr = get_preedit_string ();
810 if (wstr.length ()) {
812 attrs.push_back(Attribute(0, m_preedit.length(), SCIM_ATTR_DECORATE, SCIM_ATTR_DECORATE_UNDERLINE));
813 attrs.push_back(Attribute(m_preedit.length(), wstr.length() - m_preedit.length(), SCIM_ATTR_DECORATE, SCIM_ATTR_DECORATE_UNDERLINE));
814 show_preedit_string ();
815 update_preedit_string (wstr, attrs);
816 update_preedit_caret (wstr.length());
818 hide_preedit_string ();
823 HangulInstance::match_key_event (const KeyEventList &keys, const KeyEvent &key) const
825 KeyEventList::const_iterator kit;
827 for (kit = keys.begin (); kit != keys.end (); ++kit) {
828 if (!key.is_key_release()) {
829 if (key.code == kit->code) {
831 // we should ignore capslock and numlock
832 mask &= ~SCIM_KEY_CapsLockMask;
833 mask &= ~SCIM_KEY_NumLockMask;
834 if (mask == kit->mask)
843 HangulInstance::toggle_hangul_mode()
845 m_hangul_mode = !m_hangul_mode;
849 hangul_mode.set_label("한");
851 hangul_mode.set_label("A");
854 update_property(hangul_mode);
858 HangulInstance::toggle_hanja_mode()
860 m_factory->m_hanja_mode = !m_factory->m_hanja_mode;
862 if (m_factory->m_hanja_mode) {
863 hanja_mode.set_icon(SCIM_HANGUL_ICON_ON);
865 hanja_mode.set_icon(SCIM_HANGUL_ICON_OFF);
868 update_property(hanja_mode);
870 m_factory->m_config->write(String(SCIM_CONFIG_HANJA_MODE), m_factory->m_hanja_mode);
874 HangulInstance::register_all_properties()
876 PropertyList proplist;
878 if (use_ascii_mode()) {
880 hangul_mode.set_label("한");
882 hangul_mode.set_label("A");
884 proplist.push_back(hangul_mode);
887 if (m_factory->m_hanja_mode) {
888 hanja_mode.set_icon(SCIM_HANGUL_ICON_ON);
890 hanja_mode.set_icon(SCIM_HANGUL_ICON_OFF);
892 hanja_mode.set_label(_("Hanja Lock"));
893 proplist.push_back(hanja_mode);
895 register_properties(proplist);
899 HangulInstance::on_transition (HangulInputContext *hic,
901 const ucschar *preedit,
904 HangulInstance *self = reinterpret_cast<HangulInstance*>(data);
906 if (!self->m_factory->m_auto_reorder) {
907 if (hangul_is_choseong (c)) {
908 if (hangul_ic_has_jungseong (hic) || hangul_ic_has_jongseong (hic))
912 if (hangul_is_jungseong (c)) {
913 if (hangul_ic_has_jongseong (hic))
921 // vim: sts=4 sw=4 nowrap ai expandtab