From 2b8338f5373913571e8fe13951a054dab6a8673d Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 2 May 2024 22:13:35 +0200 Subject: [PATCH] Clean up Main GUI inheritance --- novelwriter/dialogs/wordlist.py | 10 ++---- novelwriter/gui/doceditor.py | 21 +++++-------- novelwriter/gui/noveltree.py | 16 +++------- novelwriter/gui/projtree.py | 22 +++++--------- novelwriter/gui/sidebar.py | 4 +-- novelwriter/gui/statusbar.py | 14 ++++----- novelwriter/guimain.py | 49 ++++++++++++++---------------- novelwriter/shared.py | 6 ++++ novelwriter/tools/writingstats.py | 4 +-- tests/test_gui/test_gui_guimain.py | 18 +++++------ 10 files changed, 68 insertions(+), 96 deletions(-) diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py index 54ec2f2f..721ab91e 100644 --- a/novelwriter/dialogs/wordlist.py +++ b/novelwriter/dialogs/wordlist.py @@ -25,14 +25,13 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING from pathlib import Path from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt5.QtGui import QCloseEvent from PyQt5.QtWidgets import ( QAbstractItemView, QApplication, QDialog, QDialogButtonBox, QFileDialog, - QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout + QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout, QWidget ) from novelwriter import CONFIG, SHARED @@ -42,9 +41,6 @@ from novelwriter.extensions.configlayout import NColourLabel from novelwriter.extensions.modified import NIconToolButton from novelwriter.types import QtDialogClose, QtDialogSave -if TYPE_CHECKING: # pragma: no cover - from novelwriter.guimain import GuiMain - logger = logging.getLogger(__name__) @@ -52,8 +48,8 @@ class GuiWordList(QDialog): newWordListReady = pyqtSignal() - def __init__(self, mainGui: GuiMain) -> None: - super().__init__(parent=mainGui) + def __init__(self, parent: QWidget) -> None: + super().__init__(parent=parent) logger.debug("Create: GuiWordList") self.setObjectName("GuiWordList") diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 97b336c1..662b1c35 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -36,7 +36,6 @@ import logging from enum import Enum from time import time -from typing import TYPE_CHECKING from PyQt5.QtCore import ( QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal, @@ -69,9 +68,6 @@ from novelwriter.types import ( QtMoveAnchor, QtMoveLeft, QtMoveRight ) -if TYPE_CHECKING: # pragma: no cover - from novelwriter.guimain import GuiMain - logger = logging.getLogger(__name__) @@ -107,15 +103,14 @@ class GuiDocEditor(QPlainTextEdit): requestProjectItemSelected = pyqtSignal(str, bool) requestProjectItemRenamed = pyqtSignal(str, str) requestNewNoteCreation = pyqtSignal(str, nwItemClass) + requestNextDocument = pyqtSignal(str, bool) - def __init__(self, mainGui: GuiMain) -> None: - super().__init__(parent=mainGui) + def __init__(self, parent: QWidget) -> None: + super().__init__(parent=parent) logger.debug("Create: GuiDocEditor") # Class Variables - self.mainGui = mainGui - self._nwDocument = None self._nwItem = None @@ -1319,9 +1314,8 @@ class GuiDocEditor(QPlainTextEdit): self.docSearch.setResultCount(0, 0) self._lastFind = None if CONFIG.searchNextFile and not goBack: - self.mainGui.openNextDocument( - self._docHandle, wrapAround=CONFIG.searchLoop - ) + self.requestNextDocument.emit(self._docHandle, CONFIG.searchLoop) + QApplication.processEvents() self.beginSearch() self.setFocus() return @@ -1340,9 +1334,8 @@ class GuiDocEditor(QPlainTextEdit): if resIdx > maxIdx and self._docHandle: if CONFIG.searchNextFile and not goBack: - self.mainGui.openNextDocument( - self._docHandle, wrapAround=CONFIG.searchLoop - ) + self.requestNextDocument.emit(self._docHandle, CONFIG.searchLoop) + QApplication.processEvents() self.beginSearch() self.setFocus() return diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 6643df47..68aa8c46 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -29,9 +29,8 @@ import logging from enum import Enum from time import time -from typing import TYPE_CHECKING -from PyQt5.QtCore import QModelIndex, QPoint, Qt, pyqtSlot, pyqtSignal +from PyQt5.QtCore import QModelIndex, QPoint, Qt, pyqtSignal, pyqtSlot from PyQt5.QtGui import QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent from PyQt5.QtWidgets import ( QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, @@ -52,9 +51,6 @@ from novelwriter.types import ( QtUserRole ) -if TYPE_CHECKING: # pragma: no cover - from novelwriter.guimain import GuiMain - logger = logging.getLogger(__name__) @@ -74,10 +70,8 @@ class GuiNovelView(QWidget): selectedItemChanged = pyqtSignal(str) openDocumentRequest = pyqtSignal(str, Enum, str, bool) - def __init__(self, mainGui: GuiMain) -> None: - super().__init__(parent=mainGui) - - self.mainGui = mainGui + def __init__(self, parent: QWidget) -> None: + super().__init__(parent=parent) # Build GUI self.novelTree = GuiNovelTree(self) @@ -202,7 +196,6 @@ class GuiNovelToolBar(QWidget): logger.debug("Create: GuiNovelToolBar") self.novelView = novelView - self.mainGui = novelView.mainGui iSz = SHARED.theme.baseIconSize mPx = CONFIG.pxInt(2) @@ -378,7 +371,6 @@ class GuiNovelTree(QTreeWidget): logger.debug("Create: GuiNovelTree") self.novelView = novelView - self.mainGui = novelView.mainGui # Internal Variables self._treeMap = {} @@ -493,7 +485,7 @@ class GuiNovelTree(QTreeWidget): if rootHandle is None: rootHandle = SHARED.project.tree.findRoot(nwItemClass.NOVEL) - treeChanged = self.mainGui.projView.changedSince(self._lastBuild) + treeChanged = SHARED.mainGui.projView.changedSince(self._lastBuild) indexChanged = SHARED.project.index.rootChangedSince(rootHandle, self._lastBuild) if not (treeChanged or indexChanged or overRide): logger.debug("No changes have been made to the novel index") diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index ddb03f5e..34a324da 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -30,7 +30,6 @@ import logging from enum import Enum from time import time -from typing import TYPE_CHECKING from PyQt5.QtCore import QPoint, Qt, QTimer, pyqtSignal, pyqtSlot from PyQt5.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon, QMouseEvent, QPalette @@ -57,9 +56,6 @@ from novelwriter.types import ( QtUserRole ) -if TYPE_CHECKING: # pragma: no cover - from novelwriter.guimain import GuiMain - logger = logging.getLogger(__name__) @@ -81,10 +77,8 @@ class GuiProjectView(QWidget): # Requests for the main GUI projectSettingsRequest = pyqtSignal(int) - def __init__(self, mainGui: GuiMain) -> None: - super().__init__(parent=mainGui) - - self.mainGui = mainGui + def __init__(self, parent: QWidget) -> None: + super().__init__(parent=parent) # Build GUI self.projTree = GuiProjectTree(self) @@ -263,7 +257,6 @@ class GuiProjectToolBar(QWidget): self.projView = projView self.projTree = projView.projTree - self.mainGui = projView.mainGui iSz = SHARED.theme.baseIconSize mPx = CONFIG.pxInt(2) @@ -499,7 +492,6 @@ class GuiProjectTree(QTreeWidget): logger.debug("Create: GuiProjectTree") self.projView = projView - self.mainGui = projView.mainGui # Internal Variables self._treeMap: dict[str, QTreeWidgetItem] = {} @@ -1010,8 +1002,7 @@ class GuiProjectTree(QTreeWidget): trItemP.takeChild(tIndex) for dHandle in reversed(self.getTreeFromHandle(tHandle)): - if self.mainGui.docEditor.docHandle == dHandle: - self.mainGui.closeDocument() + SHARED.closeDocument(dHandle) SHARED.project.removeItem(dHandle) self._treeMap.pop(dHandle, None) @@ -1410,7 +1401,7 @@ class GuiProjectTree(QTreeWidget): if not newFile: itemList.remove(tHandle) - dlgMerge = GuiDocMerge(self.mainGui, tHandle, itemList) + dlgMerge = GuiDocMerge(SHARED.mainGui, tHandle, itemList) dlgMerge.exec() if dlgMerge.result() == QDialog.DialogCode.Accepted: @@ -1451,7 +1442,8 @@ class GuiProjectTree(QTreeWidget): if newFile: self.revealNewTreeItem(mHandle, nHandle=tHandle, wordCount=True) - self.mainGui.openDocument(mHandle, doScroll=True) + self.projView.openDocumentRequest.emit(mHandle, nwDocMode.EDIT, "", False) + self.projView.setSelectedHandle(mHandle, doScroll=True) if mrgData.get("moveToTrash", False): for sHandle in reversed(mrgData.get("finalItems", [])): @@ -1480,7 +1472,7 @@ class GuiProjectTree(QTreeWidget): logger.error("Only valid document items can be split") return False - dlgSplit = GuiDocSplit(self.mainGui, tHandle) + dlgSplit = GuiDocSplit(SHARED.mainGui, tHandle) dlgSplit.exec() if dlgSplit.result() == QDialog.DialogCode.Accepted: diff --git a/novelwriter/gui/sidebar.py b/novelwriter/gui/sidebar.py index d5dce50d..0620ad2d 100644 --- a/novelwriter/gui/sidebar.py +++ b/novelwriter/gui/sidebar.py @@ -27,8 +27,8 @@ import logging from typing import TYPE_CHECKING -from PyQt5.QtGui import QPalette from PyQt5.QtCore import QEvent, QPoint, QSize, pyqtSignal +from PyQt5.QtGui import QPalette from PyQt5.QtWidgets import QMenu, QVBoxLayout, QWidget from novelwriter import CONFIG, SHARED @@ -58,7 +58,7 @@ class GuiSideBar(QWidget): iSz = QSize(iPx, iPx) self.setContentsMargins(0, 0, 0, 0) - self.installEventFilter(StatusTipFilter(mainGui)) + self.installEventFilter(StatusTipFilter(self.mainGui)) # Buttons self.tbProject = NIconToolButton(self, iSz) diff --git a/novelwriter/gui/statusbar.py b/novelwriter/gui/statusbar.py index 1992d609..6b51731a 100644 --- a/novelwriter/gui/statusbar.py +++ b/novelwriter/gui/statusbar.py @@ -27,26 +27,23 @@ import logging from datetime import datetime from time import time -from typing import TYPE_CHECKING, Literal +from typing import Literal -from PyQt5.QtCore import pyqtSlot, QLocale -from PyQt5.QtWidgets import QApplication, QStatusBar, QLabel +from PyQt5.QtCore import QLocale, pyqtSlot +from PyQt5.QtWidgets import QApplication, QLabel, QStatusBar, QWidget from novelwriter import CONFIG, SHARED from novelwriter.common import formatTime from novelwriter.constants import nwConst from novelwriter.extensions.statusled import StatusLED -if TYPE_CHECKING: # pragma: no cover - from novelwriter.guimain import GuiMain - logger = logging.getLogger(__name__) class GuiMainStatus(QStatusBar): - def __init__(self, mainGui: GuiMain) -> None: - super().__init__(parent=mainGui) + def __init__(self, parent: QWidget) -> None: + super().__init__(parent=parent) logger.debug("Create: GuiMainStatus") @@ -238,6 +235,7 @@ class GuiMainStatus(QStatusBar): before starting novelWriter. """ import tracemalloc + from collections import Counter widgets = QApplication.allWidgets() diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 533de19e..71b061b2 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -263,6 +263,7 @@ class GuiMain(QMainWindow): self.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem) self.docEditor.requestNewNoteCreation.connect(self.projView.createNewNote) self.docEditor.docTextChanged.connect(self.projSearch.textChanged) + self.docEditor.requestNextDocument.connect(self.openNextDocument) self.docViewer.documentLoaded.connect(self.docViewerPanel.updateHandle) self.docViewer.loadDocumentTagRequest.connect(self._followTag) @@ -549,36 +550,30 @@ class GuiMain(QMainWindow): return True - def openNextDocument(self, tHandle: str, wrapAround: bool = False) -> bool: + @pyqtSlot(str, bool) + def openNextDocument(self, tHandle: str, wrapAround: bool) -> None: """Open the next document in the project tree, following the document with the given handle. Stop when reaching the end. """ - if not SHARED.hasProject: - logger.error("No project open") - return False - - nHandle = None # The next handle after tHandle - fHandle = None # The first file handle we encounter - foundIt = False # We've found tHandle, pick the next we see - for tItem in SHARED.project.tree: - if not tItem.isFileType(): - continue - if fHandle is None: - fHandle = tItem.itemHandle - if tItem.itemHandle == tHandle: - foundIt = True - elif foundIt: - nHandle = tItem.itemHandle - break - - if nHandle is not None: - self.openDocument(nHandle, tLine=1, doScroll=True) - return True - elif wrapAround: - self.openDocument(fHandle, tLine=1, doScroll=True) - return False - - return False + if SHARED.hasProject: + nHandle = None # The next handle after tHandle + fHandle = None # The first file handle we encounter + foundIt = False # We've found tHandle, pick the next we see + for tItem in SHARED.project.tree: + if not tItem.isFileType(): + continue + if fHandle is None: + fHandle = tItem.itemHandle + if tItem.itemHandle == tHandle: + foundIt = True + elif foundIt: + nHandle = tItem.itemHandle + break + if nHandle is not None: + self.openDocument(nHandle, tLine=1, doScroll=True) + elif wrapAround: + self.openDocument(fHandle, tLine=1, doScroll=True) + return @pyqtSlot() def saveDocument(self) -> None: diff --git a/novelwriter/shared.py b/novelwriter/shared.py index 40d08c1f..6586f676 100644 --- a/novelwriter/shared.py +++ b/novelwriter/shared.py @@ -171,6 +171,12 @@ class SharedData(QObject): logger.debug("Thread Pool Max Count: %d", QThreadPool.globalInstance().maxThreadCount()) return + def closeDocument(self, tHandle: str | None = None) -> None: + """Close the document editor, optionally a specific document.""" + if tHandle is None or tHandle == self.mainGui.docEditor.docHandle: + self.mainGui.closeDocument() + return + def saveDocument(self) -> None: """Forward save document call to main GUI.""" self.mainGui.saveDocument() diff --git a/novelwriter/tools/writingstats.py b/novelwriter/tools/writingstats.py index ad0a5508..cc59f9f0 100644 --- a/novelwriter/tools/writingstats.py +++ b/novelwriter/tools/writingstats.py @@ -69,8 +69,8 @@ class GuiWritingStats(NToolDialog): FMT_JSON = 0 FMT_CSV = 1 - def __init__(self, mainGui: GuiMain) -> None: - super().__init__(parent=mainGui) + def __init__(self, parent: GuiMain) -> None: + super().__init__(parent=parent) logger.debug("Create: GuiWritingStats") self.setObjectName("GuiWritingStats") diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index dc6ffcfc..202d829b 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -21,24 +21,25 @@ along with this program. If not, see . from __future__ import annotations import sys -import pytest from shutil import copyfile -from tools import C, NWD_IGNORE, cmpFiles, buildTestProject, XML_IGNORE +import pytest -from PyQt5.QtGui import QPalette from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QMenu, QInputDialog +from PyQt5.QtGui import QPalette +from PyQt5.QtWidgets import QInputDialog, QMenu from novelwriter import CONFIG, SHARED +from novelwriter.dialogs.editlabel import GuiEditLabel from novelwriter.enum import nwItemType, nwView, nwWidget -from novelwriter.gui.outline import GuiOutlineView -from novelwriter.gui.projtree import GuiProjectTree from novelwriter.gui.doceditor import GuiDocEditor from novelwriter.gui.noveltree import GuiNovelView +from novelwriter.gui.outline import GuiOutlineView +from novelwriter.gui.projtree import GuiProjectTree from novelwriter.tools.welcome import GuiWelcome -from novelwriter.dialogs.editlabel import GuiEditLabel + +from tests.tools import NWD_IGNORE, XML_IGNORE, C, buildTestProject, cmpFiles KEY_DELAY = 1 @@ -50,7 +51,6 @@ def testGuiMain_ProjectBlocker(nwGUI): assert nwGUI.closeProject() is True assert nwGUI.saveProject() is False assert nwGUI.openDocument(None) is False - assert nwGUI.openNextDocument(None) is False assert nwGUI.viewDocument(None) is False assert nwGUI.importDocument() is False @@ -84,7 +84,7 @@ def testGuiMain_Launch(qtbot, monkeypatch, nwGUI, projPath): nwGUI.closeProject() # Check that latest release info updated - CONFIG.lastNotes != "0x0" + assert CONFIG.lastNotes != "0x0" # Check that project open dialog launches nwGUI.postLaunchTasks(None)