diff options
Diffstat (limited to 'src/silx/gui/console.py')
-rw-r--r-- | src/silx/gui/console.py | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/src/silx/gui/console.py b/src/silx/gui/console.py new file mode 100644 index 0000000..df0e36c --- /dev/null +++ b/src/silx/gui/console.py @@ -0,0 +1,202 @@ +# /*########################################################################## +# +# Copyright (c) 2004-2022 European Synchrotron Radiation Facility +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""This module provides an IPython console widget. + +You can push variables - any python object - to the +console's interactive namespace. This provides users with an advanced way +of interacting with your program. For instance, if your program has a +:class:`PlotWidget` or a :class:`PlotWindow`, you can push a reference to +these widgets to allow your users to add curves, save data to files… by using +the widgets' methods from the console. + +.. note:: + + This module has a dependency on + `qtconsole <https://pypi.org/project/qtconsole/>`_. + An ``ImportError`` will be raised if it is + imported while the dependencies are not satisfied. + +Basic usage example:: + + from silx.gui import qt + from silx.gui.console import IPythonWidget + + app = qt.QApplication([]) + + hello_button = qt.QPushButton("Hello World!", None) + hello_button.show() + + console = IPythonWidget() + console.show() + console.pushVariables({"the_button": hello_button}) + + app.exec() + +This program will display a console widget and a push button in two separate +windows. You will be able to interact with the button from the console, +for example change its text:: + + >>> the_button.setText("Spam spam") + +An IPython interactive console is a powerful tool that enables you to work +with data and plot it. +See `this tutorial <https://plot.ly/python/ipython-notebook-tutorial/>`_ +for more information on some of the rich features of IPython. +""" +__authors__ = ["Tim Rae", "V.A. Sole", "P. Knobel"] +__license__ = "MIT" +__date__ = "24/05/2016" + +import logging + +from . import qt + +_logger = logging.getLogger(__name__) + + +# This widget cannot be used inside an interactive IPython shell. +# It would raise MultipleInstanceError("Multiple incompatible subclass +# instances of InProcessInteractiveShell are being created"). +try: + __IPYTHON__ +except NameError: + pass # Not in IPython +else: + msg = "Module " + __name__ + " cannot be used within an IPython shell" + raise ImportError(msg) + +try: + from qtconsole.rich_jupyter_widget import RichJupyterWidget as _RichJupyterWidget +except ImportError: + try: + from qtconsole.rich_ipython_widget import ( + RichJupyterWidget as _RichJupyterWidget, + ) + except ImportError: + from qtconsole.rich_ipython_widget import ( + RichIPythonWidget as _RichJupyterWidget, + ) + +from qtconsole.inprocess import QtInProcessKernelManager + +try: + from ipykernel import version_info as _ipykernel_version_info +except ImportError: + _ipykernel_version_info = None + + +class IPythonWidget(_RichJupyterWidget): + """Live IPython console widget. + + .. image:: img/IPythonWidget.png + + :param custom_banner: Custom welcome message to be printed at the top of + the console. + """ + + def __init__(self, parent=None, custom_banner=None, *args, **kwargs): + if parent is not None: + kwargs["parent"] = parent + super(IPythonWidget, self).__init__(*args, **kwargs) + if custom_banner is not None: + self.banner = custom_banner + self.setWindowTitle(self.banner) + self.kernel_manager = kernel_manager = QtInProcessKernelManager() + kernel_manager.start_kernel() + + # Monkey-patch to workaround issue: + # https://github.com/ipython/ipykernel/issues/370 + if ( + _ipykernel_version_info is not None + and _ipykernel_version_info[0] > 4 + and _ipykernel_version_info[:3] <= (5, 1, 0) + ): + + def _abort_queues(*args, **kwargs): + pass + + kernel_manager.kernel._abort_queues = _abort_queues + + self.kernel_client = kernel_client = self._kernel_manager.client() + kernel_client.start_channels() + + def stop(): + kernel_client.stop_channels() + kernel_manager.shutdown_kernel() + + self.exit_requested.connect(stop) + + def sizeHint(self): + """Return a reasonable default size for usage in :class:`PlotWindow`""" + return qt.QSize(500, 300) + + def pushVariables(self, variable_dict): + """Given a dictionary containing name / value pairs, push those + variables to the IPython console widget. + + :param variable_dict: Dictionary of variables to be pushed to the + console's interactive namespace (```{variable_name: object, …}```) + """ + self.kernel_manager.kernel.shell.push(variable_dict) + + +class IPythonDockWidget(qt.QDockWidget): + """Dock Widget including a :class:`IPythonWidget` inside + a vertical layout. + + .. image:: img/IPythonDockWidget.png + + :param available_vars: Dictionary of variables to be pushed to the + console's interactive namespace: ``{"variable_name": object, …}`` + :param custom_banner: Custom welcome message to be printed at the top of + the console + :param title: Dock widget title + :param parent: Parent :class:`qt.QMainWindow` containing this + :class:`qt.QDockWidget` + """ + + def __init__( + self, parent=None, available_vars=None, custom_banner=None, title="Console" + ): + super(IPythonDockWidget, self).__init__(title, parent) + + self.ipyconsole = IPythonWidget(custom_banner=custom_banner) + + self.layout().setContentsMargins(0, 0, 0, 0) + self.setWidget(self.ipyconsole) + + if available_vars is not None: + self.ipyconsole.pushVariables(available_vars) + + +def main(): + """Run a Qt app with an IPython console""" + app = qt.QApplication([]) + widget = IPythonDockWidget() + widget.show() + app.exec() + + +if __name__ == "__main__": + main() |