Source code for schrodinger.application.jaguar.gui.solvent_selector
"""
A filterable selector for solvents.
Copyright Schrodinger, LLC. All rights reserved.
"""
import csv
from schrodinger.application.jaguar import jaguar_keyword_utils
from schrodinger.application.matsci import msutils
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtWidgets
from schrodinger.Qt.QtCore import Qt
from schrodinger.ui.qt import filter_list
from schrodinger.ui.qt import swidgets
from schrodinger.utils import csv_unicode
[docs]class SolventListWidgetItem(QtWidgets.QListWidgetItem):
    """
    A solvent that appears in the selector.
    """
[docs]    def __init__(self, name, common, halogenated, aromatic, hydrocarbon,
                 carbonyl, polar, nonpolar):
        super().__init__(name)
        self.name = name
        self.common = common
        self.halogenated = halogenated
        self.aromatic = aromatic
        self.hydrocarbon = hydrocarbon
        self.carbonyl = carbonyl
        self.polar = polar
        self.nonpolar = nonpolar
[docs]class SolventSelectorFilterListPopUp(filter_list.FilterListPopUp):
    """
    A pop up that allows the solvents to be filtered by category.
    """
[docs]    def __init__(self, parent):
        list_items = self._createListItems()
        cbs = self._createFilterCheckBoxes()
        super().__init__(parent, list_items, cbs.values(),
                         'Limit list to matching solvents')
        # make the polar and non-polar check boxes mutually exclusive
        self._polar_group = swidgets.SRadioButtonGroup(self, radio=False)
        self._polar_group.setExclusive(True, uncheckable=True)
        self._polar_group.addExistingButton(cbs["polar"])
        self._polar_group.addExistingButton(cbs["nonpolar"])
    def _createListItems(self):
        """
        :return: A tuple of items for the list widget.
        :rtype: tuple(SolventListWidgetItem)
        """
        list_items = []
        with csv_unicode.reader_open(jaguar_keyword_utils.SOLVENTS) as fh:
            reader = csv.DictReader(fh)
            for row in reader:
                name = row.pop("name")
                # convert the category values from strings of "0" or "1" to
                # Booleans
                categories = {
                    key: msutils.setting_to_bool(val)
                    for key, val in row.items()
                }
                list_items.append(SolventListWidgetItem(name, **categories))
        return tuple(list_items)
    def _createFilterCheckBoxes(self):
        """
        :return: A dictionary of filter checkboxes in the form
            {`SolventListWidgetItem` attribute name: check box}
        :rtype: dict(str, filter_list.FilterCheckBox)
        """
        # tuples of (text to display in GUI, attribute name, enabled by default)
        cb_text_and_attr_name = (
            ("Common", "common", True),
            ("Halogenated", "halogenated", False),
            ("Aromatic", "aromatic", False),
            ("Hydrocarbon", "hydrocarbon", False),
            ("Carbonyl", "carbonyl", False),
            ("Polar", "polar", False),
            ("Non-polar", "nonpolar", False),
        )
        def filter_on(attr_name):
            return lambda list_item: getattr(list_item, attr_name)
        return {
            attr_name: filter_list.FilterCheckBox(cb_text,
                                                  filter_on(attr_name),
                                                  on_by_default=on_by_default)
            for cb_text, attr_name, on_by_default in cb_text_and_attr_name
        }
[docs]    def getSolvent(self):
        """
        :return: The currently selected solvent
        :rtype: str or None
        """
        current_item = self._list_widget.currentItem()
        if not current_item:
            return None
        return current_item.name
[docs]    def setSolvent(self, solvent=None):
        """
        Set the current solvent.
        :param solvent: Solvent value to be set.  If None, no solvent will be
            selected.
        :type solvent: str or None
        :raise ValueError: If the specified solvent was not found.
        """
        if solvent is None:
            self._list_widget.setCurrentItem(None)
            return
        items = self._list_widget.findItems(solvent,
                                            Qt.MatchFlag.MatchFixedString)
        if len(items) != 1:
            raise ValueError(f"Solvent {solvent} not found")
        self._list_widget.setCurrentItem(items[0])
[docs]class SolventSelectorFilterListToolButton(
        filter_list.ToolButtonWithFilterListPopUp):
    """
    Custom tool button with a solvent selector filter list pop up.
    """
    POP_UP_CLASS = SolventSelectorFilterListPopUp
    solventChanged = QtCore.pyqtSignal(str)
[docs]    def __init__(self, parent):
        super().__init__(parent)
        self.popUpClosing.connect(self._emitSolventChanged)
[docs]    def getSolvent(self):
        """
        :return: The currently selected solvent
        :rtype: str or None
        """
        return self._pop_up.getSolvent()
[docs]    def setSolvent(self, solvent=None):
        """
        Set the current solvent.
        :param solvent: Solvent value to be set.  If None, no solvent will be
            selected.
        :type solvent: str or None
        :raise ValueError: If the specified solvent was not found.
        """
        self._pop_up.setSolvent(solvent)
        self._emitSolventChanged()
    def _emitSolventChanged(self):
        self.solventChanged.emit(self.getSolvent())