Refactor SpellChecker
- use Gspell instead of GtkSpell, it seems to have alot less problems and needs less code
This commit is contained in:
		
							parent
							
								
									f75dd8bcad
								
							
						
					
					
						commit
						7692b376ee
					
				
					 10 changed files with 93 additions and 188 deletions
				
			
		|  | @ -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) | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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): | ||||
|         """ | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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') | ||||
|  |  | |||
|  | @ -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) | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  | @ -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() | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue