Refactor SpellChecker

- use Gspell instead of GtkSpell, it seems to have alot less problems
and needs less code
This commit is contained in:
Philipp Hörist 2017-11-25 17:37:46 +01:00
parent f75dd8bcad
commit 7692b376ee
10 changed files with 93 additions and 188 deletions

View File

@ -18,7 +18,7 @@
- python3-crypto to enable End to end encryption
- python3-gnupg to enable GPG encryption
- For zeroconf (bonjour) you need dbus-glib, python-avahi
- gir1.2-gtkspell3-3.0 and aspell-LANG where lang is your locale eg. en, fr etc
- gir1.2-gspell-1 and hunspell-LANG where lang is your locale eg. en, fr etc
- gir1.2-secret-1 for GNOME Keyring or KDE support as password storage
- D-Bus running to have gajim-remote working. Some distributions split dbus-x11, which is needed for dbus to work with Gajim. Version >= 0.80 is required.
- python3-dbus bindings (>=1.2.0)

View File

@ -1173,7 +1173,6 @@ class ChatControl(ChatControlBase):
self.handlers[i].disconnect(i)
del self.handlers[i]
self.conv_textview.del_handlers()
self.remove_speller()
self.msg_textview.destroy()
# PluginSystem: calling shutdown of super class (ChatControlBase) to let
# it remove it's GUI extension points

View File

@ -32,7 +32,6 @@ import time
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Pango
from gi.repository import GObject
from gi.repository import GLib
from gi.repository import Gio
@ -42,7 +41,6 @@ from gajim import message_control
from gajim import dialogs
from gajim import history_window
from gajim import notify
from gajim import gtkspell
import re
from gajim import emoticons
@ -66,6 +64,9 @@ from gajim.command_system.implementation.middleware import CommandTools
from gajim.command_system.implementation import standard
from gajim.command_system.implementation import execute
if app.HAVE_SPELL:
from gi.repository import Gspell
################################################################################
class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
@ -345,8 +346,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.set_emoticon_popover()
# Attach speller
self.spell = None
self.spell_handlers = []
self.spell_checker = None
self.set_speller()
self.conv_textview.tv.show()
@ -471,44 +471,22 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
image.set_from_pixbuf(icon)
def set_speller(self):
if not gtkspell.HAS_GTK_SPELL or not app.config.get('use_speller'):
if not app.HAVE_SPELL or not app.config.get('use_speller'):
return
def _on_focus_in(*args):
if self.spell is None:
return
self.spell.attach(self.msg_textview)
def _on_focus_out(*args):
if self.spell is None:
return
if not self.msg_textview.has_text():
self.spell.detach()
lang = self.get_speller_language()
if not lang:
gspell_lang = self.get_speller_language()
if gspell_lang is None:
return
try:
self.spell = gtkspell.Spell(self.msg_textview, lang)
self.spell.connect('language_changed', self.on_language_changed)
handler_id = self.msg_textview.connect('focus-in-event',
_on_focus_in)
self.spell_handlers.append(handler_id)
handler_id = self.msg_textview.connect('focus-out-event',
_on_focus_out)
self.spell_handlers.append(handler_id)
except OSError:
dialogs.AspellDictError(lang)
app.config.set('use_speller', False)
def remove_speller(self):
if self.spell is None:
return
self.spell.detach()
for id_ in self.spell_handlers:
self.msg_textview.disconnect(id_)
self.spell_handlers.remove(id_)
self.spell = None
self.spell_checker = Gspell.Checker.new(gspell_lang)
spell_buffer = Gspell.TextBuffer.get_from_gtk_text_buffer(
self.msg_textview.get_buffer())
spell_buffer.set_spell_checker(self.spell_checker)
spell_view = Gspell.TextView.get_from_gtk_text_view(self.msg_textview)
spell_view.set_inline_spell_checking(False)
spell_view.set_enable_language_menu(True)
self.spell_checker.connect('notify::language', self.on_language_changed)
def get_speller_language(self):
per_type = 'contacts'
@ -521,16 +499,20 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
lang = app.config.get('speller_language')
if not lang:
lang = app.LANG
return lang or None
gspell_lang = Gspell.language_lookup(lang)
if gspell_lang is None:
gspell_lang = Gspell.language_get_default()
return gspell_lang
def on_language_changed(self, spell, lang):
def on_language_changed(self, checker, param):
gspell_lang = checker.get_language()
per_type = 'contacts'
if self.type_id == message_control.TYPE_GC:
per_type = 'rooms'
if not app.config.get_per(per_type, self.contact.jid):
app.config.add_per(per_type, self.contact.jid)
app.config.set_per(
per_type, self.contact.jid, 'speller_language', lang)
app.config.set_per(per_type, self.contact.jid,
'speller_language', gspell_lang.get_code())
def on_banner_label_populate_popup(self, label, menu):
"""

View File

@ -253,6 +253,21 @@ except Exception:
glog.info(_('Unable to load idle module'))
HAVE_IDLE = False
HAVE_SPELL = False
try:
spell_log = logging.getLogger('gajim.speller')
gi.require_version('Gspell', '1')
from gi.repository import Gspell
langs = Gspell.language_get_available()
for lang in langs:
spell_log.info('%s (%s) dict available',
lang.get_name(), lang.get_code())
if langs:
HAVE_SPELL = True
else:
spell_log.info('No dicts available')
except ImportError:
pass
gajim_identity = {'type': 'pc', 'category': 'client', 'name': 'Gajim'}
gajim_common_features = [nbxmpp.NS_BYTESTREAM, nbxmpp.NS_SI, nbxmpp.NS_FILE,

View File

@ -50,7 +50,6 @@ from gajim import message_control
from gajim.chat_control_base import ChatControlBase
from gajim import dataforms_widget
from gajim import gui_menu_builder
from gajim import gtkspell
from gajim.common import helpers
from gajim.common import app
@ -66,6 +65,9 @@ try:
except (ImportError, ValueError):
HAS_GST = False
if app.HAVE_SPELL:
from gi.repository import Gspell
#---------- PreferencesWindow class -------------#
class PreferencesWindow:
"""
@ -187,7 +189,7 @@ class PreferencesWindow:
self.xml.get_object('xhtml_checkbutton').set_active(st)
# use speller
if gtkspell.HAS_GTK_SPELL:
if app.HAVE_SPELL:
st = app.config.get('use_speller')
self.xml.get_object('speller_checkbutton').set_active(st)
else:
@ -657,30 +659,22 @@ class PreferencesWindow:
if isinstance(ctrl, ChatControlBase):
ctrl.set_speller()
def remove_speller(self):
for ctrl in self._get_all_controls():
if isinstance(ctrl, ChatControlBase):
if ctrl.spell is not None:
ctrl.remove_speller()
def on_speller_checkbutton_toggled(self, widget):
active = widget.get_active()
app.config.set('use_speller', active)
if active:
lang = app.config.get('speller_language')
if not lang:
lang = app.LANG
available = gtkspell.test_language(lang)
if not available:
dialogs.AspellDictError(lang)
app.config.set('use_speller', False)
widget.set_active(False)
else:
app.config.set('speller_language', lang)
self.apply_speller()
if not active:
return
lang = app.config.get('speller_language')
gspell_lang = Gspell.language_lookup(lang)
if gspell_lang is None:
gspell_lang = Gspell.language_get_default()
if gspell_lang is None:
dialogs.AspellDictError(lang)
app.config.set('use_speller', False)
widget.set_active(False)
else:
self.remove_speller()
app.config.set('speller_language', gspell_lang.get_code())
self.apply_speller()
def on_positive_184_ack_checkbutton_toggled(self, widget):
self.on_checkbutton_toggled(widget, 'positive_184_ack')

View File

@ -34,6 +34,7 @@ from gi.repository import Gdk
from gi.repository import GdkPixbuf
from gi.repository import GObject
from gi.repository import GLib
import os
import nbxmpp
import time
@ -42,7 +43,6 @@ from gajim import gtkgui_helpers
from gajim import vcard
from gajim import conversation_textview
from gajim import dataforms_widget
from gajim import gtkspell
from random import randrange
from gajim.common import pep
@ -64,6 +64,9 @@ from gajim.common import dataforms
from gajim.common.exceptions import GajimGeneralException
from gajim.common.connection_handlers_events import MessageOutgoingEvent
if app.HAVE_SPELL:
from gi.repository import Gspell
import logging
log = logging.getLogger('gajim.dialogs')
@ -3077,15 +3080,19 @@ class SingleMessageWindow:
else:
self.to_entry.set_text(to)
if app.config.get('use_speller') and gtkspell.HAS_GTK_SPELL and action == 'send':
try:
lang = app.config.get('speller_language')
if not lang:
lang = app.LANG
self.spell = gtkspell.Spell(self.message_textview, lang)
self.spell.attach(self.message_textview)
except OSError:
if app.config.get('use_speller') and app.HAVE_SPELL and action == 'send':
lang = app.config.get('speller_language')
gspell_lang = Gspell.language_lookup(lang)
if gspell_lang is None:
AspellDictError(lang)
else:
spell_buffer = Gspell.TextBuffer.get_from_gtk_text_buffer(
self.message_textview.get_buffer())
spell_buffer.set_spell_checker(Gspell.Checker.new(gspell_lang))
spell_view = Gspell.TextView.get_from_gtk_text_view(
self.message_textview)
spell_view.set_inline_spell_checking(True)
spell_view.set_enable_language_menu(True)
self.prepare_widgets_for(self.action)

View File

@ -63,8 +63,8 @@ class FeaturesWindow:
_('On Windows the Windows Credential Vault is used.')),
_('Spell Checker'): (self.speller_available,
_('Spellchecking of composed messages.'),
_('Requires libgtkspell.'),
_('Requires libgtkspell and libenchant.')),
_('Requires Gspell'),
_('Requires Gspell')),
_('Automatic status'): (self.idle_available,
_('Ability to measure idle time, in order to set auto status.'),
_('Requires libxss library.'),
@ -164,11 +164,7 @@ class FeaturesWindow:
return True
def speller_available(self):
try:
__import__('gajim.gtkspell')
except ValueError:
return False
return True
return app.HAVE_SPELL
def idle_available(self):
from gajim.common import sleepy

View File

@ -1,104 +0,0 @@
## src/gtkspell.py
##
## (C) 2008 Thorsten P. 'dGhvcnN0ZW5wIEFUIHltYWlsIGNvbQ==\n'.decode("base64")
## (C) 2015 Yann Leboulanger <asterix AT lagaule.org>
##
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import GLib
import gi
try:
gi.require_version('GtkSpell', '3.0')
from gi.repository import GtkSpell
HAS_GTK_SPELL = True
except (ImportError, ValueError):
HAS_GTK_SPELL = False
def ensure_attached(func):
def f(self, *args, **kwargs):
if self.spell:
func(self, *args, **kwargs)
else:
raise RuntimeError("Spell object is already detached")
return f
class Spell(GObject.GObject):
__gsignals__ = {
'language_changed': (GObject.SignalFlags.RUN_FIRST, None, (str,))
}
def __init__(self, textview, language=None, create=True, jid=None,
per_type=None):
GObject.GObject.__init__(self)
if not isinstance(textview, Gtk.TextView):
raise TypeError("Textview must be derived from Gtk.TextView")
spell = GtkSpell.Checker.get_from_text_view(textview)
if create:
if spell:
raise RuntimeError("Textview has already a Spell obj attached")
self.spell = GtkSpell.Checker.new()
try:
self.spell.set_language(language)
except GLib.GError as error:
if error.domain == 'gtkspell-error-quark':
raise OSError("Unable to set language: '%s'" % language)
self.spell.connect('language-changed', self.on_language_changed)
else:
if spell:
self.spell = spell
else:
raise RuntimeError("Textview has no Spell object attached")
def on_language_changed(self, spell, lang):
self.emit('language_changed', lang)
@ensure_attached
def set_language(self, language):
if not self.spell.set_language(language):
raise OSError("Unable to set language: '%s'" % language)
@ensure_attached
def recheck_all(self):
self.spell.recheck_all()
def detach(self):
if self.spell is not None:
self.spell.detach()
def attach(self, textview):
spell = GtkSpell.Checker.get_from_text_view(textview)
if spell is None:
print('attached')
self.spell.attach(textview)
GObject.type_register(Spell)
def test_language(lang):
spell = GtkSpell.Checker.new()
try:
spell.set_language(lang)
except GLib.GError as error:
if error.domain == 'gtkspell-error-quark':
return False
return True

View File

@ -30,6 +30,10 @@ from gi.repository import Pango
from gajim.common import app
from gajim import gtkgui_helpers
if app.HAVE_SPELL:
from gi.repository import Gspell
class MessageTextView(Gtk.TextView):
"""
Class for the message textview (where user writes new messages) for
@ -101,6 +105,7 @@ class MessageTextView(Gtk.TextView):
def _on_focus_in(self, *args):
if not self.has_text():
self.get_buffer().set_text('')
self.toggle_speller(True)
def _on_focus_out(self, *args):
buf = self.get_buffer()
@ -109,6 +114,12 @@ class MessageTextView(Gtk.TextView):
if text == '':
buf.insert_with_tags(
start, self.PLACEHOLDER, self.placeholder_tag)
self.toggle_speller(False)
def toggle_speller(self, activate):
if app.HAVE_SPELL and app.config.get('use_speller'):
spell_view = Gspell.TextView.get_from_gtk_text_view(self)
spell_view.set_inline_spell_checking(activate)
def remove_placeholder(self):
self._on_focus_in()

View File

@ -75,7 +75,9 @@ function install_deps {
mingw-w64-"${ARCH}"-adwaita-icon-theme \
mingw-w64-"${ARCH}"-libwebp \
mingw-w64-"${ARCH}"-sqlite3 \
mingw-w64-"${ARCH}"-goocanvas
mingw-w64-"${ARCH}"-goocanvas \
mingw-w64-"${ARCH}"-gspell \
mingw-w64-"${ARCH}"-hunspell
build_pip install setuptools_scm
@ -136,6 +138,10 @@ function install_gajim {
mkdir "${PACKAGE_DIR}"/gajim/data/plugins
7z x -o"${PACKAGE_DIR}"/gajim/data/plugins "${BUILD_ROOT}"/plugin_installer.zip
# Install language dicts
curl -o "${BUILD_ROOT}"/speller_dicts.zip https://gajim.org/downloads/snap/win/build/speller_dicts.zip
7z x -o"${MINGW_ROOT}"/share "${BUILD_ROOT}"/speller_dicts.zip
# Install themes
# rm -Rf "${MINGW_ROOT}"/etc
# rm -Rf "${MINGW_ROOT}"/share/themes
@ -194,7 +200,6 @@ function cleanup_install {
rm -Rf "${MINGW_ROOT}"/share/ffmpeg
rm -Rf "${MINGW_ROOT}"/share/vala
rm -Rf "${MINGW_ROOT}"/share/readline
rm -Rf "${MINGW_ROOT}"/share/xml
rm -Rf "${MINGW_ROOT}"/share/bash-completion
rm -Rf "${MINGW_ROOT}"/share/common-lisp
rm -Rf "${MINGW_ROOT}"/share/emacs