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 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])
[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()