import os
import sys
import schrodinger
from schrodinger.infra import jobhub
# Appframework2 modules
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
from schrodinger.ui.qt import config_dialog
from schrodinger.ui.qt import swidgets
from schrodinger.ui.qt import table_helper
from schrodinger.ui.qt.appframework2 import settings
from schrodinger.ui.qt.appframework2 import taskwidgets
from schrodinger.ui.qt.config_dialog import DISP_NAMES
from schrodinger.ui.qt.standard import constants
maestro = schrodinger.get_maestro()
#===============================================================================
# Constants
#===============================================================================
CONFIG_DIALOG_HELP = 'MM_TOPIC_JOB_START_DIALOG'
#===============================================================================
# Sub-widgets
#===============================================================================
#===============================================================================
# Job Bar
#===============================================================================
[docs]class JobBar(taskwidgets.TaskBar):
"""
A re-implementation of the standard Schrodinger job bar. To use, simply
instantiate and connect a job runner.
:cvar SETTINGS_BUTTON_CLASS: the class used for the settings ("gear") button
:vartype SETTINGS_BUTTON_CLASS: :class:JobSettingsButton
"""
SETTINGS_BUTTON_CLASS = JobSettingsButton
[docs] def __init__(self,
runner=None,
label_text='Job name:',
button_text='Run',
task_reset=True):
taskwidgets.TaskBar.__init__(self,
runner,
label_text,
button_text,
task_reset=task_reset)
[docs] def setup(self):
taskwidgets.TaskBar.setup(self)
self.settings_btn = self.SETTINGS_BUTTON_CLASS(
task_reset=self.task_reset)
self.spinner = JobMonitorButton()
self.toolbar = QtWidgets.QToolBar()
[docs] def layOut(self):
taskwidgets.TaskBar.layOut(self)
self.main_layout.addWidget(self.toolbar)
if sys.platform == "darwin":
style = self.styleSheet()
self.setStyleSheet(style +
"QToolBar{background: none; border: 0px;}")
self.toolbar.addWidget(self.name_lbl)
self.toolbar.addWidget(self.name_le)
self.toolbar.addWidget(self.settings_btn)
self.toolbar.addWidget(self.spinner)
self.main_layout.addWidget(self.start_btn)
[docs] def onStartPressed(self):
if not self.settings_btn.validateAndApplyConfig():
return False
taskwidgets.TaskBar.onStartPressed(self)
[docs]class MiniJobBar(JobBar):
[docs] def setup(self):
JobBar.setup(self)
self.main_layout = QtWidgets.QVBoxLayout()
[docs] def layOut(self):
taskwidgets.TaskUIWidget.layOut(self)
self.main_layout.addWidget(self.name_lbl)
self.run_layout = QtWidgets.QHBoxLayout()
self.main_layout.addLayout(self.run_layout)
self.run_layout.addWidget(self.name_le)
self.run_layout.addWidget(self.start_btn)
self.line = QtWidgets.QFrame()
self.line.setFrameShape(QtWidgets.QFrame.HLine)
self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.main_layout.addWidget(self.line)
self.bottom_layout = QtWidgets.QHBoxLayout()
self.bottom_layout.addStretch()
self.bottom_layout.addWidget(self.settings_btn)
self.bottom_layout.addWidget(self.spinner)
self.main_layout.addLayout(self.bottom_layout)
self.main_layout.setSpacing(3)
self.bottom_layout.setSpacing(0)
self.run_layout.setSpacing(0)
#===============================================================================
# Job Table (Mini monitor)
#===============================================================================
IGNORE_STATUSES = set(
["finished", "killed", "completed", "incorporated", 'written'])
FAILED_STATUSES = set(["died", "failed"])
[docs]class JobTableColumns(taskwidgets.TaskTableColumns):
HEADERS = ['Job Name', 'Status']
[docs]class JobTableModel(taskwidgets.TaskTableModel):
COLUMN = JobTableColumns
@table_helper.data_method(QtCore.Qt.BackgroundRole)
def _backgroundColor(self, col, task, role):
# Status job attribute, except for completed jobs, for which
# ExitStatus attribute is returned.
jobstatus = task.status()
status_enum = jobhub.get_status_string_to_enum(jobstatus)
color = jobhub.get_status_color_as_hex(status_enum)
return QtGui.QColor(color)
[docs]class FilteredJobTableModel(QtCore.QSortFilterProxyModel):
"""
A simple proxy model to filter out completed jobs, which should not appear
in the mini monitor
"""
[docs] def __init__(self, parent=None):
super(FilteredJobTableModel, self).__init__(parent)
# maintain Qt4 dynamicSortFilter default
self.setDynamicSortFilter(False)
[docs] def filterAcceptsRow(self, source_row, source_parent):
index = self.sourceModel().index(source_row, JobTableColumns.STATUS,
source_parent)
return self.sourceModel().data(index) not in IGNORE_STATUSES
#=========================================================================
# Basic ConfigDialog Class
#=========================================================================
CDSuper = settings.BaseOptionsPanel
[docs]class ConfigDialog(CDSuper):
"""
A re-implementation of the Schrodinger Job Settings dialog.
"""
[docs] def __init__(self, jobrunner):
self.runner = jobrunner
self.options = jobrunner.jobOptions()
self.requested_action = config_dialog.RequestedAction.DoNothing
CDSuper.__init__(self)
[docs] def setPanelOptions(self):
CDSuper.setPanelOptions(self)
self.title = 'Job Settings'
self.help_topic = CONFIG_DIALOG_HELP
[docs] def setup(self):
CDSuper.setup(self)
self.setupOutputGroup()
self.setupJobGroup()
[docs] def setupOutputGroup(self):
if not self.options.incorporation:
self.output_grp = None
return
self.output_grp = QtWidgets.QGroupBox('Output')
self.output_layout = QtWidgets.QGridLayout()
layout = self.output_layout
self.output_grp.setLayout(layout)
layout.addWidget(QtWidgets.QLabel('Incorporate:'))
self.incorporation_selector = IncorporationSelector()
self.setAlias('disp', self.incorporation_selector)
layout.addWidget(self.incorporation_selector, 0, 1)
layout.addItem(
QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Expanding), 0,
2)
[docs] def setupJobGroup(self):
self.job_grp = QtWidgets.QGroupBox('Job')
self.job_layout = QtWidgets.QGridLayout()
layout = self.job_layout
self.job_grp.setLayout(self.job_layout)
self.jobname_le = taskwidgets.TaskNameLineEdit(self.runner)
self.jobname_le.textChanged.disconnect(self.jobname_le.onTextChanged)
self.setAlias('jobname', self.jobname_le)
layout.addWidget(QtWidgets.QLabel('Name:'), 0, 0)
layout.addWidget(self.jobname_le, 0, 1, 1, 2)
self.host_selector = HostSelector()
self.host_selector.loadHosts(exclude_gpus=not self.options.gpus)
cpus_layout = QtWidgets.QHBoxLayout()
cpus_layout.addWidget(QtWidgets.QLabel('Total:'))
self.cpus_sb = QtWidgets.QSpinBox()
self.cpus_sb.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
cpus_layout.addWidget(self.cpus_sb)
self.cpus_units_lbl = QtWidgets.QLabel('')
cpus_layout.addWidget(self.cpus_units_lbl)
self.onHostChanged()
if self.options.host:
self.setAlias('host', self.host_selector, True)
layout.addWidget(QtWidgets.QLabel('Host:'), 1, 0)
layout.addWidget(self.host_selector, 1, 1)
self.host_selector.currentIndexChanged.connect(self.onHostChanged)
if self.options.cpus:
self.setAlias('cpus', self.cpus_sb, True)
layout.addLayout(cpus_layout, 1, 2)
[docs] def layOut(self):
CDSuper.layOut(self)
if self.output_grp:
self.main_layout.addWidget(self.output_grp)
if self.job_grp:
self.main_layout.addWidget(self.job_grp)
[docs] def run(self):
self.getConfigFromRunner()
saved_config = self.runner.getNextConfig()
accept = self._run() # separate method for ease of testing
if accept:
self.applyConfigToRunner()
else:
# Revert job config if user cancels
self.runner.setConfig(saved_config)
def _run(self):
# This single line has its own method so it can easily be mocked out
return CDSuper.run(self)
[docs] def getConfigFromRunner(self):
config = self.runner.getNextConfig()
settings = {}
for alias in self.settings_aliases:
settings[alias] = getattr(config, alias)
self.applyAliasedSettings(settings)
[docs] def applyConfigToRunner(self):
settings = self.getAliasedSettings()
config = self.runner.getNextConfig()
config.applySettings(settings)
self.runner.setConfig(config)
[docs] def onHostChanged(self):
host = self.host_selector.currentHost()
self.cpus_units_lbl.setText(host.units())
self.cpus_sb.setRange(1, host.maxNum())
[docs] def getPersistenceKey(self, alias):
"""
Overrides the parent method so that settings keys are based on the
job runner, not the config dialog class (which would result in multiple
job runners sharing the same job settings). See parent class for more
information.
"""
module = self.runner.__module__
classname = self.runner.__class__.__name__
key = '%s-%s-configdialog-%s' % (module, classname, alias)
return key
#=========================================================================
# Config Dialog Widgets
#=========================================================================
[docs]class HostSelector(QtWidgets.QComboBox):
[docs] def loadHosts(self, hosts=None, exclude_gpus=True):
self.clear()
if hosts is None:
hosts = config_dialog.get_hosts(excludeGPGPUs=exclude_gpus)
for host in hosts:
self.addItem(host.label())
self.hosts = hosts
[docs] def currentHost(self):
host = self.hosts[self.currentIndex()]
return host
[docs] def setCurrentHost(self, host):
index = self.hosts.index(host)
self.setCurrentIndex(index)
[docs] def af2SettingsGetValue(self):
return self.currentHost().name
[docs] def af2SettingsSetValue(self, name):
for host in self.hosts:
if host.name == name:
break
else:
self.setCurrentIndex(0)
return
self.setCurrentHost(host)
[docs]class IncorporationSelector(QtWidgets.QComboBox):
[docs] def __init__(self):
QtWidgets.QComboBox.__init__(self)
for key, name in DISP_NAMES.items():
self.addItem(name, key)
[docs] def af2SettingsGetValue(self):
return self.itemData(self.currentIndex())
[docs] def af2SettingsSetValue(self, disp):
index = self.findData(disp)
if index == -1:
raise ValueError('Disposition not found: %s' % disp)
self.setCurrentIndex(index)
if __name__ == "__main__":
cd = ConfigDialog()
cd.run()