add English Editor
authorPeng Wu <alexepico@gmail.com>
Fri, 22 Apr 2011 11:31:53 +0000 (07:31 -0400)
committerPeng Huang <shawn.p.huang@gmail.com>
Fri, 22 Apr 2011 11:31:53 +0000 (07:31 -0400)
to speed up English input

BUG=none
TEST=build fine

Review URL: http://codereview.appspot.com/4200041
Patch from Peng Wu <alexepico@gmail.com>.

configure.ac
data/Makefile.am
data/english.awk [new file with mode: 0644]
src/Makefile.am
src/PYEnglishEditor.cc [new file with mode: 0644]
src/PYEnglishEditor.h [new file with mode: 0644]
src/PYExtEditor.cc
src/PYPinyinEngine.cc
src/PYPinyinEngine.h

index 2668288..8ffdd29 100644 (file)
@@ -173,9 +173,18 @@ then
     );
 fi
 
-
 AM_CONDITIONAL(IBUS_BUILD_LUA_EXTENSION, [test x"$enable_lua_extension" = x"yes"])
 
+# --disable-english-input-mode
+AC_ARG_ENABLE(english-input-mode,
+    AS_HELP_STRING([--disable-english-input-mode],
+    [do not build english input mode]),
+    [enable_english_input_mode=$enableval],
+    [enable_english_input_mode=yes]
+)
+
+AM_CONDITIONAL(IBUS_BUILD_ENGLISH_INPUT_MODE, [test x"$enable_english_input_mode" = x"yes"])
+
 # OUTPUT files
 AC_CONFIG_FILES([ po/Makefile.in
 Makefile
@@ -205,5 +214,6 @@ Build options:
     Build database android      $enable_db_android
     Build database open-phrase  $enable_db_open_phrase
     Build lua extension         $enable_lua_extension
+    Build english input mode    $enable_english_input_mode
 ])
 
index d0ca35c..02e8feb 100644 (file)
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
+WORDLIST = wordlist
+ENGLISH_AWK = english.awk
+ENGLISH_DB = english.db
 
 SUBDIRS = \
        db \
        icons \
        $(NULL)
 
+
+EXTRA_DIST = \
+       $(WORDLIST) \
+       $(ENGLISH_AWK) \
+       $(NULL)
+
+english_db_DATA = \
+        $(ENGLISH_DB) \
+        $(NULL)
+english_dbdir = $(pkgdatadir)/db
+
+$(ENGLISH_DB): $(WORDLIST)
+       $(AWK) -f english.awk $< |sqlite3 $@
diff --git a/data/english.awk b/data/english.awk
new file mode 100644 (file)
index 0000000..1828d2c
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/bin/awk
+
+BEGIN {
+    # Begin a transaction
+    print "BEGIN TRANSACTION;"
+
+    # Create english table
+    print "CREATE TABLE IF NOT EXISTS \"english\" ( "   \
+        "\"word\" TEXT NOT NULL PRIMARY KEY,"                       \
+        "\"freq\" FLOAT NOT NULL DEFAULT(0)"            \
+        ");";
+
+    # Create desc table
+    print "CREATE TABLE IF NOT EXISTS desc (name TEXT PRIMARY KEY, value TEXT);";
+    print "INSERT OR IGNORE INTO desc VALUES ('version', '1.2.0');";
+}
+
+    # Insert data into english table
+    {   printf "INSERT INTO english (word, freq) VALUES (\"%s\", \"%f\");\n", $1, $2}
+
+    #quit sqlite3
+END {
+    # Commit the transcation
+    print "COMMIT;"
+}
\ No newline at end of file
index 094d97e..2ca6c1e 100644 (file)
@@ -105,12 +105,17 @@ ibus_engine_pinyin_h_sources = \
        PYText.h \
        PYTypes.h \
        PYUtil.h \
+       PYEnglishEditor.h \
        $(NULL)
 
 if IBUS_BUILD_LUA_EXTENSION
 ibus_engine_pinyin_c_sources += PYExtEditor.cc
 endif
 
+if IBUS_BUILD_ENGLISH_INPUT_MODE
+ibus_engine_pinyin_c_sources += PYEnglishEditor.cc
+endif
+
 ibus_engine_pinyin_SOURCES = \
        $(ibus_engine_pinyin_c_sources) \
        $(ibus_engine_pinyin_h_sources) \
@@ -165,6 +170,12 @@ if IBUS_BUILD_LUA_EXTENSION
        $(NULL)
 endif
 
+if IBUS_BUILD_ENGLISH_INPUT_MODE
+   ibus_engine_pinyin_CXXFLAGS += \
+       -DIBUS_BUILD_ENGLISH_INPUT_MODE \
+       $(NULL)
+endif
+
 BUILT_SOURCES = \
        $(ibus_engine_built_c_sources) \
        $(ibus_engine_built_h_sources) \
diff --git a/src/PYEnglishEditor.cc b/src/PYEnglishEditor.cc
new file mode 100644 (file)
index 0000000..824e167
--- /dev/null
@@ -0,0 +1,795 @@
+/* vim:set et ts=4 sts=4:
+ *
+ * ibus-pinyin - The Chinese PinYin engine for IBus
+ *
+ * Copyright (c) 2010-2011 Peng Wu <alexepico@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "PYEnglishEditor.h"
+#include <string.h>
+#include <string>
+#include <vector>
+#include <stdio.h>
+#include <sqlite3.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+#include "PYConfig.h"
+#include "PYString.h"
+
+
+namespace PY {
+
+#define DB_BACKUP_TIMEOUT   (60)
+
+class EnglishDatabase{
+public:
+    EnglishDatabase(){
+        m_sqlite = NULL;
+        m_sql = "";
+        m_user_db = "";
+        m_timeout_id = 0;
+        m_timer = g_timer_new ();
+    }
+
+    ~EnglishDatabase(){
+        g_timer_destroy (m_timer);
+        if (m_timeout_id != 0) {
+            saveUserDB ();
+            g_source_remove (m_timeout_id);
+        }
+
+        if (m_sqlite){
+            sqlite3_close (m_sqlite);
+            m_sqlite = NULL;
+        }
+        m_sql = "";
+        m_user_db = NULL;
+    }
+
+    gboolean isDatabaseExisted(const char *filename) {
+         gboolean result = g_file_test (filename, G_FILE_TEST_IS_REGULAR);
+         if (!result)
+             return FALSE;
+
+         sqlite3 *tmp_db = NULL;
+         if (sqlite3_open_v2 (filename, &tmp_db,
+                              SQLITE_OPEN_READONLY, NULL) != SQLITE_OK){
+             return FALSE;
+         }
+
+         /* Check the desc table */
+         sqlite3_stmt *stmt = NULL;
+         const char *tail = NULL;
+         m_sql = "SELECT value FROM desc WHERE name = 'version';";
+         result = sqlite3_prepare_v2 (tmp_db, m_sql.c_str(), -1, &stmt, &tail);
+         g_assert (result == SQLITE_OK);
+         result = sqlite3_step (stmt);
+         if (result != SQLITE_ROW)
+             return FALSE;
+         result = sqlite3_column_type (stmt, 0);
+         if (result != SQLITE_TEXT)
+             return FALSE;
+         const char *version = (const char *) sqlite3_column_text (stmt, 0);
+         if (strcmp("1.2.0", version ) != 0)
+             return FALSE;
+         result = sqlite3_finalize (stmt);
+         g_assert (result == SQLITE_OK);
+         sqlite3_close (tmp_db);
+         return TRUE;
+    }
+
+    gboolean createDatabase(const char *filename) {
+        /* unlink the old database. */
+        gboolean retval = g_file_test (filename, G_FILE_TEST_IS_REGULAR);
+        if (retval) {
+            int result = g_unlink (filename);
+            if (result == -1)
+                return FALSE;
+        }
+
+        char *dirname = g_path_get_dirname (filename);
+        g_mkdir_with_parents (dirname, 0700);
+        g_free (dirname);
+
+        sqlite3 *tmp_db = NULL;
+        if (sqlite3_open_v2 (filename, &tmp_db,
+                             SQLITE_OPEN_READWRITE | 
+                             SQLITE_OPEN_CREATE, NULL) != SQLITE_OK) {
+            return FALSE;
+        }
+
+        /* Create DESCription table */
+        m_sql = "BEGIN TRANSACTION;\n";
+        m_sql << "CREATE TABLE IF NOT EXISTS desc (name TEXT PRIMARY KEY, value TEXT);\n";
+        m_sql << "INSERT OR IGNORE INTO desc VALUES ('version', '1.2.0');";
+        m_sql << "COMMIT;\n";
+
+        if (!executeSQL (tmp_db)) {
+            sqlite3_close (tmp_db);
+            return FALSE;
+        }
+
+        /* Create Schema */
+        m_sql = "CREATE TABLE IF NOT EXISTS english ("
+                "word TEXT NOT NULL PRIMARY KEY,"
+                "freq FLOAT NOT NULL DEFAULT(0)"
+                ");";
+        if (!executeSQL (tmp_db)) {
+            sqlite3_close (tmp_db);
+            return FALSE;
+        }
+        return TRUE;
+    }
+
+    gboolean openDatabase(const char *system_db, const char *user_db){
+        if (!isDatabaseExisted (system_db))
+            return FALSE;
+        if (!isDatabaseExisted (user_db)) {
+            gboolean result = createDatabase (user_db);
+            if (!result)
+                return FALSE;
+        }
+        /* cache the user db name. */
+        m_user_db = user_db;
+
+        /* do database attach here. :) */
+        if (sqlite3_open_v2 (system_db, &m_sqlite,
+                             SQLITE_OPEN_READWRITE |
+                             SQLITE_OPEN_CREATE, NULL) != SQLITE_OK) {
+            m_sqlite = NULL;
+            return FALSE;
+        }
+
+#if 0
+        m_sql.printf (SQL_ATTACH_DB, user_db);
+        if (!executeSQL (m_sqlite)) {
+            sqlite3_close (m_sqlite);
+            m_sqlite = NULL;
+            return FALSE;
+        }
+        return TRUE;
+#endif
+        return loadUserDB();
+    }
+
+    /* List the words in freq order. */
+    gboolean listWords(const char *prefix, std::vector<std::string> & words){
+        sqlite3_stmt *stmt = NULL;
+        const char *tail = NULL;
+        words.clear ();
+
+        /* list words */
+        const char *SQL_DB_LIST = 
+            "SELECT word FROM ( "
+            "SELECT * FROM english UNION ALL SELECT * FROM userdb.english) "
+            " WHERE word LIKE '%s%%' GROUP BY word ORDER BY SUM(freq) DESC;";
+        m_sql.printf (SQL_DB_LIST, prefix);
+        int result = sqlite3_prepare_v2 (m_sqlite, m_sql.c_str(), -1, &stmt, &tail);
+        g_assert(result == SQLITE_OK);
+        result = sqlite3_step (stmt);
+        while (result == SQLITE_ROW){
+            /* get the words. */
+            result = sqlite3_column_type (stmt, 0);
+            if (result != SQLITE_TEXT)
+                return FALSE;
+            const char *word = (const char *)sqlite3_column_text (stmt, 0);
+            words.push_back (word);
+            result = sqlite3_step (stmt);
+        }
+        sqlite3_finalize (stmt);
+        if (result != SQLITE_DONE)
+            return FALSE;
+        return TRUE;
+    }
+
+    /* Get the freq of user sqlite db. */
+    gboolean getWordInfo(const char *word, float & freq){
+        sqlite3_stmt *stmt = NULL;
+        const char *tail = NULL;
+        /* get word info. */
+        const char *SQL_DB_SELECT = 
+            "SELECT freq FROM userdb.english WHERE word = \"%s\";";
+        m_sql.printf (SQL_DB_SELECT, word);
+        int result = sqlite3_prepare_v2 (m_sqlite, m_sql.c_str(), -1, &stmt, &tail);
+        g_assert (result == SQLITE_OK);
+        result = sqlite3_step (stmt);
+        if (result != SQLITE_ROW)
+            return FALSE;
+        result = sqlite3_column_type (stmt, 0);
+        if (result != SQLITE_FLOAT)
+            return FALSE;
+        freq = sqlite3_column_double (stmt, 0);
+        result = sqlite3_finalize (stmt);
+        g_assert (result == SQLITE_OK);
+        return TRUE;
+    }
+
+    /* Update the freq with delta value. */
+    gboolean updateWord(const char *word, float freq){
+        const char *SQL_DB_UPDATE =
+            "UPDATE userdb.english SET freq = \"%f\" WHERE word = \"%s\";";
+        m_sql.printf (SQL_DB_UPDATE, freq, word);
+        gboolean retval =  executeSQL (m_sqlite);
+        modified ();
+        return retval;
+    }
+
+    /* Insert the word into user db with the initial freq. */
+    gboolean insertWord(const char *word, float freq){
+        const char *SQL_DB_INSERT =
+            "INSERT INTO userdb.english (word, freq) VALUES (\"%s\", \"%f\");";
+        m_sql.printf (SQL_DB_INSERT, word, freq);
+        gboolean retval = executeSQL (m_sqlite);
+        modified ();
+        return retval;
+    }
+
+private:
+    gboolean executeSQL(sqlite3 *sqlite){
+        gchar *errmsg = NULL;
+        if (sqlite3_exec (sqlite, m_sql.c_str (), NULL, NULL, &errmsg)
+             != SQLITE_OK) {
+            g_warning ("%s: %s", errmsg, m_sql.c_str());
+            sqlite3_free (errmsg);
+            return FALSE;
+        }
+        m_sql.clear ();
+        return TRUE;
+    }
+
+    gboolean loadUserDB (void){
+        sqlite3 *userdb =  NULL;
+        /* Attach user database */
+        do {
+            const char *SQL_ATTACH_DB =
+                "ATTACH DATABASE ':memory:' AS userdb;";
+            m_sql.printf (SQL_ATTACH_DB);
+            if (!executeSQL (m_sqlite))
+                break;
+
+            /* Note: user db is always created by openDatabase. */
+            if (sqlite3_open_v2 ( m_user_db, &userdb,
+                                  SQLITE_OPEN_READWRITE |
+                                  SQLITE_OPEN_CREATE, NULL) != SQLITE_OK)
+                break;
+
+            sqlite3_backup *backup = sqlite3_backup_init (m_sqlite, "userdb", userdb, "main");
+
+            if (backup) {
+                sqlite3_backup_step (backup, -1);
+                sqlite3_backup_finish (backup);
+            }
+
+            sqlite3_close (userdb);
+            return TRUE;
+        } while (0);
+
+        if (userdb)
+            sqlite3_close (userdb);
+        return FALSE;
+    }
+
+    gboolean saveUserDB (void){
+        sqlite3 *userdb = NULL;
+        String tmpfile = String(m_user_db) + "-tmp";
+        do {
+            /* remove tmpfile if it exist */
+            g_unlink(tmpfile);
+
+            if (sqlite3_open_v2 (tmpfile, &userdb,
+                                 SQLITE_OPEN_READWRITE |
+                                 SQLITE_OPEN_CREATE, NULL) != SQLITE_OK)
+                break;
+
+            sqlite3_backup *backup = sqlite3_backup_init (userdb, "main", m_sqlite, "userdb");
+
+            if (backup == NULL)
+                break;
+
+            sqlite3_backup_step (backup, -1);
+            sqlite3_backup_finish (backup);
+            sqlite3_close (userdb);
+
+            g_rename(tmpfile, m_user_db);
+            return TRUE;
+        } while (0);
+
+        if (userdb)
+            sqlite3_close (userdb);
+        g_unlink (tmpfile);
+        return FALSE;
+    }
+
+    void modified (void){
+        /* Restart the timer */
+        g_timer_start (m_timer);
+
+        if (m_timeout_id != 0)
+            return;
+
+        m_timeout_id = g_timeout_add_seconds (DB_BACKUP_TIMEOUT,
+                                              EnglishDatabase::timeoutCallback,
+                                              static_cast<gpointer> (this));
+    }
+
+    static gboolean timeoutCallback (gpointer data){
+        EnglishDatabase *self = static_cast<EnglishDatabase *> (data);
+
+        /* Get elapsed time since last modification of database. */
+        guint elapsed = (guint) g_timer_elapsed (self->m_timer, NULL);
+
+        if (elapsed >= DB_BACKUP_TIMEOUT &&
+            self->saveUserDB ()) {
+            self->m_timeout_id = 0;
+            return FALSE;
+        }
+
+        return TRUE;
+    }
+
+    sqlite3 *m_sqlite;
+    String m_sql;
+    const char *m_user_db;
+
+    guint m_timeout_id;
+    GTimer *m_timer;
+};
+
+EnglishEditor::EnglishEditor (PinyinProperties & props, Config &config)
+    : Editor (props, config), m_train_factor (0.1)
+{
+    m_english_database = new EnglishDatabase;
+
+    gchar *path = g_build_filename (g_get_user_cache_dir (),
+                                     "ibus", "pinyin", "english-user.db", NULL);
+
+    bool result = m_english_database->openDatabase
+        (".." G_DIR_SEPARATOR_S "data" G_DIR_SEPARATOR_S "english.db",
+         "english-user.db") ||
+        m_english_database->openDatabase
+        (PKGDATADIR G_DIR_SEPARATOR_S "db" G_DIR_SEPARATOR_S "english.db", path);
+    if (!result)
+        g_warning ("can't open english word list database.\n");
+}
+
+EnglishEditor::~EnglishEditor ()
+{
+    delete m_english_database;
+    m_english_database = NULL;
+}
+
+gboolean
+EnglishEditor::processKeyEvent (guint keyval, guint keycode, guint modifiers)
+{
+    //IBUS_SHIFT_MASK is removed.
+    modifiers &= (IBUS_CONTROL_MASK |
+                  IBUS_MOD1_MASK |
+                  IBUS_SUPER_MASK |
+                  IBUS_HYPER_MASK |
+                  IBUS_META_MASK |
+                  IBUS_LOCK_MASK);
+    if (modifiers)
+        return FALSE;
+
+    //handle backspace/delete here.
+    if (processEditKey (keyval))
+        return TRUE;
+
+    //handle page/cursor up/down here.
+    if (processPageKey (keyval))
+        return TRUE;
+
+    //handle label key select here.
+    if (processLabelKey (keyval))
+        return TRUE;
+
+    if (processSpace (keyval))
+        return TRUE;
+
+    if (processEnter (keyval))
+        return TRUE;
+
+    m_cursor = std::min (m_cursor, (guint)m_text.length ());
+
+    /* Remember the input string. */
+    switch (m_cursor) {
+    case 0: //Empty input string
+        {
+            g_return_val_if_fail ('v' == keyval, FALSE);
+            if ( 'v' == keyval ) {
+                m_text.insert (m_cursor, keyval);
+                m_cursor++;
+            }
+        }
+        break;
+    default: //append string
+        {
+            g_return_val_if_fail ('v' == m_text[0], FALSE);
+            if (isalpha (keyval)) {
+                m_text.insert (m_cursor, keyval);
+                m_cursor++;
+            }
+        }
+        break;
+    }
+    /* Deal other staff with updateStateFromInput (). */
+    updateStateFromInput ();
+    update ();
+    return TRUE;
+}
+
+gboolean
+EnglishEditor::processEditKey (guint keyval)
+{
+    switch (keyval) {
+    case IBUS_Delete:
+    case IBUS_KP_Delete:
+        removeCharAfter ();
+        updateStateFromInput ();
+        update ();
+        return TRUE;
+    case IBUS_BackSpace:
+        removeCharBefore ();
+        updateStateFromInput ();
+        update ();
+        return TRUE;
+    }
+    return FALSE;
+}
+
+gboolean
+EnglishEditor::processPageKey (guint keyval)
+{
+    switch (keyval) {
+    case IBUS_comma:
+        if (m_config.commaPeriodPage ()) {
+            pageUp ();
+            return TRUE;
+        }
+        break;
+    case IBUS_minus:
+        if (m_config.minusEqualPage ()) {
+            pageUp ();
+            return TRUE;
+        }
+        break;
+    case IBUS_period:
+        if (m_config.commaPeriodPage ()) {
+            pageDown ();
+            return TRUE;
+        }
+        break;
+    case IBUS_equal:
+        if (m_config.minusEqualPage ()) {
+            pageDown ();
+            return TRUE;
+        }
+        break;
+    case IBUS_Up:
+    case IBUS_KP_Up:
+        cursorUp ();
+        return TRUE;
+
+    case IBUS_Down:
+    case IBUS_KP_Down:
+        cursorDown ();
+        return TRUE;
+
+    case IBUS_Page_Up:
+    case IBUS_KP_Page_Up:
+        pageUp ();
+        return TRUE;
+
+    case IBUS_Page_Down:
+    case IBUS_KP_Page_Down:
+        pageDown ();
+        return TRUE;
+
+    case IBUS_Escape:
+        reset ();
+        return TRUE;
+    }
+    return FALSE;
+}
+
+gboolean
+EnglishEditor::processLabelKey (guint keyval)
+{
+    switch (keyval) {
+    case '1' ... '9':
+        return selectCandidateInPage (keyval - '1');
+        break;
+    case '0':
+        return selectCandidateInPage (9);
+        break;
+    }
+    return FALSE;
+}
+
+gboolean
+EnglishEditor::processEnter (guint keyval){
+    if (keyval != IBUS_Return)
+        return FALSE;
+
+    if (m_text.length () == 0)
+        return FALSE;
+
+    String preedit = m_text.substr (1);
+    Text text (preedit);
+    commitText (text);
+    train (preedit.c_str (), m_train_factor);
+    reset ();
+    return TRUE;
+}
+
+gboolean
+EnglishEditor::processSpace (guint keyval)
+{
+    if (!(keyval == IBUS_space || keyval == IBUS_KP_Space))
+        return FALSE;
+
+    guint cursor_pos = m_lookup_table.cursorPos ();
+    return selectCandidate (cursor_pos);
+}
+
+void
+EnglishEditor::candidateClicked (guint index, guint button, guint state)
+{
+    selectCandidateInPage (index);
+}
+
+gboolean
+EnglishEditor::selectCandidateInPage (guint index)
+{
+    guint page_size = m_lookup_table.pageSize ();
+    guint cursor_pos = m_lookup_table.cursorPos ();
+
+    if (G_UNLIKELY (index >= page_size))
+        return FALSE;
+    index += (cursor_pos / page_size) * page_size;
+
+    return selectCandidate (index);
+}
+
+gboolean
+EnglishEditor::selectCandidate (guint index)
+{
+    if (index >= m_lookup_table.size ())
+        return FALSE;
+
+    IBusText *candidate = m_lookup_table.getCandidate (index);
+    Text text (candidate);
+    commitText (text);
+    train (candidate->text, m_train_factor);
+    reset ();
+    return TRUE;
+}
+
+bool
+EnglishEditor::updateStateFromInput (void)
+{
+    /* Do parse and candidates update here. */
+    /* prefix v double check here. */
+    if (!m_text.length ()) {
+        m_preedit_text = "";
+        m_auxiliary_text = "";
+        m_cursor = 0;
+        clearLookupTable ();
+        return FALSE;
+    }
+
+    if (!'v' == m_text[0]) {
+        g_warning ("v is expected in m_text string.\n");
+        return FALSE;
+    }
+
+    m_auxiliary_text = "v";
+    if (1 == m_text.length ()) {
+        clearLookupTable ();
+        return TRUE;
+    }
+
+    m_auxiliary_text += " ";
+
+    String prefix = m_text.substr (1);
+    m_auxiliary_text += prefix;
+
+    /* lookup table candidate fill here. */
+    std::vector<std::string> words;
+    bool retval = m_english_database->listWords (prefix.c_str (), words);
+    if (!retval)
+        return false;
+
+    clearLookupTable ();
+    std::vector<std::string>::iterator iter;
+    for (iter = words.begin (); iter != words.end (); ++iter){
+        Text text (*iter);
+        m_lookup_table.appendCandidate (text);
+    }
+    return true;
+}
+
+/* Auxiliary Functions */
+
+void
+EnglishEditor::pageUp (void)
+{
+    if (G_LIKELY (m_lookup_table.pageUp ())) {
+        update ();
+    }
+}
+
+void
+EnglishEditor::pageDown (void)
+{
+    if (G_LIKELY (m_lookup_table.pageDown ())) {
+        update ();
+    }
+}
+
+void
+EnglishEditor::cursorUp (void)
+{
+    if (G_LIKELY (m_lookup_table.cursorUp ())) {
+        update ();
+    }
+}
+
+void
+EnglishEditor::cursorDown (void)
+{
+    if (G_LIKELY (m_lookup_table.cursorDown ())) {
+        update ();
+    }
+}
+
+void
+EnglishEditor::update (void)
+{
+    updateLookupTable ();
+    updatePreeditText ();
+    updateAuxiliaryText ();
+}
+
+void
+EnglishEditor::reset (void)
+{
+    m_text = "";
+    updateStateFromInput ();
+    update ();
+}
+
+void
+EnglishEditor::clearLookupTable (void)
+{
+    m_lookup_table.clear ();
+    m_lookup_table.setPageSize (m_config.pageSize ());
+    m_lookup_table.setOrientation (m_config.orientation ());
+}
+
+void
+EnglishEditor::updateLookupTable (void)
+{
+    if (m_lookup_table.size ()) {
+        Editor::updateLookupTableFast (m_lookup_table, TRUE);
+    }
+    else {
+        hideLookupTable ();
+    }
+}
+
+void
+EnglishEditor::updatePreeditText (void)
+{
+    if (G_UNLIKELY (m_preedit_text.empty ())) {
+        hidePreeditText ();
+        return;
+    }
+
+    StaticText preedit_text (m_preedit_text);
+    Editor::updatePreeditText (preedit_text, m_cursor, TRUE);
+}
+
+void
+EnglishEditor::updateAuxiliaryText (void)
+{
+    if (G_UNLIKELY (m_auxiliary_text.empty ())) {
+        hideAuxiliaryText ();
+        return;
+    }
+    
+    StaticText aux_text (m_auxiliary_text);
+    Editor::updateAuxiliaryText (aux_text, TRUE);
+}
+
+gboolean
+EnglishEditor::removeCharBefore (void)
+{
+    if (G_UNLIKELY (m_cursor <= 0)) {
+        m_cursor = 0;
+        return FALSE;
+    }
+
+    if (G_UNLIKELY (m_cursor > m_text.length ())) {
+        m_cursor = m_text.length ();
+        return FALSE;
+    }
+
+    m_text.erase (m_cursor - 1, 1);
+    m_cursor = std::max (0, static_cast<int>(m_cursor) - 1);
+    return TRUE;
+}
+
+gboolean
+EnglishEditor::removeCharAfter (void)
+{
+    if (G_UNLIKELY (m_cursor < 0)) {
+        m_cursor = 0;
+        return FALSE;
+    }
+
+    if (G_UNLIKELY (m_cursor >= m_text.length ())) {
+        m_cursor = m_text.length ();
+        return FALSE;
+    }
+
+    m_text.erase (m_cursor, 1);
+    m_cursor = std::min (m_cursor, (guint) m_text.length ());
+    return TRUE;
+}
+
+bool
+EnglishEditor::train (const char *word, float delta)
+{
+    float freq = 0;
+    bool retval = m_english_database->getWordInfo (word, freq);
+    if (retval) {
+        freq += delta;
+        m_english_database->updateWord (word, freq);
+    } else {
+        m_english_database->insertWord (word, delta);
+    }
+    return true;
+}
+
+#if 0
+
+/* using static initializor to test english database here. */
+static class TestEnglishDatabase{
+public:
+    TestEnglishDatabase (){
+        EnglishDatabase *db = new EnglishDatabase ();
+        bool retval = db->isDatabaseExisted ("/tmp/english-user.db");
+        g_assert (!retval);
+        retval = db->createDatabase ("english-user.db");
+        g_assert (retval);
+        retval = db->openDatabase ("english.db", "english-user.db");
+        g_assert (retval);
+        float freq = 0;
+        retval = db->getWordInfo ("hello", freq);
+        printf ("word hello:%d, %f.\n", retval, freq);
+        if (retval) {
+            db->updateWord ("hello", 0.1);
+        } else {
+            db->insertWord ("hello", 0.1);
+        }
+        printf ("english database test ok.\n");
+    }
+} test_english_database;
+
+#endif
+};
diff --git a/src/PYEnglishEditor.h b/src/PYEnglishEditor.h
new file mode 100644 (file)
index 0000000..c2ac73b
--- /dev/null
@@ -0,0 +1,82 @@
+/* vim:set et ts=4 sts=4:
+ *
+ * ibus-pinyin - The Chinese PinYin engine for IBus
+ *
+ * Copyright (c) 2010-2011 Peng Wu <alexepico@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __PY_ENGLISH_EDITOR_
+#define __PY_ENGLISH_EDITOR_
+
+#include "PYEditor.h"
+#include "PYLookupTable.h"
+
+namespace PY {
+
+class EnglishDatabase;
+
+class EnglishEditor : public Editor {
+private:
+    const float m_train_factor;
+public:
+    EnglishEditor (PinyinProperties &props, Config & config);
+    virtual ~EnglishEditor();
+
+    virtual gboolean processKeyEvent (guint keyval, guint keycode, guint modifers);
+    virtual void pageUp (void);
+    virtual void pageDown (void);
+    virtual void cursorUp (void);
+    virtual void cursorDown (void);
+    virtual void update (void);
+    virtual void reset (void);
+    virtual void candidateClicked (guint index, guint button, guint state);
+
+private:
+    bool updateStateFromInput (void);
+
+    void clearLookupTable (void);
+    void updateLookupTable (void);
+    void updatePreeditText (void);
+    void updateAuxiliaryText (void);
+
+    gboolean selectCandidateInPage (guint index);
+    gboolean selectCandidate (guint index);
+
+    gboolean processSpace(guint keyval);
+    gboolean processEnter(guint keyval);
+
+    gboolean removeCharBefore (void);
+    gboolean removeCharAfter (void);
+
+    gboolean processLabelKey(guint keyval);
+    gboolean processEditKey(guint keyval);
+    gboolean processPageKey(guint keyval);
+
+    bool train(const char *word, float delta);
+
+    /* variables */
+    LookupTable m_lookup_table;
+
+    String m_preedit_text;
+    String m_auxiliary_text;
+
+    EnglishDatabase *m_english_database;
+};
+
+};
+
+#endif
index 03a903a..c55714e 100644 (file)
@@ -51,7 +51,7 @@ ExtEditor::ExtEditor (PinyinProperties & props, Config & config)
     m_lua_plugin = ibus_engine_plugin_new ();
 
     gchar * path = g_build_filename (g_get_user_config_dir (),
-                                     "ibus", "pinyin", "base.lua", NULL);
+                                     ".ibus", "pinyin", "base.lua", NULL);
     loadLuaScript ( ".." G_DIR_SEPARATOR_S "lua" G_DIR_SEPARATOR_S "base.lua")||
         loadLuaScript (path) ||
         loadLuaScript (PKGDATADIR G_DIR_SEPARATOR_S "base.lua");
@@ -282,10 +282,10 @@ ExtEditor::processSpace (guint keyval)
 gboolean
 ExtEditor::processEnter(guint keyval)
 {
-    if ( !(keyval == IBUS_Return) )
+    if (keyval != IBUS_Return)
         return FALSE;
 
-    if ( m_text.length () == 0 )
+    if (m_text.length () == 0)
         return FALSE;
 
     Text text(m_text);
@@ -495,7 +495,7 @@ ExtEditor::updateStateFromInput (void)
     }
 
     if ( ! 'i' == m_text[0] ) {
-        g_warning ("i is expected in m_input string.\n");
+        g_warning ("i is expected in m_text string.\n");
         return FALSE;
     }
 
@@ -750,7 +750,7 @@ void
 ExtEditor::updateLookupTable (void)
 {
     if (m_lookup_table.size ()) {
-        Editor::updateLookupTable (m_lookup_table, TRUE);
+        Editor::updateLookupTableFast (m_lookup_table, TRUE);
     }
     else {
         hideLookupTable ();
index f773675..c871b7c 100644 (file)
@@ -26,6 +26,9 @@
 #ifdef IBUS_BUILD_LUA_EXTENSION
 #include "PYExtEditor.h"
 #endif
+#ifdef IBUS_BUILD_ENGLISH_INPUT_MODE
+#include "PYEnglishEditor.h"
+#endif
 #include "PYFullPinyinEditor.h"
 #include "PYDoublePinyinEditor.h"
 #include "PYFallbackEditor.h"
@@ -54,6 +57,11 @@ PinyinEngine::PinyinEngine (IBusEngine *engine)
 #else
     m_editors[MODE_EXTENSION].reset (new Editor (m_props, PinyinConfig::instance ()));
 #endif
+#ifdef IBUS_BUILD_ENGLISH_INPUT_MODE
+    m_editors[MODE_ENGLISH].reset (new EnglishEditor (m_props, PinyinConfig::instance ()));
+#else
+    m_editors[MODE_ENGLISH].reset (new Editor (m_props, PinyinConfig::instance ()));
+#endif
 
     m_props.signalUpdateProperty ().connect (std::bind (&PinyinEngine::updateProperty, this, _1));
 
@@ -101,7 +109,7 @@ PinyinEngine::processKeyEvent (guint keyval, guint keycode, guint modifiers)
 
     /* Toggle simp/trad Chinese Mode when hotkey Ctrl + Shift + F pressed */
     if (keyval == IBUS_F && scmshm_test (modifiers, (IBUS_SHIFT_MASK | IBUS_CONTROL_MASK))) {
-        m_props.toggleModeSimp();
+        m_props.toggleModeSimp ();
         m_prev_pressed_key = IBUS_F;
         return TRUE;
     }
@@ -123,6 +131,14 @@ PinyinEngine::processKeyEvent (guint keyval, guint keycode, guint modifiers)
                     m_input_mode = MODE_EXTENSION;
                     break;
 #endif
+#ifdef IBUS_BUILD_ENGLISH_INPUT_MODE
+                case IBUS_v:
+                    // do not enable english mode when use double pinyin.
+                    if (PinyinConfig::instance ().doublePinyin ())
+                        break;
+                    m_input_mode = MODE_ENGLISH;
+                    break;
+#endif
                 }
             }
             else {
index 4e98256..c5407c1 100644 (file)
@@ -64,8 +64,8 @@ private:
         MODE_INIT = 0,          // init mode
         MODE_PUNCT,             // punct mode
         MODE_RAW,               // raw mode
-    #if 0
         MODE_ENGLISH,           // press v into English input mode
+    #if 0
         MODE_STROKE,            // press u into stroke input mode
     #endif
         MODE_EXTENSION,         // press i into extension input mode