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 mm
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.structure import StructureReader
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.appframework2 import maestro_callback
from schrodinger.ui.qt.multi_combo_box import MultiComboBox
from schrodinger.ui.qt.utils import EnhancedComboBox
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])