From 61a06afccc673a738ad952b6d54cadb73e501dca Mon Sep 17 00:00:00 2001 From: michaellans Date: Thu, 6 Aug 2026 10:20:54 -0700 Subject: [PATCH 1/6] set run_until as default behavior for mini --- src/badger/gui/mini/pages/home_page.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/badger/gui/mini/pages/home_page.py b/src/badger/gui/mini/pages/home_page.py index e9b7989b..629168e1 100644 --- a/src/badger/gui/mini/pages/home_page.py +++ b/src/badger/gui/mini/pages/home_page.py @@ -283,6 +283,18 @@ def config_logic(self): self.sig_routine_invalid.connect(self.run_action_bar.routine_invalid) + self._configure_default_run_action() + + def _configure_default_run_action(self): + """Set the default run action as run_until_action""" + self.run_action_bar.btn_stop.setDefaultAction( + self.run_action_bar.run_until_action + ) + # configure default to max_eval (tc_idx=0), 50 iterations + self.run_monitor.save_termination_condition( + {"tc_idx": 0, "max_eval": 50, "max_time": 300, "ftol": 0} + ) + def update_saved_values_from_monitor(self): """ Sync Saved column values to match run monitor reset_env targets. From 9a9cfce9a7835e88514549f5dc5183208de3f633 Mon Sep 17 00:00:00 2001 From: michaellans Date: Tue, 11 Aug 2026 16:42:03 -0700 Subject: [PATCH 2/6] add termination_reached_dialog --- .../gui/windows/termination_reached_dialog.py | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 src/badger/gui/windows/termination_reached_dialog.py diff --git a/src/badger/gui/windows/termination_reached_dialog.py b/src/badger/gui/windows/termination_reached_dialog.py new file mode 100644 index 00000000..af5d76b6 --- /dev/null +++ b/src/badger/gui/windows/termination_reached_dialog.py @@ -0,0 +1,110 @@ +"""Dialog shown when a run-until threshold is reached during optimization. + +Lets users choose whether to continue running or end the current run, +after the run is paused by a termination condition. +""" + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import ( + QDialog, + QDialogButtonBox, + QHBoxLayout, + QLabel, + QVBoxLayout, +) + +stylesheet_run = """ +QPushButton:hover:pressed +{ + background-color: #92D38C; +} +QPushButton:hover +{ + background-color: #6EC566; +} +QPushButton +{ + background-color: #4AB640; + color: #000000; +} +""" + +stylesheet_stop = """ +QPushButton:hover:pressed +{ + background-color: #C7737B; +} +QPushButton:hover +{ + background-color: #BF616A; +} +QPushButton +{ + background-color: #A9444E; +} +""" + + +class BadgerTerminationReachedDialog(QDialog): + def __init__(self, tc_condition=None, text="", parent=None): + super().__init__(parent) + + self.setWindowTitle("Termination Condition Reached") + self.setMinimumWidth(360) + + layout = QVBoxLayout(self) + layout.setContentsMargins(14, 14, 14, 14) + layout.setSpacing(8) + + tc_type = tc_condition["type"] + if tc_type == "max_eval": + tc_type_text = "Max evaluation" + state = tc_condition["state"] + else: + tc_type_text = "Timeout" + state = f"{tc_condition['state']:.2f} s" + + content_row = QHBoxLayout() + content_row.setSpacing(6) + + text_column = QVBoxLayout() + text_column.setSpacing(3) + + title_label = QLabel("Termination condition reached") + title_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + title_label.setStyleSheet("font-size: 14px; font-weight: 600;") + text_column.addWidget(title_label) + + body_label = QLabel("Badger optimization stopped.") + body_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + text_column.addWidget(body_label) + + summary_label = QLabel(f"{tc_type_text}: {state}/{tc_condition['config']}") + summary_label.setWordWrap(True) + summary_label.setAlignment(Qt.AlignLeft) + summary_label.setStyleSheet("color: #8A949E;") + text_column.addWidget(summary_label) + + content_row.addLayout(text_column) + layout.addLayout(content_row) + + button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.continueButton = button_box.button(QDialogButtonBox.Ok) + self.endButton = button_box.button(QDialogButtonBox.Cancel) + + font = self.font() + font.setPointSize(12) + self.setFont(font) + + self.continueButton.setText("Continue") + # self.continueButton.setStyleSheet(stylesheet_run) + self.continueButton.setFixedSize(96, 24) + self.endButton.setText("End Run") + self.endButton.setStyleSheet(stylesheet_stop) + self.endButton.setFixedSize(96, 24) + + button_box.accepted.connect(self.accept) + button_box.rejected.connect(self.reject) + layout.addWidget(button_box) + + self.resize(360, 150) From 6d5b1741a8a13d9b017a80e4f112abd458aaedfa Mon Sep 17 00:00:00 2001 From: michaellans Date: Tue, 11 Aug 2026 16:59:11 -0700 Subject: [PATCH 3/6] reaching termination condition in optimization loop pauses and opens dialog --- src/badger/core_subprocess.py | 83 ++++++++++++++++++++- src/badger/errors.py | 7 ++ src/badger/gui/components/routine_runner.py | 28 +++++++ 3 files changed, 114 insertions(+), 4 deletions(-) diff --git a/src/badger/core_subprocess.py b/src/badger/core_subprocess.py index be94c6bd..ea2cf3a9 100644 --- a/src/badger/core_subprocess.py +++ b/src/badger/core_subprocess.py @@ -31,6 +31,10 @@ MEASUREMENT_ACTION_TYPE, MEASUREMENT_ACTION_RETRY, MEASUREMENT_ACTION_ABORT, + TERMINATION_REACHED_TYPE, + TERMINATION_ACTION_TYPE, + TERMINATION_ACTION_CONTINUE, + TERMINATION_ACTION_END, ) from badger.logger import _get_default_logger from badger.logger.event import Events @@ -89,6 +93,47 @@ def evaluate_measurement_with_retry( ) +def pause_for_termination_dialog_action( + queue: mp.Queue, + stop_process: mp.Event, + pause_process: mp.Event, + dialog_action_queue: mp.Queue, + tc_condition: dict, +) -> None: + """Pause the run and wait for user action when run-until condition is reached.""" + queue.put( + { + "type": TERMINATION_REACHED_TYPE, + "tc_condition": tc_condition, + } + ) + + while True: + if stop_process.is_set(): + raise BadgerRunTerminated + + try: + msg = dialog_action_queue.get( + timeout=0.1 + ) # short timeout here, so we can make checks for stop_process + except Empty: + continue + + if ( + isinstance(msg, dict) + and msg.get("type") == TERMINATION_ACTION_TYPE + and msg.get("action") + in [TERMINATION_ACTION_CONTINUE, TERMINATION_ACTION_END] + ): + if msg["action"] == TERMINATION_ACTION_CONTINUE: + pause_process.set() + return + + raise BadgerRunTerminated( + "Run terminated after termination condition reached" + ) + + def convert_to_solution(result: DataFrame, routine: Routine): """ This method is passed the latest evaluated solution and converts that to a printable format for the terminal. @@ -323,16 +368,46 @@ def run_routine_subprocess( if count >= max_eval: logger.info( - "Max evaluations reached. Terminating optimization." + "Max evaluations reached. Pausing optimization and waiting for user action." ) - raise BadgerRunTerminated + pause_process.clear() + pause_for_termination_dialog_action( + queue=queue, + stop_process=stop_process, + pause_process=pause_process, + dialog_action_queue=dialog_action_queue, + tc_condition={ + "type": "max_eval", + "config": max_eval, + "state": count, + }, + ) + # reset termination condition + termination_condition = None + continue elif idx == 1: max_time = tc_config["max_time"] dt = time.time() - start_time logger.debug(f"Checking max_time termination: {dt} >= {max_time}") if dt >= max_time: - logger.info("Max time reached. Terminating optimization.") - raise BadgerRunTerminated + logger.info( + "Max time reached. Pausing optimization and waiting for user action." + ) + pause_process.clear() + pause_for_termination_dialog_action( + queue=queue, + stop_process=stop_process, + pause_process=pause_process, + dialog_action_queue=dialog_action_queue, + tc_condition={ + "type": "max_time", + "config": max_time, + "state": dt, + }, + ) + # reset termination condition + termination_condition = None + continue candidates = routine.generator.generate(1)[0] logger.debug(f"Generated candidates: {candidates}") diff --git a/src/badger/errors.py b/src/badger/errors.py index fdc42237..02a575d4 100644 --- a/src/badger/errors.py +++ b/src/badger/errors.py @@ -121,3 +121,10 @@ def __init__(self, message="Optimization run has been terminated!"): MEASUREMENT_ACTION_TYPE = "measurement_action" MEASUREMENT_ACTION_RETRY = "retry" MEASUREMENT_ACTION_ABORT = "abort" + +# Constants for run-until termination dialog feature. +# Used in communication between routine runner and subprocess. +TERMINATION_REACHED_TYPE = "termination_reached" +TERMINATION_ACTION_TYPE = "termination_action" +TERMINATION_ACTION_CONTINUE = "continue" +TERMINATION_ACTION_END = "end" diff --git a/src/badger/gui/components/routine_runner.py b/src/badger/gui/components/routine_runner.py index 63e874cd..bd8a01e3 100644 --- a/src/badger/gui/components/routine_runner.py +++ b/src/badger/gui/components/routine_runner.py @@ -22,12 +22,19 @@ MEASUREMENT_ACTION_TYPE, MEASUREMENT_ACTION_RETRY, MEASUREMENT_ACTION_ABORT, + TERMINATION_REACHED_TYPE, + TERMINATION_ACTION_TYPE, + TERMINATION_ACTION_CONTINUE, + TERMINATION_ACTION_END, ) from badger.tests.utils import get_current_vars from badger.routine import calculate_variable_bounds, calculate_initial_points from badger.settings import init_settings from badger.gui.components.process_manager import ProcessManager from badger.gui.windows.measurement_retry_dialog import BadgerMeasurementRetryDialog +from badger.gui.windows.termination_reached_dialog import ( + BadgerTerminationReachedDialog, +) from badger.routine import Routine logger = logging.getLogger(__name__) @@ -261,6 +268,17 @@ def check_queue(self) -> None: "action": action, } ) + elif ( + isinstance(msg, dict) + and msg.get("type") == TERMINATION_REACHED_TYPE + ): + action = self.handle_termination_reached(msg) + self.dialog_action_queue.put( + { + "type": TERMINATION_ACTION_TYPE, + "action": action, + } + ) else: error_title, error_traceback = msg BadgerError(error_title, error_traceback) @@ -281,6 +299,16 @@ def handle_measurement_error(self, msg: dict) -> str: return MEASUREMENT_ACTION_RETRY return MEASUREMENT_ACTION_ABORT + def handle_termination_reached(self, msg: dict) -> str: + dialog = BadgerTerminationReachedDialog( + tc_condition=msg.get("tc_condition"), + text=msg.get("title", "A termination condition has been reached."), + ) + result = dialog.exec_() + if result == QDialog.Accepted: + return TERMINATION_ACTION_CONTINUE + return TERMINATION_ACTION_END + def after_evaluate(self, results: pd.DataFrame) -> None: logger.debug("Received evaluation results from subprocess.") """ From 9434db81f563813497e44c6e69cd40ec5b53f8a4 Mon Sep 17 00:00:00 2001 From: michaellans Date: Wed, 12 Aug 2026 12:17:46 -0700 Subject: [PATCH 4/6] Add logic to skip tc dialog from stop button press, show if selected from menu --- src/badger/gui/components/action_bar.py | 36 +++++++++++++++++++---- src/badger/gui/mini/pages/home_page.py | 38 ++++++++++++++++--------- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/badger/gui/components/action_bar.py b/src/badger/gui/components/action_bar.py index 4a0b8c5f..fd56099c 100644 --- a/src/badger/gui/components/action_bar.py +++ b/src/badger/gui/components/action_bar.py @@ -89,7 +89,9 @@ class BadgerActionBar(QWidget): sig_start = pyqtSignal() - sig_start_until = pyqtSignal() + sig_start_until = pyqtSignal( + bool + ) # bool True launches termination condition dialog menu sig_stop = pyqtSignal() sig_delete_run = pyqtSignal() @@ -198,8 +200,14 @@ def load_internal_icon(name: str) -> QIcon: run_action.setIcon(self.icon_play) self.run_until_action = run_until_action = QAction("Run until", self) run_until_action.setIcon(self.icon_play) + self.run_until_menu_action = run_until_menu_action = QAction("Run until", self) + run_until_menu_action.setIcon(self.icon_play) menu.addAction(run_action) - menu.addAction(run_until_action) + menu.addAction(run_until_menu_action) + # Note: run_until_menu_action is triggered by selecting "run until" from the menu + # It emits sig_start_until(True) to launch the BadgerTerminationConditionDialog + # and sets the default run action to run_until_action. Pressing the play/stop button + # will then emit sig_start_until(False) and skip the dialog popup. # Set the menu as the run button's dropdown menu self.btn_stop.setMenu(menu) @@ -244,8 +252,11 @@ def config_logic(self): self.btn_opt.clicked.connect(self.jump_to_optimal) self.btn_set.clicked.connect(self.dial_in) self.btn_ctrl.clicked.connect(self.ctrl_routine) - self.run_action.triggered.connect(self.set_run_action) - self.run_until_action.triggered.connect(self.set_run_until_action) + self.run_action.triggered.connect(self._on_run_action_triggered) + self.run_until_action.triggered.connect(self._on_run_until_action_triggered) + self.run_until_menu_action.triggered.connect( + self._on_run_until_menu_action_triggered + ) self.save_checkpoint_action.triggered.connect( lambda: self.sig_save_checkpoint.emit() ) @@ -293,6 +304,8 @@ def routine_finished(self): self.run_action.setIcon(self.icon_play) self.run_until_action.setText("Run until") self.run_until_action.setIcon(self.icon_play) + self.run_until_menu_action.setText("Run until") + self.run_until_menu_action.setIcon(self.icon_play) # self.btn_stop.setToolTip('') self.btn_stop.setDisabled(False) @@ -320,6 +333,8 @@ def run_start(self): self.run_action.setIcon(self.icon_stop) self.run_until_action.setText("Stop") self.run_until_action.setIcon(self.icon_stop) + self.run_until_menu_action.setText("Stop") + self.run_until_menu_action.setIcon(self.icon_stop) self.btn_checkpoint.setDisabled(False) self.btn_ctrl.setDisabled(False) self.btn_set.setDisabled(True) @@ -335,16 +350,25 @@ def set_run_action(self): self.btn_stop.setDisabled(True) self.sig_stop.emit() - def set_run_until_action(self): + def set_run_until_action(self, from_menu=False): if self.btn_stop.defaultAction() is not self.run_until_action: self.btn_stop.setDefaultAction(self.run_until_action) if self.run_until_action.text() == "Run until": - self.sig_start_until.emit() + self.sig_start_until.emit(from_menu) else: self.btn_stop.setDisabled(True) self.sig_stop.emit() + def _on_run_action_triggered(self): + self.set_run_action() + + def _on_run_until_action_triggered(self): + self.set_run_until_action(from_menu=False) + + def _on_run_until_menu_action_triggered(self): + self.set_run_until_action(from_menu=True) + def delete_run(self): self.sig_delete_run.emit() diff --git a/src/badger/gui/mini/pages/home_page.py b/src/badger/gui/mini/pages/home_page.py index 629168e1..7d8acc52 100644 --- a/src/badger/gui/mini/pages/home_page.py +++ b/src/badger/gui/mini/pages/home_page.py @@ -485,20 +485,32 @@ def start_run(self, use_termination_condition: bool = False): init_points_flag=True, ) - def start_run_until(self): + def start_run_until(self, dialog: bool = True): + """ + Starts run with termination condition. + + Args: + dialog (bool): If True, opens dialog popup for selecting termination condition. + If False skips dialog and uses the tc_config which + was previously saved in the run_monitor. + + Notes: If no tc_config is found, opens popup regardless of dialog flag. + """ logger.info("Starting run until condition met.") - dlg = BadgerTerminationConditionDialog( - self, - self.start_run, - self.run_monitor.save_termination_condition, - self.run_monitor.termination_condition, - ) - self.tc_dialog = dlg - try: - dlg.exec() - finally: - self.tc_dialog = None - # self.run_monitor.start_until() + if dialog or not self.run_monitor.termination_condition: + dlg = BadgerTerminationConditionDialog( + self, + self.start_run, + self.run_monitor.save_termination_condition, + self.run_monitor.termination_condition, + ) + self.tc_dialog = dlg + try: + dlg.exec() + finally: + self.tc_dialog = None + else: + self.start_run(use_termination_condition=True) def new_run(self): logger.info("Creating new run.") From cc1d5e473048165ada5b2c814bbc18a5f2fcea74 Mon Sep 17 00:00:00 2001 From: michaellans Date: Wed, 12 Aug 2026 17:00:27 -0700 Subject: [PATCH 5/6] Update tooltips for stop/run button to indicate termination condition --- src/badger/gui/components/action_bar.py | 57 +++++++++++++++++++++++-- src/badger/gui/mini/pages/home_page.py | 9 ++-- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/src/badger/gui/components/action_bar.py b/src/badger/gui/components/action_bar.py index fd56099c..63f5e0b3 100644 --- a/src/badger/gui/components/action_bar.py +++ b/src/badger/gui/components/action_bar.py @@ -1,14 +1,48 @@ """Toolbar with run-control buttons (start, pause, stop), logbook submission, docs access, and the extensions palette launcher.""" -from PyQt5.QtWidgets import QWidget, QHBoxLayout +from PyQt5.QtWidgets import QStyle, QStyleOptionToolButton, QWidget, QHBoxLayout from PyQt5.QtWidgets import QToolButton, QMenu, QAction from PyQt5.QtGui import QIcon, QFont -from PyQt5.QtCore import pyqtSignal, QSize +from PyQt5.QtCore import QEvent, pyqtSignal, QSize from importlib import resources from badger.gui.utils import create_button from badger.gui.windows.docs_window import BadgerDocsWindow + +class SplitTooltipToolButton(QToolButton): + """ + QToolButton that shows a separate tooltip over the dropdown-arrow area. + Use arg menu_tooltip="desired tooltip" to set the menu tooltip + """ + + def __init__(self, menu_tooltip="", parent=None): + """ + Parameters + ---------- + menu_tooltip (str) + tooltip for menu + """ + super().__init__(parent) + self.menu_tooltip = menu_tooltip + + def _over_menu_arrow(self, pos): + opt = QStyleOptionToolButton() + self.initStyleOption(opt) + rect = self.style().subControlRect( + QStyle.CC_ToolButton, opt, QStyle.SC_ToolButtonMenu, self + ) + return rect.contains(pos) + + def event(self, event): + if event.type() == QEvent.ToolTip and self._over_menu_arrow(event.pos()): + from PyQt5.QtWidgets import QToolTip + + QToolTip.showText(event.globalPos(), self.menu_tooltip, self) + return True + return super().event(event) + + stylesheet_del = """ QPushButton:hover:pressed { @@ -162,7 +196,9 @@ def load_internal_icon(name: str) -> QIcon: self.btn_ctrl.setDisabled(True) # self.btn_stop = btn_stop = QPushButton('Run') - self.btn_stop = QToolButton() + self.btn_stop = SplitTooltipToolButton( + menu_tooltip="Update Termination Condition" + ) self.btn_stop.setFixedSize(96, 32) self.btn_stop.setFont(cool_font) self.btn_stop.setStyleSheet(stylesheet_run) @@ -214,7 +250,7 @@ def load_internal_icon(name: str) -> QIcon: self.btn_stop.setDefaultAction(run_action) self.btn_stop.setPopupMode(QToolButton.MenuButtonPopup) self.btn_stop.setDisabled(False) - # btn_stop.setToolTip('') + run_action.setToolTip("Run") # Config button self.btn_config = btn_config = create_button("tools.png", "Configure run") @@ -406,3 +442,16 @@ def open_extensions_palette(self): def env_ready(self): self.btn_log.setDisabled(False) self.btn_opt.setDisabled(False) + + def update_run_tooltip(self, tc=None): + """Update btn_stop tooltip: tc dict for run-until mode, or None.""" + if tc is None: + self.run_action.setToolTip("Run") + else: + tc_idx = tc.get("tc_idx", 0) + if tc_idx == 0: + tip = f"Run until: n iterations = {tc.get('max_eval')}" + elif tc_idx == 1: + tip = f"Run until: timeout = {tc.get('max_time')}s" + self.run_until_action.setToolTip(tip) + self.run_until_menu_action.setToolTip(tip) diff --git a/src/badger/gui/mini/pages/home_page.py b/src/badger/gui/mini/pages/home_page.py index 7d8acc52..1a8ded55 100644 --- a/src/badger/gui/mini/pages/home_page.py +++ b/src/badger/gui/mini/pages/home_page.py @@ -291,9 +291,9 @@ def _configure_default_run_action(self): self.run_action_bar.run_until_action ) # configure default to max_eval (tc_idx=0), 50 iterations - self.run_monitor.save_termination_condition( - {"tc_idx": 0, "max_eval": 50, "max_time": 300, "ftol": 0} - ) + initial_tc = {"tc_idx": 0, "max_eval": 50, "max_time": 300, "ftol": 0} + self.run_monitor.save_termination_condition(initial_tc) + self.run_action_bar.update_run_tooltip(initial_tc) def update_saved_values_from_monitor(self): """ @@ -509,6 +509,9 @@ def start_run_until(self, dialog: bool = True): dlg.exec() finally: self.tc_dialog = None + self.run_action_bar.update_run_tooltip( + self.run_monitor.termination_condition + ) else: self.start_run(use_termination_condition=True) From 7cc2cedb95734ae9118208f2d5a37bb372591f9e Mon Sep 17 00:00:00 2001 From: michaellans Date: Wed, 12 Aug 2026 18:25:38 -0700 Subject: [PATCH 6/6] update status when routine paused, including termination_condition --- src/badger/gui/components/routine_runner.py | 24 +++++++++++++++++++-- src/badger/gui/components/run_monitor.py | 1 + 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/badger/gui/components/routine_runner.py b/src/badger/gui/components/routine_runner.py index bd8a01e3..968cb5cf 100644 --- a/src/badger/gui/components/routine_runner.py +++ b/src/badger/gui/components/routine_runner.py @@ -47,6 +47,7 @@ class BadgerRoutineSignals(QObject): error = pyqtSignal(Exception) info = pyqtSignal(str) states = pyqtSignal(str) + sig_status = pyqtSignal(str) # status message information class BadgerRoutineSubprocess: @@ -300,15 +301,32 @@ def handle_measurement_error(self, msg: dict) -> str: return MEASUREMENT_ACTION_ABORT def handle_termination_reached(self, msg: dict) -> str: + # update status + tc_condition = msg.get("tc_condition") + status_str = self._format_tc_status_str(tc_condition) + self.signals.sig_status.emit(status_str) + + # launch dialog dialog = BadgerTerminationReachedDialog( - tc_condition=msg.get("tc_condition"), - text=msg.get("title", "A termination condition has been reached."), + tc_condition=tc_condition, + text=msg.get("title"), ) result = dialog.exec_() if result == QDialog.Accepted: + self.signals.sig_status.emit(f"Running routine {self.routine.name}...") return TERMINATION_ACTION_CONTINUE return TERMINATION_ACTION_END + def _format_tc_status_str(self, tc_condition: dict) -> str: + tc_type = tc_condition["type"] + if tc_type == "max_eval": + tc_type_text = "N iterations" + state = tc_condition["state"] + else: + tc_type_text = "timeout" + state = f"{tc_condition['state']:.2f} s" + return f"Routine {self.routine.name} paused: Condition {tc_type_text} = {state} reached" + def after_evaluate(self, results: pd.DataFrame) -> None: logger.debug("Received evaluation results from subprocess.") """ @@ -357,8 +375,10 @@ def ctrl_routine(self, pause: bool) -> None: pause : bool """ if pause: + self.signals.sig_status.emit(f"Routine {self.routine.name} paused") self.pause_event.clear() else: + self.signals.sig_status.emit(f"Running routine {self.routine.name}...") self.pause_event.set() def close(self) -> None: diff --git a/src/badger/gui/components/run_monitor.py b/src/badger/gui/components/run_monitor.py index b5e2e7bf..bfba360f 100644 --- a/src/badger/gui/components/run_monitor.py +++ b/src/badger/gui/components/run_monitor.py @@ -454,6 +454,7 @@ def init_routine_runner(self): routine_runner.signals.error.connect(self.on_error) routine_runner.signals.info.connect(self.on_info) routine_runner.signals.states.connect(self.states) + routine_runner.signals.sig_status.connect(self.sig_status.emit) self.sig_pause.connect(routine_runner.ctrl_routine) self.sig_stop.connect(routine_runner.stop_routine)