From 5df562d302c7331287a5dca3b5a691e7dcca2ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Apitzsch?= Date: Mon, 3 Jul 2023 22:18:55 +0200 Subject: [PATCH] Port from GtkSpell to Gspell --- README.md | 6 +- docs/coregui/gui_widgets.rst | 2 +- gramps/gen/utils/grampslocale.py | 2 + gramps/grampsapp.py | 52 +---------- gramps/gui/configure.py | 6 +- gramps/gui/spell.py | 122 ++++++++----------------- gramps/gui/widgets/styledtextbuffer.py | 24 +++-- gramps/gui/widgets/styledtexteditor.py | 5 +- 8 files changed, 69 insertions(+), 150 deletions(-) diff --git a/README.md b/README.md index efff48466..90307e9a5 100644 --- a/README.md +++ b/README.md @@ -75,11 +75,9 @@ The following packages are **STRONGLY RECOMMENDED** to be installed: The following packages are optional: ------------------------------------ -* **gtkspell** +* **gspell** - Enable spell checking in the notes. Gtkspell depends on - enchant. A version of gtkspell with gobject introspection - is needed, so minimally version 3.0.0. + Enable spell checking in the notes. * **rcs** diff --git a/docs/coregui/gui_widgets.rst b/docs/coregui/gui_widgets.rst index 09f25fe99..64c481ee6 100644 --- a/docs/coregui/gui_widgets.rst +++ b/docs/coregui/gui_widgets.rst @@ -302,7 +302,7 @@ Styled Text Buffer :members: :undoc-members: :show-inheritance: -.. autoclass:: GtkSpellState +.. autoclass:: GspellState :members: :undoc-members: :show-inheritance: diff --git a/gramps/gen/utils/grampslocale.py b/gramps/gen/utils/grampslocale.py index 1bd3ff570..39c73a83a 100644 --- a/gramps/gen/utils/grampslocale.py +++ b/gramps/gen/utils/grampslocale.py @@ -790,6 +790,8 @@ class GrampsLocale: """ return self.language + def locale_code(self): + return self.lang def get_addon_translator(self, filename, domain="addon", languages=None): diff --git a/gramps/grampsapp.py b/gramps/grampsapp.py index 79fa27190..1d4ddae6b 100644 --- a/gramps/grampsapp.py +++ b/gramps/grampsapp.py @@ -363,53 +363,12 @@ def show_settings(): def verstr(nums): return '.'.join(str(num) for num in nums) - #GTKSPELL_MIN_VER = (3, 0) - #gtkspell_min_ver_str = verstr(GTKSPELL_MIN_VER) - # ENCHANT_MIN_VER = (0, 0) # TODO ? - gtkspell_ver_tp = (0, 0) - # Attempting to import gtkspell gives an error dialog if gtkspell is - # not available so test first and log just a warning to the console - # instead. try: - from gi import Repository - repository = Repository.get_default() - gtkspell_ver = _("not found") - if repository.enumerate_versions("GtkSpell"): - try: - gi.require_version('GtkSpell', '3.0') - from gi.repository import GtkSpell as Gtkspell - gtkspell_ver = str(Gtkspell._version) - aaa = Gtkspell._version.split(".") - v1 = int(aaa[0]) - v2 = int(aaa[1]) - gtkspell_ver_tp = (v1, v2) - # print("gtkspell_ver " + gtkspell_ver) - except Exception: - gtkspell_ver = _("not found") - elif repository.enumerate_versions("Gtkspell"): - try: - gi.require_version('Gtkspell', '3.0') - from gi.repository import Gtkspell - gtkspell_ver = str(Gtkspell._version) - gtkspell_ver_tp = Gtkspell._version - # print("gtkspell_ver " + gtkspell_ver) - except Exception: - gtkspell_ver = _("not found") + gi.require_version('Gspell', '1') + from gi.repository import Gspell + gspell_ver = str(Gspell._version) except Exception: - gtkspell_ver = _("not found") - - try: - import enchant - enchant_result = enchant.get_enchant_version() - except Exception: - from ctypes import cdll, c_char_p - try: - enchant = cdll.LoadLibrary("libenchant") - except FileNotFoundError: - enchant = cdll.LoadLibrary("libenchant-2") - enchant_ver_call = enchant.enchant_get_version - enchant_ver_call.restype = c_char_p - enchant_result = enchant_ver_call().decode("utf-8") + gspell_ver = _("not found") #RCS_MIN_VER = (5, 9, 4) #rcs_ver_str = verstr(RCS_MIN_VER) @@ -539,8 +498,7 @@ def show_settings(): print('') print("Optional:") print("---------") - print(' Gtkspell :', gtkspell_ver) - print(' Enchant :', enchant_result) + print(' Gspell :', gspell_ver) print(' RCS :', rcs_ver) print(' PILLOW :', pil_ver) print(' GExiv2 : %s' % gexiv2_str) diff --git a/gramps/gui/configure.py b/gramps/gui/configure.py index 8f660df4b..281fddcf0 100644 --- a/gramps/gui/configure.py +++ b/gramps/gui/configure.py @@ -71,7 +71,7 @@ from .display import display_help from gramps.gen.plug.utils import available_updates from .plug import PluginWindows #from gramps.gen.errors import WindowActiveError -from .spell import HAVE_GTKSPELL +from .spell import HAVE_GSPELL from gramps.gen.constfunc import win _ = glocale.translation.gettext from gramps.gen.utils.symbols import Symbols @@ -1654,14 +1654,14 @@ class GrampsPreferences(ConfigureDialog): row, 'behavior.spellcheck', start=1, stop=3, tooltip=_("Enable the spelling checker" " for notes.")) - if not HAVE_GTKSPELL: + if not HAVE_GSPELL: obj.set_sensitive(False) spell_dict = {'gramps_wiki_build_spell_url': URL_WIKISTRING + "GEPS_029:_GTK3-GObject_introspection" "_Conversion#Spell_Check_Install"} obj.set_tooltip_text( - _("GtkSpell not loaded. " + _("Gspell not loaded. " "Spell checking will not be available.\n" "To build it for Gramps see " "%(gramps_wiki_build_spell_url)s") % spell_dict) diff --git a/gramps/gui/spell.py b/gramps/gui/spell.py index d400b5916..2c98e80b1 100644 --- a/gramps/gui/spell.py +++ b/gramps/gui/spell.py @@ -20,8 +20,7 @@ # """ -Provide an interface to the gtkspell interface. This requires -python-gnome-extras package. If the gtkspell package is not +Provide an interface to the gspell interface. If the gspell package is not present, we default to no spell checking. """ @@ -45,33 +44,24 @@ LOG = logging.getLogger(".Spell") # GTK libraries # #------------------------------------------------------------------------- -from gi.repository import Gtk -from gi import Repository - from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext -HAVE_GTKSPELL = False +HAVE_GSPELL = False -# Attempting to import gtkspell gives an error dialog if gtkspell is not -# available so test first and log just a warning to the console instead. -repository = Repository.get_default() -if repository.enumerate_versions("GtkSpell"): - try: - import gi - gi.require_version('GtkSpell', '3.0') - from gi.repository import GtkSpell as Gtkspell - HAVE_GTKSPELL = True - except: - pass -elif repository.enumerate_versions("Gtkspell"): - try: - import gi - gi.require_version('Gtkspell', '3.0') - from gi.repository import Gtkspell - HAVE_GTKSPELL = True - except: - pass +try: + import gi + gi.require_version('Gspell', '1') + from gi.repository import Gspell + langs = Gspell.language_get_available() + for lang in langs: + LOG.debug('%s (%s) dict available', lang.get_name(), lang.get_code()) + if langs: + HAVE_GSPELL = True + else: + LOG.warning(_("You have no installed dictionaries.")) +except (ImportError, ValueError): + pass #------------------------------------------------------------------------- # @@ -87,81 +77,47 @@ from gramps.gen.config import config #------------------------------------------------------------------------- class Spell: - """Attach a gtkspell instance to the passed TextView instance. + """Attach a Gspell instance to the passed TextView instance. """ _spellcheck_options = {'off': _('Off')} - if HAVE_GTKSPELL: + if HAVE_GSPELL: _spellcheck_options['on'] = _('On') def __init__(self, textview): - self.textview = textview + self._active_spellcheck = 'off' - if HAVE_GTKSPELL and config.get('behavior.spellcheck'): + if not HAVE_GSPELL: + return + + locale_code = glocale.locale_code() + gspell_language = None + if locale_code is not None: + gspell_language = Gspell.language_lookup(locale_code[:5]) + if gspell_language is None: + gspell_language= Gspell.language_lookup(locale_code[:2]) + checker = Gspell.Checker.new(gspell_language) + buffer = Gspell.TextBuffer.get_from_gtk_text_buffer(textview.get_buffer()) + buffer.set_spell_checker(checker) + self.gspell_view = Gspell.TextView.get_from_gtk_text_view(textview) + + if config.get('behavior.spellcheck'): self.spellcheck = 'on' else: self.spellcheck = 'off' - self._active_spellcheck = 'off' self.__real_set_active_spellcheck(self.spellcheck) # Private def __real_set_active_spellcheck(self, spellcheck_code): """Set active spellcheck by its code.""" - if self._active_spellcheck == 'off': - if spellcheck_code == 'off': - return - else: - try: - #transfer full GTK object, so assign to an attribute! - if Gtkspell._namespace == "Gtkspell": - self.gtkspell_spell = Gtkspell.Spell.new() - elif Gtkspell._namespace == "GtkSpell": - self.gtkspell_spell = Gtkspell.Checker.new() - try: - #check for dictionary in system locale (LANG) - #if exist it will be default one - self.gtkspell_spell.set_language(None) - #TODO: use "get_language_list" for use when there - #is no English or systemlocale one - except: - #else check for English dictionary - #if exist it will be default one - #other installed one will also be available - self.gtkspell_spell.set_language("en") - #if that fails no spellchecker will be available - with self.textview.undo_disabled(): - success = self.gtkspell_spell.attach(self.textview) - try: - #show decoded language codes in the context menu - #requires the iso-codes package from http://pkg-isocodes.alioth.debian.org - self.gtkspell_spell.set_property("decode-language-codes", True) - except TypeError: - #available in GtkSpell since version 3.0.3 (2013-06-04) - pass - self._active_spellcheck = spellcheck_code - except Exception as err: - # attaching the spellchecker will fail if - # the language does not exist - # and presumably if there is no dictionary - if not self.gtkspell_spell.get_language_list(): - LOG.warning(_("You have no installed dictionaries. " - "Either install one or disable spell " - "checking")) - else: - LOG.warning(_("Spelling checker initialization " - "failed: %s"), err) - else: - if spellcheck_code == 'on': - return - else: - if Gtkspell._namespace == "Gtkspell": - self.gtkspell_spell = Gtkspell.Spell.get_from_text_view(self.textview) - elif Gtkspell._namespace == "GtkSpell": - self.gtkspell_spell = Gtkspell.Checker.get_from_text_view(self.textview) - self.gtkspell_spell.detach() - self._active_spellcheck = spellcheck_code + if self._active_spellcheck == spellcheck_code: + return + + self.gspell_view.set_inline_spell_checking(spellcheck_code == 'on') + self.gspell_view.set_enable_language_menu(spellcheck_code == 'on') + self._active_spellcheck = spellcheck_code # Public API diff --git a/gramps/gui/widgets/styledtextbuffer.py b/gramps/gui/widgets/styledtextbuffer.py index ca05136bc..3ca07426c 100644 --- a/gramps/gui/widgets/styledtextbuffer.py +++ b/gramps/gui/widgets/styledtextbuffer.py @@ -116,15 +116,15 @@ class LinkTag(Gtk.TextTag): #------------------------------------------------------------------------- # -# GtkSpellState class +# GspellState class # #------------------------------------------------------------------------- -class GtkSpellState: +class GspellState: """ A simple state machine kinda thingy. - Trying to track Gtk.Spell activities on a buffer and re-apply formatting - after Gtk.Spell replaces a misspelled word. + Trying to track Gspell activities on a buffer and re-apply formatting + after Gspell replaces a misspelled word. """ (STATE_NONE, STATE_CLICKED, @@ -180,10 +180,10 @@ class GtkSpellState: def get_word_extents_from_mark(self, textbuffer, mark): """ - Get the word extents as Gtk.Spell does. + Get the word extents as Gspell does. Used to get the beginning of the word, in which user right clicked. - Formatting found at that position used after Gtk.Spell replaces + Formatting found at that position used after Gspell replaces misspelled words. """ start = textbuffer.get_iter_at_mark(mark) @@ -198,7 +198,7 @@ class GtkSpellState: def forward_word_end(self, iter): """ - Gtk.Spell style Gtk.TextIter.forward_word_end. + Gspell style Gtk.TextIter.forward_word_end. The parameter 'iter' is changing as side effect. """ @@ -217,7 +217,7 @@ class GtkSpellState: def backward_word_start(self, iter): """ - Gtk.Spell style Gtk.TextIter.backward_word_start. + Gspell style Gtk.TextIter.backward_word_start. The parameter 'iter' is changing as side effect. """ @@ -310,8 +310,8 @@ class StyledTextBuffer(UndoableBuffer): self.linkcolor = 'blue' - # init gtkspell "state machine" - self.gtkspell_state = GtkSpellState(self) + # init gspell "state machine" + self.gspell_state = GspellState(self) # Virtual methods @@ -385,6 +385,8 @@ class StyledTextBuffer(UndoableBuffer): else: value = StyledTextTagType.STYLE_DEFAULT[style] for tname in tag_names: + if tname is None: + continue if tname.startswith(str(style)): value = tname.split(' ', 1)[1] value = StyledTextTagType.STYLE_TYPE[style](value) @@ -612,6 +614,8 @@ class StyledTextBuffer(UndoableBuffer): s_tags = [] for g_tagname, g_ranges in g_tags.items(): + if g_tagname is None: + continue if g_tagname.startswith('link'): tag = self.get_tag_table().lookup(g_tagname) s_ranges = [(start, end+1) for (start, end) in g_ranges] diff --git a/gramps/gui/widgets/styledtexteditor.py b/gramps/gui/widgets/styledtexteditor.py index 8c01bb101..a79ba6ec9 100644 --- a/gramps/gui/widgets/styledtexteditor.py +++ b/gramps/gui/widgets/styledtexteditor.py @@ -402,7 +402,8 @@ class StyledTextEditor(Gtk.TextView): self.match = self.textbuffer.match_check(iter_at_location.get_offset()) tooltip = None for tag in (tag for tag in iter_at_location.get_tags() - if tag.get_property('name').startswith("link")): + if tag.get_property('name') is not None and + tag.get_property('name').startswith("link")): self.match = (x, y, LINK, tag.data, tag) tooltip = self.make_tooltip_from_link(tag) break @@ -808,7 +809,7 @@ class StyledTextEditor(Gtk.TextView): """ Remove all formats from the selection or from all. - Remove only our own tags without touching other ones (e.g. Gtk.Spell), + Remove only our own tags without touching other ones (e.g. Gspell), thus remove_all_tags() can not be used. """ clear_anything = self.textbuffer.clear_selection()