diff --git a/src/badger/gui/components/routine_page.py b/src/badger/gui/components/routine_page.py index 1ad3810a..7d065139 100644 --- a/src/badger/gui/components/routine_page.py +++ b/src/badger/gui/components/routine_page.py @@ -49,6 +49,7 @@ from pydantic import ValidationError from PyQt5.QtCore import Qt, QTimer, pyqtSignal from PyQt5.QtWidgets import ( + QApplication, QFileDialog, QHBoxLayout, QLabel, @@ -99,6 +100,7 @@ ) from badger.gui.windows.lim_vrange_dialog import BadgerLimitVariableRangeDialog from badger.gui.windows.message_dialog import BadgerScrollableMessageBox +from badger.gui.utils import with_busy_cursor from badger.gui.windows.review_dialog import BadgerReviewDialog from badger.routine import Routine from badger.settings import init_settings @@ -156,6 +158,7 @@ class BadgerRoutinePage(QWidget): sig_updated = pyqtSignal(str, str) # routine name, routine description sig_load_template = pyqtSignal(str) # template path sig_save_template = pyqtSignal(str) # template path + sig_status = pyqtSignal(str) def __init__(self) -> None: logger.info("Initializing BadgerRoutinePage.") @@ -1119,8 +1122,16 @@ def refresh_params_generator(self): except Exception as e: QMessageBox.warning(self, "Invalid script!", str(e)) + @with_busy_cursor def select_env(self, i: int): logger.info(f"Environment selected: {self.env_box.cb.itemText(i)} (index={i})") + + self.sig_status.emit("Loading variables...") + # We need this for the text to actually get drawn in the GUI at the time we want, + # since select_env() is a slot function so the Qt main event loop is paused while it runs + # and the text drawing won't be processed immediately. + QApplication.processEvents() + # Reset the initial table actions and ratio var ranges self.init_table_actions = [] self.ratio_var_ranges = {} @@ -1246,6 +1257,9 @@ def select_env(self, i: int): # Update the docs self.window_env_docs.update_docs(env.name, "environment") + self.sig_status.emit(f"Badger Environment '{env.name}' loaded") + QApplication.processEvents() + def get_init_table_header(self): table = self.env_box.init_table header_list = [] diff --git a/src/badger/gui/mini/pages/home_page.py b/src/badger/gui/mini/pages/home_page.py index 1b07473b..25a573b7 100644 --- a/src/badger/gui/mini/pages/home_page.py +++ b/src/badger/gui/mini/pages/home_page.py @@ -230,6 +230,7 @@ def config_logic(self): self.routine_editor.sig_load_template.connect(self.update_status) self.routine_editor.sig_save_template.connect(self.update_status) self.routine_editor.sig_go_run.connect(self.go_run) + self.routine_editor.sig_status.connect(self.update_status) self.run_monitor.sig_inspect.connect(self.inspect_solution) self.run_monitor.sig_lock.connect(self.toggle_lock) diff --git a/src/badger/gui/mini/pages/routine_page.py b/src/badger/gui/mini/pages/routine_page.py index 5c2e9a23..bc381035 100644 --- a/src/badger/gui/mini/pages/routine_page.py +++ b/src/badger/gui/mini/pages/routine_page.py @@ -23,6 +23,7 @@ from PyQt5.QtWidgets import QMessageBox, QWidget, QTabWidget from PyQt5.QtWidgets import QVBoxLayout, QScrollArea from PyQt5.QtWidgets import QTableWidgetItem, QPlainTextEdit +from PyQt5.QtWidgets import QApplication from badger.gui.components.navigators import HistoryNavigator from coolname import generate_slug from xopt import VOCS @@ -58,7 +59,7 @@ from badger.gui.windows.review_dialog import BadgerReviewDialog from badger.gui.windows.add_random_dialog import BadgerAddRandomDialog from badger.gui.windows.message_dialog import BadgerScrollableMessageBox -from badger.gui.utils import filter_generator_config +from badger.gui.utils import filter_generator_config, with_busy_cursor from badger.environment import instantiate_env from badger.errors import ( BadgerEnvNotFoundError, @@ -126,6 +127,7 @@ class BadgerRoutinePage(QWidget): sig_save_template = pyqtSignal(str) # template path sig_go_run = pyqtSignal() sig_select_env = pyqtSignal(str) + sig_status = pyqtSignal(str) def __init__(self): logger.info("Initializing BadgerRoutinePage.") @@ -1067,8 +1069,16 @@ def refresh_params_generator(self): except Exception as e: QMessageBox.warning(self, "Invalid script!", str(e)) + @with_busy_cursor def select_env(self, i: int): logger.info(f"Environment selected: {self.env_box.env_name} (index={i})") + + self.sig_status.emit("Loading variables...") + # We need this for the text to actually get drawn in the GUI at the time we want, + # since select_env() is a slot function so the Qt main event loop is paused while it runs + # and the text drawing won't be processed immediately. + QApplication.processEvents() + # Reset the initial table actions and ratio var ranges self.init_table_actions = [] self.ratio_var_ranges = {} @@ -1186,6 +1196,9 @@ def select_env(self, i: int): self.check_for_nan_vars() + self.sig_status.emit(f"Badger Environment '{env.name}' loaded") + QApplication.processEvents() + def check_for_nan_vars(self) -> None: """Show a warning popup to notify user if any var_table values are NaN""" nan_value_keys = self.env_box.get_nan_vars() diff --git a/src/badger/gui/pages/home_page.py b/src/badger/gui/pages/home_page.py index 64a3644f..bfa3e0b4 100644 --- a/src/badger/gui/pages/home_page.py +++ b/src/badger/gui/pages/home_page.py @@ -241,6 +241,7 @@ def config_logic(self) -> None: self.routine_editor.sig_load_template.connect(self.update_status) self.routine_editor.sig_save_template.connect(self.update_status) + self.routine_editor.sig_status.connect(self.update_status) self.run_monitor.sig_inspect.connect(self.inspect_solution) self.run_monitor.sig_lock.connect(self.toggle_lock) diff --git a/src/badger/gui/utils.py b/src/badger/gui/utils.py index 65859252..afe66894 100644 --- a/src/badger/gui/utils.py +++ b/src/badger/gui/utils.py @@ -6,7 +6,8 @@ import logging import os from importlib import resources -from typing import Any +from typing import Any, Callable +from functools import wraps from PyQt5.QtCore import QEvent, QObject, QSize, Qt from PyQt5.QtGui import QIcon @@ -18,6 +19,7 @@ QPushButton, QToolButton, QVBoxLayout, + QApplication, ) from badger.errors import BadgerConfigError @@ -169,3 +171,23 @@ def __init__(self, parent=None, info=None): self.setLayout(layout) # Semi-transparent background self.setStyleSheet("background-color: rgba(0, 0, 0, 80);") + + +def set_busy_cursor() -> None: + QApplication.setOverrideCursor(Qt.BusyCursor) + + +def unset_busy_cursor() -> None: + QApplication.restoreOverrideCursor() + + +def with_busy_cursor(func: Callable) -> Callable: + @wraps(func) + def wrapped(*args, **kwargs): + set_busy_cursor() + try: + return func(*args, **kwargs) + finally: + unset_busy_cursor() + + return wrapped diff --git a/src/badger/gui/windows/expandable_message_box.py b/src/badger/gui/windows/expandable_message_box.py index cc887257..52b34eeb 100644 --- a/src/badger/gui/windows/expandable_message_box.py +++ b/src/badger/gui/windows/expandable_message_box.py @@ -12,6 +12,7 @@ ) from PyQt5.QtGui import QTextOption, QFont, QFontDatabase from PyQt5.QtCore import Qt +from badger.gui.utils import unset_busy_cursor class ExpandableMessageBox(QDialog): @@ -21,6 +22,8 @@ def __init__( super().__init__(parent) self.setWindowFlags(self.windowFlags() | Qt.WindowMaximizeButtonHint) + unset_busy_cursor() + # Main layout mainLayout = QVBoxLayout(self)