Source code for schrodinger.ui.qt.jobwidgets

"""
This module provides job widgets compatible with all Schrodinger python
application frameworks.

Copyright Schrodinger, LLC. All rights reserved.
"""
from typing import Optional

from schrodinger import get_maestro
from schrodinger.application.job_monitor import job_monitor_gui
from schrodinger.infra import jobhub
from schrodinger.models import mappers
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
from schrodinger.tasks import taskmanager
from schrodinger.ui.qt.standard.icons import icons
from schrodinger.ui.qt.standard_widgets import buttons
from schrodinger.utils import mmutil

maestro = get_maestro()

SPINNER_ICON_BASE = ":/schrodinger/ui/qt/icons_dir/spinner/"
MAX_SPINNER_ICON_NUM = 9

JobIconState = jobhub.ProjectLatestJobIconTracker.JobIconState


[docs]class JobStatusButton(mappers.TargetMixin, buttons.FlatButton): """ A button with a spinner icon that spins upon the parent widget launching a job. It continues to spin for up to 30 seconds after the job has started, or until the job finishes or fails. Failures result in an error icon. Clicking this tool button will launch the job monitor panel. This button also implements `targetSetValue` to support mapping to a task or taskmanager. Note: This button will not show/hide properly if added to a QToolBar since this is not a QAction. It works well inside a layout. """
[docs] def __init__(self, parent: Optional[QtWidgets.QWidget] = None, viewname: Optional[str] = None): """ :ivar viewname: View name to use for job launching and monitoring. If not specified, use setViewname() to set it at a later time. """ super().__init__(parent=parent) self.setToolTip('Click to display the job monitor') self._updateVisibility() self._setUpSpinnerIcons() self._icon_state_tracker = None self._viewname = None if viewname: self.setViewname(viewname) self.pressed.connect(self._showJobMonitor)
def _setUpSpinnerIcons(self): self._icons = [ QtGui.QIcon(f'{SPINNER_ICON_BASE}{num}.png') for num in range(1, MAX_SPINNER_ICON_NUM) ] self._standard_icon = QtGui.QIcon(f'{SPINNER_ICON_BASE}0.png') self._error_icon = QtGui.QIcon(icons.EXCLAMATION_ERROR_LB) self._cur_icon_idx = 0 self._num_icons = len(self._icons) self.setIcon(self._standard_icon) self._spin_timer = QtCore.QTimer(self) self._spin_timer.timeout.connect(self._advanceSpinner) def _setUpIconStateTracker(self): """ Set up the icon state tracker. Called whenever the viewname is set. """ if self._icon_state_tracker is not None: self._icon_state_tracker.jobIconStateChanged.disconnect( self._updateSpinnerIconState) if self._viewname is None: self._icon_state_tracker = None return self._icon_state_tracker = jobhub.ProjectLatestJobIconTracker( self, jobhub.get_job_manager(), self._viewname) self._icon_state_tracker.jobIconStateChanged.connect( self._updateSpinnerIconState) self._icon_state_tracker.jobIconStateChanged.connect( self._updateVisibility) # ========================================================================== # Public API # ==========================================================================
[docs] def setViewname(self, viewname: Optional[str] = None): """ Set the viewname and instantiate the job icon state tracker. """ old_viewname = self._viewname self._viewname = viewname if old_viewname != viewname and viewname is not None: self._setUpIconStateTracker()
# ========================================================================== # TargetMixin methods # ==========================================================================
[docs] def targetSetValue(self, task_or_taskman): if isinstance(task_or_taskman, taskmanager.TaskManager): task = task_or_taskman.nextTask() else: task = task_or_taskman self.setViewname(task.job_config.viewname)
# ========================================================================== # Slots # ========================================================================== def _updateSpinnerIconState(self, icon_state: JobIconState): """ Called when the icon state tracker emits jobIconStateChanged. :param icon_state: The current icon state. """ if icon_state == JobIconState.Active: self._startSpinner() return self._stopSpinner() if icon_state == JobIconState.Fail: self.setIcon(self._error_icon) def _updateVisibility(self, icon_state: Optional[JobIconState] = None): """ Update this button's visibility when the job icon state changes. """ visible = icon_state in (JobIconState.Active, JobIconState.Fail) self.setVisible(visible) def _showJobMonitor(self): """ Show the correct job monitor and display the current job if a job is currently being tracked. Will not display anything outside of maestro if JOB_SERVER is off. Called when this button is pressed. """ if self._icon_state_tracker is None: return job_id = self._icon_state_tracker.getJobId() if mmutil.feature_flag_is_enabled(mmutil.JOB_SERVER): # Show new job monitor job_monitor = job_monitor_gui.panel() if job_id: job_monitor.selectJob(job_id) elif maestro: # Show legacy job monitor only in maestro maestro.command('showpanel monitor') if job_id: maestro.command(f'energymonitor {job_id}') # ========================================================================== # Private implementation methods # ========================================================================== def _startSpinner(self): if not self._spin_timer.isActive(): self._spin_timer.start(250) def _stopSpinner(self): if self._spin_timer.isActive(): self._spin_timer.stop() self.setIcon(self._standard_icon) def _advanceSpinner(self): self._cur_icon_idx = (self._cur_icon_idx + 1) % self._num_icons self.setIcon(self._icons[self._cur_icon_idx])