From 3509dba9e370ea2b25b6fa198e172c2d527b52b4 Mon Sep 17 00:00:00 2001 From: Charles Titus Date: Wed, 30 Oct 2024 18:25:34 -0400 Subject: [PATCH 1/3] IPython Console widget moved over from nbs-gui, tested and working --- bluesky_widgets/qt/ipython_console.py | 127 ++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 bluesky_widgets/qt/ipython_console.py diff --git a/bluesky_widgets/qt/ipython_console.py b/bluesky_widgets/qt/ipython_console.py new file mode 100644 index 0000000..f5cd8d4 --- /dev/null +++ b/bluesky_widgets/qt/ipython_console.py @@ -0,0 +1,127 @@ +from qtpy.QtWidgets import ( + QVBoxLayout, + QWidget, + QSizePolicy, + QPushButton, + QLabel, +) +from qtpy.QtCore import Signal, Slot, Qt +from qtconsole.rich_jupyter_widget import RichJupyterWidget +from qtconsole.manager import QtKernelManager + + +class QtReIPythonConsole(QWidget): + """ + A QWidget that contains an embedded IPython console. + + Attributes + ---------- + console : RichJupyterWidget + The embedded IPython console widget. + kernel_manager : QtKernelManager + Manager for the IPython kernel. + kernel_client : QtKernelClient + Client for interacting with the kernel. + """ + + signal_update_widget = Signal(object) + + def __init__(self, model): + """ + Initializes the IPythonConsoleTab widget, setting up the IPython console, + kernel manager, and kernel client, and connecting them together. + """ + + super().__init__() + self.kernel_label = QLabel("Kernel Status: Not Connected") + self.REClientModel = model + self.REClientModel.events.status_changed.connect(self.on_update_widgets) + self.signal_update_widget.connect(self.slot_update_widgets) + + # Create main layout + self.vbox = QVBoxLayout() + self.vbox.addWidget(self.kernel_label) + + # Create placeholder widget + self.placeholder = QLabel("Connect to Kernel by hitting the button when the kernel status is idle") + self.placeholder.setAlignment(Qt.AlignCenter) + self.placeholder.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + + # Add placeholder to layout + self.vbox.addWidget(self.placeholder) + + # Create connect button + self.connectButton = QPushButton("Connect to Kernel") + self.connectButton.clicked.connect(self.connect_to_kernel) + self.connectButton.setEnabled(False) + + self.vbox.addWidget(self.connectButton) + # self.vbox.addWidget(QtReStatusMonitor(self.REClientModel)) + + # Initialize console reference as None + self.console = None + self.kernel_manager = None + self.kernel_client = None + self.setLayout(self.vbox) + + def on_update_widgets(self, event): + status = event.status + self.signal_update_widget.emit(status) + + @Slot(object) + def slot_update_widgets(self, status): + kernel_state = status.get("ip_kernel_state", None) + if kernel_state is not None: + self.kernel_label.setText(f"Kernel State: {kernel_state}") + else: + self.kernel_label.setText("Kernel State: Not Connected") + if kernel_state in ["idle", "busy"] and not self.is_console_connected(): + self.connectButton.setEnabled(True) + else: + self.connectButton.setEnabled(False) + + def connect_to_kernel(self): + """ + Connects to the IPython kernel when the button is pressed. + """ + print("Connecting to Kernel") + + # Clean up existing console if it exists + if self.console is not None: + self.console.kernel_client.stop_channels() + self.console.kernel_manager = None + self.console.kernel_client = None + self.vbox.removeWidget(self.console) + self.console.deleteLater() + self.console = None + + # Remove placeholder if it exists + if self.placeholder is not None: + self.vbox.removeWidget(self.placeholder) + self.placeholder.hide() + + # Create new console widget + self.console = RichJupyterWidget() + self.console.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + + # Insert console widget after kernel label but before buttons + self.vbox.insertWidget(1, self.console) + + # Setup kernel connection + msg = self.REClientModel._client.config_get() + connect_info = msg["config"]["ip_connect_info"] + + self.kernel_manager = QtKernelManager() + self.kernel_manager.load_connection_info(connect_info) + self.kernel_client = self.kernel_manager.client() + self.kernel_client.start_channels() + + # Connect the console widget to the kernel + self.console.kernel_manager = self.kernel_manager + self.console.kernel_client = self.kernel_client + print("Done connecting to Kernel") + + def is_console_connected(self): + if self.console is not None and self.console.kernel_client and self.console.kernel_client.is_alive(): + return True + return False From 34d31555198be7a3b185ad7715389c70fdd63817 Mon Sep 17 00:00:00 2001 From: Charles Titus Date: Wed, 30 Oct 2024 18:29:14 -0400 Subject: [PATCH 2/3] Adding parent to console widget --- bluesky_widgets/qt/ipython_console.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bluesky_widgets/qt/ipython_console.py b/bluesky_widgets/qt/ipython_console.py index f5cd8d4..62301db 100644 --- a/bluesky_widgets/qt/ipython_console.py +++ b/bluesky_widgets/qt/ipython_console.py @@ -26,16 +26,16 @@ class QtReIPythonConsole(QWidget): signal_update_widget = Signal(object) - def __init__(self, model): + def __init__(self, model, parent=None): """ Initializes the IPythonConsoleTab widget, setting up the IPython console, kernel manager, and kernel client, and connecting them together. """ - super().__init__() + super().__init__(parent) self.kernel_label = QLabel("Kernel Status: Not Connected") - self.REClientModel = model - self.REClientModel.events.status_changed.connect(self.on_update_widgets) + self.model = model + self.model.events.status_changed.connect(self.on_update_widgets) self.signal_update_widget.connect(self.slot_update_widgets) # Create main layout @@ -108,7 +108,7 @@ def connect_to_kernel(self): self.vbox.insertWidget(1, self.console) # Setup kernel connection - msg = self.REClientModel._client.config_get() + msg = self.model._client.config_get() connect_info = msg["config"]["ip_connect_info"] self.kernel_manager = QtKernelManager() From 8bd19e61eebee71a770027530fe9443d43bd224d Mon Sep 17 00:00:00 2001 From: Charles Titus Date: Wed, 30 Oct 2024 18:41:01 -0400 Subject: [PATCH 3/3] STY: Removed superfluous comments and debugging print statements --- bluesky_widgets/qt/ipython_console.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/bluesky_widgets/qt/ipython_console.py b/bluesky_widgets/qt/ipython_console.py index 62301db..5dfa835 100644 --- a/bluesky_widgets/qt/ipython_console.py +++ b/bluesky_widgets/qt/ipython_console.py @@ -28,8 +28,13 @@ class QtReIPythonConsole(QWidget): def __init__(self, model, parent=None): """ - Initializes the IPythonConsoleTab widget, setting up the IPython console, - kernel manager, and kernel client, and connecting them together. + Initialize the IPython console widget in an unconnected state. + The connection must be initialized by pressing a button in the GUI + + Parameters + ---------- + model : bluesky_widgets.models.run_engine_client.RunEngineClient + Run engine model that provides kernel connection status and control """ super().__init__(parent) @@ -42,12 +47,11 @@ def __init__(self, model, parent=None): self.vbox = QVBoxLayout() self.vbox.addWidget(self.kernel_label) - # Create placeholder widget + # Create placeholder widget before console is initialized self.placeholder = QLabel("Connect to Kernel by hitting the button when the kernel status is idle") self.placeholder.setAlignment(Qt.AlignCenter) self.placeholder.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - # Add placeholder to layout self.vbox.addWidget(self.placeholder) # Create connect button @@ -56,9 +60,8 @@ def __init__(self, model, parent=None): self.connectButton.setEnabled(False) self.vbox.addWidget(self.connectButton) - # self.vbox.addWidget(QtReStatusMonitor(self.REClientModel)) - # Initialize console reference as None + # Initialize console reference as None, just in case self.console = None self.kernel_manager = None self.kernel_client = None @@ -84,7 +87,6 @@ def connect_to_kernel(self): """ Connects to the IPython kernel when the button is pressed. """ - print("Connecting to Kernel") # Clean up existing console if it exists if self.console is not None: @@ -119,7 +121,6 @@ def connect_to_kernel(self): # Connect the console widget to the kernel self.console.kernel_manager = self.kernel_manager self.console.kernel_client = self.kernel_client - print("Done connecting to Kernel") def is_console_connected(self): if self.console is not None and self.console.kernel_client and self.console.kernel_client.is_alive():