1 /* vim:set et ts=4 sts=4:
3 * ibus-pinyin - The Chinese PinYin engine for IBus
5 * Copyright (c) 2010-2011 Peng Wu <alexepico@gmail.com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 #include "PYEnglishEditor.h"
29 #include <glib/gstdio.h>
36 #define DB_BACKUP_TIMEOUT (60)
38 class EnglishDatabase{
45 m_timer = g_timer_new ();
49 g_timer_destroy (m_timer);
50 if (m_timeout_id != 0) {
52 g_source_remove (m_timeout_id);
56 sqlite3_close (m_sqlite);
63 gboolean isDatabaseExisted(const char *filename) {
64 gboolean result = g_file_test (filename, G_FILE_TEST_IS_REGULAR);
68 sqlite3 *tmp_db = NULL;
69 if (sqlite3_open_v2 (filename, &tmp_db,
70 SQLITE_OPEN_READONLY, NULL) != SQLITE_OK){
74 /* Check the desc table */
75 sqlite3_stmt *stmt = NULL;
76 const char *tail = NULL;
77 m_sql = "SELECT value FROM desc WHERE name = 'version';";
78 result = sqlite3_prepare_v2 (tmp_db, m_sql.c_str(), -1, &stmt, &tail);
79 g_assert (result == SQLITE_OK);
80 result = sqlite3_step (stmt);
81 if (result != SQLITE_ROW)
83 result = sqlite3_column_type (stmt, 0);
84 if (result != SQLITE_TEXT)
86 const char *version = (const char *) sqlite3_column_text (stmt, 0);
87 if (strcmp("1.2.0", version ) != 0)
89 result = sqlite3_finalize (stmt);
90 g_assert (result == SQLITE_OK);
91 sqlite3_close (tmp_db);
95 gboolean createDatabase(const char *filename) {
96 /* unlink the old database. */
97 gboolean retval = g_file_test (filename, G_FILE_TEST_IS_REGULAR);
99 int result = g_unlink (filename);
104 char *dirname = g_path_get_dirname (filename);
105 g_mkdir_with_parents (dirname, 0700);
108 sqlite3 *tmp_db = NULL;
109 if (sqlite3_open_v2 (filename, &tmp_db,
110 SQLITE_OPEN_READWRITE |
111 SQLITE_OPEN_CREATE, NULL) != SQLITE_OK) {
115 /* Create DESCription table */
116 m_sql = "BEGIN TRANSACTION;\n";
117 m_sql << "CREATE TABLE IF NOT EXISTS desc (name TEXT PRIMARY KEY, value TEXT);\n";
118 m_sql << "INSERT OR IGNORE INTO desc VALUES ('version', '1.2.0');";
119 m_sql << "COMMIT;\n";
121 if (!executeSQL (tmp_db)) {
122 sqlite3_close (tmp_db);
127 m_sql = "CREATE TABLE IF NOT EXISTS english ("
128 "word TEXT NOT NULL PRIMARY KEY,"
129 "freq FLOAT NOT NULL DEFAULT(0)"
131 if (!executeSQL (tmp_db)) {
132 sqlite3_close (tmp_db);
138 gboolean openDatabase(const char *system_db, const char *user_db){
139 if (!isDatabaseExisted (system_db))
141 if (!isDatabaseExisted (user_db)) {
142 gboolean result = createDatabase (user_db);
146 /* cache the user db name. */
149 /* do database attach here. :) */
150 if (sqlite3_open_v2 (system_db, &m_sqlite,
151 SQLITE_OPEN_READWRITE |
152 SQLITE_OPEN_CREATE, NULL) != SQLITE_OK) {
158 m_sql.printf (SQL_ATTACH_DB, user_db);
159 if (!executeSQL (m_sqlite)) {
160 sqlite3_close (m_sqlite);
169 /* List the words in freq order. */
170 gboolean listWords(const char *prefix, std::vector<std::string> & words){
171 sqlite3_stmt *stmt = NULL;
172 const char *tail = NULL;
176 const char *SQL_DB_LIST =
177 "SELECT word FROM ( "
178 "SELECT * FROM english UNION ALL SELECT * FROM userdb.english) "
179 " WHERE word LIKE '%s%%' GROUP BY word ORDER BY SUM(freq) DESC;";
180 m_sql.printf (SQL_DB_LIST, prefix);
181 int result = sqlite3_prepare_v2 (m_sqlite, m_sql.c_str(), -1, &stmt, &tail);
182 g_assert(result == SQLITE_OK);
183 result = sqlite3_step (stmt);
184 while (result == SQLITE_ROW){
186 result = sqlite3_column_type (stmt, 0);
187 if (result != SQLITE_TEXT)
189 const char *word = (const char *)sqlite3_column_text (stmt, 0);
190 words.push_back (word);
191 result = sqlite3_step (stmt);
193 sqlite3_finalize (stmt);
194 if (result != SQLITE_DONE)
199 /* Get the freq of user sqlite db. */
200 gboolean getWordInfo(const char *word, float & freq){
201 sqlite3_stmt *stmt = NULL;
202 const char *tail = NULL;
204 const char *SQL_DB_SELECT =
205 "SELECT freq FROM userdb.english WHERE word = \"%s\";";
206 m_sql.printf (SQL_DB_SELECT, word);
207 int result = sqlite3_prepare_v2 (m_sqlite, m_sql.c_str(), -1, &stmt, &tail);
208 g_assert (result == SQLITE_OK);
209 result = sqlite3_step (stmt);
210 if (result != SQLITE_ROW)
212 result = sqlite3_column_type (stmt, 0);
213 if (result != SQLITE_FLOAT)
215 freq = sqlite3_column_double (stmt, 0);
216 result = sqlite3_finalize (stmt);
217 g_assert (result == SQLITE_OK);
221 /* Update the freq with delta value. */
222 gboolean updateWord(const char *word, float freq){
223 const char *SQL_DB_UPDATE =
224 "UPDATE userdb.english SET freq = \"%f\" WHERE word = \"%s\";";
225 m_sql.printf (SQL_DB_UPDATE, freq, word);
226 gboolean retval = executeSQL (m_sqlite);
231 /* Insert the word into user db with the initial freq. */
232 gboolean insertWord(const char *word, float freq){
233 const char *SQL_DB_INSERT =
234 "INSERT INTO userdb.english (word, freq) VALUES (\"%s\", \"%f\");";
235 m_sql.printf (SQL_DB_INSERT, word, freq);
236 gboolean retval = executeSQL (m_sqlite);
242 gboolean executeSQL(sqlite3 *sqlite){
243 gchar *errmsg = NULL;
244 if (sqlite3_exec (sqlite, m_sql.c_str (), NULL, NULL, &errmsg)
246 g_warning ("%s: %s", errmsg, m_sql.c_str());
247 sqlite3_free (errmsg);
254 gboolean loadUserDB (void){
255 sqlite3 *userdb = NULL;
256 /* Attach user database */
258 const char *SQL_ATTACH_DB =
259 "ATTACH DATABASE ':memory:' AS userdb;";
260 m_sql.printf (SQL_ATTACH_DB);
261 if (!executeSQL (m_sqlite))
264 /* Note: user db is always created by openDatabase. */
265 if (sqlite3_open_v2 ( m_user_db, &userdb,
266 SQLITE_OPEN_READWRITE |
267 SQLITE_OPEN_CREATE, NULL) != SQLITE_OK)
270 sqlite3_backup *backup = sqlite3_backup_init (m_sqlite, "userdb", userdb, "main");
273 sqlite3_backup_step (backup, -1);
274 sqlite3_backup_finish (backup);
277 sqlite3_close (userdb);
282 sqlite3_close (userdb);
286 gboolean saveUserDB (void){
287 sqlite3 *userdb = NULL;
288 String tmpfile = String(m_user_db) + "-tmp";
290 /* remove tmpfile if it exist */
293 if (sqlite3_open_v2 (tmpfile, &userdb,
294 SQLITE_OPEN_READWRITE |
295 SQLITE_OPEN_CREATE, NULL) != SQLITE_OK)
298 sqlite3_backup *backup = sqlite3_backup_init (userdb, "main", m_sqlite, "userdb");
303 sqlite3_backup_step (backup, -1);
304 sqlite3_backup_finish (backup);
305 sqlite3_close (userdb);
307 g_rename(tmpfile, m_user_db);
312 sqlite3_close (userdb);
317 void modified (void){
318 /* Restart the timer */
319 g_timer_start (m_timer);
321 if (m_timeout_id != 0)
324 m_timeout_id = g_timeout_add_seconds (DB_BACKUP_TIMEOUT,
325 EnglishDatabase::timeoutCallback,
326 static_cast<gpointer> (this));
329 static gboolean timeoutCallback (gpointer data){
330 EnglishDatabase *self = static_cast<EnglishDatabase *> (data);
332 /* Get elapsed time since last modification of database. */
333 guint elapsed = (guint) g_timer_elapsed (self->m_timer, NULL);
335 if (elapsed >= DB_BACKUP_TIMEOUT &&
336 self->saveUserDB ()) {
337 self->m_timeout_id = 0;
346 const char *m_user_db;
352 EnglishEditor::EnglishEditor (PinyinProperties & props, Config &config)
353 : Editor (props, config), m_train_factor (0.1)
355 m_english_database = new EnglishDatabase;
357 gchar *path = g_build_filename (g_get_user_cache_dir (),
358 "ibus", "pinyin", "english-user.db", NULL);
360 gboolean result = m_english_database->openDatabase
361 (".." G_DIR_SEPARATOR_S "data" G_DIR_SEPARATOR_S "english.db",
362 "english-user.db") ||
363 m_english_database->openDatabase
364 (PKGDATADIR G_DIR_SEPARATOR_S "db" G_DIR_SEPARATOR_S "english.db", path);
366 g_warning ("can't open english word list database.\n");
369 EnglishEditor::~EnglishEditor ()
371 delete m_english_database;
372 m_english_database = NULL;
376 EnglishEditor::processKeyEvent (guint keyval, guint keycode, guint modifiers)
378 //IBUS_SHIFT_MASK is removed.
379 modifiers &= (IBUS_CONTROL_MASK |
388 //handle backspace/delete here.
389 if (processEditKey (keyval))
392 //handle page/cursor up/down here.
393 if (processPageKey (keyval))
396 //handle label key select here.
397 if (processLabelKey (keyval))
400 if (processSpace (keyval))
403 if (processEnter (keyval))
406 m_cursor = std::min (m_cursor, (guint)m_text.length ());
408 /* Remember the input string. */
410 g_return_val_if_fail ('v' == keyval, FALSE);
415 g_return_val_if_fail ('v' == m_text[0], FALSE);
416 if ((keyval >= 'a' && keyval <= 'z') ||
417 (keyval >= 'A' && keyval <= 'Z')) {
418 m_text.insert (m_cursor, keyval);
423 /* Deal other staff with updateStateFromInput (). */
424 updateStateFromInput ();
430 EnglishEditor::processEditKey (guint keyval)
436 updateStateFromInput ();
441 updateStateFromInput ();
449 EnglishEditor::processPageKey (guint keyval)
453 if (m_config.commaPeriodPage ()) {
459 if (m_config.minusEqualPage ()) {
465 if (m_config.commaPeriodPage ()) {
471 if (m_config.minusEqualPage ()) {
487 case IBUS_KP_Page_Up:
492 case IBUS_KP_Page_Down:
504 EnglishEditor::processLabelKey (guint keyval)
508 return selectCandidateInPage (keyval - '1');
511 return selectCandidateInPage (9);
518 EnglishEditor::processEnter (guint keyval){
519 if (keyval != IBUS_Return)
522 if (m_text.length () == 0)
525 String preedit = m_text.substr (1);
528 train (preedit.c_str (), m_train_factor);
534 EnglishEditor::processSpace (guint keyval)
536 if (!(keyval == IBUS_space || keyval == IBUS_KP_Space))
539 guint cursor_pos = m_lookup_table.cursorPos ();
540 return selectCandidate (cursor_pos);
544 EnglishEditor::candidateClicked (guint index, guint button, guint state)
546 selectCandidateInPage (index);
550 EnglishEditor::selectCandidateInPage (guint index)
552 guint page_size = m_lookup_table.pageSize ();
553 guint cursor_pos = m_lookup_table.cursorPos ();
555 if (G_UNLIKELY (index >= page_size))
557 index += (cursor_pos / page_size) * page_size;
559 return selectCandidate (index);
563 EnglishEditor::selectCandidate (guint index)
565 if (index >= m_lookup_table.size ())
568 IBusText *candidate = m_lookup_table.getCandidate (index);
569 Text text (candidate);
571 train (candidate->text, m_train_factor);
577 EnglishEditor::updateStateFromInput (void)
579 /* Do parse and candidates update here. */
580 /* prefix v double check here. */
581 if (m_text.empty ()) {
583 m_auxiliary_text = "";
589 if ('v' != m_text[0]) {
590 g_warning ("v is expected in m_text string.\n");
591 m_auxiliary_text = "";
596 m_auxiliary_text = "v";
597 if (1 == m_text.length ()) {
602 m_auxiliary_text += " ";
604 String prefix = m_text.substr (1);
605 m_auxiliary_text += prefix;
607 /* lookup table candidate fill here. */
608 std::vector<std::string> words;
609 gboolean retval = m_english_database->listWords (prefix.c_str (), words);
614 std::vector<std::string>::iterator iter;
615 for (iter = words.begin (); iter != words.end (); ++iter){
617 m_lookup_table.appendCandidate (text);
622 /* Auxiliary Functions */
625 EnglishEditor::pageUp (void)
627 if (G_LIKELY (m_lookup_table.pageUp ())) {
633 EnglishEditor::pageDown (void)
635 if (G_LIKELY (m_lookup_table.pageDown ())) {
641 EnglishEditor::cursorUp (void)
643 if (G_LIKELY (m_lookup_table.cursorUp ())) {
649 EnglishEditor::cursorDown (void)
651 if (G_LIKELY (m_lookup_table.cursorDown ())) {
657 EnglishEditor::update (void)
659 updateLookupTable ();
660 updatePreeditText ();
661 updateAuxiliaryText ();
665 EnglishEditor::reset (void)
668 updateStateFromInput ();
673 EnglishEditor::clearLookupTable (void)
675 m_lookup_table.clear ();
676 m_lookup_table.setPageSize (m_config.pageSize ());
677 m_lookup_table.setOrientation (m_config.orientation ());
681 EnglishEditor::updateLookupTable (void)
683 if (m_lookup_table.size ()) {
684 Editor::updateLookupTableFast (m_lookup_table, TRUE);
692 EnglishEditor::updatePreeditText (void)
694 if (G_UNLIKELY (m_preedit_text.empty ())) {
699 StaticText preedit_text (m_preedit_text);
700 Editor::updatePreeditText (preedit_text, m_cursor, TRUE);
704 EnglishEditor::updateAuxiliaryText (void)
706 if (G_UNLIKELY (m_auxiliary_text.empty ())) {
707 hideAuxiliaryText ();
711 StaticText aux_text (m_auxiliary_text);
712 Editor::updateAuxiliaryText (aux_text, TRUE);
716 EnglishEditor::removeCharBefore (void)
718 if (G_UNLIKELY (m_cursor <= 0)) {
723 if (G_UNLIKELY (m_cursor > m_text.length ())) {
724 m_cursor = m_text.length ();
728 m_text.erase (m_cursor - 1, 1);
729 m_cursor = std::max (0, static_cast<int>(m_cursor) - 1);
734 EnglishEditor::removeCharAfter (void)
736 if (G_UNLIKELY (m_cursor < 0)) {
741 if (G_UNLIKELY (m_cursor >= m_text.length ())) {
742 m_cursor = m_text.length ();
746 m_text.erase (m_cursor, 1);
747 m_cursor = std::min (m_cursor, (guint) m_text.length ());
752 EnglishEditor::train (const char *word, float delta)
755 gboolean retval = m_english_database->getWordInfo (word, freq);
758 m_english_database->updateWord (word, freq);
760 m_english_database->insertWord (word, delta);
767 /* using static initializor to test english database here. */
768 static class TestEnglishDatabase{
770 TestEnglishDatabase (){
771 EnglishDatabase *db = new EnglishDatabase ();
772 bool retval = db->isDatabaseExisted ("/tmp/english-user.db");
774 retval = db->createDatabase ("english-user.db");
776 retval = db->openDatabase ("english.db", "english-user.db");
779 retval = db->getWordInfo ("hello", freq);
780 printf ("word hello:%d, %f.\n", retval, freq);
782 db->updateWord ("hello", 0.1);
784 db->insertWord ("hello", 0.1);
786 printf ("english database test ok.\n");
788 } test_english_database;