diff --git a/manuskript/functions/__init__.py b/manuskript/functions/__init__.py index 9a1ea876..2975710e 100644 --- a/manuskript/functions/__init__.py +++ b/manuskript/functions/__init__.py @@ -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 diff --git a/manuskript/functions/spellchecker.py b/manuskript/functions/spellchecker.py new file mode 100644 index 00000000..d5b259bb --- /dev/null +++ b/manuskript/functions/spellchecker.py @@ -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) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 87bce2c7..6ae76982 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -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 diff --git a/manuskript/settingsWindow.py b/manuskript/settingsWindow.py index 96116ae3..b1fcfc7a 100644 --- a/manuskript/settingsWindow.py +++ b/manuskript/settingsWindow.py @@ -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): diff --git a/manuskript/ui/editors/fullScreenEditor.py b/manuskript/ui/editors/fullScreenEditor.py index 47afe8b0..dfbefbf7 100644 --- a/manuskript/ui/editors/fullScreenEditor.py +++ b/manuskript/ui/editors/fullScreenEditor.py @@ -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")) diff --git a/manuskript/ui/highlighters/basicHighlighter.py b/manuskript/ui/highlighters/basicHighlighter.py index 6e45eba2..d20b4c14 100644 --- a/manuskript/ui/highlighters/basicHighlighter.py +++ b/manuskript/ui/highlighters/basicHighlighter.py @@ -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 diff --git a/manuskript/ui/views/MDEditCompleter.py b/manuskript/ui/views/MDEditCompleter.py index 5be00afd..e0db6808 100644 --- a/manuskript/ui/views/MDEditCompleter.py +++ b/manuskript/ui/views/MDEditCompleter.py @@ -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="", diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py index 38983d2d..85bd0249 100644 --- a/manuskript/ui/views/textEditView.py +++ b/manuskript/ui/views/textEditView.py @@ -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() ###############################################################################