Complete new Add Dictionaries tool

This commit is contained in:
Veronica Berglyd Olsen
2023-11-20 17:26:12 +01:00
parent e00e64dc46
commit 71d17da483
2 changed files with 157 additions and 17 deletions
+154 -11
View File
@@ -26,24 +26,25 @@ from __future__ import annotations
import logging
from pathlib import Path
from zipfile import ZipFile
from urllib.parse import urljoin
from urllib.request import pathname2url
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QCloseEvent, QDesktopServices, QTextCursor
from PyQt5.QtCore import QUrl, pyqtSlot
from PyQt5.QtWidgets import (
QDialog, QDialogButtonBox, QVBoxLayout, QWidget
QDialog, QDialogButtonBox, QFileDialog, QFrame, QHBoxLayout, QLabel,
QLineEdit, QPlainTextEdit, QPushButton, QVBoxLayout, QWidget, qApp
)
from novelwriter import CONFIG
from novelwriter import CONFIG, SHARED
from novelwriter.common import formatInt, getFileSize
logger = logging.getLogger(__name__)
class GuiDictionaries(QDialog):
C_CODE = 0
C_NAME = 1
C_STATE = 2
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
@@ -51,18 +52,80 @@ class GuiDictionaries(QDialog):
self.setObjectName("GuiDictionaries")
self.setWindowTitle(self.tr("Add Dictionaries"))
self._installPath = None
self._currDicts = set()
iPx = CONFIG.pxInt(4)
mPx = CONFIG.pxInt(8)
sPx = CONFIG.pxInt(16)
self.setMinimumWidth(CONFIG.pxInt(500))
self.setMinimumHeight(CONFIG.pxInt(200))
self.setMinimumHeight(CONFIG.pxInt(300))
# Hunspell Dictionaries
foUrl = "https://www.freeoffice.com/en/download/dictionaries"
loUrl = "https://extensions.libreoffice.org"
self.huInfo = QLabel("<br>".join([
self.tr("Download a dictionary from one of the links, and add it below."),
f"&nbsp;\u203a <a href='{foUrl}'>{foUrl}</a>",
f"&nbsp;\u203a <a href='{loUrl}'>{loUrl}</a>",
]))
self.huInfo.setOpenExternalLinks(True)
self.huInfo.setWordWrap(True)
self.huInput = QLineEdit(self)
self.huBrowse = QPushButton(self)
self.huBrowse.setIcon(SHARED.theme.getIcon("browse"))
self.huBrowse.clicked.connect(self._doBrowseHunspell)
self.huImport = QPushButton(self.tr("Add Dictionary"), self)
self.huImport.setIcon(SHARED.theme.getIcon("add"))
self.huImport.clicked.connect(self._doImportHunspell)
self.huPathBox = QHBoxLayout()
self.huPathBox.addWidget(self.huInput)
self.huPathBox.addWidget(self.huBrowse)
self.huPathBox.setSpacing(iPx)
self.huAddBox = QHBoxLayout()
self.huAddBox.addStretch(1)
self.huAddBox.addWidget(self.huImport)
# Install Path
self.inInfo = QLabel(self.tr("Dictionary install location"))
self.inPath = QLineEdit(self)
self.inPath.setReadOnly(True)
self.inBrowse = QPushButton(self)
self.inBrowse.setIcon(SHARED.theme.getIcon("browse"))
self.inBrowse.clicked.connect(self._doOpenInstallLocation)
self.inBox = QHBoxLayout()
self.inBox.addWidget(self.inPath)
self.inBox.addWidget(self.inBrowse)
self.inBox.setSpacing(iPx)
# Info Box
self.infoBox = QPlainTextEdit(self)
self.infoBox.setReadOnly(True)
self.infoBox.setFixedHeight(4*SHARED.theme.fontPixelSize)
self.infoBox.setFrameStyle(QFrame.Shape.NoFrame)
# Buttons
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
self.buttonBox.rejected.connect(self._doClose)
# Assemble
self.innerBox = QVBoxLayout()
self.innerBox.addWidget(self.huInfo)
self.innerBox.addLayout(self.huPathBox)
self.innerBox.addLayout(self.huAddBox)
self.innerBox.addSpacing(mPx)
self.innerBox.addWidget(self.inInfo)
self.innerBox.addLayout(self.inBox)
self.innerBox.addWidget(self.infoBox)
self.innerBox.setSpacing(iPx)
self.outerBox = QVBoxLayout()
self.outerBox.addWidget(self.buttonBox)
self.outerBox.addLayout(self.innerBox, 0)
self.outerBox.addStretch(1)
self.outerBox.addWidget(self.buttonBox, 0)
self.outerBox.setSpacing(sPx)
self.setLayout(self.outerBox)
@@ -84,8 +147,20 @@ class GuiDictionaries(QDialog):
logger.error("Could not get enchant path")
return False
self._installPath = Path(path).resolve()
if path.is_dir():
pass
self.inPath.setText(str(path))
hunspell = path / "hunspell"
if hunspell.is_dir():
self._currDicts = set(
i.stem for i in hunspell.iterdir() if i.is_file() and i.suffix == ".aff"
)
self._appendLog(self.tr(
"{0} additional dictionaries currently installed"
).format(len(self._currDicts)))
qApp.processEvents()
self.adjustSize()
return True
@@ -103,10 +178,78 @@ class GuiDictionaries(QDialog):
# Private Slots
##
@pyqtSlot()
def _doBrowseHunspell(self):
"""Browse for a Free/Libre Office dictionary."""
extFilter = [
self.tr("Free or Libre Office extension ({0})").format("*.sox *.oxt"),
self.tr("All files ({0})").format("*"),
]
soxFile, _ = QFileDialog.getOpenFileName(
self, self.tr("Browse Files"), "", filter=";;".join(extFilter)
)
if soxFile:
path = Path(soxFile).absolute()
self.huInput.setText(str(path))
return
@pyqtSlot()
def _doImportHunspell(self):
"""Import a hunspell dictionary from .sox or .oxt file."""
temp = self.huInput.text()
output = self._installPath
if not output:
return
if output and temp and (path := Path(temp)).is_file():
hunspell = output / "hunspell"
hunspell.mkdir(exist_ok=True)
try:
with ZipFile(path, mode="r") as zipObj:
for item in zipObj.namelist():
zPath = Path(item)
if zPath.suffix in (".aff", ".dic"):
with zipObj.open(item) as zF:
oPath = hunspell / zPath.name
oPath.write_bytes(zF.read())
size = getFileSize(oPath)
self._appendLog(self.tr(
"Added: {0} [{1}B]"
).format(zPath.name, formatInt(size)))
except Exception as exc:
SHARED.error(self.tr("Could not process dictionary file."), exc=exc)
else:
SHARED.error(self.tr("File not found."))
return
@pyqtSlot()
def _doOpenInstallLocation(self) -> None:
"""Open the dictionary folder."""
path = self.inPath.text()
if Path(path).is_dir():
QDesktopServices.openUrl(
QUrl(urljoin("file:", pathname2url(path)))
)
else:
SHARED.error("Path not found.")
return
@pyqtSlot()
def _doClose(self) -> None:
"""Close the dialog."""
self.close()
return
##
# Internal Functions
##
def _appendLog(self, text: str) -> None:
"""Append a line to the log output."""
self.infoBox.moveCursor(QTextCursor.MoveOperation.End)
if self.infoBox.textCursor().position() > 0:
self.infoBox.insertPlainText("\n")
self.infoBox.insertPlainText(text)
self.infoBox.moveCursor(QTextCursor.MoveOperation.End)
return
# END Class GuiDictionaries
+3 -6
View File
@@ -35,8 +35,7 @@ from novelwriter.gui.doceditor import GuiDocEditor
@pytest.mark.gui
def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
"""Test the main menu Edit and Format entries.
"""
"""Test the main menu Edit and Format entries."""
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True)
# Test Document Action with No Project
@@ -343,8 +342,7 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
@pytest.mark.gui
def testGuiMenu_ContextMenus(qtbot, nwGUI, prjLipsum):
"""Test the context menus.
"""
"""Test the context menus."""
assert nwGUI.openProject(prjLipsum)
assert nwGUI.openDocument("4c4f28287af27")
@@ -423,8 +421,7 @@ def testGuiMenu_ContextMenus(qtbot, nwGUI, prjLipsum):
@pytest.mark.gui
def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
"""Test the Insert menu.
"""
"""Test the Insert menu."""
buildTestProject(nwGUI, projPath)
assert nwGUI.projView.projTree._getTreeItem(C.hSceneDoc) is not None