Skip to content
11 changes: 5 additions & 6 deletions mne/viz/_brain/_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ def __init__(self, subject_id, hemi, surf, title=None,
if len(size) not in (1, 2):
raise ValueError('"size" parameter must be an int or length-2 '
'sequence of ints.')
self._size = size if len(size) == 2 else size * 2 # 1-tuple to 2-tuple
size = size if len(size) == 2 else size * 2 # 1-tuple to 2-tuple
subjects_dir = get_subjects_dir(subjects_dir)

self.theme = theme
Expand Down Expand Up @@ -474,12 +474,11 @@ def __init__(self, subject_id, hemi, surf, title=None,
offset = (surf == 'inflated')
offset = None if (not offset or hemi != 'both') else 0.0

self._renderer = _get_renderer(name=self._title, size=self._size,
self._renderer = _get_renderer(name=self._title, size=size,
bgcolor=background,
shape=shape,
fig=figure)

self._renderer._window_initialize(self._clean)
self._renderer._window_close_connect(self._clean)
self._renderer._window_set_theme(theme)
self.plotter = self._renderer.plotter

Expand Down Expand Up @@ -663,7 +662,7 @@ def setup_time_viewer(self, time_viewer=True, show_traces=True):
self._configure_help()
# show everything at the end
self.toggle_interface()
self._renderer._window_show(self._size)
self._renderer.show()

# sizes could change, update views
for hemi in ('lh', 'rh'):
Expand Down Expand Up @@ -726,7 +725,7 @@ def toggle_interface(self, value=None):
self.visibility = value

# update tool bar and dock
with self._renderer._window_ensure_minimum_sizes(self._size):
with self._renderer._window_ensure_minimum_sizes():
if self.visibility:
self._renderer._dock_show()
self._renderer._tool_bar_update_button_icon(
Expand Down
2 changes: 1 addition & 1 deletion mne/viz/_brain/tests/test_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ def test_brain_save_movie(tmpdir, renderer, brain_gc):
brain.close()


_TINY_SIZE = (300, 250)
_TINY_SIZE = (350, 300)


def tiny(tmpdir):
Expand Down
20 changes: 14 additions & 6 deletions mne/viz/backends/_abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,10 @@ def _tool_bar_add_spacer(self):
def _tool_bar_add_screenshot_button(self, name, desc, func):
pass

@abstractmethod
def _tool_bar_set_theme(self, theme):
pass


class _AbstractDock(ABC):
@abstractmethod
Expand Down Expand Up @@ -716,8 +720,16 @@ def clear(self):


class _AbstractWindow(ABC):
def _window_initialize(self):
self._window = None
self._interactor = None
self._mplcanvas = None
self._show_traces = None
self._separate_canvas = None
self._interactor_fraction = None

@abstractmethod
def _window_initialize(self, func=None):
def _window_close_connect(self, func):
pass

@abstractmethod
Expand Down Expand Up @@ -757,13 +769,9 @@ def _window_set_cursor(self, cursor):
pass

@abstractmethod
def _window_ensure_minimum_sizes(self, sz):
def _window_ensure_minimum_sizes(self):
pass

@abstractmethod
def _window_set_theme(self, theme):
pass

@abstractmethod
def _window_show(self, sz):
pass
18 changes: 6 additions & 12 deletions mne/viz/backends/_notebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ def _screenshot():
placeholder="Type a file name",
)

def _tool_bar_set_theme(self, theme):
pass


class _IpyMenuBar(_AbstractMenuBar):
def _menu_initialize(self, window=None):
Expand Down Expand Up @@ -244,14 +247,8 @@ def __init__(self, brain, width, height, dpi):


class _IpyWindow(_AbstractWindow):
def _window_initialize(self, func=None):
self._window = None
self._interactor = None
self._mplcanvas = None
self._show_traces = None
self._separate_canvas = None
self._splitter = None
self._interactor_fraction = None
def _window_close_connect(self, func):
pass

def _window_get_dpi(self):
return 96
Expand Down Expand Up @@ -282,15 +279,12 @@ def _window_set_cursor(self, cursor):
pass

@contextmanager
def _window_ensure_minimum_sizes(self, sz):
def _window_ensure_minimum_sizes(self):
yield

def _window_set_theme(self, theme):
pass

def _window_show(self, sz):
self.show()


class _IpyWidget(_AbstractWidget):
def set_value(self, value):
Expand Down
38 changes: 1 addition & 37 deletions mne/viz/backends/_pyvista.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@

from ._abstract import _AbstractRenderer
from ._utils import (_get_colormap_from_array, _alpha_blend_background,
ALLOWED_QUIVER_MODES, _init_qt_resources,
_qt_disable_paint)
ALLOWED_QUIVER_MODES, _init_qt_resources)
from ...fixes import _get_args
from ...transforms import apply_trans
from ...utils import copy_base_doc_to_subclass_doc, _check_option
Expand Down Expand Up @@ -211,35 +210,6 @@ def _get_screenshot_filename(self):
dt_string = now.strftime("_%Y-%m-%d_%H-%M-%S")
return "MNE" + dt_string + ".png"

@contextmanager
def _ensure_minimum_sizes(self):
sz = self.figure.store['window_size']
# plotter: pyvista.plotting.qt_plotting.BackgroundPlotter
# plotter.interactor: vtk.qt.QVTKRenderWindowInteractor.QVTKRenderWindowInteractor -> QWidget # noqa
# plotter.app_window: pyvista.plotting.qt_plotting.MainWindow -> QMainWindow # noqa
# plotter.frame: QFrame with QVBoxLayout with plotter.interactor as centralWidget # noqa
# plotter.ren_win: vtkXOpenGLRenderWindow
self.plotter.interactor.setMinimumSize(*sz)
try:
yield # show
finally:
# 1. Process events
_process_events(self.plotter)
_process_events(self.plotter)
# 2. Get the window and interactor sizes that work
win_sz = self.plotter.app_window.size()
ren_sz = self.plotter.interactor.size()
# 3. Undo the min size setting and process events
self.plotter.interactor.setMinimumSize(0, 0)
_process_events(self.plotter)
_process_events(self.plotter)
# 4. Resize the window and interactor to the correct size
# (not sure why, but this is required on macOS at least)
self.plotter.window_size = (win_sz.width(), win_sz.height())
self.plotter.interactor.resize(ren_sz.width(), ren_sz.height())
_process_events(self.plotter)
_process_events(self.plotter)

def _index_to_loc(self, idx):
_ncols = self.figure._ncols
row = idx // _ncols
Expand Down Expand Up @@ -625,12 +595,6 @@ def scalarbar(self, source, color="white", title=None, n_labels=4,

def show(self):
self.plotter.show()
if hasattr(self.plotter, "app_window"):
with _qt_disable_paint(self.plotter):
with self._ensure_minimum_sizes():
self.plotter.app_window.show()
self.plotter.update()
return self.scene()

def close(self):
_close_3d_figure(figure=self.figure)
Expand Down
129 changes: 69 additions & 60 deletions mne/viz/backends/_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
QHBoxLayout, QLabel, QToolButton, QMenuBar,
QSlider, QSpinBox, QVBoxLayout, QWidget,
QSizePolicy, QScrollArea, QStyle, QProgressBar,
QStyleOptionSlider, QLayout, QSplitter)
QStyleOptionSlider, QLayout)

from ._pyvista import _PyVistaRenderer
from ._pyvista import (_close_all, _close_3d_figure, _check_3d_figure, # noqa: F401,E501 analysis:ignore
Expand All @@ -42,18 +42,10 @@ def _layout_add_widget(self, layout, widget, max_width=None):

class _QtDock(_AbstractDock, _QtLayout):
def _dock_initialize(self, window=None):
self.dock = QDockWidget()
self.scroll = QScrollArea(self.dock)
self.dock.setWidget(self.scroll)
widget = QWidget(self.scroll)
self.scroll.setWidget(widget)
self.scroll.setWidgetResizable(True)
self.dock.setAllowedAreas(Qt.LeftDockWidgetArea)
self.dock.setFeatures(QDockWidget.NoDockWidgetFeatures)
window = self._window if window is None else window
window.addDockWidget(Qt.LeftDockWidgetArea, self.dock)
self.dock_layout = QVBoxLayout()
widget.setLayout(self.dock_layout)
self.dock, self.dock_layout = _create_dock_widget(
self._window, "Controls", Qt.LeftDockWidgetArea)
window.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea)

def _dock_finalize(self):
self.dock.setMinimumSize(self.dock.sizeHint().width(), 0)
Expand Down Expand Up @@ -364,17 +356,14 @@ def __init__(self, brain, width, height, dpi):


class _QtWindow(_AbstractWindow):
def _window_initialize(self, func=None):
self._window = self.figure.plotter.app_window
def _window_initialize(self):
super()._window_initialize()
self._interactor = self.figure.plotter.interactor
self._mplcanvas = None
self._show_traces = None
self._separate_canvas = None
self._splitter = None
self._interactor_fraction = None
self._window = self.figure.plotter.app_window
self._window.setLocale(QLocale(QLocale.Language.English))
if func is not None:
self._window.signal_close.connect(func)

def _window_close_connect(self, func):
self._window.signal_close.connect(func)

def _window_get_dpi(self):
return self._window.windowHandle().screen().logicalDotsPerInch()
Expand All @@ -399,16 +388,9 @@ def _window_get_mplcanvas(self, brain, interactor_fraction, show_traces,

def _window_adjust_mplcanvas_layout(self):
canvas = self._mplcanvas.canvas
vlayout = self._interactor.frame.layout()
vlayout.removeWidget(self._interactor)
splitter = QSplitter(
orientation=Qt.Vertical,
parent=self._interactor.frame
)
vlayout.addWidget(splitter)
splitter.addWidget(self._interactor)
splitter.addWidget(canvas)
self._splitter = splitter
dock, dock_layout = _create_dock_widget(
self._window, "Traces", Qt.BottomDockWidgetArea)
dock_layout.addWidget(canvas)

def _window_get_cursor(self):
return self._interactor.cursor()
Expand All @@ -417,33 +399,38 @@ def _window_set_cursor(self, cursor):
self._interactor.setCursor(cursor)

@contextmanager
def _window_ensure_minimum_sizes(self, sz):
"""Ensure that widgets respect the windows size."""
def _window_ensure_minimum_sizes(self):
sz = self.figure.store['window_size']
adjust_mpl = (self._show_traces and not self._separate_canvas)
if not adjust_mpl:
yield
else:
# plotter: pyvista.plotting.qt_plotting.BackgroundPlotter
# plotter.interactor: vtk.qt.QVTKRenderWindowInteractor.QVTKRenderWindowInteractor -> QWidget # noqa
# plotter.app_window: pyvista.plotting.qt_plotting.MainWindow -> QMainWindow # noqa
# plotter.frame: QFrame with QVBoxLayout with plotter.interactor as centralWidget # noqa
# plotter.ren_win: vtkXOpenGLRenderWindow
self._interactor.setMinimumSize(*sz)
if adjust_mpl:
mpl_h = int(round((sz[1] * self._interactor_fraction) /
(1 - self._interactor_fraction)))
self._mplcanvas.canvas.setMinimumSize(sz[0], mpl_h)
try:
yield
finally:
self._splitter.setSizes([sz[1], mpl_h])
# 1. Process events
self._process_events()
self._process_events()
# 2. Get the window size that accommodates the size
sz = self._window.size()
# 3. Call app_window.setBaseSize and resize (in pyvistaqt)
self.figure.plotter.window_size = (sz.width(), sz.height())
# 4. Undo the min size setting and process events
self._interactor.setMinimumSize(0, 0)
self._process_events()
self._process_events()
# 5. Resize the window (again!) to the correct size
# (not sure why, but this is required on macOS at least)
self.figure.plotter.window_size = (sz.width(), sz.height())
try:
yield # show
finally:
# 1. Process events
self._process_events()
self._process_events()
# 2. Get the window and interactor sizes that work
win_sz = self._window.size()
ren_sz = self._interactor.size()
# 3. Undo the min size setting and process events
self._interactor.setMinimumSize(0, 0)
if adjust_mpl:
self._mplcanvas.canvas.setMinimumSize(0, 0)
self._process_events()
self._process_events()
# 4. Resize the window and interactor to the correct size
# (not sure why, but this is required on macOS at least)
self._interactor.window_size = (win_sz.width(), win_sz.height())
self._interactor.resize(ren_sz.width(), ren_sz.height())
self._process_events()
self._process_events()

Expand All @@ -468,11 +455,6 @@ def _window_set_theme(self, theme):

self._window.setStyleSheet(stylesheet)

def _window_show(self, sz):
with _qt_disable_paint(self._interactor):
with self._window_ensure_minimum_sizes(sz):
self.show()


class _QtWidget(_AbstractWidget):
def set_value(self, value):
Expand All @@ -495,7 +477,34 @@ def get_value(self):

class _Renderer(_PyVistaRenderer, _QtDock, _QtToolBar, _QtMenuBar,
_QtStatusBar, _QtWindow, _QtPlayback):
pass
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._window_initialize()

def show(self):
super().show()
with _qt_disable_paint(self.plotter):
with self._window_ensure_minimum_sizes():
self.plotter.app_window.show()
self.plotter.update()


def _create_dock_widget(window, name, area):
dock = QDockWidget()
scroll = QScrollArea(dock)
dock.setWidget(scroll)
widget = QWidget(scroll)
scroll.setWidget(widget)
scroll.setWidgetResizable(True)
dock.setAllowedAreas(area)
dock.setTitleBarWidget(QLabel(name))
window.addDockWidget(area, dock)
dock_layout = QVBoxLayout()
widget.setLayout(dock_layout)
# Fix resize grip size
# https://stackoverflow.com/a/65050468/2175965
dock.setStyleSheet("QDockWidget { margin: 4px; }")
return dock, dock_layout


def _detect_theme():
Expand Down