mirror of
https://github.com/olivierkes/manuskript.git
synced 2024-05-15 02:12:29 +12:00
Add a Spellchecker abstraction layer and clean up code
This is in preparation for adding support for additional spellchecking libraries other than PyEnchant which seems to be unmaintained and does not build in Windows 64 bit.
This commit is contained in:
parent
0238ccec7b
commit
d0f02cb2a7
|
@ -12,6 +12,7 @@ from PyQt5.QtGui import QDesktopServices
|
|||
from PyQt5.QtWidgets import qApp, QTextEdit
|
||||
|
||||
from manuskript.enums import Outline
|
||||
from manuskript.functions.spellchecker import Spellchecker
|
||||
|
||||
# Used to detect multiple connections
|
||||
AUC = Qt.AutoConnection | Qt.UniqueConnection
|
||||
|
|
139
manuskript/functions/spellchecker.py
Normal file
139
manuskript/functions/spellchecker.py
Normal file
|
@ -0,0 +1,139 @@
|
|||
|
||||
from PyQt5.QtCore import QLocale
|
||||
|
||||
try:
|
||||
import enchant
|
||||
except ImportError:
|
||||
enchant = None
|
||||
|
||||
class Spellchecker:
|
||||
dictionaries = {}
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def isInstalled():
|
||||
return enchant is not None
|
||||
|
||||
@staticmethod
|
||||
def availableDictionaries():
|
||||
return EnchantDictionary.availableDictionaries()
|
||||
|
||||
@staticmethod
|
||||
def getDefaultDictionary():
|
||||
return EnchantDictionary.getDefaultDictionary()
|
||||
|
||||
@staticmethod
|
||||
def getLibraryURL():
|
||||
return EnchantDictionary.getLibraryURL()
|
||||
|
||||
@staticmethod
|
||||
def getDictionary(dictionary):
|
||||
if not dictionary:
|
||||
dictionary = Spellchecker.getDefaultDictionary()
|
||||
try:
|
||||
d = Spellchecker.dictionaries.get(dictionary, None)
|
||||
if d is None:
|
||||
d = EnchantDictionary(dictionary)
|
||||
Spellchecker.dictionaries[d.name] = d
|
||||
return d
|
||||
except Exception as e:
|
||||
return None
|
||||
|
||||
class BasicDictionary:
|
||||
def __init__(self, name):
|
||||
pass
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
raise NotImplemented
|
||||
|
||||
@staticmethod
|
||||
def getLibraryName():
|
||||
raise NotImplemented
|
||||
|
||||
@staticmethod
|
||||
def getLibraryURL():
|
||||
raise NotImplemented
|
||||
|
||||
@staticmethod
|
||||
def getDefaultDictionary():
|
||||
raise NotImplemented
|
||||
|
||||
@staticmethod
|
||||
def availableDictionaries():
|
||||
raise NotImplemented
|
||||
|
||||
def isMisspelled(self, word):
|
||||
raise NotImplemented
|
||||
|
||||
def getSuggestions(self, word):
|
||||
raise NotImplemented
|
||||
|
||||
def isCustomWord(self, word):
|
||||
raise NotImplemented
|
||||
|
||||
def addWord(self, word):
|
||||
raise NotImplemented
|
||||
|
||||
def removeWord(self, word):
|
||||
raise NotImplemented
|
||||
|
||||
class EnchantDictionary(BasicDictionary):
|
||||
|
||||
def __init__(self, name):
|
||||
self._lang = name
|
||||
if not (self._lang and enchant.dict_exists(self._lang)):
|
||||
self._lang = self.getDefaultDictionary()
|
||||
|
||||
self._dict = enchant.Dict(self._lang)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._lang
|
||||
|
||||
@staticmethod
|
||||
def getLibraryName():
|
||||
return "pyEnchant"
|
||||
|
||||
@staticmethod
|
||||
def getLibraryURL():
|
||||
return "https://pypi.org/project/pyenchant/"
|
||||
|
||||
@staticmethod
|
||||
def availableDictionaries():
|
||||
if enchant:
|
||||
return list(map(lambda i: str(i[0]), enchant.list_dicts()))
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def getDefaultDictionary():
|
||||
if not enchant:
|
||||
return None
|
||||
|
||||
default_locale = enchant.get_default_language()
|
||||
if default_locale and not enchant.dict_exists(default_locale):
|
||||
default_locale = None
|
||||
|
||||
if default_locale is None:
|
||||
default_locale = QLocale.system().name()
|
||||
if default_locale is None:
|
||||
default_locale = self.availableDictionaries()[0]
|
||||
|
||||
return default_locale
|
||||
|
||||
def isMisspelled(self, word):
|
||||
return not self._dict.check(word)
|
||||
|
||||
def getSuggestions(self, word):
|
||||
return self._dict.suggest(word)
|
||||
|
||||
def isCustomWord(self, word):
|
||||
return self._dict.is_added(word)
|
||||
|
||||
def addWord(self, word):
|
||||
self._dict.add(word)
|
||||
|
||||
def removeWord(self, word):
|
||||
self._dict.remove(word)
|
|
@ -34,15 +34,10 @@ from manuskript.ui.statusLabel import statusLabel
|
|||
|
||||
# Spellcheck support
|
||||
from manuskript.ui.views.textEditView import textEditView
|
||||
|
||||
try:
|
||||
import enchant
|
||||
except ImportError:
|
||||
enchant = None
|
||||
|
||||
from manuskript.functions import Spellchecker
|
||||
|
||||
class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
dictChanged = pyqtSignal(str)
|
||||
# dictChanged = pyqtSignal(str)
|
||||
|
||||
# Tab indexes
|
||||
TabInfos = 0
|
||||
|
@ -1246,16 +1241,16 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
self.actShowHelp.setChecked(False)
|
||||
|
||||
# Spellcheck
|
||||
if enchant:
|
||||
if Spellchecker.isInstalled():
|
||||
self.menuDict = QMenu(self.tr("Dictionary"))
|
||||
self.menuDictGroup = QActionGroup(self)
|
||||
self.updateMenuDict()
|
||||
self.menuTools.addMenu(self.menuDict)
|
||||
|
||||
self.actSpellcheck.toggled.connect(self.toggleSpellcheck, F.AUC)
|
||||
self.dictChanged.connect(self.mainEditor.setDict, F.AUC)
|
||||
self.dictChanged.connect(self.redacMetadata.setDict, F.AUC)
|
||||
self.dictChanged.connect(self.outlineItemEditor.setDict, F.AUC)
|
||||
# self.dictChanged.connect(self.mainEditor.setDict, F.AUC)
|
||||
# self.dictChanged.connect(self.redacMetadata.setDict, F.AUC)
|
||||
# self.dictChanged.connect(self.outlineItemEditor.setDict, F.AUC)
|
||||
|
||||
else:
|
||||
# No Spell check support
|
||||
|
@ -1271,23 +1266,23 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
|
||||
def updateMenuDict(self):
|
||||
|
||||
if not enchant:
|
||||
if not Spellchecker.isInstalled():
|
||||
return
|
||||
|
||||
self.menuDict.clear()
|
||||
for i in enchant.list_dicts():
|
||||
a = QAction(str(i[0]), self)
|
||||
for i in Spellchecker.availableDictionaries():
|
||||
a = QAction(i, self)
|
||||
a.setCheckable(True)
|
||||
if settings.dict is None:
|
||||
settings.dict = enchant.get_default_language()
|
||||
if str(i[0]) == settings.dict:
|
||||
settings.dict = Spellchecker.getDefaultDictionary()
|
||||
if i == settings.dict:
|
||||
a.setChecked(True)
|
||||
a.triggered.connect(self.setDictionary, F.AUC)
|
||||
self.menuDictGroup.addAction(a)
|
||||
self.menuDict.addAction(a)
|
||||
|
||||
def setDictionary(self):
|
||||
if not enchant:
|
||||
if not Spellchecker.isInstalled():
|
||||
return
|
||||
|
||||
for i in self.menuDictGroup.actions():
|
||||
|
@ -1301,7 +1296,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
w.setDict(settings.dict)
|
||||
|
||||
def openPyEnchantWebPage(self):
|
||||
F.openURL("http://pythonhosted.org/pyenchant/")
|
||||
F.openURL(Spellchecker.getLibraryURL())
|
||||
|
||||
def toggleSpellcheck(self, val):
|
||||
settings.spellcheck = val
|
||||
|
|
|
@ -26,11 +26,6 @@ from manuskript.ui.views.textEditView import textEditView
|
|||
from manuskript.ui.welcome import welcome
|
||||
from manuskript.ui import style as S
|
||||
|
||||
try:
|
||||
import enchant
|
||||
except ImportError:
|
||||
enchant = None
|
||||
|
||||
|
||||
class settingsWindow(QWidget, Ui_Settings):
|
||||
def __init__(self, mainWindow):
|
||||
|
|
|
@ -17,11 +17,7 @@ from manuskript.ui.editors.locker import locker
|
|||
from manuskript.ui.editors.themes import findThemePath, generateTheme, setThemeEditorDatas
|
||||
from manuskript.ui.editors.themes import loadThemeDatas
|
||||
from manuskript.ui.views.MDEditView import MDEditView
|
||||
|
||||
try:
|
||||
import enchant
|
||||
except ImportError:
|
||||
enchant = None
|
||||
from manuskript.functions import Spellchecker
|
||||
|
||||
|
||||
class fullScreenEditor(QWidget):
|
||||
|
@ -54,7 +50,7 @@ class fullScreenEditor(QWidget):
|
|||
# self.topPanel.layout().addStretch(1)
|
||||
|
||||
# Spell checking
|
||||
if enchant:
|
||||
if Spellchecker.isInstalled():
|
||||
self.btnSpellCheck = QPushButton(self)
|
||||
self.btnSpellCheck.setFlat(True)
|
||||
self.btnSpellCheck.setIcon(QIcon.fromTheme("tools-check-spelling"))
|
||||
|
|
|
@ -152,7 +152,7 @@ class BasicHighlighter(QSyntaxHighlighter):
|
|||
if hasattr(self.editor, "spellcheck") and self.editor.spellcheck:
|
||||
for word_object in re.finditer(WORDS, textedText):
|
||||
if (self.editor._dict
|
||||
and not self.editor._dict.check(word_object.group(1))):
|
||||
and self.editor._dict.isMisspelled(word_object.group(1))):
|
||||
format = self.format(word_object.start(1))
|
||||
format.setUnderlineColor(self._misspelledColor)
|
||||
# SpellCheckUnderline fails with some fonts
|
||||
|
|
|
@ -10,11 +10,6 @@ from manuskript.ui.editors.completer import completer
|
|||
from manuskript.ui.views.MDEditView import MDEditView
|
||||
from manuskript.models import references as Ref
|
||||
|
||||
try:
|
||||
import enchant
|
||||
except ImportError:
|
||||
enchant = None
|
||||
|
||||
|
||||
class MDEditCompleter(MDEditView):
|
||||
def __init__(self, parent=None, index=None, html=None, spellcheck=True, highlighting=False, dict="",
|
||||
|
|
|
@ -13,12 +13,7 @@ from manuskript import functions as F
|
|||
from manuskript.models import outlineModel, outlineItem
|
||||
from manuskript.ui.highlighters import BasicHighlighter
|
||||
from manuskript.ui import style as S
|
||||
|
||||
try:
|
||||
import enchant
|
||||
except ImportError:
|
||||
enchant = None
|
||||
|
||||
from manuskript.functions import Spellchecker
|
||||
|
||||
class textEditView(QTextEdit):
|
||||
def __init__(self, parent=None, index=None, html=None, spellcheck=None,
|
||||
|
@ -77,29 +72,16 @@ class textEditView(QTextEdit):
|
|||
self.setReadOnly(True)
|
||||
|
||||
# Spellchecking
|
||||
if enchant and self.spellcheck:
|
||||
try:
|
||||
self._dict = enchant.Dict(self.currentDict if self.currentDict
|
||||
else self.getDefaultLocale())
|
||||
except enchant.errors.DictNotFoundError:
|
||||
self.spellcheck = False
|
||||
if self.spellcheck:
|
||||
self._dict = Spellchecker.getDictionary(self.currentDict)
|
||||
|
||||
else:
|
||||
if not self._dict:
|
||||
self.spellcheck = False
|
||||
|
||||
if self._highlighting and not self.highlighter:
|
||||
self.highlighter = self._highlighterClass(self)
|
||||
self.highlighter.setDefaultBlockFormat(self._defaultBlockFormat)
|
||||
|
||||
def getDefaultLocale(self):
|
||||
default_locale = enchant.get_default_language()
|
||||
if default_locale is None:
|
||||
default_locale = QLocale.system().name()
|
||||
if default_locale is None:
|
||||
default_locale = enchant.list_dicts()[0][0]
|
||||
|
||||
return default_locale
|
||||
|
||||
def setModel(self, model):
|
||||
self._model = model
|
||||
try:
|
||||
|
@ -389,20 +371,18 @@ class textEditView(QTextEdit):
|
|||
|
||||
def setDict(self, d):
|
||||
self.currentDict = d
|
||||
if d and enchant.dict_exists(d):
|
||||
self._dict = enchant.Dict(d)
|
||||
if d:
|
||||
self._dict = Spellchecker.getDictionary(d)
|
||||
if self.highlighter:
|
||||
self.highlighter.rehighlight()
|
||||
|
||||
def toggleSpellcheck(self, v):
|
||||
self.spellcheck = v
|
||||
if enchant and self.spellcheck and not self._dict:
|
||||
if self.currentDict and enchant.dict_exists(self.currentDict):
|
||||
self._dict = enchant.Dict(self.currentDict)
|
||||
elif enchant.get_default_language() and enchant.dict_exists(enchant.get_default_language()):
|
||||
self._dict = enchant.Dict(enchant.get_default_language())
|
||||
else:
|
||||
self.spellcheck = False
|
||||
if self.spellcheck and not self._dict:
|
||||
self._dict = Spellchecker.getDictionary(self.currentDict)
|
||||
|
||||
if not self._dict:
|
||||
self.spellcheck = False
|
||||
|
||||
if self.highlighter:
|
||||
self.highlighter.rehighlight()
|
||||
|
@ -475,12 +455,12 @@ class textEditView(QTextEdit):
|
|||
# suggestions if it is.
|
||||
if self._dict and cursor.hasSelection():
|
||||
text = str(cursor.selectedText())
|
||||
valid = self._dict.check(text)
|
||||
valid = not self._dict.isMisspelled(text)
|
||||
selectedWord = cursor.selectedText()
|
||||
if not valid:
|
||||
spell_menu = QMenu(self.tr('Spelling Suggestions'), self)
|
||||
spell_menu.setIcon(F.themeIcon("spelling"))
|
||||
for word in self._dict.suggest(text):
|
||||
for word in self._dict.getSuggestions(text):
|
||||
action = self.SpellAction(word, spell_menu)
|
||||
action.correct.connect(self.correctWord)
|
||||
spell_menu.addAction(action)
|
||||
|
@ -499,7 +479,7 @@ class textEditView(QTextEdit):
|
|||
# popup_menu.insertSeparator(popup_menu.actions()[0])
|
||||
|
||||
# If word was added to custom dict, give the possibility to remove it
|
||||
elif valid and self._dict.is_added(selectedWord):
|
||||
elif valid and self._dict.isCustomWord(selectedWord):
|
||||
popup_menu.insertSeparator(popup_menu.actions()[0])
|
||||
# Adds: remove from dictionary
|
||||
rmAction = QAction(self.tr("&Remove from custom dictionary"), popup_menu)
|
||||
|
@ -524,12 +504,12 @@ class textEditView(QTextEdit):
|
|||
|
||||
def addWordToDict(self):
|
||||
word = self.sender().data()
|
||||
self._dict.add(word)
|
||||
self._dict.addWord(word)
|
||||
self.highlighter.rehighlight()
|
||||
|
||||
def rmWordFromDict(self):
|
||||
word = self.sender().data()
|
||||
self._dict.remove(word)
|
||||
self._dict.removeWord(word)
|
||||
self.highlighter.rehighlight()
|
||||
|
||||
###############################################################################
|
||||
|
|
Loading…
Reference in a new issue