Source code for schrodinger.ui.qt.recent_completer
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtWidgets
from schrodinger.ui.qt import swidgets
from schrodinger.ui.qt.appframework2 import settings
STRING_DELIMITER = '@@@'
SEPARATOR = ''
[docs]class StringListModelInterface:
"""
Interface for Qt classes that can use a QStringListModel.
You add to the QStringListModel by calling addSuggestion. If a prefkey is
passed to the init, then these suggestions will be stored in Maestro,
and reloaded next time the widget is created
"""
MODEL_CLASS = QtCore.QStringListModel
[docs] def __init__(self, prefkey=None, suggestion_limit=20):
"""
:param prefkey: if defined, a unique string that defines where this
object's suggestions can be saved to and loaded from persistently
:type prefkey: str or None
:param suggestion_limit: the maximum number of suggestions that this
object will store; must be a nonnegative integer or None
:type suggestion: int or None
"""
model = self.MODEL_CLASS(parent=self)
self.setModel(model)
self._prefkey = prefkey
suggestions = self._getPersistentSuggestions() if prefkey else []
self.suggestion_limit = suggestion_limit
self.setSuggestions(suggestions)
@property
def suggestion_limit(self):
"""
:return: the maximum number of completion suggestions that this object
will remember; if `None`, there is no limit.
:rtype: int or None
"""
return self._suggestion_limit
@suggestion_limit.setter
def suggestion_limit(self, suggestion_limit):
"""
:param suggestion_limit: the new limit for the number of completion
suggestions. Must be a nonnegative integer or `None` (for no limit)
:type suggestion_limit: int or None
"""
if suggestion_limit is not None and suggestion_limit < 0:
msg = 'String limit must be a nonnegative integer or None.'
raise ValueError(msg)
self._suggestion_limit = suggestion_limit
self._updateModel()
[docs] def setSuggestions(self, suggestions):
"""
Replace the current set of completion suggestions with the supplied
values.
:param suggestions: a new list of completion suggestions to set on this
object
:rtype suggestions: list(str)
"""
model = self.model()
model.setStringList(suggestions)
self._updateModel()
[docs] def addSuggestion(self, new_suggestion):
"""
Add a new completion suggestion to this object. If it already exists in
this object, put it to the top of the list to indicate that it the most
recent suggestion.
:param new_suggestion: a new completion suggestion
:type new_suggestion: str
"""
model = self.model()
suggestions = model.stringList()
if new_suggestion in suggestions:
suggestions.remove(new_suggestion)
suggestions = [new_suggestion] + suggestions
model.setStringList(suggestions)
self._updateModel()
def _updateModel(self):
"""
Cut any extra completion suggestions from this object if a limit is set.
Save the resulting completion list persistently if a preference key is
set.
"""
model = self.model()
suggestions = model.stringList()
limit = self._suggestion_limit
while limit is not None and len(suggestions) > limit:
suggestions.pop()
model.setStringList(suggestions)
if self._prefkey is not None:
self._setPersistentSuggestions(suggestions)
def _getPersistentSuggestions(self):
"""
Attempt to read and return a list of completion suggestions saved
persistently at a location described by this object's preference key.
:return: a list of completion suggestions, if found
:rtype: list(str)
"""
store_suggestion = settings.get_persistent_value(self._prefkey, '')
if store_suggestion:
return store_suggestion.split(STRING_DELIMITER)
return []
def _setPersistentSuggestions(self, suggestions):
"""
Save the specified completion suggestions persistently at a location
described by this object's preference key.
:param suggestions: a list of completion suggestions
:type suggestions: list(str)
"""
store_suggestion = STRING_DELIMITER.join(suggestions)
settings.set_persistent_value(self._prefkey, store_suggestion)
[docs]class RecentCombo(QtWidgets.QComboBox, StringListModelInterface):
"""
A `QComboBox` which is able to store its past values in Maestro persistent
settings. You add to this combobox by using `addSuggestion`.
When adding a suggestion, the model gets reset in the process, which may
clear selection and emit a signal indicating that the selection has changed.
"""
[docs]class RecentCompleter(QtWidgets.QCompleter, StringListModelInterface):
"""
A `QLineEdit` completer that can remember a limited number of completion
suggestions, and optionally retain completion suggestions persistently.
"""
[docs]class PlaceholderRecentCombo(swidgets.PlaceholderComboMixin, RecentCombo):
"""
A RecentCombo which enables persistent options as well as placeholder text
"""
[docs]class SeparatorStringModel(QtCore.QStringListModel):
"""
A QStringList model which supports separator. Subclass needs to add the
separator at desired position using 'setStringList' method.
"""
[docs] def data(self, index, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.AccessibleDescriptionRole:
if self.isSeparator(index):
# QComboBox delegate uses the AccessibleDescriptionRole to
# indicate that the item is a separator
return "separator"
else:
return super().data(index, role)
[docs] def isSeparator(self, index):
"""
Returns True if the item corresponding to the given index is a
separator
"""
return self.data(index, QtCore.Qt.DisplayRole) == SEPARATOR
[docs] def flags(self, index):
"""
The separator row should not be enabled or selectable
"""
flags = super().flags(index)
if self.isSeparator(index):
flags &= ~(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable)
return flags
[docs]class BrowseStringModel(SeparatorStringModel):
"""
A QStringList model which adds a separator and a browse option for
comboboxes that need a browse button appended to the end
:cvar BROWSE: Text for browse option.
:vartype BROWSE: str
"""
BROWSE = "Browse..."
[docs] def stringList(self):
"""
Return the string list, not including the separator and browse
:return:
"""
return super().stringList()[:-2]
[docs] def setStringList(self, strings):
"""
When setting a string list, add a separator and browse option
"""
super().setStringList(strings + [SEPARATOR, self.BROWSE])
[docs]class BrowsePlaceholderRecentCombo(PlaceholderRecentCombo):
"""
A PlaceholderRecentCombo which always keeps a Browse option at the
bottom.
:ivar browseSelected: A signal emitted when the browse option in the
combobox is selected.
"""
browseSelected = QtCore.pyqtSignal()
MODEL_CLASS = BrowseStringModel
[docs] def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.currentTextChanged.connect(self._checkBrowseSelected)
@QtCore.pyqtSlot(str)
def _checkBrowseSelected(self, text):
if text == self.MODEL_CLASS.BROWSE:
self.browseSelected.emit()