Port from GtkSpell to Gspell

This commit is contained in:
André Apitzsch 2023-07-03 22:18:55 +02:00 committed by Nick Hall
parent 28073ee118
commit 5df562d302
8 changed files with 69 additions and 150 deletions

View File

@ -75,11 +75,9 @@ The following packages are **STRONGLY RECOMMENDED** to be installed:
The following packages are optional: The following packages are optional:
------------------------------------ ------------------------------------
* **gtkspell** * **gspell**
Enable spell checking in the notes. Gtkspell depends on Enable spell checking in the notes.
enchant. A version of gtkspell with gobject introspection
is needed, so minimally version 3.0.0.
* **rcs** * **rcs**

View File

@ -302,7 +302,7 @@ Styled Text Buffer
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
.. autoclass:: GtkSpellState .. autoclass:: GspellState
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@ -790,6 +790,8 @@ class GrampsLocale:
""" """
return self.language return self.language
def locale_code(self):
return self.lang
def get_addon_translator(self, filename, domain="addon", def get_addon_translator(self, filename, domain="addon",
languages=None): languages=None):

View File

@ -363,53 +363,12 @@ def show_settings():
def verstr(nums): def verstr(nums):
return '.'.join(str(num) for num in 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: try:
from gi import Repository gi.require_version('Gspell', '1')
repository = Repository.get_default() from gi.repository import Gspell
gtkspell_ver = _("not found") gspell_ver = str(Gspell._version)
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: except Exception:
gtkspell_ver = _("not found") gspell_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")
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")
#RCS_MIN_VER = (5, 9, 4) #RCS_MIN_VER = (5, 9, 4)
#rcs_ver_str = verstr(RCS_MIN_VER) #rcs_ver_str = verstr(RCS_MIN_VER)
@ -539,8 +498,7 @@ def show_settings():
print('') print('')
print("Optional:") print("Optional:")
print("---------") print("---------")
print(' Gtkspell :', gtkspell_ver) print(' Gspell :', gspell_ver)
print(' Enchant :', enchant_result)
print(' RCS :', rcs_ver) print(' RCS :', rcs_ver)
print(' PILLOW :', pil_ver) print(' PILLOW :', pil_ver)
print(' GExiv2 : %s' % gexiv2_str) print(' GExiv2 : %s' % gexiv2_str)

View File

@ -71,7 +71,7 @@ from .display import display_help
from gramps.gen.plug.utils import available_updates from gramps.gen.plug.utils import available_updates
from .plug import PluginWindows from .plug import PluginWindows
#from gramps.gen.errors import WindowActiveError #from gramps.gen.errors import WindowActiveError
from .spell import HAVE_GTKSPELL from .spell import HAVE_GSPELL
from gramps.gen.constfunc import win from gramps.gen.constfunc import win
_ = glocale.translation.gettext _ = glocale.translation.gettext
from gramps.gen.utils.symbols import Symbols from gramps.gen.utils.symbols import Symbols
@ -1654,14 +1654,14 @@ class GrampsPreferences(ConfigureDialog):
row, 'behavior.spellcheck', start=1, stop=3, row, 'behavior.spellcheck', start=1, stop=3,
tooltip=_("Enable the spelling checker" tooltip=_("Enable the spelling checker"
" for notes.")) " for notes."))
if not HAVE_GTKSPELL: if not HAVE_GSPELL:
obj.set_sensitive(False) obj.set_sensitive(False)
spell_dict = {'gramps_wiki_build_spell_url': spell_dict = {'gramps_wiki_build_spell_url':
URL_WIKISTRING + URL_WIKISTRING +
"GEPS_029:_GTK3-GObject_introspection" "GEPS_029:_GTK3-GObject_introspection"
"_Conversion#Spell_Check_Install"} "_Conversion#Spell_Check_Install"}
obj.set_tooltip_text( obj.set_tooltip_text(
_("GtkSpell not loaded. " _("Gspell not loaded. "
"Spell checking will not be available.\n" "Spell checking will not be available.\n"
"To build it for Gramps see " "To build it for Gramps see "
"%(gramps_wiki_build_spell_url)s") % spell_dict) "%(gramps_wiki_build_spell_url)s") % spell_dict)

View File

@ -20,8 +20,7 @@
# #
""" """
Provide an interface to the gtkspell interface. This requires Provide an interface to the gspell interface. If the gspell package is not
python-gnome-extras package. If the gtkspell package is not
present, we default to no spell checking. present, we default to no spell checking.
""" """
@ -45,32 +44,23 @@ LOG = logging.getLogger(".Spell")
# GTK libraries # GTK libraries
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
from gi.repository import Gtk
from gi import Repository
from gramps.gen.const import GRAMPS_LOCALE as glocale from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext _ = 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: try:
import gi import gi
gi.require_version('GtkSpell', '3.0') gi.require_version('Gspell', '1')
from gi.repository import GtkSpell as Gtkspell from gi.repository import Gspell
HAVE_GTKSPELL = True langs = Gspell.language_get_available()
except: for lang in langs:
pass LOG.debug('%s (%s) dict available', lang.get_name(), lang.get_code())
elif repository.enumerate_versions("Gtkspell"): if langs:
try: HAVE_GSPELL = True
import gi else:
gi.require_version('Gtkspell', '3.0') LOG.warning(_("You have no installed dictionaries."))
from gi.repository import Gtkspell except (ImportError, ValueError):
HAVE_GTKSPELL = True
except:
pass pass
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
@ -87,80 +77,46 @@ from gramps.gen.config import config
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
class Spell: class Spell:
"""Attach a gtkspell instance to the passed TextView instance. """Attach a Gspell instance to the passed TextView instance.
""" """
_spellcheck_options = {'off': _('Off')} _spellcheck_options = {'off': _('Off')}
if HAVE_GTKSPELL: if HAVE_GSPELL:
_spellcheck_options['on'] = _('On') _spellcheck_options['on'] = _('On')
def __init__(self, textview): 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' self.spellcheck = 'on'
else: else:
self.spellcheck = 'off' self.spellcheck = 'off'
self._active_spellcheck = 'off'
self.__real_set_active_spellcheck(self.spellcheck) self.__real_set_active_spellcheck(self.spellcheck)
# Private # Private
def __real_set_active_spellcheck(self, spellcheck_code): def __real_set_active_spellcheck(self, spellcheck_code):
"""Set active spellcheck by its code.""" """Set active spellcheck by its code."""
if self._active_spellcheck == 'off': if self._active_spellcheck == spellcheck_code:
if spellcheck_code == 'off':
return return
else:
try: self.gspell_view.set_inline_spell_checking(spellcheck_code == 'on')
#transfer full GTK object, so assign to an attribute! self.gspell_view.set_enable_language_menu(spellcheck_code == 'on')
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 self._active_spellcheck = spellcheck_code
# Public API # Public API

View File

@ -116,15 +116,15 @@ class LinkTag(Gtk.TextTag):
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
# GtkSpellState class # GspellState class
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
class GtkSpellState: class GspellState:
""" """
A simple state machine kinda thingy. A simple state machine kinda thingy.
Trying to track Gtk.Spell activities on a buffer and re-apply formatting Trying to track Gspell activities on a buffer and re-apply formatting
after Gtk.Spell replaces a misspelled word. after Gspell replaces a misspelled word.
""" """
(STATE_NONE, (STATE_NONE,
STATE_CLICKED, STATE_CLICKED,
@ -180,10 +180,10 @@ class GtkSpellState:
def get_word_extents_from_mark(self, textbuffer, mark): 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. 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. misspelled words.
""" """
start = textbuffer.get_iter_at_mark(mark) start = textbuffer.get_iter_at_mark(mark)
@ -198,7 +198,7 @@ class GtkSpellState:
def forward_word_end(self, iter): 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. The parameter 'iter' is changing as side effect.
""" """
@ -217,7 +217,7 @@ class GtkSpellState:
def backward_word_start(self, iter): 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. The parameter 'iter' is changing as side effect.
""" """
@ -310,8 +310,8 @@ class StyledTextBuffer(UndoableBuffer):
self.linkcolor = 'blue' self.linkcolor = 'blue'
# init gtkspell "state machine" # init gspell "state machine"
self.gtkspell_state = GtkSpellState(self) self.gspell_state = GspellState(self)
# Virtual methods # Virtual methods
@ -385,6 +385,8 @@ class StyledTextBuffer(UndoableBuffer):
else: else:
value = StyledTextTagType.STYLE_DEFAULT[style] value = StyledTextTagType.STYLE_DEFAULT[style]
for tname in tag_names: for tname in tag_names:
if tname is None:
continue
if tname.startswith(str(style)): if tname.startswith(str(style)):
value = tname.split(' ', 1)[1] value = tname.split(' ', 1)[1]
value = StyledTextTagType.STYLE_TYPE[style](value) value = StyledTextTagType.STYLE_TYPE[style](value)
@ -612,6 +614,8 @@ class StyledTextBuffer(UndoableBuffer):
s_tags = [] s_tags = []
for g_tagname, g_ranges in g_tags.items(): for g_tagname, g_ranges in g_tags.items():
if g_tagname is None:
continue
if g_tagname.startswith('link'): if g_tagname.startswith('link'):
tag = self.get_tag_table().lookup(g_tagname) tag = self.get_tag_table().lookup(g_tagname)
s_ranges = [(start, end+1) for (start, end) in g_ranges] s_ranges = [(start, end+1) for (start, end) in g_ranges]

View File

@ -402,7 +402,8 @@ class StyledTextEditor(Gtk.TextView):
self.match = self.textbuffer.match_check(iter_at_location.get_offset()) self.match = self.textbuffer.match_check(iter_at_location.get_offset())
tooltip = None tooltip = None
for tag in (tag for tag in iter_at_location.get_tags() 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) self.match = (x, y, LINK, tag.data, tag)
tooltip = self.make_tooltip_from_link(tag) tooltip = self.make_tooltip_from_link(tag)
break break
@ -808,7 +809,7 @@ class StyledTextEditor(Gtk.TextView):
""" """
Remove all formats from the selection or from all. 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. thus remove_all_tags() can not be used.
""" """
clear_anything = self.textbuffer.clear_selection() clear_anything = self.textbuffer.clear_selection()