From a16d70e01a441193c6bb8a38d7cf093a7ebed19c Mon Sep 17 00:00:00 2001 From: Peng Wu Date: Fri, 22 Apr 2011 07:31:53 -0400 Subject: [PATCH] add English Editor to speed up English input BUG=none TEST=build fine Review URL: http://codereview.appspot.com/4200041 Patch from Peng Wu . --- configure.ac | 12 +- data/Makefile.am | 16 + data/english.awk | 25 ++ src/Makefile.am | 11 + src/PYEnglishEditor.cc | 795 +++++++++++++++++++++++++++++++++++++++++++++++++ src/PYEnglishEditor.h | 82 +++++ src/PYExtEditor.cc | 10 +- src/PYPinyinEngine.cc | 18 +- src/PYPinyinEngine.h | 2 +- 9 files changed, 963 insertions(+), 8 deletions(-) create mode 100644 data/english.awk create mode 100644 src/PYEnglishEditor.cc create mode 100644 src/PYEnglishEditor.h diff --git a/configure.ac b/configure.ac index 2668288..8ffdd29 100644 --- a/configure.ac +++ b/configure.ac @@ -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 ]) diff --git a/data/Makefile.am b/data/Makefile.am index d0ca35c..02e8feb 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -18,9 +18,25 @@ # 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 index 0000000..1828d2c --- /dev/null +++ b/data/english.awk @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index 094d97e..2ca6c1e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 index 0000000..824e167 --- /dev/null +++ b/src/PYEnglishEditor.cc @@ -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 + * + * 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 +#include +#include +#include +#include +#include +#include +#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 & 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 (this)); + } + + static gboolean timeoutCallback (gpointer data){ + EnglishDatabase *self = static_cast (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 words; + bool retval = m_english_database->listWords (prefix.c_str (), words); + if (!retval) + return false; + + clearLookupTable (); + std::vector::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(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 index 0000000..c2ac73b --- /dev/null +++ b/src/PYEnglishEditor.h @@ -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 + * + * 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 diff --git a/src/PYExtEditor.cc b/src/PYExtEditor.cc index 03a903a..c55714e 100644 --- a/src/PYExtEditor.cc +++ b/src/PYExtEditor.cc @@ -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 (); diff --git a/src/PYPinyinEngine.cc b/src/PYPinyinEngine.cc index f773675..c871b7c 100644 --- a/src/PYPinyinEngine.cc +++ b/src/PYPinyinEngine.cc @@ -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 { diff --git a/src/PYPinyinEngine.h b/src/PYPinyinEngine.h index 4e98256..c5407c1 100644 --- a/src/PYPinyinEngine.h +++ b/src/PYPinyinEngine.h @@ -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 -- 2.7.4