Source code for schrodinger.ui.qt.entryselector

'''
File: entryselector.py
Author: Pat Lorton
Description: This dialog mimics the Maestro 'Choose Entry', with a few upgrades
using our Python infrastructure.

For almost all uses you'll simply want to use the module functions
get_entry() or get_entries()
'''

import schrodinger
from schrodinger import project
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
from schrodinger.Qt.QtCore import Qt
from schrodinger.ui.qt import propertyselector
from schrodinger.ui.qt.structure2d import StructureToolTip

from . import entryselector_ui

maestro = schrodinger.get_maestro()

ENTRY_ID_COLUMN = 1
ALL_IDX = 0
SELECTED_IDX = 1
INCLUDED_IDX = 2


[docs]def QSI(value, entry_id=False): """ Create a QStandardItem containing the specified data :param value: The value to store in the QStandardItem :param entry_id: This should be True if the value represents an entry ID, and False otherwise. This ensures that entry IDs will be sorted numerically. :type entry_id: bool :return: The newly created QStandardItem :rtype: `PyQt5.QtGui.QStandardItem` """ if value is None: value = "" item = QtGui.QStandardItem(str(value)) item.setEditable(False) if entry_id: item.setData(int(value), Qt.UserRole) else: item.setData(value, Qt.UserRole) return item
[docs]def get_entry(parent=None): """ This is a helper function to make this module trivially usable. :return: Single entry_id :rtype: string """ selector = _EntrySelector(multi_selection=False, parent=parent) entries = selector.getEntries() if entries: return entries[0] else: return None
[docs]def get_entries(parent=None): """ This is a helper function to make this module trivially usable. :return: List of entry_id's :rtype: List of strings """ selector = _EntrySelector(multi_selection=True, parent=parent) return selector.getEntries()
Super = QtWidgets.QDialog class _EntrySelector(Super): """ This class can be used to select an entry from the PT via a popup dialog. """ selection = None def __init__(self, multi_selection=False, **kwargs): ''' :param multi_selection: Whether to select a single or multiple entries :type multi_selection: bool ''' self.multi_selection = multi_selection if multi_selection: self.selection_mode = QtWidgets.QAbstractItemView.ExtendedSelection else: self.selection_mode = QtWidgets.QAbstractItemView.SingleSelection Super.__init__(self, **kwargs) self.setWindowModality(Qt.WindowModal) self.ui = entryselector_ui.Ui_Dialog() self.ui.setupUi(self) #Set title based on selection type if self.multi_selection: title = "Choose Entries" else: title = "Choose Entry" self.setWindowTitle(title) # Change the name of the OK button: self.ui.buttonBox.button( QtWidgets.QDialogButtonBox.Ok).setText("Choose") self.additional_properties = [] self._qsi_model = QtGui.QStandardItemModel() self.table_view = _TableWithTooltip(self.ui.table_frame) self.table_view.setSortingEnabled(True) self.table_view.setSelectionBehavior(self.table_view.SelectRows) self.table_model = QtCore.QSortFilterProxyModel() self.table_model.setSourceModel(self._qsi_model) self.table_model.setDynamicSortFilter(True) self.table_model.setFilterKeyColumn(-1) self.table_model.setSortRole(Qt.UserRole) self.table_view.verticalHeader().hide() self.table_view.setModel(self.table_model) self.table_view.setSelectionMode(self.selection_mode) layout = QtWidgets.QVBoxLayout() layout.addWidget(self.table_view) self.ui.table_frame.setLayout(layout) maestro.project_update_callback_add(self.projectUpdated) #Connect signals and slots self.ui.choose_from_combo.currentIndexChanged.connect( self.projectUpdated) self.ui.show_property_btn.clicked.connect(self.selectProperties) self.ui.filter_le.textChanged.connect(self._onFilterChanged) def closeEvent(self, event): maestro.project_update_callback_remove(self.projectUpdated) Super.closeEvent(self, event) def getEntries(self): """ Opens the modal entry selection dialog, and returns a list of entries that the user selected. :return: List of Entry IDs :rtype: List of str """ self.selection = None self.projectUpdated() self.show() self.exec() self.close() return self.selection def accept(self): self.selection = [] for index in self.table_view.selectionModel().selection().indexes(): if index.column() == ENTRY_ID_COLUMN: entry_id = str(self.table_model.data(index)) self.selection.append(entry_id) Super.accept(self) def updateTableFromIterator(self, row_iter): ''' This function is called to update the current table when the option to show selected entries is selected. :param row_iter: A list of rows to iterate over :type row_iter: An object that can be iterated over ''' headers = ["Title", "Entry ID"] headers += [prop.userName() for prop in self.additional_properties] self._qsi_model.clear() self._qsi_model.setHorizontalHeaderLabels(headers) for row in row_iter: entryselector_row = [QSI(row.title), QSI(row.entry_id, True)] entryselector_row += [ QSI(row[str(prop)]) for prop in self.additional_properties ] self._qsi_model.appendRow(entryselector_row) @QtCore.pyqtSlot(int) def projectUpdated(self, combo_idx=None): ''' This function is called whenever the project table is updated. It then gathers the Project object and passes it to sub-functions to deal with however the entry selector is currently monitoring the PT. :param combo_idx: The current index of the PT source Combo Box, if None we fetch from the UI. :type combo_idx: None or int ''' try: pt = maestro.project_table_get() except project.ProjectException: # It we are in the middle of project open/close pass else: if not combo_idx: combo_idx = self.ui.choose_from_combo.currentIndex() if combo_idx == SELECTED_IDX: self.updateTableFromIterator(pt.selected_rows) elif combo_idx == ALL_IDX: self.updateTableFromIterator(pt.all_rows) elif combo_idx == INCLUDED_IDX: self.updateTableFromIterator(pt.included_rows) def _getAvailableAdditionalProps(self): """ Return all PT properties except the entry ID and Title (which are always visible, hence are not "additional"). """ proplist = maestro.project_table_get().getPropertyNames() proplist.remove('s_m_entry_id') proplist.remove('s_m_title') return proplist def selectProperties(self): """ This is used to select what properties are visible in the table. """ selector = propertyselector.PropertySelectorDialog( self, show_alpha_toggle=True, show_filter_field=True, multi=True, allow_empty=True) # PT properties except Entry ID and Title (which are always visible): proplist = self._getAvailableAdditionalProps() chosen_properties = selector.chooseFromList(proplist) if chosen_properties is None: # User cancelled return self.additional_properties = chosen_properties self.projectUpdated() def _onFilterChanged(self, filter_txt: str): """ Whenever the filter line edit changes, update model filtering. :param filter_txt: The text in the filter line edit """ qre = QtCore.QRegularExpression( filter_txt, QtCore.QRegularExpression.CaseInsensitiveOption) self.table_model.setFilterRegularExpression(qre) class _TableWithTooltip(QtWidgets.QTableView): """ A table that can show 2D tooltips from the entry ID in the model. We use a timer to show the tooltip, to make sure they're not shown too rapidly. """ def __init__(self, parent): """ :param parent: Parent widget this is embedded in :type parent: QtWidgets.QWidget """ QtWidgets.QTableView.__init__(self, parent) self.tooltip = None self.setMouseTracking(True) self.tooltip_timer = QtCore.QTimer() self.tooltip_timer.timeout.connect(self.showTooltip) def showTooltip(self): """ This shows the currently set tooltip """ self.tooltip.show() def event(self, event): """ We override the default event function to custom handle tooltips """ if event.type() == QtCore.QEvent.ToolTip: headerless_y = event.pos().y() - self.horizontalHeader().height() qp = QtCore.QPoint(event.pos().x(), headerless_y) index = self.indexAt(qp) if index.row() < 0: return True eid_index = self.model().index(index.row(), 1) entry_id = self.model().data(eid_index) pt = maestro.project_table_get() row = pt.getRow(entry_id) struct = row.getStructure() # Set the tooltip, and start the timer to show it self.tooltip = StructureToolTip(structure=struct) self.tooltip_timer.start(300) return True else: return QtWidgets.QTableView.event(self, event) def mouseMoveEvent(self, event): """ Hide the tooltip, and stop a timer if it was counting to show """ if self.tooltip is not None: self.tooltip_timer.stop() self.tooltip.hide()