diff --git a/README.md b/README.md index dd753c869..a971750fa 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/gajim/chat_control.py b/gajim/chat_control.py index 57a03f58f..9442e19a2 100644 --- a/gajim/chat_control.py +++ b/gajim/chat_control.py @@ -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 diff --git a/gajim/chat_control_base.py b/gajim/chat_control_base.py index 8fc76313b..13bbf599d 100644 --- a/gajim/chat_control_base.py +++ b/gajim/chat_control_base.py @@ -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): """ diff --git a/gajim/common/app.py b/gajim/common/app.py index f88d8f591..780a6f50e 100644 --- a/gajim/common/app.py +++ b/gajim/common/app.py @@ -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, diff --git a/gajim/config.py b/gajim/config.py index 72b496526..1fab60f1b 100644 --- a/gajim/config.py +++ b/gajim/config.py @@ -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') diff --git a/gajim/dialogs.py b/gajim/dialogs.py index 03829a2bf..6f6968389 100644 --- a/gajim/dialogs.py +++ b/gajim/dialogs.py @@ -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) diff --git a/gajim/features_window.py b/gajim/features_window.py index 17ffd8307..5fa363eeb 100644 --- a/gajim/features_window.py +++ b/gajim/features_window.py @@ -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 diff --git a/gajim/gtkspell.py b/gajim/gtkspell.py deleted file mode 100644 index e3a06ed48..000000000 --- a/gajim/gtkspell.py +++ /dev/null @@ -1,104 +0,0 @@ -## src/gtkspell.py -## -## (C) 2008 Thorsten P. 'dGhvcnN0ZW5wIEFUIHltYWlsIGNvbQ==\n'.decode("base64") -## (C) 2015 Yann Leboulanger -## -## 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 . - -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 diff --git a/gajim/message_textview.py b/gajim/message_textview.py index acd6c4dbf..f9bb99c29 100644 --- a/gajim/message_textview.py +++ b/gajim/message_textview.py @@ -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() diff --git a/win/_base.sh b/win/_base.sh index 805c33c1c..32d68bac1 100644 --- a/win/_base.sh +++ b/win/_base.sh @@ -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