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