Fix custom theme handling for Qt6

This commit is contained in:
Veronica Berglyd Olsen
2025-01-11 23:55:08 +01:00
parent ff7446a2ef
commit 27a0a47aaa
7 changed files with 50 additions and 17 deletions
+3
View File
@@ -254,11 +254,13 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
pass # Quietly ignore error
# Import GUI (after dependency checks), and launch
from novelwriter.gui.theme import GuiTheme
from novelwriter.guimain import GuiMain
if testMode:
# Only used for testing where the test framework creates the app
CONFIG.loadConfig()
SHARED.initTheme(GuiTheme())
return GuiMain()
app = QApplication([CONFIG.appName, (f"-style={qtStyle}")])
@@ -274,6 +276,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
# Run Config steps that require the QApplication
CONFIG.loadConfig()
CONFIG.initLocalisation(app)
SHARED.initTheme(GuiTheme())
# Launch main GUI
nwGUI = GuiMain()
+3 -2
View File
@@ -98,14 +98,14 @@ class NSwitch(QAbstractButton):
trackBrush = palette.highlight()
thumbBrush = palette.highlightedText()
else:
trackBrush = palette.dark()
trackBrush = palette.mid()
thumbBrush = palette.light()
if self.isEnabled():
trackOpacity = 1.0
else:
trackOpacity = 0.6
trackBrush = palette.shadow()
trackBrush = palette.dark()
thumbBrush = palette.mid()
painter.setBrush(trackBrush)
@@ -114,6 +114,7 @@ class NSwitch(QAbstractButton):
painter.setBrush(thumbBrush)
painter.drawEllipse(self._offset - self._rR, self._rB, self._rH, self._rH)
painter.end()
return
+14
View File
@@ -304,6 +304,20 @@ class GuiTheme:
backCol = self._guiPalette.window().color()
textCol = self._guiPalette.windowText().color()
# Calculate Based on Qt Fusion
light = backCol.lighter(150)
mid = backCol.darker(130)
midLight = mid.lighter(110)
dark = backCol.darker(150)
shadow = dark.darker(135)
self._guiPalette.setColor(QPalette.ColorRole.Light, light)
self._guiPalette.setColor(QPalette.ColorRole.Mid, mid)
self._guiPalette.setColor(QPalette.ColorRole.Midlight, midLight)
self._guiPalette.setColor(QPalette.ColorRole.Dark, dark)
self._guiPalette.setColor(QPalette.ColorRole.Shadow, shadow)
# Calculate Help Text
backLNess = backCol.lightnessF()
textLNess = textCol.lightnessF()
self.isLightTheme = backLNess > textLNess
+2 -2
View File
@@ -56,7 +56,6 @@ from novelwriter.gui.projtree import GuiProjectView
from novelwriter.gui.search import GuiProjectSearch
from novelwriter.gui.sidebar import GuiSideBar
from novelwriter.gui.statusbar import GuiMainStatus
from novelwriter.gui.theme import GuiTheme
from novelwriter.tools.dictionaries import GuiDictionaries
from novelwriter.tools.manuscript import GuiManuscript
from novelwriter.tools.noveldetails import GuiNovelDetails
@@ -101,7 +100,7 @@ class GuiMain(QMainWindow):
# ============
# Initialise UserData Instance
SHARED.initSharedData(self, GuiTheme())
SHARED.initSharedData(self)
# Prepare Main Window
self.resize(*CONFIG.mainWinSize)
@@ -1054,6 +1053,7 @@ class GuiMain(QMainWindow):
# We are doing this manually instead of connecting to
# paletteChanged since the processing order matters
SHARED.theme.loadTheme()
self.setPalette(QApplication.palette())
self.docEditor.updateTheme()
self.docViewer.updateTheme()
self.docViewerPanel.updateTheme()
+15 -7
View File
@@ -164,17 +164,24 @@ class SharedData(QObject):
# Methods
##
def initSharedData(self, gui: GuiMain, theme: GuiTheme) -> None:
def initTheme(self, theme: GuiTheme) -> None:
"""Initialise the GUI theme. This must be called before the GUI
is created.
"""
self._theme = theme
return
def initSharedData(self, gui: GuiMain) -> None:
"""Initialise the SharedData instance. This must be called as
soon as the Main GUI is created to ensure the SHARED singleton
has the properties needed for operation.
"""
self._clock.start()
self._gui = gui
self._theme = theme
self._resetProject()
logger.debug("Ready: SharedData")
logger.debug("Thread Pool Max Count: %d", QThreadPool.globalInstance().maxThreadCount())
if pool := QThreadPool.globalInstance():
logger.debug("Thread Pool Max Count: %d", pool.maxThreadCount())
return
def closeDocument(self, tHandle: str | None = None) -> None:
@@ -266,7 +273,8 @@ class SharedData(QObject):
def runInThreadPool(self, runnable: QRunnable, priority: int = 0) -> None:
"""Queue a runnable in the application thread pool."""
QThreadPool.globalInstance().start(runnable, priority=priority)
if pool := QThreadPool.globalInstance():
pool.start(runnable, priority=priority)
return
def getProjectPath(
@@ -278,13 +286,13 @@ class SharedData(QObject):
label = (self.tr("novelWriter Project File or Zip File")
if allowZip else self.tr("novelWriter Project File"))
ext = f"{nwFiles.PROJ_FILE} *.zip" if allowZip else nwFiles.PROJ_FILE
ffilter = formatFileFilter([(label, ext), "*"])
fFilter = formatFileFilter([(label, ext), "*"])
selected, _ = QFileDialog.getOpenFileName(
parent, self.tr("Open Project"), str(path or ""), filter=ffilter
parent, self.tr("Open Project"), str(path or ""), filter=fFilter
)
return Path(selected) if selected else None
def getFont(self, current: QFont, native: bool) -> tuple[QFont, bool]:
def getFont(self, current: QFont, native: bool) -> tuple[QFont, bool | None]:
"""Open the font dialog and select a font."""
kwargs = {}
if not native:
+7 -3
View File
@@ -33,7 +33,7 @@ from PyQt6.QtWidgets import QMessageBox
sys.path.insert(1, str(Path(__file__).parent.parent.absolute()))
from novelwriter import CONFIG, SHARED, main # noqa: E402
from novelwriter import CONFIG, SHARED # noqa: E402
from tests.mocked import MockGuiMain, MockTheme # noqa: E402
from tests.tools import cleanProject # noqa: E402
@@ -160,11 +160,15 @@ def mockGUI(qtbot, monkeypatch):
@pytest.fixture(scope="function")
def nwGUI(qtbot, monkeypatch, functionFixture):
"""Create an instance of the novelWriter GUI."""
from novelwriter.gui.theme import GuiTheme
from novelwriter.guimain import GuiMain
monkeypatch.setattr(QMessageBox, "exec", lambda *a: None)
monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes)
nwGUI = main(["--testmode", f"--config={_TMP_CONF}", f"--data={_TMP_CONF}"])
assert nwGUI is not None
CONFIG.loadConfig()
SHARED.initTheme(GuiTheme())
nwGUI = GuiMain()
qtbot.addWidget(nwGUI)
resetConfigVars()
nwGUI.docEditor.initEditor()
+6 -3
View File
@@ -56,7 +56,8 @@ def testBaseSharedData_Init():
assert mockGui is not mockTheme
# Properly initialise the class
shared.initSharedData(mockGui, mockTheme) # type: ignore
shared.initTheme(mockTheme) # type: ignore
shared.initSharedData(mockGui) # type: ignore
assert shared.mainGui is mockGui
assert shared.theme is mockTheme
@@ -94,7 +95,8 @@ def testBaseSharedData_Projects(monkeypatch, caplog, fncPath):
# Initialise the instance, should create an empty project
mockGui = MockGuiMain()
mockTheme = MockTheme()
shared.initSharedData(mockGui, mockTheme) # type: ignore
shared.initTheme(mockTheme) # type: ignore
shared.initSharedData(mockGui) # type: ignore
assert isinstance(shared.project, NWProject)
assert shared.hasProject is False
@@ -150,7 +152,8 @@ def testBaseSharedData_Alerts(qtbot, monkeypatch, caplog):
mockGui = MockGuiMain()
mockTheme = MockTheme()
shared.initSharedData(mockGui, mockTheme) # type: ignore
shared.initTheme(mockTheme) # type: ignore
shared.initSharedData(mockGui) # type: ignore
assert shared.lastAlert == ""