From e00e64dc460ec21bb1410f295f2cefc6966d038e Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 20 Nov 2023 17:24:49 +0100 Subject: [PATCH] Copy changes from download_dicts branch --- novelwriter/common.py | 8 +++ novelwriter/config.py | 2 + novelwriter/constants.py | 3 + novelwriter/dialogs/updates.py | 39 +++++++---- novelwriter/gui/docviewer.py | 2 +- novelwriter/gui/mainmenu.py | 5 ++ novelwriter/guimain.py | 17 ++++- novelwriter/tools/dictionaries.py | 112 ++++++++++++++++++++++++++++++ novelwriter/tools/manusbuild.py | 5 +- tests/conftest.py | 1 + 10 files changed, 176 insertions(+), 18 deletions(-) create mode 100644 novelwriter/tools/dictionaries.py diff --git a/novelwriter/common.py b/novelwriter/common.py index f914a6d1..2627a1b0 100644 --- a/novelwriter/common.py +++ b/novelwriter/common.py @@ -487,6 +487,14 @@ def makeFileNameSafe(text: str) -> str: return "".join(c for c in text if c.isalnum() or c in allowed) +def getFileSize(path: Path) -> int: + """Return the size of a file.""" + try: + return path.stat().st_size + except Exception: + return -1 + + # =============================================================================================== # # Other Functions # =============================================================================================== # diff --git a/novelwriter/config.py b/novelwriter/config.py index ba422efa..39035371 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -225,6 +225,7 @@ class Config: # Other System Info self.hostName = QSysInfo.machineHostName() self.kernelVer = QSysInfo.kernelVersion() + self.isDebug = False # Packages self.hasEnchant = False # The pyenchant package @@ -484,6 +485,7 @@ class Config: self._recentObj.loadCache() self._checkOptionalPackages() + self.isDebug = logger.getEffectiveLevel() == logging.DEBUG logger.debug("Config instance initialised") diff --git a/novelwriter/constants.py b/novelwriter/constants.py index d8ce3562..4afa7781 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -50,6 +50,9 @@ class nwConst: URL_HELP = "https://github.com/vkbo/novelWriter/discussions" URL_RELEASE = "https://github.com/vkbo/novelWriter/releases/latest" + # Requests + USER_AGENT = "Mozilla/5.0 (compatible; novelWriter (Python))" + # Gui Settings STATUS_MSG_TIMEOUT = 15000 # milliseconds diff --git a/novelwriter/dialogs/updates.py b/novelwriter/dialogs/updates.py index c39a31cc..bbdaf038 100644 --- a/novelwriter/dialogs/updates.py +++ b/novelwriter/dialogs/updates.py @@ -29,10 +29,10 @@ import logging from datetime import datetime from urllib.request import Request, urlopen -from PyQt5.QtGui import QCursor -from PyQt5.QtCore import Qt +from PyQt5.QtGui import QCloseEvent, QCursor +from PyQt5.QtCore import Qt, pyqtSlot from PyQt5.QtWidgets import ( - qApp, QDialog, QHBoxLayout, QVBoxLayout, QDialogButtonBox, QLabel + QWidget, qApp, QDialog, QHBoxLayout, QVBoxLayout, QDialogButtonBox, QLabel ) from novelwriter import CONFIG, SHARED, __version__, __date__ @@ -44,8 +44,8 @@ logger = logging.getLogger(__name__) class GuiUpdates(QDialog): - def __init__(self, mainGui): - super().__init__(parent=mainGui) + def __init__(self, parent: QWidget) -> None: + super().__init__(parent=parent) logger.debug("Create: GuiUpdates") self.setObjectName("GuiUpdates") @@ -94,8 +94,8 @@ class GuiUpdates(QDialog): self.latestLabel.setFont(hFont) # Buttons - self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok) - self.buttonBox.accepted.connect(self._doClose) + self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close) + self.buttonBox.rejected.connect(self._doClose) # Assemble self.innerBox = QHBoxLayout() @@ -114,17 +114,16 @@ class GuiUpdates(QDialog): return - def __del__(self): # pragma: no cover + def __del__(self) -> None: # pragma: no cover logger.debug("Delete: GuiUpdates") return - def checkLatest(self): - """Check for latest release. - """ + def checkLatest(self) -> None: + """Check for latest release.""" qApp.setOverrideCursor(QCursor(Qt.WaitCursor)) urlReq = Request("https://api.github.com/repos/vkbo/novelwriter/releases/latest") - urlReq.add_header("User-Agent", "Mozilla/5.0 (compatible; novelWriter (Python))") + urlReq.add_header("User-Agent", nwConst.USER_AGENT) urlReq.add_header("Accept", "application/vnd.github.v3+json") rawData = {} @@ -161,10 +160,22 @@ class GuiUpdates(QDialog): return ## - # Internal Functions + # Events ## - def _doClose(self): + def closeEvent(self, event: QCloseEvent) -> None: + """Capture the user closing the window.""" + event.accept() + self.deleteLater() + return + + ## + # Private Slots + ## + + @pyqtSlot() + def _doClose(self) -> None: + """Close the dialog.""" self.close() return diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index 8992505d..a0e2c3b9 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -620,7 +620,7 @@ class GuiDocViewHistory: """Debug function to dump history to the logger. Since it is a for loop, it is skipped entirely if log level isn't DEBUG. """ - if logger.getEffectiveLevel() == logging.DEBUG: # pragma: no cover + if CONFIG.isDebug: # pragma: no cover for i, (h, p) in enumerate(zip(self._navHistory, self._posHistory)): logger.debug( "History %02d: %s %13s [x:%d]" % ( diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index a0420144..8c74f270 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -870,6 +870,11 @@ class GuiMainMenu(QMenuBar): self.aEditWordList = self.toolsMenu.addAction(self.tr("Project Word List")) self.aEditWordList.triggered.connect(lambda: self.mainGui.showProjectWordListDialog()) + # Tools > Add Dictionaries + if CONFIG.osWindows or CONFIG.isDebug: + self.aAddDicts = self.toolsMenu.addAction(self.tr("Add Dictionaries")) + self.aAddDicts.triggered.connect(self.mainGui.showDictionariesDialog) + # Tools > Separator self.toolsMenu.addSeparator() diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 8e02e0e6..635e9b8f 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -59,6 +59,7 @@ from novelwriter.dialogs.projsettings import GuiProjectSettings from novelwriter.tools.lipsum import GuiLipsum from novelwriter.tools.manuscript import GuiManuscript from novelwriter.tools.projwizard import GuiProjectWizard +from novelwriter.tools.dictionaries import GuiDictionaries from novelwriter.tools.writingstats import GuiWritingStats from novelwriter.core.coretools import ProjectBuilder @@ -340,7 +341,7 @@ class GuiMain(QMainWindow): logger.debug("Ready: GUI") - if __hexversion__[-2] == "a" and logger.getEffectiveLevel() > logging.DEBUG: + if __hexversion__[-2] == "a" and not CONFIG.isDebug: SHARED.warn(self.tr( "You are running an untested development version of novelWriter. " "Please be careful when working on a live project " @@ -1065,6 +1066,20 @@ class GuiMain(QMainWindow): return + @pyqtSlot() + def showDictionariesDialog(self) -> None: + """Show the download dictionaries dialog.""" + dlgDicts = GuiDictionaries(self) + dlgDicts.setModal(True) + dlgDicts.show() + dlgDicts.raise_() + qApp.processEvents() + if not dlgDicts.initDialog(): + dlgDicts.close() + SHARED.error(self.tr("Could not initialise the dialog.")) + + return + def reportConfErr(self) -> bool: """Checks if the Config module has any errors to report, and let the user know if this is the case. The Config module caches diff --git a/novelwriter/tools/dictionaries.py b/novelwriter/tools/dictionaries.py new file mode 100644 index 00000000..bbf3d6b0 --- /dev/null +++ b/novelwriter/tools/dictionaries.py @@ -0,0 +1,112 @@ +""" +novelWriter – GUI Dictionary Downloader +======================================= + +File History: +Created: 2023-11-19 [2.2rc1] + +This file is a part of novelWriter +Copyright 2018–2023, Veronica Berglyd Olsen + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" +from __future__ import annotations + +import logging + +from pathlib import Path + +from PyQt5.QtGui import QCloseEvent +from PyQt5.QtCore import pyqtSlot +from PyQt5.QtWidgets import ( + QDialog, QDialogButtonBox, QVBoxLayout, QWidget +) + +from novelwriter import CONFIG + +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) + + logger.debug("Create: GuiDictionaries") + self.setObjectName("GuiDictionaries") + self.setWindowTitle(self.tr("Add Dictionaries")) + + sPx = CONFIG.pxInt(16) + + self.setMinimumWidth(CONFIG.pxInt(500)) + self.setMinimumHeight(CONFIG.pxInt(200)) + + # Buttons + self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close) + self.buttonBox.rejected.connect(self._doClose) + + # Assemble + self.outerBox = QVBoxLayout() + self.outerBox.addWidget(self.buttonBox) + self.outerBox.setSpacing(sPx) + + self.setLayout(self.outerBox) + + logger.debug("Ready: GuiDictionaries") + + return + + def __del__(self) -> None: # pragma: no cover + logger.debug("Delete: GuiDictionaries") + return + + def initDialog(self) -> bool: + """Prepare and check that we can proceed.""" + try: + import enchant + path = Path(enchant.get_user_config_dir()) + except Exception: + logger.error("Could not get enchant path") + return False + + if path.is_dir(): + pass + + return True + + ## + # Events + ## + + def closeEvent(self, event: QCloseEvent) -> None: + """Capture the user closing the window.""" + event.accept() + self.deleteLater() + return + + ## + # Private Slots + ## + + @pyqtSlot() + def _doClose(self) -> None: + """Close the dialog.""" + self.close() + return + +# END Class GuiDictionaries diff --git a/novelwriter/tools/manusbuild.py b/novelwriter/tools/manusbuild.py index 390d626c..70235ebf 100644 --- a/novelwriter/tools/manusbuild.py +++ b/novelwriter/tools/manusbuild.py @@ -27,6 +27,7 @@ import logging from pathlib import Path +from PyQt5.QtGui import QCloseEvent from PyQt5.QtCore import QSize, QTimer, Qt, pyqtSlot from PyQt5.QtWidgets import ( QAbstractButton, QAbstractItemView, QDialog, QDialogButtonBox, QFileDialog, @@ -227,7 +228,7 @@ class GuiManuscriptBuild(QDialog): return - def __del__(self): # pragma: no cover + def __del__(self) -> None: # pragma: no cover logger.debug("Delete: GuiManuscriptBuild") return @@ -235,7 +236,7 @@ class GuiManuscriptBuild(QDialog): # Events ## - def closeEvent(self, event): + def closeEvent(self, event: QCloseEvent) -> None: """Capture the user closing the window so we can save GUI settings. """ diff --git a/tests/conftest.py b/tests/conftest.py index 1969567e..1886bfd4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -159,6 +159,7 @@ def nwGUI(qtbot, monkeypatch, functionFixture): nwGUI = main(["--testmode", f"--config={_TMP_CONF}", f"--data={_TMP_CONF}"]) qtbot.addWidget(nwGUI) resetConfigVars() + SHARED._alert = None nwGUI.docEditor.initEditor() nwGUI.show()