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