From e1a1b5980e21726cf57965324afba47296f26010 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 1 May 2024 20:23:59 +0200 Subject: [PATCH 1/7] Make sure build settings are closed before deletion --- novelwriter/tools/manuscript.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 1cec67d5..635a9d24 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -30,9 +30,9 @@ from datetime import datetime from time import time from typing import TYPE_CHECKING -from PyQt5.QtCore import QTimer, QUrl, Qt, pyqtSignal, pyqtSlot +from PyQt5.QtCore import Qt, QTimer, QUrl, pyqtSignal, pyqtSlot from PyQt5.QtGui import QCloseEvent, QColor, QCursor, QFont, QPalette, QResizeEvent -from PyQt5.QtPrintSupport import QPrintPreviewDialog, QPrinter +from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog from PyQt5.QtWidgets import ( QAbstractItemView, QApplication, QDialog, QFormLayout, QGridLayout, QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QPushButton, @@ -319,6 +319,8 @@ class GuiManuscript(QDialog): """Delete the currently selected build settings entry.""" if build := self._getSelectedBuild(): if SHARED.question(self.tr("Delete build '{0}'?".format(build.name))): + if dialog := self._findSettingsDialog(build.buildID): + dialog.close() self._builds.removeBuild(build.buildID) self._updateBuildsList() return @@ -467,14 +469,10 @@ class GuiManuscript(QDialog): def _openSettingsDialog(self, build: BuildSettings) -> None: """Open the build settings dialog.""" - for obj in self.mainGui.children(): - # Don't open a second dialog if one exists - if isinstance(obj, GuiBuildSettings): - if obj.buildID == build.buildID: - logger.debug("Found instance of GuiBuildSettings") - obj.show() - obj.raise_() - return + if dialog := self._findSettingsDialog(build.buildID): + dialog.show() + dialog.raise_() + return dlgSettings = GuiBuildSettings(self.mainGui, build) dlgSettings.setModal(False) @@ -507,6 +505,15 @@ class GuiManuscript(QDialog): self._updateBuildsList() return + def _findSettingsDialog(self, buildID: str) -> GuiBuildSettings | None: + """Return an open build settings dialog for a given build, if + one exists. + """ + for obj in SHARED.mainGui.children(): + if isinstance(obj, GuiBuildSettings) and obj.buildID == buildID: + return obj + return None + # END Class GuiManuscript From d0a45bcecac1ce621994e1f446dd7a5cbffd08da Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 1 May 2024 20:30:32 +0200 Subject: [PATCH 2/7] Make sure build map is cleared when list is updated --- novelwriter/tools/manuscript.py | 1 + 1 file changed, 1 insertion(+) diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 635a9d24..72a3442e 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -487,6 +487,7 @@ class GuiManuscript(QDialog): def _updateBuildsList(self) -> None: """Update the list of available builds.""" self.buildList.clear() + self._buildMap.clear() for key, name in self._builds.builds(): bItem = QListWidgetItem() bItem.setText(name) From ceec43db6716de3f5f0471d6eda4daf008ff64a2 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 1 May 2024 22:09:43 +0200 Subject: [PATCH 3/7] Make sure open document is saved before we update preview or build manuscript (#1835) --- novelwriter/tools/manusbuild.py | 7 ++++--- novelwriter/tools/manuscript.py | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/novelwriter/tools/manusbuild.py b/novelwriter/tools/manusbuild.py index 68b5e867..e27e8b6a 100644 --- a/novelwriter/tools/manusbuild.py +++ b/novelwriter/tools/manusbuild.py @@ -44,9 +44,7 @@ from novelwriter.core.item import NWItem from novelwriter.enum import nwBuildFmt from novelwriter.extensions.modified import NIconToolButton from novelwriter.extensions.simpleprogress import NProgressSimple -from novelwriter.types import ( - QtAlignCenter, QtDialogClose, QtRoleAction, QtRoleReject, QtUserRole -) +from novelwriter.types import QtAlignCenter, QtDialogClose, QtRoleAction, QtRoleReject, QtUserRole logger = logging.getLogger(__name__) @@ -328,6 +326,9 @@ class GuiManuscriptBuild(QDialog): ): return False + # Make sure editor content is saved before we start + SHARED.mainGui.saveDocument() + docBuild = NWBuildDocument(SHARED.project, self._build) docBuild.queueAll() diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 72a3442e..68a30f6f 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -342,6 +342,9 @@ class GuiManuscript(QDialog): if not (build := self._getSelectedBuild()): return + # Make sure editor content is saved before we start + SHARED.mainGui.saveDocument() + docBuild = NWBuildDocument(SHARED.project, build) docBuild.setPreviewMode(True) docBuild.queueAll() From cfb19a697a74ccb4c95db451eee9726898aa969b Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 1 May 2024 22:10:54 +0200 Subject: [PATCH 4/7] Restore build preview scroll bar after update (#1837) --- novelwriter/tools/manuscript.py | 11 ++++++----- tests/test_tools/test_tools_manuscript.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 68a30f6f..ed25277b 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -752,6 +752,7 @@ class _PreviewWidget(QTextBrowser): self._docTime = 0 self._buildName = "" + self._scrollPos = 0 # Document Setup dPalette = self.palette() @@ -848,6 +849,7 @@ class _PreviewWidget(QTextBrowser): self.buildProgress.setValue(0) self.buildProgress.setCentreText(None) self.buildProgress.setVisible(True) + self._scrollPos = self.verticalScrollBar().value() self.setPlaceholderText("") self.clear() return @@ -860,7 +862,6 @@ class _PreviewWidget(QTextBrowser): def setContent(self, data: dict) -> None: """Set the content of the preview widget.""" - sPos = self.verticalScrollBar().value() QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor)) self.buildProgress.setCentreText(self.tr("Processing ...")) @@ -877,7 +878,6 @@ class _PreviewWidget(QTextBrowser): cursor = self.textCursor() cursor.insertText("\t") - self.verticalScrollBar().setValue(sPos) self._docTime = checkInt(data.get("time"), 0) self._updateBuildAge() @@ -888,7 +888,7 @@ class _PreviewWidget(QTextBrowser): self.buildProgress.setCentreText(self.tr("Done")) QApplication.restoreOverrideCursor() QApplication.processEvents() - QTimer.singleShot(300, self._hideProgress) + QTimer.singleShot(300, self._postUpdate) return @@ -943,9 +943,10 @@ class _PreviewWidget(QTextBrowser): return @pyqtSlot() - def _hideProgress(self) -> None: - """Clean up the build progress bar.""" + def _postUpdate(self) -> None: + """Run tasks after content update.""" self.buildProgress.setVisible(False) + self.verticalScrollBar().setValue(self._scrollPos) return ## diff --git a/tests/test_tools/test_tools_manuscript.py b/tests/test_tools/test_tools_manuscript.py index 5e80767f..b8856516 100644 --- a/tests/test_tools/test_tools_manuscript.py +++ b/tests/test_tools/test_tools_manuscript.py @@ -270,7 +270,7 @@ def testManuscript_Features(monkeypatch, qtbot, nwGUI, projPath, mockRnd): assert manus.docPreview.document().defaultTextOption().alignment() == QtAlignAbsolute # Tests are too fast to trigger this one, so we trigger it manually to ensure it isn't failing - manus.docPreview._hideProgress() + manus.docPreview._postUpdate() # Builds # ====== From acc463eb768dbd03aa7803516103e183116827a6 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 2 May 2024 19:38:13 +0200 Subject: [PATCH 5/7] Prune some unneeded translation strings --- i18n/nw_base.ts | 131 +++++++++++++----------------------------------- i18n/qtbase.py | 16 ------ pkgutils.py | 66 ++++++++++++------------ 3 files changed, 69 insertions(+), 144 deletions(-) diff --git a/i18n/nw_base.ts b/i18n/nw_base.ts index 87c473e3..8e5e5379 100644 --- a/i18n/nw_base.ts +++ b/i18n/nw_base.ts @@ -2198,57 +2198,57 @@ GuiManuscriptBuild - + Build Manuscript - + Output Format - + Table of Contents - + Path - + File Name - + Reset file name to default - + Open Folder - + &Build - + Select Folder - + Output folder does not exist. - + The file already exists. Do you want to overwrite it? @@ -2415,7 +2415,7 @@ GuiOutlineToolBar - + Outline of @@ -3860,65 +3860,6 @@ - - QWizard - - - Go Back - - - - - < &Back - - - - - Continue - - - - - &Next - - - - - &Next > - - - - - Commit - - - - - Done - - - - - &Finish - - - - - - Cancel - - - - - Help - - - - - &Help - - - SharedData @@ -4041,32 +3982,32 @@ _DetailsWidget - + Setting - + Value - + Name - + Selection - + Title - + Hidden @@ -4438,27 +4379,27 @@ _PreviewWidget - + Press the "Preview" button to generate ... - + Processing ... - + Done - + Unknown - + Built @@ -4557,59 +4498,59 @@ _StatsWidget - - + + Words - - + + Characters - + Words in Headings - + Words in Text - + Headings - + Paragraphs - + Characters in Headings - + Characters in Text - + Characters, No Spaces - + Characters in Headings, No Spaces - + Characters in Text, No Spaces diff --git a/i18n/qtbase.py b/i18n/qtbase.py index df3ee3e1..90f6f9ae 100644 --- a/i18n/qtbase.py +++ b/i18n/qtbase.py @@ -47,19 +47,3 @@ QT_TRANSLATE_NOOP("QPlatformTheme", "Help") QT_TRANSLATE_NOOP("QPlatformTheme", "Apply") QT_TRANSLATE_NOOP("QPlatformTheme", "Reset") QT_TRANSLATE_NOOP("QPlatformTheme", "Restore Defaults") - -# QWizard -# ======= - -QT_TRANSLATE_NOOP("QWizard", "Go Back") -QT_TRANSLATE_NOOP("QWizard", "< &Back") -QT_TRANSLATE_NOOP("QWizard", "Continue") -QT_TRANSLATE_NOOP("QWizard", "&Next") -QT_TRANSLATE_NOOP("QWizard", "&Next >") -QT_TRANSLATE_NOOP("QWizard", "Commit") -QT_TRANSLATE_NOOP("QWizard", "Done") -QT_TRANSLATE_NOOP("QWizard", "&Finish") -QT_TRANSLATE_NOOP("QWizard", "Cancel") -QT_TRANSLATE_NOOP("QWizard", "Cancel") -QT_TRANSLATE_NOOP("QWizard", "Help") -QT_TRANSLATE_NOOP("QWizard", "&Help") diff --git a/pkgutils.py b/pkgutils.py index 19152c41..1147228d 100755 --- a/pkgutils.py +++ b/pkgutils.py @@ -25,13 +25,13 @@ along with this program. If not, see . """ from __future__ import annotations -import os -import sys -import shutil -import zipfile import datetime -import subprocess import email.utils +import os +import shutil +import subprocess +import sys +import zipfile OS_NONE = 0 OS_LINUX = 1 @@ -330,59 +330,59 @@ def buildQtI18nTS(sysArgs: list[str]) -> None: print("Scanning Source Tree:") print("") - srcList = [os.path.join("i18n", "qtbase.py")] - for nRoot, _, nFiles in os.walk("novelwriter"): - if os.path.isdir(nRoot): - for aFile in nFiles: - aPath = os.path.join(nRoot, aFile) - if os.path.isfile(aPath) and aFile.endswith(".py"): - srcList.append(aPath) + sources = [os.path.join("i18n", "qtbase.py")] + for root, _, files in os.walk("novelwriter"): + if os.path.isdir(root): + for file in files: + source = os.path.join(root, file) + if os.path.isfile(source) and file.endswith(".py"): + sources.append(source) - for aSource in srcList: - print(aSource) + for source in sources: + print(source) print("") print("TS Files to Update:") print("") - tsList = [] + translations = [] if len(sysArgs) >= 2: - for anArg in sysArgs[1:]: - if not (anArg.startswith("i18n") and anArg.endswith(".ts")): + for arg in sysArgs[1:]: + if not (arg.startswith("i18n") and arg.endswith(".ts")): continue - fName = os.path.basename(anArg) - if not fName.startswith("nw_") and len(fName) > 6: - print("Skipping non-novelWriter TS file %s" % fName) + file = os.path.basename(arg) + if not file.startswith("nw_") and len(file) > 6: + print("Skipping non-novelWriter TS file %s" % file) continue - if os.path.isfile(anArg): - tsList.append(anArg) - elif os.path.exists(anArg): + if os.path.isfile(arg): + translations.append(arg) + elif os.path.exists(arg): pass else: # Create an empty new language file - lCode = fName[3:-3] - writeFile(anArg, ( + langCode = file[3:-3] + writeFile(arg, ( "\n" "\n" - f"\n" + f"\n" )) - tsList.append(anArg) + translations.append(arg) else: print("No translation files selected for update ...") print("") return - for aTS in tsList: - print(aTS) + for translation in translations: + print(translation) print("") print("Updating Language Files:") print("") # Using the pylupdate tool from PyQt6 as it supports TS file format 2.1. - lupdate(srcList, tsList, no_obsolete=True, no_summary=False) + lupdate(sources, translations, no_obsolete=True, no_summary=False) print("") @@ -563,7 +563,7 @@ def importI18nUpdates(sysArgs: list[str]) -> None: def makeMinimalPackage(targetOS: int) -> None: """Pack the core source file in a single zip file.""" - from zipfile import ZipFile, ZIP_DEFLATED + from zipfile import ZIP_DEFLATED, ZipFile print("") print("Building Minimal ZIP File") @@ -926,8 +926,8 @@ def makeForLaunchpad(doSign: bool = False, isFirst: bool = False) -> None: def makeAppImage(sysArgs: list[str]) -> list[str]: """Build an AppImage.""" - import glob import argparse + import glob try: import python_appimage # noqa: F401 # type: ignore @@ -1144,9 +1144,9 @@ def makeWindowsEmbedded(sysArgs: list[str]) -> None: """Set up a package with embedded Python and dependencies for Windows installation. """ + import compileall import urllib.request import zipfile - import compileall print("") print("Build Standalone Windows Package") From 5faeb12a745e695ec231b43c44309f9e8d5a4c4e Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 2 May 2024 21:02:23 +0200 Subject: [PATCH 6/7] Close non-modal dialogs when project closes --- novelwriter/shared.py | 20 ++++++++++++++++---- tests/test_tools/test_tools_manuscript.py | 12 +++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/novelwriter/shared.py b/novelwriter/shared.py index 25410871..be323386 100644 --- a/novelwriter/shared.py +++ b/novelwriter/shared.py @@ -26,21 +26,21 @@ from __future__ import annotations import logging +from pathlib import Path from time import time from typing import TYPE_CHECKING, TypeVar -from pathlib import Path from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, pyqtSignal from PyQt5.QtWidgets import QFileDialog, QMessageBox, QWidget -from novelwriter.common import formatFileFilter +from novelwriter.common import formatFileFilter from novelwriter.constants import nwFiles from novelwriter.core.spellcheck import NWSpellEnchant if TYPE_CHECKING: # pragma: no cover - from novelwriter.guimain import GuiMain - from novelwriter.gui.theme import GuiTheme from novelwriter.core.project import NWProject + from novelwriter.gui.theme import GuiTheme + from novelwriter.guimain import GuiMain logger = logging.getLogger(__name__) @@ -198,6 +198,7 @@ class SharedData(QObject): def closeProject(self) -> None: """Close the current project.""" + self._closeDialogs() self.project.closeProject(self._idleTime) self._resetProject() self._resetIdleTimer() @@ -356,6 +357,17 @@ class SharedData(QObject): self._idleTime = 0.0 return + def _closeDialogs(self) -> None: + """Close non-modal dialogs.""" + from novelwriter.tools.manuscript import GuiManuscript + from novelwriter.tools.writingstats import GuiWritingStats + + for widget in self.mainGui.children(): + if isinstance(widget, (GuiManuscript, GuiWritingStats)): + widget.close() + + return + # END Class SharedData diff --git a/tests/test_tools/test_tools_manuscript.py b/tests/test_tools/test_tools_manuscript.py index b8856516..00b26b4b 100644 --- a/tests/test_tools/test_tools_manuscript.py +++ b/tests/test_tools/test_tools_manuscript.py @@ -20,18 +20,18 @@ along with this program. If not, see . """ from __future__ import annotations -import pytest import sys from pathlib import Path -from pytestqt.qtbot import QtBot + +import pytest from mocked import causeOSError -from tools import C, buildTestProject - from PyQt5.QtCore import pyqtSlot from PyQt5.QtPrintSupport import QPrintPreviewDialog from PyQt5.QtWidgets import QAction, QListWidgetItem +from pytestqt.qtbot import QtBot +from tools import C, buildTestProject from novelwriter import CONFIG, SHARED from novelwriter.constants import nwHeadFmt @@ -81,7 +81,9 @@ def testManuscript_Init(monkeypatch, qtbot: QtBot, nwGUI: GuiMain, projPath: Pat manus.show() manus.loadContent() assert manus.docPreview.toPlainText().strip() == "" - manus.close() + + nwGUI.closeProject() # This should auto-close the manuscript tool + assert manus.isHidden() # qtbot.stop() From 976ce16ac2b908af13a54e1616741ee4c8cf218f Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 2 May 2024 21:04:39 +0200 Subject: [PATCH 7/7] Update imports --- tests/test_tools/test_tools_manuscript.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_tools/test_tools_manuscript.py b/tests/test_tools/test_tools_manuscript.py index 00b26b4b..33f75c14 100644 --- a/tests/test_tools/test_tools_manuscript.py +++ b/tests/test_tools/test_tools_manuscript.py @@ -26,12 +26,10 @@ from pathlib import Path import pytest -from mocked import causeOSError from PyQt5.QtCore import pyqtSlot from PyQt5.QtPrintSupport import QPrintPreviewDialog from PyQt5.QtWidgets import QAction, QListWidgetItem from pytestqt.qtbot import QtBot -from tools import C, buildTestProject from novelwriter import CONFIG, SHARED from novelwriter.constants import nwHeadFmt @@ -42,6 +40,9 @@ from novelwriter.tools.manuscript import GuiManuscript from novelwriter.tools.manussettings import GuiBuildSettings from novelwriter.types import QtAlignAbsolute, QtAlignJustify, QtDialogApply, QtDialogSave +from tests.mocked import causeOSError +from tests.tools import C, buildTestProject + @pytest.mark.gui def testManuscript_Init(monkeypatch, qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockRnd):