ibus-table/add-a-test-suite.patch

6975 lines
276 KiB
Diff
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

diff -Nru ibus-table-1.9.18.orig/Makefile.am ibus-table-1.9.18/Makefile.am
--- ibus-table-1.9.18.orig/Makefile.am 2020-07-22 11:52:11.640532230 +0200
+++ ibus-table-1.9.18/Makefile.am 2020-07-22 14:43:51.905260956 +0200
@@ -30,6 +30,7 @@
data \
po \
setup \
+ tests \
$(NULL)
ACLOCAL_AMFLAGS = -I m4
diff -Nru ibus-table-1.9.18.orig/Makefile.in ibus-table-1.9.18/Makefile.in
--- ibus-table-1.9.18.orig/Makefile.in 2017-08-02 11:32:47.000000000 +0200
+++ ibus-table-1.9.18/Makefile.in 2020-07-22 16:15:15.492860836 +0200
@@ -1,7 +1,7 @@
-# Makefile.in generated by automake 1.15 from Makefile.am.
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
# @configure_input@
-# Copyright (C) 1994-2014 Free Software Foundation, Inc.
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
# This Makefile.in is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -168,7 +168,7 @@
$(RECURSIVE_CLEAN_TARGETS) \
$(am__extra_recursive_targets)
AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
- cscope distdir dist dist-all distcheck
+ cscope distdir distdir-am dist dist-all distcheck
am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
# Read a list of newline-separated strings from the standard input,
# and print each of them once, without duplicates. Input order is
@@ -193,7 +193,7 @@
am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/ibus-table.pc.in \
$(srcdir)/ibus-table.spec.in ABOUT-NLS AUTHORS COPYING \
ChangeLog INSTALL NEWS README compile config.guess \
- config.rpath config.sub install-sh missing
+ config.rpath config.sub install-sh missing py-compile
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
distdir = $(PACKAGE)-$(VERSION)
top_distdir = $(distdir)
@@ -392,6 +392,7 @@
data \
po \
setup \
+ tests \
$(NULL)
ACLOCAL_AMFLAGS = -I m4
@@ -457,8 +458,8 @@
echo ' $(SHELL) ./config.status'; \
$(SHELL) ./config.status;; \
*) \
- echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \
- cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles);; \
esac;
$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
@@ -622,7 +623,10 @@
-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
-rm -f cscope.out cscope.in.out cscope.po.out cscope.files
-distdir: $(DISTFILES)
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
$(am__remove_distdir)
test -d "$(distdir)" || mkdir "$(distdir)"
@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
diff -Nru ibus-table-1.9.18.orig/configure.ac ibus-table-1.9.18/configure.ac
diff -Nru ibus-table-1.9.18.orig/tests/Makefile.in ibus-table-1.9.18/tests/Makefile.in
--- ibus-table-1.9.18.orig/tests/Makefile.in 1970-01-01 01:00:00.000000000 +0100
+++ ibus-table-1.9.18/tests/Makefile.in 2020-07-22 16:28:37.394963243 +0200
@@ -0,0 +1,853 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# vim:set noet ts=4
+#
+# ibus-table - The Tables engine for IBus
+#
+# Copyright (c) 2018 Mike FABIAN <mfabian@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/lib-ld.m4 \
+ $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \
+ $(top_srcdir)/m4/nls.m4 $(top_srcdir)/m4/po.m4 \
+ $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__recheck_rx = ^[ ]*:recheck:[ ]*
+am__global_test_result_rx = ^[ ]*:global-test-result:[ ]*
+am__copy_in_global_log_rx = ^[ ]*:copy-in-global-log:[ ]*
+# A command that, given a newline-separated list of test names on the
+# standard input, print the name of the tests that are to be re-run
+# upon "make recheck".
+am__list_recheck_tests = $(AWK) '{ \
+ recheck = 1; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ { \
+ if ((getline line2 < ($$0 ".log")) < 0) \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \
+ { \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \
+ { \
+ break; \
+ } \
+ }; \
+ if (recheck) \
+ print $$0; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# A command that, given a newline-separated list of test names on the
+# standard input, create the global log from their .trs and .log files.
+am__create_global_log = $(AWK) ' \
+function fatal(msg) \
+{ \
+ print "fatal: making $@: " msg | "cat >&2"; \
+ exit 1; \
+} \
+function rst_section(header) \
+{ \
+ print header; \
+ len = length(header); \
+ for (i = 1; i <= len; i = i + 1) \
+ printf "="; \
+ printf "\n\n"; \
+} \
+{ \
+ copy_in_global_log = 1; \
+ global_test_result = "RUN"; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".trs"); \
+ if (line ~ /$(am__global_test_result_rx)/) \
+ { \
+ sub("$(am__global_test_result_rx)", "", line); \
+ sub("[ ]*$$", "", line); \
+ global_test_result = line; \
+ } \
+ else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \
+ copy_in_global_log = 0; \
+ }; \
+ if (copy_in_global_log) \
+ { \
+ rst_section(global_test_result ": " $$0); \
+ while ((rc = (getline line < ($$0 ".log"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".log"); \
+ print line; \
+ }; \
+ printf "\n"; \
+ }; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# Restructured Text title.
+am__rst_title = { sed 's/.*/ & /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; }
+# Solaris 10 'make', and several other traditional 'make' implementations,
+# pass "-e" to $(SHELL), and POSIX 2008 even requires this. Work around it
+# by disabling -e (using the XSI extension "set +e") if it's set.
+am__sh_e_setup = case $$- in *e*) set +e;; esac
+# Default flags passed to test drivers.
+am__common_driver_flags = \
+ --color-tests "$$am__color_tests" \
+ --enable-hard-errors "$$am__enable_hard_errors" \
+ --expect-failure "$$am__expect_failure"
+# To be inserted before the command running the test. Creates the
+# directory for the log if needed. Stores in $dir the directory
+# containing $f, in $tst the test, in $log the log. Executes the
+# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and
+# passes TESTS_ENVIRONMENT. Set up options for the wrapper that
+# will run the test scripts (or their associated LOG_COMPILER, if
+# thy have one).
+am__check_pre = \
+$(am__sh_e_setup); \
+$(am__vpath_adj_setup) $(am__vpath_adj) \
+$(am__tty_colors); \
+srcdir=$(srcdir); export srcdir; \
+case "$@" in \
+ */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;; \
+ *) am__odir=.;; \
+esac; \
+test "x$$am__odir" = x"." || test -d "$$am__odir" \
+ || $(MKDIR_P) "$$am__odir" || exit $$?; \
+if test -f "./$$f"; then dir=./; \
+elif test -f "$$f"; then dir=; \
+else dir="$(srcdir)/"; fi; \
+tst=$$dir$$f; log='$@'; \
+if test -n '$(DISABLE_HARD_ERRORS)'; then \
+ am__enable_hard_errors=no; \
+else \
+ am__enable_hard_errors=yes; \
+fi; \
+case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$f[\ \ ]* | *[\ \ ]$$dir$$f[\ \ ]*) \
+ am__expect_failure=yes;; \
+ *) \
+ am__expect_failure=no;; \
+esac; \
+$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT)
+# A shell command to get the names of the tests scripts with any registered
+# extension removed (i.e., equivalently, the names of the test logs, with
+# the '.log' extension removed). The result is saved in the shell variable
+# '$bases'. This honors runtime overriding of TESTS and TEST_LOGS. Sadly,
+# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)",
+# since that might cause problem with VPATH rewrites for suffix-less tests.
+# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'.
+am__set_TESTS_bases = \
+ bases='$(TEST_LOGS)'; \
+ bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
+ bases=`echo $$bases`
+RECHECK_LOGS = $(TEST_LOGS)
+AM_RECURSIVE_TARGETS = check recheck
+TEST_SUITE_LOG = test-suite.log
+TEST_EXTENSIONS = @EXEEXT@ .test
+LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS)
+am__set_b = \
+ case '$@' in \
+ */*) \
+ case '$*' in \
+ */*) b='$*';; \
+ *) b=`echo '$@' | sed 's/\.log$$//'`; \
+ esac;; \
+ *) \
+ b='$*';; \
+ esac
+am__test_logs1 = $(TESTS:=.log)
+am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log)
+TEST_LOGS = $(am__test_logs2:.test.log=.log)
+TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver
+TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \
+ $(TEST_LOG_FLAGS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/test-driver
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EXEEXT = @EXEEXT@
+GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+IBUS_CFLAGS = @IBUS_CFLAGS@
+IBUS_LIBS = @IBUS_LIBS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+TESTS = run_tests
+EXTRA_DIST = \
+ run_tests.in \
+ test_it.py \
+ __init__.py \
+ $(NULL)
+
+CLEANFILES = \
+ run_tests \
+ $(NULL)
+
+MAINTAINERCLEANFILES = \
+ Makefile.in \
+ $(NULL)
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .log .test .test$(EXEEXT) .trs
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu tests/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+
+# Recover from deleted '.trs' file; this should ensure that
+# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create
+# both 'foo.log' and 'foo.trs'. Break the recipe in two subshells
+# to avoid problems with "make -n".
+.log.trs:
+ rm -f $< $@
+ $(MAKE) $(AM_MAKEFLAGS) $<
+
+# Leading 'am--fnord' is there to ensure the list of targets does not
+# expand to empty, as could happen e.g. with make check TESTS=''.
+am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
+am--force-recheck:
+ @:
+
+$(TEST_SUITE_LOG): $(TEST_LOGS)
+ @$(am__set_TESTS_bases); \
+ am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
+ redo_bases=`for i in $$bases; do \
+ am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \
+ done`; \
+ if test -n "$$redo_bases"; then \
+ redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \
+ redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \
+ if $(am__make_dryrun); then :; else \
+ rm -f $$redo_logs && rm -f $$redo_results || exit 1; \
+ fi; \
+ fi; \
+ if test -n "$$am__remaking_logs"; then \
+ echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \
+ "recursion detected" >&2; \
+ elif test -n "$$redo_logs"; then \
+ am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \
+ fi; \
+ if $(am__make_dryrun); then :; else \
+ st=0; \
+ errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \
+ for i in $$redo_bases; do \
+ test -f $$i.trs && test -r $$i.trs \
+ || { echo "$$errmsg $$i.trs" >&2; st=1; }; \
+ test -f $$i.log && test -r $$i.log \
+ || { echo "$$errmsg $$i.log" >&2; st=1; }; \
+ done; \
+ test $$st -eq 0 || exit 1; \
+ fi
+ @$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \
+ ws='[ ]'; \
+ results=`for b in $$bases; do echo $$b.trs; done`; \
+ test -n "$$results" || results=/dev/null; \
+ all=` grep "^$$ws*:test-result:" $$results | wc -l`; \
+ pass=` grep "^$$ws*:test-result:$$ws*PASS" $$results | wc -l`; \
+ fail=` grep "^$$ws*:test-result:$$ws*FAIL" $$results | wc -l`; \
+ skip=` grep "^$$ws*:test-result:$$ws*SKIP" $$results | wc -l`; \
+ xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \
+ xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \
+ error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \
+ if test `expr $$fail + $$xpass + $$error` -eq 0; then \
+ success=true; \
+ else \
+ success=false; \
+ fi; \
+ br='==================='; br=$$br$$br$$br$$br; \
+ result_count () \
+ { \
+ if test x"$$1" = x"--maybe-color"; then \
+ maybe_colorize=yes; \
+ elif test x"$$1" = x"--no-color"; then \
+ maybe_colorize=no; \
+ else \
+ echo "$@: invalid 'result_count' usage" >&2; exit 4; \
+ fi; \
+ shift; \
+ desc=$$1 count=$$2; \
+ if test $$maybe_colorize = yes && test $$count -gt 0; then \
+ color_start=$$3 color_end=$$std; \
+ else \
+ color_start= color_end=; \
+ fi; \
+ echo "$${color_start}# $$desc $$count$${color_end}"; \
+ }; \
+ create_testsuite_report () \
+ { \
+ result_count $$1 "TOTAL:" $$all "$$brg"; \
+ result_count $$1 "PASS: " $$pass "$$grn"; \
+ result_count $$1 "SKIP: " $$skip "$$blu"; \
+ result_count $$1 "XFAIL:" $$xfail "$$lgn"; \
+ result_count $$1 "FAIL: " $$fail "$$red"; \
+ result_count $$1 "XPASS:" $$xpass "$$red"; \
+ result_count $$1 "ERROR:" $$error "$$mgn"; \
+ }; \
+ { \
+ echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \
+ $(am__rst_title); \
+ create_testsuite_report --no-color; \
+ echo; \
+ echo ".. contents:: :depth: 2"; \
+ echo; \
+ for b in $$bases; do echo $$b; done \
+ | $(am__create_global_log); \
+ } >$(TEST_SUITE_LOG).tmp || exit 1; \
+ mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG); \
+ if $$success; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \
+ fi; \
+ echo "$${col}$$br$${std}"; \
+ echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}"; \
+ echo "$${col}$$br$${std}"; \
+ create_testsuite_report --maybe-color; \
+ echo "$$col$$br$$std"; \
+ if $$success; then :; else \
+ echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}"; \
+ if test -n "$(PACKAGE_BUGREPORT)"; then \
+ echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}"; \
+ fi; \
+ echo "$$col$$br$$std"; \
+ fi; \
+ $$success || exit 1
+
+check-TESTS:
+ @list='$(RECHECK_LOGS)'; test -z "$$list" || rm -f $$list
+ @list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ trs_list=`for i in $$bases; do echo $$i.trs; done`; \
+ log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
+ exit $$?;
+recheck: all
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ bases=`for i in $$bases; do echo $$i; done \
+ | $(am__list_recheck_tests)` || exit 1; \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ log_list=`echo $$log_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \
+ am__force_recheck=am--force-recheck \
+ TEST_LOGS="$$log_list"; \
+ exit $$?
+run_tests.log: run_tests
+ @p='run_tests'; \
+ b='run_tests'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+.test.log:
+ @p='$<'; \
+ $(am__set_b); \
+ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+@am__EXEEXT_TRUE@.test$(EXEEXT).log:
+@am__EXEEXT_TRUE@ @p='$<'; \
+@am__EXEEXT_TRUE@ $(am__set_b); \
+@am__EXEEXT_TRUE@ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \
+@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT)
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-am
+all-am: Makefile
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+ -test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
+ -test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
+ -test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+ -test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES)
+clean: clean-am
+
+clean-am: clean-generic mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: all all-am check check-TESTS check-am clean clean-generic \
+ cscopelist-am ctags-am distclean distclean-generic distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-generic pdf \
+ pdf-am ps ps-am recheck tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+run_tests: run_tests.in
+ sed -e 's&@PYTHON_BIN@&$(PYTHON)&g' \
+ -e 's&@SRCDIR@&$(srcdir)&g' $< > $@
+ chmod +x $@
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
--- ibus-table-1.9.18.orig/configure.ac 2020-07-22 11:52:11.639532241 +0200
+++ ibus-table-1.9.18/configure.ac 2020-07-22 14:43:51.905260956 +0200
@@ -68,6 +68,7 @@
setup/Makefile
setup/ibus-setup-table
setup/version.py
+ tests/Makefile
ibus-table.spec
ibus-table.pc]
)
diff -Nru ibus-table-1.9.18.orig/engine/Makefile.am ibus-table-1.9.18/engine/Makefile.am
--- ibus-table-1.9.18.orig/engine/Makefile.am 2020-07-22 11:52:11.650532123 +0200
+++ ibus-table-1.9.18/engine/Makefile.am 2020-07-22 14:43:51.906260946 +0200
@@ -32,6 +32,7 @@
table.py \
tabcreatedb.py \
tabsqlitedb.py \
+ it_util.py \
$(NULL)
engine_table_DATA = \
$(NULL)
diff -Nru ibus-table-1.9.18.orig/engine/it_util.py ibus-table-1.9.18/engine/it_util.py
--- ibus-table-1.9.18.orig/engine/it_util.py 1970-01-01 01:00:00.000000000 +0100
+++ ibus-table-1.9.18/engine/it_util.py 2020-07-22 14:43:51.906260946 +0200
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+# vim:et sts=4 sw=4
+#
+# ibus-table - The Tables engine for IBus
+#
+# Copyright (c) 2008-2009 Yu Yuwei <acevery@gmail.com>
+# Copyright (c) 2009-2014 Caius "kaio" CHANCE <me@kaio.net>
+# Copyright (c) 2012-2015 Mike FABIAN <mfabian@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+'''
+Utility functions used in ibus-table
+'''
+
+import sys
+import re
+import string
+
+def config_section_normalize(section):
+ '''Replaces “_:” with “-” in the dconf section and converts to lower case
+
+ :param section: The name of the dconf section
+ :type section: string
+ :rtype: string
+
+ To make the comparison of the dconf sections work correctly.
+
+ I avoid using .lower() here because it is locale dependent, when
+ using .lower() this would not achieve the desired effect of
+ comparing the dconf sections case insentively in some locales, it
+ would fail for example if Turkish locale (tr_TR.UTF-8) is set.
+
+ Examples:
+
+ >>> config_section_normalize('Foo_bAr:Baz')
+ 'foo-bar-baz'
+ '''
+ return re.sub(r'[_:]', r'-', section).translate(
+ bytes.maketrans(
+ bytes(string.ascii_uppercase.encode('ascii')),
+ bytes(string.ascii_lowercase.encode('ascii'))))
+
+
+if __name__ == "__main__":
+ import doctest
+ (FAILED, ATTEMPTED) = doctest.testmod()
+ if FAILED:
+ sys.exit(1)
+ else:
+ sys.exit(0)
diff -Nru ibus-table-1.9.18.orig/engine/table.py ibus-table-1.9.18/engine/table.py
--- ibus-table-1.9.18.orig/engine/table.py 2020-07-22 11:52:11.650532123 +0200
+++ ibus-table-1.9.18/engine/table.py 2020-07-22 14:43:51.907260935 +0200
@@ -37,6 +37,7 @@
import re
from gi.repository import GObject
import time
+import it_util
debug_level = int(0)
@@ -215,8 +216,10 @@
max_key_length, database):
self.db = database
self._config = config
- engine_name = os.path.basename(self.db.filename).replace('.db', '')
- self._config_section = "engine/Table/%s" % engine_name.replace(' ','_')
+ engine_name = os.path.basename(
+ self.db.filename).replace('.db', '').replace(' ','_')
+ self._config_section = it_util.config_section_normalize(
+ "engine/Table/%s" % engine_name)
self._max_key_length = int(max_key_length)
self._max_key_length_pinyin = 7
self._valid_input_chars = valid_input_chars
@@ -320,7 +323,7 @@
self._config_section,
"ChineseMode"))
if self._chinese_mode == None:
- self._chinese_mode = self.get_chinese_mode()
+ self._chinese_mode = self.get_default_chinese_mode()
elif debug_level > 1:
sys.stderr.write(
"Chinese mode found in user config, mode=%s\n"
@@ -342,7 +345,7 @@
def get_new_lookup_table(
self, page_size=10,
select_keys=[49, 50, 51, 52, 53, 54, 55, 56, 57, 48],
- orientation=True):
+ orientation=IBus.Orientation.VERTICAL):
'''
[49, 50, 51, 52, 53, 54, 55, 56, 57, 48] are the key codes
for the characters ['1', '2', '3', '4', '5', '6', '7', '8', '0']
@@ -351,7 +354,7 @@
page_size = 1
if page_size > len(select_keys):
page_size = len(select_keys)
- lookup_table = IBus.LookupTable.new(
+ lookup_table = IBus.LookupTable(
page_size=page_size,
cursor_pos=0,
cursor_visible=True,
@@ -371,7 +374,7 @@
"""
return self._select_keys
- def get_chinese_mode (self):
+ def get_default_chinese_mode (self):
'''
Use db value or LC_CTYPE in your box to determine the _chinese_mode
'''
@@ -380,7 +383,7 @@
if __db_chinese_mode >= 0:
if debug_level > 1:
sys.stderr.write(
- "get_chinese_mode(): "
+ "get_default_chinese_mode(): "
+ "default Chinese mode found in database, mode=%s\n"
%__db_chinese_mode)
return __db_chinese_mode
@@ -390,19 +393,19 @@
__lc = os.environ['LC_ALL'].split('.')[0].lower()
if debug_level > 1:
sys.stderr.write(
- "get_chinese_mode(): __lc=%s found in LC_ALL\n"
+ "get_default_chinese_mode(): __lc=%s found in LC_ALL\n"
% __lc)
elif 'LC_CTYPE' in os.environ:
__lc = os.environ['LC_CTYPE'].split('.')[0].lower()
if debug_level > 1:
sys.stderr.write(
- "get_chinese_mode(): __lc=%s found in LC_CTYPE\n"
+ "get_default_chinese_mode(): __lc=%s found in LC_CTYPE\n"
% __lc)
else:
__lc = os.environ['LANG'].split('.')[0].lower()
if debug_level > 1:
sys.stderr.write(
- "get_chinese_mode(): __lc=%s found in LANG\n"
+ "get_default_chinese_mode(): __lc=%s found in LANG\n"
% __lc)
if '_cn' in __lc or '_sg' in __lc:
@@ -419,14 +422,14 @@
# variant:
if debug_level > 1:
sys.stderr.write(
- "get_chinese_mode(): last fallback, "
+ "get_default_chinese_mode(): last fallback, "
+ "database is Chinese but we dont know "
+ "which variant.\n")
return 4 # show all Chinese characters
else:
if debug_level > 1:
sys.stderr.write(
- "get_chinese_mode(): last fallback, "
+ "get_default_chinese_mode(): last fallback, "
+ "database is not Chinese, returning -1.\n")
return -1
except:
@@ -1202,7 +1205,7 @@
class tabengine (IBus.Engine):
'''The IM Engine for Tables'''
- def __init__(self, bus, obj_path, db ):
+ def __init__(self, bus, obj_path, db, unit_test=False):
super(tabengine, self).__init__(connection=bus.get_connection(),
object_path=obj_path)
global debug_level
@@ -1210,6 +1213,7 @@
debug_level = int(os.getenv('IBUS_TABLE_DEBUG_LEVEL'))
except (TypeError, ValueError):
debug_level = int(0)
+ self._unit_test = unit_test
self._input_purpose = 0
self._has_input_purpose = False
if hasattr(IBus, 'InputPurpose'):
@@ -1224,12 +1228,16 @@
os.path.sep, 'icons', os.path.sep)
# name for config section
self._engine_name = os.path.basename(
- self.db.filename).replace('.db', '')
- self._config_section = (
- "engine/Table/%s" % self._engine_name.replace(' ','_'))
+ self.db.filename).replace('.db', '').replace(' ','_')
+ self._config_section = it_util.config_section_normalize(
+ "engine/Table/%s" % self._engine_name)
+ if debug_level > 1:
+ sys.stderr.write(
+ 'tabengine.__init__() self._config_section = %s\n'
+ % self._config_section)
# config module
- self._config = self._bus.get_config ()
+ self._config = self._bus.get_config()
self._config.connect ("value-changed", self.config_value_changed_cb)
# self._ime_py: Indicates whether this table supports pinyin mode
@@ -1699,7 +1707,7 @@
self._save_user_count = 0
super(tabengine, self).destroy()
- def set_input_mode(self, mode=0):
+ def set_input_mode(self, mode=1):
if mode == self._input_mode:
return
self._input_mode = mode
@@ -1722,6 +1730,9 @@
self._full_width_punct[self._input_mode])
self.reset()
+ def get_input_mode(self):
+ return self._input_mode
+
def set_pinyin_mode(self, mode=False):
if mode == self._editor._py_mode:
return
@@ -1741,76 +1752,302 @@
self._input_mode)
self._update_ui()
- def set_onechar_mode(self, mode=False):
+ def set_onechar_mode(self, mode=False, update_dconf=True):
if mode == self._editor._onechar:
return
self._editor._onechar = mode
self._init_or_update_property_menu(
self.onechar_mode_menu, mode)
- self._config.set_value(
- self._config_section,
- "OneChar",
- GLib.Variant.new_boolean(mode))
+ self.db.reset_phrases_cache()
+ if update_dconf:
+ self._config.set_value(
+ self._config_section,
+ "OneChar",
+ GLib.Variant.new_boolean(mode))
+
+ def get_onechar_mode(self):
+ return self._editor._onechar
- def set_autocommit_mode(self, mode=False):
+ def set_autocommit_mode(self, mode=False, update_dconf=True):
if mode == self._auto_commit:
return
self._auto_commit = mode
self._init_or_update_property_menu(
self.autocommit_mode_menu, mode)
- self._config.set_value(
- self._config_section,
- "AutoCommit",
- GLib.Variant.new_boolean(mode))
+ if update_dconf:
+ self._config.set_value(
+ self._config_section,
+ "AutoCommit",
+ GLib.Variant.new_boolean(mode))
- def set_letter_width(self, mode=False, input_mode=0):
- if mode == self._full_width_letter[input_mode]:
+ def get_autocommit_mode(self):
+ return self._auto_commit
+
+ def set_autoselect_mode(self, mode=False, update_dconf=True):
+ if mode == self._auto_select:
return
- self._full_width_letter[input_mode] = mode
- self._editor._full_width_letter[input_mode] = mode
- if input_mode == self._input_mode:
- self._init_or_update_property_menu(
- self.letter_width_menu, mode)
- if input_mode:
+ self._auto_select = mode
+ self._editor._auto_select = mode
+ if update_dconf:
self._config.set_value(
self._config_section,
- "TabDefFullWidthLetter",
+ "AutoSelect",
GLib.Variant.new_boolean(mode))
- else:
+
+ def get_autoselect_mode(self):
+ return self._auto_select
+
+ def set_autowildcard_mode(self, mode=False, update_dconf=True):
+ if mode == self._auto_wildcard:
+ return
+ self._auto_wildcard = mode
+ self._editor._auto_wildcard = mode
+ self.db.reset_phrases_cache()
+ if update_dconf:
self._config.set_value(
self._config_section,
- "EnDefFullWidthLetter",
+ "AutoWildcard",
GLib.Variant.new_boolean(mode))
- def set_punctuation_width(self, mode=False, input_mode=0):
- if mode == self._full_width_punct[input_mode]:
+ def get_autowildcard_mode(self):
+ return self._auto_wildcard
+
+ def set_single_wildcard_char(self, char=u'', update_dconf=True):
+ if char == self._single_wildcard_char:
return
- self._full_width_punct[input_mode] = mode
- self._editor._full_width_punct[input_mode] = mode
- if input_mode == self._input_mode:
- self._init_or_update_property_menu(
- self.punctuation_width_menu, mode)
- if input_mode:
+ self._single_wildcard_char = char
+ self._editor._single_wildcard_char = char
+ self.db.reset_phrases_cache()
+ if update_dconf:
+ self._config.set_value(
+ self._config_section,
+ "singlewildcardchar",
+ GLib.Variant.new_string(char))
+
+ def get_single_wildcard_char(self):
+ return self._single_wildcard_char
+
+ def set_multi_wildcard_char(self, char=u'', update_dconf=True):
+ if char == self._multi_wildcard_char:
+ return
+ self._multi_wildcard_char = char
+ self._editor._multi_wildcard_char = char
+ self.db.reset_phrases_cache()
+ if update_dconf:
+ self._config.set_value(
+ self._config_section,
+ "multiwildcardchar",
+ GLib.Variant.new_string(char))
+
+ def get_multi_wildcard_char(self):
+ return self._multi_wildcard_char
+
+ def set_space_key_behavior_mode(self, mode=False, update_dconf=True):
+ '''Sets the behaviour of the space key
+
+ :param mode: How the space key should behave
+ :type mode: Boolean
+ True: space is used as a page down key
+ and not as a commit key.
+ False: space is used as a commit key
+ and not used as a page down key
+ :param update_dconf: Whether to write the change to dconf.
+ Set this to False if this method is
+ called because the dconf key changed
+ to avoid endless loops when the dconf
+ key is changed twice in a short time.
+ :type update_dconf: boolean
+ '''
+ if debug_level > 1:
+ sys.stderr.write(
+ "set_space_key_behavior_mode(%s)\n"
+ %mode)
+ if mode == True:
+ # space is used as a page down key and not as a commit key:
+ if IBus.KEY_space not in self._page_down_keys:
+ self._page_down_keys.append(IBus.KEY_space)
+ if IBus.KEY_space in self._commit_keys:
+ self._commit_keys.remove(IBus.KEY_space)
+ if mode == False:
+ # space is used as a commit key and not used as a page down key:
+ if IBus.KEY_space in self._page_down_keys:
+ self._page_down_keys.remove(IBus.KEY_space)
+ if IBus.KEY_space not in self._commit_keys:
+ self._commit_keys.append(IBus.KEY_space)
+ if debug_level > 1:
+ sys.stderr.write(
+ 'set_space_key_behavior_mode(): self._page_down_keys=%s\n'
+ % repr(self._page_down_keys))
+ sys.stderr.write(
+ 'set_space_key_behavior_mode(): self._commit_keys=%s\n'
+ % repr(self._commit_keys))
+ if update_dconf:
self._config.set_value(
self._config_section,
- "TabDefFullWidthPunct",
+ "spacekeybehavior",
GLib.Variant.new_boolean(mode))
- else:
+
+ def get_space_key_behavior_mode(self):
+ mode = False
+ if IBus.KEY_space in self._page_down_keys:
+ mode = True
+ if IBus.KEY_space in self._commit_keys:
+ # commit key behaviour overrides the page down behaviour
+ mode = False
+ return mode
+
+ def set_always_show_lookup(self, mode=False, update_dconf=True):
+ if mode == self._always_show_lookup:
+ return
+ self._always_show_lookup = mode
+ if update_dconf:
self._config.set_value(
self._config_section,
- "EnDefFullWidthPunct",
+ "AlwaysShowLookup",
GLib.Variant.new_boolean(mode))
- def set_chinese_mode(self, mode=0):
+ def get_always_show_lookup(self):
+ return self._always_show_lookup
+
+ def set_lookup_table_orientation(self, orientation, update_dconf=True):
+ '''Sets the orientation of the lookup table
+
+ :param orientation: The orientation of the lookup table
+ :type mode: integer >= 0 and <= 2
+ IBUS_ORIENTATION_HORIZONTAL = 0,
+ IBUS_ORIENTATION_VERTICAL = 1,
+ IBUS_ORIENTATION_SYSTEM = 2.
+ :param update_dconf: Whether to write the change to dconf.
+ Set this to False if this method is
+ called because the dconf key changed
+ to avoid endless loops when the dconf
+ key is changed twice in a short time.
+ :type update_dconf: boolean
+ '''
+ if debug_level > 1:
+ sys.stderr.write(
+ "set_lookup_table_orientation(%s)\n"
+ %orientation)
+ if orientation == self._editor._orientation:
+ return
+ if orientation >= 0 and orientation <= 2:
+ self._editor._orientation = orientation
+ self._editor._lookup_table.set_orientation(orientation)
+ if update_dconf:
+ self._config.set_value(
+ self._config_section,
+ 'lookuptableorientation',
+ GLib.Variant.new_int32(orientation))
+
+ def get_lookup_table_orientation(self):
+ '''Returns the current orientation of the lookup table
+
+ :rtype: integer
+ '''
+ return self._editor._orientation
+
+ def set_page_size(self, page_size, update_dconf=True):
+ '''Sets the page size of the lookup table
+
+ :param orientation: The orientation of the lookup table
+ :type mode: integer >= 1 and <= number of select keys
+ :param update_dconf: Whether to write the change to dconf.
+ Set this to False if this method is
+ called because the dconf key changed
+ to avoid endless loops when the dconf
+ key is changed twice in a short time.
+ :type update_dconf: boolean
+ '''
+ if debug_level > 1:
+ sys.stderr.write(
+ "set_page_size(%s)\n"
+ %page_size)
+ if page_size == self._editor._page_size:
+ return
+ if page_size > len(self._editor._select_keys):
+ page_size = len(self._editor._select_keys)
+ if page_size < 1:
+ page_size = 1
+ self._editor._page_size = page_size
+ self._editor._lookup_table = self._editor.get_new_lookup_table(
+ page_size = self._editor._page_size,
+ select_keys = self._editor._select_keys,
+ orientation = self._editor._orientation)
+ self.reset()
+ if update_dconf:
+ self._config.set_value(
+ self._config_section,
+ 'lookuptablepagesize',
+ GLib.Variant.new_int32(page_size))
+
+ def get_page_size(self):
+ '''Returns the current page size of the lookup table
+
+ :rtype: integer
+ '''
+ return self._editor._page_size
+
+ def set_letter_width(self, mode=False, input_mode=0, update_dconf=True):
+ if mode == self._full_width_letter[input_mode]:
+ return
+ self._full_width_letter[input_mode] = mode
+ self._editor._full_width_letter[input_mode] = mode
+ if input_mode == self._input_mode:
+ self._init_or_update_property_menu(
+ self.letter_width_menu, mode)
+ if update_dconf:
+ if input_mode:
+ self._config.set_value(
+ self._config_section,
+ "TabDefFullWidthLetter",
+ GLib.Variant.new_boolean(mode))
+ else:
+ self._config.set_value(
+ self._config_section,
+ "EnDefFullWidthLetter",
+ GLib.Variant.new_boolean(mode))
+
+ def get_letter_width(self):
+ return self._full_width_letter
+
+ def set_punctuation_width(self, mode=False, input_mode=0, update_dconf=True):
+ if mode == self._full_width_punct[input_mode]:
+ return
+ self._full_width_punct[input_mode] = mode
+ self._editor._full_width_punct[input_mode] = mode
+ if input_mode == self._input_mode:
+ self._init_or_update_property_menu(
+ self.punctuation_width_menu, mode)
+ if update_dconf:
+ if input_mode:
+ self._config.set_value(
+ self._config_section,
+ "TabDefFullWidthPunct",
+ GLib.Variant.new_boolean(mode))
+ else:
+ self._config.set_value(
+ self._config_section,
+ "EnDefFullWidthPunct",
+ GLib.Variant.new_boolean(mode))
+
+ def get_punctuation_width(self):
+ return self._full_width_punct
+
+ def set_chinese_mode(self, mode=0, update_dconf=True):
if mode == self._editor._chinese_mode:
return
self._editor._chinese_mode = mode
+ self.db.reset_phrases_cache()
self._init_or_update_property_menu(
self.chinese_mode_menu, mode)
- self._config.set_value(
- self._config_section,
- "ChineseMode",
- GLib.Variant.new_int32(mode))
+ if update_dconf:
+ self._config.set_value(
+ self._config_section,
+ "ChineseMode",
+ GLib.Variant.new_int32(mode))
+
+ def get_chinese_mode(self):
+ return self._editor._chinese_mode
def _init_or_update_property_menu(self, menu, current_mode=0):
key = menu['key']
@@ -2233,6 +2470,44 @@
return True
return False
+ def _return_false(self, keyval, keycode, state):
+ '''A replacement for “return False” in do_process_key_event()
+
+ do_process_key_event should return “True” if a key event has
+ been handled completely. It should return “False” if the key
+ event should be passed to the application.
+
+ But just doing “return False” doesnt work well when trying to
+ do the unit tests. The MockEngine class in the unit tests
+ cannot get that return value. Therefore, it cannot do the
+ necessary updates to the self._mock_committed_text etc. which
+ prevents proper testing of the effects of such keys passed to
+ the application. Instead of “return False”, one can also use
+ self.forward_key_event(keyval, keycode, keystate) to pass the
+ key to the application. And this works fine with the unit
+ tests because a forward_key_event function is implemented in
+ MockEngine as well which then gets the key and can test its
+ effects.
+
+ Unfortunately, “forward_key_event()” does not work in Qt5
+ applications because the ibus module in Qt5 does not implement
+ “forward_key_event()”. Therefore, always using
+ “forward_key_event()” instead of “return False” in
+ “do_process_key_event()” would break ibus-typing-booster
+ completely for all Qt5 applictions.
+
+ To work around this problem and make unit testing possible
+ without breaking Qt5 applications, we use this helper function
+ which uses “forward_key_event()” when unit testing and “return
+ False” during normal usage.
+
+ '''
+ if self._unit_test:
+ self.forward_key_event(keyval, keycode, state)
+ return True
+ else:
+ return False
+
def do_process_key_event(self, keyval, keycode, state):
'''Process Key Events
Key Events include Key Press and Key Release,
@@ -2243,9 +2518,13 @@
if (self._has_input_purpose
and self._input_purpose
in [IBus.InputPurpose.PASSWORD, IBus.InputPurpose.PIN]):
- return False
+ return self._return_false(keyval, keycode, state)
key = KeyEvent(keyval, keycode, state)
+ if debug_level > 1:
+ sys.stderr.write(
+ "process_key_event() "
+ "KeyEvent object: %s" % key)
result = self._process_key_event (key)
self._prev_key = key
@@ -2308,13 +2587,13 @@
def _english_mode_process_key_event(self, key):
# Ignore key release events
if key.state & IBus.ModifierType.RELEASE_MASK:
- return False
+ return self._return_false(key.val, key.code, key.state)
if key.val >= 128:
- return False
+ return self._return_false(key.val, key.code, key.state)
# we ignore all hotkeys here
if (key.state
& (IBus.ModifierType.CONTROL_MASK|IBus.ModifierType.MOD1_MASK)):
- return False
+ return self._return_false(key.val, key.code, key.state)
keychar = IBus.keyval_to_unicode(key.val)
if type(keychar) != type(u''):
keychar = keychar.decode('UTF-8')
@@ -2323,7 +2602,7 @@
else:
trans_char = self.cond_letter_translate(keychar)
if trans_char == keychar:
- return False
+ return self._return_false(key.val, key.code, key.state)
self.commit_string(trans_char)
return True
@@ -2387,7 +2666,7 @@
# (Must be below all self._match_hotkey() callse
# because these match on a release event).
if key.state & IBus.ModifierType.RELEASE_MASK:
- return False
+ return self._return_false(key.val, key.code, key.state)
keychar = IBus.keyval_to_unicode(key.val)
if type(keychar) != type(u''):
@@ -2419,7 +2698,7 @@
trans_char = self.cond_letter_translate(keychar)
if trans_char == keychar:
self._prev_char = trans_char
- return False
+ return self._return_false(key.val, key.code, key.state)
else:
self.commit_string(trans_char)
return True
@@ -2441,12 +2720,12 @@
# input but it ends up here. If it is leading input
# (i.e. the preëdit is empty) we should always pass
# IBus.KEY_KP_Enter to the application:
- return False
+ return self._return_false(key.val, key.code, key.state)
if self._auto_select:
self._editor.commit_to_preedit()
commit_string = self._editor.get_preedit_string_complete()
self.commit_string(commit_string)
- return False
+ return self._return_false(key.val, key.code, key.state)
else:
commit_string = self._editor.get_preedit_tabkeys_complete()
self.commit_string(commit_string)
@@ -2474,18 +2753,18 @@
# to “шшш”.
self._editor.commit_to_preedit()
self.commit_string(self._editor.get_preedit_string_complete())
- return False
+ return self._return_false(key.val, key.code, key.state)
if key.val in (IBus.KEY_Down, IBus.KEY_KP_Down) :
if not self._editor.get_preedit_string_complete():
- return False
+ return self._return_false(key.val, key.code, key.state)
res = self._editor.cursor_down()
self._update_ui()
return res
if key.val in (IBus.KEY_Up, IBus.KEY_KP_Up):
if not self._editor.get_preedit_string_complete():
- return False
+ return self._return_false(key.val, key.code, key.state)
res = self._editor.cursor_up()
self._update_ui()
return res
@@ -2493,7 +2772,7 @@
if (key.val in (IBus.KEY_Left, IBus.KEY_KP_Left)
and key.state & IBus.ModifierType.CONTROL_MASK):
if not self._editor.get_preedit_string_complete():
- return False
+ return self._return_false(key.val, key.code, key.state)
self._editor.control_arrow_left()
self._update_ui()
return True
@@ -2501,21 +2780,21 @@
if (key.val in (IBus.KEY_Right, IBus.KEY_KP_Right)
and key.state & IBus.ModifierType.CONTROL_MASK):
if not self._editor.get_preedit_string_complete():
- return False
+ return self._return_false(key.val, key.code, key.state)
self._editor.control_arrow_right()
self._update_ui()
return True
if key.val in (IBus.KEY_Left, IBus.KEY_KP_Left):
if not self._editor.get_preedit_string_complete():
- return False
+ return self._return_false(key.val, key.code, key.state)
self._editor.arrow_left()
self._update_ui()
return True
if key.val in (IBus.KEY_Right, IBus.KEY_KP_Right):
if not self._editor.get_preedit_string_complete():
- return False
+ return self._return_false(key.val, key.code, key.state)
self._editor.arrow_right()
self._update_ui()
return True
@@ -2523,14 +2802,14 @@
if (key.val == IBus.KEY_BackSpace
and key.state & IBus.ModifierType.CONTROL_MASK):
if not self._editor.get_preedit_string_complete():
- return False
+ return self._return_false(key.val, key.code, key.state)
self._editor.remove_preedit_before_cursor()
self._update_ui()
return True
if key.val == IBus.KEY_BackSpace:
if not self._editor.get_preedit_string_complete():
- return False
+ return self._return_false(key.val, key.code, key.state)
self._editor.remove_char()
self._update_ui()
return True
@@ -2538,14 +2817,14 @@
if (key.val == IBus.KEY_Delete
and key.state & IBus.ModifierType.CONTROL_MASK):
if not self._editor.get_preedit_string_complete():
- return False
+ return self._return_false(key.val, key.code, key.state)
self._editor.remove_preedit_after_cursor()
self._update_ui()
return True
if key.val == IBus.KEY_Delete:
if not self._editor.get_preedit_string_complete():
- return False
+ return self._return_false(key.val, key.code, key.state)
self._editor.delete()
self._update_ui()
return True
@@ -2567,10 +2846,10 @@
# now we ignore all other hotkeys
if (key.state
& (IBus.ModifierType.CONTROL_MASK|IBus.ModifierType.MOD1_MASK)):
- return False
+ return self._return_false(key.val, key.code, key.state)
if key.state & IBus.ModifierType.MOD1_MASK:
- return False
+ return self._return_false(key.val, key.code, key.state)
# Section to handle valid input characters:
#
@@ -2731,7 +3010,7 @@
#
# returned no result. So whatever this was, we cannot handle it,
# just pass it through to the application by returning “False”.
- return False
+ return self._return_false(key.val, key.code, key.state)
def do_focus_in (self):
if debug_level > 1:
@@ -2802,92 +3081,47 @@
self.set_input_mode(value)
return
if name == u'autoselect':
- self._editor._auto_select = value
- self._auto_select = value
+ self.set_autoselect_mode(value, update_dconf=False)
return
if name == u'autocommit':
- self.set_autocommit_mode(value)
+ self.set_autocommit_mode(value, update_dconf=False)
return
if name == u'chinesemode':
- self.set_chinese_mode(value)
- self.db.reset_phrases_cache()
+ self.set_chinese_mode(value, update_dconf=False)
return
if name == u'endeffullwidthletter':
- self.set_letter_width(value, input_mode=0)
+ self.set_letter_width(value, input_mode=0, update_dconf=False)
return
if name == u'endeffullwidthpunct':
- self.set_punctuation_width(value, input_mode=0)
+ self.set_punctuation_width(value, input_mode=0, update_dconf=False)
return
if name == u'lookuptableorientation':
- self._editor._orientation = value
- self._editor._lookup_table.set_orientation(value)
+ self.set_lookup_table_orientation(value, update_dconf=False)
return
if name == u'lookuptablepagesize':
- if value > len(self._editor._select_keys):
- value = len(self._editor._select_keys)
- self._config.set_value(
- self._config_section,
- 'lookuptablepagesize',
- GLib.Variant.new_int32(value))
- if value < 1:
- value = 1
- self._config.set_value(
- self._config_section,
- 'lookuptablepagesize',
- GLib.Variant.new_int32(value))
- self._editor._page_size = value
- self._editor._lookup_table = self._editor.get_new_lookup_table(
- page_size = self._editor._page_size,
- select_keys = self._editor._select_keys,
- orientation = self._editor._orientation)
- self.reset()
- return
- if name == u'lookuptableselectkeys':
- self._editor.set_select_keys(value)
+ self.set_page_size(value, update_dconf=False)
return
if name == u'onechar':
- self.set_onechar_mode(value)
- self.db.reset_phrases_cache()
+ self.set_onechar_mode(value, update_dconf=False)
return
if name == u'tabdeffullwidthletter':
- self.set_letter_width(value, input_mode=1)
+ self.set_letter_width(value, input_mode=1, update_dconf=False)
return
if name == u'tabdeffullwidthpunct':
- self.set_punctuation_width(value, input_mode=1)
+ self.set_punctuation_width(value, input_mode=1, update_dconf=False)
return
if name == u'alwaysshowlookup':
- self._always_show_lookup = value
+ self.set_always_show_lookup(value, update_dconf=False)
return
if name == u'spacekeybehavior':
- if value == True:
- # space is used as a page down key and not as a commit key:
- if IBus.KEY_space not in self._page_down_keys:
- self._page_down_keys.append(IBus.KEY_space)
- if IBus.KEY_space in self._commit_keys:
- self._commit_keys.remove(IBus.KEY_space)
- if value == False:
- # space is used as a commit key and not used as a page down key:
- if IBus.KEY_space in self._page_down_keys:
- self._page_down_keys.remove(IBus.KEY_space)
- if IBus.KEY_space not in self._commit_keys:
- self._commit_keys.append(IBus.KEY_space)
- if debug_level > 1:
- sys.stderr.write(
- "self._page_down_keys=%s\n"
- % repr(self._page_down_keys))
+ self.set_space_key_behavior_mode(value, update_dconf=False)
return
if name == u'singlewildcardchar':
- self._single_wildcard_char = value
- self._editor._single_wildcard_char = value
- self.db.reset_phrases_cache()
+ self.set_single_wildcard_char(value, update_dconf=False)
return
if name == u'multiwildcardchar':
- self._multi_wildcard_char = value
- self._editor._multi_wildcard_char = value
- self.db.reset_phrases_cache()
+ self.set_multi_wildcard_char(value, update_dconf=False)
return
if name == u'autowildcard':
- self._auto_wildcard = value
- self._editor._auto_wildcard = value
- self.db.reset_phrases_cache()
+ self.set_autowildcard_mode(value, update_dconf=False)
return
diff -Nru ibus-table-1.9.18.orig/engine/table.py.orig ibus-table-1.9.18/engine/table.py.orig
--- ibus-table-1.9.18.orig/engine/table.py.orig 1970-01-01 01:00:00.000000000 +0100
+++ ibus-table-1.9.18/engine/table.py.orig 2020-07-22 14:43:13.607657038 +0200
@@ -0,0 +1,2890 @@
+# -*- coding: utf-8 -*-
+# vim:et sts=4 sw=4
+#
+# ibus-table - The Tables engine for IBus
+#
+# Copyright (c) 2008-2009 Yu Yuwei <acevery@gmail.com>
+# Copyright (c) 2009-2014 Caius "kaio" CHANCE <me@kaio.net>
+# Copyright (c) 2012-2015 Mike FABIAN <mfabian@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+__all__ = (
+ "tabengine",
+)
+
+import sys
+import os
+import string
+from gi import require_version
+require_version('IBus', '1.0')
+from gi.repository import IBus
+from gi.repository import GLib
+#import tabsqlitedb
+import re
+from gi.repository import GObject
+import time
+
+debug_level = int(0)
+
+from gettext import dgettext
+_ = lambda a : dgettext ("ibus-table", a)
+N_ = lambda a : a
+
+
+def ascii_ispunct(character):
+ '''
+ Use our own function instead of ascii.ispunct()
+ from “from curses import ascii” because the behaviour
+ of the latter is kind of weird. In Python 3.3.2 it does
+ for example:
+
+ >>> from curses import ascii
+ >>> ascii.ispunct('.')
+ True
+ >>> ascii.ispunct(u'.')
+ True
+ >>> ascii.ispunct('a')
+ False
+ >>> ascii.ispunct(u'a')
+ False
+ >>>
+ >>> ascii.ispunct(u'あ')
+ True
+ >>> ascii.ispunct('あ')
+ True
+ >>>
+
+ あ isnt punctuation. ascii.ispunct() only really works
+ in the ascii range, it returns weird results when used
+ over the whole unicode range. Maybe we should better use
+ unicodedata.category(), which works fine to figure out
+ what is punctuation for all of unicode. But at the moment
+ I am only porting from Python2 to Python3 and just want to
+ preserve the original behaviour for the moment.
+ '''
+ if character in '''!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~''':
+ return True
+ else:
+ return False
+
+def variant_to_value(variant):
+ if type(variant) != GLib.Variant:
+ return variant
+ type_string = variant.get_type_string()
+ if type_string == 's':
+ return variant.get_string()
+ elif type_string == 'i':
+ return variant.get_int32()
+ elif type_string == 'b':
+ return variant.get_boolean()
+ elif type_string == 'as':
+ # In the latest pygobject3 3.3.4 or later, g_variant_dup_strv
+ # returns the allocated strv but in the previous release,
+ # it returned the tuple of (strv, length)
+ if type(GLib.Variant.new_strv([]).dup_strv()) == tuple:
+ return variant.dup_strv()[0]
+ else:
+ return variant.dup_strv()
+ else:
+ print('error: unknown variant type: %s' %type_string)
+ return variant
+
+def argb(a, r, g, b):
+ return (((a & 0xff)<<24)
+ + ((r & 0xff) << 16)
+ + ((g & 0xff) << 8)
+ + (b & 0xff))
+
+def rgb(r, g, b):
+ return argb(255, r, g, b)
+
+__half_full_table = [
+ (0x0020, 0x3000, 1),
+ (0x0021, 0xFF01, 0x5E),
+ (0x00A2, 0xFFE0, 2),
+ (0x00A5, 0xFFE5, 1),
+ (0x00A6, 0xFFE4, 1),
+ (0x00AC, 0xFFE2, 1),
+ (0x00AF, 0xFFE3, 1),
+ (0x20A9, 0xFFE6, 1),
+ (0xFF61, 0x3002, 1),
+ (0xFF62, 0x300C, 2),
+ (0xFF64, 0x3001, 1),
+ (0xFF65, 0x30FB, 1),
+ (0xFF66, 0x30F2, 1),
+ (0xFF67, 0x30A1, 1),
+ (0xFF68, 0x30A3, 1),
+ (0xFF69, 0x30A5, 1),
+ (0xFF6A, 0x30A7, 1),
+ (0xFF6B, 0x30A9, 1),
+ (0xFF6C, 0x30E3, 1),
+ (0xFF6D, 0x30E5, 1),
+ (0xFF6E, 0x30E7, 1),
+ (0xFF6F, 0x30C3, 1),
+ (0xFF70, 0x30FC, 1),
+ (0xFF71, 0x30A2, 1),
+ (0xFF72, 0x30A4, 1),
+ (0xFF73, 0x30A6, 1),
+ (0xFF74, 0x30A8, 1),
+ (0xFF75, 0x30AA, 2),
+ (0xFF77, 0x30AD, 1),
+ (0xFF78, 0x30AF, 1),
+ (0xFF79, 0x30B1, 1),
+ (0xFF7A, 0x30B3, 1),
+ (0xFF7B, 0x30B5, 1),
+ (0xFF7C, 0x30B7, 1),
+ (0xFF7D, 0x30B9, 1),
+ (0xFF7E, 0x30BB, 1),
+ (0xFF7F, 0x30BD, 1),
+ (0xFF80, 0x30BF, 1),
+ (0xFF81, 0x30C1, 1),
+ (0xFF82, 0x30C4, 1),
+ (0xFF83, 0x30C6, 1),
+ (0xFF84, 0x30C8, 1),
+ (0xFF85, 0x30CA, 6),
+ (0xFF8B, 0x30D2, 1),
+ (0xFF8C, 0x30D5, 1),
+ (0xFF8D, 0x30D8, 1),
+ (0xFF8E, 0x30DB, 1),
+ (0xFF8F, 0x30DE, 5),
+ (0xFF94, 0x30E4, 1),
+ (0xFF95, 0x30E6, 1),
+ (0xFF96, 0x30E8, 6),
+ (0xFF9C, 0x30EF, 1),
+ (0xFF9D, 0x30F3, 1),
+ (0xFFA0, 0x3164, 1),
+ (0xFFA1, 0x3131, 30),
+ (0xFFC2, 0x314F, 6),
+ (0xFFCA, 0x3155, 6),
+ (0xFFD2, 0x315B, 9),
+ (0xFFE9, 0x2190, 4),
+ (0xFFED, 0x25A0, 1),
+ (0xFFEE, 0x25CB, 1)]
+
+def unichar_half_to_full(c):
+ code = ord(c)
+ for half, full, size in __half_full_table:
+ if code >= half and code < half + size:
+ if sys.version_info >= (3, 0, 0):
+ return chr(full + code - half)
+ else:
+ return unichr(full + code - half)
+ return c
+
+def unichar_full_to_half(c):
+ code = ord(c)
+ for half, full, size in __half_full_table:
+ if code >= full and code < full + size:
+ if sys.version_info >= (3, 0, 0):
+ return chr(half + code - full)
+ else:
+ return unichr(half + code - full)
+ return c
+
+SAVE_USER_COUNT_MAX = 16
+SAVE_USER_TIMEOUT = 30 # in seconds
+
+class KeyEvent:
+ def __init__(self, keyval, keycode, state):
+ self.val = keyval
+ self.code = keycode
+ self.state = state
+ def __str__(self):
+ return "%s 0x%08x" % (IBus.keyval_name(self.val), self.state)
+
+
+class editor(object):
+ '''Hold user inputs chars and preedit string'''
+ def __init__ (self, config, valid_input_chars, pinyin_valid_input_chars,
+ single_wildcard_char, multi_wildcard_char,
+ auto_wildcard, full_width_letter, full_width_punct,
+ max_key_length, database):
+ self.db = database
+ self._config = config
+ engine_name = os.path.basename(self.db.filename).replace('.db', '')
+ self._config_section = "engine/Table/%s" % engine_name.replace(' ','_')
+ self._max_key_length = int(max_key_length)
+ self._max_key_length_pinyin = 7
+ self._valid_input_chars = valid_input_chars
+ self._pinyin_valid_input_chars = pinyin_valid_input_chars
+ self._single_wildcard_char = single_wildcard_char
+ self._multi_wildcard_char = multi_wildcard_char
+ self._auto_wildcard = auto_wildcard
+ self._full_width_letter = full_width_letter
+ self._full_width_punct = full_width_punct
+ #
+ # The values below will be reset in
+ # self.clear_input_not_committed_to_preedit()
+ self._chars_valid = u'' # valid user input in table mode
+ self._chars_invalid = u'' # invalid user input in table mode
+ self._chars_valid_update_candidates_last = u''
+ self._chars_invalid_update_candidates_last = u''
+ # self._candidates holds the “best” candidates matching the user input
+ # [(tabkeys, phrase, freq, user_freq), ...]
+ self._candidates = []
+ self._candidates_previous = []
+
+ # self._u_chars: holds the user input of the phrases which
+ # have been automatically committed to preedit (but not yet
+ # “really” committed).
+ self._u_chars = []
+ # self._strings: holds the phrases which have been
+ # automatically committed to preedit (but not yet “really”
+ # committed).
+ #
+ # self._u_chars and self._strings should always have the same
+ # length, if I understand it correctly.
+ #
+ # Example when using the wubi-jidian86 table:
+ #
+ # self._u_chars = ['gaaa', 'gggg', 'ihty']
+ # self._strings = ['形式', '王', '小']
+ #
+ # I.e. after typing 'gaaa', '形式' is in the preedit and
+ # both self._u_chars and self._strings are empty. When typing
+ # another 'g', the maximum key length of the wubi table (which is 4)
+ # is exceeded and '形式' is automatically committed to the preedit
+ # (but not yet “really” committed, i.e. not yet committed into
+ # the application). The key 'gaaa' and the matching phrase '形式'
+ # are stored in self._u_chars and self._strings respectively
+ # and 'gaaa' is removed from self._chars_valid. Now self._chars_valid
+ # contains only the 'g' which starts a new search for candidates ...
+ # When removing the 'g' with backspace, the 'gaaa' is moved
+ # back from self._u_chars into self._chars_valid again and
+ # the same candidate list is shown as before the last 'g' had
+ # been entered.
+ self._strings = []
+ # self._cursor_precommit: The cursor
+ # position inthe array of strings which have already been
+ # committed to preëdit but not yet “really” committed.
+ self._cursor_precommit = 0
+
+ self._prompt_characters = eval(
+ self.db.ime_properties.get('char_prompts'))
+
+ select_keys_csv = variant_to_value(self._config.get_value(
+ self._config_section,
+ "LookupTableSelectKeys"))
+ if select_keys_csv == None:
+ select_keys_csv = self.db.get_select_keys()
+ if select_keys_csv == None:
+ select_keys_csv = '1,2,3,4,5,6,7,8,9'
+ self._select_keys = [
+ IBus.keyval_from_name(y)
+ for y in [x.strip() for x in select_keys_csv.split(",")]]
+ self._page_size = variant_to_value(self._config.get_value(
+ self._config_section,
+ "lookuptablepagesize"))
+ if self._page_size == None or self._page_size > len(self._select_keys):
+ self._page_size = len(self._select_keys)
+ self._orientation = variant_to_value(self._config.get_value(
+ self._config_section,
+ "LookupTableOrientation"))
+ if self._orientation == None:
+ self._orientation = self.db.get_orientation()
+ self._lookup_table = self.get_new_lookup_table(
+ page_size = self._page_size,
+ select_keys = self._select_keys,
+ orientation = self._orientation)
+ # self._py_mode: whether in pinyin mode
+ self._py_mode = False
+ # self._onechar: whether we only select single character
+ self._onechar = variant_to_value(self._config.get_value(
+ self._config_section,
+ "OneChar"))
+ if self._onechar == None:
+ self._onechar = False
+ # self._chinese_mode: the candidate filter mode,
+ # 0 means to show simplified Chinese only
+ # 1 means to show traditional Chinese only
+ # 2 means to show all characters but show simplified Chinese first
+ # 3 means to show all characters but show traditional Chinese first
+ # 4 means to show all characters
+ # we use LC_CTYPE or LANG to determine which one to use if
+ # no default comes from the config.
+ self._chinese_mode = variant_to_value(self._config.get_value(
+ self._config_section,
+ "ChineseMode"))
+ if self._chinese_mode == None:
+ self._chinese_mode = self.get_chinese_mode()
+ elif debug_level > 1:
+ sys.stderr.write(
+ "Chinese mode found in user config, mode=%s\n"
+ % self._chinese_mode)
+
+ # If auto select is true, then the first candidate phrase will
+ # be selected automatically during typing. Auto select is true
+ # by default for the stroke5 table for example.
+ self._auto_select = variant_to_value(self._config.get_value(
+ self._config_section,
+ "AutoSelect"))
+ if self._auto_select == None:
+ if self.db.ime_properties.get('auto_select') != None:
+ self._auto_select = self.db.ime_properties.get(
+ 'auto_select').lower() == u'true'
+ else:
+ self._auto_select = False
+
+ def get_new_lookup_table(
+ self, page_size=10,
+ select_keys=[49, 50, 51, 52, 53, 54, 55, 56, 57, 48],
+ orientation=True):
+ '''
+ [49, 50, 51, 52, 53, 54, 55, 56, 57, 48] are the key codes
+ for the characters ['1', '2', '3', '4', '5', '6', '7', '8', '0']
+ '''
+ if page_size < 1:
+ page_size = 1
+ if page_size > len(select_keys):
+ page_size = len(select_keys)
+ lookup_table = IBus.LookupTable.new(
+ page_size=page_size,
+ cursor_pos=0,
+ cursor_visible=True,
+ round=True)
+ for keycode in select_keys:
+ lookup_table.append_label(
+ IBus.Text.new_from_string("%s." %IBus.keyval_name(keycode)))
+ lookup_table.set_orientation(orientation)
+ return lookup_table
+
+ def get_select_keys(self):
+ """
+ Returns the list of key codes for the select keys.
+ For example, if the select keys are ["1", "2", ...] the
+ key codes are [49, 50, ...]. If the select keys are
+ ["F1", "F2", ...] the key codes are [65470, 65471, ...]
+ """
+ return self._select_keys
+
+ def get_chinese_mode (self):
+ '''
+ Use db value or LC_CTYPE in your box to determine the _chinese_mode
+ '''
+ # use db value, if applicable
+ __db_chinese_mode = self.db.get_chinese_mode()
+ if __db_chinese_mode >= 0:
+ if debug_level > 1:
+ sys.stderr.write(
+ "get_chinese_mode(): "
+ + "default Chinese mode found in database, mode=%s\n"
+ %__db_chinese_mode)
+ return __db_chinese_mode
+ # otherwise
+ try:
+ if 'LC_ALL' in os.environ:
+ __lc = os.environ['LC_ALL'].split('.')[0].lower()
+ if debug_level > 1:
+ sys.stderr.write(
+ "get_chinese_mode(): __lc=%s found in LC_ALL\n"
+ % __lc)
+ elif 'LC_CTYPE' in os.environ:
+ __lc = os.environ['LC_CTYPE'].split('.')[0].lower()
+ if debug_level > 1:
+ sys.stderr.write(
+ "get_chinese_mode(): __lc=%s found in LC_CTYPE\n"
+ % __lc)
+ else:
+ __lc = os.environ['LANG'].split('.')[0].lower()
+ if debug_level > 1:
+ sys.stderr.write(
+ "get_chinese_mode(): __lc=%s found in LANG\n"
+ % __lc)
+
+ if '_cn' in __lc or '_sg' in __lc:
+ # CN and SG should prefer traditional Chinese by default
+ return 2 # show simplified Chinese first
+ elif '_hk' in __lc or '_tw' in __lc or '_mo' in __lc:
+ # HK, TW, and MO should prefer traditional Chinese by default
+ return 3 # show traditional Chinese first
+ else:
+ if self.db._is_chinese:
+ # This table is used for Chinese, but we dont
+ # know for which variant. Therefore, better show
+ # all Chinese characters and dont prefer any
+ # variant:
+ if debug_level > 1:
+ sys.stderr.write(
+ "get_chinese_mode(): last fallback, "
+ + "database is Chinese but we dont know "
+ + "which variant.\n")
+ return 4 # show all Chinese characters
+ else:
+ if debug_level > 1:
+ sys.stderr.write(
+ "get_chinese_mode(): last fallback, "
+ + "database is not Chinese, returning -1.\n")
+ return -1
+ except:
+ import traceback
+ traceback.print_exc()
+ return -1
+
+ def clear_all_input_and_preedit(self):
+ '''
+ Clear all input, whether committed to preëdit or not.
+ '''
+ if debug_level > 1:
+ sys.stderr.write("clear_all_input_and_preedit()\n")
+ self.clear_input_not_committed_to_preedit()
+ self._u_chars = []
+ self._strings = []
+ self._cursor_precommit = 0
+ self.update_candidates()
+
+ def is_empty(self):
+ return u'' == self._chars_valid + self._chars_invalid
+
+ def clear_input_not_committed_to_preedit(self):
+ '''
+ Clear the input which has not yet been committed to preëdit.
+ '''
+ if debug_level > 1:
+ sys.stderr.write("clear_input_not_committed_to_preedit()\n")
+ self._chars_valid = u''
+ self._chars_invalid = u''
+ self._chars_valid_update_candidates_last = u''
+ self._chars_invalid_update_candidates_last = u''
+ self._lookup_table.clear()
+ self._lookup_table.set_cursor_visible(True)
+ self._candidates = []
+ self._candidates_previous = []
+
+ def add_input(self, c):
+ '''
+ Add input character and update candidates.
+
+ Returns “True” if candidates were found, “False” if not.
+ '''
+ if (self._chars_invalid
+ or (not self._py_mode
+ and (c not in
+ self._valid_input_chars
+ + self._single_wildcard_char
+ + self._multi_wildcard_char))
+ or (self._py_mode
+ and (c not in
+ self._pinyin_valid_input_chars
+ + self._single_wildcard_char
+ + self._multi_wildcard_char))):
+ self._chars_invalid += c
+ else:
+ self._chars_valid += c
+ res = self.update_candidates()
+ return res
+
+ def pop_input(self):
+ '''remove and display last input char held'''
+ _c = ''
+ if self._chars_invalid:
+ _c = self._chars_invalid[-1]
+ self._chars_invalid = self._chars_invalid[:-1]
+ elif self._chars_valid:
+ _c = self._chars_valid[-1]
+ self._chars_valid = self._chars_valid[:-1]
+ if (not self._chars_valid) and self._u_chars:
+ self._chars_valid = self._u_chars.pop(
+ self._cursor_precommit - 1)
+ self._strings.pop(self._cursor_precommit - 1)
+ self._cursor_precommit -= 1
+ self.update_candidates ()
+ return _c
+
+ def get_input_chars (self):
+ '''get characters held, valid and invalid'''
+ return self._chars_valid + self._chars_invalid
+
+ def split_strings_committed_to_preedit(self, index, index_in_phrase):
+ head = self._strings[index][:index_in_phrase]
+ tail = self._strings[index][index_in_phrase:]
+ self._u_chars.pop(index)
+ self._strings.pop(index)
+ self._u_chars.insert(index, self.db.parse_phrase(head))
+ self._strings.insert(index, head)
+ self._u_chars.insert(index+1, self.db.parse_phrase(tail))
+ self._strings.insert(index+1, tail)
+
+ def remove_preedit_before_cursor(self):
+ '''Remove preëdit left of cursor'''
+ if self._chars_invalid:
+ return
+ if self.get_input_chars():
+ self.commit_to_preedit()
+ if not self._strings:
+ return
+ if self._cursor_precommit <= 0:
+ return
+ self._u_chars = self._u_chars[self._cursor_precommit:]
+ self._strings = self._strings[self._cursor_precommit:]
+ self._cursor_precommit = 0
+
+ def remove_preedit_after_cursor(self):
+ '''Remove preëdit right of cursor'''
+ if self._chars_invalid:
+ return
+ if self.get_input_chars():
+ self.commit_to_preedit()
+ if not self._strings:
+ return
+ if self._cursor_precommit >= len(self._strings):
+ return
+ self._u_chars = self._u_chars[:self._cursor_precommit]
+ self._strings = self._strings[:self._cursor_precommit]
+ self._cursor_precommit = len(self._strings)
+
+ def remove_preedit_character_before_cursor(self):
+ '''Remove character before cursor in strings comitted to preëdit'''
+ if self._chars_invalid:
+ return
+ if self.get_input_chars():
+ self.commit_to_preedit()
+ if not self._strings:
+ return
+ if self._cursor_precommit < 1:
+ return
+ self._cursor_precommit -= 1
+ self._chars_valid = self._u_chars.pop(self._cursor_precommit)
+ self._strings.pop(self._cursor_precommit)
+ self.update_candidates()
+
+ def remove_preedit_character_after_cursor (self):
+ '''Remove character after cursor in strings committed to preëdit'''
+ if self._chars_invalid:
+ return
+ if self.get_input_chars():
+ self.commit_to_preedit()
+ if not self._strings:
+ return
+ if self._cursor_precommit > len(self._strings) - 1:
+ return
+ self._u_chars.pop(self._cursor_precommit)
+ self._strings.pop(self._cursor_precommit)
+
+ def get_preedit_tabkeys_parts(self):
+ '''Returns the tabkeys which were used to type the parts
+ of the preëdit string.
+
+ Such as “(left_of_current_edit, current_edit, right_of_current_edit)”
+
+ “left_of_current_edit” and “right_of_current_edit” are
+ strings of tabkeys which have been typed to get the phrases
+ which have already been committed to preëdit, but not
+ “really” committed yet. “current_edit” is the string of
+ tabkeys of the part of the preëdit string which is not
+ committed at all.
+
+ For example, the return value could look like:
+
+ (('gggg', 'aahw'), 'adwu', ('ijgl', 'jbus'))
+
+ See also get_preedit_string_parts() which might return something
+ like
+
+ (('王', '工具'), '其', ('漫画', '最新'))
+
+ when the wubi-jidian86 table is used.
+ '''
+ left_of_current_edit = ()
+ current_edit = u''
+ right_of_current_edit = ()
+ if self.get_input_chars():
+ current_edit = self.get_input_chars()
+ if self._u_chars:
+ left_of_current_edit = tuple(
+ self._u_chars[:self._cursor_precommit])
+ right_of_current_edit = tuple(
+ self._u_chars[self._cursor_precommit:])
+ return (left_of_current_edit, current_edit, right_of_current_edit)
+
+ def get_preedit_tabkeys_complete(self):
+ '''Returns the tabkeys which belong to the parts of the preëdit
+ string as a single string
+ '''
+ (left_tabkeys,
+ current_tabkeys,
+ right_tabkeys) = self.get_preedit_tabkeys_parts()
+ return (u''.join(left_tabkeys)
+ + current_tabkeys
+ + u''.join(right_tabkeys))
+
+ def get_preedit_string_parts(self):
+ '''Returns the phrases which are parts of the preëdit string.
+
+ Such as “(left_of_current_edit, current_edit, right_of_current_edit)”
+
+ “left_of_current_edit” and “right_of_current_edit” are
+ tuples of strings which have already been committed to preëdit, but not
+ “really” committed yet. “current_edit” is the phrase in the part of the
+ preëdit string which is not yet committed at all.
+
+ For example, the return value could look like:
+
+ (('王', '工具'), '其', ('漫画', '最新'))
+
+ See also get_preedit_tabkeys_parts() which might return something
+ like
+
+ (('gggg', 'aahw'), 'adwu', ('ijgl', 'jbus'))
+
+ when the wubi-jidian86 table is used.
+ '''
+ left_of_current_edit = ()
+ current_edit = u''
+ right_of_current_edit = ()
+ if self._candidates:
+ current_edit = self._candidates[
+ int(self._lookup_table.get_cursor_pos())][1]
+ elif self.get_input_chars():
+ current_edit = self.get_input_chars()
+ if self._strings:
+ left_of_current_edit = tuple(
+ self._strings[:self._cursor_precommit])
+ right_of_current_edit = tuple(
+ self._strings[self._cursor_precommit:])
+ return (left_of_current_edit, current_edit, right_of_current_edit)
+
+ def get_preedit_string_complete(self):
+ '''Returns the phrases which are parts of the preëdit string as a
+ single string
+
+ '''
+ (left_strings,
+ current_string,
+ right_strings) = self.get_preedit_string_parts()
+ return u''.join(left_strings) + current_string + u''.join(right_strings)
+
+ def get_caret (self):
+ '''Get caret position in preëdit string'''
+ caret = 0
+ if self._cursor_precommit and self._strings:
+ for x in self._strings[:self._cursor_precommit]:
+ caret += len(x)
+ if self._candidates:
+ caret += len(
+ self._candidates[int(self._lookup_table.get_cursor_pos())][1])
+ else:
+ caret += len(self.get_input_chars())
+ return caret
+
+ def arrow_left(self):
+ '''Move cursor left in the preëdit string.'''
+ if self._chars_invalid:
+ return
+ if self.get_input_chars():
+ self.commit_to_preedit()
+ if not self._strings:
+ return
+ if self._cursor_precommit <= 0:
+ return
+ if len(self._strings[self._cursor_precommit-1]) <= 1:
+ self._cursor_precommit -= 1
+ else:
+ self.split_strings_committed_to_preedit(
+ self._cursor_precommit-1, -1)
+ self.update_candidates()
+
+ def arrow_right(self):
+ '''Move cursor right in the preëdit string.'''
+ if self._chars_invalid:
+ return
+ if self.get_input_chars():
+ self.commit_to_preedit()
+ if not self._strings:
+ return
+ if self._cursor_precommit >= len(self._strings):
+ return
+ self._cursor_precommit += 1
+ if len(self._strings[self._cursor_precommit-1]) > 1:
+ self.split_strings_committed_to_preedit(self._cursor_precommit-1, 1)
+ self.update_candidates()
+
+ def control_arrow_left(self):
+ '''Move cursor to the beginning of the preëdit string.'''
+ if self._chars_invalid:
+ return
+ if self.get_input_chars():
+ self.commit_to_preedit()
+ if not self._strings:
+ return
+ self._cursor_precommit = 0
+ self.update_candidates ()
+
+ def control_arrow_right(self):
+ '''Move cursor to the end of the preëdit string'''
+ if self._chars_invalid:
+ return
+ if self.get_input_chars():
+ self.commit_to_preedit()
+ if not self._strings:
+ return
+ self._cursor_precommit = len(self._strings)
+ self.update_candidates ()
+
+ def append_candidate_to_lookup_table(
+ self, tabkeys=u'', phrase=u'', freq=0, user_freq=0):
+ '''append candidate to lookup_table'''
+ if debug_level > 1:
+ sys.stderr.write(
+ "append_candidate() "
+ + "tabkeys=%(t)s phrase=%(p)s freq=%(f)s user_freq=%(u)s\n"
+ % {'t': tabkeys, 'p': phrase, 'f': freq, 'u': user_freq})
+ if not tabkeys or not phrase:
+ return
+ regexp = self._chars_valid
+ if self._multi_wildcard_char:
+ regexp = regexp.replace(
+ self._multi_wildcard_char, '_multi_wildchard_char_')
+ if self._single_wildcard_char:
+ regexp = regexp.replace(
+ self._single_wildcard_char, '_single_wildchard_char_')
+ regexp = re.escape(regexp)
+ regexp = regexp.replace('_multi_wildchard_char_', '.*')
+ regexp = regexp.replace('_single_wildchard_char_', '.?')
+ match = re.match(r'^'+regexp, tabkeys)
+ if match:
+ remaining_tabkeys = tabkeys[match.end():]
+ else:
+ # This should never happen! For the candidates
+ # added to the lookup table here, a match has
+ # been found for self._chars_valid in the database.
+ # In that case, the above regular expression should
+ # match as well.
+ remaining_tabkeys = tabkeys
+ if debug_level > 1:
+ sys.stderr.write(
+ "append_candidate() "
+ + "remaining_tabkeys=%(remaining_tabkeys)s "
+ + "self._chars_valid=%(chars_valid)s phrase=%(phrase)s\n"
+ % {'remaining_tabkeys': remaining_tabkeys,
+ 'chars_valid': self._chars_valid,
+ 'phrase': phrase})
+ table_code = u''
+ if self.db._is_chinese and self._py_mode:
+ # restore tune symbol
+ remaining_tabkeys = remaining_tabkeys.replace(
+ '!','↑1').replace(
+ '@','↑2').replace(
+ '#','↑3').replace(
+ '$','↑4').replace(
+ '%','↑5')
+ # If in pinyin mode, phrase can only be one character.
+ # When using pinyin mode for a table like Wubi or Cangjie,
+ # the reason is probably because one does not know the
+ # Wubi or Cangjie code. So get that code from the table
+ # and display it as well to help the user learn that code.
+ # The Wubi tables contain several codes for the same
+ # character, therefore self.db.find_zi_code(phrase) may
+ # return a list. The last code in that list is the full
+ # table code for that characters, other entries in that
+ # list are shorter substrings of the full table code which
+ # are not interesting to display. Therefore, we use only
+ # the last element of the list of table codes.
+ possible_table_codes = self.db.find_zi_code(phrase)
+ if possible_table_codes:
+ table_code = possible_table_codes[-1]
+ table_code_new = u''
+ for char in table_code:
+ if char in self._prompt_characters:
+ table_code_new += self._prompt_characters[char]
+ else:
+ table_code_new += char
+ table_code = table_code_new
+ if not self._py_mode:
+ remaining_tabkeys_new = u''
+ for char in remaining_tabkeys:
+ if char in self._prompt_characters:
+ remaining_tabkeys_new += self._prompt_characters[char]
+ else:
+ remaining_tabkeys_new += char
+ remaining_tabkeys = remaining_tabkeys_new
+ candidate_text = phrase + u' ' + remaining_tabkeys
+ if table_code:
+ candidate_text = candidate_text + u' ' + table_code
+ attrs = IBus.AttrList ()
+ attrs.append(IBus.attr_foreground_new(
+ rgb(0x19,0x73,0xa2), 0, len(candidate_text)))
+ if not self._py_mode and freq < 0:
+ # this is a user defined phrase:
+ attrs.append(
+ IBus.attr_foreground_new(rgb(0x77,0x00,0xc3), 0, len(phrase)))
+ elif not self._py_mode and user_freq > 0:
+ # this is a system phrase which has already been used by the user:
+ attrs.append(IBus.attr_foreground_new(
+ rgb(0x00,0x00,0x00), 0, len(phrase)))
+ else:
+ # this is a system phrase that has not been used yet:
+ attrs.append(IBus.attr_foreground_new(
+ rgb(0x00,0x00,0x00), 0, len(phrase)))
+ if debug_level > 0:
+ debug_text = u' ' + str(freq) + u' ' + str(user_freq)
+ candidate_text += debug_text
+ attrs.append(IBus.attr_foreground_new(
+ rgb(0x00,0xff,0x00),
+ len(candidate_text) - len(debug_text),
+ len(candidate_text)))
+ text = IBus.Text.new_from_string(candidate_text)
+ i = 0
+ while attrs.get(i) != None:
+ attr = attrs.get(i)
+ text.append_attribute(attr.get_attr_type(),
+ attr.get_value(),
+ attr.get_start_index(),
+ attr.get_end_index())
+ i += 1
+ self._lookup_table.append_candidate (text)
+ self._lookup_table.set_cursor_visible(True)
+
+ def update_candidates (self):
+ '''
+ Searches for candidates and updates the lookuptable.
+
+ Returns “True” if candidates were found and “False” if not.
+ '''
+ if debug_level > 1:
+ sys.stderr.write(
+ "update_candidates() "
+ + "self._chars_valid=%(chars_valid)s "
+ + "self._chars_invalid=%(chars_invalid)s "
+ + "self._chars_valid_update_candidates_last=%(chars_last)s "
+ + "self._candidates=%(candidates)s "
+ + "self.db.startchars=%(start)s "
+ + "self._strings=%(strings)s\n"
+ % {'chars_valid': self._chars_valid,
+ 'chars_invalid': self._chars_invalid,
+ 'chars_last': self._chars_valid_update_candidates_last,
+ 'candidates': self._candidates,
+ 'start': self.db.startchars,
+ 'strings': self._strings})
+ if (self._chars_valid == self._chars_valid_update_candidates_last
+ and
+ self._chars_invalid == self._chars_invalid_update_candidates_last):
+ # The input did not change since we came here last, do
+ # nothing and leave candidates and lookup table unchanged:
+ if self._candidates:
+ return True
+ else:
+ return False
+ self._chars_valid_update_candidates_last = self._chars_valid
+ self._chars_invalid_update_candidates_last = self._chars_invalid
+ self._lookup_table.clear()
+ self._lookup_table.set_cursor_visible(True)
+ if self._chars_invalid or not self._chars_valid:
+ self._candidates = []
+ self._candidates_previous = self._candidates
+ return False
+ if self._py_mode and self.db._is_chinese:
+ self._candidates = self.db.select_chinese_characters_by_pinyin(
+ tabkeys=self._chars_valid,
+ chinese_mode=self._chinese_mode,
+ single_wildcard_char=self._single_wildcard_char,
+ multi_wildcard_char=self._multi_wildcard_char)
+ else:
+ self._candidates = self.db.select_words(
+ tabkeys=self._chars_valid,
+ onechar=self._onechar,
+ chinese_mode=self._chinese_mode,
+ single_wildcard_char=self._single_wildcard_char,
+ multi_wildcard_char=self._multi_wildcard_char,
+ auto_wildcard=self._auto_wildcard)
+ # If only a wildcard character has been typed, insert a
+ # special candidate at the first position for the wildcard
+ # character itself. For example, if “?” is used as a
+ # wildcard character and this is the only character typed, add
+ # a candidate ('?', '?', 0, 1000000000) in halfwidth mode or a
+ # candidate ('?', '', 0, 1000000000) in fullwidth mode.
+ # This is needed to make it possible to input the wildcard
+ # characters themselves, if “?” acted only as a wildcard
+ # it would be impossible to input a fullwidth question mark.
+ if (self._chars_valid
+ in [self._single_wildcard_char, self._multi_wildcard_char]):
+ wildcard_key = self._chars_valid
+ wildcard_phrase = self._chars_valid
+ if ascii_ispunct(wildcard_key):
+ if self._full_width_punct[1]:
+ wildcard_phrase = unichar_half_to_full(wildcard_phrase)
+ else:
+ wildcard_phrase = unichar_full_to_half(wildcard_phrase)
+ else:
+ if self._full_width_letter[1]:
+ wildcard_phrase = unichar_half_to_full(wildcard_phrase)
+ else:
+ wildcard_phrase = unichar_full_to_half(wildcard_phrase)
+ self._candidates.insert(
+ 0, (wildcard_key, wildcard_phrase, 0, 1000000000))
+ if self._candidates:
+ self.fill_lookup_table()
+ self._candidates_previous = self._candidates
+ return True
+ # There are only valid and no invalid input characters but no
+ # matching candidates could be found from the databases. The
+ # last of self._chars_valid must have caused this. That
+ # character is valid in the sense that it is listed in
+ # self._valid_input_chars, it is only invalid in the sense
+ # that after adding this character, no candidates could be
+ # found anymore. Add this character to self._chars_invalid
+ # and remove it from self._chars_valid.
+ self._chars_invalid += self._chars_valid[-1]
+ self._chars_valid = self._chars_valid[:-1]
+ self._chars_valid_update_candidates_last = self._chars_valid
+ self._chars_invalid_update_candidates_last = self._chars_invalid
+ return False
+
+ def commit_to_preedit(self):
+ '''Add selected phrase in lookup table to preëdit string'''
+ if not self._chars_valid:
+ return False
+ if self._candidates:
+ self._u_chars.insert(self._cursor_precommit,
+ self._candidates[self.get_cursor_pos()][0])
+ self._strings.insert(self._cursor_precommit,
+ self._candidates[self.get_cursor_pos()][1])
+ self._cursor_precommit += 1
+ self.clear_input_not_committed_to_preedit()
+ self.update_candidates()
+ return True
+
+ def commit_to_preedit_current_page(self, index):
+ '''
+ Commits the candidate at position “index” in the current
+ page of the lookup table to the preëdit. Does not yet “really”
+ commit the candidate, only to the preëdit.
+ '''
+ cursor_pos = self._lookup_table.get_cursor_pos()
+ cursor_in_page = self._lookup_table.get_cursor_in_page()
+ current_page_start = cursor_pos - cursor_in_page
+ real_index = current_page_start + index
+ if real_index >= len(self._candidates):
+ # the index given is out of range we do not commit anything
+ return False
+ self._lookup_table.set_cursor_pos(real_index)
+ return self.commit_to_preedit()
+
+ def get_aux_strings (self):
+ '''Get aux strings'''
+ input_chars = self.get_input_chars ()
+ if input_chars:
+ aux_string = input_chars
+ if debug_level > 0 and self._u_chars:
+ (tabkeys_left,
+ tabkeys_current,
+ tabkeys_right) = self.get_preedit_tabkeys_parts()
+ (strings_left,
+ string_current,
+ strings_right) = self.get_preedit_string_parts()
+ aux_string = u''
+ for i in range(0, len(strings_left)):
+ aux_string += (
+ u'('
+ + tabkeys_left[i] + u' '+ strings_left[i]
+ + u') ')
+ aux_string += input_chars
+ for i in range(0, len(strings_right)):
+ aux_string += (
+ u' ('
+ + tabkeys_right[i]+u' '+strings_right[i]
+ + u')')
+ if self._py_mode:
+ aux_string = aux_string.replace(
+ '!','1').replace(
+ '@','2').replace(
+ '#','3').replace(
+ '$','4').replace(
+ '%','5')
+ else:
+ aux_string_new = u''
+ for char in aux_string:
+ if char in self._prompt_characters:
+ aux_string_new += self._prompt_characters[char]
+ else:
+ aux_string_new += char
+ aux_string = aux_string_new
+ return aux_string
+
+ # There are no input strings at the moment. But there could
+ # be stuff committed to the preëdit. If there is something
+ # committed to the preëdit, show some information in the
+ # auxiliary text.
+ #
+ # For the character at the position of the cursor in the
+ # preëdit, show a list of possible input key sequences which
+ # could be used to type that character at the left side of the
+ # auxiliary text.
+ #
+ # If the preëdit is longer than one character, show the input
+ # key sequence which will be defined for the complete current
+ # contents of the preëdit, if the preëdit is committed.
+ aux_string = u''
+ if self._strings:
+ if self._cursor_precommit >= len(self._strings):
+ char = self._strings[-1][0]
+ else:
+ char = self._strings[self._cursor_precommit][0]
+ aux_string = u' '.join(self.db.find_zi_code(char))
+ cstr = u''.join(self._strings)
+ if self.db.user_can_define_phrase:
+ if len(cstr) > 1:
+ aux_string += (u'\t#: ' + self.db.parse_phrase(cstr))
+ aux_string_new = u''
+ for char in aux_string:
+ if char in self._prompt_characters:
+ aux_string_new += self._prompt_characters[char]
+ else:
+ aux_string_new += char
+ return aux_string_new
+
+ def fill_lookup_table(self):
+ '''Fill more entries to self._lookup_table if needed.
+
+ If the cursor in _lookup_table moved beyond current length,
+ add more entries from _candidiate[0] to _lookup_table.'''
+
+ looklen = self._lookup_table.get_number_of_candidates()
+ psize = self._lookup_table.get_page_size()
+ if (self._lookup_table.get_cursor_pos() + psize >= looklen and
+ looklen < len(self._candidates)):
+ endpos = looklen + psize
+ batch = self._candidates[looklen:endpos]
+ for x in batch:
+ self.append_candidate_to_lookup_table(
+ tabkeys=x[0], phrase=x[1], freq=x[2], user_freq=x[3])
+
+ def cursor_down(self):
+ '''Process Arrow Down Key Event
+ Move Lookup Table cursor down'''
+ self.fill_lookup_table()
+
+ res = self._lookup_table.cursor_down()
+ self.update_candidates ()
+ if not res and self._candidates:
+ return True
+ return res
+
+ def cursor_up(self):
+ '''Process Arrow Up Key Event
+ Move Lookup Table cursor up'''
+ res = self._lookup_table.cursor_up()
+ self.update_candidates ()
+ if not res and self._candidates:
+ return True
+ return res
+
+ def page_down(self):
+ '''Process Page Down Key Event
+ Move Lookup Table page down'''
+ self.fill_lookup_table()
+ res = self._lookup_table.page_down()
+ self.update_candidates ()
+ if not res and self._candidates:
+ return True
+ return res
+
+ def page_up(self):
+ '''Process Page Up Key Event
+ move Lookup Table page up'''
+ res = self._lookup_table.page_up()
+ self.update_candidates ()
+ if not res and self._candidates:
+ return True
+ return res
+
+ def select_key(self, keycode):
+ '''
+ Commit a candidate which was selected by typing a selection key
+ from the lookup table to the preedit. Does not yet “really”
+ commit the candidate, only to the preedit.
+ '''
+ if keycode not in self._select_keys:
+ return False
+ return self.commit_to_preedit_current_page(
+ self._select_keys.index(keycode))
+
+ def remove_candidate_from_user_database(self, keycode):
+ '''Remove a candidate displayed in the lookup table from the user
+ database.
+
+ The candidate indicated by the selection key with the key code
+ “keycode” is removed, if possible. If it is not in the user
+ database at all, nothing happens.
+
+ If this is a candidate which is also in the system database,
+ removing it from the user database only means that its user
+ frequency data is reset. It might still appear in subsequent
+ matches but with much lower priority.
+
+ If this is a candidate which is user defined and not in the system
+ database, it will not match at all anymore after removing it.
+
+ '''
+ if keycode not in self._select_keys:
+ return False
+ index = self._select_keys.index(keycode)
+ cursor_pos = self._lookup_table.get_cursor_pos()
+ cursor_in_page = self._lookup_table.get_cursor_in_page()
+ current_page_start = cursor_pos - cursor_in_page
+ real_index = current_page_start + index
+ if len(self._candidates) > real_index: # this index is valid
+ candidate = self._candidates[real_index]
+ self.db.remove_phrase(
+ tabkeys=candidate[0], phrase=candidate[1], commit=True)
+ # call update_candidates() to get a new SQL query. The
+ # input has not really changed, therefore we must clear
+ # the remembered list of characters to
+ # force update_candidates() to really do something and not
+ # return immediately:
+ self._chars_valid_update_candidates_last = u''
+ self._chars_invalid_update_candidates_last = u''
+ self.update_candidates()
+ return True
+ else:
+ return False
+
+ def get_cursor_pos (self):
+ '''get lookup table cursor position'''
+ return self._lookup_table.get_cursor_pos()
+
+ def get_lookup_table (self):
+ '''Get lookup table'''
+ return self._lookup_table
+
+ def remove_char(self):
+ '''Process remove_char Key Event'''
+ if debug_level > 1:
+ sys.stderr.write("remove_char()\n")
+ if self.get_input_chars():
+ self.pop_input ()
+ return
+ self.remove_preedit_character_before_cursor()
+
+ def delete(self):
+ '''Process delete Key Event'''
+ if self.get_input_chars():
+ return
+ self.remove_preedit_character_after_cursor()
+
+ def cycle_next_cand(self):
+ '''Cycle cursor to next candidate in the page.'''
+ total = len(self._candidates)
+
+ if total > 0:
+ page_size = self._lookup_table.get_page_size()
+ pos = self._lookup_table.get_cursor_pos()
+ page = int(pos/page_size)
+ pos += 1
+ if pos >= (page+1)*page_size or pos >= total:
+ pos = page*page_size
+ res = self._lookup_table.set_cursor_pos(pos)
+ return True
+ else:
+ return False
+
+ def one_candidate (self):
+ '''Return true if there is only one candidate'''
+ return len(self._candidates) == 1
+
+
+########################
+### Engine Class #####
+####################
+class tabengine (IBus.Engine):
+ '''The IM Engine for Tables'''
+
+ def __init__(self, bus, obj_path, db ):
+ super(tabengine, self).__init__(connection=bus.get_connection(),
+ object_path=obj_path)
+ global debug_level
+ try:
+ debug_level = int(os.getenv('IBUS_TABLE_DEBUG_LEVEL'))
+ except (TypeError, ValueError):
+ debug_level = int(0)
+ self._input_purpose = 0
+ self._has_input_purpose = False
+ if hasattr(IBus, 'InputPurpose'):
+ self._has_input_purpose = True
+ self._bus = bus
+ # this is the backend sql db we need for our IME
+ # we receive this db from IMEngineFactory
+ #self.db = tabsqlitedb.tabsqlitedb( name = dbname )
+ self.db = db
+ self._setup_pid = 0
+ self._icon_dir = '%s%s%s%s' % (os.getenv('IBUS_TABLE_LOCATION'),
+ os.path.sep, 'icons', os.path.sep)
+ # name for config section
+ self._engine_name = os.path.basename(
+ self.db.filename).replace('.db', '')
+ self._config_section = (
+ "engine/Table/%s" % self._engine_name.replace(' ','_'))
+
+ # config module
+ self._config = self._bus.get_config ()
+ self._config.connect ("value-changed", self.config_value_changed_cb)
+
+ # self._ime_py: Indicates whether this table supports pinyin mode
+ self._ime_py = self.db.ime_properties.get('pinyin_mode')
+ if self._ime_py:
+ if self._ime_py.lower() == u'true':
+ self._ime_py = True
+ else:
+ self._ime_py = False
+ else:
+ print('We could not find "pinyin_mode" entry in database, '
+ + 'is it an outdated database?')
+ self._ime_py = False
+
+ self._symbol = self.db.ime_properties.get('symbol')
+ if self._symbol == None or self._symbol == u'':
+ self._symbol = self.db.ime_properties.get('status_prompt')
+ if self._symbol == None:
+ self._symbol = u''
+ # some Chinese tables have “STATUS_PROMPT = CN” replace it
+ # with the shorter and nicer “中”:
+ if self._symbol == u'CN':
+ self._symbol = u'中'
+ # workaround for the translit and translit-ua tables which
+ # have 2 character symbols. '☑' + self._symbol then is
+ # 3 characters and currently gnome-shell ignores symbols longer
+ # than 3 characters:
+ if self._symbol == u'Ya':
+ self._symbol = u'Я'
+ if self._symbol == u'Yi':
+ self._symbol = u'Ї'
+ # now we check and update the valid input characters
+ self._valid_input_chars = self.db.ime_properties.get(
+ 'valid_input_chars')
+ self._pinyin_valid_input_chars = u'abcdefghijklmnopqrstuvwxyz!@#$%'
+
+ self._single_wildcard_char = variant_to_value(self._config.get_value(
+ self._config_section,
+ "singlewildcardchar"))
+ if self._single_wildcard_char == None:
+ self._single_wildcard_char = self.db.ime_properties.get(
+ 'single_wildcard_char')
+ if self._single_wildcard_char == None:
+ self._single_wildcard_char = u''
+ if len(self._single_wildcard_char) > 1:
+ self._single_wildcard_char = self._single_wildcard_char[0]
+
+ self._multi_wildcard_char = variant_to_value(self._config.get_value(
+ self._config_section,
+ "multiwildcardchar"))
+ if self._multi_wildcard_char == None:
+ self._multi_wildcard_char = self.db.ime_properties.get(
+ 'multi_wildcard_char')
+ if self._multi_wildcard_char == None:
+ self._multi_wildcard_char = u''
+ if len(self._multi_wildcard_char) > 1:
+ self._multi_wildcard_char = self._multi_wildcard_char[0]
+
+ self._auto_wildcard = variant_to_value(self._config.get_value(
+ self._config_section,
+ "autowildcard"))
+ if self._auto_wildcard == None:
+ self._auto_wildcard = self.db.ime_properties.get('auto_wildcard')
+ if self._auto_wildcard and self._auto_wildcard.lower() == u'false':
+ self._auto_wildcard = False
+ else:
+ self._auto_wildcard = True
+
+ self._max_key_length = int(self.db.ime_properties.get('max_key_length'))
+ self._max_key_length_pinyin = 7
+
+ self._page_up_keys = [
+ IBus.KEY_Page_Up,
+ IBus.KEY_KP_Page_Up,
+ IBus.KEY_minus
+ ]
+ self._page_down_keys = [
+ IBus.KEY_Page_Down,
+ IBus.KEY_KP_Page_Down,
+ IBus.KEY_equal
+ ]
+ # If page up or page down keys are defined in the database,
+ # use the values from the database instead of the above
+ # hardcoded defaults:
+ page_up_keys_csv = self.db.ime_properties.get('page_up_keys')
+ page_down_keys_csv = self.db.ime_properties.get('page_down_keys')
+ if page_up_keys_csv:
+ self._page_up_keys = [
+ IBus.keyval_from_name(x)
+ for x in page_up_keys_csv.split(',')]
+ if page_down_keys_csv:
+ self._page_down_keys = [
+ IBus.keyval_from_name(x)
+ for x in page_down_keys_csv.split(',')]
+ # Remove keys from the page up/down keys if they are needed
+ # for input (for example, '=' or '-' could well be needed for
+ # input. Input is more important):
+ for character in (
+ self._valid_input_chars
+ + self._single_wildcard_char
+ + self._multi_wildcard_char):
+ keyval = IBus.unicode_to_keyval(character)
+ if keyval in self._page_up_keys:
+ self._page_up_keys.remove(keyval)
+ if keyval in self._page_down_keys:
+ self._page_down_keys.remove(keyval)
+ self._commit_keys = [IBus.KEY_space]
+ # If commit keys are are defined in the database, use the
+ # value from the database instead of the above hardcoded
+ # default:
+ commit_keys_csv = self.db.ime_properties.get('commit_keys')
+ if commit_keys_csv:
+ self._commit_keys = [
+ IBus.keyval_from_name(x)
+ for x in commit_keys_csv.split(',')]
+ # If commit keys conflict with page up/down keys, remove them
+ # from the page up/down keys (They cannot really be used for
+ # both at the same time. Theoretically, keys from the page
+ # up/down keys could still be used to commit when the number
+ # of candidates is 0 because then there is nothing to
+ # page. But that would be only confusing):
+ for keyval in self._commit_keys:
+ if keyval in self._page_up_keys:
+ self._page_up_keys.remove(keyval)
+ if keyval in self._page_down_keys:
+ self._page_down_keys.remove(keyval)
+ # Finally, check the user setting, i.e. the config value
+ # “spacekeybehavior” and let the user have the last word
+ # how to use the space key:
+ spacekeybehavior = variant_to_value(self._config.get_value(
+ self._config_section,
+ "spacekeybehavior"))
+ if spacekeybehavior == True:
+ # space is used as a page down key and not as a commit key:
+ if IBus.KEY_space not in self._page_down_keys:
+ self._page_down_keys.append(IBus.KEY_space)
+ if IBus.KEY_space in self._commit_keys:
+ self._commit_keys.remove(IBus.KEY_space)
+ if spacekeybehavior == False:
+ # space is used as a commit key and not used as a page down key:
+ if IBus.KEY_space in self._page_down_keys:
+ self._page_down_keys.remove(IBus.KEY_space)
+ if IBus.KEY_space not in self._commit_keys:
+ self._commit_keys.append(IBus.KEY_space)
+ if debug_level > 1:
+ sys.stderr.write(
+ "self._page_down_keys=%s\n" %repr(self._page_down_keys))
+ sys.stderr.write(
+ "self._commit_keys=%s\n" %repr(self._commit_keys))
+
+ # 0 = Direct input, i.e. table input OFF (aka “English input mode”),
+ # most characters are just passed through to the application
+ # (but some fullwidth ↔ halfwidth conversion may be done even
+ # in this mode, depending on the settings)
+ # 1 = Table input ON (aka “Table input mode”, “Chinese mode”)
+ self._input_mode = variant_to_value(self._config.get_value(
+ self._config_section,
+ "inputmode"))
+ if self._input_mode == None:
+ self._input_mode = 1
+
+ # self._prev_key: hold the key event last time.
+ self._prev_key = None
+ self._prev_char = None
+ self._double_quotation_state = False
+ self._single_quotation_state = False
+
+ self._full_width_letter = [
+ variant_to_value(self._config.get_value(
+ self._config_section,
+ "EnDefFullWidthLetter")),
+ variant_to_value(self._config.get_value(
+ self._config_section,
+ "TabDefFullWidthLetter"))
+ ]
+ if self._full_width_letter[0] == None:
+ self._full_width_letter[0] = False
+ if self._full_width_letter[1] == None:
+ self._full_width_letter[1] = self.db.ime_properties.get(
+ 'def_full_width_letter').lower() == u'true'
+ self._full_width_punct = [
+ variant_to_value(self._config.get_value(
+ self._config_section,
+ "EnDefFullWidthPunct")),
+ variant_to_value(self._config.get_value(
+ self._config_section,
+ "TabDefFullWidthPunct"))
+ ]
+ if self._full_width_punct[0] == None:
+ self._full_width_punct[0] = False
+ if self._full_width_punct[1] == None:
+ self._full_width_punct[1] = self.db.ime_properties.get(
+ 'def_full_width_punct').lower() == u'true'
+
+ self._auto_commit = variant_to_value(self._config.get_value(
+ self._config_section,
+ "AutoCommit"))
+ if self._auto_commit == None:
+ self._auto_commit = self.db.ime_properties.get(
+ 'auto_commit').lower() == u'true'
+
+ # If auto select is true, then the first candidate phrase will
+ # be selected automatically during typing. Auto select is true
+ # by default for the stroke5 table for example.
+ self._auto_select = variant_to_value(self._config.get_value(
+ self._config_section,
+ "AutoSelect"))
+ if self._auto_select == None:
+ if self.db.ime_properties.get('auto_select') != None:
+ self._auto_select = self.db.ime_properties.get(
+ 'auto_select').lower() == u'true'
+ else:
+ self._auto_select = False
+
+ self._always_show_lookup = variant_to_value(self._config.get_value(
+ self._config_section,
+ "AlwaysShowLookup"))
+ if self._always_show_lookup == None:
+ if self.db.ime_properties.get('always_show_lookup') != None:
+ self._always_show_lookup = self.db.ime_properties.get(
+ 'always_show_lookup').lower() == u'true'
+ else:
+ self._always_show_lookup = True
+
+ self._editor = editor(self._config,
+ self._valid_input_chars,
+ self._pinyin_valid_input_chars,
+ self._single_wildcard_char,
+ self._multi_wildcard_char,
+ self._auto_wildcard,
+ self._full_width_letter,
+ self._full_width_punct,
+ self._max_key_length,
+ self.db)
+
+ self.chinese_mode_properties = {
+ 'ChineseMode.Simplified': {
+ # show simplified Chinese only
+ 'number': 0,
+ 'symbol': '簡',
+ 'icon': 'sc-mode.svg',
+ 'label': _('Simplified Chinese'),
+ 'tooltip':
+ _('Switch to “Simplified Chinese only”.')},
+ 'ChineseMode.Traditional': {
+ # show traditional Chinese only
+ 'number': 1,
+ 'symbol': '繁',
+ 'icon': 'tc-mode.svg',
+ 'label': _('Traditional Chinese'),
+ 'tooltip':
+ _('Switch to “Traditional Chinese only”.')},
+ 'ChineseMode.SimplifiedFirst': {
+ # show all but simplified first
+ 'number': 2,
+ 'symbol': '簡/大',
+ 'icon': 'scb-mode.svg',
+ 'label': _('Simplified Chinese first'),
+ 'tooltip':
+ _('Switch to “Simplified Chinese before traditional”.')},
+ 'ChineseMode.TraditionalFirst': {
+ # show all but traditional first
+ 'number': 3,
+ 'symbol': '繁/大',
+ 'icon': 'tcb-mode.svg',
+ 'label': _('Traditional Chinese first'),
+ 'tooltip':
+ _('Switch to “Traditional Chinese before simplified”.')},
+ 'ChineseMode.All': {
+ # show all Chinese characters, no particular order
+ 'number': 4,
+ 'symbol': '大',
+ 'icon': 'cb-mode.svg',
+ 'label': _('All Chinese characters'),
+ 'tooltip': _('Switch to “All Chinese characters”.')}
+ }
+ self.chinese_mode_menu = {
+ 'key': 'ChineseMode',
+ 'label': _('Chinese mode'),
+ 'tooltip': _('Switch Chinese mode'),
+ 'shortcut_hint': '(Ctrl-;)',
+ 'sub_properties': self.chinese_mode_properties
+ }
+ if self.db._is_chinese:
+ self.input_mode_properties = {
+ 'InputMode.Direct': {
+ 'number': 0,
+ 'symbol': '英',
+ 'icon': 'english.svg',
+ 'label': _('English'),
+ 'tooltip': _('Switch to English input')},
+ 'InputMode.Table': {
+ 'number': 1,
+ 'symbol': '中',
+ 'symbol_table': '中',
+ 'symbol_pinyin': '拼音',
+ 'icon': 'chinese.svg',
+ 'label': _('Chinese'),
+ 'tooltip': _('Switch to Chinese input')}
+ }
+ else:
+ self.input_mode_properties = {
+ 'InputMode.Direct': {
+ 'number': 0,
+ 'symbol': '☐' + self._symbol,
+ 'icon': 'english.svg',
+ 'label': _('Direct'),
+ 'tooltip': _('Switch to direct input')},
+ 'InputMode.Table': {
+ 'number': 1,
+ 'symbol': '☑' + self._symbol,
+ 'icon': 'ibus-table.svg',
+ 'label': _('Table'),
+ 'tooltip': _('Switch to table input')}
+ }
+ # The symbol of the property “InputMode” is displayed
+ # in the input method indicator of the Gnome3 panel.
+ # This depends on the property name “InputMode” and
+ # is case sensitive!
+ self.input_mode_menu = {
+ 'key': 'InputMode',
+ 'label': _('Input mode'),
+ 'tooltip': _('Switch Input mode'),
+ 'shortcut_hint': '(Left Shift)',
+ 'sub_properties': self.input_mode_properties
+ }
+ self.letter_width_properties = {
+ 'LetterWidth.Half': {
+ 'number': 0,
+ 'symbol': '◑',
+ 'icon': 'half-letter.svg',
+ 'label': _('Half'),
+ 'tooltip': _('Switch to halfwidth letters')},
+ 'LetterWidth.Full': {
+ 'number': 1,
+ 'symbol': '●',
+ 'icon': 'full-letter.svg',
+ 'label': _('Full'),
+ 'tooltip': _('Switch to fullwidth letters')}
+ }
+ self.letter_width_menu = {
+ 'key': 'LetterWidth',
+ 'label': _('Letter width'),
+ 'tooltip': _('Switch letter width'),
+ 'shortcut_hint': '(Shift-Space)',
+ 'sub_properties': self.letter_width_properties
+ }
+ self.punctuation_width_properties = {
+ 'PunctuationWidth.Half': {
+ 'number': 0,
+ 'symbol': ',.',
+ 'icon': 'half-punct.svg',
+ 'label': _('Half'),
+ 'tooltip': _('Switch to halfwidth punctuation')},
+ 'PunctuationWidth.Full': {
+ 'number': 1,
+ 'symbol': '、。',
+ 'icon': 'full-punct.svg',
+ 'label': _('Full'),
+ 'tooltip': _('Switch to fullwidth punctuation')}
+ }
+ self.punctuation_width_menu = {
+ 'key': 'PunctuationWidth',
+ 'label': _('Punctuation width'),
+ 'tooltip': _('Switch punctuation width'),
+ 'shortcut_hint': '(Ctrl-.)',
+ 'sub_properties': self.punctuation_width_properties
+ }
+ self.pinyin_mode_properties = {
+ 'PinyinMode.Table': {
+ 'number': 0,
+ 'symbol': '☐ 拼音',
+ 'icon': 'tab-mode.svg',
+ 'label': _('Table'),
+ 'tooltip': _('Switch to table mode')},
+ 'PinyinMode.Pinyin': {
+ 'number': 1,
+ 'symbol': '☑ 拼音',
+ 'icon': 'py-mode.svg',
+ 'label': _('Pinyin'),
+ 'tooltip': _('Switch to pinyin mode')}
+ }
+ self.pinyin_mode_menu = {
+ 'key': 'PinyinMode',
+ 'label': _('Pinyin mode'),
+ 'tooltip': _('Switch pinyin mode'),
+ 'shortcut_hint': '(Right Shift)',
+ 'sub_properties': self.pinyin_mode_properties
+ }
+ self.onechar_mode_properties = {
+ 'OneCharMode.Phrase': {
+ 'number': 0,
+ 'symbol': '☐ 1',
+ 'icon': 'phrase.svg',
+ 'label': _('Multiple character match'),
+ 'tooltip': _('Switch to matching multiple characters at once')},
+ 'OneCharMode.OneChar': {
+ 'number': 1,
+ 'symbol': '☑ 1',
+ 'icon': 'onechar.svg',
+ 'label': _('Single character match'),
+ 'tooltip': _('Switch to matching only single characters')}
+ }
+ self.onechar_mode_menu = {
+ 'key': 'OneCharMode',
+ 'label': _('Onechar mode'),
+ 'tooltip': _('Switch onechar mode'),
+ 'shortcut_hint': '(Ctrl-,)',
+ 'sub_properties': self.onechar_mode_properties
+ }
+ self.autocommit_mode_properties = {
+ 'AutoCommitMode.Direct': {
+ 'number': 0,
+ 'symbol': '☐ ↑',
+ 'icon': 'ncommit.svg',
+ 'label': _('Normal'),
+ 'tooltip':
+ _('Switch to normal commit mode '
+ + '(automatic commits go into the preedit '
+ + 'instead of into the application. '
+ + 'This enables automatic definitions of new shortcuts)')},
+ 'AutoCommitMode.Normal': {
+ 'number': 1,
+ 'symbol': '☑ ↑',
+ 'icon': 'acommit.svg',
+ 'label': _('Direct'),
+ 'tooltip':
+ _('Switch to direct commit mode '
+ + '(automatic commits go directly into the application)')}
+ }
+ self.autocommit_mode_menu = {
+ 'key': 'AutoCommitMode',
+ 'label': _('Auto commit mode'),
+ 'tooltip': _('Switch autocommit mode'),
+ 'shortcut_hint': '(Ctrl-/)',
+ 'sub_properties': self.autocommit_mode_properties
+ }
+ self._prop_dict = {}
+ self._init_properties()
+
+ self._on = False
+ self._save_user_count = 0
+ self._save_user_start = time.time()
+
+ self._save_user_count_max = SAVE_USER_COUNT_MAX
+ self._save_user_timeout = SAVE_USER_TIMEOUT
+ self.reset()
+
+ self.sync_timeout_id = GObject.timeout_add_seconds(1,
+ self._sync_user_db)
+
+ def reset(self):
+ self._editor.clear_all_input_and_preedit()
+ self._double_quotation_state = False
+ self._single_quotation_state = False
+ self._prev_key = None
+ self._update_ui()
+
+ def do_destroy(self):
+ if self.sync_timeout_id > 0:
+ GObject.source_remove(self.sync_timeout_id)
+ self.sync_timeout_id = 0
+ self.reset ()
+ self.do_focus_out ()
+ if self._save_user_count > 0:
+ self.db.sync_usrdb()
+ self._save_user_count = 0
+ super(tabengine, self).destroy()
+
+ def set_input_mode(self, mode=0):
+ if mode == self._input_mode:
+ return
+ self._input_mode = mode
+ # Not saved to config on purpose. In the setup tool one
+ # can select whether “Table input” or “Direct input” should
+ # be the default when the input method starts. But when
+ # changing this input mode using the property menu,
+ # the change is not remembered.
+ self._init_or_update_property_menu(
+ self.input_mode_menu,
+ self._input_mode)
+ # Letter width and punctuation width depend on the input mode.
+ # Therefore, the properties for letter width and punctuation
+ # width need to be updated here:
+ self._init_or_update_property_menu(
+ self.letter_width_menu,
+ self._full_width_letter[self._input_mode])
+ self._init_or_update_property_menu(
+ self.punctuation_width_menu,
+ self._full_width_punct[self._input_mode])
+ self.reset()
+
+ def set_pinyin_mode(self, mode=False):
+ if mode == self._editor._py_mode:
+ return
+ # The pinyin mode is never saved to config on purpose
+ self._editor.commit_to_preedit()
+ self._editor._py_mode = mode
+ self._init_or_update_property_menu(
+ self.pinyin_mode_menu, mode)
+ if mode:
+ self.input_mode_properties['InputMode.Table']['symbol'] = (
+ self.input_mode_properties['InputMode.Table']['symbol_pinyin'])
+ else:
+ self.input_mode_properties['InputMode.Table']['symbol'] = (
+ self.input_mode_properties['InputMode.Table']['symbol_table'])
+ self._init_or_update_property_menu(
+ self.input_mode_menu,
+ self._input_mode)
+ self._update_ui()
+
+ def set_onechar_mode(self, mode=False):
+ if mode == self._editor._onechar:
+ return
+ self._editor._onechar = mode
+ self._init_or_update_property_menu(
+ self.onechar_mode_menu, mode)
+ self._config.set_value(
+ self._config_section,
+ "OneChar",
+ GLib.Variant.new_boolean(mode))
+
+ def set_autocommit_mode(self, mode=False):
+ if mode == self._auto_commit:
+ return
+ self._auto_commit = mode
+ self._init_or_update_property_menu(
+ self.autocommit_mode_menu, mode)
+ self._config.set_value(
+ self._config_section,
+ "AutoCommit",
+ GLib.Variant.new_boolean(mode))
+
+ def set_letter_width(self, mode=False, input_mode=0):
+ if mode == self._full_width_letter[input_mode]:
+ return
+ self._full_width_letter[input_mode] = mode
+ self._editor._full_width_letter[input_mode] = mode
+ if input_mode == self._input_mode:
+ self._init_or_update_property_menu(
+ self.letter_width_menu, mode)
+ if input_mode:
+ self._config.set_value(
+ self._config_section,
+ "TabDefFullWidthLetter",
+ GLib.Variant.new_boolean(mode))
+ else:
+ self._config.set_value(
+ self._config_section,
+ "EnDefFullWidthLetter",
+ GLib.Variant.new_boolean(mode))
+
+ def set_punctuation_width(self, mode=False, input_mode=0):
+ if mode == self._full_width_punct[input_mode]:
+ return
+ self._full_width_punct[input_mode] = mode
+ self._editor._full_width_punct[input_mode] = mode
+ if input_mode == self._input_mode:
+ self._init_or_update_property_menu(
+ self.punctuation_width_menu, mode)
+ if input_mode:
+ self._config.set_value(
+ self._config_section,
+ "TabDefFullWidthPunct",
+ GLib.Variant.new_boolean(mode))
+ else:
+ self._config.set_value(
+ self._config_section,
+ "EnDefFullWidthPunct",
+ GLib.Variant.new_boolean(mode))
+
+ def set_chinese_mode(self, mode=0):
+ if mode == self._editor._chinese_mode:
+ return
+ self._editor._chinese_mode = mode
+ self._init_or_update_property_menu(
+ self.chinese_mode_menu, mode)
+ self._config.set_value(
+ self._config_section,
+ "ChineseMode",
+ GLib.Variant.new_int32(mode))
+
+ def _init_or_update_property_menu(self, menu, current_mode=0):
+ key = menu['key']
+ if key in self._prop_dict:
+ update_prop = True
+ else:
+ update_prop = False
+ sub_properties = menu['sub_properties']
+ for prop in sub_properties:
+ if sub_properties[prop]['number'] == int(current_mode):
+ symbol = sub_properties[prop]['symbol']
+ icon = sub_properties[prop]['icon']
+ label = '%(label)s (%(symbol)s) %(shortcut_hint)s' % {
+ 'label': menu['label'],
+ 'symbol': symbol,
+ 'shortcut_hint': menu['shortcut_hint']}
+ tooltip = '%(tooltip)s\n%(shortcut_hint)s' % {
+ 'tooltip': menu['tooltip'],
+ 'shortcut_hint': menu['shortcut_hint']}
+ self._prop_dict[key] = IBus.Property(
+ key=key,
+ prop_type=IBus.PropType.MENU,
+ label=IBus.Text.new_from_string(label),
+ symbol=IBus.Text.new_from_string(symbol),
+ icon=os.path.join(self._icon_dir, icon),
+ tooltip=IBus.Text.new_from_string(tooltip),
+ sensitive=True,
+ visible=True,
+ state=IBus.PropState.UNCHECKED,
+ sub_props=None)
+ self._prop_dict[key].set_sub_props(
+ self._init_sub_properties(
+ sub_properties, current_mode=current_mode))
+ if update_prop:
+ self.properties.update_property(self._prop_dict[key])
+ self.update_property(self._prop_dict[key])
+ else:
+ self.properties.append(self._prop_dict[key])
+
+ def _init_sub_properties(self, modes, current_mode=0):
+ sub_props = IBus.PropList()
+ for mode in sorted(modes, key=lambda x: (modes[x]['number'])):
+ sub_props.append(IBus.Property(
+ key=mode,
+ prop_type=IBus.PropType.RADIO,
+ label=IBus.Text.new_from_string(modes[mode]['label']),
+ icon=os.path.join(modes[mode]['icon']),
+ tooltip=IBus.Text.new_from_string(modes[mode]['tooltip']),
+ sensitive=True,
+ visible=True,
+ state=IBus.PropState.UNCHECKED,
+ sub_props=None))
+ i = 0
+ while sub_props.get(i) != None:
+ prop = sub_props.get(i)
+ key = prop.get_key()
+ self._prop_dict[key] = prop
+ if modes[key]['number'] == int(current_mode):
+ prop.set_state(IBus.PropState.CHECKED)
+ else:
+ prop.set_state(IBus.PropState.UNCHECKED)
+ self.update_property(prop) # important!
+ i += 1
+ return sub_props
+
+ def _init_properties(self):
+ self._prop_dict = {}
+ self.properties = IBus.PropList()
+
+ self._init_or_update_property_menu(
+ self.input_mode_menu,
+ self._input_mode)
+
+ if self.db._is_chinese and self._editor._chinese_mode != -1:
+ self._init_or_update_property_menu(
+ self.chinese_mode_menu,
+ self._editor._chinese_mode)
+
+ if self.db._is_cjk:
+ self._init_or_update_property_menu(
+ self.letter_width_menu,
+ self._full_width_letter[self._input_mode])
+ self._init_or_update_property_menu(
+ self.punctuation_width_menu,
+ self._full_width_punct[self._input_mode])
+
+ if self._ime_py:
+ self._init_or_update_property_menu(
+ self.pinyin_mode_menu,
+ self._editor._py_mode)
+
+ if self.db._is_cjk:
+ self._init_or_update_property_menu(
+ self.onechar_mode_menu,
+ self._editor._onechar)
+
+ if self.db.user_can_define_phrase and self.db.rules:
+ self._init_or_update_property_menu(
+ self.autocommit_mode_menu,
+ self._auto_commit)
+
+ self._setup_property = IBus.Property(
+ key = u'setup',
+ label = IBus.Text.new_from_string(_('Setup')),
+ icon = 'gtk-preferences',
+ tooltip = IBus.Text.new_from_string(_('Configure ibus-table “%(engine-name)s”') %{
+ 'engine-name': self._engine_name}),
+ sensitive = True,
+ visible = True)
+ self.properties.append(self._setup_property)
+
+ self.register_properties(self.properties)
+
+ def do_property_activate(
+ self, property, prop_state = IBus.PropState.UNCHECKED):
+ '''
+ Handle clicks on properties
+ '''
+ if debug_level > 1:
+ sys.stderr.write(
+ "do_property_activate() property=%(p)s prop_state=%(ps)s\n"
+ % {'p': property, 'ps': prop_state})
+ if property == "setup":
+ self._start_setup()
+ return
+ if prop_state != IBus.PropState.CHECKED:
+ # If the mouse just hovered over a menu button and
+ # no sub-menu entry was clicked, there is nothing to do:
+ return
+ if property.startswith(self.input_mode_menu['key']+'.'):
+ self.set_input_mode(
+ self.input_mode_properties[property]['number'])
+ return
+ if (property.startswith(self.pinyin_mode_menu['key']+'.')
+ and self._ime_py):
+ self.set_pinyin_mode(
+ bool(self.pinyin_mode_properties[property]['number']))
+ return
+ if (property.startswith(self.onechar_mode_menu['key']+'.')
+ and self.db._is_cjk):
+ self.set_onechar_mode(
+ bool(self.onechar_mode_properties[property]['number']))
+ return
+ if (property.startswith(self.autocommit_mode_menu['key']+'.')
+ and self.db.user_can_define_phrase and self.db.rules):
+ self.set_autocommit_mode(
+ bool(self.autocommit_mode_properties[property]['number']))
+ return
+ if (property.startswith(self.letter_width_menu['key']+'.')
+ and self.db._is_cjk):
+ self.set_letter_width(
+ bool(self.letter_width_properties[property]['number']),
+ input_mode=self._input_mode)
+ return
+ if (property.startswith(self.punctuation_width_menu['key']+'.')
+ and self.db._is_cjk):
+ self.set_punctuation_width(
+ bool(self.punctuation_width_properties[property]['number']),
+ input_mode=self._input_mode)
+ return
+ if (property.startswith(self.chinese_mode_menu['key']+'.')
+ and self.db._is_chinese
+ and self._editor._chinese_mode != -1):
+ self.set_chinese_mode(
+ self.chinese_mode_properties[property]['number'])
+ return
+
+ def _start_setup(self):
+ if self._setup_pid != 0:
+ pid, state = os.waitpid(self._setup_pid, os.P_NOWAIT)
+ if pid != self._setup_pid:
+ # If the last setup tool started from here is still
+ # running the pid returned by the above os.waitpid()
+ # is 0. In that case just return, dont start a
+ # second setup tool.
+ return
+ self._setup_pid = 0
+ setup_cmd = os.path.join(
+ os.getenv('IBUS_TABLE_LIB_LOCATION'),
+ 'ibus-setup-table')
+ self._setup_pid = os.spawnl(
+ os.P_NOWAIT,
+ setup_cmd,
+ 'ibus-setup-table',
+ '--engine-name table:%s' %self._engine_name)
+
+ def _update_preedit(self):
+ '''Update Preedit String in UI'''
+ preedit_string_parts = self._editor.get_preedit_string_parts()
+ left_of_current_edit = u''.join(preedit_string_parts[0])
+ current_edit = preedit_string_parts[1]
+ right_of_current_edit = u''.join(preedit_string_parts[2])
+ if not self._editor._py_mode:
+ current_edit_new = u''
+ for char in current_edit:
+ if char in self._editor._prompt_characters:
+ current_edit_new += self._editor._prompt_characters[char]
+ else:
+ current_edit_new += char
+ current_edit = current_edit_new
+ preedit_string_complete = (
+ left_of_current_edit + current_edit + right_of_current_edit)
+ if not preedit_string_complete:
+ super(tabengine, self).update_preedit_text(
+ IBus.Text.new_from_string(u''), 0, False)
+ return
+ color_left = rgb(0xf9, 0x0f, 0x0f) # bright red
+ color_right = rgb(0x1e, 0xdc, 0x1a) # light green
+ color_invalid = rgb(0xff, 0x00, 0xff) # magenta
+ attrs = IBus.AttrList()
+ attrs.append(
+ IBus.attr_foreground_new(
+ color_left,
+ 0,
+ len(left_of_current_edit)))
+ attrs.append(
+ IBus.attr_foreground_new(
+ color_right,
+ len(left_of_current_edit) + len(current_edit),
+ len(preedit_string_complete)))
+ if self._editor._chars_invalid:
+ attrs.append(
+ IBus.attr_foreground_new(
+ color_invalid,
+ len(left_of_current_edit) + len(current_edit)
+ - len(self._editor._chars_invalid),
+ len(left_of_current_edit) + len(current_edit)
+ ))
+ attrs.append(
+ IBus.attr_underline_new(
+ IBus.AttrUnderline.SINGLE,
+ 0,
+ len(preedit_string_complete)))
+ text = IBus.Text.new_from_string(preedit_string_complete)
+ i = 0
+ while attrs.get(i) != None:
+ attr = attrs.get(i)
+ text.append_attribute(attr.get_attr_type(),
+ attr.get_value(),
+ attr.get_start_index(),
+ attr.get_end_index())
+ i += 1
+ super(tabengine, self).update_preedit_text(
+ text, self._editor.get_caret(), True)
+
+ def _update_aux (self):
+ '''Update Aux String in UI'''
+ aux_string = self._editor.get_aux_strings()
+ if len(self._editor._candidates) > 0:
+ aux_string += u' (%d / %d)' % (
+ self._editor._lookup_table.get_cursor_pos() +1,
+ self._editor._lookup_table.get_number_of_candidates())
+ if aux_string:
+ attrs = IBus.AttrList()
+ attrs.append(IBus.attr_foreground_new(
+ rgb(0x95,0x15,0xb5),0, len(aux_string)))
+ text = IBus.Text.new_from_string(aux_string)
+ i = 0
+ while attrs.get(i) != None:
+ attr = attrs.get(i)
+ text.append_attribute(attr.get_attr_type(),
+ attr.get_value(),
+ attr.get_start_index(),
+ attr.get_end_index())
+ i += 1
+ visible = True
+ if not aux_string or not self._always_show_lookup:
+ visible = False
+ super(tabengine, self).update_auxiliary_text(text, visible)
+ else:
+ self.hide_auxiliary_text()
+
+ def _update_lookup_table (self):
+ '''Update Lookup Table in UI'''
+ if len(self._editor._candidates) == 0:
+ # Also make sure to hide lookup table if there are
+ # no candidates to display. On f17, this makes no
+ # difference but gnome-shell in f18 will display
+ # an empty suggestion popup if the number of candidates
+ # is zero!
+ self.hide_lookup_table()
+ return
+ if self._editor.is_empty ():
+ self.hide_lookup_table()
+ return
+ if not self._always_show_lookup:
+ self.hide_lookup_table()
+ return
+ self.update_lookup_table(self._editor.get_lookup_table(), True)
+
+ def _update_ui (self):
+ '''Update User Interface'''
+ self._update_lookup_table ()
+ self._update_preedit ()
+ self._update_aux ()
+
+ def _check_phrase (self, tabkeys=u'', phrase=u''):
+ """Check the given phrase and update save user db info"""
+ if not tabkeys or not phrase:
+ return
+ self.db.check_phrase(tabkeys=tabkeys, phrase=phrase)
+
+ if self._save_user_count <= 0:
+ self._save_user_start = time.time()
+ self._save_user_count += 1
+
+ def _sync_user_db(self):
+ """Save user db to disk"""
+ if self._save_user_count >= 0:
+ now = time.time()
+ time_delta = now - self._save_user_start
+ if (self._save_user_count > self._save_user_count_max or
+ time_delta >= self._save_user_timeout):
+ self.db.sync_usrdb()
+ self._save_user_count = 0
+ self._save_user_start = now
+ return True
+
+ def commit_string (self, phrase, tabkeys=u''):
+ if debug_level > 1:
+ sys.stderr.write("commit_string() phrase=%(p)s\n"
+ %{'p': phrase})
+ self._editor.clear_all_input_and_preedit()
+ self._update_ui()
+ super(tabengine, self).commit_text(IBus.Text.new_from_string(phrase))
+ if len(phrase) > 0:
+ self._prev_char = phrase[-1]
+ else:
+ self._prev_char = None
+ self._check_phrase(tabkeys=tabkeys, phrase=phrase)
+
+ def commit_everything_unless_invalid(self):
+ '''
+ Commits the current input to the preëdit and then
+ commits the preëdit to the application unless there are
+ invalid input characters.
+
+ Returns “True” if something was committed, “False” if not.
+ '''
+ if debug_level > 1:
+ sys.stderr.write("commit_everything_unless_invalid()\n")
+ if self._editor._chars_invalid:
+ return False
+ if not self._editor.is_empty():
+ self._editor.commit_to_preedit()
+ self.commit_string(self._editor.get_preedit_string_complete(),
+ tabkeys=self._editor.get_preedit_tabkeys_complete())
+ return True
+
+ def _convert_to_full_width(self, c):
+ '''Convert half width character to full width'''
+
+ # This function handles punctuation that does not comply to the
+ # Unicode conversion formula in unichar_half_to_full(c).
+ # For ".", "\"", "'"; there are even variations under specific
+ # cases. This function should be more abstracted by extracting
+ # that to another handling function later on.
+ special_punct_dict = {u"<": u"《", # 《 U+300A LEFT DOUBLE ANGLE BRACKET
+ u">": u"》", # 》 U+300B RIGHT DOUBLE ANGLE BRACKET
+ u"[": u"「", # 「 U+300C LEFT CORNER BRACKET
+ u"]": u"」", # 」U+300D RIGHT CORNER BRACKET
+ u"{": u"『", # 『 U+300E LEFT WHITE CORNER BRACKET
+ u"}": u"』", # 』U+300F RIGHT WHITE CORNER BRACKET
+ u"\\": u"、", # 、 U+3001 IDEOGRAPHIC COMMA
+ u"^": u"……", # … U+2026 HORIZONTAL ELLIPSIS
+ u"_": u"——", # — U+2014 EM DASH
+ u"$": u"¥" # ¥ U+FFE5 FULLWIDTH YEN SIGN
+ }
+
+ # special puncts w/o further conditions
+ if c in special_punct_dict.keys():
+ if c in [u"\\", u"^", u"_", u"$"]:
+ return special_punct_dict[c]
+ elif self._input_mode:
+ return special_punct_dict[c]
+
+ # special puncts w/ further conditions
+ if c == u".":
+ if (self._prev_char
+ and self._prev_char.isdigit()
+ and self._prev_key
+ and chr(self._prev_key.val) == self._prev_char):
+ return u"."
+ else:
+ return u"。" # 。U+3002 IDEOGRAPHIC FULL STOP
+ elif c == u"\"":
+ self._double_quotation_state = not self._double_quotation_state
+ if self._double_quotation_state:
+ return u"“" # “ U+201C LEFT DOUBLE QUOTATION MARK
+ else:
+ return u"”" # ” U+201D RIGHT DOUBLE QUOTATION MARK
+ elif c == u"'":
+ self._single_quotation_state = not self._single_quotation_state
+ if self._single_quotation_state:
+ return u"" # U+2018 LEFT SINGLE QUOTATION MARK
+ else:
+ return u"" # U+2019 RIGHT SINGLE QUOTATION MARK
+
+ return unichar_half_to_full(c)
+
+ def _match_hotkey (self, key, keyval, state):
+
+ # Match only when keys are released
+ state = state | IBus.ModifierType.RELEASE_MASK
+ if key.val == keyval and (key.state & state) == state:
+ # If it is a key release event, the previous key
+ # must have been the same key pressed down.
+ if (self._prev_key
+ and key.val == self._prev_key.val):
+ return True
+
+ return False
+
+ def do_candidate_clicked(self, index, button, state):
+ if self._editor.commit_to_preedit_current_page(index):
+ # commits to preëdit
+ self.commit_string(
+ self._editor.get_preedit_string_complete(),
+ tabkeys=self._editor.get_preedit_tabkeys_complete())
+ return True
+ return False
+
+ def do_process_key_event(self, keyval, keycode, state):
+ '''Process Key Events
+ Key Events include Key Press and Key Release,
+ modifier means Key Pressed
+ '''
+ if debug_level > 1:
+ sys.stderr.write("do_process_key_event()\n")
+ if (self._has_input_purpose
+ and self._input_purpose
+ in [IBus.InputPurpose.PASSWORD, IBus.InputPurpose.PIN]):
+ return False
+
+ key = KeyEvent(keyval, keycode, state)
+
+ result = self._process_key_event (key)
+ self._prev_key = key
+ return result
+
+ def _process_key_event (self, key):
+ '''Internal method to process key event'''
+ # Match mode switch hotkey
+ if (self._editor.is_empty()
+ and (self._match_hotkey(
+ key, IBus.KEY_Shift_L,
+ IBus.ModifierType.SHIFT_MASK))):
+ self.set_input_mode(int(not self._input_mode))
+ return True
+
+ # Match fullwidth/halfwidth letter mode switch hotkey
+ if self.db._is_cjk:
+ if (key.val == IBus.KEY_space
+ and key.state & IBus.ModifierType.SHIFT_MASK
+ and not key.state & IBus.ModifierType.RELEASE_MASK):
+ # Ignore when Shift+Space was pressed, the key release
+ # event will toggle the fullwidth/halfwidth letter mode, we
+ # dont want to insert an extra space on the key press
+ # event.
+ return True
+ if (self._match_hotkey(
+ key, IBus.KEY_space,
+ IBus.ModifierType.SHIFT_MASK)):
+ self.set_letter_width(
+ not self._full_width_letter[self._input_mode],
+ input_mode = self._input_mode)
+ return True
+
+ # Match full half punct mode switch hotkey
+ if (self._match_hotkey(
+ key, IBus.KEY_period,
+ IBus.ModifierType.CONTROL_MASK) and self.db._is_cjk):
+ self.set_punctuation_width(
+ not self._full_width_punct[self._input_mode],
+ input_mode = self._input_mode)
+ return True
+
+ if self._input_mode:
+ return self._table_mode_process_key_event (key)
+ else:
+ return self._english_mode_process_key_event (key)
+
+ def cond_letter_translate(self, char):
+ if self._full_width_letter[self._input_mode] and self.db._is_cjk:
+ return self._convert_to_full_width(char)
+ else:
+ return char
+
+ def cond_punct_translate(self, char):
+ if self._full_width_punct[self._input_mode] and self.db._is_cjk:
+ return self._convert_to_full_width(char)
+ else:
+ return char
+
+ def _english_mode_process_key_event(self, key):
+ # Ignore key release events
+ if key.state & IBus.ModifierType.RELEASE_MASK:
+ return False
+ if key.val >= 128:
+ return False
+ # we ignore all hotkeys here
+ if (key.state
+ & (IBus.ModifierType.CONTROL_MASK|IBus.ModifierType.MOD1_MASK)):
+ return False
+ keychar = IBus.keyval_to_unicode(key.val)
+ if type(keychar) != type(u''):
+ keychar = keychar.decode('UTF-8')
+ if ascii_ispunct(keychar):
+ trans_char = self.cond_punct_translate(keychar)
+ else:
+ trans_char = self.cond_letter_translate(keychar)
+ if trans_char == keychar:
+ return False
+ self.commit_string(trans_char)
+ return True
+
+ def _table_mode_process_key_event(self, key):
+ if debug_level > 0:
+ sys.stderr.write('_table_mode_process_key_event() ')
+ sys.stderr.write('repr(key)=%(key)s\n' %{'key': key})
+ # Change pinyin mode
+ # (change only if the editor is empty. When the editor
+ # is not empty, the right shift key should commit to preëdit
+ # and not change the pinyin mode).
+ if (self._ime_py
+ and self._editor.is_empty()
+ and self._match_hotkey(
+ key, IBus.KEY_Shift_R,
+ IBus.ModifierType.SHIFT_MASK)):
+ self.set_pinyin_mode(not self._editor._py_mode)
+ return True
+ # process commit to preedit
+ if (self._match_hotkey(
+ key, IBus.KEY_Shift_R,
+ IBus.ModifierType.SHIFT_MASK)
+ or self._match_hotkey(
+ key, IBus.KEY_Shift_L,
+ IBus.ModifierType.SHIFT_MASK)):
+ res = self._editor.commit_to_preedit()
+ self._update_ui()
+ return res
+
+ # Left ALT key to cycle candidates in the current page.
+ if (self._match_hotkey(
+ key, IBus.KEY_Alt_L,
+ IBus.ModifierType.MOD1_MASK)):
+ res = self._editor.cycle_next_cand()
+ self._update_ui()
+ return res
+
+ # Match single char mode switch hotkey
+ if (self._match_hotkey(
+ key, IBus.KEY_comma,
+ IBus.ModifierType.CONTROL_MASK) and self.db._is_cjk):
+ self.set_onechar_mode(not self._editor._onechar)
+ return True
+
+ # Match direct commit mode switch hotkey
+ if (self._match_hotkey(
+ key, IBus.KEY_slash,
+ IBus.ModifierType.CONTROL_MASK)
+ and self.db.user_can_define_phrase and self.db.rules):
+ self.set_autocommit_mode(not self._auto_commit)
+ return True
+
+ # Match Chinese mode shift
+ if (self._match_hotkey(
+ key, IBus.KEY_semicolon,
+ IBus.ModifierType.CONTROL_MASK) and self.db._is_chinese):
+ self.set_chinese_mode((self._editor._chinese_mode+1) % 5)
+ return True
+
+ # Ignore key release events
+ # (Must be below all self._match_hotkey() callse
+ # because these match on a release event).
+ if key.state & IBus.ModifierType.RELEASE_MASK:
+ return False
+
+ keychar = IBus.keyval_to_unicode(key.val)
+ if type(keychar) != type(u''):
+ keychar = keychar.decode('UTF-8')
+
+ # Section to handle leading invalid input:
+ #
+ # This is the first character typed, if it is invalid
+ # input, handle it immediately here, if it is valid, continue.
+ if (self._editor.is_empty()
+ and not self._editor.get_preedit_string_complete()):
+ if ((keychar not in (
+ self._valid_input_chars
+ + self._single_wildcard_char
+ + self._multi_wildcard_char)
+ or (self.db.startchars and keychar not in self.db.startchars))
+ and (not key.state &
+ (IBus.ModifierType.MOD1_MASK |
+ IBus.ModifierType.CONTROL_MASK))):
+ if debug_level > 0:
+ sys.stderr.write(
+ '_table_mode_process_key_event() '
+ + 'leading invalid input: '
+ + 'repr(keychar)=%(keychar)s\n'
+ % {'keychar': keychar})
+ if ascii_ispunct(keychar):
+ trans_char = self.cond_punct_translate(keychar)
+ else:
+ trans_char = self.cond_letter_translate(keychar)
+ if trans_char == keychar:
+ self._prev_char = trans_char
+ return False
+ else:
+ self.commit_string(trans_char)
+ return True
+
+ if key.val == IBus.KEY_Escape:
+ self.reset()
+ self._update_ui()
+ return True
+
+ if key.val in (IBus.KEY_Return, IBus.KEY_KP_Enter):
+ if (self._editor.is_empty()
+ and not self._editor.get_preedit_string_complete()):
+ # When IBus.KEY_Return is typed,
+ # IBus.keyval_to_unicode(key.val) returns a non-empty
+ # string. But when IBus.KEY_KP_Enter is typed it
+ # returns an empty string. Therefore, when typing
+ # IBus.KEY_KP_Enter as leading input, the key is not
+ # handled by the section to handle leading invalid
+ # input but it ends up here. If it is leading input
+ # (i.e. the preëdit is empty) we should always pass
+ # IBus.KEY_KP_Enter to the application:
+ return False
+ if self._auto_select:
+ self._editor.commit_to_preedit()
+ commit_string = self._editor.get_preedit_string_complete()
+ self.commit_string(commit_string)
+ return False
+ else:
+ commit_string = self._editor.get_preedit_tabkeys_complete()
+ self.commit_string(commit_string)
+ return True
+
+ if key.val in (IBus.KEY_Tab, IBus.KEY_KP_Tab) and self._auto_select:
+ # Used for example for the Russian transliteration method
+ # “translit”, which uses “auto select”. If for example
+ # a file with the name “шшш” exists and one types in
+ # a bash shell:
+ #
+ # “ls sh”
+ #
+ # the “sh” is converted to “ш” and one sees
+ #
+ # “ls ш”
+ #
+ # in the shell where the “ш” is still in preëdit
+ # because “shh” would be converted to “щ”, i.e. there
+ # is more than one candidate and the input method is still
+ # waiting whether one more “h” will be typed or not. But
+ # if the next character typed is a Tab, the preëdit is
+ # committed here and “False” is returned to pass the Tab
+ # character through to the bash to complete the file name
+ # to “шшш”.
+ self._editor.commit_to_preedit()
+ self.commit_string(self._editor.get_preedit_string_complete())
+ return False
+
+ if key.val in (IBus.KEY_Down, IBus.KEY_KP_Down) :
+ if not self._editor.get_preedit_string_complete():
+ return False
+ res = self._editor.cursor_down()
+ self._update_ui()
+ return res
+
+ if key.val in (IBus.KEY_Up, IBus.KEY_KP_Up):
+ if not self._editor.get_preedit_string_complete():
+ return False
+ res = self._editor.cursor_up()
+ self._update_ui()
+ return res
+
+ if (key.val in (IBus.KEY_Left, IBus.KEY_KP_Left)
+ and key.state & IBus.ModifierType.CONTROL_MASK):
+ if not self._editor.get_preedit_string_complete():
+ return False
+ self._editor.control_arrow_left()
+ self._update_ui()
+ return True
+
+ if (key.val in (IBus.KEY_Right, IBus.KEY_KP_Right)
+ and key.state & IBus.ModifierType.CONTROL_MASK):
+ if not self._editor.get_preedit_string_complete():
+ return False
+ self._editor.control_arrow_right()
+ self._update_ui()
+ return True
+
+ if key.val in (IBus.KEY_Left, IBus.KEY_KP_Left):
+ if not self._editor.get_preedit_string_complete():
+ return False
+ self._editor.arrow_left()
+ self._update_ui()
+ return True
+
+ if key.val in (IBus.KEY_Right, IBus.KEY_KP_Right):
+ if not self._editor.get_preedit_string_complete():
+ return False
+ self._editor.arrow_right()
+ self._update_ui()
+ return True
+
+ if (key.val == IBus.KEY_BackSpace
+ and key.state & IBus.ModifierType.CONTROL_MASK):
+ if not self._editor.get_preedit_string_complete():
+ return False
+ self._editor.remove_preedit_before_cursor()
+ self._update_ui()
+ return True
+
+ if key.val == IBus.KEY_BackSpace:
+ if not self._editor.get_preedit_string_complete():
+ return False
+ self._editor.remove_char()
+ self._update_ui()
+ return True
+
+ if (key.val == IBus.KEY_Delete
+ and key.state & IBus.ModifierType.CONTROL_MASK):
+ if not self._editor.get_preedit_string_complete():
+ return False
+ self._editor.remove_preedit_after_cursor()
+ self._update_ui()
+ return True
+
+ if key.val == IBus.KEY_Delete:
+ if not self._editor.get_preedit_string_complete():
+ return False
+ self._editor.delete()
+ self._update_ui()
+ return True
+
+ if (key.val in self._editor.get_select_keys()
+ and self._editor._candidates
+ and key.state & IBus.ModifierType.CONTROL_MASK):
+ res = self._editor.select_key(key.val)
+ self._update_ui()
+ return res
+
+ if (key.val in self._editor.get_select_keys()
+ and self._editor._candidates
+ and key.state & IBus.ModifierType.MOD1_MASK):
+ res = self._editor.remove_candidate_from_user_database(key.val)
+ self._update_ui()
+ return res
+
+ # now we ignore all other hotkeys
+ if (key.state
+ & (IBus.ModifierType.CONTROL_MASK|IBus.ModifierType.MOD1_MASK)):
+ return False
+
+ if key.state & IBus.ModifierType.MOD1_MASK:
+ return False
+
+ # Section to handle valid input characters:
+ #
+ # All keys which could possibly conflict with the valid input
+ # characters should be checked below this section. These are
+ # SELECT_KEYS, PAGE_UP_KEYS, PAGE_DOWN_KEYS, and COMMIT_KEYS.
+ #
+ # For example, consider a table has
+ #
+ # SELECT_KEYS = 1,2,3,4,5,6,7,8,9,0
+ #
+ # and
+ #
+ # VALID_INPUT_CHARS = 0123456789abcdef
+ #
+ # (Currently the cns11643 table has this, for example)
+ #
+ # Then the digit “1” could be interpreted either as an input
+ # character or as a select key but of course not both. If the
+ # meaning as a select key or page down key were preferred,
+ # this would make some input impossible which probably makes
+ # the whole input method useless. If the meaning as an input
+ # character is preferred, this makes selection using that key
+ # impossible. Making selection by key impossible is not nice
+ # either, but it is not a complete show stopper as there are
+ # still other possibilities to select, for example using the
+ # arrow-up/arrow-down keys or click with the mouse.
+ #
+ # Of course one should maybe consider fixing the conflict
+ # between the keys by using different SELECT_KEYS and/or
+ # PAGE_UP_KEYS/PAGE_DOWN_KEYS in that table ...
+ if (keychar
+ and (keychar in (self._valid_input_chars
+ + self._single_wildcard_char
+ + self._multi_wildcard_char)
+ or (self._editor._py_mode
+ and keychar in (self._pinyin_valid_input_chars
+ + self._single_wildcard_char
+ + self._multi_wildcard_char)))):
+ if debug_level > 0:
+ sys.stderr.write(
+ '_table_mode_process_key_event() valid input: '
+ + 'repr(keychar)=%(keychar)s\n'
+ % {'keychar': keychar})
+ if self._editor._py_mode:
+ if ((len(self._editor._chars_valid)
+ == self._max_key_length_pinyin)
+ or (len(self._editor._chars_valid) > 1
+ and self._editor._chars_valid[-1] in '!@#$%')):
+ if self._auto_commit:
+ self.commit_everything_unless_invalid()
+ else:
+ self._editor.commit_to_preedit()
+ else:
+ if ((len(self._editor._chars_valid)
+ == self._max_key_length)
+ or (len(self._editor._chars_valid)
+ in self.db.possible_tabkeys_lengths)):
+ if self._auto_commit:
+ self.commit_everything_unless_invalid()
+ else:
+ self._editor.commit_to_preedit()
+ res = self._editor.add_input(keychar)
+ if not res:
+ if self._auto_select and self._editor._candidates_previous:
+ # Used for example for the Russian transliteration method
+ # “translit”, which uses “auto select”.
+ # The “translit” table contains:
+ #
+ # sh ш
+ # shh щ
+ #
+ # so typing “sh” matches “ш” and “щ”. The
+ # candidate with the shortest key sequence comes
+ # first in the lookup table, therefore “sh ш”
+ # is shown in the preëdit (The other candidate,
+ # “shh щ” comes second in the lookup table and
+ # could be selected using arrow-down. But
+ # “translit” hides the lookup table by default).
+ #
+ # Now, when after typing “sh” one types “s”,
+ # the key “shs” has no match, so add_input('s')
+ # returns “False” and we end up here. We pop the
+ # last character “s” which caused the match to
+ # fail, commit first of the previous candidates,
+ # i.e. “sh ш” and feed the “s” into the
+ # key event handler again.
+ self._editor.pop_input()
+ self.commit_everything_unless_invalid()
+ return self._table_mode_process_key_event(key)
+ self.commit_everything_unless_invalid()
+ self._update_ui()
+ return True
+ else:
+ if (self._auto_commit and self._editor.one_candidate()
+ and
+ (self._editor._chars_valid
+ == self._editor._candidates[0][0])):
+ self.commit_everything_unless_invalid()
+ self._update_ui()
+ return True
+
+ if key.val in self._commit_keys:
+ if self.commit_everything_unless_invalid():
+ if self._editor._auto_select:
+ self.commit_string(u' ')
+ return True
+
+ if key.val in self._page_down_keys and self._editor._candidates:
+ res = self._editor.page_down()
+ self._update_ui()
+ return res
+
+ if key.val in self._page_up_keys and self._editor._candidates:
+ res = self._editor.page_up()
+ self._update_ui()
+ return res
+
+ if (key.val in self._editor.get_select_keys()
+ and self._editor._candidates):
+ if self._editor.select_key(key.val): # commits to preëdit
+ self.commit_string(
+ self._editor.get_preedit_string_complete(),
+ tabkeys=self._editor.get_preedit_tabkeys_complete())
+ return True
+
+ # Section to handle trailing invalid input:
+ #
+ # If the key has still not been handled when this point is
+ # reached, it cannot be a valid input character. Neither can
+ # it be a select key nor a page-up/page-down key. Adding this
+ # key to the tabkeys and search for matching candidates in the
+ # table would thus be pointless.
+ #
+ # So we commit all pending input immediately and then commit
+ # this invalid input character as well, possibly converted to
+ # fullwidth or halfwidth.
+ if keychar:
+ if debug_level > 0:
+ sys.stderr.write(
+ '_table_mode_process_key_event() trailing invalid input: '
+ + 'repr(keychar)=%(keychar)s\n'
+ % {'keychar': keychar})
+ if not self._editor._candidates:
+ self.commit_string(self._editor.get_preedit_tabkeys_complete())
+ else:
+ self._editor.commit_to_preedit()
+ self.commit_string(self._editor.get_preedit_string_complete())
+ if ascii_ispunct(keychar):
+ self.commit_string(self.cond_punct_translate(keychar))
+ else:
+ self.commit_string(self.cond_letter_translate(keychar))
+ return True
+
+ # What kind of key was this??
+ #
+ # keychar = IBus.keyval_to_unicode(key.val)
+ #
+ # returned no result. So whatever this was, we cannot handle it,
+ # just pass it through to the application by returning “False”.
+ return False
+
+ def do_focus_in (self):
+ if debug_level > 1:
+ sys.stderr.write("do_focus_in()")
+ if self._on:
+ self.register_properties(self.properties)
+ self._init_or_update_property_menu(
+ self.input_mode_menu,
+ self._input_mode)
+ self._update_ui ()
+
+ def do_focus_out (self):
+ if self._has_input_purpose:
+ self._input_purpose = 0
+ self._editor.clear_all_input_and_preedit()
+
+ def do_set_content_type(self, purpose, hints):
+ if self._has_input_purpose:
+ self._input_purpose = purpose
+
+ def do_enable (self):
+ self._on = True
+ self.do_focus_in()
+
+ def do_disable (self):
+ self._on = False
+
+ def do_page_up (self):
+ if self._editor.page_up ():
+ self._update_ui ()
+ return True
+ return False
+
+ def do_page_down (self):
+ if self._editor.page_down ():
+ self._update_ui ()
+ return True
+ return False
+
+ def config_section_normalize(self, section):
+ # This function replaces _: with - in the dconf
+ # section and converts to lower case to make
+ # the comparison of the dconf sections work correctly.
+ # I avoid using .lower() here because it is locale dependent,
+ # when using .lower() this would not achieve the desired
+ # effect of comparing the dconf sections case insentively
+ # in some locales, it would fail for example if Turkish
+ # locale (tr_TR.UTF-8) is set.
+ if sys.version_info >= (3, 0, 0): # Python3
+ return re.sub(r'[_:]', r'-', section).translate(
+ ''.maketrans(
+ string.ascii_uppercase,
+ string.ascii_lowercase))
+ else: # Python2
+ return re.sub(r'[_:]', r'-', section).translate(
+ string.maketrans(
+ string.ascii_uppercase,
+ string.ascii_lowercase).decode('ISO-8859-1'))
+
+ def config_value_changed_cb(self, config, section, name, value):
+ if (self.config_section_normalize(self._config_section)
+ != self.config_section_normalize(section)):
+ return
+ value = variant_to_value(value)
+ print('config value %(n)s for engine %(en)s changed to %(value)s'
+ % {'n': name, 'en': self._engine_name, 'value': value})
+ if name == u'inputmode':
+ self.set_input_mode(value)
+ return
+ if name == u'autoselect':
+ self._editor._auto_select = value
+ self._auto_select = value
+ return
+ if name == u'autocommit':
+ self.set_autocommit_mode(value)
+ return
+ if name == u'chinesemode':
+ self.set_chinese_mode(value)
+ self.db.reset_phrases_cache()
+ return
+ if name == u'endeffullwidthletter':
+ self.set_letter_width(value, input_mode=0)
+ return
+ if name == u'endeffullwidthpunct':
+ self.set_punctuation_width(value, input_mode=0)
+ return
+ if name == u'lookuptableorientation':
+ self._editor._orientation = value
+ self._editor._lookup_table.set_orientation(value)
+ return
+ if name == u'lookuptablepagesize':
+ if value > len(self._editor._select_keys):
+ value = len(self._editor._select_keys)
+ self._config.set_value(
+ self._config_section,
+ 'lookuptablepagesize',
+ GLib.Variant.new_int32(value))
+ if value < 1:
+ value = 1
+ self._config.set_value(
+ self._config_section,
+ 'lookuptablepagesize',
+ GLib.Variant.new_int32(value))
+ self._editor._page_size = value
+ self._editor._lookup_table = self._editor.get_new_lookup_table(
+ page_size = self._editor._page_size,
+ select_keys = self._editor._select_keys,
+ orientation = self._editor._orientation)
+ self.reset()
+ return
+ if name == u'onechar':
+ self.set_onechar_mode(value)
+ self.db.reset_phrases_cache()
+ return
+ if name == u'tabdeffullwidthletter':
+ self.set_letter_width(value, input_mode=1)
+ return
+ if name == u'tabdeffullwidthpunct':
+ self.set_punctuation_width(value, input_mode=1)
+ return
+ if name == u'alwaysshowlookup':
+ self._always_show_lookup = value
+ return
+ if name == u'spacekeybehavior':
+ if value == True:
+ # space is used as a page down key and not as a commit key:
+ if IBus.KEY_space not in self._page_down_keys:
+ self._page_down_keys.append(IBus.KEY_space)
+ if IBus.KEY_space in self._commit_keys:
+ self._commit_keys.remove(IBus.KEY_space)
+ if value == False:
+ # space is used as a commit key and not used as a page down key:
+ if IBus.KEY_space in self._page_down_keys:
+ self._page_down_keys.remove(IBus.KEY_space)
+ if IBus.KEY_space not in self._commit_keys:
+ self._commit_keys.append(IBus.KEY_space)
+ if debug_level > 1:
+ sys.stderr.write(
+ "self._page_down_keys=%s\n"
+ % repr(self._page_down_keys))
+ return
+ if name == u'singlewildcardchar':
+ self._single_wildcard_char = value
+ self._editor._single_wildcard_char = value
+ self.db.reset_phrases_cache()
+ return
+ if name == u'multiwildcardchar':
+ self._multi_wildcard_char = value
+ self._editor._multi_wildcard_char = value
+ self.db.reset_phrases_cache()
+ return
+ if name == u'autowildcard':
+ self._auto_wildcard = value
+ self._editor._auto_wildcard = value
+ self.db.reset_phrases_cache()
+ return
diff -Nru ibus-table-1.9.18.orig/engine/tabsqlitedb.py ibus-table-1.9.18/engine/tabsqlitedb.py
--- ibus-table-1.9.18.orig/engine/tabsqlitedb.py 2020-07-22 11:52:11.651532112 +0200
+++ ibus-table-1.9.18/engine/tabsqlitedb.py 2020-07-22 14:43:51.907260935 +0200
@@ -1047,6 +1047,8 @@
traceback.print_exc ()
def init_user_db(self, db_file):
+ if db_file == ':memory:':
+ return
if not path.exists(db_file):
db = sqlite3.connect(db_file)
# 20000 pages should be enough to cache the whole database
diff -Nru ibus-table-1.9.18.orig/engine/tabsqlitedb.py.orig ibus-table-1.9.18/engine/tabsqlitedb.py.orig
--- ibus-table-1.9.18.orig/engine/tabsqlitedb.py.orig 1970-01-01 01:00:00.000000000 +0100
+++ ibus-table-1.9.18/engine/tabsqlitedb.py.orig 2020-07-22 11:52:11.651532112 +0200
@@ -0,0 +1,1433 @@
+# -*- coding: utf-8 -*-
+# vim:et sts=4 sw=4
+#
+# ibus-table - The Tables engine for IBus
+#
+# Copyright (c) 2008-2009 Yu Yuwei <acevery@gmail.com>
+# Copyright (c) 2009-2014 Caius "kaio" CHANCE <me@kaio.net>
+# Copyright (c) 2012-2015 Mike FABIAN <mfabian@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import sys
+if sys.version_info < (3, 0, 0):
+ reload (sys)
+ sys.setdefaultencoding('utf-8')
+import os
+import os.path as path
+import shutil
+import sqlite3
+import uuid
+import time
+import re
+import chinese_variants
+
+debug_level = int(0)
+
+database_version = '1.00'
+
+patt_r = re.compile(r'c([ea])(\d):(.*)')
+patt_p = re.compile(r'p(-{0,1}\d)(-{0,1}\d)')
+
+chinese_nocheck_chars = u"“”‘’《》〈〉〔〕「」『』【】〖〗()[]{}"\
+ u".。,、;:?!…—·ˉˇ¨々~‖∶"'`|"\
+ u"⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛"\
+ u"АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯЁ"\
+ u"ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ"\
+ u"⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛"\
+ u"㎎㎏㎜㎝㎞㎡㏄㏎㏑㏒㏕"\
+ u"ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ"\
+ u"⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇"\
+ u"€$¢£¥"\
+ u"¤→↑←↓↖↗↘↙"\
+ u"ァアィイゥウェエォオカガキギクグケゲコゴサザシジ"\
+ u"スズセゼソゾタダチヂッツヅテデトドナニヌネノハバパ"\
+ u"ヒビピフブプヘベペホボポマミムメモャヤュユョヨラ"\
+ u"リルレロヮワヰヱヲンヴヵヶーヽヾ"\
+ u"ぁあぃいぅうぇえぉおかがきぎぱくぐけげこごさざしじ"\
+ u"すずせぜそぞただちぢっつづてでとどなにぬねのはば"\
+ u"ひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらり"\
+ u"るれろゎわゐゑをん゛゜ゝゞ"\
+ u"勹灬冫艹屮辶刂匚阝廾丨虍彐卩钅冂冖宀疒肀丿攵凵犭"\
+ u"亻彡饣礻扌氵纟亠囗忄讠衤廴尢夂丶"\
+ u"āáǎàōóǒòêēéěèīíǐìǖǘǚǜüūúǔù"\
+ u"+-<=>±×÷∈∏∑∕√∝∞∟∠∣∥∧∨∩∪∫∮"\
+ u"∴∵∶∷∽≈≌≒≠≡≤≥≦≧≮≯⊕⊙⊥⊿℃°‰"\
+ u"♂♀§№☆★○●◎◇◆□■△▲※〓#&@\^_ ̄"\
+ u"абвгдежзийклмнопрстуфхцчшщъыьэюяё"\
+ u"ⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹβγδεζηαικλμνξοπρστυφθψω"\
+ u"①②③④⑤⑥⑦⑧⑨⑩①②③④⑤⑥⑦⑧⑨⑩"\
+ u"㈠㈡㈢㈣㈤㈥㈦㈧㈨㈩㈠㈡㈢㈣㈤㈥㈦㈧㈨㈩"\
+ u"ㄅㄆㄇㄈㄉㄊㄋㄌㄍㄎㄏㄐㄑㄒㄓㄔㄕㄖㄗㄘㄙㄧㄨㄩ"\
+ u"ㄚㄛㄜㄝㄞㄟㄠㄡㄢㄣㄤㄥㄦ"
+
+class ImeProperties:
+ def __init__(self, db=None, default_properties={}):
+ '''
+ “db” is the handle of the sqlite3 database file obtained by
+ sqlite3.connect().
+ '''
+ if not db:
+ return None
+ self.ime_property_cache = default_properties
+ sqlstr = 'SELECT attr, val FROM main.ime;'
+ try:
+ results = db.execute(sqlstr).fetchall()
+ except:
+ import traceback
+ traceback.print_exc()
+ for result in results:
+ self.ime_property_cache[result[0]] = result[1]
+
+ def get(self, key):
+ if key in self.ime_property_cache:
+ return self.ime_property_cache[key]
+ else:
+ return None
+
+class tabsqlitedb:
+ '''Phrase database for tables
+
+ The phrases table in the database has columns with the names:
+
+ “id”, “tabkeys”, “phrase”, “freq”, “user_freq”
+
+ There are 2 databases, sysdb, userdb.
+
+ sysdb: System database for the input method, for example something
+ like /usr/share/ibus-table/tables/wubi-jidian86.db
+ “user_freq” is always 0 in a system database. “freq”
+ is some number in a system database indicating a frequency
+ of use of that phrase relative to the other phrases in that
+ database.
+
+ user_db: Database on disk where the phrases used or defined by the
+ user are stored. “user_freq” is a counter which counts how
+ many times that combination of “tabkeys” and “phrase” has
+ been used. “freq” is equal to 0 for all combinations of
+ “tabkeys” and “phrase” where an entry for that phrase is
+ already in the system database which starts with the same
+ “tabkeys”.
+ For combinations of “tabkeys” and “phrase” which do not exist
+ at all in the system database, “freq” is equal to -1 to
+ indidated that this is a user defined phrase.
+ '''
+ def __init__(
+ self, filename = None, user_db = None, create_database = False):
+ global debug_level
+ try:
+ debug_level = int(os.getenv('IBUS_TABLE_DEBUG_LEVEL'))
+ except (TypeError, ValueError):
+ debug_level = int(0)
+ self.old_phrases = []
+ self.filename = filename
+ self._user_db = user_db
+ self.reset_phrases_cache()
+
+ if create_database or os.path.isfile(self.filename):
+ self.db = sqlite3.connect(self.filename)
+ else:
+ print('Cannot open database file %s' %self.filename)
+ try:
+ self.db.execute('PRAGMA encoding = "UTF-8";')
+ self.db.execute('PRAGMA case_sensitive_like = true;')
+ self.db.execute('PRAGMA page_size = 4096;')
+ # 20000 pages should be enough to cache the whole database
+ self.db.execute('PRAGMA cache_size = 20000;')
+ self.db.execute('PRAGMA temp_store = MEMORY;')
+ self.db.execute('PRAGMA journal_size_limit = 1000000;')
+ self.db.execute('PRAGMA synchronous = NORMAL;')
+ except:
+ import traceback
+ traceback.print_exc()
+ print('Error while initializing database.')
+ # create IME property table
+ self.db.executescript(
+ 'CREATE TABLE IF NOT EXISTS main.ime (attr TEXT, val TEXT);')
+ # Initalize missing attributes in the ime table with some
+ # default values, they should be updated using the attributes
+ # found in the source when creating a system database with
+ # tabcreatedb.py
+ self._default_ime_attributes = {
+ 'name':'',
+ 'name.zh_cn':'',
+ 'name.zh_hk':'',
+ 'name.zh_tw':'',
+ 'author':'somebody',
+ 'uuid':'%s' % uuid.uuid4(),
+ 'serial_number':'%s' % time.strftime('%Y%m%d'),
+ 'icon':'ibus-table.svg',
+ 'license':'LGPL',
+ 'languages':'',
+ 'language_filter':'',
+ 'valid_input_chars':'abcdefghijklmnopqrstuvwxyz',
+ 'max_key_length':'4',
+ 'commit_keys':'space',
+ # 'forward_keys':'Return',
+ 'select_keys':'1,2,3,4,5,6,7,8,9,0',
+ 'page_up_keys':'Page_Up,minus',
+ 'page_down_keys':'Page_Down,equal',
+ 'status_prompt':'',
+ 'def_full_width_punct':'true',
+ 'def_full_width_letter':'false',
+ 'user_can_define_phrase':'false',
+ 'pinyin_mode':'false',
+ 'dynamic_adjust':'false',
+ 'auto_select':'false',
+ 'auto_commit':'false',
+ # 'no_check_chars':u'',
+ 'description':'A IME under IBus Table',
+ 'layout':'us',
+ 'symbol':'',
+ 'rules':'',
+ 'least_commit_length':'0',
+ 'start_chars':'',
+ 'orientation':'true',
+ 'always_show_lookup':'true',
+ 'char_prompts':'{}'
+ # we use this entry for those IME, which don't
+ # have rules to build up phrase, but still need
+ # auto commit to preedit
+ }
+ if create_database:
+ select_sqlstr = '''
+ SELECT val FROM main.ime WHERE attr = :attr;'''
+ insert_sqlstr = '''
+ INSERT INTO main.ime (attr, val) VALUES (:attr, :val);'''
+ for attr in self._default_ime_attributes:
+ sqlargs = {
+ 'attr': attr,
+ 'val': self._default_ime_attributes[attr]
+ }
+ if not self.db.execute(select_sqlstr, sqlargs).fetchall():
+ self.db.execute(insert_sqlstr, sqlargs)
+ self.ime_properties = ImeProperties(
+ db=self.db,
+ default_properties=self._default_ime_attributes)
+ # shared variables in this class:
+ self._mlen = int(self.ime_properties.get("max_key_length"))
+ self._is_chinese = self.is_chinese()
+ self._is_cjk = self.is_cjk()
+ self.user_can_define_phrase = self.ime_properties.get(
+ 'user_can_define_phrase')
+ if self.user_can_define_phrase:
+ if self.user_can_define_phrase.lower() == u'true' :
+ self.user_can_define_phrase = True
+ else:
+ self.user_can_define_phrase = False
+ else:
+ print(
+ 'Could not find "user_can_define_phrase" entry from database, '
+ + 'is it an outdated database?')
+ self.user_can_define_phrase = False
+
+ self.dynamic_adjust = self.ime_properties.get('dynamic_adjust')
+ if self.dynamic_adjust:
+ if self.dynamic_adjust.lower() == u'true' :
+ self.dynamic_adjust = True
+ else:
+ self.dynamic_adjust = False
+ else:
+ print(
+ 'Could not find "dynamic_adjust" entry from database, '
+ + 'is it an outdated database?')
+ self.dynamic_adjust = False
+
+ self.rules = self.get_rules ()
+ self.possible_tabkeys_lengths = self.get_possible_tabkeys_lengths()
+ self.startchars = self.get_start_chars ()
+
+ if not user_db or create_database:
+ # No user database requested or we are
+ # just creating the system database and
+ # we do not need a user database for that
+ return
+
+ if user_db != ":memory:":
+ # Do not move this import to the beginning of this script!
+ # If for example the home directory is not writeable,
+ # ibus_table_location.py would fail because it cannot
+ # create some directories.
+ #
+ # But for tabcreatedb.py, no such directories are needed,
+ # tabcreatedb.py should not fail just because
+ # ibus_table_location.py cannot create some directories.
+ #
+ # “HOME=/foobar ibus-table-createdb” should not fail if
+ # “/foobar” is not writeable.
+ import ibus_table_location
+ tables_path = path.join(ibus_table_location.data_home(), "tables")
+ if not path.isdir(tables_path):
+ old_tables_path = os.path.join(
+ os.getenv('HOME'), '.ibus/tables')
+ if path.isdir(old_tables_path):
+ if os.access(os.path.join(
+ old_tables_path, 'debug.log'), os.F_OK):
+ os.unlink(os.path.join(old_tables_path, 'debug.log'))
+ if os.access(os.path.join(
+ old_tables_path, 'setup-debug.log'), os.F_OK):
+ os.unlink(os.path.join(
+ old_tables_path, 'setup-debug.log'))
+ shutil.copytree(old_tables_path, tables_path)
+ shutil.rmtree(old_tables_path)
+ os.symlink(tables_path, old_tables_path)
+ else:
+ os.makedirs(tables_path)
+ user_db = path.join(tables_path, user_db)
+ if not path.exists(user_db):
+ sys.stderr.write(
+ 'The user database %(udb)s does not exist yet.\n'
+ % {'udb': user_db})
+ else:
+ try:
+ desc = self.get_database_desc(user_db)
+ phrase_table_column_names = [
+ 'id', 'tabkeys', 'phrase','freq','user_freq']
+ if (desc == None
+ or desc["version"] != database_version
+ or (self.get_number_of_columns_of_phrase_table(user_db)
+ != len(phrase_table_column_names))):
+ sys.stderr.write(
+ 'The user database %s seems to be incompatible.\n'
+ % user_db)
+ if desc == None:
+ sys.stderr.write(
+ 'There is no version information in '
+ + 'the database.\n')
+ self.old_phrases = self.extract_user_phrases(
+ user_db, old_database_version = '0.0')
+ elif desc["version"] != database_version:
+ sys.stderr.write(
+ 'The version of the database does not match '
+ + '(too old or too new?).\n'
+ 'ibus-table wants version=%s\n'
+ % database_version
+ + 'But the database actually has version=%s\n'
+ % desc['version'])
+ self.old_phrases = self.extract_user_phrases(
+ user_db, old_database_version = desc['version'])
+ elif (self.get_number_of_columns_of_phrase_table(
+ user_db)
+ != len(phrase_table_column_names)):
+ sys.stderr.write(
+ 'The number of columns of the database '
+ + 'does not match.\n'
+ + 'ibus-table expects %s columns.\n'
+ % len(phrase_table_column_names)
+ + 'But the database actually has %s columns.\n'
+ % self.get_number_of_columns_of_phrase_table(
+ user_db)
+ + 'But the versions of the databases are '
+ + 'identical.\n'
+ + 'This should never happen!\n')
+ self.old_phrases = None
+ from time import strftime
+ timestamp = strftime('-%Y-%m-%d_%H:%M:%S')
+ sys.stderr.write(
+ 'Renaming the incompatible database to "%s".\n'
+ % user_db+timestamp)
+ if os.path.exists(user_db):
+ os.rename(user_db, user_db+timestamp)
+ if os.path.exists(user_db+'-shm'):
+ os.rename(user_db+'-shm', user_db+'-shm'+timestamp)
+ if os.path.exists(user_db+'-wal'):
+ os.rename(user_db+'-wal', user_db+'-wal'+timestamp)
+ sys.stderr.write(
+ 'Creating a new, empty database "s".\n'
+ % user_db)
+ self.init_user_db(user_db)
+ sys.stderr.write(
+ 'If user phrases were successfully recovered from '
+ + 'the old,\n'
+ + 'incompatible database, they will be used to '
+ + 'initialize the new database.\n')
+ else:
+ sys.stderr.write(
+ 'Compatible database %s found.\n' % user_db)
+ except:
+ import traceback
+ traceback.print_exc()
+
+ # open user phrase database
+ try:
+ sys.stderr.write(
+ 'Connect to the database %(name)s.\n' %{'name': user_db})
+ self.db.executescript('''
+ ATTACH DATABASE "%s" AS user_db;
+ PRAGMA user_db.encoding = "UTF-8";
+ PRAGMA user_db.case_sensitive_like = true;
+ PRAGMA user_db.page_size = 4096;
+ PRAGMA user_db.cache_size = 20000;
+ PRAGMA user_db.temp_store = MEMORY;
+ PRAGMA user_db.journal_mode = WAL;
+ PRAGMA user_db.journal_size_limit = 1000000;
+ PRAGMA user_db.synchronous = NORMAL;
+ ''' % user_db)
+ except:
+ sys.stderr.write('Could not open the database %s.\n' % user_db)
+ from time import strftime
+ timestamp = strftime('-%Y-%m-%d_%H:%M:%S')
+ sys.stderr.write('Renaming the incompatible database to "%s".\n'
+ % user_db+timestamp)
+ if os.path.exists(user_db):
+ os.rename(user_db, user_db+timestamp)
+ if os.path.exists(user_db+'-shm'):
+ os.rename(user_db+'-shm', user_db+'-shm'+timestamp)
+ if os.path.exists(user_db+'-wal'):
+ os.rename(user_db+'-wal', user_db+'-wal'+timestamp)
+ sys.stderr.write('Creating a new, empty database "%s".\n'
+ % user_db)
+ self.init_user_db(user_db)
+ self.db.executescript('''
+ ATTACH DATABASE "%s" AS user_db;
+ PRAGMA user_db.encoding = "UTF-8";
+ PRAGMA user_db.case_sensitive_like = true;
+ PRAGMA user_db.page_size = 4096;
+ PRAGMA user_db.cache_size = 20000;
+ PRAGMA user_db.temp_store = MEMORY;
+ PRAGMA user_db.journal_mode = WAL;
+ PRAGMA user_db.journal_size_limit = 1000000;
+ PRAGMA user_db.synchronous = NORMAL;
+ ''' % user_db)
+ self.create_tables("user_db")
+ if self.old_phrases:
+ sqlargs = []
+ for x in self.old_phrases:
+ sqlargs.append(
+ {'tabkeys': x[0],
+ 'phrase': x[1],
+ 'freq': x[2],
+ 'user_freq': x[3]})
+ sqlstr = '''
+ INSERT INTO user_db.phrases (tabkeys, phrase, freq, user_freq)
+ VALUES (:tabkeys, :phrase, :freq, :user_freq)
+ '''
+ try:
+ self.db.executemany(sqlstr, sqlargs)
+ except:
+ import traceback
+ traceback.print_exec()
+ self.db.commit ()
+ self.db.execute('PRAGMA wal_checkpoint;')
+
+ # try create all tables in user database
+ self.create_indexes ("user_db", commit=False)
+ self.generate_userdb_desc ()
+
+ def update_phrase(
+ self, tabkeys=u'', phrase=u'',
+ user_freq=0, database='user_db', commit=True):
+ '''update phrase freqs'''
+ if debug_level > 1:
+ sys.stderr.write(
+ 'update_phrase() tabkeys=%(t)s phrase=%(p)s '
+ % {'t': tabkeys, 'p': phrase}
+ + 'user_freq=%(u)s database=%(d)s\n'
+ % {'u': user_freq, 'd': database})
+ if not tabkeys or not phrase:
+ return
+ sqlstr = '''
+ UPDATE %s.phrases SET user_freq = :user_freq
+ WHERE tabkeys = :tabkeys AND phrase = :phrase
+ ;''' % database
+ sqlargs = {'user_freq': user_freq, 'tabkeys': tabkeys, 'phrase': phrase}
+ try:
+ self.db.execute(sqlstr, sqlargs)
+ if commit:
+ self.db.commit()
+ self.invalidate_phrases_cache(tabkeys)
+ except:
+ import traceback
+ traceback.print_exc()
+
+ def sync_usrdb (self):
+ '''
+ Trigger a checkpoint operation.
+ '''
+ if self._user_db is None:
+ return
+ self.db.commit()
+ self.db.execute('PRAGMA wal_checkpoint;')
+
+ def reset_phrases_cache (self):
+ self._phrases_cache = {}
+
+ def invalidate_phrases_cache (self, tabkeys=u''):
+ for i in range(1, self._mlen + 1):
+ if self._phrases_cache.get(tabkeys[0:i]):
+ self._phrases_cache.pop(tabkeys[0:i])
+
+ def is_chinese (self):
+ __lang = self.ime_properties.get('languages')
+ if __lang:
+ __langs = __lang.split(',')
+ for _l in __langs:
+ if _l.lower().find('zh') != -1:
+ return True
+ return False
+
+ def is_cjk(self):
+ languages = self.ime_properties.get('languages')
+ if languages:
+ languages = languages.split(',')
+ for language in languages:
+ for lang in ['zh', 'ja', 'ko']:
+ if language.strip().startswith(lang):
+ return True
+ return False
+
+ def get_chinese_mode (self):
+ try:
+ __dict = {'cm0':0, 'cm1':1, 'cm2':2, 'cm3':3, 'cm4':4}
+ __filt = self.ime_properties.get('language_filter')
+ return __dict[__filt]
+ except:
+ return -1
+
+ def get_select_keys (self):
+ ret = self.ime_properties.get("select_keys")
+ if ret:
+ return ret
+ return "1,2,3,4,5,6,7,8,9,0"
+
+ def get_orientation (self):
+ try:
+ return int(self.ime_properties.get('orientation'))
+ except:
+ return 1
+
+ def create_tables (self, database):
+ '''Create tables that contain all phrase'''
+ if database == 'main':
+ sqlstr = '''
+ CREATE TABLE IF NOT EXISTS %s.goucima
+ (zi TEXT PRIMARY KEY, goucima TEXT);
+ ''' % database
+ self.db.execute (sqlstr)
+ sqlstr = '''
+ CREATE TABLE IF NOT EXISTS %s.pinyin
+ (pinyin TEXT, zi TEXT, freq INTEGER);
+ ''' % database
+ self.db.execute(sqlstr)
+
+ sqlstr = '''
+ CREATE TABLE IF NOT EXISTS %s.phrases
+ (id INTEGER PRIMARY KEY, tabkeys TEXT, phrase TEXT,
+ freq INTEGER, user_freq INTEGER);
+ ''' % database
+ self.db.execute (sqlstr)
+ self.db.commit()
+
+ def update_ime (self, attrs):
+ '''Update or insert attributes in ime table, attrs is a iterable object
+ Like [(attr,val), (attr,val), ...]
+
+ This is called only by tabcreatedb.py.
+ '''
+ select_sqlstr = 'SELECT val from main.ime WHERE attr = :attr'
+ update_sqlstr = 'UPDATE main.ime SET val = :val WHERE attr = :attr;'
+ insert_sqlstr = 'INSERT INTO main.ime (attr, val) VALUES (:attr, :val);'
+ for attr, val in attrs:
+ sqlargs = {'attr': attr, 'val': val}
+ if self.db.execute(select_sqlstr, sqlargs).fetchall():
+ self.db.execute(update_sqlstr, sqlargs)
+ else:
+ self.db.execute(insert_sqlstr, sqlargs)
+ self.db.commit()
+ # update ime properties cache:
+ self.ime_properties = ImeProperties(
+ db=self.db,
+ default_properties=self._default_ime_attributes)
+ # The self variables used by tabcreatedb.py need to be updated now:
+ self._mlen = int(self.ime_properties.get('max_key_length'))
+ self._is_chinese = self.is_chinese()
+ self.user_can_define_phrase = self.ime_properties.get(
+ 'user_can_define_phrase')
+ if self.user_can_define_phrase:
+ if self.user_can_define_phrase.lower() == u'true' :
+ self.user_can_define_phrase = True
+ else:
+ self.user_can_define_phrase = False
+ else:
+ print(
+ 'Could not find "user_can_define_phrase" entry from database, '
+ + 'is it a outdated database?')
+ self.user_can_define_phrase = False
+ self.rules = self.get_rules()
+
+ def get_rules (self):
+ '''Get phrase construct rules'''
+ rules = {}
+ if self.user_can_define_phrase:
+ try:
+ _rules = self.ime_properties.get('rules')
+ if _rules:
+ _rules = _rules.strip().split(';')
+ for rule in _rules:
+ res = patt_r.match (rule)
+ if res:
+ cms = []
+ if res.group(1) == 'a':
+ rules['above'] = int(res.group(2))
+ _cms = res.group(3).split('+')
+ if len(_cms) > self._mlen:
+ print('rule: "%s" over max key length' %rule)
+ break
+ for _cm in _cms:
+ cm_res = patt_p.match(_cm)
+ cms.append((int(cm_res.group(1)),
+ int(cm_res.group(2))))
+ rules[int(res.group(2))]=cms
+ else:
+ print('not a legal rule: "%s"' %rule)
+ except Exception:
+ import traceback
+ traceback.print_exc ()
+ return rules
+ else:
+ return ""
+
+ def get_possible_tabkeys_lengths(self):
+ '''Return a list of the possible lengths for tabkeys in this table.
+
+ Example:
+
+ If the table source has rules like:
+
+ RULES = ce2:p11+p12+p21+p22;ce3:p11+p21+p22+p31;ca4:p11+p21+p31+p41
+
+ self._rules will be set to
+
+ self._rules={2: [(1, 1), (1, 2), (2, 1), (2, 2)], 3: [(1, 1), (1, 2), (2, 1), (3, 1)], 4: [(1, 1), (2, 1), (3, 1), (-1, 1)], 'above': 4}
+
+ and then this function returns “[4, 4, 4]”
+
+ Or, if the table source has no RULES but LEAST_COMMIT_LENGTH=2
+ and MAX_KEY_LENGTH = 4, then it returns “[2, 3, 4]”
+
+ I cannot find any tables which use LEAST_COMMIT_LENGTH though.
+ '''
+ if self.rules:
+ max_len = self.rules["above"]
+ return [len(self.rules[x]) for x in range(2, max_len+1)][:]
+ else:
+ try:
+ least_commit_len = int(
+ self.ime_properties.get('least_commit_length'))
+ except:
+ least_commit_len = 0
+ if least_commit_len > 0:
+ return list(range(least_commit_len, self._mlen + 1))
+ else:
+ return []
+
+ def get_start_chars (self):
+ '''return possible start chars of IME'''
+ return self.ime_properties.get('start_chars')
+
+ def get_no_check_chars (self):
+ '''Get the characters which engine should not change freq'''
+ _chars = self.ime_properties.get('no_check_chars')
+ if type(_chars) != type(u''):
+ _chars = _chars.decode('utf-8')
+ return _chars
+
+ def add_phrases (self, phrases, database = 'main'):
+ '''Add many phrases to database fast. Used by tabcreatedb.py when
+ creating the system database from scratch.
+
+ “phrases” is a iterable object which looks like:
+
+ [(tabkeys, phrase, freq ,user_freq), (tabkeys, phrase, freq, user_freq), ...]
+
+ This function does not check whether phrases are already
+ there. As this function is only used while creating the
+ system database, it is not really necessary to check whether
+ phrases are already there because the database is initially
+ empty anyway. And the caller should take care that the
+ “phrases” argument does not contain duplicates.
+
+ '''
+ if debug_level > 1:
+ sys.stderr.write("add_phrases() len(phrases)=%s\n"
+ %len(phrases))
+ insert_sqlstr = '''
+ INSERT INTO %(database)s.phrases
+ (tabkeys, phrase, freq, user_freq)
+ VALUES (:tabkeys, :phrase, :freq, :user_freq);
+ ''' % {'database': database}
+ insert_sqlargs = []
+ for (tabkeys, phrase, freq, user_freq) in phrases:
+ insert_sqlargs.append({
+ 'tabkeys': tabkeys,
+ 'phrase': phrase,
+ 'freq': freq,
+ 'user_freq': user_freq})
+ self.invalidate_phrases_cache(tabkeys)
+ self.db.executemany(insert_sqlstr, insert_sqlargs)
+ self.db.commit()
+ self.db.execute('PRAGMA wal_checkpoint;')
+
+ def add_phrase(
+ self, tabkeys=u'', phrase=u'', freq=0, user_freq=0,
+ database='main',commit=True):
+ '''Add phrase to database, phrase is a object of
+ (tabkeys, phrase, freq ,user_freq)
+ '''
+ if debug_level > 1:
+ sys.stderr.write(
+ 'add_phrase tabkeys=%(t)s phrase=%(p)s '
+ % {'t': tabkeys, 'p': phrase}
+ + 'freq=%(f)s user_freq=%(u)s\n'
+ % {'f': freq, 'u': user_freq})
+ if not tabkeys or not phrase:
+ return
+ select_sqlstr = '''
+ SELECT * FROM %(database)s.phrases
+ WHERE tabkeys = :tabkeys AND phrase = :phrase;
+ ''' % {'database': database}
+ select_sqlargs = {'tabkeys': tabkeys, 'phrase': phrase}
+ results = self.db.execute(select_sqlstr, select_sqlargs).fetchall()
+ if results:
+ # there is already such a phrase, i.e. add_phrase was called
+ # in error, do nothing to avoid duplicate entries.
+ if debug_level > 1:
+ sys.stderr.write(
+ 'add_phrase() '
+ + 'select_sqlstr=%(sql)s select_sqlargs=%(arg)s '
+ % {'sql': select_sqlstr, 'arg': select_sqlargs}
+ + 'already there!: results=%(r)s \n'
+ % {'r': results})
+ return
+
+ insert_sqlstr = '''
+ INSERT INTO %(database)s.phrases
+ (tabkeys, phrase, freq, user_freq)
+ VALUES (:tabkeys, :phrase, :freq, :user_freq);
+ ''' % {'database': database}
+ insert_sqlargs = {
+ 'tabkeys': tabkeys,
+ 'phrase': phrase,
+ 'freq': freq,
+ 'user_freq': user_freq}
+ if debug_level > 1:
+ sys.stderr.write(
+ 'add_phrase() insert_sqlstr=%(sql)s insert_sqlargs=%(arg)s\n'
+ % {'sql': insert_sqlstr, 'arg': insert_sqlargs})
+ try:
+ self.db.execute (insert_sqlstr, insert_sqlargs)
+ if commit:
+ self.db.commit()
+ self.invalidate_phrases_cache(tabkeys)
+ except:
+ import traceback
+ traceback.print_exc()
+
+ def add_goucima (self, goucimas):
+ '''Add goucima into database, goucimas is iterable object
+ Like goucimas = [(zi,goucima), (zi,goucima), ...]
+ '''
+ sqlstr = '''
+ INSERT INTO main.goucima (zi, goucima) VALUES (:zi, :goucima);
+ '''
+ sqlargs = []
+ for zi, goucima in goucimas:
+ sqlargs.append({'zi': zi, 'goucima': goucima})
+ try:
+ self.db.commit()
+ self.db.executemany(sqlstr, sqlargs)
+ self.db.commit()
+ self.db.execute('PRAGMA wal_checkpoint;')
+ except:
+ import traceback
+ traceback.print_exc()
+
+ def add_pinyin (self, pinyins, database = 'main'):
+ '''Add pinyin to database, pinyins is a iterable object
+ Like: [(zi,pinyin, freq), (zi, pinyin, freq), ...]
+ '''
+ sqlstr = '''
+ INSERT INTO %s.pinyin (pinyin, zi, freq) VALUES (:pinyin, :zi, :freq);
+ ''' % database
+ count = 0
+ for pinyin, zi, freq in pinyins:
+ count += 1
+ pinyin = pinyin.replace(
+ '1','!').replace(
+ '2','@').replace(
+ '3','#').replace(
+ '4','$').replace(
+ '5','%')
+ try:
+ self.db.execute(
+ sqlstr, {'pinyin': pinyin, 'zi': zi, 'freq': freq})
+ except Exception:
+ sys.stderr.write(
+ 'Error when inserting into pinyin table. '
+ + 'count=%(c)s pinyin=%(p)s zi=%(z)s freq=%(f)s\n'
+ % {'c': count, 'p': pinyin, 'z': zi, 'f': freq})
+ import traceback
+ traceback.print_exc()
+ self.db.commit()
+
+ def optimize_database (self, database='main'):
+ sqlstr = '''
+ CREATE TABLE tmp AS SELECT * FROM %(database)s.phrases;
+ DELETE FROM %(database)s.phrases;
+ INSERT INTO %(database)s.phrases SELECT * FROM tmp ORDER BY
+ tabkeys ASC, phrase ASC, user_freq DESC, freq DESC, id ASC;
+ DROP TABLE tmp;
+ CREATE TABLE tmp AS SELECT * FROM %(database)s.goucima;
+ DELETE FROM %(database)s.goucima;
+ INSERT INTO %(database)s.goucima SELECT * FROM tmp ORDER BY zi, goucima;
+ DROP TABLE tmp;
+ CREATE TABLE tmp AS SELECT * FROM %(database)s.pinyin;
+ DELETE FROM %(database)s.pinyin;
+ INSERT INTO %(database)s.pinyin SELECT * FROM tmp ORDER BY pinyin ASC, freq DESC;
+ DROP TABLE tmp;
+ ''' % {'database':database}
+ self.db.executescript (sqlstr)
+ self.db.executescript ("VACUUM;")
+ self.db.commit()
+
+ def drop_indexes(self, database):
+ '''Drop the indexes in the database to reduce its size
+
+ We do not use any indexes at the moment, therefore this
+ function does nothing.
+ '''
+ if debug_level > 1:
+ sys.stderr.write("drop_indexes()\n")
+ return
+
+ def create_indexes(self, database, commit=True):
+ '''Create indexes for the database.
+
+ We do not use any indexes at the moment, therefore
+ this function does nothing. We used indexes before,
+ but benchmarking showed that none of them was really
+ speeding anything up, therefore we deleted all of them
+ to get much smaller databases (about half the size).
+
+ If some index turns out to be very useful in future, it could
+ be created here (and dropped in “drop_indexes()”).
+ '''
+ if debug_level > 1:
+ sys.stderr.write("create_indexes()\n")
+ return
+
+ def big5_code(self, phrase):
+ try:
+ big5 = phrase.encode('Big5')
+ except:
+ big5 = b'\xff\xff' # higher than any Big5 code
+ return big5
+
+ def best_candidates(
+ self, typed_tabkeys=u'', candidates=[], chinese_mode=-1):
+ '''
+ “candidates” is an array containing something like:
+ [(tabkeys, phrase, freq, user_freq), ...]
+
+ “typed_tabkeys” is key sequence the user really typed, which
+ maybe only the beginning part of the “tabkeys” in a matched
+ candidate.
+ '''
+ maximum_number_of_candidates = 100
+ engine_name = os.path.basename(self.filename).replace('.db', '')
+ if engine_name in [
+ 'cangjie3', 'cangjie5', 'cangjie-big',
+ 'quick-classic', 'quick3', 'quick5']:
+ code_point_function = self.big5_code
+ else:
+ code_point_function = lambda x: (1)
+ if chinese_mode in (2, 3) and self._is_chinese:
+ if chinese_mode == 2:
+ bitmask = (1 << 0) # used in simplified Chinese
+ else:
+ bitmask = (1 << 1) # used in traditional Chinese
+ return sorted(candidates,
+ key=lambda x: (
+ - int(
+ typed_tabkeys == x[0]
+ ), # exact matches first!
+ -1*x[3], # user_freq descending
+ # Prefer characters used in the
+ # desired Chinese variant:
+ -(bitmask
+ & chinese_variants.detect_chinese_category(
+ x[1])),
+ -1*x[2], # freq descending
+ len(x[0]), # len(tabkeys) ascending
+ x[0], # tabkeys alphabetical
+ code_point_function(x[1][0]),
+ # Unicode codepoint of first character of phrase:
+ ord(x[1][0])
+ ))[:maximum_number_of_candidates]
+ return sorted(candidates,
+ key=lambda x: (
+ - int(
+ typed_tabkeys == x[0]
+ ), # exact matches first!
+ -1*x[3], # user_freq descending
+ -1*x[2], # freq descending
+ len(x[0]), # len(tabkeys) ascending
+ x[0], # tabkeys alphabetical
+ code_point_function(x[1][0]),
+ # Unicode codepoint of first character of phrase:
+ ord(x[1][0])
+ ))[:maximum_number_of_candidates]
+
+ def select_words(
+ self, tabkeys=u'', onechar=False, chinese_mode=-1,
+ single_wildcard_char=u'', multi_wildcard_char=u'',
+ auto_wildcard=False):
+ '''
+ Get matching phrases for tabkeys from the database.
+ '''
+ if not tabkeys:
+ return []
+ # query phrases cache first
+ best = self._phrases_cache.get(tabkeys)
+ if best:
+ return best
+ one_char_condition = ''
+ if onechar:
+ # for some users really like to select only single characters
+ one_char_condition = ' AND length(phrase)=1 '
+
+ if self.user_can_define_phrase or self.dynamic_adjust:
+ sqlstr = '''
+ SELECT tabkeys, phrase, freq, user_freq FROM
+ (
+ SELECT tabkeys, phrase, freq, user_freq FROM main.phrases
+ WHERE tabkeys LIKE :tabkeys ESCAPE :escapechar %(one_char_condition)s
+ UNION ALL
+ SELECT tabkeys, phrase, freq, user_freq FROM user_db.phrases
+ WHERE tabkeys LIKE :tabkeys ESCAPE :escapechar %(one_char_condition)s
+ )
+ ''' % {'one_char_condition': one_char_condition}
+ else:
+ sqlstr = '''
+ SELECT tabkeys, phrase, freq, user_freq FROM main.phrases
+ WHERE tabkeys LIKE :tabkeys ESCAPE :escapechar %(one_char_condition)s
+ ''' % {'one_char_condition': one_char_condition}
+ escapechar = '☺'
+ for c in '!@#':
+ if c not in [single_wildcard_char, multi_wildcard_char]:
+ escapechar = c
+ tabkeys_for_like = tabkeys
+ tabkeys_for_like = tabkeys_for_like.replace(
+ escapechar, escapechar+escapechar)
+ if '%' not in [single_wildcard_char, multi_wildcard_char]:
+ tabkeys_for_like = tabkeys_for_like.replace('%', escapechar+'%')
+ if '_' not in [single_wildcard_char, multi_wildcard_char]:
+ tabkeys_for_like = tabkeys_for_like.replace('_', escapechar+'_')
+ if single_wildcard_char:
+ tabkeys_for_like = tabkeys_for_like.replace(
+ single_wildcard_char, '_')
+ if multi_wildcard_char:
+ tabkeys_for_like = tabkeys_for_like.replace(
+ multi_wildcard_char, '%%')
+ if auto_wildcard:
+ tabkeys_for_like += '%%'
+ sqlargs = {'tabkeys': tabkeys_for_like, 'escapechar': escapechar}
+ unfiltered_results = self.db.execute(sqlstr, sqlargs).fetchall()
+ bitmask = None
+ if chinese_mode == 0:
+ bitmask = (1 << 0) # simplified only
+ elif chinese_mode == 1:
+ bitmask = (1 << 1) # traditional only
+ if not bitmask:
+ results = unfiltered_results
+ else:
+ results = []
+ for result in unfiltered_results:
+ if (bitmask
+ & chinese_variants.detect_chinese_category(result[1])):
+ results.append(result)
+ # merge matches from the system database and from the user
+ # database to avoid duplicates in the candidate list for
+ # example, if we have the result ('aaaa', '工', 551000000, 0)
+ # from the system database and ('aaaa', '工', 0, 5) from the
+ # user database, these should be merged into one match
+ # ('aaaa', '工', 551000000, 5).
+ phrase_frequencies = {}
+ for result in results:
+ key = (result[0], result[1])
+ if key not in phrase_frequencies:
+ phrase_frequencies[key] = result
+ else:
+ phrase_frequencies.update([(
+ key,
+ key +
+ (
+ max(result[2], phrase_frequencies[key][2]),
+ max(result[3], phrase_frequencies[key][3]))
+ )])
+ best = self.best_candidates(
+ typed_tabkeys=tabkeys,
+ candidates=phrase_frequencies.values(),
+ chinese_mode=chinese_mode)
+ if debug_level > 1:
+ sys.stderr.write("select_words() best=%s\n" %repr(best))
+ self._phrases_cache[tabkeys] = best
+ return best
+
+ def select_chinese_characters_by_pinyin(
+ self, tabkeys=u'', chinese_mode=-1, single_wildcard_char=u'',
+ multi_wildcard_char=u''):
+ '''
+ Get Chinese characters matching the pinyin given by tabkeys
+ from the database.
+ '''
+ if not tabkeys:
+ return []
+ sqlstr = '''
+ SELECT pinyin, zi, freq FROM main.pinyin WHERE pinyin LIKE :tabkeys
+ ORDER BY freq DESC, pinyin ASC
+ ;'''
+ tabkeys_for_like = tabkeys
+ if single_wildcard_char:
+ tabkeys_for_like = tabkeys_for_like.replace(
+ single_wildcard_char, '_')
+ if multi_wildcard_char:
+ tabkeys_for_like = tabkeys_for_like.replace(
+ multi_wildcard_char, '%%')
+ tabkeys_for_like += '%%'
+ sqlargs = {'tabkeys': tabkeys_for_like}
+ results = self.db.execute(sqlstr, sqlargs).fetchall()
+ # now convert the results into a list of candidates in the format
+ # which was returned before I simplified the pinyin database table.
+ bitmask = None
+ if chinese_mode == 0:
+ bitmask = (1 << 0) # simplified only
+ elif chinese_mode == 1:
+ bitmask = (1 << 1) # traditional only
+ phrase_frequencies = []
+ for (pinyin, zi, freq) in results:
+ if not bitmask:
+ phrase_frequencies.append(tuple([pinyin, zi, freq, 0]))
+ else:
+ if bitmask & chinese_variants.detect_chinese_category(zi):
+ phrase_frequencies.append(tuple([pinyin, zi, freq, 0]))
+ return self.best_candidates(
+ typed_tabkeys=tabkeys,
+ candidates=phrase_frequencies,
+ chinese_mode=chinese_mode)
+
+ def generate_userdb_desc (self):
+ try:
+ sqlstring = (
+ 'CREATE TABLE IF NOT EXISTS user_db.desc '
+ + '(name PRIMARY KEY, value);')
+ self.db.executescript (sqlstring)
+ sqlstring = 'INSERT OR IGNORE INTO user_db.desc VALUES (?, ?);'
+ self.db.execute (sqlstring, ('version', database_version))
+ sqlstring = (
+ 'INSERT OR IGNORE INTO user_db.desc '
+ + 'VALUES (?, DATETIME("now", "localtime"));')
+ self.db.execute (sqlstring, ("create-time", ))
+ self.db.commit ()
+ except:
+ import traceback
+ traceback.print_exc ()
+
+ def init_user_db(self, db_file):
+ if not path.exists(db_file):
+ db = sqlite3.connect(db_file)
+ # 20000 pages should be enough to cache the whole database
+ db.executescript('''
+ PRAGMA encoding = "UTF-8";
+ PRAGMA case_sensitive_like = true;
+ PRAGMA page_size = 4096;
+ PRAGMA cache_size = 20000;
+ PRAGMA temp_store = MEMORY;
+ PRAGMA journal_mode = WAL;
+ PRAGMA journal_size_limit = 1000000;
+ PRAGMA synchronous = NORMAL;
+ ''')
+ db.commit()
+
+ def get_database_desc (self, db_file):
+ if not path.exists (db_file):
+ return None
+ try:
+ db = sqlite3.connect (db_file)
+ desc = {}
+ for row in db.execute ("SELECT * FROM desc;").fetchall():
+ desc [row[0]] = row[1]
+ db.close()
+ return desc
+ except:
+ return None
+
+ def get_number_of_columns_of_phrase_table(self, db_file):
+ '''
+ Get the number of columns in the 'phrases' table in
+ the database in db_file.
+
+ Determines the number of columns by parsing this:
+
+ sqlite> select sql from sqlite_master where name='phrases';
+ CREATE TABLE phrases
+ (id INTEGER PRIMARY KEY, tabkeys TEXT, phrase TEXT,
+ freq INTEGER, user_freq INTEGER)
+ sqlite>
+
+ This result could be on a single line, as above, or on multiple
+ lines.
+ '''
+ if not path.exists (db_file):
+ return 0
+ try:
+ db = sqlite3.connect (db_file)
+ tp_res = db.execute(
+ "select sql from sqlite_master where name='phrases';"
+ ).fetchall()
+ # Remove possible line breaks from the string where we
+ # want to match:
+ string = ' '.join(tp_res[0][0].splitlines())
+ res = re.match(r'.*\((.*)\)', string)
+ if res:
+ tp = res.group(1).split(',')
+ return len(tp)
+ else:
+ return 0
+ except:
+ return 0
+
+ def get_goucima (self, zi):
+ '''Get goucima of given character'''
+ if not zi:
+ return u''
+ sqlstr = 'SELECT goucima FROM main.goucima WHERE zi = :zi;'
+ results = self.db.execute(sqlstr, {'zi': zi}).fetchall()
+ if results:
+ goucima = results[0][0]
+ else:
+ goucima = u''
+ if debug_level > 1:
+ sys.stderr.write("get_goucima() goucima=%s\n" %goucima)
+ return goucima
+
+ def parse_phrase (self, phrase):
+ '''Parse phrase to get its table code
+
+ Example:
+
+ Lets assume we use wubi-jidian86. The rules in the source of
+ that table are:
+
+ RULES = ce2:p11+p12+p21+p22;ce3:p11+p21+p31+p32;ca4:p11+p21+p31+p-11
+
+ “ce2” is a rule for phrases of length 2, “ce3” is a rule
+ for phrases of length 3, “ca4” is a rule for phrases of
+ length 4 *and* for all phrases with a length greater then
+ 4. “pnm” in such a rule means to use the n-th character of
+ the phrase and take the m-th character of the table code of
+ that character. I.e. “p-11” is the first character of the
+ table code of the last character in the phrase.
+
+ Lets assume the phrase is “天下大事”. The goucima (構詞碼
+ = “word formation keys”) for these 4 characters are:
+
+ character goucima
+ 天 gdi
+ 下 ghi
+ 大 dddd
+ 事 gkvh
+
+ (If no special goucima are defined by the user, the longest
+ encoding for a single character in a table is the goucima for
+ that character).
+
+ The length of the phrase “天下大事” is 4 characters,
+ therefore the rule ca4:p11+p21+p31+p-11 applies, i.e. the
+ table code for “天下大事” is calculated by using the first,
+ second, third and last character of the phrase and taking the
+ first character of the goucima for each of these. Therefore,
+ the table code for “天下大事” is “ggdg”.
+
+ '''
+ if debug_level > 1:
+ sys.stderr.write(
+ 'parse_phrase() phrase=%(p)s rules%(r)s\n'
+ % {'p': phrase, 'r': self.rules})
+ if type(phrase) != type(u''):
+ phrase = phrase.decode('UTF-8')
+ # Shouldnt this function try first whether the system database
+ # already has an entry for this phrase and if yes return it
+ # instead of constructing a new entry according to the rules?
+ # And construct a new entry only when no entry already exists
+ # in the system database??
+ if len(phrase) == 0:
+ return u''
+ if len(phrase) == 1:
+ return self.get_goucima(phrase)
+ if not self.rules:
+ return u''
+ if len(phrase) in self.rules:
+ rule = self.rules[len(phrase)]
+ elif len(phrase) > self.rules['above']:
+ rule = self.rules[self.rules['above']]
+ else:
+ sys.stderr.write(
+ 'No rule for this phrase length. phrase=%(p)s rules=%(r)s\n'
+ %{'p': phrase, 'r': self.rules})
+ return u''
+ if len(rule) > self._mlen:
+ sys.stderr.write(
+ 'Rule exceeds maximum key length. rule=%(r)s self._mlen=%(m)s\n'
+ %{'r': rule, 'm': self._mlen})
+ return u''
+ tabkeys = u''
+ for (zi, ma) in rule:
+ if zi > 0:
+ zi -= 1
+ if ma > 0:
+ ma -= 1
+ tabkey = self.get_goucima(phrase[zi])[ma]
+ if not tabkey:
+ return u''
+ tabkeys += tabkey
+ if debug_level > 1:
+ sys.stderr.write("parse_phrase() tabkeys=%s\n" %tabkeys)
+ return tabkeys
+
+ def is_in_system_database(self, tabkeys=u'', phrase=u''):
+ '''
+ Checks whether “phrase” can be matched in the system database
+ with a key sequence *starting* with “tabkeys”.
+ '''
+ if debug_level > 1:
+ sys.stderr.write(
+ 'is_in_system_database() tabkeys=%(t)s phrase=%(p)s\n'
+ % {'t': tabkeys, 'p': phrase})
+ if not tabkeys or not phrase:
+ return False
+ sqlstr = '''
+ SELECT * FROM main.phrases
+ WHERE tabkeys LIKE :tabkeys AND phrase = :phrase;
+ '''
+ sqlargs = {'tabkeys': tabkeys+'%%', 'phrase': phrase}
+ results = self.db.execute(sqlstr, sqlargs).fetchall()
+ if debug_level > 1:
+ sys.stderr.write(
+ 'is_in_system_database() tabkeys=%(t)s phrase=%(p)s '
+ % {'t': tabkeys, 'p': phrase}
+ + 'results=%(r)s\n'
+ % {'r': results})
+ if results:
+ return True
+ else:
+ return False
+
+ def user_frequency(self, tabkeys=u'', phrase=u''):
+ if debug_level > 1:
+ sys.stderr.write(
+ 'user_frequency() tabkeys=%(t)s phrase=%(p)s\n'
+ % {'t': tabkeys, 'p': phrase})
+ if not tabkeys or not phrase:
+ return 0
+ sqlstr = '''
+ SELECT sum(user_freq) FROM user_db.phrases
+ WHERE tabkeys = :tabkeys AND phrase = :phrase GROUP BY tabkeys, phrase;
+ '''
+ sqlargs = {'tabkeys': tabkeys, 'phrase': phrase}
+ result = self.db.execute(sqlstr, sqlargs).fetchall()
+ if debug_level > 1:
+ sys.stderr.write("user_frequency() result=%s\n" %result)
+ if result:
+ return result[0][0]
+ else:
+ return 0
+
+ def check_phrase(self, tabkeys=u'', phrase=u''):
+ '''Adjust user_freq in user database if necessary.
+
+ Also, if the phrase is not in the system database, and it is a
+ Chinese table, and defining user phrases is allowed, add it as
+ a user defined phrase to the user database if it is not yet
+ there.
+ '''
+ if debug_level > 1:
+ sys.stderr.write(
+ 'check_phrase_internal() tabkey=%(t)s phrase=%(p)s\n'
+ % {'t': tabkeys, 'p': phrase})
+ if type(phrase) != type(u''):
+ phrase = phrase.decode('utf8')
+ if type(tabkeys) != type(u''):
+ tabkeys = tabkeys.decode('utf8')
+ if not tabkeys or not phrase:
+ return
+ if self._is_chinese and phrase in chinese_nocheck_chars:
+ return
+ if not self.dynamic_adjust:
+ if not self.user_can_define_phrase or not self.is_chinese:
+ return
+ tabkeys = self.parse_phrase(phrase)
+ if not tabkeys:
+ # no tabkeys could be constructed from the rules in the table
+ return
+ if self.is_in_system_database(tabkeys=tabkeys, phrase=phrase):
+ # if it is in the system database, it does not need to
+ # be defined
+ return
+ if self.user_frequency(tabkeys=tabkeys, phrase=phrase) > 0:
+ # if it is in the user database, it has been defined before
+ return
+ # add this user defined phrase to the user database:
+ self.add_phrase(
+ tabkeys=tabkeys, phrase=phrase, freq=-1, user_freq=1,
+ database='user_db')
+ else:
+ if self.is_in_system_database(tabkeys=tabkeys, phrase=phrase):
+ user_freq = self.user_frequency(tabkeys=tabkeys, phrase=phrase)
+ if user_freq > 0:
+ self.update_phrase(
+ tabkeys=tabkeys, phrase=phrase, user_freq=user_freq+1)
+ else:
+ self.add_phrase(
+ tabkeys=tabkeys, phrase=phrase, freq=0, user_freq=1,
+ database='user_db')
+ else:
+ if not self.user_can_define_phrase or not self.is_chinese:
+ return
+ tabkeys = self.parse_phrase(phrase)
+ if not tabkeys:
+ # no tabkeys could be constructed from the rules
+ # in the table
+ return
+ user_freq = self.user_frequency(tabkeys=tabkeys, phrase=phrase)
+ if user_freq > 0:
+ self.update_phrase(
+ tabkeys=tabkeys, phrase=phrase, user_freq=user_freq+1)
+ else:
+ self.add_phrase(
+ tabkeys=tabkeys, phrase=phrase, freq=-1, user_freq=1,
+ database='user_db')
+
+ def find_zi_code (self, phrase):
+ '''
+ Return the list of possible tabkeys for a phrase.
+
+ For example, if “phrase” is “你” and the table is wubi-jidian.86.txt,
+ the result will be ['wq', 'wqi', 'wqiy'] because that table
+ contains the following 3 lines matching that phrase exactly:
+
+ wq 你 597727619
+ wqi 你 1490000000
+ wqiy 你 1490000000
+ '''
+ if type(phrase) != type(u''):
+ phrase = phrase.decode('utf8')
+ sqlstr = '''
+ SELECT tabkeys FROM main.phrases WHERE phrase = :phrase
+ ORDER by length(tabkeys) ASC;
+ '''
+ sqlargs = {'phrase': phrase}
+ results = self.db.execute(sqlstr, sqlargs).fetchall()
+ list_of_possible_tabkeys = [x[0] for x in results]
+ return list_of_possible_tabkeys
+
+ def remove_phrase (
+ self, tabkeys=u'', phrase=u'', database='user_db', commit=True):
+ '''Remove phrase from database
+ '''
+ if not phrase:
+ return
+ if tabkeys:
+ delete_sqlstr = '''
+ DELETE FROM %(database)s.phrases
+ WHERE tabkeys = :tabkeys AND phrase = :phrase;
+ ''' % {'database': database}
+ else:
+ delete_sqlstr = '''
+ DELETE FROM %(database)s.phrases
+ WHERE phrase = :phrase;
+ ''' % {'database': database}
+ delete_sqlargs = {'tabkeys': tabkeys, 'phrase': phrase}
+ self.db.execute(delete_sqlstr, delete_sqlargs)
+ if commit:
+ self.db.commit()
+ self.invalidate_phrases_cache(tabkeys)
+
+ def extract_user_phrases(
+ self, database_file='', old_database_version='0.0'):
+ '''extract user phrases from database'''
+ sys.stderr.write(
+ 'Trying to recover the phrases from the old, '
+ + 'incompatible database.\n')
+ try:
+ db = sqlite3.connect(database_file)
+ db.execute('PRAGMA wal_checkpoint;')
+ if old_database_version >= '1.00':
+ phrases = db.execute(
+ '''
+ SELECT tabkeys, phrase, freq, sum(user_freq) FROM phrases
+ GROUP BY tabkeys, phrase, freq;
+ '''
+ ).fetchall()
+ db.close()
+ phrases = sorted(
+ phrases, key=lambda x: (x[0], x[1], x[2], x[3]))
+ sys.stderr.write(
+ 'Recovered phrases from the old database: phrases=%s\n'
+ % repr(phrases))
+ return phrases[:]
+ else:
+ # database is very old, it may still use many columns
+ # of type INTEGER for the tabkeys. Therefore, ignore
+ # the tabkeys in the database and try to get them
+ # from the system database instead.
+ phrases = []
+ results = db.execute(
+ 'SELECT phrase, sum(user_freq) '
+ + 'FROM phrases GROUP BY phrase;'
+ ).fetchall()
+ for result in results:
+ sqlstr = '''
+ SELECT tabkeys FROM main.phrases WHERE phrase = :phrase
+ ORDER BY length(tabkeys) DESC;
+ '''
+ sqlargs = {'phrase': result[0]}
+ tabkeys_results = self.db.execute(
+ sqlstr, sqlargs).fetchall()
+ if tabkeys_results:
+ phrases.append(
+ (tabkeys_results[0][0], result[0], 0, result[1]))
+ else:
+ # No tabkeys for that phrase could not be
+ # found in the system database. Try to get
+ # tabkeys by calling self.parse_phrase(), that
+ # might return something if the table has
+ # rules to construct user defined phrases:
+ tabkeys = self.parse_phrase(result[0])
+ if tabkeys:
+ # for user defined phrases, the “freq” column is -1:
+ phrases.append((tabkeys, result[0], -1, result[1]))
+ db.close()
+ phrases = sorted(
+ phrases, key=lambda x: (x[0], x[1], x[2], x[3]))
+ sys.stderr.write(
+ 'Recovered phrases from the very old database: phrases=%s\n'
+ % repr(phrases))
+ return phrases[:]
+ except:
+ import traceback
+ traceback.print_exc()
+ return []
diff -Nru ibus-table-1.9.18.orig/setup/main.py ibus-table-1.9.18/setup/main.py
--- ibus-table-1.9.18.orig/setup/main.py 2020-07-22 11:52:11.654532080 +0200
+++ ibus-table-1.9.18/setup/main.py 2020-07-22 14:43:51.908260925 +0200
@@ -62,7 +62,7 @@
"endeffullwidthletter": False,
"endeffullwidthpunct": False,
"alwaysshowlookup": True,
- "lookuptableorientation": True,
+ "lookuptableorientation": 1, # 0 = horizontal, 1 = vertical, 2 = system
"lookuptablepagesize": 6,
"onechar": False,
"autoselect": False,
@@ -224,12 +224,8 @@
and type(auto_commit) == type(u'')
and auto_commit.lower() in [u'true', u'false']):
OPTION_DEFAULTS['autocommit'] = auto_commit.lower() == u'true'
- orientation = self.tabsqlitedb.ime_properties.get('orientation')
- if (orientation
- and type(orientation) == type(u'')
- and orientation.lower() in [u'true', u'false']):
- OPTION_DEFAULTS['lookuptableorientation'] = (
- orientation.lower() == u'true')
+ orientation = self.tabsqlitedb.get_orientation()
+ OPTION_DEFAULTS['lookuptableorientation'] = orientation
# if space is a page down key, set the option
# “spacekeybehavior” to “True”:
page_down_keys_csv = self.tabsqlitedb.ime_properties.get(
@@ -258,14 +254,16 @@
single_wildcard_char = self.tabsqlitedb.ime_properties.get(
'single_wildcard_char')
if (single_wildcard_char
- and type(single_wildcard_char) == type(u'')
- and len(single_wildcard_char) == 1):
+ and type(single_wildcard_char) == type(u'')):
+ if len(single_wildcard_char) > 1:
+ single_wildcard_char = single_wildcard_char[0]
OPTION_DEFAULTS['singlewildcardchar'] = single_wildcard_char
multi_wildcard_char = self.tabsqlitedb.ime_properties.get(
'multi_wildcard_char')
if (multi_wildcard_char
- and type(multi_wildcard_char) == type(u'')
- and len(multi_wildcard_char) == 1):
+ and type(multi_wildcard_char) == type(u'')):
+ if len(multi_wildcard_char) > 1:
+ multi_wildcard_char = multi_wildcard_char[0]
OPTION_DEFAULTS['multiwildcardchar'] = multi_wildcard_char
def __restore_defaults(self):
diff -Nru ibus-table-1.9.18.orig/tests/.gitignore ibus-table-1.9.18/tests/.gitignore
--- ibus-table-1.9.18.orig/tests/.gitignore 1970-01-01 01:00:00.000000000 +0100
+++ ibus-table-1.9.18/tests/.gitignore 2020-07-22 14:43:51.908260925 +0200
@@ -0,0 +1,5 @@
+run_tests
+run_tests.log
+run_tests.trs
+test-suite.log
+__pycache__/
diff -Nru ibus-table-1.9.18.orig/tests/Makefile.am ibus-table-1.9.18/tests/Makefile.am
--- ibus-table-1.9.18.orig/tests/Makefile.am 1970-01-01 01:00:00.000000000 +0100
+++ ibus-table-1.9.18/tests/Makefile.am 2020-07-22 14:43:51.908260925 +0200
@@ -0,0 +1,41 @@
+# vim:set noet ts=4
+#
+# ibus-table - The Tables engine for IBus
+#
+# Copyright (c) 2018 Mike FABIAN <mfabian@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TESTS = run_tests
+
+run_tests: run_tests.in
+ sed -e 's&@PYTHON_BIN@&$(PYTHON)&g' \
+ -e 's&@SRCDIR@&$(srcdir)&g' $< > $@
+ chmod +x $@
+
+EXTRA_DIST = \
+ run_tests.in \
+ test_it.py \
+ __init__.py \
+ $(NULL)
+
+CLEANFILES = \
+ run_tests \
+ $(NULL)
+
+MAINTAINERCLEANFILES = \
+ Makefile.in \
+ $(NULL)
diff -Nru ibus-table-1.9.18.orig/tests/run_tests.in ibus-table-1.9.18/tests/run_tests.in
--- ibus-table-1.9.18.orig/tests/run_tests.in 1970-01-01 01:00:00.000000000 +0100
+++ ibus-table-1.9.18/tests/run_tests.in 2020-07-22 14:43:51.908260925 +0200
@@ -0,0 +1,254 @@
+#!/usr/bin/python3
+
+# ibus-table - The Tables engine for IBus
+#
+# Copyright (c) 2018 Mike FABIAN <mfabian@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import os
+import sys
+import unittest
+
+from gi import require_version
+require_version('IBus', '1.0')
+from gi.repository import IBus
+
+# -- Define some mock classes for the tests ----------------------------------
+class MockEngine:
+ def __init__(self, engine_name = '', connection = None, object_path = ''):
+ self.mock_auxiliary_text = ''
+ self.mock_preedit_text = ''
+ self.mock_preedit_text_cursor_pos = 0
+ self.mock_preedit_text_visible = True
+ self.mock_committed_text = ''
+ self.mock_committed_text_cursor_pos = 0
+ self.client_capabilities = (
+ IBus.Capabilite.PREEDIT_TEXT
+ | IBus.Capabilite.AUXILIARY_TEXT
+ | IBus.Capabilite.LOOKUP_TABLE
+ | IBus.Capabilite.FOCUS
+ | IBus.Capabilite.PROPERTY)
+ # There are lots of weird problems with surrounding text
+ # which makes this hard to test. Therefore this mock
+ # engine does not try to support surrounding text, i.e.
+ # we omit “| IBus.Capabilite.SURROUNDING_TEXT” here.
+
+ def update_auxiliary_text(self, text, visible):
+ self.mock_auxiliary_text = text.text
+
+ def hide_auxiliary_text(self):
+ pass
+
+ def commit_text(self, text):
+ self.mock_committed_text = (
+ self.mock_committed_text[
+ :self.mock_committed_text_cursor_pos]
+ + text.text
+ + self.mock_committed_text[
+ self.mock_committed_text_cursor_pos:])
+ self.mock_committed_text_cursor_pos += len(text.text)
+
+ def forward_key_event(self, val, code, state):
+ if (val == IBus.KEY_Left
+ and self.mock_committed_text_cursor_pos > 0):
+ self.mock_committed_text_cursor_pos -= 1
+ return
+ unicode = IBus.keyval_to_unicode(val)
+ if unicode:
+ self.mock_committed_text = (
+ self.mock_committed_text[
+ :self.mock_committed_text_cursor_pos]
+ + unicode
+ + self.mock_committed_text[
+ self.mock_committed_text_cursor_pos:])
+ self.mock_committed_text_cursor_pos += len(unicode)
+
+ def update_lookup_table(self, table, visible):
+ pass
+
+ def update_preedit_text(self, text, cursor_pos, visible):
+ self.mock_preedit_text = text.get_text()
+ self.mock_preedit_text_cursor_pos = cursor_pos
+ self.mock_preedit_text_visible = visible
+
+ def register_properties(self, property_list):
+ pass
+
+ def update_property(self, property):
+ pass
+
+ def hide_lookup_table(self):
+ pass
+
+class MockLookupTable:
+ def __init__(self, page_size = 9, cursor_pos = 0, cursor_visible = False, round = True):
+ self.clear()
+ self.mock_page_size = page_size
+ self.mock_cursor_pos = cursor_pos
+ self.mock_cursor_visible = cursor_visible
+ self.cursor_visible = cursor_visible
+ self.mock_round = round
+ self.mock_candidates = []
+ self.mock_labels = []
+ self.mock_page_number = 0
+
+ def clear(self):
+ self.mock_candidates = []
+ self.mock_cursor_pos = 0
+
+ def set_page_size(self, size):
+ self.mock_page_size = size
+
+ def get_page_size(self):
+ return self.mock_page_size
+
+ def set_round(self, round):
+ self.mock_round = round
+
+ def set_cursor_pos(self, pos):
+ self.mock_cursor_pos = pos
+
+ def get_cursor_pos(self):
+ return self.mock_cursor_pos
+
+ def get_cursor_in_page(self):
+ return (self.mock_cursor_pos
+ - self.mock_page_size * self.mock_page_number)
+
+ def set_cursor_visible(self, visible):
+ self.mock_cursor_visible = visible
+ self.cursor_visible = visible
+
+ def cursor_down(self):
+ if len(self.mock_candidates):
+ self.mock_cursor_pos += 1
+ self.mock_cursor_pos %= len(self.mock_candidates)
+
+ def cursor_up(self):
+ if len(self.mock_candidates):
+ if self.mock_cursor_pos > 0:
+ self.mock_cursor_pos -= 1
+ else:
+ self.mock_cursor_pos = len(self.mock_candidates) - 1
+
+ def page_down(self):
+ if len(self.mock_candidates):
+ self.mock_page_number += 1
+ self.mock_cursor_pos += self.mock_page_size
+
+ def page_up(self):
+ if len(self.mock_candidates):
+ if self.mock_page_number > 0:
+ self.mock_page_number -= 1
+ self.mock_cursor_pos -= self.mock_page_size
+
+ def set_orientation(self, orientation):
+ self.mock_orientation = orientation
+
+ def get_number_of_candidates(self):
+ return len(self.mock_candidates)
+
+ def append_candidate(self, candidate):
+ self.mock_candidates.append(candidate.get_text())
+
+ def get_candidate(self, index):
+ return self.mock_candidates[index]
+
+ def get_number_of_candidates(self):
+ return len(self.mock_candidates)
+
+ def append_label(self, label):
+ self.mock_labels.append(label.get_text())
+
+class MockPropList:
+ def __init__(self, *args, **kwargs):
+ self._mock_proplist = []
+
+ def append(self, property):
+ self._mock_proplist.append(property)
+
+ def get(self, index):
+ if index >= 0 and index < len(self._mock_proplist):
+ return self._mock_proplist[index]
+ else:
+ return None
+
+ def update_property(self, property):
+ pass
+
+class MockProperty:
+ def __init__(self,
+ key='',
+ prop_type=IBus.PropType.RADIO,
+ label=IBus.Text.new_from_string(''),
+ symbol=IBus.Text.new_from_string(''),
+ icon='',
+ tooltip=IBus.Text.new_from_string(''),
+ sensitive=True,
+ visible=True,
+ state=IBus.PropState.UNCHECKED,
+ sub_props=None):
+ self.mock_property_key = key
+ self.mock_property_prop_type = prop_type
+ self.mock_property_label = label.get_text()
+ self.mock_property_symbol = symbol.get_text()
+ self.mock_property_icon = icon
+ self.mock_property_tooltip = tooltip.get_text()
+ self.mock_property_sensitive = sensitive
+ self.mock_property_visible = visible
+ self.mock_property_state = state
+ self.mock_property_sub_props = sub_props
+
+ def set_label(self, ibus_text):
+ self.mock_property_label = ibus_text.get_text()
+
+ def set_symbol(self, ibus_text):
+ self.mock_property_symbol = ibus_text.get_text()
+
+ def set_tooltip(self, ibus_text):
+ self.mock_property_tooltip = ibus_text.get_text()
+
+ def set_sensitive(self, sensitive):
+ self.mock_property_sensitive = sensitive
+
+ def set_visible(self, visible):
+ self.mock_property_visible = visible
+
+ def set_state(self, state):
+ self.mock_property_state = state
+
+ def set_sub_props(self, proplist):
+ self.mock_property_sub_props = proplist
+
+ def get_key(self):
+ return self.mock_property_key
+
+# -- Monkey patch the environment with the mock classes ----------------------
+sys.modules["gi.repository.IBus"].Engine = MockEngine
+sys.modules["gi.repository.IBus"].LookupTable = MockLookupTable
+sys.modules["gi.repository.IBus"].Property = MockProperty
+sys.modules["gi.repository.IBus"].PropList = MockPropList
+
+# -- Load and run our unit tests ---------------------------------------------
+os.environ['IBUS_TABLE_DEBUG_LEVEL'] = '255'
+loader = unittest.TestLoader()
+suite = loader.discover(".")
+runner = unittest.TextTestRunner(stream = sys.stderr, verbosity = 255)
+result = runner.run(suite)
+
+if result.failures or result.errors:
+ sys.exit(1)
diff -Nru ibus-table-1.9.18.orig/tests/test_it.py ibus-table-1.9.18/tests/test_it.py
--- ibus-table-1.9.18.orig/tests/test_it.py 1970-01-01 01:00:00.000000000 +0100
+++ ibus-table-1.9.18/tests/test_it.py 2020-07-22 14:43:51.908260925 +0200
@@ -0,0 +1,403 @@
+# -*- coding: utf-8 -*-
+# vim:et sts=4 sw=4
+#
+# ibus-table - The Tables engine for IBus
+#
+# Copyright (c) 2018 Mike FABIAN <mfabian@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+'''
+This file implements the test cases for the unit tests of ibus-table
+'''
+
+import sys
+import os
+import unicodedata
+import unittest
+import subprocess
+
+from gi import require_version
+require_version('IBus', '1.0')
+from gi.repository import IBus
+
+sys.path.insert(0, "../engine")
+from table import *
+import tabsqlitedb
+import it_util
+#sys.path.pop(0)
+
+ENGINE = None
+TABSQLITEDB = None
+ORIG_INPUT_MODE = None
+ORIG_CHINESE_MODE = None
+ORIG_LETTER_WIDTH = None
+ORIG_PUNCTUATION_WIDTH = None
+ORIG_ALWAYS_SHOW_LOOKUP = None
+ORIG_LOOKUP_TABLE_ORIENTATION = None
+ORIG_PAGE_SIZE = None
+ORIG_ONECHAR_MODE = None
+ORIG_AUTOSELECT_MODE = None
+ORIG_AUTOCOMMIT_MODE = None
+ORIG_SPACE_KEY_BEHAVIOR_MODE = None
+ORIG_AUTOWILDCARD_MODE = None
+ORIG_SINGLE_WILDCARD_CHAR = None
+ORIG_MULTI_WILDCARD_CHAR = None
+
+def backup_original_settings():
+ global ENGINE
+ global ORIG_INPUT_MODE
+ global ORIG_CHINESE_MODE
+ global ORIG_LETTER_WIDTH
+ global ORIG_PUNCTUATION_WIDTH
+ global ORIG_ALWAYS_SHOW_LOOKUP
+ global ORIG_LOOKUP_TABLE_ORIENTATION
+ global ORIG_PAGE_SIZE
+ global ORIG_ONECHAR_MODE
+ global ORIG_AUTOSELECT_MODE
+ global ORIG_AUTOCOMMIT_MODE
+ global ORIG_SPACE_KEY_BEHAVIOR_MODE
+ global ORIG_AUTOWILDCARD_MODE
+ global ORIG_SINGLE_WILDCARD_CHAR
+ global ORIG_MULTI_WILDCARD_CHAR
+ ORIG_INPUT_MODE = ENGINE.get_input_mode()
+ ORIG_CHINESE_MODE = ENGINE.get_chinese_mode()
+ ORIG_LETTER_WIDTH = ENGINE.get_letter_width()
+ ORIG_PUNCTUATION_WIDTH = ENGINE.get_punctuation_width()
+ ORIG_ALWAYS_SHOW_LOOKUP = ENGINE.get_always_show_lookup()
+ ORIG_LOOKUP_TABLE_ORIENTATION = ENGINE.get_lookup_table_orientation()
+ ORIG_PAGE_SIZE = ENGINE.get_page_size()
+ ORIG_ONECHAR_MODE = ENGINE.get_onechar_mode()
+ ORIG_AUTOSELECT_MODE = ENGINE.get_autoselect_mode()
+ ORIG_AUTOCOMMIT_MODE = ENGINE.get_autocommit_mode()
+ ORIG_SPACE_KEY_BEHAVIOR_MODE = ENGINE.get_space_key_behavior_mode()
+ ORIG_AUTOWILDCARD_MODE = ENGINE.get_autowildcard_mode()
+ ORIG_SINGLE_WILDCARD_CHAR = ENGINE.get_single_wildcard_char()
+ ORIG_MULTI_WILDCARD_CHAR = ENGINE.get_multi_wildcard_char()
+
+def restore_original_settings():
+ global ENGINE
+ global ORIG_INPUT_MODE
+ global ORIG_CHINESE_MODE
+ global ORIG_LETTER_WIDTH
+ global ORIG_PUNCTUATION_WIDTH
+ global ORIG_ALWAYS_SHOW_LOOKUP
+ global ORIG_LOOKUP_TABLE_ORIENTATION
+ global ORIG_PAGE_SIZE
+ global ORIG_ONECHAR_MODE
+ global ORIG_AUTOSELECT_MODE
+ global ORIG_AUTOCOMMIT_MODE
+ global ORIG_SPACE_KEY_BEHAVIOR_MODE
+ global ORIG_AUTOWILDCARD_MODE
+ global ORIG_SINGLE_WILDCARD_CHAR
+ global ORIG_MULTI_WILDCARD_CHAR
+ ENGINE.set_input_mode(ORIG_INPUT_MODE)
+ ENGINE.set_chinese_mode(ORIG_CHINESE_MODE)
+ ENGINE.set_letter_width(ORIG_LETTER_WIDTH[0], input_mode=0)
+ ENGINE.set_letter_width(ORIG_LETTER_WIDTH[1], input_mode=1)
+ ENGINE.set_punctuation_width(ORIG_PUNCTUATION_WIDTH[0], input_mode=0)
+ ENGINE.set_punctuation_width(ORIG_PUNCTUATION_WIDTH[1], input_mode=1)
+ ENGINE.set_always_show_lookup(ORIG_ALWAYS_SHOW_LOOKUP)
+ ENGINE.set_lookup_table_orientation(ORIG_LOOKUP_TABLE_ORIENTATION)
+ ENGINE.set_page_size(ORIG_PAGE_SIZE)
+ ENGINE.set_onechar_mode(ORIG_ONECHAR_MODE)
+ ENGINE.set_autoselect_mode(ORIG_AUTOSELECT_MODE)
+ ENGINE.set_autocommit_mode(ORIG_AUTOCOMMIT_MODE)
+ ENGINE.set_space_key_behavior_mode(ORIG_SPACE_KEY_BEHAVIOR_MODE)
+ ENGINE.set_autowildcard_mode(ORIG_AUTOWILDCARD_MODE)
+ ENGINE.set_single_wildcard_char(ORIG_SINGLE_WILDCARD_CHAR)
+ ENGINE.set_multi_wildcard_char(ORIG_MULTI_WILDCARD_CHAR)
+
+def set_default_settings():
+ global ENGINE
+ global TABSQLITEDB
+ ENGINE.set_input_mode(mode=1)
+ chinese_mode = 0
+ language_filter = TABSQLITEDB.ime_properties.get('language_filter')
+ if language_filter in ['cm0', 'cm1', 'cm2', 'cm3', 'cm4']:
+ chinese_mode = int(language_filter[-1])
+ ENGINE.set_chinese_mode(mode=chinese_mode)
+
+ letter_width_mode = False
+ def_full_width_letter = TABSQLITEDB.ime_properties.get(
+ 'def_full_width_letter')
+ if def_full_width_letter:
+ letter_width_mode = (def_full_width_letter.lower() == u'true')
+ ENGINE.set_letter_width(mode=letter_width_mode, input_mode=0)
+ ENGINE.set_letter_width(mode=letter_width_mode, input_mode=1)
+
+ punctuation_width_mode = False
+ def_full_width_punct = TABSQLITEDB.ime_properties.get(
+ 'def_full_width_punct')
+ if def_full_width_punct:
+ punctuation_width_mode = (def_full_width_punct.lower() == u'true')
+ ENGINE.set_punctuation_width(mode=punctuation_width_mode, input_mode=0)
+ ENGINE.set_punctuation_width(mode=punctuation_width_mode, input_mode=1)
+
+ always_show_lookup_mode = True
+ always_show_lookup = TABSQLITEDB.ime_properties.get(
+ 'always_show_lookup')
+ if always_show_lookup:
+ always_show_lookup_mode = (always_show_lookup.lower() == u'true')
+ ENGINE.set_always_show_lookup(always_show_lookup_mode)
+
+ orientation = TABSQLITEDB.get_orientation()
+ ENGINE.set_lookup_table_orientation(orientation)
+
+ page_size = 6
+ select_keys_csv = TABSQLITEDB.ime_properties.get('select_keys')
+ # select_keys_csv is something like: "1,2,3,4,5,6,7,8,9,0"
+ if select_keys_csv:
+ ENGINE.set_page_size(len(select_keys_csv.split(",")))
+
+ onechar = False
+ ENGINE.set_onechar_mode(onechar)
+
+ auto_select_mode = False
+ auto_select = TABSQLITEDB.ime_properties.get('auto_select')
+ if auto_select:
+ auto_select_mode = (auto_select.lower() == u'true')
+ ENGINE.set_autoselect_mode(auto_select_mode)
+
+ auto_commit_mode = False
+ auto_commit = TABSQLITEDB.ime_properties.get('auto_commit')
+ if auto_commit:
+ auto_commit_mode = (auto_commit.lower() == u'true')
+ ENGINE.set_autocommit_mode(auto_commit_mode)
+
+ space_key_behavior_mode = False
+ # if space is a page down key, set the option
+ # “spacekeybehavior” to “True”:
+ page_down_keys_csv = TABSQLITEDB.ime_properties.get(
+ 'page_down_keys')
+ if page_down_keys_csv:
+ page_down_keys = [
+ IBus.keyval_from_name(x)
+ for x in page_down_keys_csv.split(',')]
+ if IBus.KEY_space in page_down_keys:
+ space_key_behavior_mode = True
+ # if space is a commit key, set the option
+ # “spacekeybehavior” to “False” (overrides if space is
+ # also a page down key):
+ commit_keys_csv = TABSQLITEDB.ime_properties.get('commit_keys')
+ if commit_keys_csv:
+ commit_keys = [
+ IBus.keyval_from_name(x)
+ for x in commit_keys_csv.split(',')]
+ if IBus.KEY_space in commit_keys:
+ space_key_behavior_mode = False
+ ENGINE.set_space_key_behavior_mode(space_key_behavior_mode)
+
+ auto_wildcard_mode = True
+ auto_wildcard = TABSQLITEDB.ime_properties.get('auto_wildcard')
+ if auto_wildcard:
+ auto_wildcard_mode = (auto_wildcard.lower() == u'true')
+ ENGINE.set_autowildcard_mode(auto_wildcard_mode)
+
+ single_wildcard_char = TABSQLITEDB.ime_properties.get(
+ 'single_wildcard_char')
+ if not single_wildcard_char:
+ single_wildcard_char = u''
+ if len(single_wildcard_char) > 1:
+ single_wildcard_char = single_wildcard_char[0]
+ ENGINE.set_single_wildcard_char(single_wildcard_char)
+
+ multi_wildcard_char = TABSQLITEDB.ime_properties.get(
+ 'multi_wildcard_char')
+ if not multi_wildcard_char:
+ multi_wildcard_char = u''
+ if len(multi_wildcard_char) > 1:
+ multi_wildcard_char = multi_wildcard_char[0]
+ ENGINE.set_multi_wildcard_char(multi_wildcard_char)
+
+def set_up(engine_name):
+ global TABSQLITEDB
+ global ENGINE
+ bus = IBus.Bus()
+ db_dir = '/usr/share/ibus-table/tables'
+ db_file = os.path.join(db_dir, engine_name + '.db')
+ TABSQLITEDB = tabsqlitedb.tabsqlitedb(
+ filename=db_file, user_db=':memory:')
+ ENGINE = tabengine(
+ bus,
+ '/com/redhat/IBus/engines/table/%s/engine/0' %engine_name,
+ TABSQLITEDB,
+ unit_test = True)
+ backup_original_settings()
+ set_default_settings()
+
+def tear_down():
+ restore_original_settings()
+
+class Wubi_Jidian86TestCase(unittest.TestCase):
+ def setUp(self):
+ set_up('wubi-jidian86')
+
+ def tearDown(self):
+ tear_down()
+
+ def test_dummy(self):
+ self.assertEqual(True, True)
+
+ def test_single_char_commit_with_space(self):
+ ENGINE.do_process_key_event(IBus.KEY_a, 0, 0)
+ ENGINE.do_process_key_event(IBus.KEY_space, 0, 0)
+ self.assertEqual(ENGINE.mock_committed_text, '工')
+
+ def test_commit_to_preedit_and_switching_to_pinyin_and_defining_a_phrase(self):
+ ENGINE.do_process_key_event(IBus.KEY_a, 0, 0)
+ # commit to preëdit needs a press and release of either
+ # the left or the right shift key:
+ ENGINE.do_process_key_event(
+ IBus.KEY_Shift_L, 0,
+ IBus.ModifierType.SHIFT_MASK)
+ ENGINE.do_process_key_event(
+ IBus.KEY_Shift_L, 0,
+ IBus.ModifierType.SHIFT_MASK | IBus.ModifierType.RELEASE_MASK)
+ self.assertEqual(ENGINE.mock_preedit_text, '工')
+ ENGINE.do_process_key_event(IBus.KEY_b, 0, 0)
+ ENGINE.do_process_key_event(
+ IBus.KEY_Shift_R, 0,
+ IBus.ModifierType.SHIFT_MASK)
+ ENGINE.do_process_key_event(
+ IBus.KEY_Shift_R, 0,
+ IBus.ModifierType.SHIFT_MASK | IBus.ModifierType.RELEASE_MASK)
+ self.assertEqual(ENGINE.mock_preedit_text, '工了')
+ ENGINE.do_process_key_event(IBus.KEY_c, 0, 0)
+ ENGINE.do_process_key_event(
+ IBus.KEY_Shift_R, 0,
+ IBus.ModifierType.SHIFT_MASK)
+ ENGINE.do_process_key_event(
+ IBus.KEY_Shift_R, 0,
+ IBus.ModifierType.SHIFT_MASK | IBus.ModifierType.RELEASE_MASK)
+ self.assertEqual(ENGINE.mock_preedit_text, '工了以')
+ ENGINE.do_process_key_event(IBus.KEY_d, 0, 0)
+ ENGINE.do_process_key_event(
+ IBus.KEY_Shift_L, 0,
+ IBus.ModifierType.SHIFT_MASK)
+ ENGINE.do_process_key_event(
+ IBus.KEY_Shift_L, 0,
+ IBus.ModifierType.SHIFT_MASK | IBus.ModifierType.RELEASE_MASK)
+ self.assertEqual(ENGINE.mock_preedit_text, '工了以在')
+ # Move left two characters in the preëdit:
+ ENGINE.do_process_key_event(IBus.KEY_Left, 0, 0)
+ ENGINE.do_process_key_event(IBus.KEY_Left, 0, 0)
+ # Switch to pinyin mode by pressing and releasing the right
+ # shift key:
+ ENGINE.do_process_key_event(
+ IBus.KEY_Shift_R, 0,
+ IBus.ModifierType.SHIFT_MASK)
+ ENGINE.do_process_key_event(
+ IBus.KEY_Shift_R, 0,
+ IBus.ModifierType.SHIFT_MASK | IBus.ModifierType.RELEASE_MASK)
+ ENGINE.do_process_key_event(IBus.KEY_n, 0, 0)
+ ENGINE.do_process_key_event(IBus.KEY_i, 0, 0)
+ ENGINE.do_process_key_event(IBus.KEY_numbersign, 0, 0)
+ ENGINE.do_process_key_event(
+ IBus.KEY_Shift_L, 0,
+ IBus.ModifierType.SHIFT_MASK)
+ ENGINE.do_process_key_event(
+ IBus.KEY_Shift_L, 0,
+ IBus.ModifierType.SHIFT_MASK | IBus.ModifierType.RELEASE_MASK)
+ self.assertEqual(ENGINE.mock_preedit_text, '工了你以在')
+ ENGINE.do_process_key_event(IBus.KEY_h, 0, 0)
+ ENGINE.do_process_key_event(IBus.KEY_a, 0, 0)
+ ENGINE.do_process_key_event(IBus.KEY_o, 0, 0)
+ ENGINE.do_process_key_event(IBus.KEY_numbersign, 0, 0)
+ ENGINE.do_process_key_event(
+ IBus.KEY_Shift_L, 0,
+ IBus.ModifierType.SHIFT_MASK)
+ ENGINE.do_process_key_event(
+ IBus.KEY_Shift_L, 0,
+ IBus.ModifierType.SHIFT_MASK | IBus.ModifierType.RELEASE_MASK)
+ self.assertEqual(ENGINE.mock_preedit_text, '工了你好以在')
+ # Move right two characters in the preëdit (triggers a commit to preëdit):
+ ENGINE.do_process_key_event(IBus.KEY_Right, 0, 0)
+ ENGINE.do_process_key_event(IBus.KEY_Right, 0, 0)
+ self.assertEqual(ENGINE.mock_auxiliary_text, 'd dhf dhfd\t#: abwd')
+ # commit the preëdit:
+ ENGINE.do_process_key_event(IBus.KEY_space, 0, 0)
+ self.assertEqual(ENGINE.mock_committed_text, '工了你好以在')
+ # Switch out of pinyin mode:
+ ENGINE.do_process_key_event(
+ IBus.KEY_Shift_R, 0,
+ IBus.ModifierType.SHIFT_MASK)
+ ENGINE.do_process_key_event(
+ IBus.KEY_Shift_R, 0,
+ IBus.ModifierType.SHIFT_MASK | IBus.ModifierType.RELEASE_MASK)
+ # “abwd” shown on the right of the auxiliary text above shows the
+ # newly defined shortcut for this phrase. Lets try to type
+ # the same phrase again using the new shortcut:
+ ENGINE.do_process_key_event(IBus.KEY_a, 0, 0)
+ self.assertEqual(ENGINE.mock_preedit_text, '工')
+ ENGINE.do_process_key_event(IBus.KEY_b, 0, 0)
+ self.assertEqual(ENGINE.mock_preedit_text, '节')
+ ENGINE.do_process_key_event(IBus.KEY_w, 0, 0)
+ self.assertEqual(ENGINE.mock_preedit_text, '工了你好以在')
+ ENGINE.do_process_key_event(IBus.KEY_d, 0, 0)
+ self.assertEqual(ENGINE.mock_preedit_text, '工了你好以在')
+ # commit the preëdit:
+ ENGINE.do_process_key_event(IBus.KEY_space, 0, 0)
+ self.assertEqual(ENGINE.mock_committed_text, '工了你好以在工了你好以在')
+
+class Stroke5TestCase(unittest.TestCase):
+ def setUp(self):
+ set_up('stroke5')
+
+ def tearDown(self):
+ tear_down()
+
+ def test_dummy(self):
+ self.assertEqual(True, True)
+
+ def test_single_char_commit_with_space(self):
+ ENGINE.do_process_key_event(IBus.KEY_comma, 0, 0)
+ ENGINE.do_process_key_event(IBus.KEY_slash, 0, 0)
+ ENGINE.do_process_key_event(IBus.KEY_n, 0, 0)
+ ENGINE.do_process_key_event(IBus.KEY_m, 0, 0)
+ ENGINE.do_process_key_event(IBus.KEY_m, 0, 0)
+ ENGINE.do_process_key_event(IBus.KEY_space, 0, 0)
+ self.assertEqual(ENGINE.mock_committed_text, '的')
+
+class TranslitTestCase(unittest.TestCase):
+ def setUp(self):
+ set_up('translit')
+
+ def tearDown(self):
+ tear_down()
+
+ def test_dummy(self):
+ self.assertEqual(True, True)
+
+ def test_sh_multiple_match(self):
+ ENGINE.do_process_key_event(IBus.KEY_s, 0, 0)
+ self.assertEqual(ENGINE.mock_preedit_text, 'с')
+ ENGINE.do_process_key_event(IBus.KEY_h, 0, 0)
+ self.assertEqual(ENGINE.mock_preedit_text, 'ш')
+ ENGINE.do_process_key_event(IBus.KEY_s, 0, 0)
+ self.assertEqual(ENGINE.mock_committed_text, 'ш')
+ self.assertEqual(ENGINE.mock_preedit_text, 'с')
+ ENGINE.do_process_key_event(IBus.KEY_h, 0, 0)
+ self.assertEqual(ENGINE.mock_committed_text, 'ш')
+ self.assertEqual(ENGINE.mock_preedit_text, 'ш')
+ ENGINE.do_process_key_event(IBus.KEY_h, 0, 0)
+ self.assertEqual(ENGINE.mock_committed_text, 'шщ')
+ self.assertEqual(ENGINE.mock_preedit_text, '')
+ ENGINE.do_process_key_event(IBus.KEY_s, 0, 0)
+ self.assertEqual(ENGINE.mock_committed_text, 'шщ')
+ self.assertEqual(ENGINE.mock_preedit_text, 'с')
+ ENGINE.do_process_key_event(IBus.KEY_space, 0, 0)
+ self.assertEqual(ENGINE.mock_committed_text, 'шщс ')