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
|
||||
|
||||
# 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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
@@ -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 == ""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user