1 /* vim:set et ts=4 sts=4:
3 * ibus-libpinyin - Intelligent Pinyin engine based on libpinyin 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 #include "PYEnglishEditor.h"
30 #include <glib/gstdio.h>
34 #define _(text) (gettext(text))
38 #define DB_BACKUP_TIMEOUT (60)
40 class EnglishDatabase{
47 m_timer = g_timer_new ();
51 g_timer_destroy (m_timer);
52 if (m_timeout_id != 0) {
54 g_source_remove (m_timeout_id);
58 sqlite3_close (m_sqlite);
65 gboolean isDatabaseExisted(const char *filename) {
66 gboolean result = g_file_test (filename, G_FILE_TEST_IS_REGULAR);
70 sqlite3 *tmp_db = NULL;
71 if (sqlite3_open_v2 (filename, &tmp_db,
72 SQLITE_OPEN_READONLY, NULL) != SQLITE_OK){
76 /* Check the desc table */
77 sqlite3_stmt *stmt = NULL;
78 const char *tail = NULL;
79 m_sql = "SELECT value FROM desc WHERE name = 'version';";
80 result = sqlite3_prepare_v2 (tmp_db, m_sql.c_str(), -1, &stmt, &tail);
81 if (result != SQLITE_OK)
84 result = sqlite3_step (stmt);
85 if (result != SQLITE_ROW)
88 result = sqlite3_column_type (stmt, 0);
89 if (result != SQLITE_TEXT)
92 const char *version = (const char *) sqlite3_column_text (stmt, 0);
93 if (strcmp("1.2.0", version ) != 0)
96 result = sqlite3_finalize (stmt);
97 g_assert (result == SQLITE_OK);
98 sqlite3_close (tmp_db);
102 gboolean createDatabase(const char *filename) {
103 /* unlink the old database. */
104 gboolean retval = g_file_test (filename, G_FILE_TEST_IS_REGULAR);
106 int result = g_unlink (filename);
111 char *dirname = g_path_get_dirname (filename);
112 g_mkdir_with_parents (dirname, 0700);
115 sqlite3 *tmp_db = NULL;
116 if (sqlite3_open_v2 (filename, &tmp_db,
117 SQLITE_OPEN_READWRITE |
118 SQLITE_OPEN_CREATE, NULL) != SQLITE_OK) {
122 /* Create DESCription table */
123 m_sql = "BEGIN TRANSACTION;\n";
124 m_sql << "CREATE TABLE IF NOT EXISTS desc (name TEXT PRIMARY KEY, value TEXT);\n";
125 m_sql << "INSERT OR IGNORE INTO desc VALUES ('version', '1.2.0');";
126 m_sql << "COMMIT;\n";
128 if (!executeSQL (tmp_db)) {
129 sqlite3_close (tmp_db);
134 m_sql = "CREATE TABLE IF NOT EXISTS english ("
135 "word TEXT NOT NULL PRIMARY KEY,"
136 "freq FLOAT NOT NULL DEFAULT(0)"
138 if (!executeSQL (tmp_db)) {
139 sqlite3_close (tmp_db);
145 gboolean openDatabase(const char *system_db, const char *user_db){
146 if (!isDatabaseExisted (system_db))
148 if (!isDatabaseExisted (user_db)) {
149 gboolean result = createDatabase (user_db);
153 /* cache the user db name. */
156 /* do database attach here. :) */
157 if (sqlite3_open_v2 (system_db, &m_sqlite,
158 SQLITE_OPEN_READWRITE |
159 SQLITE_OPEN_CREATE, NULL) != SQLITE_OK) {
165 m_sql.printf (SQL_ATTACH_DB, user_db);
166 if (!executeSQL (m_sqlite)) {
167 sqlite3_close (m_sqlite);
176 /* List the words in freq order. */
177 gboolean listWords(const char *prefix, std::vector<std::string> & words){
178 sqlite3_stmt *stmt = NULL;
179 const char *tail = NULL;
183 const char *SQL_DB_LIST =
184 "SELECT word FROM ( "
185 "SELECT * FROM english UNION ALL SELECT * FROM userdb.english) "
186 " WHERE word LIKE \"%s%\" GROUP BY word ORDER BY SUM(freq) DESC;";
187 m_sql.printf (SQL_DB_LIST, prefix);
188 int result = sqlite3_prepare_v2 (m_sqlite, m_sql.c_str(), -1, &stmt, &tail);
189 if (result != SQLITE_OK)
192 result = sqlite3_step (stmt);
193 while (result == SQLITE_ROW){
195 result = sqlite3_column_type (stmt, 0);
196 if (result != SQLITE_TEXT)
199 const char *word = (const char *)sqlite3_column_text (stmt, 0);
200 words.push_back (word);
201 result = sqlite3_step (stmt);
204 sqlite3_finalize (stmt);
205 if (result != SQLITE_DONE)
210 /* Get the freq of user sqlite db. */
211 gboolean getWordInfo(const char *word, float & freq){
212 sqlite3_stmt *stmt = NULL;
213 const char *tail = NULL;
215 const char *SQL_DB_SELECT =
216 "SELECT freq FROM userdb.english WHERE word = \"%s\";";
217 m_sql.printf (SQL_DB_SELECT, word);
218 int result = sqlite3_prepare_v2 (m_sqlite, m_sql.c_str(), -1, &stmt, &tail);
219 g_assert (result == SQLITE_OK);
220 result = sqlite3_step (stmt);
221 if (result != SQLITE_ROW)
223 result = sqlite3_column_type (stmt, 0);
224 if (result != SQLITE_FLOAT)
226 freq = sqlite3_column_double (stmt, 0);
227 result = sqlite3_finalize (stmt);
228 g_assert (result == SQLITE_OK);
232 /* Update the freq with delta value. */
233 gboolean updateWord(const char *word, float freq){
234 const char *SQL_DB_UPDATE =
235 "UPDATE userdb.english SET freq = \"%f\" WHERE word = \"%s\";";
236 m_sql.printf (SQL_DB_UPDATE, freq, word);
237 gboolean retval = executeSQL (m_sqlite);
242 /* Insert the word into user db with the initial freq. */
243 gboolean insertWord(const char *word, float freq){
244 const char *SQL_DB_INSERT =
245 "INSERT INTO userdb.english (word, freq) VALUES (\"%s\", \"%f\");";
246 m_sql.printf (SQL_DB_INSERT, word, freq);
247 gboolean retval = executeSQL (m_sqlite);
253 gboolean executeSQL(sqlite3 *sqlite){
254 gchar *errmsg = NULL;
255 if (sqlite3_exec (sqlite, m_sql.c_str (), NULL, NULL, &errmsg)
257 g_warning ("%s: %s", errmsg, m_sql.c_str());
258 sqlite3_free (errmsg);
265 gboolean loadUserDB (void){
266 sqlite3 *userdb = NULL;
267 /* Attach user database */
269 const char *SQL_ATTACH_DB =
270 "ATTACH DATABASE ':memory:' AS userdb;";
271 m_sql.printf (SQL_ATTACH_DB);
272 if (!executeSQL (m_sqlite))
275 /* Note: user db is always created by openDatabase. */
276 if (sqlite3_open_v2 ( m_user_db, &userdb,
277 SQLITE_OPEN_READWRITE |
278 SQLITE_OPEN_CREATE, NULL) != SQLITE_OK)
281 sqlite3_backup *backup = sqlite3_backup_init (m_sqlite, "userdb", userdb, "main");
284 sqlite3_backup_step (backup, -1);
285 sqlite3_backup_finish (backup);
288 sqlite3_close (userdb);
293 sqlite3_close (userdb);
297 gboolean saveUserDB (void){
298 sqlite3 *userdb = NULL;
299 String tmpfile = String(m_user_db) + "-tmp";
301 /* remove tmpfile if it exist */
304 if (sqlite3_open_v2 (tmpfile, &userdb,
305 SQLITE_OPEN_READWRITE |
306 SQLITE_OPEN_CREATE, NULL) != SQLITE_OK)
309 sqlite3_backup *backup = sqlite3_backup_init (userdb, "main", m_sqlite, "userdb");
314 sqlite3_backup_step (backup, -1);
315 sqlite3_backup_finish (backup);
316 sqlite3_close (userdb);
318 g_rename(tmpfile, m_user_db);
323 sqlite3_close (userdb);
328 void modified (void){
329 /* Restart the timer */
330 g_timer_start (m_timer);
332 if (m_timeout_id != 0)
335 m_timeout_id = g_timeout_add_seconds (DB_BACKUP_TIMEOUT,
336 EnglishDatabase::timeoutCallback,
337 static_cast<gpointer> (this));
340 static gboolean timeoutCallback (gpointer data){
341 EnglishDatabase *self = static_cast<EnglishDatabase *> (data);
343 /* Get elapsed time since last modification of database. */
344 guint elapsed = (guint) g_timer_elapsed (self->m_timer, NULL);
346 if (elapsed >= DB_BACKUP_TIMEOUT &&
347 self->saveUserDB ()) {
348 self->m_timeout_id = 0;
357 const char *m_user_db;
363 EnglishEditor::EnglishEditor (PinyinProperties & props, Config &config)
364 : Editor (props, config), m_train_factor (0.1)
366 m_english_database = new EnglishDatabase;
368 gchar *path = g_build_filename (g_get_user_cache_dir (),
369 "ibus", "pinyin", "english-user.db", NULL);
371 gboolean result = m_english_database->openDatabase
372 (".." G_DIR_SEPARATOR_S "data" G_DIR_SEPARATOR_S "english.db",
373 "english-user.db") ||
374 m_english_database->openDatabase
375 (PKGDATADIR G_DIR_SEPARATOR_S "db" G_DIR_SEPARATOR_S "english.db", path);
377 g_warning ("can't open english word list database.\n");
380 EnglishEditor::~EnglishEditor ()
382 delete m_english_database;
383 m_english_database = NULL;
387 EnglishEditor::processKeyEvent (guint keyval, guint keycode, guint modifiers)
389 //IBUS_SHIFT_MASK is removed.
390 modifiers &= (IBUS_CONTROL_MASK |
399 //handle backspace/delete here.
400 if (processEditKey (keyval))
403 //handle page/cursor up/down here.
404 if (processPageKey (keyval))
407 //handle label key select here.
408 if (processLabelKey (keyval))
411 if (processSpace (keyval))
414 if (processEnter (keyval))
417 m_cursor = std::min (m_cursor, (guint)m_text.length ());
419 /* Remember the input string. */
421 g_return_val_if_fail ('v' == keyval, FALSE);
425 g_return_val_if_fail ('v' == m_text[0], FALSE);
426 if ((keyval >= 'a' && keyval <= 'z') ||
427 (keyval >= 'A' && keyval <= 'Z')) {
428 m_text.insert (m_cursor, keyval);
433 /* Deal other staff with updateStateFromInput (). */
434 updateStateFromInput ();
440 EnglishEditor::processEditKey (guint keyval)
446 updateStateFromInput ();
451 updateStateFromInput ();
459 EnglishEditor::processPageKey (guint keyval)
463 if (m_config.commaPeriodPage ()) {
469 if (m_config.minusEqualPage ()) {
475 if (m_config.commaPeriodPage ()) {
481 if (m_config.minusEqualPage ()) {
497 case IBUS_KP_Page_Up:
502 case IBUS_KP_Page_Down:
514 EnglishEditor::processLabelKey (guint keyval)
518 return selectCandidateInPage (keyval - '1');
521 return selectCandidateInPage (9);
528 EnglishEditor::processEnter (guint keyval)
530 if (keyval != IBUS_Return)
533 if (m_text.length () == 0)
538 train (m_text.c_str (), m_train_factor);
544 EnglishEditor::processSpace (guint keyval)
546 if (!(keyval == IBUS_space || keyval == IBUS_KP_Space))
549 guint cursor_pos = m_lookup_table.cursorPos ();
550 return selectCandidate (cursor_pos);
554 EnglishEditor::candidateClicked (guint index, guint button, guint state)
556 selectCandidateInPage (index);
560 EnglishEditor::selectCandidateInPage (guint index)
562 guint page_size = m_lookup_table.pageSize ();
563 guint cursor_pos = m_lookup_table.cursorPos ();
565 if (G_UNLIKELY (index >= page_size))
567 index += (cursor_pos / page_size) * page_size;
569 return selectCandidate (index);
573 EnglishEditor::selectCandidate (guint index)
575 if (index >= m_lookup_table.size ())
578 IBusText *candidate = m_lookup_table.getCandidate (index);
579 Text text (candidate);
581 train (candidate->text, m_train_factor);
587 EnglishEditor::updateStateFromInput (void)
589 /* Do parse and candidates update here. */
590 /* prefix v double check here. */
591 if (m_text.empty ()) {
593 m_auxiliary_text = "";
599 if ('v' != m_text[0]) {
600 g_warning ("v is expected in m_text string.\n");
601 m_auxiliary_text = "";
606 m_auxiliary_text = "v";
607 if (1 == m_text.length ()) {
610 const char * help_string = _("Please input the english word.");
611 int space_len = std::max ( 0, m_aux_text_len
612 - (int) g_utf8_strlen (help_string, -1));
613 m_auxiliary_text.append (space_len, ' ');
614 m_auxiliary_text += help_string;
619 m_auxiliary_text += " ";
621 String prefix = m_text.substr (1);
622 m_auxiliary_text += prefix;
624 /* lookup table candidate fill here. */
625 std::vector<std::string> words;
626 gboolean retval = m_english_database->listWords (prefix.c_str (), words);
631 std::vector<std::string>::iterator iter;
632 for (iter = words.begin (); iter != words.end (); ++iter){
634 m_lookup_table.appendCandidate (text);
639 /* Auxiliary Functions */
642 EnglishEditor::pageUp (void)
644 if (G_LIKELY (m_lookup_table.pageUp ())) {
650 EnglishEditor::pageDown (void)
652 if (G_LIKELY (m_lookup_table.pageDown ())) {
658 EnglishEditor::cursorUp (void)
660 if (G_LIKELY (m_lookup_table.cursorUp ())) {
666 EnglishEditor::cursorDown (void)
668 if (G_LIKELY (m_lookup_table.cursorDown ())) {
674 EnglishEditor::update (void)
676 updateLookupTable ();
677 updatePreeditText ();
678 updateAuxiliaryText ();
682 EnglishEditor::reset (void)
685 updateStateFromInput ();
690 EnglishEditor::clearLookupTable (void)
692 m_lookup_table.clear ();
693 m_lookup_table.setPageSize (m_config.pageSize ());
694 m_lookup_table.setOrientation (m_config.orientation ());
698 EnglishEditor::updateLookupTable (void)
700 if (m_lookup_table.size ()) {
701 Editor::updateLookupTableFast (m_lookup_table, TRUE);
708 EnglishEditor::updatePreeditText (void)
710 if (G_UNLIKELY (m_preedit_text.empty ())) {
715 StaticText preedit_text (m_preedit_text);
716 Editor::updatePreeditText (preedit_text, m_cursor, TRUE);
720 EnglishEditor::updateAuxiliaryText (void)
722 if (G_UNLIKELY (m_auxiliary_text.empty ())) {
723 hideAuxiliaryText ();
727 StaticText aux_text (m_auxiliary_text);
728 Editor::updateAuxiliaryText (aux_text, TRUE);
732 EnglishEditor::removeCharBefore (void)
734 if (G_UNLIKELY (m_cursor <= 0)) {
739 if (G_UNLIKELY (m_cursor > m_text.length ())) {
740 m_cursor = m_text.length ();
744 m_text.erase (m_cursor - 1, 1);
745 m_cursor = std::max (0, static_cast<int>(m_cursor) - 1);
750 EnglishEditor::removeCharAfter (void)
752 if (G_UNLIKELY (m_cursor < 0)) {
757 if (G_UNLIKELY (m_cursor >= m_text.length ())) {
758 m_cursor = m_text.length ();
762 m_text.erase (m_cursor, 1);
763 m_cursor = std::min (m_cursor, (guint) m_text.length ());
768 EnglishEditor::train (const char *word, float delta)
771 gboolean retval = m_english_database->getWordInfo (word, freq);
774 m_english_database->updateWord (word, freq);
776 m_english_database->insertWord (word, delta);
783 /* using static initializor to test english database here. */
784 static class TestEnglishDatabase{
786 TestEnglishDatabase (){
787 EnglishDatabase *db = new EnglishDatabase ();
788 bool retval = db->isDatabaseExisted ("/tmp/english-user.db");
790 retval = db->createDatabase ("english-user.db");
792 retval = db->openDatabase ("english.db", "english-user.db");
795 retval = db->getWordInfo ("hello", freq);
796 printf ("word hello:%d, %f.\n", retval, freq);
798 db->updateWord ("hello", 0.1);
800 db->insertWord ("hello", 0.1);
802 printf ("english database test ok.\n");
804 } test_english_database;