From 096686457d1a171ef4b05b95ba7ae196a4f9205c Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 25 Oct 2025 20:47:32 +0200
Subject: [PATCH 01/10] Add a standard button generator
---
novelwriter/enum.py | 15 +++++++++++++
novelwriter/extensions/modified.py | 34 +++++++++++++++++++++++++++--
novelwriter/gui/theme.py | 35 +++++++++++++++++++++++++-----
novelwriter/tools/welcome.py | 32 ++++++++-------------------
4 files changed, 85 insertions(+), 31 deletions(-)
diff --git a/novelwriter/enum.py b/novelwriter/enum.py
index 7ea60294..1bb85120 100644
--- a/novelwriter/enum.py
+++ b/novelwriter/enum.py
@@ -247,3 +247,18 @@ class nwStatusShape(Enum):
BLOCK_2 = 17
BLOCK_3 = 18
BLOCK_4 = 19
+
+
+class nwStandardButton(Enum):
+ """Enum: Standard Dialog Buttons."""
+
+ OK = 0
+ CANCEL = 1
+ YES = 2
+ NO = 3
+ OPEN = 4
+ CLOSE = 5
+ BROWSE = 6
+ LIST = 7
+ NEW = 8
+ CREATE = 9
diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py
index bb256ff1..7e245d21 100644
--- a/novelwriter/extensions/modified.py
+++ b/novelwriter/extensions/modified.py
@@ -31,8 +31,8 @@ from typing import TYPE_CHECKING
from PyQt6.QtCore import QModelIndex, QSize, Qt, pyqtSignal, pyqtSlot
from PyQt6.QtWidgets import (
- QApplication, QComboBox, QDialog, QDoubleSpinBox, QLabel, QSpinBox,
- QToolButton, QTreeView, QWidget
+ QApplication, QComboBox, QDialog, QDoubleSpinBox, QLabel, QPushButton,
+ QSpinBox, QToolButton, QTreeView, QWidget
)
from novelwriter import CONFIG, SHARED
@@ -199,6 +199,36 @@ class NDoubleSpinBox(QDoubleSpinBox):
event.ignore()
+class NPushButton(QPushButton):
+ """Custom: Modified QPushButton.
+
+ A quicker way to create a push button using the app theme.
+ """
+
+ def __init__(
+ self, parent: QWidget, text: str, iconSize: QSize,
+ icon: str | None = None, color: str | None = None
+ ) -> None:
+ super().__init__(parent=parent)
+ self.setText(text)
+ self.setIconSize(iconSize)
+ self._icon = icon
+ self._color = color
+ if icon:
+ self.refreshIcon()
+
+ def setThemeIcon(self, icon: str, color: str | None = None) -> None:
+ """Set an icon from the current theme."""
+ self._icon = icon
+ self._color = color
+ self.refreshIcon()
+
+ def refreshIcon(self) -> None:
+ """Reload the theme icon."""
+ if self._icon:
+ self.setIcon(SHARED.theme.getIcon(self._icon, self._color))
+
+
class NIconToolButton(QToolButton):
"""Custom: Modified QToolButton.
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index f3a026f1..9b3c10d4 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -31,19 +31,20 @@ from dataclasses import dataclass
from math import ceil
from typing import TYPE_CHECKING, Final
-from PyQt6.QtCore import QSize, Qt
+from PyQt6.QtCore import QT_TRANSLATE_NOOP, QCoreApplication, QSize, Qt
from PyQt6.QtGui import (
QColor, QFont, QFontDatabase, QFontMetrics, QGuiApplication, QIcon,
QPainter, QPainterPath, QPalette, QPixmap
)
-from PyQt6.QtWidgets import QApplication
+from PyQt6.QtWidgets import QApplication, QWidget
from novelwriter import CONFIG
from novelwriter.common import checkInt, minmax
from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_ICONS, DEF_TREECOL
from novelwriter.constants import nwLabels
-from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType, nwTheme
+from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType, nwStandardButton, nwTheme
from novelwriter.error import logException
+from novelwriter.extensions.modified import NPushButton
from novelwriter.types import QtBlack, QtHexArgb, QtPaintAntiAlias, QtTransparent
if TYPE_CHECKING:
@@ -55,6 +56,19 @@ STYLES_FLAT_TABS = "flatTabWidget"
STYLES_MIN_TOOLBUTTON = "minimalToolButton"
STYLES_BIG_TOOLBUTTON = "bigToolButton"
+STANDARD_BUTTONS = {
+ nwStandardButton.OK: (QT_TRANSLATE_NOOP("Button", "OK"), "bullet-on", "blue"),
+ nwStandardButton.CANCEL: (QT_TRANSLATE_NOOP("Button", "Cancel"), "cancel", "red"),
+ nwStandardButton.YES: (QT_TRANSLATE_NOOP("Button", "Yes"), "bullet-on", "green"),
+ nwStandardButton.NO: (QT_TRANSLATE_NOOP("Button", "No"), "bullet-on", "red"),
+ nwStandardButton.OPEN: (QT_TRANSLATE_NOOP("Button", "Open"), "open", "blue"),
+ nwStandardButton.CLOSE: (QT_TRANSLATE_NOOP("Button", "Close"), "close", "default"),
+ nwStandardButton.BROWSE: (QT_TRANSLATE_NOOP("Button", "Browse"), "browse", "yellow"),
+ nwStandardButton.LIST: (QT_TRANSLATE_NOOP("Button", "List"), "list", "blue"),
+ nwStandardButton.NEW: (QT_TRANSLATE_NOOP("Button", "New"), "add", "green"),
+ nwStandardButton.CREATE: (QT_TRANSLATE_NOOP("Button", "Create"), "star", "yellow"),
+}
+
@dataclass
class ThemeEntry:
@@ -120,9 +134,9 @@ class GuiTheme:
"_qColors", "_styleSheets", "_svgColors", "_syntaxList", "accentCol", "baseButtonHeight",
"baseIconHeight", "baseIconSize", "buttonIconSize", "errorText", "fadedText",
"fontPixelSize", "fontPointSize", "getDecoration", "getHeaderDecoration",
- "getHeaderDecorationNarrow", "getIcon", "getItemIcon", "getPixmap", "getToggleIcon",
- "guiFont", "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall", "helpText",
- "iconCache", "isDarkTheme", "syntaxTheme", "textNHeight", "textNWidth",
+ "getHeaderDecorationNarrow", "getIcon", "getItemIcon", "getPixmap", "getStandardButton",
+ "getToggleIcon", "guiFont", "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall",
+ "helpText", "iconCache", "isDarkTheme", "syntaxTheme", "textNHeight", "textNWidth",
)
def __init__(self) -> None:
@@ -153,6 +167,7 @@ class GuiTheme:
self.getItemIcon = self.iconCache.getItemIcon
self.getToggleIcon = self.iconCache.getToggleIcon
self.getDecoration = self.iconCache.getDecoration
+ self.getStandardButton = self.iconCache.getStandardButton
self.getHeaderDecoration = self.iconCache.getHeaderDecoration
self.getHeaderDecorationNarrow = self.iconCache.getHeaderDecorationNarrow
@@ -808,6 +823,14 @@ class GuiIcons:
w, h = size
return self.getIcon(name, color, w, h).pixmap(w, h, QIcon.Mode.Normal)
+ def getStandardButton(self, button: nwStandardButton, parent: QWidget) -> NPushButton:
+ """Return a standard button with icon and text."""
+ text, icon, color = STANDARD_BUTTONS.get(button, ("", "", ""))
+ return NPushButton(
+ parent, QCoreApplication.translate("Button", text),
+ self._theme.buttonIconSize, icon, color
+ )
+
def getDecoration(self, name: str, w: int | None = None, h: int | None = None) -> QPixmap:
"""Load graphical decoration element based on the decoration
map or the icon map. This function always returns a QPixmap.
diff --git a/novelwriter/tools/welcome.py b/novelwriter/tools/welcome.py
index 69d2e090..041a0031 100644
--- a/novelwriter/tools/welcome.py
+++ b/novelwriter/tools/welcome.py
@@ -35,15 +35,15 @@ from PyQt6.QtCore import (
from PyQt6.QtGui import QAction, QCloseEvent, QFont, QPainter, QPaintEvent, QPen, QShortcut
from PyQt6.QtWidgets import (
QApplication, QFileDialog, QFormLayout, QHBoxLayout, QLabel, QLineEdit,
- QListView, QMenu, QPushButton, QScrollArea, QStackedWidget,
- QStyledItemDelegate, QStyleOptionViewItem, QVBoxLayout, QWidget
+ QListView, QMenu, QScrollArea, QStackedWidget, QStyledItemDelegate,
+ QStyleOptionViewItem, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import formatInt, makeFileNameSafe, qtAddAction, qtLambda
from novelwriter.constants import nwFiles
from novelwriter.core.coretools import ProjectBuilder
-from novelwriter.enum import nwItemClass
+from novelwriter.enum import nwItemClass, nwStandardButton
from novelwriter.extensions.configlayout import NWrappedWidgetBox
from novelwriter.extensions.modified import NDialog, NIconToolButton, NSpinBox
from novelwriter.extensions.switch import NSwitch
@@ -75,8 +75,6 @@ class GuiWelcome(NDialog):
self.setMinimumHeight(450)
self.resize(*CONFIG.welcomeWinSize)
- btnIconSize = SHARED.theme.buttonIconSize
-
# Elements
# ========
@@ -104,34 +102,22 @@ class GuiWelcome(NDialog):
# Buttons
# =======
- self.btnList = QPushButton(self.tr("List"), self)
- self.btnList.setIcon(SHARED.theme.getIcon("list", "blue"))
- self.btnList.setIconSize(btnIconSize)
+ self.btnList = SHARED.theme.getStandardButton(nwStandardButton.LIST, self)
self.btnList.clicked.connect(self._showOpenProjectPage)
- self.btnNew = QPushButton(self.tr("New"), self)
- self.btnNew.setIcon(SHARED.theme.getIcon("add", "green"))
- self.btnNew.setIconSize(btnIconSize)
+ self.btnNew = SHARED.theme.getStandardButton(nwStandardButton.NEW, self)
self.btnNew.clicked.connect(self._showNewProjectPage)
- self.btnBrowse = QPushButton(self.tr("Browse"), self)
- self.btnBrowse.setIcon(SHARED.theme.getIcon("browse", "yellow"))
- self.btnBrowse.setIconSize(btnIconSize)
+ self.btnBrowse = SHARED.theme.getStandardButton(nwStandardButton.BROWSE, self)
self.btnBrowse.clicked.connect(self._browseForProject)
- self.btnCancel = QPushButton(self.tr("Cancel"), self)
- self.btnCancel.setIcon(SHARED.theme.getIcon("cancel", "red"))
- self.btnCancel.setIconSize(btnIconSize)
+ self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self)
self.btnCancel.clicked.connect(qtLambda(self.close))
- self.btnCreate = QPushButton(self.tr("Create"), self)
- self.btnCreate.setIcon(SHARED.theme.getIcon("star", "yellow"))
- self.btnCreate.setIconSize(btnIconSize)
+ self.btnCreate = SHARED.theme.getStandardButton(nwStandardButton.CREATE, self)
self.btnCreate.clicked.connect(self.tabNew.createNewProject)
- self.btnOpen = QPushButton(self.tr("Open"), self)
- self.btnOpen.setIcon(SHARED.theme.getIcon("open", "blue"))
- self.btnOpen.setIconSize(btnIconSize)
+ self.btnOpen = SHARED.theme.getStandardButton(nwStandardButton.OPEN, self)
self.btnOpen.clicked.connect(self._openSelectedItem)
self.btnBox = QHBoxLayout()
From bb1d90213b400526a5760fcc7a3e5b8954189c71 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 25 Oct 2025 21:38:30 +0200
Subject: [PATCH 02/10] Update standard dialog boxes
---
novelwriter/enum.py | 1 +
novelwriter/gui/theme.py | 21 +++++++++++----------
novelwriter/shared.py | 35 ++++++++++++++++++++++++++++++-----
3 files changed, 42 insertions(+), 15 deletions(-)
diff --git a/novelwriter/enum.py b/novelwriter/enum.py
index 1bb85120..4db5cdce 100644
--- a/novelwriter/enum.py
+++ b/novelwriter/enum.py
@@ -262,3 +262,4 @@ class nwStandardButton(Enum):
LIST = 7
NEW = 8
CREATE = 9
+ RESET = 10
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index 9b3c10d4..441c9cd1 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -57,16 +57,17 @@ STYLES_MIN_TOOLBUTTON = "minimalToolButton"
STYLES_BIG_TOOLBUTTON = "bigToolButton"
STANDARD_BUTTONS = {
- nwStandardButton.OK: (QT_TRANSLATE_NOOP("Button", "OK"), "bullet-on", "blue"),
- nwStandardButton.CANCEL: (QT_TRANSLATE_NOOP("Button", "Cancel"), "cancel", "red"),
- nwStandardButton.YES: (QT_TRANSLATE_NOOP("Button", "Yes"), "bullet-on", "green"),
- nwStandardButton.NO: (QT_TRANSLATE_NOOP("Button", "No"), "bullet-on", "red"),
- nwStandardButton.OPEN: (QT_TRANSLATE_NOOP("Button", "Open"), "open", "blue"),
- nwStandardButton.CLOSE: (QT_TRANSLATE_NOOP("Button", "Close"), "close", "default"),
- nwStandardButton.BROWSE: (QT_TRANSLATE_NOOP("Button", "Browse"), "browse", "yellow"),
- nwStandardButton.LIST: (QT_TRANSLATE_NOOP("Button", "List"), "list", "blue"),
- nwStandardButton.NEW: (QT_TRANSLATE_NOOP("Button", "New"), "add", "green"),
- nwStandardButton.CREATE: (QT_TRANSLATE_NOOP("Button", "Create"), "star", "yellow"),
+ nwStandardButton.OK: (QT_TRANSLATE_NOOP("Button", "OK"), "btn_ok", "blue"),
+ nwStandardButton.CANCEL: (QT_TRANSLATE_NOOP("Button", "Cancel"), "btn_cancel", "red"),
+ nwStandardButton.YES: (QT_TRANSLATE_NOOP("Button", "Yes"), "btn_yes", "green"),
+ nwStandardButton.NO: (QT_TRANSLATE_NOOP("Button", "No"), "btn_no", "red"),
+ nwStandardButton.OPEN: (QT_TRANSLATE_NOOP("Button", "Open"), "btn_open", "blue"),
+ nwStandardButton.CLOSE: (QT_TRANSLATE_NOOP("Button", "Close"), "btn_close", "red"),
+ nwStandardButton.BROWSE: (QT_TRANSLATE_NOOP("Button", "Browse"), "btn_browse", "yellow"),
+ nwStandardButton.LIST: (QT_TRANSLATE_NOOP("Button", "List"), "btn_list", "blue"),
+ nwStandardButton.NEW: (QT_TRANSLATE_NOOP("Button", "New"), "btn_new", "green"),
+ nwStandardButton.CREATE: (QT_TRANSLATE_NOOP("Button", "Create"), "btn_create", "yellow"),
+ nwStandardButton.RESET: (QT_TRANSLATE_NOOP("Button", "Reset"), "btn_reset", "green"),
}
diff --git a/novelwriter/shared.py b/novelwriter/shared.py
index 7c971341..be439b48 100644
--- a/novelwriter/shared.py
+++ b/novelwriter/shared.py
@@ -34,12 +34,12 @@ from typing import TYPE_CHECKING, TypeVar
from PyQt6.QtCore import QObject, QRunnable, QThreadPool, QTimer, QUrl, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QDesktopServices, QFont, QScreen
-from PyQt6.QtWidgets import QApplication, QFileDialog, QFontDialog, QMessageBox, QWidget
+from PyQt6.QtWidgets import QApplication, QDialog, QFileDialog, QFontDialog, QMessageBox, QWidget
from novelwriter.common import formatFileFilter
from novelwriter.constants import nwFiles
from novelwriter.core.spellcheck import NWSpellEnchant
-from novelwriter.enum import nwChange, nwItemClass
+from novelwriter.enum import nwChange, nwItemClass, nwStandardButton
if TYPE_CHECKING:
from collections.abc import Callable
@@ -422,7 +422,7 @@ class SharedData(QObject):
alert.setAlertType(_GuiAlert.WARN if warn else _GuiAlert.ASK, True)
self._lastAlert = alert.logMessage
alert.exec()
- return alert.result() == QMessageBox.StandardButton.Yes
+ return alert.finalState
##
# Internal Functions
@@ -469,6 +469,7 @@ class _GuiAlert(QMessageBox):
super().__init__(parent=parent)
self._theme = theme
self._message = ""
+ self._state = False
logger.debug("Ready: _GuiAlert")
def __del__(self) -> None: # pragma: no cover
@@ -478,6 +479,10 @@ class _GuiAlert(QMessageBox):
def logMessage(self) -> str:
return self._message
+ @property
+ def finalState(self) -> bool:
+ return self._state
+
def setMessage(self, text: str, info: str, details: str) -> None:
"""Set the alert box message."""
self._message = " ".join(filter(None, [text, info, details]))
@@ -496,9 +501,17 @@ class _GuiAlert(QMessageBox):
Yes/No buttons or just an Ok button.
"""
if isYesNo:
- self.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
+ self._btnYes = self._theme.getStandardButton(nwStandardButton.YES, self)
+ self._btnYes.clicked.connect(self._onAccept)
+ self._btnNo = self._theme.getStandardButton(nwStandardButton.NO, self)
+ self._btnNo.clicked.connect(self._onReject)
+ self.addButton(self._btnYes, QMessageBox.ButtonRole.YesRole)
+ self.addButton(self._btnNo, QMessageBox.ButtonRole.NoRole)
else:
- self.setStandardButtons(QMessageBox.StandardButton.Ok)
+ self._btnOk = self._theme.getStandardButton(nwStandardButton.OK, self)
+ self._btnOk.clicked.connect(self._onAccept)
+ self.addButton(self._btnOk, QMessageBox.ButtonRole.AcceptRole)
+
pSz = 2*self._theme.baseIconHeight
if level == self.INFO:
self.setIconPixmap(self._theme.getPixmap("alert_info", (pSz, pSz), "blue"))
@@ -512,3 +525,15 @@ class _GuiAlert(QMessageBox):
elif level == self.ASK:
self.setIconPixmap(self._theme.getPixmap("alert_question", (pSz, pSz), "blue"))
self.setWindowTitle(self.tr("Question"))
+
+ @pyqtSlot()
+ def _onAccept(self) -> None:
+ """Process accepted state."""
+ self._state = True
+ self.close()
+
+ @pyqtSlot()
+ def _onReject(self) -> None:
+ """Process rejected state."""
+ self._state = False
+ self.setResult(QDialog.DialogCode.Rejected)
From 1938969a63be1afdd28cc81ae8f174bd927b9248 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 25 Oct 2025 21:42:39 +0200
Subject: [PATCH 03/10] Update icon themes
---
.../assets/icons/material_filled_normal.icons | 13 +++++++++++--
.../assets/icons/material_filled_thin.icons | 13 +++++++++++--
.../assets/icons/material_rounded_normal.icons | 13 +++++++++++--
.../assets/icons/material_rounded_thin.icons | 13 +++++++++++--
.../assets/icons/material_sharp_normal.icons | 13 +++++++++++--
.../assets/icons/material_sharp_thin.icons | 13 +++++++++++--
tests/files/all_icons.json | 14 ++++++++++++--
utils/icon_themes.py | 16 ++++++++++++++--
utils/icon_themes/font_awesome.json | 18 ++++++++++++++----
utils/icon_themes/material_symbols.json | 14 ++++++++++++--
utils/icon_themes/remix.json | 14 ++++++++++++--
11 files changed, 130 insertions(+), 24 deletions(-)
diff --git a/novelwriter/assets/icons/material_filled_normal.icons b/novelwriter/assets/icons/material_filled_normal.icons
index 36b8536f..1cd8cfb1 100644
--- a/novelwriter/assets/icons/material_filled_normal.icons
+++ b/novelwriter/assets/icons/material_filled_normal.icons
@@ -59,6 +59,17 @@ icon:sb_stats =
icon:theme_dark =
icon:theme_auto =
+icon:btn_ok =
+icon:btn_cancel =
+icon:btn_yes =
+icon:btn_no =
+icon:btn_open =
+icon:btn_close =
+icon:btn_browse =
+icon:btn_list =
+icon:btn_new =
+icon:btn_create =
+icon:btn_reset =
icon:add =
icon:bookmarks =
icon:browse =
@@ -94,7 +105,6 @@ icon:minimise =
icon:more_vertical =
icon:noncheckable =
-icon:open =
icon:panel =
icon:pin =
icon:project_copy =
@@ -103,7 +113,6 @@ icon:refresh =
icon:revert =
icon:settings =
-icon:star =
icon:stats =
icon:text =
icon:timer_off =
diff --git a/novelwriter/assets/icons/material_filled_thin.icons b/novelwriter/assets/icons/material_filled_thin.icons
index a405ea3e..2d399f22 100644
--- a/novelwriter/assets/icons/material_filled_thin.icons
+++ b/novelwriter/assets/icons/material_filled_thin.icons
@@ -59,6 +59,17 @@ icon:sb_stats =
icon:theme_dark =
icon:theme_auto =
+icon:btn_ok =
+icon:btn_cancel =
+icon:btn_yes =
+icon:btn_no =
+icon:btn_open =
+icon:btn_close =
+icon:btn_browse =
+icon:btn_list =
+icon:btn_new =
+icon:btn_create =
+icon:btn_reset =
icon:add =
icon:bookmarks =
icon:browse =
@@ -94,7 +105,6 @@ icon:minimise =
icon:more_vertical =
icon:noncheckable =
-icon:open =
icon:panel =
icon:pin =
icon:project_copy =
@@ -103,7 +113,6 @@ icon:refresh =
icon:revert =
icon:settings =
-icon:star =
icon:stats =
icon:text =
icon:timer_off =
diff --git a/novelwriter/assets/icons/material_rounded_normal.icons b/novelwriter/assets/icons/material_rounded_normal.icons
index 9c22a001..044bdd5d 100644
--- a/novelwriter/assets/icons/material_rounded_normal.icons
+++ b/novelwriter/assets/icons/material_rounded_normal.icons
@@ -59,6 +59,17 @@ icon:sb_stats =
icon:theme_dark =
icon:theme_auto =
+icon:btn_ok =
+icon:btn_cancel =
+icon:btn_yes =
+icon:btn_no =
+icon:btn_open =
+icon:btn_close =
+icon:btn_browse =
+icon:btn_list =
+icon:btn_new =
+icon:btn_create =
+icon:btn_reset =
icon:add =
icon:bookmarks =
icon:browse =
@@ -94,7 +105,6 @@ icon:minimise =
icon:more_vertical =
icon:noncheckable =
-icon:open =
icon:panel =
icon:pin =
icon:project_copy =
@@ -103,7 +113,6 @@ icon:refresh =
icon:revert =
icon:settings =
-icon:star =
icon:stats =
icon:text =
icon:timer_off =
diff --git a/novelwriter/assets/icons/material_rounded_thin.icons b/novelwriter/assets/icons/material_rounded_thin.icons
index 1002fcbf..22834e45 100644
--- a/novelwriter/assets/icons/material_rounded_thin.icons
+++ b/novelwriter/assets/icons/material_rounded_thin.icons
@@ -59,6 +59,17 @@ icon:sb_stats =
icon:theme_dark =
icon:theme_auto =
+icon:btn_ok =
+icon:btn_cancel =
+icon:btn_yes =
+icon:btn_no =
+icon:btn_open =
+icon:btn_close =
+icon:btn_browse =
+icon:btn_list =
+icon:btn_new =
+icon:btn_create =
+icon:btn_reset =
icon:add =
icon:bookmarks =
icon:browse =
@@ -94,7 +105,6 @@ icon:minimise =
icon:more_vertical =
icon:noncheckable =
-icon:open =
icon:panel =
icon:pin =
icon:project_copy =
@@ -103,7 +113,6 @@ icon:refresh =
icon:revert =
icon:settings =
-icon:star =
icon:stats =
icon:text =
icon:timer_off =
diff --git a/novelwriter/assets/icons/material_sharp_normal.icons b/novelwriter/assets/icons/material_sharp_normal.icons
index b8b8c0e6..13c68127 100644
--- a/novelwriter/assets/icons/material_sharp_normal.icons
+++ b/novelwriter/assets/icons/material_sharp_normal.icons
@@ -59,6 +59,17 @@ icon:sb_stats =
icon:theme_dark =
icon:theme_auto =
+icon:btn_ok =
+icon:btn_cancel =
+icon:btn_yes =
+icon:btn_no =
+icon:btn_open =
+icon:btn_close =
+icon:btn_browse =
+icon:btn_list =
+icon:btn_new =
+icon:btn_create =
+icon:btn_reset =
icon:add =
icon:bookmarks =
icon:browse =
@@ -94,7 +105,6 @@ icon:minimise =
icon:more_vertical =
icon:noncheckable =
-icon:open =
icon:panel =
icon:pin =
icon:project_copy =
@@ -103,7 +113,6 @@ icon:refresh =
icon:revert =
icon:settings =
-icon:star =
icon:stats =
icon:text =
icon:timer_off =
diff --git a/novelwriter/assets/icons/material_sharp_thin.icons b/novelwriter/assets/icons/material_sharp_thin.icons
index afe8528d..c8bb13a8 100644
--- a/novelwriter/assets/icons/material_sharp_thin.icons
+++ b/novelwriter/assets/icons/material_sharp_thin.icons
@@ -59,6 +59,17 @@ icon:sb_stats =
icon:theme_dark =
icon:theme_auto =
+icon:btn_ok =
+icon:btn_cancel =
+icon:btn_yes =
+icon:btn_no =
+icon:btn_open =
+icon:btn_close =
+icon:btn_browse =
+icon:btn_list =
+icon:btn_new =
+icon:btn_create =
+icon:btn_reset =
icon:add =
icon:bookmarks =
icon:browse =
@@ -94,7 +105,6 @@ icon:minimise =
icon:more_vertical =
icon:noncheckable =
-icon:open =
icon:panel =
icon:pin =
icon:project_copy =
@@ -103,7 +113,6 @@ icon:refresh =
icon:revert =
icon:settings =
-icon:star =
icon:stats =
icon:text =
icon:timer_off =
diff --git a/tests/files/all_icons.json b/tests/files/all_icons.json
index 3d85f818..41797f20 100644
--- a/tests/files/all_icons.json
+++ b/tests/files/all_icons.json
@@ -60,6 +60,18 @@
"theme_dark",
"theme_auto",
+ "btn_ok",
+ "btn_cancel",
+ "btn_yes",
+ "btn_no",
+ "btn_open",
+ "btn_close",
+ "btn_browse",
+ "btn_list",
+ "btn_new",
+ "btn_create",
+ "btn_reset",
+
"add",
"bookmarks",
"browse",
@@ -95,7 +107,6 @@
"more_arrow",
"more_vertical",
"noncheckable",
- "open",
"panel",
"pin",
"project_copy",
@@ -104,7 +115,6 @@
"remove",
"revert",
"settings",
- "star",
"stats",
"text",
"timer_off",
diff --git a/utils/icon_themes.py b/utils/icon_themes.py
index cb906b01..e5c2268f 100644
--- a/utils/icon_themes.py
+++ b/utils/icon_themes.py
@@ -107,6 +107,18 @@ ICONS = [
"theme_dark",
"theme_auto",
+ "btn_ok",
+ "btn_cancel",
+ "btn_yes",
+ "btn_no",
+ "btn_open",
+ "btn_close",
+ "btn_browse",
+ "btn_list",
+ "btn_new",
+ "btn_create",
+ "btn_reset",
+
"add",
"bookmarks",
"browse",
@@ -142,7 +154,6 @@ ICONS = [
"more_arrow",
"more_vertical",
"noncheckable",
- "open",
"panel",
"pin",
"project_copy",
@@ -151,7 +162,6 @@ ICONS = [
"remove",
"revert",
"settings",
- "star",
"stats",
"text",
"timer_off",
@@ -288,6 +298,8 @@ def processFontAwesome(workDir: Path, iconsDir: Path, jobs: dict) -> None:
viewbox = [int(x) for x in svg.get("viewBox", "").split()]
viewbox = [viewbox[2]//2 - 256, 0, 512, 512]
svg.set("viewBox", " ".join(str(x) for x in viewbox))
+ for elem in svg.iter():
+ elem.attrib.pop("fill", None)
icons[key] = svg
else:
print(f"Not Found: {icon}.svg")
diff --git a/utils/icon_themes/font_awesome.json b/utils/icon_themes/font_awesome.json
index d98dc9e2..1769409e 100644
--- a/utils/icon_themes/font_awesome.json
+++ b/utils/icon_themes/font_awesome.json
@@ -60,6 +60,18 @@
"theme_dark": "moon",
"theme_auto": "circle-half-stroke",
+ "btn_ok": "circle-check",
+ "btn_cancel": "ban",
+ "btn_yes": "circle-check",
+ "btn_no": "circle-xmark",
+ "btn_open": "file-arrow-up",
+ "btn_close": "circle-xmark",
+ "btn_browse": "folder-open",
+ "btn_list": "list",
+ "btn_new": "plus",
+ "btn_create": "star",
+ "btn_reset": "rotate-left",
+
"add": "plus",
"bookmarks": "bookmark",
"browse": "folder-open",
@@ -95,16 +107,14 @@
"more_arrow": "caret-right",
"more_vertical": "ellipsis-vertical",
"noncheckable": "square-minus",
- "open": "file-arrow-up",
"panel": "table-list",
"pin": "thumbtack",
"project_copy": "copy",
"quote": "quote-right",
- "refresh": "arrow-rotate-right",
+ "refresh": "rotate-right",
"remove": "minus",
- "revert": "arrow-rotate-left",
+ "revert": "rotate-left",
"settings": "gear",
- "star": "star",
"stats": "chart-line",
"text": "file-lines",
"timer_off": "pause",
diff --git a/utils/icon_themes/material_symbols.json b/utils/icon_themes/material_symbols.json
index 61c1975a..3d1adc47 100644
--- a/utils/icon_themes/material_symbols.json
+++ b/utils/icon_themes/material_symbols.json
@@ -60,6 +60,18 @@
"theme_dark": "dark_mode",
"theme_auto": "contrast",
+ "btn_ok": "check_circle",
+ "btn_cancel": "cancel",
+ "btn_yes": "check_circle",
+ "btn_no": "do_not_disturb_on",
+ "btn_open": "open_in_new",
+ "btn_close": "close",
+ "btn_browse": "folder_open",
+ "btn_list": "format_list_bulleted",
+ "btn_new": "new_window",
+ "btn_create": "star",
+ "btn_reset": "undo",
+
"add": "add",
"bookmarks": "bookmarks",
"browse": "folder_open",
@@ -95,7 +107,6 @@
"more_arrow": "arrow_right",
"more_vertical": "more_vert",
"noncheckable": "indeterminate_check_box",
- "open": "open_in_new",
"panel": "dock_to_bottom",
"pin": "keep",
"project_copy": "folder_copy",
@@ -104,7 +115,6 @@
"remove": "remove",
"revert": "settings_backup_restore",
"settings": "settings",
- "star": "star",
"stats": "stacked_line_chart",
"text": "subject",
"timer_off": "timer_off",
diff --git a/utils/icon_themes/remix.json b/utils/icon_themes/remix.json
index 0ac173ec..da78a84f 100644
--- a/utils/icon_themes/remix.json
+++ b/utils/icon_themes/remix.json
@@ -60,6 +60,18 @@
"theme_dark": "moon",
"theme_auto": "contrast",
+ "btn_ok": "checkbox-circle",
+ "btn_cancel": "indeterminate-circle",
+ "btn_yes": "checkbox-circle",
+ "btn_no": "close-circle",
+ "btn_open": "file-upload",
+ "btn_close": "close-circle",
+ "btn_browse": "folder-2",
+ "btn_list": "list-unordered",
+ "btn_new": "add",
+ "btn_create": "star-fill",
+ "btn_reset": "reset-left",
+
"add": "add",
"bookmarks": "bookmark",
"browse": "folder-2",
@@ -95,7 +107,6 @@
"more_arrow": "arrow-right-s-fill",
"more_vertical": "more-2-fill",
"noncheckable": "checkbox-indeterminate",
- "open": "file-upload",
"panel": "layout-bottom",
"pin": "pushpin",
"project_copy": "file-copy-2",
@@ -104,7 +115,6 @@
"remove": "subtract",
"revert": "reset-left",
"settings": "settings-2",
- "star": "star-fill",
"stats": "line-chart",
"text": "file-text",
"timer_off": "zzz",
From 2d67cb7dcc7df98ee2856dabce186bb8da4b79b4 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 25 Oct 2025 22:37:19 +0200
Subject: [PATCH 04/10] Update tests and test coverage
---
novelwriter/extensions/modified.py | 6 --
novelwriter/shared.py | 4 +-
run_tests.py | 5 +-
tests/conftest.py | 8 ++-
tests/mocked.py | 6 ++
tests/test_base/test_base_shared.py | 70 +++++++++++++++++++++--
tests/test_core/test_core_project.py | 7 +--
tests/test_gui/test_gui_guimain.py | 9 +--
tests/test_gui/test_gui_i18n.py | 4 +-
tests/test_gui/test_gui_mainmenu.py | 5 +-
tests/test_gui/test_gui_projtree.py | 13 +++--
tests/test_tools/test_tools_manusbuild.py | 5 +-
12 files changed, 105 insertions(+), 37 deletions(-)
diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py
index 7e245d21..b802a4d9 100644
--- a/novelwriter/extensions/modified.py
+++ b/novelwriter/extensions/modified.py
@@ -217,12 +217,6 @@ class NPushButton(QPushButton):
if icon:
self.refreshIcon()
- def setThemeIcon(self, icon: str, color: str | None = None) -> None:
- """Set an icon from the current theme."""
- self._icon = icon
- self._color = color
- self.refreshIcon()
-
def refreshIcon(self) -> None:
"""Reload the theme icon."""
if self._icon:
diff --git a/novelwriter/shared.py b/novelwriter/shared.py
index be439b48..b76ad165 100644
--- a/novelwriter/shared.py
+++ b/novelwriter/shared.py
@@ -34,7 +34,7 @@ from typing import TYPE_CHECKING, TypeVar
from PyQt6.QtCore import QObject, QRunnable, QThreadPool, QTimer, QUrl, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QDesktopServices, QFont, QScreen
-from PyQt6.QtWidgets import QApplication, QDialog, QFileDialog, QFontDialog, QMessageBox, QWidget
+from PyQt6.QtWidgets import QApplication, QFileDialog, QFontDialog, QMessageBox, QWidget
from novelwriter.common import formatFileFilter
from novelwriter.constants import nwFiles
@@ -536,4 +536,4 @@ class _GuiAlert(QMessageBox):
def _onReject(self) -> None:
"""Process rejected state."""
self._state = False
- self.setResult(QDialog.DialogCode.Rejected)
+ self.close()
diff --git a/run_tests.py b/run_tests.py
index 18e1eeae..6f593127 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -25,7 +25,10 @@ if __name__ == "__main__":
env["QT_SCALE_FACTOR"] = "1.0"
if args.r or args.t or args.u:
- cmd = ["coverage", "run", "-m"]
+ cmd = ["coverage", "run"]
+ if args.lf or args.sw:
+ cmd += ["--append"]
+ cmd += ["-m"]
else:
cmd = [sys.executable, "-m"]
diff --git a/tests/conftest.py b/tests/conftest.py
index 765755bf..f8e3a5eb 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -157,9 +157,11 @@ def projPath(fncPath):
def mockGUI(qtbot, monkeypatch):
"""Create a mock instance of novelWriter's main GUI class."""
from novelwriter.gui.theme import GuiTheme
+ from novelwriter.shared import _GuiAlert
monkeypatch.setattr(QMessageBox, "exec", lambda *a: None)
- monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes)
+ monkeypatch.setattr(_GuiAlert, "exec", lambda *a: None)
+ monkeypatch.setattr(_GuiAlert, "finalState", True)
gui = MockGuiMain()
theme = GuiTheme()
monkeypatch.setattr(SHARED, "_gui", gui)
@@ -182,9 +184,11 @@ def nwGUI(qtbot, monkeypatch, functionFixture):
"""Create an instance of the novelWriter GUI."""
from novelwriter.gui.theme import GuiTheme
from novelwriter.guimain import GuiMain
+ from novelwriter.shared import _GuiAlert
monkeypatch.setattr(QMessageBox, "exec", lambda *a: None)
- monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes)
+ monkeypatch.setattr(_GuiAlert, "exec", lambda *a: None)
+ monkeypatch.setattr(_GuiAlert, "finalState", True)
CONFIG.loadConfig()
SHARED.initTheme(GuiTheme())
diff --git a/tests/mocked.py b/tests/mocked.py
index f86d5be0..92b56abe 100644
--- a/tests/mocked.py
+++ b/tests/mocked.py
@@ -22,9 +22,12 @@ from __future__ import annotations
from unittest.mock import MagicMock
+from PyQt6.QtCore import QSize
from PyQt6.QtGui import QFont, QIcon, QPixmap
from PyQt6.QtWidgets import QWidget
+from novelwriter.extensions.modified import NPushButton
+
class MockGuiMain(QWidget):
@@ -72,6 +75,9 @@ class MockTheme:
def getHeaderDecoration(self, *a) -> QPixmap:
return QPixmap()
+ def getStandardButton(self, *a) -> NPushButton:
+ return NPushButton(None, "", QSize(1, 1)) # type: ignore
+
def getIcon(self, *a) -> QIcon:
return QIcon()
diff --git a/tests/test_base/test_base_shared.py b/tests/test_base/test_base_shared.py
index 64cc306b..a020a694 100644
--- a/tests/test_base/test_base_shared.py
+++ b/tests/test_base/test_base_shared.py
@@ -26,10 +26,10 @@ import pytest
from PyQt6.QtCore import QUrl
from PyQt6.QtGui import QDesktopServices
-from PyQt6.QtWidgets import QFileDialog, QMessageBox, QWidget
+from PyQt6.QtWidgets import QFileDialog, QWidget
from novelwriter.core.project import NWProject
-from novelwriter.shared import SharedData
+from novelwriter.shared import SharedData, _GuiAlert
from tests.mocked import MockGuiMain, MockTheme
from tests.tools import buildTestProject
@@ -143,10 +143,10 @@ def testBaseSharedData_Projects(monkeypatch, caplog, fncPath):
@pytest.mark.base
-def testBaseSharedData_Alerts(qtbot, monkeypatch, caplog):
+def testBaseSharedData_Alerts(qtbot, monkeypatch, caplog, mockGUI):
"""Test SharedData class alert helper functions."""
- monkeypatch.setattr(QMessageBox, "exec", lambda *a: None)
- monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes)
+ monkeypatch.setattr(_GuiAlert, "exec", lambda *a: None)
+ monkeypatch.setattr(_GuiAlert, "finalState", True)
shared = SharedData()
@@ -188,3 +188,63 @@ def testBaseSharedData_Alerts(qtbot, monkeypatch, caplog):
# Question box
assert shared.question("Why?") is True
assert shared.lastAlert == "Why?"
+
+
+@pytest.mark.base
+def testBaseSharedData_GuiAlert():
+ """Test the _GuiAlert class."""
+ alert = _GuiAlert(None, MockTheme()) # type: ignore
+
+ # Default states
+ assert alert.logMessage == ""
+ assert alert.finalState is False
+
+ # Populate message
+ text = "one"
+ info = "two"
+ details = "three"
+ alert.setMessage(text, info, details)
+ assert alert.logMessage == f"{text} {info} {details}"
+ assert alert.text() == text
+ assert alert.informativeText() == info
+ assert alert.detailedText() == details
+
+ # Populate exception
+ exc = Exception("oops")
+ alert.setException(exc)
+ assert alert.logMessage == f"{text} {info} {details}"
+ assert alert.informativeText() == f"{info}
Exception: {exc!s}"
+
+ # Alert: Info
+ alert.setAlertType(_GuiAlert.INFO, False)
+ assert hasattr(alert, "_btnOk")
+ assert alert.windowTitle() == "Information"
+ alert._btnOk.click()
+ assert alert.finalState is True
+ alert._state = False
+
+ # Alert: Warning
+ alert.setAlertType(_GuiAlert.WARN, False)
+ assert hasattr(alert, "_btnOk")
+ assert alert.windowTitle() == "Warning"
+ alert._btnOk.click()
+ assert alert.finalState is True
+ alert._state = False
+
+ # Alert: Error
+ alert.setAlertType(_GuiAlert.ERROR, False)
+ assert hasattr(alert, "_btnOk")
+ assert alert.windowTitle() == "Error"
+ alert._btnOk.click()
+ assert alert.finalState is True
+ alert._state = False
+
+ # Alert: Question
+ alert.setAlertType(_GuiAlert.ASK, True)
+ assert hasattr(alert, "_btnYes")
+ assert hasattr(alert, "_btnNo")
+ assert alert.windowTitle() == "Question"
+ alert._btnYes.click()
+ assert alert.finalState is True
+ alert._btnNo.click()
+ assert alert.finalState is False
diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py
index cdb202c1..799cd423 100644
--- a/tests/test_core/test_core_project.py
+++ b/tests/test_core/test_core_project.py
@@ -25,13 +25,12 @@ from zipfile import ZipFile
import pytest
-from PyQt6.QtWidgets import QMessageBox
-
from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwFiles
from novelwriter.core.project import NWProject, NWProjectState
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
from novelwriter.enum import nwItemClass
+from novelwriter.shared import _GuiAlert
from tests.mocked import causeOSError
from tests.tools import XML_IGNORE, C, buildTestProject, cmpFiles
@@ -274,14 +273,14 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
# Won't convert legacy file
with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY))
- mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
+ mp.setattr(_GuiAlert, "finalState", False)
assert project.openProject(fncPath, clearLock=True) is False
assert "The file format of your project is about to be" in SHARED.lastAlert
# Won't open project from newer version
with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "hexVersion", property(lambda *a: 0x99999999))
- mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
+ mp.setattr(_GuiAlert, "finalState", False)
assert project.openProject(fncPath, clearLock=True) is False
assert "This project was saved by a newer version" in SHARED.lastAlert
diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py
index 0e15b71e..4cfa3a91 100644
--- a/tests/test_gui/test_gui_guimain.py
+++ b/tests/test_gui/test_gui_guimain.py
@@ -30,7 +30,7 @@ import pytest
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QPalette
-from PyQt6.QtWidgets import QInputDialog, QMessageBox
+from PyQt6.QtWidgets import QInputDialog
from novelwriter import CONFIG, SHARED, __hexversion__
from novelwriter.common import jsonEncode
@@ -42,6 +42,7 @@ 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.shared import _GuiAlert
from novelwriter.tools.welcome import GuiWelcome
from novelwriter.types import QtModCtrl, QtModShift
@@ -104,7 +105,7 @@ def testGuiMain_Launch(qtbot, monkeypatch, nwGUI, projPath):
# Check that closes can be blocked
with monkeypatch.context() as mp:
- mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
+ mp.setattr(_GuiAlert, "finalState", False)
assert nwGUI.openProject(projPath) is True
assert nwGUI.closeMain() is False
nwGUI.closeProject()
@@ -841,7 +842,7 @@ def testGuiMain_OpenClose(qtbot, monkeypatch, nwGUI, projPath, fncPath, mockRnd)
# Block closing
assert SHARED.hasProject is True
with monkeypatch.context() as mp:
- mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
+ mp.setattr(_GuiAlert, "finalState", False)
assert nwGUI.openProject(projPath) is False
assert SHARED.hasProject is True
@@ -854,7 +855,7 @@ def testGuiMain_OpenClose(qtbot, monkeypatch, nwGUI, projPath, fncPath, mockRnd)
shutil.copyfile(lockBack, lockPath)
with monkeypatch.context() as mp:
- mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
+ mp.setattr(_GuiAlert, "finalState", False)
assert nwGUI.openProject(projPath) is False
assert nwGUI.openProject(projPath) is True
diff --git a/tests/test_gui/test_gui_i18n.py b/tests/test_gui/test_gui_i18n.py
index 915d83a4..e482d85f 100644
--- a/tests/test_gui/test_gui_i18n.py
+++ b/tests/test_gui/test_gui_i18n.py
@@ -24,7 +24,7 @@ import sys
import pytest
-from PyQt6.QtWidgets import QApplication, QDialog, QMessageBox
+from PyQt6.QtWidgets import QApplication, QDialog
from novelwriter import CONFIG, SHARED
from novelwriter.dialogs.about import GuiAbout
@@ -49,8 +49,6 @@ LANG_DATA = CONFIG.listLanguages(CONFIG.LANG_NW)
def testGuiI18n_Localisation(qtbot, monkeypatch, language, nwGUI, projPath):
"""Test loading the gui with a specific language."""
monkeypatch.setattr(QDialog, "exec", lambda *a: None)
- monkeypatch.setattr(QMessageBox, "exec", lambda *a: None)
- monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes)
# Set the test language
CONFIG.guiLocale = language
diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py
index 0d6344bc..e599e9f9 100644
--- a/tests/test_gui/test_gui_mainmenu.py
+++ b/tests/test_gui/test_gui_mainmenu.py
@@ -25,12 +25,13 @@ from unittest.mock import MagicMock
import pytest
from PyQt6.QtGui import QAction, QDesktopServices, QTextBlock
-from PyQt6.QtWidgets import QFileDialog, QMessageBox
+from PyQt6.QtWidgets import QFileDialog
from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwKeyWords, nwShortcode, nwStats, nwUnicode
from novelwriter.enum import nwDocAction, nwDocInsert
from novelwriter.gui.doceditor import GuiDocEditor
+from novelwriter.shared import _GuiAlert
from novelwriter.types import QtKeepAnchor, QtMoveRight, QtSelectWord
from tests.tools import C, buildTestProject, writeFile
@@ -575,7 +576,7 @@ def testGuiMainMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd
# The document isn't empty, so the message box should pop
with monkeypatch.context() as mp:
- mp.setattr(QMessageBox, "result", lambda *a, **k: QMessageBox.StandardButton.No)
+ mp.setattr(_GuiAlert, "finalState", False)
assert not nwGUI.importDocument()
assert docEditor.getText() == "Bar"
diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py
index f6430f55..bdd50ad0 100644
--- a/tests/test_gui/test_gui_projtree.py
+++ b/tests/test_gui/test_gui_projtree.py
@@ -26,7 +26,7 @@ import pytest
from PyQt6.QtCore import QEvent, QItemSelectionModel, QModelIndex, QPointF
from PyQt6.QtGui import QMouseEvent
-from PyQt6.QtWidgets import QMenu, QMessageBox
+from PyQt6.QtWidgets import QMenu
from novelwriter import CONFIG, SHARED
from novelwriter.dialogs.docmerge import GuiDocMerge
@@ -34,6 +34,7 @@ from novelwriter.dialogs.docsplit import GuiDocSplit
from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType
from novelwriter.gui.projtree import _TreeContextMenu
+from novelwriter.shared import _GuiAlert
from novelwriter.types import (
QtAccepted, QtModNone, QtMouseLeft, QtMouseMiddle, QtRejected,
QtScrollAlwaysOff, QtScrollAsNeeded
@@ -627,7 +628,7 @@ def testGuiProjTree_DeleteRequest(qtbot, caplog, monkeypatch, nwGUI, projPath, m
# User can cancel move to trash
with monkeypatch.context() as mp:
- mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
+ mp.setattr(_GuiAlert, "finalState", False)
projTree.processDeleteRequest(hScenes, askFirst=True)
assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "New Folder", "New Chapter", "New Scene",
@@ -645,7 +646,7 @@ def testGuiProjTree_DeleteRequest(qtbot, caplog, monkeypatch, nwGUI, projPath, m
# User can block permanent deletion
with monkeypatch.context() as mp:
- mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
+ mp.setattr(_GuiAlert, "finalState", False)
projTree.processDeleteRequest(hScenes[0:2], askFirst=True)
assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "New Folder", "New Chapter", "New Scene",
@@ -677,7 +678,7 @@ def testGuiProjTree_DeleteRequest(qtbot, caplog, monkeypatch, nwGUI, projPath, m
# Trash can be completely emptied, but user can block it
with monkeypatch.context() as mp:
- mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
+ mp.setattr(_GuiAlert, "finalState", False)
projTree.emptyTrash()
assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "Chapter Folder", "Plot", "Characters", "Trash",
@@ -995,7 +996,7 @@ def testGuiProjTree_Duplicate(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
# Duplicate title page, but select no
with monkeypatch.context() as mp:
- mp.setattr(QMessageBox, "result", lambda *a: QtRejected)
+ mp.setattr(_GuiAlert, "finalState", False)
projTree.duplicateFromHandle(C.hTitlePage)
assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "New Folder", "New Chapter", "New Scene",
@@ -1302,7 +1303,7 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
# Click no on the dialog
with monkeypatch.context() as mp:
- mp.setattr(QMessageBox, "result", lambda *a: QtRejected)
+ mp.setattr(_GuiAlert, "finalState", False)
ctxMenu._convertFolderToFile(nwItemLayout.DOCUMENT)
assert nodeOne.item.isFolderType()
diff --git a/tests/test_tools/test_tools_manusbuild.py b/tests/test_tools/test_tools_manusbuild.py
index 91924120..35ddb7fa 100644
--- a/tests/test_tools/test_tools_manusbuild.py
+++ b/tests/test_tools/test_tools_manusbuild.py
@@ -26,13 +26,14 @@ import pytest
from PyQt6.QtCore import QUrl
from PyQt6.QtGui import QDesktopServices
-from PyQt6.QtWidgets import QFileDialog, QListWidgetItem, QMessageBox
+from PyQt6.QtWidgets import QFileDialog, QListWidgetItem
from pytestqt.qtbot import QtBot
from novelwriter.constants import nwLabels
from novelwriter.core.buildsettings import BuildSettings
from novelwriter.enum import nwBuildFmt
from novelwriter.guimain import GuiMain
+from novelwriter.shared import _GuiAlert
from novelwriter.tools.manusbuild import GuiManuscriptBuild
from novelwriter.types import QtDialogClose
@@ -134,7 +135,7 @@ def testToolManuscriptBuild_Main(
manus.buildPath.setText(str(fncPath))
manus.buildName.setText("TestBuild")
with monkeypatch.context() as mp:
- mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
+ mp.setattr(_GuiAlert, "finalState", False)
assert manus._runBuild() is False
# Test that the open button works
From 1b0c33984585640982e17c0837452b71c8677082 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 25 Oct 2025 23:31:47 +0200
Subject: [PATCH 05/10] Add more icons
---
.../assets/icons/material_filled_normal.icons | 8 +++++-
.../assets/icons/material_filled_thin.icons | 8 +++++-
.../icons/material_rounded_normal.icons | 8 +++++-
.../assets/icons/material_rounded_thin.icons | 8 +++++-
.../assets/icons/material_sharp_normal.icons | 8 +++++-
.../assets/icons/material_sharp_thin.icons | 8 +++++-
novelwriter/enum.py | 28 +++++++++++--------
tests/files/all_icons.json | 6 ++++
utils/icon_themes.py | 6 ++++
utils/icon_themes/font_awesome.json | 6 ++++
utils/icon_themes/material_symbols.json | 8 +++++-
utils/icon_themes/remix.json | 6 ++++
12 files changed, 90 insertions(+), 18 deletions(-)
diff --git a/novelwriter/assets/icons/material_filled_normal.icons b/novelwriter/assets/icons/material_filled_normal.icons
index 1cd8cfb1..7b58f1a4 100644
--- a/novelwriter/assets/icons/material_filled_normal.icons
+++ b/novelwriter/assets/icons/material_filled_normal.icons
@@ -64,12 +64,18 @@ icon:btn_cancel =
icon:btn_no =
icon:btn_open =
-icon:btn_close =
+icon:btn_close =
+icon:btn_save =
icon:btn_browse =
icon:btn_list =
icon:btn_new =
icon:btn_create =
icon:btn_reset =
+icon:btn_insert =
+icon:btn_apply =
+icon:btn_build =
+icon:btn_print =
+icon:btn_preview =
icon:add =
icon:bookmarks =
icon:browse =
diff --git a/novelwriter/assets/icons/material_filled_thin.icons b/novelwriter/assets/icons/material_filled_thin.icons
index 2d399f22..0bf9758b 100644
--- a/novelwriter/assets/icons/material_filled_thin.icons
+++ b/novelwriter/assets/icons/material_filled_thin.icons
@@ -64,12 +64,18 @@ icon:btn_cancel =
icon:btn_no =
icon:btn_open =
-icon:btn_close =
+icon:btn_close =
+icon:btn_save =
icon:btn_browse =
icon:btn_list =
icon:btn_new =
icon:btn_create =
icon:btn_reset =
+icon:btn_insert =
+icon:btn_apply =
+icon:btn_build =
+icon:btn_print =
+icon:btn_preview =
icon:add =
icon:bookmarks =
icon:browse =
diff --git a/novelwriter/assets/icons/material_rounded_normal.icons b/novelwriter/assets/icons/material_rounded_normal.icons
index 044bdd5d..0ff44cbd 100644
--- a/novelwriter/assets/icons/material_rounded_normal.icons
+++ b/novelwriter/assets/icons/material_rounded_normal.icons
@@ -64,12 +64,18 @@ icon:btn_cancel =
icon:btn_no =
icon:btn_open =
-icon:btn_close =
+icon:btn_close =
+icon:btn_save =
icon:btn_browse =
icon:btn_list =
icon:btn_new =
icon:btn_create =
icon:btn_reset =
+icon:btn_insert =
+icon:btn_apply =
+icon:btn_build =
+icon:btn_print =
+icon:btn_preview =
icon:add =
icon:bookmarks =
icon:browse =
diff --git a/novelwriter/assets/icons/material_rounded_thin.icons b/novelwriter/assets/icons/material_rounded_thin.icons
index 22834e45..b5606512 100644
--- a/novelwriter/assets/icons/material_rounded_thin.icons
+++ b/novelwriter/assets/icons/material_rounded_thin.icons
@@ -64,12 +64,18 @@ icon:btn_cancel =
icon:btn_no =
icon:btn_open =
-icon:btn_close =
+icon:btn_close =
+icon:btn_save =
icon:btn_browse =
icon:btn_list =
icon:btn_new =
icon:btn_create =
icon:btn_reset =
+icon:btn_insert =
+icon:btn_apply =
+icon:btn_build =
+icon:btn_print =
+icon:btn_preview =
icon:add =
icon:bookmarks =
icon:browse =
diff --git a/novelwriter/assets/icons/material_sharp_normal.icons b/novelwriter/assets/icons/material_sharp_normal.icons
index 13c68127..a9b6ff25 100644
--- a/novelwriter/assets/icons/material_sharp_normal.icons
+++ b/novelwriter/assets/icons/material_sharp_normal.icons
@@ -64,12 +64,18 @@ icon:btn_cancel =
icon:btn_no =
icon:btn_open =
-icon:btn_close =
+icon:btn_close =
+icon:btn_save =
icon:btn_browse =
icon:btn_list =
icon:btn_new =
icon:btn_create =
icon:btn_reset =
+icon:btn_insert =
+icon:btn_apply =
+icon:btn_build =
+icon:btn_print =
+icon:btn_preview =
icon:add =
icon:bookmarks =
icon:browse =
diff --git a/novelwriter/assets/icons/material_sharp_thin.icons b/novelwriter/assets/icons/material_sharp_thin.icons
index c8bb13a8..11e04deb 100644
--- a/novelwriter/assets/icons/material_sharp_thin.icons
+++ b/novelwriter/assets/icons/material_sharp_thin.icons
@@ -64,12 +64,18 @@ icon:btn_cancel =
icon:btn_no =
icon:btn_open =
-icon:btn_close =
+icon:btn_close =
+icon:btn_save =
icon:btn_browse =
icon:btn_list =
icon:btn_new =
icon:btn_create =
icon:btn_reset =
+icon:btn_insert =
+icon:btn_apply =
+icon:btn_build =
+icon:btn_print =
+icon:btn_preview =
icon:add =
icon:bookmarks =
icon:browse =
diff --git a/novelwriter/enum.py b/novelwriter/enum.py
index 4db5cdce..0b88f8ab 100644
--- a/novelwriter/enum.py
+++ b/novelwriter/enum.py
@@ -252,14 +252,20 @@ class nwStatusShape(Enum):
class nwStandardButton(Enum):
"""Enum: Standard Dialog Buttons."""
- OK = 0
- CANCEL = 1
- YES = 2
- NO = 3
- OPEN = 4
- CLOSE = 5
- BROWSE = 6
- LIST = 7
- NEW = 8
- CREATE = 9
- RESET = 10
+ OK = 0
+ CANCEL = 1
+ YES = 2
+ NO = 3
+ OPEN = 4
+ CLOSE = 5
+ SAVE = 6
+ BROWSE = 7
+ LIST = 8
+ NEW = 9
+ CREATE = 10
+ RESET = 11
+ INSERT = 12
+ APPLY = 13
+ BUILD = 14
+ PRINT = 15
+ PREVIEW = 16
diff --git a/tests/files/all_icons.json b/tests/files/all_icons.json
index 41797f20..0579a3df 100644
--- a/tests/files/all_icons.json
+++ b/tests/files/all_icons.json
@@ -66,11 +66,17 @@
"btn_no",
"btn_open",
"btn_close",
+ "btn_save",
"btn_browse",
"btn_list",
"btn_new",
"btn_create",
"btn_reset",
+ "btn_insert",
+ "btn_apply",
+ "btn_build",
+ "btn_print",
+ "btn_preview",
"add",
"bookmarks",
diff --git a/utils/icon_themes.py b/utils/icon_themes.py
index e5c2268f..cd133dbc 100644
--- a/utils/icon_themes.py
+++ b/utils/icon_themes.py
@@ -113,11 +113,17 @@ ICONS = [
"btn_no",
"btn_open",
"btn_close",
+ "btn_save",
"btn_browse",
"btn_list",
"btn_new",
"btn_create",
"btn_reset",
+ "btn_insert",
+ "btn_apply",
+ "btn_build",
+ "btn_print",
+ "btn_preview",
"add",
"bookmarks",
diff --git a/utils/icon_themes/font_awesome.json b/utils/icon_themes/font_awesome.json
index 1769409e..82f295fc 100644
--- a/utils/icon_themes/font_awesome.json
+++ b/utils/icon_themes/font_awesome.json
@@ -66,11 +66,17 @@
"btn_no": "circle-xmark",
"btn_open": "file-arrow-up",
"btn_close": "circle-xmark",
+ "btn_save": "floppy-disk",
"btn_browse": "folder-open",
"btn_list": "list",
"btn_new": "plus",
"btn_create": "star",
"btn_reset": "rotate-left",
+ "btn_insert": "i-cursor",
+ "btn_apply": "square-check",
+ "btn_build": "up-right-from-square",
+ "btn_print": "print",
+ "btn_preview": "eye",
"add": "plus",
"bookmarks": "bookmark",
diff --git a/utils/icon_themes/material_symbols.json b/utils/icon_themes/material_symbols.json
index 3d1adc47..0727a21c 100644
--- a/utils/icon_themes/material_symbols.json
+++ b/utils/icon_themes/material_symbols.json
@@ -65,12 +65,18 @@
"btn_yes": "check_circle",
"btn_no": "do_not_disturb_on",
"btn_open": "open_in_new",
- "btn_close": "close",
+ "btn_close": "cancel",
+ "btn_save": "file_save",
"btn_browse": "folder_open",
"btn_list": "format_list_bulleted",
"btn_new": "new_window",
"btn_create": "star",
"btn_reset": "undo",
+ "btn_insert": "insert_text",
+ "btn_apply": "check_box",
+ "btn_build": "export_notes",
+ "btn_print": "print",
+ "btn_preview": "preview",
"add": "add",
"bookmarks": "bookmarks",
diff --git a/utils/icon_themes/remix.json b/utils/icon_themes/remix.json
index da78a84f..9e4bd207 100644
--- a/utils/icon_themes/remix.json
+++ b/utils/icon_themes/remix.json
@@ -66,11 +66,17 @@
"btn_no": "close-circle",
"btn_open": "file-upload",
"btn_close": "close-circle",
+ "btn_save": "save-3",
"btn_browse": "folder-2",
"btn_list": "list-unordered",
"btn_new": "add",
"btn_create": "star-fill",
"btn_reset": "reset-left",
+ "btn_insert": "add-box",
+ "btn_apply": "checkbox",
+ "btn_build": "stack",
+ "btn_print": "printer",
+ "btn_preview": "eye",
"add": "add",
"bookmarks": "bookmark",
From c9c2b354554dd1265e30222a57ca4f757cc6d544 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 25 Oct 2025 23:38:19 +0200
Subject: [PATCH 06/10] Update dialog buttons on all dialogs and tools
---
novelwriter/dialogs/about.py | 10 +++++--
novelwriter/dialogs/docmerge.py | 23 ++++++++++------
novelwriter/dialogs/docsplit.py | 17 ++++++++----
novelwriter/dialogs/editlabel.py | 18 ++++++++----
novelwriter/dialogs/preferences.py | 17 ++++++++----
novelwriter/dialogs/projectsettings.py | 21 ++++++++------
novelwriter/dialogs/quotes.py | 21 ++++++++------
novelwriter/dialogs/wordlist.py | 16 +++++++----
novelwriter/gui/theme.py | 28 +++++++++++--------
novelwriter/tools/dictionaries.py | 12 +++++---
novelwriter/tools/lipsum.py | 23 ++++++++--------
novelwriter/tools/manusbuild.py | 38 +++++++++++---------------
novelwriter/tools/manuscript.py | 15 +++++-----
novelwriter/tools/manussettings.py | 21 +++++++++-----
novelwriter/tools/noveldetails.py | 12 +++++---
novelwriter/tools/writingstats.py | 31 ++++++++++-----------
16 files changed, 193 insertions(+), 130 deletions(-)
diff --git a/novelwriter/dialogs/about.py b/novelwriter/dialogs/about.py
index 59d4251f..429930cd 100644
--- a/novelwriter/dialogs/about.py
+++ b/novelwriter/dialogs/about.py
@@ -33,10 +33,11 @@ from PyQt6.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import readTextFile
+from novelwriter.enum import nwStandardButton
from novelwriter.extensions.configlayout import NColorLabel
from novelwriter.extensions.modified import NDialog
from novelwriter.extensions.versioninfo import VersionInfoWidget
-from novelwriter.types import QtAlignRightTop, QtDialogClose, QtHexArgb
+from novelwriter.types import QtAlignRightTop, QtHexArgb
if TYPE_CHECKING:
from PyQt6.QtGui import QCloseEvent
@@ -82,8 +83,11 @@ class GuiAbout(NDialog):
self.txtCredits.setViewportMargins(0, 8, 8, 0)
# Buttons
- self.btnBox = QDialogButtonBox(QtDialogClose, self)
- self.btnBox.rejected.connect(self.reject)
+ self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self)
+ self.btnClose.clicked.connect(self.reject)
+
+ self.btnBox = QDialogButtonBox(self)
+ self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole)
# Assemble
self.innerBox = QVBoxLayout()
diff --git a/novelwriter/dialogs/docmerge.py b/novelwriter/dialogs/docmerge.py
index 2c392374..0c8bdfa2 100644
--- a/novelwriter/dialogs/docmerge.py
+++ b/novelwriter/dialogs/docmerge.py
@@ -33,10 +33,11 @@ from PyQt6.QtWidgets import (
)
from novelwriter import SHARED
+from novelwriter.enum import nwStandardButton
from novelwriter.extensions.configlayout import NColorLabel
from novelwriter.extensions.modified import NDialog
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import QtAccepted, QtDialogCancel, QtDialogOk, QtDialogReset, QtUserRole
+from novelwriter.types import QtAccepted, QtUserRole
logger = logging.getLogger(__name__)
@@ -85,13 +86,19 @@ class GuiDocMerge(NDialog):
self.optBox.setColumnStretch(2, 1)
# Buttons
- self.buttonBox = QDialogButtonBox(QtDialogOk | QtDialogCancel, self)
- self.buttonBox.accepted.connect(self.accept)
- self.buttonBox.rejected.connect(self.reject)
+ self.btnOk = SHARED.theme.getStandardButton(nwStandardButton.OK, self)
+ self.btnOk.clicked.connect(self.accept)
- self.resetButton = self.buttonBox.addButton(QtDialogReset)
- if self.resetButton:
- self.resetButton.clicked.connect(self._resetList)
+ self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self)
+ self.btnCancel.clicked.connect(self.reject)
+
+ self.btnReset = SHARED.theme.getStandardButton(nwStandardButton.RESET, self)
+ self.btnReset.clicked.connect(self._resetList)
+
+ self.btnBox = QDialogButtonBox(self)
+ self.btnBox.addButton(self.btnOk, QDialogButtonBox.ButtonRole.AcceptRole)
+ self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole)
+ self.btnBox.addButton(self.btnReset, QDialogButtonBox.ButtonRole.ResetRole)
# Assemble
self.outerBox = QVBoxLayout()
@@ -103,7 +110,7 @@ class GuiDocMerge(NDialog):
self.outerBox.addSpacing(8)
self.outerBox.addLayout(self.optBox)
self.outerBox.addSpacing(12)
- self.outerBox.addWidget(self.buttonBox)
+ self.outerBox.addWidget(self.btnBox)
self.setLayout(self.outerBox)
# Load Content
diff --git a/novelwriter/dialogs/docsplit.py b/novelwriter/dialogs/docsplit.py
index 5850aff3..7355409e 100644
--- a/novelwriter/dialogs/docsplit.py
+++ b/novelwriter/dialogs/docsplit.py
@@ -33,10 +33,11 @@ from PyQt6.QtWidgets import (
)
from novelwriter import SHARED
+from novelwriter.enum import nwStandardButton
from novelwriter.extensions.configlayout import NColorLabel
from novelwriter.extensions.modified import NComboBox, NDialog
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import QtAccepted, QtDialogCancel, QtDialogOk, QtUserRole
+from novelwriter.types import QtAccepted, QtUserRole
logger = logging.getLogger(__name__)
@@ -117,9 +118,15 @@ class GuiDocSplit(NDialog):
self.optBox.setColumnStretch(3, 1)
# Buttons
- self.buttonBox = QDialogButtonBox(QtDialogOk | QtDialogCancel, self)
- self.buttonBox.accepted.connect(self.accept)
- self.buttonBox.rejected.connect(self.reject)
+ self.btnOk = SHARED.theme.getStandardButton(nwStandardButton.OK, self)
+ self.btnOk.clicked.connect(self.accept)
+
+ self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self)
+ self.btnCancel.clicked.connect(self.reject)
+
+ self.btnBox = QDialogButtonBox(self)
+ self.btnBox.addButton(self.btnOk, QDialogButtonBox.ButtonRole.AcceptRole)
+ self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole)
# Assemble
self.outerBox = QVBoxLayout()
@@ -132,7 +139,7 @@ class GuiDocSplit(NDialog):
self.outerBox.addSpacing(8)
self.outerBox.addLayout(self.optBox)
self.outerBox.addSpacing(12)
- self.outerBox.addWidget(self.buttonBox)
+ self.outerBox.addWidget(self.btnBox)
self.setLayout(self.outerBox)
# Load Content
diff --git a/novelwriter/dialogs/editlabel.py b/novelwriter/dialogs/editlabel.py
index b8b4bae4..4d56e07e 100644
--- a/novelwriter/dialogs/editlabel.py
+++ b/novelwriter/dialogs/editlabel.py
@@ -27,8 +27,10 @@ import logging
from PyQt6.QtWidgets import QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit, QVBoxLayout, QWidget
+from novelwriter import SHARED
+from novelwriter.enum import nwStandardButton
from novelwriter.extensions.modified import NDialog
-from novelwriter.types import QtAccepted, QtDialogCancel, QtDialogOk
+from novelwriter.types import QtAccepted
logger = logging.getLogger(__name__)
@@ -54,9 +56,15 @@ class GuiEditLabel(NDialog):
self.lblValue.setBuddy(self.lblValue)
# Buttons
- self.buttonBox = QDialogButtonBox(QtDialogOk | QtDialogCancel, self)
- self.buttonBox.accepted.connect(self.accept)
- self.buttonBox.rejected.connect(self.reject)
+ self.btnOk = SHARED.theme.getStandardButton(nwStandardButton.OK, self)
+ self.btnOk.clicked.connect(self.accept)
+
+ self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self)
+ self.btnCancel.clicked.connect(self.reject)
+
+ self.btnBox = QDialogButtonBox(self)
+ self.btnBox.addButton(self.btnOk, QDialogButtonBox.ButtonRole.AcceptRole)
+ self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole)
# Assemble
self.innerBox = QHBoxLayout()
@@ -67,7 +75,7 @@ class GuiEditLabel(NDialog):
self.outerBox = QVBoxLayout()
self.outerBox.setSpacing(12)
self.outerBox.addLayout(self.innerBox, 1)
- self.outerBox.addWidget(self.buttonBox, 0)
+ self.outerBox.addWidget(self.btnBox, 0)
self.setLayout(self.outerBox)
diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py
index 758f7908..7062f4ad 100644
--- a/novelwriter/dialogs/preferences.py
+++ b/novelwriter/dialogs/preferences.py
@@ -38,13 +38,14 @@ from novelwriter.common import compact, describeFont, processDialogSymbols, uniq
from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_ICONS, DEF_TREECOL
from novelwriter.constants import nwLabels, nwQuotes, nwUnicode, trConst
from novelwriter.dialogs.quotes import GuiQuoteSelect
+from novelwriter.enum import nwStandardButton
from novelwriter.extensions.configlayout import NColorLabel, NScrollableForm
from novelwriter.extensions.modified import (
NComboBox, NDialog, NDoubleSpinBox, NIconToolButton, NSpinBox
)
from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import QtAlignCenter, QtDialogCancel, QtDialogSave
+from novelwriter.types import QtAlignCenter
logger = logging.getLogger(__name__)
@@ -89,9 +90,15 @@ class GuiPreferences(NDialog):
self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
# Buttons
- self.buttonBox = QDialogButtonBox(QtDialogSave | QtDialogCancel, self)
- self.buttonBox.accepted.connect(self._doSave)
- self.buttonBox.rejected.connect(self.reject)
+ self.btnSave = SHARED.theme.getStandardButton(nwStandardButton.SAVE, self)
+ self.btnSave.clicked.connect(self._doSave)
+
+ self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self)
+ self.btnCancel.clicked.connect(self.reject)
+
+ self.btnBox = QDialogButtonBox(self)
+ self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.AcceptRole)
+ self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole)
# Assemble
self.searchBox = QHBoxLayout()
@@ -107,7 +114,7 @@ class GuiPreferences(NDialog):
self.outerBox = QVBoxLayout()
self.outerBox.addLayout(self.searchBox)
self.outerBox.addLayout(self.mainBox)
- self.outerBox.addWidget(self.buttonBox)
+ self.outerBox.addWidget(self.btnBox)
self.outerBox.setSpacing(8)
self.setLayout(self.outerBox)
diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py
index 5e16d52e..a6f090cf 100644
--- a/novelwriter/dialogs/projectsettings.py
+++ b/novelwriter/dialogs/projectsettings.py
@@ -41,15 +41,12 @@ from novelwriter import CONFIG, SHARED
from novelwriter.common import formatFileFilter, qtAddAction, qtLambda, simplified
from novelwriter.constants import nwLabels, trConst
from novelwriter.core.status import CUSTOM_COL, NWStatus, StatusEntry
-from novelwriter.enum import nwStatusShape
+from novelwriter.enum import nwStandardButton, nwStatusShape
from novelwriter.extensions.configlayout import NColorLabel, NFixedPage, NScrollableForm
from novelwriter.extensions.modified import NComboBox, NDialog, NIconToolButton
from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import (
- QtDialogCancel, QtDialogSave, QtSizeMinimum, QtSizeMinimumExpanding,
- QtUserRole
-)
+from novelwriter.types import QtSizeMinimum, QtSizeMinimumExpanding, QtUserRole
logger = logging.getLogger(__name__)
@@ -95,9 +92,15 @@ class GuiProjectSettings(NDialog):
self.sidebar.buttonClicked.connect(self._sidebarClicked)
# Buttons
- self.buttonBox = QDialogButtonBox(QtDialogSave | QtDialogCancel, self)
- self.buttonBox.accepted.connect(self._doSave)
- self.buttonBox.rejected.connect(self.reject)
+ self.btnSave = SHARED.theme.getStandardButton(nwStandardButton.SAVE, self)
+ self.btnSave.clicked.connect(self._doSave)
+
+ self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self)
+ self.btnCancel.clicked.connect(self.reject)
+
+ self.btnBox = QDialogButtonBox(self)
+ self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.AcceptRole)
+ self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole)
# Content
SHARED.project.countStatus()
@@ -126,7 +129,7 @@ class GuiProjectSettings(NDialog):
self.outerBox = QVBoxLayout()
self.outerBox.addLayout(self.topBox)
self.outerBox.addLayout(self.mainBox)
- self.outerBox.addWidget(self.buttonBox)
+ self.outerBox.addWidget(self.btnBox)
self.outerBox.setSpacing(8)
self.setLayout(self.outerBox)
diff --git a/novelwriter/dialogs/quotes.py b/novelwriter/dialogs/quotes.py
index 6030ecfe..2a813d39 100644
--- a/novelwriter/dialogs/quotes.py
+++ b/novelwriter/dialogs/quotes.py
@@ -32,12 +32,11 @@ from PyQt6.QtWidgets import (
QListWidgetItem, QVBoxLayout, QWidget
)
+from novelwriter import SHARED
from novelwriter.constants import nwQuotes, trConst
+from novelwriter.enum import nwStandardButton
from novelwriter.extensions.modified import NDialog
-from novelwriter.types import (
- QtAccepted, QtAlignCenter, QtAlignTop, QtDialogCancel, QtDialogOk,
- QtUserRole
-)
+from novelwriter.types import QtAccepted, QtAlignCenter, QtAlignTop, QtUserRole
logger = logging.getLogger(__name__)
@@ -91,9 +90,15 @@ class GuiQuoteSelect(NDialog):
self.listBox.setMinimumHeight(150)
# Buttons
- self.buttonBox = QDialogButtonBox(QtDialogOk | QtDialogCancel, self)
- self.buttonBox.accepted.connect(self.accept)
- self.buttonBox.rejected.connect(self.reject)
+ self.btnOk = SHARED.theme.getStandardButton(nwStandardButton.OK, self)
+ self.btnOk.clicked.connect(self.accept)
+
+ self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self)
+ self.btnCancel.clicked.connect(self.reject)
+
+ self.btnBox = QDialogButtonBox(self)
+ self.btnBox.addButton(self.btnOk, QDialogButtonBox.ButtonRole.AcceptRole)
+ self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole)
# Assemble
self.labelBox.addWidget(self.previewLabel, 0, QtAlignTop)
@@ -103,7 +108,7 @@ class GuiQuoteSelect(NDialog):
self.innerBox.addWidget(self.listBox)
self.outerBox.addLayout(self.innerBox)
- self.outerBox.addWidget(self.buttonBox)
+ self.outerBox.addWidget(self.btnBox)
self.setLayout(self.outerBox)
diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py
index 19e2da9d..c1db00ee 100644
--- a/novelwriter/dialogs/wordlist.py
+++ b/novelwriter/dialogs/wordlist.py
@@ -37,9 +37,9 @@ from PyQt6.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import formatFileFilter
from novelwriter.core.spellcheck import UserDictionary
+from novelwriter.enum import nwStandardButton
from novelwriter.extensions.configlayout import NColorLabel
from novelwriter.extensions.modified import NDialog, NIconToolButton
-from novelwriter.types import QtDialogClose, QtDialogSave
if TYPE_CHECKING:
from PyQt6.QtGui import QCloseEvent
@@ -110,9 +110,15 @@ class GuiWordList(NDialog):
self.editBox.addWidget(self.delButton, 0)
# Buttons
- self.buttonBox = QDialogButtonBox(QtDialogSave | QtDialogClose, self)
- self.buttonBox.accepted.connect(self._doSave)
- self.buttonBox.rejected.connect(self.reject)
+ self.btnSave = SHARED.theme.getStandardButton(nwStandardButton.SAVE, self)
+ self.btnSave.clicked.connect(self._doSave)
+
+ self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self)
+ self.btnClose.clicked.connect(self.reject)
+
+ self.btnBox = QDialogButtonBox(self)
+ self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.AcceptRole)
+ self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole)
# Assemble
self.outerBox = QVBoxLayout()
@@ -120,7 +126,7 @@ class GuiWordList(NDialog):
self.outerBox.addWidget(self.listBox, 1)
self.outerBox.addLayout(self.editBox, 0)
self.outerBox.addSpacing(12)
- self.outerBox.addWidget(self.buttonBox, 0)
+ self.outerBox.addWidget(self.btnBox, 0)
self.outerBox.setSpacing(4)
self.setLayout(self.outerBox)
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index 441c9cd1..6293a9f6 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -57,17 +57,23 @@ STYLES_MIN_TOOLBUTTON = "minimalToolButton"
STYLES_BIG_TOOLBUTTON = "bigToolButton"
STANDARD_BUTTONS = {
- nwStandardButton.OK: (QT_TRANSLATE_NOOP("Button", "OK"), "btn_ok", "blue"),
- nwStandardButton.CANCEL: (QT_TRANSLATE_NOOP("Button", "Cancel"), "btn_cancel", "red"),
- nwStandardButton.YES: (QT_TRANSLATE_NOOP("Button", "Yes"), "btn_yes", "green"),
- nwStandardButton.NO: (QT_TRANSLATE_NOOP("Button", "No"), "btn_no", "red"),
- nwStandardButton.OPEN: (QT_TRANSLATE_NOOP("Button", "Open"), "btn_open", "blue"),
- nwStandardButton.CLOSE: (QT_TRANSLATE_NOOP("Button", "Close"), "btn_close", "red"),
- nwStandardButton.BROWSE: (QT_TRANSLATE_NOOP("Button", "Browse"), "btn_browse", "yellow"),
- nwStandardButton.LIST: (QT_TRANSLATE_NOOP("Button", "List"), "btn_list", "blue"),
- nwStandardButton.NEW: (QT_TRANSLATE_NOOP("Button", "New"), "btn_new", "green"),
- nwStandardButton.CREATE: (QT_TRANSLATE_NOOP("Button", "Create"), "btn_create", "yellow"),
- nwStandardButton.RESET: (QT_TRANSLATE_NOOP("Button", "Reset"), "btn_reset", "green"),
+ nwStandardButton.OK: (QT_TRANSLATE_NOOP("Button", "OK"), "btn_ok", "blue"),
+ nwStandardButton.CANCEL: (QT_TRANSLATE_NOOP("Button", "Cancel"), "btn_cancel", "red"),
+ nwStandardButton.YES: (QT_TRANSLATE_NOOP("Button", "&Yes"), "btn_yes", "green"),
+ nwStandardButton.NO: (QT_TRANSLATE_NOOP("Button", "&No"), "btn_no", "red"),
+ nwStandardButton.OPEN: (QT_TRANSLATE_NOOP("Button", "Open"), "btn_open", "blue"),
+ nwStandardButton.CLOSE: (QT_TRANSLATE_NOOP("Button", "Close"), "btn_close", "faded"),
+ nwStandardButton.SAVE: (QT_TRANSLATE_NOOP("Button", "Save"), "btn_save", "blue"),
+ nwStandardButton.BROWSE: (QT_TRANSLATE_NOOP("Button", "Browse"), "btn_browse", "yellow"),
+ nwStandardButton.LIST: (QT_TRANSLATE_NOOP("Button", "List"), "btn_list", "blue"),
+ nwStandardButton.NEW: (QT_TRANSLATE_NOOP("Button", "New"), "btn_new", "green"),
+ nwStandardButton.CREATE: (QT_TRANSLATE_NOOP("Button", "Create"), "btn_create", "yellow"),
+ nwStandardButton.RESET: (QT_TRANSLATE_NOOP("Button", "Reset"), "btn_reset", "green"),
+ nwStandardButton.INSERT: (QT_TRANSLATE_NOOP("Button", "Insert"), "btn_insert", "blue"),
+ nwStandardButton.APPLY: (QT_TRANSLATE_NOOP("Button", "Apply"), "btn_apply", "blue"),
+ nwStandardButton.BUILD: (QT_TRANSLATE_NOOP("Button", "Build"), "btn_build", "blue"),
+ nwStandardButton.PRINT: (QT_TRANSLATE_NOOP("Button", "Print"), "btn_print", "blue"),
+ nwStandardButton.PREVIEW: (QT_TRANSLATE_NOOP("Button", "Preview"), "btn_preview", "blue"),
}
diff --git a/novelwriter/tools/dictionaries.py b/novelwriter/tools/dictionaries.py
index 5cc97021..06f26a95 100644
--- a/novelwriter/tools/dictionaries.py
+++ b/novelwriter/tools/dictionaries.py
@@ -37,9 +37,10 @@ from PyQt6.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import formatFileFilter, formatInt, getFileSize, openExternalPath
+from novelwriter.enum import nwStandardButton
from novelwriter.error import formatException
from novelwriter.extensions.modified import NIconToolButton, NNonBlockingDialog
-from novelwriter.types import QtDialogClose, QtHexArgb
+from novelwriter.types import QtHexArgb
logger = logging.getLogger(__name__)
@@ -110,8 +111,11 @@ class GuiDictionaries(NNonBlockingDialog):
self.infoBox.setFrameStyle(QFrame.Shape.NoFrame)
# Buttons
- self.buttonBox = QDialogButtonBox(QtDialogClose, self)
- self.buttonBox.rejected.connect(self.reject)
+ self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self)
+ self.btnClose.clicked.connect(self.reject)
+
+ self.btnBox = QDialogButtonBox(self)
+ self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.AcceptRole)
# Assemble
self.outerBox = QVBoxLayout()
@@ -123,7 +127,7 @@ class GuiDictionaries(NNonBlockingDialog):
self.outerBox.addLayout(self.inBox, 0)
self.outerBox.addWidget(self.infoBox, 1)
self.outerBox.addSpacing(8)
- self.outerBox.addWidget(self.buttonBox, 0)
+ self.outerBox.addWidget(self.btnBox, 0)
self.setLayout(self.outerBox)
diff --git a/novelwriter/tools/lipsum.py b/novelwriter/tools/lipsum.py
index d28fb845..49b793a5 100644
--- a/novelwriter/tools/lipsum.py
+++ b/novelwriter/tools/lipsum.py
@@ -34,9 +34,10 @@ from PyQt6.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import readTextFile
+from novelwriter.enum import nwStandardButton
from novelwriter.extensions.modified import NDialog
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import QtAlignLeft, QtAlignRight, QtDialogClose, QtRoleAction
+from novelwriter.types import QtAlignLeft, QtAlignRight
logger = logging.getLogger(__name__)
@@ -91,22 +92,22 @@ class GuiLipsum(NDialog):
self.innerBox.addLayout(self.formBox)
# Buttons
- self.buttonBox = QDialogButtonBox(self)
- self.buttonBox.rejected.connect(self.reject)
+ self.btnInsert = SHARED.theme.getStandardButton(nwStandardButton.INSERT, self)
+ self.btnInsert.clicked.connect(self._doInsert)
+ self.btnInsert.setAutoDefault(False)
- self.btnClose = self.buttonBox.addButton(QtDialogClose)
- if self.btnClose:
- self.btnClose.setAutoDefault(False)
+ self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self)
+ self.btnClose.clicked.connect(self.reject)
+ self.btnClose.setAutoDefault(False)
- self.btnInsert = self.buttonBox.addButton(self.tr("Insert"), QtRoleAction)
- if self.btnInsert:
- self.btnInsert.clicked.connect(self._doInsert)
- self.btnInsert.setAutoDefault(False)
+ self.btnBox = QDialogButtonBox(self)
+ self.btnBox.addButton(self.btnInsert, QDialogButtonBox.ButtonRole.ApplyRole)
+ self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole)
# Assemble
self.outerBox = QVBoxLayout()
self.outerBox.addLayout(self.innerBox)
- self.outerBox.addWidget(self.buttonBox)
+ self.outerBox.addWidget(self.btnBox)
self.outerBox.setSpacing(16)
self.setLayout(self.outerBox)
diff --git a/novelwriter/tools/manusbuild.py b/novelwriter/tools/manusbuild.py
index 19070b14..c77898d7 100644
--- a/novelwriter/tools/manusbuild.py
+++ b/novelwriter/tools/manusbuild.py
@@ -32,7 +32,7 @@ from PyQt6.QtCore import QTimer, pyqtSlot
from PyQt6.QtWidgets import (
QAbstractButton, QAbstractItemView, QDialogButtonBox, QFileDialog,
QGridLayout, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem,
- QPushButton, QSplitter, QVBoxLayout, QWidget
+ QSplitter, QVBoxLayout, QWidget
)
from novelwriter import SHARED
@@ -40,10 +40,10 @@ from novelwriter.common import makeFileNameSafe, openExternalPath
from novelwriter.constants import nwLabels
from novelwriter.core.docbuild import NWBuildDocument
from novelwriter.core.item import NWItem
-from novelwriter.enum import nwBuildFmt
-from novelwriter.extensions.modified import NDialog, NIconToolButton
+from novelwriter.enum import nwBuildFmt, nwStandardButton
+from novelwriter.extensions.modified import NDialog, NIconToolButton, NPushButton
from novelwriter.extensions.progressbars import NProgressSimple
-from novelwriter.types import QtAlignCenter, QtDialogClose, QtRoleAction, QtRoleReject, QtUserRole
+from novelwriter.types import QtAlignCenter, QtRoleAction, QtRoleReject, QtUserRole
if TYPE_CHECKING:
from PyQt6.QtGui import QCloseEvent
@@ -178,25 +178,19 @@ class GuiManuscriptBuild(NDialog):
self.buildBox.setVerticalSpacing(4)
# Dialog Buttons
- self.buttonBox = QDialogButtonBox(self)
-
- self.btnOpen = QPushButton(
- SHARED.theme.getIcon("browse", "yellow"), self.tr("Open Folder"), self
- )
- self.btnOpen.setIconSize(bSz)
+ self.btnOpen = NPushButton(self, self.tr("Open Folder"), bSz, "browse", "yellow")
self.btnOpen.setAutoDefault(False)
- self.buttonBox.addButton(self.btnOpen, QtRoleAction)
- self.btnBuild = QPushButton(
- SHARED.theme.getIcon("sb_build", "blue"), self.tr("&Build"), self
- )
- self.btnBuild.setIconSize(bSz)
+ self.btnBuild = SHARED.theme.getStandardButton(nwStandardButton.BUILD, self)
self.btnBuild.setAutoDefault(True)
- self.buttonBox.addButton(self.btnBuild, QtRoleAction)
- self.btnClose = self.buttonBox.addButton(QtDialogClose)
- if self.btnClose:
- self.btnClose.setAutoDefault(False)
+ self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self)
+ self.btnClose.setAutoDefault(False)
+
+ self.btnBox = QDialogButtonBox(self)
+ self.btnBox.addButton(self.btnOpen, QtRoleAction)
+ self.btnBox.addButton(self.btnBuild, QtRoleAction)
+ self.btnBox.addButton(self.btnClose, QtRoleReject)
# Assemble GUI
# ============
@@ -223,7 +217,7 @@ class GuiManuscriptBuild(NDialog):
self.outerBox.addSpacing(4)
self.outerBox.addLayout(self.buildBox, 0)
self.outerBox.addSpacing(16)
- self.outerBox.addWidget(self.buttonBox, 0)
+ self.outerBox.addWidget(self.btnBox, 0)
self.outerBox.setSpacing(0)
self.setLayout(self.outerBox)
@@ -239,7 +233,7 @@ class GuiManuscriptBuild(NDialog):
# Signals
self.btnReset.clicked.connect(self._doResetBuildName)
self.btnBrowse.clicked.connect(self._doSelectPath)
- self.buttonBox.clicked.connect(self._dialogButtonClicked)
+ self.btnBox.clicked.connect(self._dialogButtonClicked)
self.listFormats.itemSelectionChanged.connect(self._resetProgress)
logger.debug("Ready: GuiManuscriptBuild")
@@ -266,7 +260,7 @@ class GuiManuscriptBuild(NDialog):
@pyqtSlot("QAbstractButton*")
def _dialogButtonClicked(self, button: QAbstractButton) -> None:
"""Handle button clicks from the dialog button box."""
- role = self.buttonBox.buttonRole(button)
+ role = self.btnBox.buttonRole(button)
if role == QtRoleAction:
if button == self.btnBuild:
self._runBuild()
diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py
index 8c458db3..79a93787 100644
--- a/novelwriter/tools/manuscript.py
+++ b/novelwriter/tools/manuscript.py
@@ -36,9 +36,9 @@ from PyQt6.QtGui import (
from PyQt6.QtPrintSupport import QPrinter, QPrintPreviewDialog
from PyQt6.QtWidgets import (
QAbstractItemView, QApplication, QFormLayout, QGridLayout, QHBoxLayout,
- QLabel, QListWidget, QListWidgetItem, QPushButton, QSplitter,
- QStackedWidget, QTabWidget, QTextBrowser, QTreeWidget, QTreeWidgetItem,
- QVBoxLayout, QWidget
+ QLabel, QListWidget, QListWidgetItem, QSplitter, QStackedWidget,
+ QTabWidget, QTextBrowser, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
+ QWidget
)
from novelwriter import CONFIG, SHARED
@@ -46,6 +46,7 @@ from novelwriter.common import fuzzyTime, qtLambda
from novelwriter.constants import nwHeadFmt, nwLabels, nwStats, nwUnicode, trStats
from novelwriter.core.buildsettings import BuildCollection, BuildSettings
from novelwriter.core.docbuild import NWBuildDocument
+from novelwriter.enum import nwStandardButton
from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton, NToolDialog
from novelwriter.extensions.progressbars import NProgressCircle
from novelwriter.extensions.switch import NSwitch
@@ -171,16 +172,16 @@ class GuiManuscript(NToolDialog):
# Process Controls
# ================
- self.btnPreview = QPushButton(self.tr("Preview"), self)
+ self.btnPreview = SHARED.theme.getStandardButton(nwStandardButton.PREVIEW, self)
self.btnPreview.clicked.connect(self._generatePreview)
- self.btnPrint = QPushButton(self.tr("Print"), self)
+ self.btnPrint = SHARED.theme.getStandardButton(nwStandardButton.PRINT, self)
self.btnPrint.clicked.connect(self._printDocument)
- self.btnBuild = QPushButton(self.tr("Build"), self)
+ self.btnBuild = SHARED.theme.getStandardButton(nwStandardButton.BUILD, self)
self.btnBuild.clicked.connect(self._buildManuscript)
- self.btnClose = QPushButton(self.tr("Close"), self)
+ self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self)
self.btnClose.clicked.connect(qtLambda(self.close))
self.processBox = QGridLayout()
diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py
index e8bf94b6..795c7eed 100644
--- a/novelwriter/tools/manussettings.py
+++ b/novelwriter/tools/manussettings.py
@@ -40,6 +40,7 @@ from novelwriter import CONFIG, SHARED
from novelwriter.common import describeFont, fontMatcher, qtAddAction, qtLambda
from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwUnicode, trConst
from novelwriter.core.buildsettings import BuildSettings, FilterMode
+from novelwriter.enum import nwStandardButton
from novelwriter.extensions.configlayout import (
NColorLabel, NFixedPage, NScrollableForm, NScrollablePage
)
@@ -50,9 +51,8 @@ from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.switchbox import NSwitchBox
from novelwriter.types import (
- QtAlignCenter, QtAlignLeft, QtDialogApply, QtDialogClose, QtDialogSave,
- QtHeaderFixed, QtHeaderStretch, QtRoleAccept, QtRoleApply, QtRoleReject,
- QtUserRole
+ QtAlignCenter, QtAlignLeft, QtHeaderFixed, QtHeaderStretch, QtRoleAccept,
+ QtRoleApply, QtRoleReject, QtUserRole
)
if TYPE_CHECKING:
@@ -125,8 +125,15 @@ class GuiBuildSettings(NToolDialog):
self.toolStack.addWidget(self.optTabFormatting)
# Buttons
- self.buttonBox = QDialogButtonBox(QtDialogApply | QtDialogSave | QtDialogClose, self)
- self.buttonBox.clicked.connect(self._dialogButtonClicked)
+ self.btnApply = SHARED.theme.getStandardButton(nwStandardButton.APPLY, self)
+ self.btnSave = SHARED.theme.getStandardButton(nwStandardButton.SAVE, self)
+ self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self)
+
+ self.btnBox = QDialogButtonBox(self)
+ self.btnBox.addButton(self.btnApply, QDialogButtonBox.ButtonRole.ApplyRole)
+ self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.AcceptRole)
+ self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole)
+ self.btnBox.clicked.connect(self._dialogButtonClicked)
# Assemble
self.topBox = QHBoxLayout()
@@ -143,7 +150,7 @@ class GuiBuildSettings(NToolDialog):
self.outerBox = QVBoxLayout()
self.outerBox.addLayout(self.topBox)
self.outerBox.addLayout(self.mainBox)
- self.outerBox.addWidget(self.buttonBox)
+ self.outerBox.addWidget(self.btnBox)
self.outerBox.setSpacing(12)
self.setLayout(self.outerBox)
@@ -205,7 +212,7 @@ class GuiBuildSettings(NToolDialog):
@pyqtSlot("QAbstractButton*")
def _dialogButtonClicked(self, button: QAbstractButton) -> None:
"""Handle button clicks from the dialog button box."""
- role = self.buttonBox.buttonRole(button)
+ role = self.btnBox.buttonRole(button)
if role == QtRoleApply:
self._applyChanges()
self._emitBuildData()
diff --git a/novelwriter/tools/noveldetails.py b/novelwriter/tools/noveldetails.py
index 7447b553..bfec8938 100644
--- a/novelwriter/tools/noveldetails.py
+++ b/novelwriter/tools/noveldetails.py
@@ -38,12 +38,13 @@ from PyQt6.QtWidgets import (
from novelwriter import SHARED
from novelwriter.common import formatTime, numberToRoman
from novelwriter.constants import nwUnicode
+from novelwriter.enum import nwStandardButton
from novelwriter.extensions.configlayout import NColorLabel, NFixedPage, NScrollablePage
from novelwriter.extensions.modified import NNonBlockingDialog
from novelwriter.extensions.novelselector import NovelSelector
from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import QtAlignRight, QtDecoration, QtDialogClose
+from novelwriter.types import QtAlignRight, QtDecoration
if TYPE_CHECKING:
from PyQt6.QtGui import QCloseEvent
@@ -102,8 +103,11 @@ class GuiNovelDetails(NNonBlockingDialog):
self.mainStack.addWidget(self.contentsPage)
# Buttons
- self.buttonBox = QDialogButtonBox(QtDialogClose, self)
- self.buttonBox.rejected.connect(self.reject)
+ self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self)
+ self.btnClose.clicked.connect(self.reject)
+
+ self.btnBox = QDialogButtonBox(self)
+ self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole)
# Assemble
self.topBox = QHBoxLayout()
@@ -119,7 +123,7 @@ class GuiNovelDetails(NNonBlockingDialog):
self.outerBox = QVBoxLayout()
self.outerBox.addLayout(self.topBox)
self.outerBox.addLayout(self.mainBox)
- self.outerBox.addWidget(self.buttonBox)
+ self.outerBox.addWidget(self.btnBox)
self.outerBox.setSpacing(8)
self.setLayout(self.outerBox)
diff --git a/novelwriter/tools/writingstats.py b/novelwriter/tools/writingstats.py
index 4494da8f..b6440e5c 100644
--- a/novelwriter/tools/writingstats.py
+++ b/novelwriter/tools/writingstats.py
@@ -39,13 +39,11 @@ from PyQt6.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import checkInt, checkIntTuple, formatTime, minmax, qtLambda
from novelwriter.constants import nwConst
+from novelwriter.enum import nwStandardButton
from novelwriter.error import formatException
-from novelwriter.extensions.modified import NToolDialog
+from novelwriter.extensions.modified import NPushButton, NToolDialog
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import (
- QtAlignLeftMiddle, QtAlignRight, QtAlignRightMiddle, QtDecoration,
- QtDialogClose, QtRoleAction
-)
+from novelwriter.types import QtAlignLeftMiddle, QtAlignRight, QtAlignRightMiddle, QtDecoration
if TYPE_CHECKING:
from novelwriter.guimain import GuiMain
@@ -182,6 +180,7 @@ class GuiWritingStats(NToolDialog):
# Filter Options
iPx = SHARED.theme.baseIconHeight
+ bSz = SHARED.theme.buttonIconSize
self.filterForm = QGridLayout(self)
self.filterForm.setRowStretch(6, 1)
@@ -276,6 +275,10 @@ class GuiWritingStats(NToolDialog):
self.optsBox.addWidget(self.histMax, 0)
# Buttons
+ self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self)
+ self.btnClose.clicked.connect(self._doClose)
+ self.btnClose.setAutoDefault(False)
+
self.saveJSON = QAction(self.tr("JSON Data File (.json)"), self)
self.saveJSON.triggered.connect(qtLambda(self._saveData, self.FMT_JSON))
@@ -286,17 +289,13 @@ class GuiWritingStats(NToolDialog):
self.saveMenu.addAction(self.saveJSON)
self.saveMenu.addAction(self.saveCSV)
- self.buttonBox = QDialogButtonBox(self)
- self.buttonBox.rejected.connect(self._doClose)
+ self.btnSave = NPushButton(self, self.tr("Save As"), bSz, "btn_save", "blue")
+ self.btnSave.setAutoDefault(False)
+ self.btnSave.setMenu(self.saveMenu)
- self.btnClose = self.buttonBox.addButton(QtDialogClose)
- if self.btnClose:
- self.btnClose.setAutoDefault(False)
-
- self.btnSave = self.buttonBox.addButton(self.tr("Save As"), QtRoleAction)
- if self.btnSave:
- self.btnSave.setAutoDefault(False)
- self.btnSave.setMenu(self.saveMenu)
+ self.btnBox = QDialogButtonBox(self)
+ self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole)
+ self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.ActionRole)
# Assemble
self.outerBox = QGridLayout()
@@ -304,7 +303,7 @@ class GuiWritingStats(NToolDialog):
self.outerBox.addLayout(self.optsBox, 1, 0, 1, 2)
self.outerBox.addWidget(self.infoBox, 2, 0)
self.outerBox.addWidget(self.filterBox, 2, 1)
- self.outerBox.addWidget(self.buttonBox, 3, 0, 1, 2)
+ self.outerBox.addWidget(self.btnBox, 3, 0, 1, 2)
self.outerBox.setRowStretch(0, 1)
self.setLayout(self.outerBox)
From ce939a40534b72819628e2063d8bcf8c7810f4c6 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 25 Oct 2025 23:52:56 +0200
Subject: [PATCH 07/10] Clean up button types
---
novelwriter/dialogs/about.py | 4 ++--
novelwriter/dialogs/docmerge.py | 8 ++++----
novelwriter/dialogs/docsplit.py | 6 +++---
novelwriter/dialogs/editlabel.py | 6 +++---
novelwriter/dialogs/preferences.py | 6 +++---
novelwriter/dialogs/projectsettings.py | 9 ++++++---
novelwriter/dialogs/quotes.py | 9 ++++++---
novelwriter/dialogs/wordlist.py | 5 +++--
novelwriter/tools/dictionaries.py | 4 ++--
novelwriter/tools/lipsum.py | 6 +++---
novelwriter/tools/manussettings.py | 8 ++++----
novelwriter/tools/noveldetails.py | 4 ++--
novelwriter/tools/writingstats.py | 9 ++++++---
novelwriter/types.py | 8 +-------
14 files changed, 48 insertions(+), 44 deletions(-)
diff --git a/novelwriter/dialogs/about.py b/novelwriter/dialogs/about.py
index 429930cd..080b3b21 100644
--- a/novelwriter/dialogs/about.py
+++ b/novelwriter/dialogs/about.py
@@ -37,7 +37,7 @@ from novelwriter.enum import nwStandardButton
from novelwriter.extensions.configlayout import NColorLabel
from novelwriter.extensions.modified import NDialog
from novelwriter.extensions.versioninfo import VersionInfoWidget
-from novelwriter.types import QtAlignRightTop, QtHexArgb
+from novelwriter.types import QtAlignRightTop, QtHexArgb, QtRoleReject
if TYPE_CHECKING:
from PyQt6.QtGui import QCloseEvent
@@ -87,7 +87,7 @@ class GuiAbout(NDialog):
self.btnClose.clicked.connect(self.reject)
self.btnBox = QDialogButtonBox(self)
- self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole)
+ self.btnBox.addButton(self.btnClose, QtRoleReject)
# Assemble
self.innerBox = QVBoxLayout()
diff --git a/novelwriter/dialogs/docmerge.py b/novelwriter/dialogs/docmerge.py
index 0c8bdfa2..9f3d6184 100644
--- a/novelwriter/dialogs/docmerge.py
+++ b/novelwriter/dialogs/docmerge.py
@@ -37,7 +37,7 @@ from novelwriter.enum import nwStandardButton
from novelwriter.extensions.configlayout import NColorLabel
from novelwriter.extensions.modified import NDialog
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import QtAccepted, QtUserRole
+from novelwriter.types import QtAccepted, QtRoleAccept, QtRoleReject, QtRoleReset, QtUserRole
logger = logging.getLogger(__name__)
@@ -96,9 +96,9 @@ class GuiDocMerge(NDialog):
self.btnReset.clicked.connect(self._resetList)
self.btnBox = QDialogButtonBox(self)
- self.btnBox.addButton(self.btnOk, QDialogButtonBox.ButtonRole.AcceptRole)
- self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole)
- self.btnBox.addButton(self.btnReset, QDialogButtonBox.ButtonRole.ResetRole)
+ self.btnBox.addButton(self.btnOk, QtRoleAccept)
+ self.btnBox.addButton(self.btnCancel, QtRoleReject)
+ self.btnBox.addButton(self.btnReset, QtRoleReset)
# Assemble
self.outerBox = QVBoxLayout()
diff --git a/novelwriter/dialogs/docsplit.py b/novelwriter/dialogs/docsplit.py
index 7355409e..059a1678 100644
--- a/novelwriter/dialogs/docsplit.py
+++ b/novelwriter/dialogs/docsplit.py
@@ -37,7 +37,7 @@ from novelwriter.enum import nwStandardButton
from novelwriter.extensions.configlayout import NColorLabel
from novelwriter.extensions.modified import NComboBox, NDialog
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import QtAccepted, QtUserRole
+from novelwriter.types import QtAccepted, QtRoleAccept, QtRoleReject, QtUserRole
logger = logging.getLogger(__name__)
@@ -125,8 +125,8 @@ class GuiDocSplit(NDialog):
self.btnCancel.clicked.connect(self.reject)
self.btnBox = QDialogButtonBox(self)
- self.btnBox.addButton(self.btnOk, QDialogButtonBox.ButtonRole.AcceptRole)
- self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole)
+ self.btnBox.addButton(self.btnOk, QtRoleAccept)
+ self.btnBox.addButton(self.btnCancel, QtRoleReject)
# Assemble
self.outerBox = QVBoxLayout()
diff --git a/novelwriter/dialogs/editlabel.py b/novelwriter/dialogs/editlabel.py
index 4d56e07e..4e998aa8 100644
--- a/novelwriter/dialogs/editlabel.py
+++ b/novelwriter/dialogs/editlabel.py
@@ -30,7 +30,7 @@ from PyQt6.QtWidgets import QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit, QV
from novelwriter import SHARED
from novelwriter.enum import nwStandardButton
from novelwriter.extensions.modified import NDialog
-from novelwriter.types import QtAccepted
+from novelwriter.types import QtAccepted, QtRoleAccept, QtRoleReject
logger = logging.getLogger(__name__)
@@ -63,8 +63,8 @@ class GuiEditLabel(NDialog):
self.btnCancel.clicked.connect(self.reject)
self.btnBox = QDialogButtonBox(self)
- self.btnBox.addButton(self.btnOk, QDialogButtonBox.ButtonRole.AcceptRole)
- self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole)
+ self.btnBox.addButton(self.btnOk, QtRoleAccept)
+ self.btnBox.addButton(self.btnCancel, QtRoleReject)
# Assemble
self.innerBox = QHBoxLayout()
diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py
index 7062f4ad..71cfca38 100644
--- a/novelwriter/dialogs/preferences.py
+++ b/novelwriter/dialogs/preferences.py
@@ -45,7 +45,7 @@ from novelwriter.extensions.modified import (
)
from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import QtAlignCenter
+from novelwriter.types import QtAlignCenter, QtRoleAccept, QtRoleReject
logger = logging.getLogger(__name__)
@@ -97,8 +97,8 @@ class GuiPreferences(NDialog):
self.btnCancel.clicked.connect(self.reject)
self.btnBox = QDialogButtonBox(self)
- self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.AcceptRole)
- self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole)
+ self.btnBox.addButton(self.btnSave, QtRoleAccept)
+ self.btnBox.addButton(self.btnCancel, QtRoleReject)
# Assemble
self.searchBox = QHBoxLayout()
diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py
index a6f090cf..dcb7f8de 100644
--- a/novelwriter/dialogs/projectsettings.py
+++ b/novelwriter/dialogs/projectsettings.py
@@ -46,7 +46,10 @@ from novelwriter.extensions.configlayout import NColorLabel, NFixedPage, NScroll
from novelwriter.extensions.modified import NComboBox, NDialog, NIconToolButton
from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import QtSizeMinimum, QtSizeMinimumExpanding, QtUserRole
+from novelwriter.types import (
+ QtRoleAccept, QtRoleReject, QtSizeMinimum, QtSizeMinimumExpanding,
+ QtUserRole
+)
logger = logging.getLogger(__name__)
@@ -99,8 +102,8 @@ class GuiProjectSettings(NDialog):
self.btnCancel.clicked.connect(self.reject)
self.btnBox = QDialogButtonBox(self)
- self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.AcceptRole)
- self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole)
+ self.btnBox.addButton(self.btnSave, QtRoleAccept)
+ self.btnBox.addButton(self.btnCancel, QtRoleReject)
# Content
SHARED.project.countStatus()
diff --git a/novelwriter/dialogs/quotes.py b/novelwriter/dialogs/quotes.py
index 2a813d39..2e625ba0 100644
--- a/novelwriter/dialogs/quotes.py
+++ b/novelwriter/dialogs/quotes.py
@@ -36,7 +36,10 @@ from novelwriter import SHARED
from novelwriter.constants import nwQuotes, trConst
from novelwriter.enum import nwStandardButton
from novelwriter.extensions.modified import NDialog
-from novelwriter.types import QtAccepted, QtAlignCenter, QtAlignTop, QtUserRole
+from novelwriter.types import (
+ QtAccepted, QtAlignCenter, QtAlignTop, QtRoleAccept, QtRoleReject,
+ QtUserRole
+)
logger = logging.getLogger(__name__)
@@ -97,8 +100,8 @@ class GuiQuoteSelect(NDialog):
self.btnCancel.clicked.connect(self.reject)
self.btnBox = QDialogButtonBox(self)
- self.btnBox.addButton(self.btnOk, QDialogButtonBox.ButtonRole.AcceptRole)
- self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole)
+ self.btnBox.addButton(self.btnOk, QtRoleAccept)
+ self.btnBox.addButton(self.btnCancel, QtRoleReject)
# Assemble
self.labelBox.addWidget(self.previewLabel, 0, QtAlignTop)
diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py
index c1db00ee..1a05e187 100644
--- a/novelwriter/dialogs/wordlist.py
+++ b/novelwriter/dialogs/wordlist.py
@@ -40,6 +40,7 @@ from novelwriter.core.spellcheck import UserDictionary
from novelwriter.enum import nwStandardButton
from novelwriter.extensions.configlayout import NColorLabel
from novelwriter.extensions.modified import NDialog, NIconToolButton
+from novelwriter.types import QtRoleAccept, QtRoleReject
if TYPE_CHECKING:
from PyQt6.QtGui import QCloseEvent
@@ -117,8 +118,8 @@ class GuiWordList(NDialog):
self.btnClose.clicked.connect(self.reject)
self.btnBox = QDialogButtonBox(self)
- self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.AcceptRole)
- self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole)
+ self.btnBox.addButton(self.btnSave, QtRoleAccept)
+ self.btnBox.addButton(self.btnClose, QtRoleReject)
# Assemble
self.outerBox = QVBoxLayout()
diff --git a/novelwriter/tools/dictionaries.py b/novelwriter/tools/dictionaries.py
index 06f26a95..cf90d65a 100644
--- a/novelwriter/tools/dictionaries.py
+++ b/novelwriter/tools/dictionaries.py
@@ -40,7 +40,7 @@ from novelwriter.common import formatFileFilter, formatInt, getFileSize, openExt
from novelwriter.enum import nwStandardButton
from novelwriter.error import formatException
from novelwriter.extensions.modified import NIconToolButton, NNonBlockingDialog
-from novelwriter.types import QtHexArgb
+from novelwriter.types import QtHexArgb, QtRoleReject
logger = logging.getLogger(__name__)
@@ -115,7 +115,7 @@ class GuiDictionaries(NNonBlockingDialog):
self.btnClose.clicked.connect(self.reject)
self.btnBox = QDialogButtonBox(self)
- self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.AcceptRole)
+ self.btnBox.addButton(self.btnClose, QtRoleReject)
# Assemble
self.outerBox = QVBoxLayout()
diff --git a/novelwriter/tools/lipsum.py b/novelwriter/tools/lipsum.py
index 49b793a5..a970fe93 100644
--- a/novelwriter/tools/lipsum.py
+++ b/novelwriter/tools/lipsum.py
@@ -37,7 +37,7 @@ from novelwriter.common import readTextFile
from novelwriter.enum import nwStandardButton
from novelwriter.extensions.modified import NDialog
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import QtAlignLeft, QtAlignRight
+from novelwriter.types import QtAlignLeft, QtAlignRight, QtRoleApply, QtRoleReject
logger = logging.getLogger(__name__)
@@ -101,8 +101,8 @@ class GuiLipsum(NDialog):
self.btnClose.setAutoDefault(False)
self.btnBox = QDialogButtonBox(self)
- self.btnBox.addButton(self.btnInsert, QDialogButtonBox.ButtonRole.ApplyRole)
- self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole)
+ self.btnBox.addButton(self.btnInsert, QtRoleApply)
+ self.btnBox.addButton(self.btnClose, QtRoleReject)
# Assemble
self.outerBox = QVBoxLayout()
diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py
index 795c7eed..407eff5e 100644
--- a/novelwriter/tools/manussettings.py
+++ b/novelwriter/tools/manussettings.py
@@ -127,12 +127,12 @@ class GuiBuildSettings(NToolDialog):
# Buttons
self.btnApply = SHARED.theme.getStandardButton(nwStandardButton.APPLY, self)
self.btnSave = SHARED.theme.getStandardButton(nwStandardButton.SAVE, self)
- self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self)
+ self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self)
self.btnBox = QDialogButtonBox(self)
- self.btnBox.addButton(self.btnApply, QDialogButtonBox.ButtonRole.ApplyRole)
- self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.AcceptRole)
- self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole)
+ self.btnBox.addButton(self.btnApply, QtRoleApply)
+ self.btnBox.addButton(self.btnSave, QtRoleAccept)
+ self.btnBox.addButton(self.btnClose, QtRoleReject)
self.btnBox.clicked.connect(self._dialogButtonClicked)
# Assemble
diff --git a/novelwriter/tools/noveldetails.py b/novelwriter/tools/noveldetails.py
index bfec8938..c0314e9b 100644
--- a/novelwriter/tools/noveldetails.py
+++ b/novelwriter/tools/noveldetails.py
@@ -44,7 +44,7 @@ from novelwriter.extensions.modified import NNonBlockingDialog
from novelwriter.extensions.novelselector import NovelSelector
from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import QtAlignRight, QtDecoration
+from novelwriter.types import QtAlignRight, QtDecoration, QtRoleReject
if TYPE_CHECKING:
from PyQt6.QtGui import QCloseEvent
@@ -107,7 +107,7 @@ class GuiNovelDetails(NNonBlockingDialog):
self.btnClose.clicked.connect(self.reject)
self.btnBox = QDialogButtonBox(self)
- self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole)
+ self.btnBox.addButton(self.btnClose, QtRoleReject)
# Assemble
self.topBox = QHBoxLayout()
diff --git a/novelwriter/tools/writingstats.py b/novelwriter/tools/writingstats.py
index b6440e5c..dcf3ce40 100644
--- a/novelwriter/tools/writingstats.py
+++ b/novelwriter/tools/writingstats.py
@@ -43,7 +43,10 @@ from novelwriter.enum import nwStandardButton
from novelwriter.error import formatException
from novelwriter.extensions.modified import NPushButton, NToolDialog
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import QtAlignLeftMiddle, QtAlignRight, QtAlignRightMiddle, QtDecoration
+from novelwriter.types import (
+ QtAlignLeftMiddle, QtAlignRight, QtAlignRightMiddle, QtDecoration,
+ QtRoleAction, QtRoleReject
+)
if TYPE_CHECKING:
from novelwriter.guimain import GuiMain
@@ -294,8 +297,8 @@ class GuiWritingStats(NToolDialog):
self.btnSave.setMenu(self.saveMenu)
self.btnBox = QDialogButtonBox(self)
- self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole)
- self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.ActionRole)
+ self.btnBox.addButton(self.btnSave, QtRoleAction)
+ self.btnBox.addButton(self.btnClose, QtRoleReject)
# Assemble
self.outerBox = QGridLayout()
diff --git a/novelwriter/types.py b/novelwriter/types.py
index f972e04f..52a18351 100644
--- a/novelwriter/types.py
+++ b/novelwriter/types.py
@@ -93,17 +93,11 @@ QtMouseMiddle = Qt.MouseButton.MiddleButton
QtAccepted = QDialog.DialogCode.Accepted
QtRejected = QDialog.DialogCode.Rejected
-QtDialogApply = QDialogButtonBox.StandardButton.Apply
-QtDialogCancel = QDialogButtonBox.StandardButton.Cancel
-QtDialogClose = QDialogButtonBox.StandardButton.Close
-QtDialogOk = QDialogButtonBox.StandardButton.Ok
-QtDialogReset = QDialogButtonBox.StandardButton.Reset
-QtDialogSave = QDialogButtonBox.StandardButton.Save
-
QtRoleAccept = QDialogButtonBox.ButtonRole.AcceptRole
QtRoleAction = QDialogButtonBox.ButtonRole.ActionRole
QtRoleApply = QDialogButtonBox.ButtonRole.ApplyRole
QtRoleReject = QDialogButtonBox.ButtonRole.RejectRole
+QtRoleReset = QDialogButtonBox.ButtonRole.ResetRole
# Cursor Types
From 5f34e924d185b4f670fdb3963d9033d35a4c9cab Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 25 Oct 2025 23:53:18 +0200
Subject: [PATCH 08/10] Fix broken tests
---
tests/test_dialogs/test_dlg_preferences.py | 14 ++-----
tests/test_tools/test_tools_manusbuild.py | 9 +----
tests/test_tools/test_tools_manuscript.py | 9 +----
tests/test_tools/test_tools_manussettings.py | 41 +++++---------------
4 files changed, 18 insertions(+), 55 deletions(-)
diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py
index dd749aa5..f9509528 100644
--- a/tests/test_dialogs/test_dlg_preferences.py
+++ b/tests/test_dialogs/test_dlg_preferences.py
@@ -32,7 +32,7 @@ from novelwriter.constants import nwUnicode
from novelwriter.dialogs.preferences import GuiPreferences
from novelwriter.dialogs.quotes import GuiQuoteSelect
from novelwriter.gui.theme import ThemeEntry
-from novelwriter.types import QtDialogCancel, QtDialogSave, QtModNone
+from novelwriter.types import QtModNone
KEY_DELAY = 1
@@ -118,16 +118,12 @@ def testDlgPreferences_Actions(qtbot, monkeypatch, nwGUI):
# Check Save Button
prefs.show()
with qtbot.waitSignal(prefs.newPreferencesReady) as signal:
- button = prefs.buttonBox.button(QtDialogSave)
- assert button is not None
- button.click()
+ prefs.btnSave.click()
assert len(signal.args) == 4
# Check Close Button
prefs.show()
- button = prefs.buttonBox.button(QtDialogCancel)
- assert button is not None
- button.click()
+ prefs.btnCancel.click()
assert prefs.isHidden() is True
# Close Using Escape Key
@@ -342,9 +338,7 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
with monkeypatch.context() as mp:
mp.setattr(QFontDatabase, "families", lambda *a: ["TestFont"])
with qtbot.waitSignal(prefs.newPreferencesReady) as signal:
- button = prefs.buttonBox.button(QtDialogSave)
- assert button is not None
- button.click()
+ prefs.btnSave.click()
assert signal.args == [True, True, True, True]
# Check Settings
diff --git a/tests/test_tools/test_tools_manusbuild.py b/tests/test_tools/test_tools_manusbuild.py
index 35ddb7fa..39875483 100644
--- a/tests/test_tools/test_tools_manusbuild.py
+++ b/tests/test_tools/test_tools_manusbuild.py
@@ -35,7 +35,6 @@ from novelwriter.enum import nwBuildFmt
from novelwriter.guimain import GuiMain
from novelwriter.shared import _GuiAlert
from novelwriter.tools.manusbuild import GuiManuscriptBuild
-from novelwriter.types import QtDialogClose
from tests.tools import buildTestProject
@@ -95,9 +94,7 @@ def testToolManuscriptBuild_Main(
assert (fncPath / "TestBuild").with_suffix(nwLabels.BUILD_EXT[fmt]).exists()
lastFmt = fmt
- button = manus.buttonBox.button(QtDialogClose)
- assert button is not None
- manus._dialogButtonClicked(button)
+ manus._dialogButtonClicked(manus.btnClose)
manus.deleteLater()
assert build.lastBuildName == "TestBuild"
@@ -151,7 +148,5 @@ def testToolManuscriptBuild_Main(
assert lastUrl.startswith("file://")
# Finish
- button = manus.buttonBox.button(QtDialogClose)
- assert button is not None
- manus._dialogButtonClicked(button)
+ manus._dialogButtonClicked(manus.btnClose)
# qtbot.stop()
diff --git a/tests/test_tools/test_tools_manuscript.py b/tests/test_tools/test_tools_manuscript.py
index 0e498ea1..0d74dbff 100644
--- a/tests/test_tools/test_tools_manuscript.py
+++ b/tests/test_tools/test_tools_manuscript.py
@@ -37,7 +37,6 @@ from novelwriter.core.buildsettings import BuildSettings
from novelwriter.tools.manusbuild import GuiManuscriptBuild
from novelwriter.tools.manuscript import GuiManuscript
from novelwriter.tools.manussettings import GuiBuildSettings
-from novelwriter.types import QtDialogApply, QtDialogSave
from tests.tools import C, buildTestProject
@@ -115,9 +114,7 @@ def testToolManuscript_Builds(qtbot, nwGUI, projPath):
with qtbot.waitSignal(bSettings.newSettingsReady, timeout=5000):
bSettings.newSettingsReady.connect(_testNewSettingsReady)
- button = bSettings.buttonBox.button(QtDialogSave)
- assert button is not None
- button.click()
+ bSettings.btnSave.click()
assert isinstance(build, BuildSettings)
assert build.name == "Test Build"
@@ -136,9 +133,7 @@ def testToolManuscript_Builds(qtbot, nwGUI, projPath):
with qtbot.waitSignal(bSettings.newSettingsReady, timeout=5000):
bSettings.newSettingsReady.connect(_testNewSettingsReady)
- button = bSettings.buttonBox.button(QtDialogApply)
- assert button is not None
- button.click() # Should leave the dialog open
+ bSettings.btnApply.click() # Should leave the dialog open
assert isinstance(build, BuildSettings)
assert build.name == "Test Build"
diff --git a/tests/test_tools/test_tools_manussettings.py b/tests/test_tools/test_tools_manussettings.py
index b5ce7880..853fe6cb 100644
--- a/tests/test_tools/test_tools_manussettings.py
+++ b/tests/test_tools/test_tools_manussettings.py
@@ -33,7 +33,6 @@ from novelwriter.core.buildsettings import BuildSettings, FilterMode
from novelwriter.tools.manussettings import (
GuiBuildSettings, _FilterTab, _FormattingTab, _HeadingsTab
)
-from novelwriter.types import QtDialogApply, QtDialogClose, QtDialogSave
from tests.tools import C, buildTestProject
@@ -78,9 +77,7 @@ def testToolBuildSettings_Init(qtbot, nwGUI, projPath, mockRnd):
# Capture Apply button
with qtbot.waitSignal(bSettings.newSettingsReady, timeout=5000):
bSettings.newSettingsReady.connect(_testNewSettingsReady)
- button = bSettings.buttonBox.button(QtDialogApply)
- assert button is not None
- bSettings._dialogButtonClicked(button)
+ bSettings._dialogButtonClicked(bSettings.btnApply)
assert triggered
@@ -89,9 +86,7 @@ def testToolBuildSettings_Init(qtbot, nwGUI, projPath, mockRnd):
with qtbot.waitSignal(bSettings.newSettingsReady, timeout=5000):
bSettings.newSettingsReady.connect(_testNewSettingsReady)
- button = bSettings.buttonBox.button(QtDialogSave)
- assert button is not None
- bSettings._dialogButtonClicked(button)
+ bSettings._dialogButtonClicked(bSettings.btnSave)
assert triggered
@@ -109,9 +104,7 @@ def testToolBuildSettings_Init(qtbot, nwGUI, projPath, mockRnd):
assert triggered
# Finish
- button = bSettings.buttonBox.button(QtDialogClose)
- assert button is not None
- bSettings._dialogButtonClicked(button)
+ bSettings._dialogButtonClicked(bSettings.btnClose)
# qtbot.stop()
@@ -326,9 +319,7 @@ def testToolBuildSettings_Filter(qtbot, nwGUI, projPath, mockRnd):
]
# Finish
- button = bSettings.buttonBox.button(QtDialogClose)
- assert button is not None
- bSettings._dialogButtonClicked(button)
+ bSettings._dialogButtonClicked(bSettings.btnClose)
# qtbot.stop()
@@ -505,9 +496,7 @@ def testToolBuildSettings_Headings(qtbot, nwGUI):
assert sBuild.getBool("headings.hideSection") is True
# Finish
- button = bSettings.buttonBox.button(QtDialogClose)
- assert button is not None
- bSettings._dialogButtonClicked(button)
+ bSettings._dialogButtonClicked(bSettings.btnClose)
# qtbot.stop()
@@ -579,9 +568,7 @@ def testToolBuildSettings_FormatTextContent(qtbot, nwGUI):
assert sBuild.getBool("text.addNoteHeadings") is True
# Finish
- button = bSettings.buttonBox.button(QtDialogClose)
- assert button is not None
- bSettings._dialogButtonClicked(button)
+ bSettings._dialogButtonClicked(bSettings.btnClose)
# qtbot.stop()
@@ -657,9 +644,7 @@ def testToolBuildSettings_FormatTextFormat(monkeypatch, qtbot, nwGUI):
assert fmtTab._textFont == font
# Finish
- button = bSettings.buttonBox.button(QtDialogClose)
- assert button is not None
- bSettings._dialogButtonClicked(button)
+ bSettings._dialogButtonClicked(bSettings.btnClose)
# qtbot.stop()
@@ -703,9 +688,7 @@ def testToolBuildSettings_FormatFirstLineIndent(monkeypatch, qtbot, nwGUI):
assert sBuild.getBool("format.indentFirstPar") is True
# Finish
- button = bSettings.buttonBox.button(QtDialogClose)
- assert button is not None
- bSettings._dialogButtonClicked(button)
+ bSettings._dialogButtonClicked(bSettings.btnClose)
# qtbot.stop()
@@ -761,9 +744,7 @@ def testToolBuildSettings_FormatPageLayout(monkeypatch, qtbot, nwGUI):
assert fmtTab.rightMargin.value() == 1.5
# Finish
- button = bSettings.buttonBox.button(QtDialogClose)
- assert button is not None
- bSettings._dialogButtonClicked(button)
+ bSettings._dialogButtonClicked(bSettings.btnClose)
# qtbot.stop()
@@ -832,7 +813,5 @@ def testToolBuildSettings_FormatOutput(qtbot, nwGUI):
assert fmtTab.odtPageHeader.text() == nwHeadFmt.DOC_AUTO
# Finish
- button = bSettings.buttonBox.button(QtDialogClose)
- assert button is not None
- bSettings._dialogButtonClicked(button)
+ bSettings._dialogButtonClicked(bSettings.btnClose)
# qtbot.stop()
From af36a3b1bf51a3bd809ae3c72dad433523397802 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 25 Oct 2025 23:55:29 +0200
Subject: [PATCH 09/10] Remove extra qtbase translations for buttons
---
i18n/README.md | 17 -----------------
i18n/qtbase.py | 49 -------------------------------------------------
utils/assets.py | 1 -
3 files changed, 67 deletions(-)
delete mode 100644 i18n/qtbase.py
diff --git a/i18n/README.md b/i18n/README.md
index b2df46f0..6b8ce27f 100644
--- a/i18n/README.md
+++ b/i18n/README.md
@@ -114,23 +114,6 @@ You can now test the translation in novelWriter. The Preferences dialog should l
language, so go ahead and select it.
-### Missing QtBase Translations
-
-The default Qt dialogs also have translations, for instance for standard buttons like "Yes", "No",
-"Ok", "Cancel", etc. Generally, these translation files are installed with the Qt libraries on your
-system, and novelWriter will collect those translations from there. However, these translations are
-missing for many languages.
-
-As a starting point, there is no need to translate any entries in the `.ts` files that are under
-elements starting with the letter "Q", like "QPlatformTheme", "QWizard", etc. If these turn up in
-English in novelWriter after activating a translation, it means they are probably missing in the Qt
-library, and you may also need to translate these.
-
-These additional translation entries are generated from a file named `i18n/qtbase.py`, which is not
-a file that novelWriter uses. It is there only to generate these additional entries for the `.ts`
-files.
-
-
## Project Localisation
Projects can have a different language setting than the GUI itself. The files with format
diff --git a/i18n/qtbase.py b/i18n/qtbase.py
deleted file mode 100644
index e40279cd..00000000
--- a/i18n/qtbase.py
+++ /dev/null
@@ -1,49 +0,0 @@
-"""
-Qt Base Translation File
-========================
-
-This file causes Qt Linguist to generate translation entries for the Qt
-elements that need translation in novelWriter for those languages who do
-not yet have a qtbase_xx.qm file shipped with Qt.
-
-If a qtbase_xx.qm file already exists, do not add a translation for the
-entries generated from this file.
-""" # noqa
-
-from PyQt6.QtCore import QT_TRANSLATE_NOOP
-
-# QDialogButtonBox
-# ================
-
-QT_TRANSLATE_NOOP("QDialogButtonBox", "OK")
-
-# QGnomeTheme
-# ===========
-
-QT_TRANSLATE_NOOP("QGnomeTheme", "&OK")
-QT_TRANSLATE_NOOP("QGnomeTheme", "&Save")
-QT_TRANSLATE_NOOP("QGnomeTheme", "&Cancel")
-QT_TRANSLATE_NOOP("QGnomeTheme", "&Close")
-QT_TRANSLATE_NOOP("QGnomeTheme", "Close without Saving")
-
-# QPlatformTheme
-# ==============
-
-QT_TRANSLATE_NOOP("QPlatformTheme", "OK")
-QT_TRANSLATE_NOOP("QPlatformTheme", "Save")
-QT_TRANSLATE_NOOP("QPlatformTheme", "Save All")
-QT_TRANSLATE_NOOP("QPlatformTheme", "Open")
-QT_TRANSLATE_NOOP("QPlatformTheme", "&Yes")
-QT_TRANSLATE_NOOP("QPlatformTheme", "Yes to &All")
-QT_TRANSLATE_NOOP("QPlatformTheme", "&No")
-QT_TRANSLATE_NOOP("QPlatformTheme", "N&o to All")
-QT_TRANSLATE_NOOP("QPlatformTheme", "Abort")
-QT_TRANSLATE_NOOP("QPlatformTheme", "Retry")
-QT_TRANSLATE_NOOP("QPlatformTheme", "Ignore")
-QT_TRANSLATE_NOOP("QPlatformTheme", "Close")
-QT_TRANSLATE_NOOP("QPlatformTheme", "Cancel")
-QT_TRANSLATE_NOOP("QPlatformTheme", "Discard")
-QT_TRANSLATE_NOOP("QPlatformTheme", "Help")
-QT_TRANSLATE_NOOP("QPlatformTheme", "Apply")
-QT_TRANSLATE_NOOP("QPlatformTheme", "Reset")
-QT_TRANSLATE_NOOP("QPlatformTheme", "Restore Defaults")
diff --git a/utils/assets.py b/utils/assets.py
index 7f0222cd..5822cc96 100644
--- a/utils/assets.py
+++ b/utils/assets.py
@@ -111,7 +111,6 @@ def updateTranslationSources(args: argparse.Namespace) -> None:
print("")
sources = list((ROOT_DIR / "novelwriter").glob("**/*.py"))
- sources.insert(0, ROOT_DIR / "i18n" / "qtbase.py")
for source in sources:
print(source.relative_to(ROOT_DIR))
From a02254f5d5cdd8e67bae1167df13282074652198 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sun, 26 Oct 2025 00:12:37 +0200
Subject: [PATCH 10/10] Make minor improvements to the code and fix test on
MacOS
---
novelwriter/extensions/modified.py | 7 +++----
tests/test_base/test_base_shared.py | 14 ++++++++++----
2 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py
index b802a4d9..ee1be5e7 100644
--- a/novelwriter/extensions/modified.py
+++ b/novelwriter/extensions/modified.py
@@ -210,12 +210,11 @@ class NPushButton(QPushButton):
icon: str | None = None, color: str | None = None
) -> None:
super().__init__(parent=parent)
- self.setText(text)
- self.setIconSize(iconSize)
self._icon = icon
self._color = color
- if icon:
- self.refreshIcon()
+ self.setText(text)
+ self.setIconSize(iconSize)
+ self.refreshIcon()
def refreshIcon(self) -> None:
"""Reload the theme icon."""
diff --git a/tests/test_base/test_base_shared.py b/tests/test_base/test_base_shared.py
index a020a694..0a37b624 100644
--- a/tests/test_base/test_base_shared.py
+++ b/tests/test_base/test_base_shared.py
@@ -20,6 +20,8 @@ along with this program. If not, see .
""" # noqa
from __future__ import annotations
+import sys
+
from unittest.mock import MagicMock
import pytest
@@ -218,7 +220,8 @@ def testBaseSharedData_GuiAlert():
# Alert: Info
alert.setAlertType(_GuiAlert.INFO, False)
assert hasattr(alert, "_btnOk")
- assert alert.windowTitle() == "Information"
+ if sys.platform != "darwin": # Not set on MacOS
+ assert alert.windowTitle() == "Information"
alert._btnOk.click()
assert alert.finalState is True
alert._state = False
@@ -226,7 +229,8 @@ def testBaseSharedData_GuiAlert():
# Alert: Warning
alert.setAlertType(_GuiAlert.WARN, False)
assert hasattr(alert, "_btnOk")
- assert alert.windowTitle() == "Warning"
+ if sys.platform != "darwin": # Not set on MacOS
+ assert alert.windowTitle() == "Warning"
alert._btnOk.click()
assert alert.finalState is True
alert._state = False
@@ -234,7 +238,8 @@ def testBaseSharedData_GuiAlert():
# Alert: Error
alert.setAlertType(_GuiAlert.ERROR, False)
assert hasattr(alert, "_btnOk")
- assert alert.windowTitle() == "Error"
+ if sys.platform != "darwin": # Not set on MacOS
+ assert alert.windowTitle() == "Error"
alert._btnOk.click()
assert alert.finalState is True
alert._state = False
@@ -243,7 +248,8 @@ def testBaseSharedData_GuiAlert():
alert.setAlertType(_GuiAlert.ASK, True)
assert hasattr(alert, "_btnYes")
assert hasattr(alert, "_btnNo")
- assert alert.windowTitle() == "Question"
+ if sys.platform != "darwin": # Not set on MacOS
+ assert alert.windowTitle() == "Question"
alert._btnYes.click()
assert alert.finalState is True
alert._btnNo.click()