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()
[docs] def reset(self): """ Reset hypotheses list model. """ self.hypo_model.reset()
[docs] def setDefaults(self): """ Sets default widget options. """ self.reset()
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()