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