Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions mne/gui/tests/test_gui_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ def test_gui_api_notebook(renderer_notebook, nbexec, *, backend="qt"):
renderer._layout_add_widget(central_layout, widget, row=0, col=0)
renderer._window_initialize(window=window, central_layout=central_layout)

# an OS light/dark mode switch re-applies the theme (gh-9182)
from qtpy.QtCore import QEvent
from qtpy.QtGui import QIcon

QIcon.setThemeName("bogus")
window.event(QEvent(QEvent.PaletteChange))
assert QIcon.themeName() in ("dark", "light")

from unittest.mock import Mock

mock = Mock()
Expand Down
68 changes: 40 additions & 28 deletions mne/viz/backends/_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,17 +224,7 @@ def _set_focus(self):
self.setFocus()

def _set_theme(self, theme=None):
if theme is None:
default_theme = _qt_detect_theme()
else:
default_theme = theme
theme = get_config("MNE_3D_OPTION_THEME", default_theme)
stylesheet = _qt_get_stylesheet(theme)
self.setStyleSheet(stylesheet)
if _qt_is_dark(self):
QIcon.setThemeName("dark")
else:
QIcon.setThemeName("light")
_qt_set_theme(self, theme)

def _set_size(self, width=None, height=None):
if width:
Expand Down Expand Up @@ -682,22 +672,51 @@ def _set_size(self, width=None, height=None):
# -------


# In theory we should be able to set the theme later (e.g., in
# _window_initialize() below), but at least on Qt6 this has to be done
# earlier. So let's do it immediately upon instantiation of the QMainWindow
# class (see _AppWindow.__init__'s self._set_theme() call below).
# TODO: This should eventually allow us to handle
# https://github.com/mne-tools/mne-python/issues/9182
def _qt_set_theme(window, theme=None):
"""(Re)apply a theme to a window, remembering any explicitly requested one."""
if theme is not None:
window._mne_theme = theme
theme = getattr(window, "_mne_theme", None)
if theme is None:
theme = _qt_detect_theme()
theme = get_config("MNE_3D_OPTION_THEME", theme)
stylesheet = _qt_get_stylesheet(theme)
Comment thread
larsoner marked this conversation as resolved.
# our own setStyleSheet emits PaletteChange; without this the signal recurses
window._mne_theme_updating = True
try:
# re-setting an unchanged sheet costs styled children (sliders) native rendering

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this comment (specifically the verb "costs")

if stylesheet != window.styleSheet():
window.setStyleSheet(stylesheet)
QIcon.setThemeName("dark" if _qt_is_dark(window) else "light")
# not a no-op: setStyleSheet re-parses, re-resolving palette(...) refs that a
# palette change alone leaves stale
for widget in window.findChildren(QWidget):
if child_stylesheet := widget.styleSheet():
widget.setStyleSheet(child_stylesheet)
finally:
window._mne_theme_updating = False


class _MNEMainWindow(MainWindow):
signal_theme_change = Signal()

def __init__(self, parent=None, title=None, size=None):
MainWindow.__init__(self, parent=parent, title=title, size=size)
self.setAttribute(Qt.WA_ShowWithoutActivating, True)
self.setAttribute(Qt.WA_DeleteOnClose, True)
self._mne_theme = None
self._mne_theme_updating = False
from . import renderer

if renderer.MNE_3D_BACKEND_TESTING:
self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnBottomHint)

def event(self, ev):
"""Turn OS light/dark mode switches into a signal (macOS only for now)."""
if ev.type() == QEvent.PaletteChange and not self._mne_theme_updating:
self.signal_theme_change.emit()
return super().event(ev)


class _AppWindow(_AbstractAppWindow, _Widget, _MNEMainWindow, metaclass=_BaseWidget):
def __init__(self, size=None, fullscreen=False):
Expand All @@ -710,6 +729,7 @@ def __init__(self, size=None, fullscreen=False):
self.setWindowState(Qt.WindowFullScreen)

self._set_theme()
self.signal_theme_change.connect(self._set_theme)
self.setLocale(QLocale(QLocale.Language.English))
self.signal_close.connect(self._clean)

Expand Down Expand Up @@ -1514,6 +1534,8 @@ def _window_initialize(self, *, window=None, central_layout=None, fullscreen=Fal
central_widget.setLayout(central_layout)
self._window_load_icons()
self._window_set_theme()
if hasattr(self._window, "signal_theme_change"): # not for a foreign window
self._window.signal_theme_change.connect(self._window_set_theme)
self._window.setLocale(QLocale(QLocale.Language.English))
self._window.signal_close.connect(self._window_clean)
self._window_before_close_callbacks = list()
Expand Down Expand Up @@ -1675,17 +1697,7 @@ def _window_ensure_minimum_sizes(self):
_qt_activate_layouts(self._window, self._interactor)

def _window_set_theme(self, theme=None):
if theme is None:
default_theme = _qt_detect_theme()
else:
default_theme = theme
theme = get_config("MNE_3D_OPTION_THEME", default_theme)
stylesheet = _qt_get_stylesheet(theme)
self._window.setStyleSheet(stylesheet)
if _qt_is_dark(self._window):
QIcon.setThemeName("dark")
else:
QIcon.setThemeName("light")
_qt_set_theme(self._window, theme)

def _window_create(self):
return _MNEMainWindow()
Expand Down
Loading