diff --git a/mne/gui/tests/test_gui_api.py b/mne/gui/tests/test_gui_api.py index 03d3af6baab..7b39a50982a 100644 --- a/mne/gui/tests/test_gui_api.py +++ b/mne/gui/tests/test_gui_api.py @@ -299,6 +299,40 @@ def _check_widget_trigger(widget, mock, before, after, call_count=True, assert widget.get_tooltip() == 'bar' # --- END: tooltips --- + # --- BEGIN: dialog --- + # dialogs are not supported yet on notebook + if renderer._kind == 'qt': + # warning + buttons = ["Save", "Cancel"] + widget = renderer._dialog_warning( + title='', + text='', + info_text='', + callback=mock, + buttons=buttons, + modal=False, + ) + widget.show() + for button in buttons: + with _check_widget_trigger(None, mock, '', '', get_value=False): + widget.trigger(button=button) + assert mock.call_args.args == (button,) + + # buttons list empty means OK button (default) + button = 'OK' + widget = renderer._dialog_warning( + title='', + text='', + info_text='', + callback=mock, + modal=False, + ) + widget.show() + with _check_widget_trigger(None, mock, '', '', get_value=False): + widget.trigger(button=button) + assert mock.call_args.args == (button,) + # --- END: dialog --- + renderer.show() renderer.close() diff --git a/mne/viz/backends/_abstract.py b/mne/viz/backends/_abstract.py index fdc65b1183b..9b0c0895554 100644 --- a/mne/viz/backends/_abstract.py +++ b/mne/viz/backends/_abstract.py @@ -623,6 +623,13 @@ def _playback_initialize(self, func, timeout, value, rng, pass +class _AbstractDialog(ABC): + @abstractmethod + def _dialog_warning(self, title, text, info_text, callback, *, + modal=True, window=None): + pass + + class _AbstractLayout(ABC): @abstractmethod def _layout_initialize(self, max_width): diff --git a/mne/viz/backends/_notebook.py b/mne/viz/backends/_notebook.py index cb1d39f457c..eea18d7ca16 100644 --- a/mne/viz/backends/_notebook.py +++ b/mne/viz/backends/_notebook.py @@ -15,10 +15,16 @@ _AbstractStatusBar, _AbstractLayout, _AbstractWidget, _AbstractWindow, _AbstractMplCanvas, _AbstractPlayback, _AbstractBrainMplCanvas, _AbstractMplInterface, - _AbstractWidgetList, _AbstractAction) + _AbstractWidgetList, _AbstractAction, _AbstractDialog) from ._pyvista import _PyVistaRenderer, _close_all, _set_3d_view, _set_3d_title # noqa: F401,E501, analysis:ignore +class _IpyDialog(_AbstractDialog): + def _dialog_warning(self, title, text, info_text, callback, *, + modal=True, window=None): + pass + + class _IpyLayout(_AbstractLayout): def _layout_initialize(self, max_width): self._layout_max_width = max_width @@ -501,7 +507,7 @@ def trigger(self): class _Renderer(_PyVistaRenderer, _IpyDock, _IpyToolBar, _IpyMenuBar, - _IpyStatusBar, _IpyWindow, _IpyPlayback): + _IpyStatusBar, _IpyWindow, _IpyPlayback, _IpyDialog): _kind = 'notebook' def __init__(self, *args, **kwargs): diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index b5332f9a21f..88c31ae0072 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -18,7 +18,7 @@ QSizePolicy, QScrollArea, QStyle, QProgressBar, QStyleOptionSlider, QLayout, QCheckBox, QButtonGroup, QRadioButton, QLineEdit, - QFileDialog, QPushButton) + QFileDialog, QPushButton, QMessageBox) from ._pyvista import _PyVistaRenderer from ._pyvista import (_close_all, _close_3d_figure, _check_3d_figure, # noqa: F401,E501 analysis:ignore @@ -27,11 +27,44 @@ _AbstractStatusBar, _AbstractLayout, _AbstractWidget, _AbstractWindow, _AbstractMplCanvas, _AbstractPlayback, _AbstractBrainMplCanvas, _AbstractMplInterface, - _AbstractWidgetList, _AbstractAction) + _AbstractWidgetList, _AbstractAction, _AbstractDialog) from ._utils import _init_qt_resources, _qt_disable_paint from ..utils import logger, _check_option +class _QtDialog(_AbstractDialog): + def _dialog_warning(self, title, text, info_text, callback, *, + buttons=[], modal=True, window=None): + window = self._window if window is None else window + widget = QMessageBox(window) + widget.setWindowTitle(title) + widget.setText(text) + widget.setIcon(QMessageBox.Warning) + widget.setInformativeText(info_text) + + if not buttons: + buttons = ["Ok"] + + button_ids = list() + for button in buttons: + # handle the special case of 'Ok' becoming 'OK' + button = "Ok" if button.upper() == "OK" else button + # button is one of QMessageBox.StandardButtons + button_id = getattr(QMessageBox, button) + button_ids.append(button_id) + standard_buttons = default_button = button_ids[0] + for button_id in button_ids[1:]: + standard_buttons |= button_id + widget.setStandardButtons(standard_buttons) + widget.setDefaultButton(default_button) + + def func(button): + callback(button.text()) + + widget.buttonClicked.connect(func) + return _QtDialogWidget(widget, modal) + + class _QtLayout(_AbstractLayout): def _layout_initialize(self, max_width): pass @@ -700,13 +733,30 @@ def set_tooltip(self, tooltip): self._widget.setToolTip(tooltip) +class _QtDialogWidget(_QtWidget): + def __init__(self, widget, modal): + super().__init__(widget) + self._modal = modal + + def trigger(self, button): + for current_button in self._widget.buttons(): + if current_button.text() == button: + current_button.click() + + def show(self): + if self._modal: + self._widget.exec() + else: + self._widget.show() + + class _QtAction(_AbstractAction): def trigger(self): self._action.trigger() class _Renderer(_PyVistaRenderer, _QtDock, _QtToolBar, _QtMenuBar, - _QtStatusBar, _QtWindow, _QtPlayback): + _QtStatusBar, _QtWindow, _QtPlayback, _QtDialog): _kind = 'qt' def __init__(self, *args, **kwargs):