Source code for schrodinger.application.phase.phase_widgets
from collections import OrderedDict
import schrodinger
from schrodinger import project
from schrodinger.application.phase import constants
from schrodinger.application.phase import hypothesis
from schrodinger.application.phase import pt_hypothesis
from schrodinger.infra import phase
from schrodinger.project import ProjectException
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtWidgets
from schrodinger.Qt.QtCore import Qt
from schrodinger.ui.qt import delegates as qt_delegates
from schrodinger.ui.qt import filedialog
from schrodinger.ui.qt import swidgets
from schrodinger.ui.qt import table_helper
from schrodinger.ui.qt.multi_combo_box import MultiComboBox
maestro = schrodinger.get_maestro()
FEATURE_TYPE_ROLE = Qt.UserRole + 1
[docs]class FeatureOptionsCombo(MultiComboBox):
"""
MultiComboBox containing feature presets. This currently includes feature
equivalencies and use of alternate feature definitions.
"""
# Feature equivalencies dictionary whose values are equivalency arguments
FEATURE_EQUIV_PRESETS = OrderedDict(
(("Make hydrophobic and aromatic rings equivalent",
"HR"), ("Make acceptor and negative equivalent", "AN"),
("Make donor and positive equivalent", "DP")))
FEATURE_DEFINITION_PRESETS = [
"Replace vectors with projected points (acceptors and donors)"
]
[docs] def __init__(self, parent):
super(FeatureOptionsCombo, self).__init__(parent)
self.addItems(list(self.FEATURE_EQUIV_PRESETS))
[docs] def addFeatureDefinitionPresets(self):
"""
Adds the optional feature definition presets.
"""
self.addItems(self.FEATURE_DEFINITION_PRESETS)
[docs] def getSelectedFeatureEquivalencies(self):
"""
Return a list of feature equivalencies that were checked/selected in
the menu.
:return: list of equivalencies to pass to PhpProject.saveFeatureEquiv
:rtype: list of str
"""
selected_presets = []
for index in self.getSelectedIndexes():
item_text = self.itemText(index)
if item_text in list(self.FEATURE_EQUIV_PRESETS):
selected_presets.append(self.FEATURE_EQUIV_PRESETS[item_text])
return selected_presets
[docs] def setADProjectedPointsChecked(self, checked):
"""
Sets the selected state of the Replace acceptor/donor projected points
preset item.
:param checked: whether to select the projected point feature preset
:type checked: bool
"""
item = self.FEATURE_DEFINITION_PRESETS[0]
self.setItemSelected(item, selected=checked)
[docs] def useADProjectedPointsChecked(self):
"""
Whether the Replace acceptor/donor projected points item is checked.
:return: string indicating the number of selected feature presets
:rtype: str
"""
all_text = [self.itemText(index) for index in self.getSelectedIndexes()]
return self.FEATURE_DEFINITION_PRESETS[0] in all_text
[docs] def currentText(self):
# See Qt documentation for method documentation
selected = self.getSelectedItems()
return '(%i selected)' % len(selected)
[docs]class FeatureMatchCombo(MultiComboBox):
"""
This class defines special variant of a combo box used in Edit Feature
dialog to define features that are allowed and forbidden to match. This
combo box would contain a list of Phase features, which should be
checkable. Line edit would show comma checked features as a string, which
contains comma separated one letter feature names. In addition some
items could be disabled.
"""
[docs] def __init__(self, parent):
super(FeatureMatchCombo, self).__init__(parent)
# List of feature character types, for each combo menu item:
self.feat_types = []
item_names = []
for feature_type in constants.FEATURE_TYPES:
feature_str = constants.get_feature_text(feature_type)
item_names.append(feature_str)
self.feat_types.append(feature_type)
self.addItems(item_names)
[docs] def currentText(self):
"""
Text to show in the combo menu, depending on the current selection.
Over-rides the standard method of MultiComboBox.
"""
# See Qt documentation for method documentation
features = self.checkedFeatures()
if features:
features_text = ','.join(features)
else:
features_text = 'None'
return features_text
def _findFeatureItem(self, feature_type):
"""
This function finds combo box model item for a given feature type.
:param feature_type: one letter feature type
:type feature_type: str
:return: Row index for a given feature type
:rtype: int
"""
return self.feat_types.index(feature_type)
[docs] def setSelectedFeatures(self, features):
"""
Select the given features in the combo menu.
:param features: List of one-letter feature types.
:type features: list of str
"""
self.setSelectedIndexes(
[self._findFeatureItem(feat) for feat in features])
[docs] def setChecked(self, feature, select):
"""
This function sets feature item 'checked' state.
:param feature_type: one letter feature type. 'Enabled' and 'checked'
states will be set for this feature type.
:type feature_type: str
:param checked: boolean indicating whether item should be checked
:type checked: bool
"""
index = self._findFeatureItem(feature)
self.setIndexSelected(index, select)
[docs] def setEnabled(self, feature_type, enabled):
"""
This function sets feature item 'enabled' state.
:param feature_type: one letter feature type. 'Enabled' and 'checked'
states will be set for this feature type.
:type feature_type: str
:param enabled: boolean indicating whether item should be enabled
:type enabled: bool
"""
idx = self._findFeatureItem(feature_type)
self.setIndexEnabled(idx, enabled)
[docs] def enableAllFeatures(self):
"""
Set all items to be enabled. Except the features that were "forced" to
be selected (they are selected and disabled).
"""
for idx in range(self.count()):
disabled = not self.isIndexEnabled(idx)
# We make an exception here for the 'right clicked' feature,
# which should always remain checked and disabled.
if disabled and self.isIndexSelected(idx):
continue
self.setIndexEnabled(idx, True)
[docs] def resetAllFeatures(self):
"""
Resets all item states to enabled and unchecked.
"""
for idx in range(self.count()):
self.setIndexEnabled(idx, True)
self.setIndexSelected(idx, False)
[docs] def checkedFeatures(self):
"""
This function returns a list that contains one letter types of
checked features. Feature that is checked and disabled is the
'current' feature type. It should be the first item in the list.
:return: list of checked features
:rtype: list
"""
# Find current feature type and make it first element of the list.
checked = [
self.feat_types[idx]
for idx in self.getSelectedIndexes()
if not self.isIndexEnabled(idx)
]
checked.extend([
self.feat_types[idx]
for idx in self.getSelectedIndexes()
if self.isIndexEnabled(idx)
])
return checked
[docs]class HypothesisRow(object):
"""
Data class that contains information about entry ids for a given
Phase hypothesis.
"""
[docs] def __init__(self, entry_id, hypo):
"""
Hypothesis data class.
:param entry_id: hypothesis entry ID
:type entry_id: int
:param hypo: hypothesis data object
:type hypo: `hypothesis.PhaseHypothesis`
"""
self.entry_id = entry_id
self.hypo = hypo
self.num_sites = hypo.getSiteCount()
# default number of sites to match
self.min_sites = self.num_sites if self.num_sites < 4 else 4
self.has_xvol = hypo.hasXvol()
self.use_xvol = True
class HypothesisColumns(table_helper.TableColumns):
HypoName = table_helper.Column("Hypothesis")
MinSites = table_helper.Column(
"Matches",
editable=True,
tooltip="Minimum number of features required for a match")
ExclVols = table_helper.Column(
"Excluded Volumes",
checkable=True,
tooltip="Toggle on/off usage of excluded volumes in screening.")
[docs]class HypothesisModel(table_helper.RowBasedTableModel):
"""
Hypotheses Model.
"""
Column = HypothesisColumns
ROW_CLASS = HypothesisRow
@table_helper.data_method(Qt.DisplayRole, Qt.CheckStateRole, Qt.EditRole)
def _getData(self, col, hypo_row, role):
# See base class for documentation.
if col == self.Column.HypoName and role == Qt.DisplayRole:
return hypo_row.hypo.getHypoID()
if col == self.Column.ExclVols:
if role == Qt.CheckStateRole:
if not hypo_row.has_xvol:
return None
if hypo_row.use_xvol:
return Qt.Checked
else:
return Qt.Unchecked
if role == Qt.DisplayRole:
if hypo_row.has_xvol:
return None
else:
return 'None'
if col == self.Column.MinSites:
if role == Qt.CheckStateRole:
return None
else:
return "%d of %d" % (hypo_row.min_sites, hypo_row.num_sites)
def _setData(self, col, hypo_row, value, role, row_num):
# See table_helper._setData for method documentation
if role == Qt.CheckStateRole and col == self.Column.ExclVols:
hypo_row.use_xvol = bool(value)
return True
if col == self.Column.MinSites:
min_sites, _, _ = value.split()
hypo_row.min_sites = int(min_sites)
return True
return False
@table_helper.data_method(qt_delegates.ComboBoxDelegate.COMBOBOX_ROLE)
def _comboBoxContents(self, col, data):
"""
Data to show in the combo box menu when editing a cell.
See data_method for argument documentation
"""
if col == self.Column.MinSites:
return [
"%d of %d" % (i, data.num_sites)
for i in range(2, data.num_sites + 1)
]
[docs] def getAllHypotheses(self):
"""
Returns a list of all PhaseHypothesis objects in this model.
:return: All hypotheses
:rtype: list of `hypothesis.PhaseHypothesis`
"""
hypos = []
for row in self.rows:
hypo = hypothesis.PhaseHypothesis(row.hypo)
hypo.addProp(phase.PHASE_MIN_SITES, row.min_sites)
if row.has_xvol and not row.use_xvol:
hypo.deleteAttr("xvol")
hypos.append(hypo)
return hypos
[docs] def getAllHypoIDs(self):
"""
Return a list of entry IDs of all hypotheses in the table.
"""
return set([row.entry_id for row in self.rows])
[docs]class HypothesesListWidget(QtWidgets.QWidget):
"""
Widget that shows list of Project Table hypotheses. It has a control
that allows to select hypotheses for 'included' and 'selected' entries.
:cvar modelChanged: signal emitted when hypotheses are added to this
widget or deleted.
:vartype modelChanged: `QtCore.pyqtSignal`
"""
ADD_HYPO_WS, ADD_HYPO_PT = list(range(2))
COMBO_TEXT = "Add Hypothesis..."
modelChanged = QtCore.pyqtSignal()
[docs] def __init__(self, parent):
"""
Initialize hypotheses widget.
"""
super(HypothesesListWidget, self).__init__(parent)
self._createWidgets()
self._layoutWidgets()
self._connectSignals()
def _createWidgets(self):
"""
Instantiate all widgets.
"""
self.hypo_model = HypothesisModel(self)
self.combo_delegate = qt_delegates.ComboBoxDelegate(self)
self.hypo_view = table_helper.SampleDataTableView(self)
header_view = self.hypo_view.horizontalHeader()
header_view.setStretchLastSection(True)
self.hypo_view.setSelectionMode(
QtWidgets.QAbstractItemView.MultiSelection)
self.hypo_view.setSelectionBehavior(
QtWidgets.QAbstractItemView.SelectRows)
self.hypo_view.setModel(self.hypo_model)
self.hypo_view.setItemDelegateForColumn(self.hypo_model.Column.MinSites,
self.combo_delegate)
self.source_combo = swidgets.ActionComboBox(self)
self.source_combo.setText(self.COMBO_TEXT)
self.source_combo.addItem("Workspace", self.addHypoFromWorkspace)
if maestro:
self.source_combo.addItem("Project Table (selected entries)",
self.addHypoFromPT)
# KNIME-4410: Maestro-less implementation
self.source_combo.addItem("File...", self._addHypothesisFromFile)
self.delete_btn = QtWidgets.QPushButton("Delete")
self.delete_btn.setEnabled(False)
def _layoutWidgets(self):
"""
Arrange all widgets
"""
source_layout = QtWidgets.QHBoxLayout()
source_layout.addWidget(self.source_combo)
source_layout.addWidget(self.delete_btn)
source_layout.addStretch()
main_layout = QtWidgets.QVBoxLayout()
main_layout.addLayout(source_layout)
main_layout.addWidget(self.hypo_view)
self.setLayout(main_layout)
def _connectSignals(self):
"""
Connect widget signals.
"""
self.delete_btn.clicked.connect(self._deleteHypotheses)
self.hypo_view.selectionModel().selectionChanged.connect(
self._selectionChanged)
def _selectionChanged(self):
"""
This slot is called when hypothesis selection is changed.
"""
num_selected = len(self.hypo_view.selectedIndexes())
self.delete_btn.setEnabled(num_selected > 0)
[docs] def addHypoFromWorkspace(self):
"""
Adds hypotheses from Workspace (included Project Table entries).
"""
for entry_id in self._getHypothesisIDs(self.ADD_HYPO_WS):
self._addHypothesisFromProject(entry_id)
[docs] def addHypoFromPT(self):
"""
Adds hypotheses from the selected Project Table entries.
"""
for entry_id in self._getHypothesisIDs(self.ADD_HYPO_PT):
self._addHypothesisFromProject(entry_id)
def _addHypothesisFromProject(self, entry_id):
"""
Adds a PT hypothesis to the list, given it's hypothesis ID. If the
hypothesis is already in the list, this method does nothing.
:param entry_id: PT entry ID
:param entry_id: str
"""
if entry_id in self.hypo_model.getAllHypoIDs():
return
hypo = pt_hypothesis.get_hypothesis_from_project(entry_id)
self.hypo_model.appendRow(entry_id, hypo)
self.modelChanged.emit()
def _addHypothesisFromFile(self):
"""
Adds hypothesis from `*.phypo` or `*_phypo.mae.gz` file.
"""
filenames = filedialog.get_open_file_names(
self,
caption="Select Phase Hypothesis File",
filter="Phase Hypotheses (*.phypo *_phypo.maegz *_phypo.mae.gz)",
id="screening_load_hypothesis")
# Nothing selected
if not filenames:
return
pt = maestro.project_table_get()
prev_num_entries = len(pt.all_rows)
for hypo_file in filenames:
pt.importStructureFile(hypo_file)
curr_num_entries = len(pt.all_rows)
for entry_index in range(prev_num_entries, curr_num_entries):
entry_id = project.ProjectRow(pt, entry_index + 1).entry_id
if pt_hypothesis.is_hypothesis_entry(entry_id):
self._addHypothesisFromProject(entry_id)
prev_num_entries = curr_num_entries
def _getHypothesisIDs(self, hypo_from):
"""
Returns entry ids of hypotheses associated with either selected or
included rows.
:param hypo_from: indicates whether hypothesis should be searched
in selected or included rows.
:type hypo_from: int
:return: list of hypothesis ids
:rtype: list
"""
if not maestro:
# unit tests
return []
try:
proj = maestro.project_table_get()
except ProjectException:
# Project may have been closed during operation
return []
if hypo_from == self.ADD_HYPO_WS:
hypo_rows = proj.included_rows
elif hypo_from == self.ADD_HYPO_PT:
hypo_rows = proj.selected_rows
# row.entry_id returns a str, but the module uses ints.
return [
int(row.entry_id)
for row in hypo_rows
if pt_hypothesis.is_hypothesis_entry(row.entry_id)
]
def _deleteHypotheses(self):
"""
Removes selected items in the hypotheses view.
"""
self.hypo_model.removeRowsByIndices(self.hypo_view.selectedIndexes())
self.modelChanged.emit()
[docs] def addHypothesisFromEntry(self, entry_id):
"""
Adds a PT hypothesis to the list, given it's entry ID.
:param entry_id: Project Table entry ID
:type entry_id: int or str
"""
self._addHypothesisFromProject(entry_id)
[docs] def updateHypothesisFromEntry(self, entry_id):
"""
Updates hypothesis in the model (if it is found) from the PT
hypothesis with a given entry ID.
"""
for row in self.hypo_model.rows:
if row.entry_id == entry_id:
hypo = pt_hypothesis.get_hypothesis_from_project(entry_id)
row.hypo = hypo
self.modelChanged.emit()
return
[docs] def getHypotheses(self):
"""
Returns a list of all hypotheses in the table.
:return: list of PhaseHypothesis objects.
:rtype: list
"""
return self.hypo_model.getAllHypotheses()