diff --git a/pyflow/blocks/executableblock.py b/pyflow/blocks/executableblock.py index 5b63c214..85f3be99 100644 --- a/pyflow/blocks/executableblock.py +++ b/pyflow/blocks/executableblock.py @@ -316,6 +316,9 @@ def run_blocks(self): def run_left(self): """Run all of the block's dependencies and then run the block.""" + # Start kernel + self.scene().kernel.start() + # Reset state to make sure that the self is run again self.run_state = ExecutableState.IDLE @@ -339,6 +342,9 @@ def run_left(self): def run_right(self): """Run all of the output blocks and all their dependencies.""" + # Start kernel + self.scene().kernel.start() + # To avoid crashing when spamming the button if self.transmitting_queue: return diff --git a/pyflow/core/kernel.py b/pyflow/core/kernel.py index 93e2f886..302ea498 100644 --- a/pyflow/core/kernel.py +++ b/pyflow/core/kernel.py @@ -23,10 +23,39 @@ class Kernel: @log_init_time(LOGGER) def __init__(self): - self.kernel_manager, self.client = start_new_kernel() + self.client = None + self.kernel_manager = None self.execution_queue: List[Tuple["ExecutableBlock", str]] = [] self.busy = False + def start(self): + """ + Starts the kernel + """ + # Check if client exists + if self.client is None: + self.kernel_manager, self.client = start_new_kernel() + + def stop(self): + """ + Stops the kernel + """ + self.kernel_manager.shutdown_kernel() + self.client = None + self.kernel_manager = None + + def interrupt(self): + """ + Interrupts the kernel + """ + self.kernel_manager.interrupt_kernel() + + def restart(self): + """ + Restarts the kernel + """ + self.kernel_manager.restart_kernel() + def message_to_output(self, message: dict) -> Tuple[str, str]: """ Converts a message sent by the kernel into a relevant output @@ -104,6 +133,7 @@ def execute(self, code: str) -> str: Return: output from the last message sent by the kernel """ + self.start() _ = self.client.execute(code) done = False while not done: diff --git a/pyflow/graphics/window.py b/pyflow/graphics/window.py index b501a505..8beb7a33 100644 --- a/pyflow/graphics/window.py +++ b/pyflow/graphics/window.py @@ -19,6 +19,8 @@ QCheckBox, ) +from pyflow.blocks.executableblock import ExecutableBlock +from pyflow.core.executable import ExecutableState from pyflow.graphics.widget import Widget from pyflow.graphics.theme_manager import theme_manager @@ -224,6 +226,32 @@ def createActions(self): self._actSeparator = QAction(self) self._actSeparator.setSeparator(True) + # Kernel + self._actStartKernel = QAction( + "&Start Kernel", + self, + statusTip="Start the kernel", + triggered=self.start_kernel, + ) + self._actInterruptKernel = QAction( + "&Interrupt Kernel", + self, + statusTip="Interrupt the kernel", + triggered=self.interrupt_kernel, + ) + self._actStopKernel = QAction( + "&Stop Kernel", + self, + statusTip="Stop the kernel", + triggered=self.stop_kernel, + ) + self._actRestartKernel = QAction( + "&Restart Kernel", + self, + statusTip="Restart the kernel", + triggered=self.restart_kernel, + ) + def createMenus(self): """Create the File menu with linked shortcuts.""" self.filemenu = self.menuBar().addMenu("&File") @@ -248,6 +276,12 @@ def createMenus(self): self.editmenu.addAction(self._actDuplicate) self.editmenu.addAction(self._actRun) + self.kernelMenu = self.menuBar().addMenu("&Kernel") + self.kernelMenu.addAction(self._actStartKernel) + self.kernelMenu.addAction(self._actInterruptKernel) + self.kernelMenu.addAction(self._actStopKernel) + self.kernelMenu.addAction(self._actRestartKernel) + self.viewmenu = self.menuBar().addMenu("&View") self.thememenu = self.viewmenu.addMenu("Theme") self.thememenu.aboutToShow.connect(self.updateThemeMenu) @@ -546,3 +580,45 @@ def onMoveToItems(self): def setTheme(self, theme_index): """Set the theme of the application.""" theme_manager().selected_theme_index = theme_index + + def start_kernel(self): + """Start the kernel.""" + current_window = self._call_kernel("Kernel started") + if current_window is not None: + current_window.scene.kernel.start() + + def interrupt_kernel(self): + """Interrupt the kernel.""" + current_window = self._call_kernel("Kernel interrupted") + if current_window is not None: + current_window.scene.kernel.interrupt() + + def stop_kernel(self): + """Stop the kernel.""" + current_window = self._call_kernel("Kernel stopped") + if current_window is not None: + current_window.scene.kernel.stop() + self.reset_block_states() + + def restart_kernel(self): + """Restart the kernel.""" + current_window = self._call_kernel("Kernel restarted") + if current_window is not None: + current_window.scene.kernel.restart() + self.reset_block_states() + + def _call_kernel(self, message): + """Call a kernel function but check if a kernel exists first""" + current_window = self.activeMdiChild() + if current_window is not None: + self.statusbar.showMessage(message) + return current_window + else: + self.statusbar.showMessage("No active window") + return None + + def reset_block_states(self): + current_scene = self.activeMdiChild().scene + for item in current_scene.items(): + if isinstance(item, ExecutableBlock): + item.run_state = ExecutableState.IDLE