diff --git a/novelwriter/common.py b/novelwriter/common.py index 988b6c78..86761b48 100644 --- a/novelwriter/common.py +++ b/novelwriter/common.py @@ -46,9 +46,9 @@ from novelwriter.constants import nwConst, nwUnicode logger = logging.getLogger(__name__) -# =============================================================================================== # +## # Checker Functions -# =============================================================================================== # +## def checkStringNone(value: Any, default: str | None) -> str | None: """Check if a variable is a string or a None.""" @@ -131,9 +131,9 @@ def checkPath(value: Any, default: Path) -> Path: return default -# =============================================================================================== # +## # Validator Functions -# =============================================================================================== # +## def isHandle(value: Any) -> bool: """Check if a string is a valid novelWriter handle. @@ -204,9 +204,9 @@ def checkIntTuple(value: int, valid: tuple | list | set, default: int) -> int: return default -# =============================================================================================== # +## # Formatting Functions -# =============================================================================================== # +## def formatInt(value: int) -> str: """Formats an integer with k, M, G etc.""" @@ -250,9 +250,9 @@ def formatTime(t: int) -> str: return "ERROR" -# =============================================================================================== # +## # String Functions -# =============================================================================================== # +## def simplified(text: str) -> str: """Take a string and strip leading and trailing whitespaces, and @@ -371,9 +371,9 @@ def numberToRoman(value: int, toLower: bool = False) -> str: return roman.lower() if toLower else roman -# =============================================================================================== # +## # Encoder Functions -# =============================================================================================== # +## def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str: """Encode a dictionary, list or tuple as a json object or array, and @@ -463,9 +463,9 @@ def xmlIndent(tree: ET.Element | ET.ElementTree) -> None: return -# =============================================================================================== # +## # File and File System Functions -# =============================================================================================== # +## def readTextFile(path: str | Path) -> str: """Read the content of a text file in a robust manner.""" @@ -507,9 +507,9 @@ def openExternalPath(path: Path) -> bool: return False -# =============================================================================================== # +## # Classes -# =============================================================================================== # +## class NWConfigParser(ConfigParser): """Common: Adapted Config Parser diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index de9544e8..d6c39dc9 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -899,7 +899,8 @@ class GuiMain(QMainWindow): def showBuildManuscriptDialog(self) -> None: """Open the build manuscript dialog.""" if SHARED.hasProject: - dialog = GuiManuscript(self) + if (dialog := SHARED.findTopLevelWidget(GuiManuscript)) is None: + dialog = GuiManuscript(self) dialog.setModal(False) dialog.show() dialog.raise_() @@ -920,7 +921,8 @@ class GuiMain(QMainWindow): def showWritingStatsDialog(self) -> None: """Open the session stats dialog.""" if SHARED.hasProject: - dialog = GuiWritingStats(self) + if (dialog := SHARED.findTopLevelWidget(GuiWritingStats)) is None: + dialog = GuiWritingStats(self) dialog.setModal(False) dialog.show() dialog.raise_() diff --git a/novelwriter/shared.py b/novelwriter/shared.py index 5e134646..871639c6 100644 --- a/novelwriter/shared.py +++ b/novelwriter/shared.py @@ -27,7 +27,7 @@ from __future__ import annotations import logging from time import time -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, TypeVar from pathlib import Path from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal @@ -42,6 +42,8 @@ if TYPE_CHECKING: # pragma: no cover logger = logging.getLogger(__name__) +NWWidget = TypeVar("NWWidget", bound=QWidget) + class SharedData(QObject): @@ -215,6 +217,13 @@ class SharedData(QObject): QThreadPool.globalInstance().start(runnable, priority=priority) return + def findTopLevelWidget(self, kind: type[NWWidget]) -> NWWidget | None: + """Find a top level widget.""" + for widget in self.mainGui.children(): + if isinstance(widget, kind): + return widget + return None + ## # Signal Proxy ##