import textwrap
from enum import Enum
import schrodinger
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
from schrodinger.structutils import analyze
from schrodinger.trajectory.trajectory_gui_dir.display_settings_data import \
MAX_RADIUS
from schrodinger.trajectory.trajectory_gui_dir.display_settings_data import \
MIN_RADIUS
from schrodinger.trajectory.trajectory_gui_dir.display_settings_data import \
DisplayOnly
from schrodinger.trajectory.trajectory_gui_dir import playback_settings_data
from schrodinger.ui import maestro_ui
from schrodinger.ui.qt import atomselector
from . import advanced_settings_ui
from . import stylesheet
maestro = schrodinger.get_maestro()
WARNING_THRESHOLD_ATOMS = 1000000
ERROR_THRESHOLD_ATOMS = 5000000
[docs]class MessageType(Enum):
"""
Holds enums for error, warning, no issue cases
"""
NONE = 'none'
ERROR = 'error'
WARNING = 'warning'
[docs]class AdvancedSettings(QtWidgets.QDialog):
"""
Advanced settings class for trajectory viewer
:cvar advancedSettingsDismissed(): A signal emitted after dismissing
AdvancedSettings dialog.
:vartype advancedSettingsDismissed: `QtCore.pyqtSignal`
:cvar advancedSettingsChanged(): A signal emitted when advanced settings are
changed.
:vartype advancedSettingsChanged: `QtCore.pyqtSignal`
"""
advancedSettingsDismissed = QtCore.pyqtSignal()
advancedSettingsChanged = QtCore.pyqtSignal()
[docs] def __init__(self, hide_hideatoms_related_settings, parent=None):
"""
:param hide_hideatoms_related_settings: A flag indicating whether to
hide/show hide atoms related settings.
:type hide_hideatoms_related_settings: bool
:param parent: The Qt parent
:type parent: QWidget
"""
super(QtWidgets.QDialog, self).__init__(parent)
self.ui = advanced_settings_ui.Ui_Dialog()
self.ui.setupUi(self)
self.setStyleSheet(stylesheet.ADVANCED_SETTINGS_STYLESHEET)
self.ui.cancel_pushbutton.clicked.connect(self.cancelClicked)
self.ui.ok_pushbutton.clicked.connect(self.okClicked)
self.ui.help_button.clicked.connect(self.help)
self.atom_selector = atomselector.AtomSelector(self,
show_pick=False,
show_selection=False,
show_plus=True)
self.atom_selector.main_layout.setContentsMargins(2, 2, 2, 2)
layout = self.ui.atomselector_widget.layout()
layout.insertWidget(0, self.atom_selector)
self.atom_selector.atomSelectionDialogDismissed.connect(self.show)
self.atom_selector.atomSelectionDialogAboutToBeShown.connect(self.hide)
self.ui.current_radiobutton.setChecked(True)
self.ui.display_only_checkbox.setChecked(False)
self.ui.display_only_frame.setEnabled(False)
self.ui.display_only_checkbox.toggled.connect(
self.ui.display_only_frame.setEnabled)
self.ui.inc_lens_checkbox.setEnabled(False)
self.ui.show_sim_checkbox.toggled.connect(
self.ui.inc_lens_checkbox.setEnabled)
self.ui.display_only_checkbox.toggled.connect(
self.updateBidningSiteRadius)
self.ui.current_radiobutton.toggled.connect(
self.updateBidningSiteRadius)
self.ui.display_only_checkbox.toggled.connect(
self.updateAtomSelectorWidget)
self.ui.matching_radiobutton.toggled.connect(
self.updateAtomSelectorWidget)
# Set range for replicate spinboxes
replicate_spinboxes = ('x_spinbox', 'y_spinbox', 'z_spinbox')
for spinbox in replicate_spinboxes:
attr = getattr(self.ui, spinbox)
attr.setRange(
playback_settings_data.PlaybackSettingsData.MIN_REPLICATE,
playback_settings_data.PlaybackSettingsData.MAX_REPLICATE)
attr.valueChanged.connect(self.enableOK)
# Set range for radius
self.ui.radius_spinbox.setRange(MIN_RADIUS, MAX_RADIUS)
# Enable 'OK' button when there is any change
spinboxes = ('radius_spinbox',) + replicate_spinboxes
for spinbox in spinboxes:
attr = getattr(self.ui, spinbox)
attr.valueChanged.connect(self.enableOK)
abstractbuttons = ('display_only_checkbox', 'current_radiobutton',
'matching_radiobutton', 'sec_struct_checkbox',
'show_sim_checkbox', 'load_sel_pushbutton',
'inc_lens_checkbox')
for abstractbutton in abstractbuttons:
attr = getattr(self.ui, abstractbutton)
attr.clicked.connect(self.enableOK)
self.atom_selector.aslTextModified.connect(self.enableOK)
self.ui.load_sel_pushbutton.clicked.connect(
self.atom_selector.loadWorkspaceSelection)
if hide_hideatoms_related_settings:
self.ui.define_widget.setVisible(False)
self.ui.hideatoms_label.setVisible(False)
self.adjustSize()
self.data = None
[docs] def setPlaybackSettingsData(self,
settings_data,
has_pbsettings_data,
fsys_atom_total=0):
"""
Set the playback settings data to the current entry trajectory.
If the entry trajectory has saved placback settings, the same
will be used to show in UI. Otherwise, populate the latest values
from UI and override the values in settings_data
:param settings_data: Playback settings data stored in PlaybackSettings
object
:type settings_data: PlaybackSettingsData Object
:param has_pbsetetings_data: True if the current entry trajectory has
saved playback setings data
:type has_pbsettings_data: bool
:param fsys_atom_total: Total number of atoms in the system
:type fsys_atom_total: int
"""
showing_first_time = self.data is None
self.data = settings_data
self.fsys_ct_atom_total = fsys_atom_total
if not has_pbsettings_data and not showing_first_time:
self.data.binding_site_radius = self.ui.radius_spinbox.value()
self.data.display_only = self.ui.display_only_checkbox.isChecked()
self.data.display_only_option = DisplayOnly.MATCHING if self.ui.matching_radiobutton.isChecked(
) else DisplayOnly.CURRENT
self.data.update_secondary_structure = self.ui.sec_struct_checkbox.isChecked(
)
self.data.show_simulation_box = self.ui.show_sim_checkbox.isChecked(
)
self.data.include_vector_lengths = self.ui.inc_lens_checkbox.isChecked(
)
self.data.replicate_x = self.ui.x_spinbox.value()
self.data.replicate_y = self.ui.y_spinbox.value()
self.data.replicate_z = self.ui.z_spinbox.value()
self.data.matching_asl = self.atom_selector.getAsl()
[docs] def showDlg(self):
"""
Update the dialog and post it.
"""
self.updateDlg()
self.ui.ok_pushbutton.setEnabled(False)
self.show()
[docs] def setReplicateErrorWarningIcon(self, message_type: MessageType):
"""
Set the icon and the text for warning and error scenarios
"""
if message_type == MessageType.WARNING:
self.updateStatusLabel(
"May cause slowness", MessageType.WARNING,
textwrap.dedent("""
Replication will exceed 1 million atoms.
Maestro may become unusably slow.
"""))
elif message_type == MessageType.ERROR:
self.updateStatusLabel(
"Too many atoms", MessageType.ERROR,
"Replication cannot exceed 5 million atoms.")
[docs] def updateStatusLabel(self, title: str, message_type: MessageType,
tooltip: str):
"""
Update status label associated with replication components.
"""
style = self.style()
if message_type == MessageType.NONE:
self.ui.status_label_icon.setPixmap(QtGui.QPixmap(""))
self.ui.status_label_icon.setToolTip("")
else:
icon = style.standardIcon(
style.SP_MessageBoxWarning if message_type ==
MessageType.WARNING else style.SP_MessageBoxCritical)
pixmap = icon.pixmap(15, 15)
self.ui.status_label_icon.setPixmap(pixmap)
self.ui.status_label_icon.setToolTip(tooltip)
self.ui.status_label_title.setMinimumHeight(
self.ui.status_label_icon.height())
self.ui.status_label_title.setProperty('errtype', message_type.value)
self.ui.status_label_title.setText(title)
self.ui.status_label_title.setToolTip(tooltip)
self.ui.status_label_title.style().unpolish(self.ui.status_label_title)
self.ui.status_label_title.style().polish(self.ui.status_label_title)
[docs] def updateReplicatedAtomTotalStatus(self):
"""
Check the current atom total based on the replication settings
"""
enable_ok = True
frame_ct_atom_total = self.fsys_ct_atom_total
x_replicate = self.ui.x_spinbox.value()
y_replicate = self.ui.y_spinbox.value()
z_replicate = self.ui.z_spinbox.value()
replicated_atom_total = frame_ct_atom_total * x_replicate * y_replicate * z_replicate
self.ui.status_label_title.setText("")
self.ui.status_label_title.setToolTip("")
self.ui.status_label_icon.setPixmap(QtGui.QPixmap(""))
# Show the error message if the replicated atom total exceeds 5 million atoms
if (replicated_atom_total >= ERROR_THRESHOLD_ATOMS):
enable_ok = False
self.setReplicateErrorWarningIcon(MessageType.ERROR)
# Show the warning message if the replicated atom total exceeds 1 million atoms
elif (replicated_atom_total >= WARNING_THRESHOLD_ATOMS):
self.setReplicateErrorWarningIcon(MessageType.WARNING)
return enable_ok
[docs] def enableOK(self):
"""
Enable 'OK' button
"""
asl_enable_ok = True
# Do not enable okay button if 'Display Only:' is toggled on, and
# 'Atoms matching ASL definition:' is checked and asl in
# atom selector empty
asl_enable_ok = not ((self.ui.display_only_checkbox.isChecked()) and
(self.ui.matching_radiobutton.isChecked()) and
(not self.atom_selector.getAsl()))
replicated_atom_count_ok = self.updateReplicatedAtomTotalStatus()
self.ui.ok_pushbutton.setEnabled(asl_enable_ok and
replicated_atom_count_ok)
[docs] def isDisplayOnlyCurrentDisplayedAtoms(self):
"""
Whether to display only currently displayed atoms
"""
return self.ui.display_only_checkbox.isChecked(
) and self.ui.current_radiobutton.isChecked()
[docs] def updateBidningSiteRadius(self):
"""
Set enable state of 'Define binding site radius as:'.
"""
self.ui.define_widget.setEnabled(
not self.isDisplayOnlyCurrentDisplayedAtoms())
[docs] def updateDlg(self):
"""
Update dialog according to data
"""
self.ui.radius_spinbox.setValue(self.data.binding_site_radius)
self.ui.display_only_checkbox.setChecked(self.data.display_only)
if self.data.display_only_option == DisplayOnly.CURRENT:
self.ui.current_radiobutton.setChecked(True)
else:
self.ui.matching_radiobutton.setChecked(True)
self.ui.sec_struct_checkbox.setChecked(
self.data.update_secondary_structure)
self.ui.show_sim_checkbox.setChecked(self.data.show_simulation_box)
self.ui.inc_lens_checkbox.setChecked(self.data.include_vector_lengths)
self.ui.x_spinbox.setValue(self.data.replicate_x)
self.ui.y_spinbox.setValue(self.data.replicate_y)
self.ui.z_spinbox.setValue(self.data.replicate_z)
allow_replication = self.data.allow_replication
for w in (self.ui.x_spinbox, self.ui.y_spinbox, self.ui.z_spinbox):
w.setEnabled(allow_replication)
self.atom_selector.setAsl(self.data.matching_asl)
ws_hub = maestro_ui.WorkspaceHub.instance()
self.ui.load_sel_pushbutton.setEnabled(
ws_hub.getSelectedAtomCount() > 0)
if allow_replication:
self.updateReplicatedAtomTotalStatus()
else:
self.updateStatusLabel(title="Workspace not replicable",
message_type=MessageType.NONE,
tooltip=textwrap.dedent("""
The trajectory cannot be replicated after a second entry has been included.
To play a trajectory with replication while viewing another entry, request the replication
immediately after the trajectory entry is included in the Workspace.
"""))
[docs] def cancelClicked(self):
"""
Reject and emit advancedSettingsDismissed() signal when clicked on
'Cancel' button
"""
self.close()
self.advancedSettingsDismissed.emit()
[docs] def isValidASL(self, asl):
"""
Return True if asl in the text box is valid.
"""
return analyze.validate_asl(asl)
[docs] def okClicked(self):
"""
Update data according to dialog
"""
# Warn user in case of invalid asl.
matching_asl_selected = (self.ui.display_only_checkbox.isChecked() and
self.ui.matching_radiobutton.isChecked())
matching_asl = self.atom_selector.getAsl()
if (matching_asl_selected and (not self.isValidASL(matching_asl))):
maestro.warning(f"Invalid ASL {matching_asl}")
return
self.close()
self.data.binding_site_radius = self.ui.radius_spinbox.value()
self.data.display_only = self.ui.display_only_checkbox.isChecked()
display_only_option = DisplayOnly.CURRENT
if self.ui.matching_radiobutton.isChecked():
display_only_option = DisplayOnly.MATCHING
self.data.display_only_option = display_only_option
self.data.update_secondary_structure = self.ui.sec_struct_checkbox.isChecked(
)
self.data.show_simulation_box = self.ui.show_sim_checkbox.isChecked()
self.data.include_vector_lengths = self.ui.inc_lens_checkbox.isChecked()
self.data.replicate_x = self.ui.x_spinbox.value()
self.data.replicate_y = self.ui.y_spinbox.value()
self.data.replicate_z = self.ui.z_spinbox.value()
self.data.matching_asl = self.atom_selector.getAsl()
self.advancedSettingsChanged.emit()
self.advancedSettingsDismissed.emit()
[docs] def help(self):
"""
Shows 'Advanced Playback Settings' dialog help
"""
if maestro:
maestro.command("helptopic TRAJECTORY_ADVANCED_PLAYBACK_SETTINGS")
maestro.command("showpanel help")