diff --git a/docs/source/release/v6.1.0/mantidworkbench.rst b/docs/source/release/v6.1.0/mantidworkbench.rst index af7ba68c260eeb5224e90e9c4de55573102f83ac..63fa255cb9868597bb95191bf491623426a6ad0b 100644 --- a/docs/source/release/v6.1.0/mantidworkbench.rst +++ b/docs/source/release/v6.1.0/mantidworkbench.rst @@ -7,14 +7,15 @@ Mantid Workbench Changes New and Improved ---------------- -Added Floating/On Top setting for all the windows that are opened by workbench (plots, interfaces, etc.) +- Added Floating/On Top setting for all the windows that are opened by workbench (plots, interfaces, etc.) - New plot interactions: Double click a legend to hide it, double click a curve to open it in the plot config dialog. - It is now possible to overplot bin data from the matrix workspace view. - Improved the performance of the table workspace display for large datasets - Added a sample material dialog that is accessed via the context menu in the workspace widget. - When a workspace is renamed it now updates the relevant plot labels with the new workspace name. - Add a checkbox to freeze the rotation in the instrument viewer in Full 3D mode. +- Calling python's `input` now raises an input dialog in the script editor and the iPython shell. - A new empty facility with empty instrument is the default facility now, and user has to select their choice of facility (including ISIS) and instrument for the first time diff --git a/qt/applications/workbench/workbench/app/mainwindow.py b/qt/applications/workbench/workbench/app/mainwindow.py index d376a13aa49a53f71c35dcadc029b48a6a5652b8..8910a0f260c2d32d474df1d5ab33e21575619e7a 100644 --- a/qt/applications/workbench/workbench/app/mainwindow.py +++ b/qt/applications/workbench/workbench/app/mainwindow.py @@ -10,6 +10,7 @@ """ Defines the QMainWindow of the application and the main() entry point. """ +import builtins import os from mantid.api import FrameworkManager @@ -39,6 +40,7 @@ from workbench.config import CONF # noqa from workbench.plotting.globalfiguremanager import GlobalFigureManager # noqa from workbench.utils.windowfinder import find_all_windows_that_are_savable # noqa from workbench.utils.workspacehistorygeneration import get_all_workspace_history_from_ads # noqa +from workbench.utils.io import input_qinputdialog from workbench.projectrecovery.projectrecovery import ProjectRecovery # noqa from workbench.utils.recentlyclosedscriptsmenu import RecentlyClosedScriptsMenu # noqa from mantidqt.utils.asynchronous import BlockingAsyncTaskWithCallback # noqa @@ -197,6 +199,8 @@ class MainWindow(QMainWindow): self.readSettings(CONF) self.config_updated() + self.override_python_input() + def post_mantid_init(self): """Run any setup that requires mantid to have been initialized @@ -752,3 +756,7 @@ class MainWindow(QMainWindow): for widget in self.widgets: if hasattr(widget, 'writeSettings'): widget.writeSettings(settings) + + def override_python_input(self): + """Replace python input with a call to a qinputdialog""" + builtins.input = QAppThreadCall(input_qinputdialog) diff --git a/qt/applications/workbench/workbench/plugins/jupyterconsole.py b/qt/applications/workbench/workbench/plugins/jupyterconsole.py index 5f8f599f011781256ff6f38b5686a4109ea4e4ba..9798b9626a104d8a06e49ab72575ebf2d721dd19 100644 --- a/qt/applications/workbench/workbench/plugins/jupyterconsole.py +++ b/qt/applications/workbench/workbench/plugins/jupyterconsole.py @@ -27,7 +27,7 @@ from ..plugins.base import PluginWidget # noqa # from mantidqt.utils.qt import toQSettings when readSettings/writeSettings are implemented # should we share this with plugins.editor? -STARTUP_CODE = """from __future__ import (absolute_import, division, print_function, unicode_literals) +STARTUP_CODE = """ from mantid.simpleapi import * import matplotlib.pyplot as plt import numpy as np diff --git a/qt/applications/workbench/workbench/test/mainwindowtest.py b/qt/applications/workbench/workbench/test/mainwindowtest.py index 2f9b1e7fe1f4f55da2b333c09ea32bb4ce049b28..233dd8a7953b8ca17a38389e54d981f80e4261da 100644 --- a/qt/applications/workbench/workbench/test/mainwindowtest.py +++ b/qt/applications/workbench/workbench/test/mainwindowtest.py @@ -337,6 +337,14 @@ class MainWindowTest(unittest.TestCase): } self.assertDictEqual(expected_interfaces, all_interfaces) + @patch('workbench.app.mainwindow.input_qinputdialog') + def test_override_python_input_replaces_input_with_qinputdialog(self, mock_input): + self.main_window.override_python_input() + + input("prompt") + + mock_input.assert_called_with("prompt") + if __name__ == '__main__': unittest.main() diff --git a/qt/applications/workbench/workbench/utils/io.py b/qt/applications/workbench/workbench/utils/io.py new file mode 100644 index 0000000000000000000000000000000000000000..9b9ca5b699d2c1793a183137ddf56dccac394859 --- /dev/null +++ b/qt/applications/workbench/workbench/utils/io.py @@ -0,0 +1,24 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2021 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source, +# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +# SPDX - License - Identifier: GPL - 3.0 + +# This file is part of the mantidworkbench package +from qtpy.QtWidgets import QInputDialog + + +def input_qinputdialog(prompt: str = "") -> str: + """ + Raises a QInputDialog with a given prompt and returns the user input as a string. + If the user cancels the dialog, an EOFError is raised. + Intended to be used to override python's `input` function to be more user friendly. + """ + dlg = QInputDialog() + dlg.setInputMode(QInputDialog.TextInput) + dlg.setLabelText(str(prompt) if prompt is not None else "") + accepted = dlg.exec_() + if accepted: + return dlg.textValue() + else: + raise EOFError("User input request cancelled") diff --git a/qt/applications/workbench/workbench/utils/test/test_io.py b/qt/applications/workbench/workbench/utils/test/test_io.py new file mode 100644 index 0000000000000000000000000000000000000000..ffcc14e79f730d599a25ab8086a1532a72b945f0 --- /dev/null +++ b/qt/applications/workbench/workbench/utils/test/test_io.py @@ -0,0 +1,42 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2021 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source, +# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +# SPDX - License - Identifier: GPL - 3.0 + +# This file is part of the mantidworkbench package +from unittest import TestCase +from unittest.mock import patch + +from qtpy.QtWidgets import QInputDialog +from workbench.utils.io import input_qinputdialog + + +class IOTest(TestCase): + + @patch('workbench.utils.io.QInputDialog') + def test_input_qinputdialog_setup_is_correct(self, mock_QInputDialogClass): + mock_QInputDialogClass.TextInput = QInputDialog.TextInput + mock_QInputDialog = mock_QInputDialogClass() + + _ = input_qinputdialog("prompt") + + mock_QInputDialog.setInputMode.assert_called_with(QInputDialog.TextInput) + mock_QInputDialog.setLabelText.assert_called_with("prompt") + + @patch('workbench.utils.io.QInputDialog') + def test_input_qinputdialog_return_value_is_correct_when_dialog_accepted(self, mock_QInputDialogClass): + mock_QInputDialog = mock_QInputDialogClass() + mock_QInputDialog.exec_.return_value = True + mock_QInputDialog.textValue.return_value = "their input" + + user_input = input_qinputdialog() + + self.assertEqual(user_input, "their input") + + @patch('workbench.utils.io.QInputDialog') + def test_input_qinputdialog_raises_RuntimeError_when_input_cancelled(self, mock_QInputDialogClass): + mock_QInputDialog = mock_QInputDialogClass() + mock_QInputDialog.exec_.return_value = False + + self.assertRaises(EOFError, input_qinputdialog) diff --git a/qt/python/mantidqt/widgets/jupyterconsole.py b/qt/python/mantidqt/widgets/jupyterconsole.py index 8b2c65657320c3906144c7a804f36ba2aac5612a..d5efc6a88b165417f26ec43d3dd08a21549ae5f7 100644 --- a/qt/python/mantidqt/widgets/jupyterconsole.py +++ b/qt/python/mantidqt/widgets/jupyterconsole.py @@ -26,6 +26,8 @@ except ImportError: # local imports from inspect import getfullargspec from mantidqt.utils.asynchronous import BlockingAsyncTaskWithCallback +from mantidqt.utils.qt.qappthreadcall import QAppThreadCall +from workbench.utils.io import input_qinputdialog class InProcessJupyterConsole(RichJupyterWidget): @@ -62,6 +64,9 @@ class InProcessJupyterConsole(RichJupyterWidget): self.kernel_manager = kernel_manager self.kernel_client = kernel_client + # Override python input to raise a QInputDialog. + kernel.raw_input = QAppThreadCall(input_qinputdialog) + def keyPressEvent(self, event): if QApplication.keyboardModifiers() & Qt.ControlModifier and (event.key() == Qt.Key_Equal): self.change_font_size(1) diff --git a/qt/python/mantidqt/widgets/test/test_jupyterconsole.py b/qt/python/mantidqt/widgets/test/test_jupyterconsole.py index a1b84668154ea983b88bf2faedb03b3e9d6694f8..f471a211778541f8b9ea3dfcb8dad5afc3cfc9c9 100644 --- a/qt/python/mantidqt/widgets/test/test_jupyterconsole.py +++ b/qt/python/mantidqt/widgets/test/test_jupyterconsole.py @@ -14,6 +14,7 @@ try: except ImportError: pass import unittest +from unittest.mock import patch # third-party library imports @@ -41,6 +42,18 @@ class InProcessJupyterConsoleTest(unittest.TestCase): widget.kernel_manager.shutdown_kernel() del widget + @patch('mantidqt.widgets.jupyterconsole.input_qinputdialog') + def test_construction_overrides_python_input_with_qinputdialog(self, mock_input): + widget = InProcessJupyterConsole() + kernel = widget.kernel_manager.kernel + kernel.raw_input("prompt") + + mock_input.assert_called_with("prompt") + + self._pre_delete_console_cleanup(widget) + widget.kernel_manager.shutdown_kernel() + del widget + def _pre_delete_console_cleanup(self, console): """Certain versions of qtconsole seem to raise an attribute error on exit of the process when an event is delivered to an event filter