Source code for schrodinger.ui.residueselector

import collections
from past.utils import old_div

from schrodinger import get_maestro
from schrodinger.application.bioluminate import protein
from schrodinger.protein import nonstandard_residues as nsr
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 pop_up_widgets
from schrodinger.ui.qt import swidgets
from schrodinger.ui.qt import tooltips
from schrodinger.ui.qt.standard_widgets import hyperlink
from schrodinger.ui.qt.utils import suppress_signals

# The images_rc import loads the imgaes into Qt
from . import images_rc  # noqa # pylint: disable=unused-import

maestro = get_maestro()

# string literals for residue and mutation explanations
SAME_TXT = 'Cannot mutate to a residue of the same type'
HIS_TXT = 'Please choose HIE, HID or HIP'
UNRECOGNIZED_TXT = 'Residue not recognized'

TINY_RESIDUES = ('GLY', 'ALA', 'CYS', 'SER')
SMALL_RESIDUES = TINY_RESIDUES + ('PRO', 'VAL', 'THR', 'ASP', 'ASN')
ALIPHATIC_RESIDUES = ("ILE", "LEU", "VAL")
# residue 'name' of radio button that needs to be checked when the selector in
# single_mutation_only mode needs clearing
NONE_RESIDUE = ''


[docs]def muts_str_to_list(muts_str): """ Convert the string of comma-separated residue names to a list. Strips spaces as needed, and properly handles an empty string. """ return [mut.strip() for mut in muts_str.split(',') if mut.strip()]
[docs]def clear_layout(layout): """ Remove all widgets from the layout and delete them. """ while True: child = layout.takeAt(0) if not child: break child.widget().deleteLater()
def _remove_all_widgets(layout): """ Remove all the widgets from the given layout without deleting them. Also hide the removed widgets. :param layout: layout instance :type layout: QtWidgets.QLayout """ while layout.count(): child = layout.takeAt(0) child.widget().setVisible(False)
[docs]def find_atom_with_pdb_name(res, pdb_name): """ Finds atom in a residue with a given PDB name. Atom name should have no spaces. This way 'OD1' will match either 'OD1 ' or ' OD1' in a residue. If atom was not found returns None. :param res: residue object :type res: structure._Residue :param pdb_name: atom PDB name :type pdb_name: str :return: matching PDB atom :rtype: structure._StructureAtom """ for atom in res.atom: if atom.pdbname.strip() == pdb_name: return atom return None
[docs]class ResidueGroups: """ Class representing the residue "groups" that appear as mixed-state checkboxes in the residue selector pop-up. Basically a collection of multiple residue name tuples. """ ALL_RESIDUES = tuple(protein.ALL_RESIDUES)
[docs] def __init__(self): self.all_residues = () self.custom_residues = () self.charged_residues = tuple( res for res in protein.CHARGED_RESIDUES if res in self.ALL_RESIDUES) self.aromatic_residues = tuple(res for res in protein.AROMATIC_RESIDUES if res in self.ALL_RESIDUES) self.polar_residues = tuple( [res for res in protein.POLAR_RESIDUES if res in self.ALL_RESIDUES]) self.uncharged_polar_residues = tuple( res for res in self.polar_residues if res not in self.charged_residues) self.nonpolar_residues = tuple( res for res in self.ALL_RESIDUES if res not in self.polar_residues) self.tiny_residues = TINY_RESIDUES self.small_residues = SMALL_RESIDUES self.aliphatic_residues = ALIPHATIC_RESIDUES self.updateNonStandardResidues()
[docs] def updateAllResidues(self): """ Restore `all_residues` to contain standard as well as custom residues. """ self.all_residues = self.ALL_RESIDUES + self.custom_residues
[docs] def validateResidueName(self, res_name): """ Used to check whether a residue can be a mutated from or to. :param res_name: The three letter name of the residue :type res_name: str :rtype: tuple of (bool, str) :return: A tuple with a bool indicating validity and a string providing an explanation """ if res_name not in self.all_residues: return False, UNRECOGNIZED_TXT return True, ''
[docs] def getHistidineType(self, res): """ Returns a HID/HIP/HIE string depending on the protonation of the HIS residue. Note that this will not handle cases where the residue is missing some side chain atoms. :param res: The residue to assess :type res: `schrodinger.structure._Residue` :rtype: str :return: The three letter code for the residue """ n_epsilon_atom_protonated = False n_delta_atom_protonated = False for atom in res.atom: name = atom.pdbname.strip() if name == 'NE2': n_epsilon_atom_protonated = (atom.bond_total == 3) elif name == 'ND1': n_delta_atom_protonated = (atom.bond_total == 3) if n_epsilon_atom_protonated and n_delta_atom_protonated: return 'HIP' if n_epsilon_atom_protonated: return 'HIE' return 'HID'
[docs] def getLysineType(self, res): """ Returns a LYS/LYN string depending on the protonation of the LYS residue. :param res: The residue to assess :type res: `schrodinger.structure._Residue` :rtype: str :return: The three letter code for the residue """ nz_atom = find_atom_with_pdb_name(res, 'NZ') if nz_atom: return 'LYS' if nz_atom.bond_total == 4 else 'LYN' return 'UNK'
[docs] def getAspartateType(self, res): """ Returns a ASP/ASH string depending on the protonation of the ASP residue. :param res: The residue to assess :type res: `schrodinger.structure._Residue` :rtype: str :return: The three letter code for the residue """ od1_atom = find_atom_with_pdb_name(res, 'OD1') od2_atom = find_atom_with_pdb_name(res, 'OD2') if od1_atom and od2_atom: bond_total = od1_atom.bond_total + od2_atom.bond_total return 'ASH' if bond_total == 3 else 'ASP' return 'UNK'
[docs] def getGlutamateType(self, res): """ Returns a GLU/GLH string depending on the protonation of the GLU residue. :param res: The residue to assess :type res: `schrodinger.structure._Residue` :rtype: str :return: The three letter code for the residue """ oe1_atom = find_atom_with_pdb_name(res, 'OE1') oe2_atom = find_atom_with_pdb_name(res, 'OE2') if oe1_atom and oe2_atom: bond_total = oe1_atom.bond_total + oe2_atom.bond_total return 'GLH' if bond_total == 3 else 'GLU' return 'UNK'
[docs] def getArginineType(self, res): """ Returns a ARG/ARN string depending on the protonation of the ARG residue. :param res: The residue to assess :type res: `schrodinger.structure._Residue` :rtype: str :return: The three letter code for the residue """ nh1_atom = find_atom_with_pdb_name(res, 'NH1') nh2_atom = find_atom_with_pdb_name(res, 'NH2') if nh1_atom and nh2_atom: bond_total = nh1_atom.bond_total + nh2_atom.bond_total return 'ARN' if bond_total == 5 else 'ARG' return 'UNK'
[docs] def getThreeLetterResidueCode(self, res): """ Returns the three letter code for the residue, where the histidine type is determined based on its protonation state. :param res: The residue to assess :type res: `schrodinger.structure._Residue` :rtype: str :return: The three letter code for the residue """ three_letter_code = res.pdbres.strip() if three_letter_code in ('HIS', 'HID', 'HIE', 'HIP'): return self.getHistidineType(res) elif three_letter_code in ('LYS', 'LYN'): return self.getLysineType(res) elif three_letter_code in ('ASP', 'ASH'): return self.getAspartateType(res) elif three_letter_code in ('GLU', 'GLH'): return self.getGlutamateType(res) elif three_letter_code in ('ARG', 'ARN'): return self.getArginineType(res) return three_letter_code
[docs] def disallowedMutations(self, res_name): """ Get all mutations that the residue is not allowed to mutate into. :param res_name: The three letter name of the residue :type res_name: str :return: A list of tuples with a residue name and a string providing an explanation as to why the mutation is not allowed :rtype: List[Tuple[str, str]] """ invalid = [] for res2 in self.all_residues: validation = self._validateMutation(res_name, res2) valid, msg = validation if not valid: invalid.append((res2, msg)) return invalid
def _validateMutation(self, res_name1, res_name2): """ Checks whether a res1 and res2 are allowed to mutate from/to each other. :param res_name1: residue 1 :type res_name1: str :param res_name2: residue 2 :type res_name2: str :return: Whether the mutation is valid and a string providing an explanation :rtype: Tuple[bool, str] """ if res_name1 == res_name2: return False, SAME_TXT if res_name2 == 'HIS': return False, HIS_TXT return True, ''
[docs] def updateNonStandardResidues(self): """ Updated the internal state from the non-standard residue database. """ residues = nsr.get_db_residues() self._setNonStandardResidues(residues)
def _setNonStandardResidues(self, residues): """ Set the stored custom residue name list to contain `residues`. :param residues: a list of custom residues :type residues: list[non_standard_residues.AminoAcid] """ custom_residues = nsr.filter_residues_mutating_to_custom(residues) self.custom_residues = tuple(res.name for res in custom_residues) self.updateAllResidues()
[docs]class GridLayoutPopUp(pop_up_widgets.PopUp): """ Popup dialog with grid layout. """
[docs] def setup(self): layout = QtWidgets.QGridLayout(self) self.setLayout(layout)
[docs]class HyperlinkWithArrowWithPopUp(pop_up_widgets._AbstractButtonWithPopUp, hyperlink.ArrowLink): pass
[docs]class CustomResidueMixin: """ Allow class to set custom residue, assign its own residue tooltip if no other tooltip is assigned. Must be subclassed by a `QWidget`. """
[docs] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._res = None
[docs] def event(self, event): # Generating tooltip containing 2D structure image is a time consuming # operation. Hence, for custom residue checkboxes it is done when the # user hovers over the checkbox for the first time if isinstance(event, QtGui.QHoverEvent) and not self.toolTip(): self.setToolTip(tooltips.tooltip_for_aa(self._res)) return super().event(event)
[docs] def setResidue(self, res): """ :param res: non-standard amino acid (custom residue) :type res: nonstandard_residues.AminoAcid """ self._res = res
[docs]class CustomResidueCheckBox(CustomResidueMixin, QtWidgets.QCheckBox): pass
[docs]class CustomResidueRadioButton(CustomResidueMixin, QtWidgets.QRadioButton): pass
[docs]class MutationSelectorMixin: """ This mixin should be used with a `QtWidgets.QFrame`. It contains widgets for selecting residue type(s) (e.g. ALA, VAL, and GLY). """ # Gets emitted when the residue selection changes. Value is a set of # residue strings: dataChanged = QtCore.pyqtSignal(set)
[docs] def getResidueGroups(self): # TODO: Figure out how to make the list of custom residues be either # and argument to the constructor/setup, or as a setter method that # can be accessed from the GUI (not sure if that would be possible). return ResidueGroups()
def _add_hline(self): line = QtWidgets.QFrame(self) line.setFrameShape(QtWidgets.QFrame.HLine) line.setFrameShadow(QtWidgets.QFrame.Sunken) self.toplevel_layout.addWidget(line) return line
[docs] def __init__(self, parent=None, single_mutation_only=False): """ :param parent: the parent widget :type parent: QtWidget.QWidget or NoneType :param single_mutation_only: whether only a single mutation is allowed. :type single_mutation_only: bool """ self._single_mutation_only = single_mutation_only super().__init__(parent) self.resize(737, 520) self.toplevel_layout = QtWidgets.QVBoxLayout(self) # Standard residues header section self.std_res_header_layout = QtWidgets.QHBoxLayout() self.toplevel_layout.addLayout(self.std_res_header_layout) if self._single_mutation_only: widget = QtWidgets.QLabel('Standard') else: widget = QtWidgets.QCheckBox('Standard') widget.stateChanged.connect(self.applyGroupSelections) self.std_res_header_layout.addWidget(widget) self.standard_widget = widget self.std_spacer_item = QtWidgets.QSpacerItem( 10, 10, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) self.std_res_header_layout.addSpacerItem(self.std_spacer_item) if not self._single_mutation_only: self.std_res_grp_btn = HyperlinkWithArrowWithPopUp(parent=self) self.std_res_grp_btn.setText('Select') self.std_res_pop_up = GridLayoutPopUp(self) self.std_res_grp_btn.setPopUp(self.std_res_pop_up) self.std_res_header_layout.addWidget(self.std_res_grp_btn) else: self.std_res_pop_up = None # Standard residue check boxes: self.std_residue_layout = QtWidgets.QGridLayout() self.toplevel_layout.addLayout(self.std_residue_layout) self._add_hline() # Custom residues header section self.custom_res_header_layout = QtWidgets.QHBoxLayout() # Setting the top margin to create some space between this layout and # the layout which is just above it self.custom_res_header_layout.setContentsMargins(0, 10, 0, 0) self.toplevel_layout.addLayout(self.custom_res_header_layout) if self._single_mutation_only: widget = QtWidgets.QLabel('Non-standard') else: widget = QtWidgets.QCheckBox('Non-standard') widget.stateChanged.connect(self.applyGroupSelections) self.custom_res_header_layout.addWidget(widget) self.non_standard_widget = widget self.custom_spacer_item = QtWidgets.QSpacerItem( 10, 10, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) self.custom_res_header_layout.addSpacerItem(self.custom_spacer_item) self.custom_res_search_le = swidgets.SLineEdit(show_search_icon=True) self.custom_res_search_le.textChanged.connect(self._onSearchTextChanged) self.custom_res_header_layout.addWidget(self.custom_res_search_le) # Custom residues layout: self.custom_res_scroll_area = QtWidgets.QScrollArea(self) self.custom_res_scroll_area.setWidgetResizable(True) self.custom_res_scroll_area.setVerticalScrollBarPolicy( QtCore.Qt.ScrollBarAsNeeded) self.custom_res_scroll_area.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarAlwaysOff) custom_res_scroll_widget = QtWidgets.QWidget() self.custom_res_scroll_area.setMaximumHeight(150) self.custom_residue_layout = QtWidgets.QGridLayout() custom_res_scroll_widget.setLayout(self.custom_residue_layout) self.custom_res_scroll_area.setWidget(custom_res_scroll_widget) self.toplevel_layout.addWidget(self.custom_res_scroll_area) # Create bottom buttons: self.bottom_btn_layout = QtWidgets.QHBoxLayout() self.toplevel_layout.addLayout(self.bottom_btn_layout) if self._single_mutation_only: self.deselect_all_btn = hyperlink.SimpleLink("Clear") else: self.deselect_all_btn = hyperlink.SimpleLink("Clear All") self.deselect_all_btn.clicked.connect(self.deselectAll) self.bottom_btn_layout.addWidget(self.deselect_all_btn) self.bottom_spacer_item = QtWidgets.QSpacerItem( 10, 10, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) self.bottom_btn_layout.addSpacerItem(self.bottom_spacer_item) self.close_btn = QtWidgets.QPushButton('Close') if isinstance(self, pop_up_widgets.PopUp): # a PopUp requires at least one strong focus widget for closing # behavior to work properly self.close_btn.setFocusPolicy(Qt.StrongFocus) self.close_btn.clicked.connect(self.close) self.bottom_btn_layout.addWidget(self.close_btn) self.setupToggles() self.setupToolTips()
def _onSearchTextChanged(self, search_text): """ Display only those custom residues which match the search text. To achieve above goal we hide the custom residue checkboxes which didn't match the search results and unhide those which were matched. Also we keep only the checkboxes for matched results in the custom residue layout (QGridLayout) and remove the rest. :param search_text: search query :type search_text: str """ search_text = search_text.strip() filtered_residues = { res for res in self.residue_groups.custom_residues if search_text in res } _remove_all_widgets(self.custom_residue_layout) self._addResiduesToLayout(filtered_residues, self.custom_residue_layout) def _setupGroupMappingCheckBoxes(self): """ Update the `residue_groups_mapping` dictionary as well as the checkboxes associated with them. The checkboxes are added to `self._toggles` as well """ group_layout = self.std_res_pop_up.layout() clear_layout(group_layout) # Setup residue groups/types mixed-state toggles: labels_for_groups = [ ("Aromatic", self.residue_groups.aromatic_residues), ("Aliphatic", self.residue_groups.aliphatic_residues), ("Polar (Not Charged)", self.residue_groups.uncharged_polar_residues), ("Charged", self.residue_groups.charged_residues), ("NonPolar (Hydrophobic)", self.residue_groups.nonpolar_residues), ("Tiny", self.residue_groups.tiny_residues), ("Small", self.residue_groups.small_residues)] # yapf:disable for label, res_list in labels_for_groups: if not res_list: continue cb = QtWidgets.QCheckBox(label) cb.setTristate(True) cb.setCheckState(Qt.Unchecked) cb.stateChanged.connect(self.applyGroupSelections) self.residue_groups_mapping[cb] = res_list self._toggles[label] = cb # FIXME: we should find a way to populate this popup without adding/ # removing widgets from its parent panel. PANEL-18988 # Add groups checkboxes to layout: group_cbs = list(self.residue_groups_mapping) for idx, checkbox in enumerate(group_cbs): row_idx = idx // 2 col_idx = idx % 2 group_layout.addWidget(checkbox, row_idx, col_idx)
[docs] def setupToggles(self): """ Populate the std residues, custom residues, and groups layouts with toggles based on the current residue groups. Can be called to re-populate the pop-up widget if the return value of getResidueGroups() of the subclass has changed. """ # Clear the dicts so that previous widgets can be deleted: self.residue_groups_mapping = collections.OrderedDict() self._toggles = {} # Clear previous contents of layouts: for layout in (self.std_residue_layout, self.custom_residue_layout): clear_layout(layout) self.residue_groups = self.getResidueGroups() self._all_residues = self.residue_groups.all_residues custom_residues = self.residue_groups.custom_residues std_residues = [ res for res in self._all_residues if res not in custom_residues ] if self._single_mutation_only: std_res_cb_class = QtWidgets.QRadioButton custom_res_cb_class = CustomResidueRadioButton else: std_res_cb_class = QtWidgets.QCheckBox custom_res_cb_class = CustomResidueCheckBox # Note the `NONE_RESIDUE` should not be in `_all_residues` since it is # not really a residue for res in self._all_residues + (NONE_RESIDUE,): text = res if len(res) <= 3 else f'{res[:3]}…' is_std_res = res in std_residues cb_class = std_res_cb_class if is_std_res else custom_res_cb_class cb = cb_class(text) cb.toggled.connect(self.updateSelections) self._toggles[res] = cb if self._single_mutation_only: btn_group = QtWidgets.QButtonGroup(self) for btn in self._toggles.values(): btn_group.addButton(btn) # Add standard residues checkboxes to layout: self._addResiduesToLayout(std_residues, self.std_residue_layout) # Add custom residues checkboxes to layout: if custom_residues: self._addResiduesToLayout(custom_residues, self.custom_residue_layout) else: self._hideBottomHalf() if not self._single_mutation_only: self._setupGroupMappingCheckBoxes() self.residue_groups_mapping[self.standard_widget] = std_residues self.residue_groups_mapping[ self.non_standard_widget] = custom_residues
[docs] def setupToolTips(self): self._tooltip_template = '<html>%s<img src=":/residue_images/%s"/></html>' self._residues_with_image_tooltips = ['HID', 'HIE', 'HIP'] self._setImageToolTips()
def _setImageToolTip(self, res_name): """ Sets an image, if available, for the tooltip. This preserves the text already set for the tooltip. Note that this should not be called twice, since after an image has been set in the tooltip, the link becomes part of the tooltip text. :param: res_name: The name of the residue :type: res_name: str """ res_toggle = self.getResidueToggle(res_name) tt_txt = res_toggle.toolTip() if tt_txt: tt_txt = tt_txt + "<br />" path = res_name + ".png" tooltip = self._tooltip_template % (tt_txt, path) res_toggle.setToolTip(tooltip) def _setImageToolTips(self): """ Convenience method that sets all the images for tooltips """ for res_name in self._residues_with_image_tooltips: self._setImageToolTip(res_name) def _hideBottomHalf(self): """ Hide the widgets which lie in the bottom half of the popup. This method is called when there are no custom residues. """ self.custom_res_scroll_area.hide() self.non_standard_widget.hide() self.custom_res_search_le.hide() self.custom_res_header_layout.setContentsMargins(0, 0, 0, 0) def _addResiduesToLayout(self, res_names, layout): """ Add checkboxes with the given names to the given QGridLayout and set them visible. :param res_names: tuple of residue names :type res_names: tuple(str) :param layout: a QGridLayout layout instance :type layout: QtWidgets.QGridLayout """ for i, res in enumerate(sorted(res_names)): if i % 5 == 0: row = old_div(i, 5) toggle = self._toggles[res] layout.addWidget(toggle, row, i % 5) toggle.setVisible(True)
[docs] def getResidueToggle(self, residue_name): """ Return the ui element for the residue or residue group. :param residue_name: The name of the residue :type residue_name: str :return: the toggle for the residue :rtype: QtWidgets.QCheckBox or QtWidgets.QRadioButton """ return self._toggles[residue_name]
[docs] def updateSelections(self): """ Collect checked residues and then add any residues selected via the group checkboxes. Emits a set of selected residues. """ selections = set() disabled = set() for residue_name in self._all_residues: ui_element = self.getResidueToggle(residue_name) if ui_element.isChecked(): selections.add(residue_name) if not ui_element.isEnabled(): disabled.add(residue_name) # Now update other group boxes; *all* allowable members of the group # need to be checked for a group checkbox to be checked. If some # members of a group are checked, the group checkbox is partially # checked for ui_element, residue_group in self.residue_groups_mapping.items(): enabled_residues_in_group = set( [res for res in residue_group if res not in disabled]) selected_residues_in_group = selections & enabled_residues_in_group checkstate = Qt.Unchecked if enabled_residues_in_group == selected_residues_in_group: checkstate = Qt.Checked elif selected_residues_in_group: checkstate = Qt.PartiallyChecked if ui_element.checkState() != checkstate: with suppress_signals(ui_element): ui_element.setCheckState(checkstate) self.dataChanged.emit(selections)
[docs] def setSelectedMutations(self, residues): """ :param residues: The residues selected by the user :type residues: set of str Checks the checkboxes corresponding to the given set of residue strings, and unchecks the other checkboxes. Connect to the dataChanged signal to get updates on the selected mutations. """ for residue_name in self._all_residues: toggle = self.getResidueToggle(residue_name) check = toggle.isEnabled() and residue_name in residues toggle.setChecked(check) self.updateSelections()
[docs] def selectResidueToggle(self, residue_name, check=True): """ Selects (or deselects, if False) a single residue checkbox :param residue_name: The name of the residue corresponding to the checkbox :type residue_name: str :param check: Whether to check or uncheck the checkbox :type check: bool """ res_toggle = self.getResidueToggle(residue_name) with suppress_signals(res_toggle): if res_toggle.isEnabled(): res_toggle.setChecked(check)
[docs] def selectAll(self): """ Selects every enabled residue toggle in the pop up """ for residue_name in self._all_residues: self.selectResidueToggle(residue_name) self.updateSelections()
[docs] def applyGroupSelections(self): """ Examines the relevant group checkboxes to determine which residues should be selected and selects them. Our groups obey the following rules: * no residues in a group selected -> group deselected * some but not all residues in a group selected -> group partially selected * all residues in a group selected -> group selected * a user selects a group -> group selected * a user deselects a group -> group deselected In other words, for users, the groups function as dual state, but in terms of display, the groups are tristate. Note that this function can only be called as a signal, because it needs to call self.sender() """ sender = self.sender() residue_group = self.residue_groups_mapping[sender] checkstate = sender.checkState() check_residue_in_group = True if checkstate == Qt.Unchecked: check_residue_in_group = False elif checkstate == Qt.PartiallyChecked: # The user checked an unchecked box. Since it's tristate, the cb # is now in a partially unchecked state. We correct this, since the # user expects to fully check the box with a single click with suppress_signals(sender): sender.setCheckState(Qt.Checked) for residue_name in residue_group: self.selectResidueToggle(residue_name, check_residue_in_group) self.updateSelections()
[docs] def deselectAll(self): """ Deselects all the residue checkboxes if the None convenience checkbox has been selected :param checked: Whether the None checkbox is checked :type checked: bool """ for residue_name in self._all_residues: self.selectResidueToggle(residue_name, False) if self._single_mutation_only: self.selectResidueToggle(NONE_RESIDUE, True) self.updateSelections()
[docs] def disableResidue(self, res_name, tooltip=None): """ Disable the given residue checkbox in the pop-up dialog. """ res_toggle = self.getResidueToggle(res_name) res_toggle.setEnabled(False) if tooltip: res_toggle.setToolTip(tooltip) if tooltip and res_name in self._residues_with_image_tooltips: self._setImageToolTip(res_name)
[docs] def clear(self): """ De-select all residues. """ self.deselectAll()
[docs] def disableResidues(self, disable_residues): """ :type disable_residues: List of (str, str) tuples. :param disable_residues: List of residues to disable. Each item is a tuple of (res_name, tooltip). """ for res_name, tooltip in disable_residues: self.disableResidue(res_name, tooltip)
[docs]class MutationSelectorFrame(MutationSelectorMixin, QtWidgets.QFrame): """ Simple mutation selector frame """
[docs]class MutationSelectorPopUp(MutationSelectorMixin, pop_up_widgets.PopUp): """ A popup editor for selecting residue mutation targets. The editor has a checkbox for each residue, along with convenience checkboxes that allow the user to toggle multiple residues that match a description (e.g., polar). """ # We must define it here as well, otherwise the signature of the PopUp # class's dataChanged signal will get used: dataChanged = QtCore.pyqtSignal(set)
[docs] def setup(self): pass
[docs] def lineEditUpdated(self, mutations_txt): mutations = muts_str_to_list(mutations_txt) self.setSelectedMutations(mutations)
[docs]class MutationSelectorLineEdit(pop_up_widgets.LineEditWithPopUp): """ This line edit will show a residue type selection pop-up box when clicked. It can be used in a Qt table by defining a custom Delegate, for example: class MutationSelectorDelegate(pop_up_widgets.PopUpDelegate): def _createEditor(self, parent, option, index): return MutationSelectorLineEdit(parent) def setModelData(self, editor, model, index): mutations = editor.getSelectedMutations() model.setData(index, mutations) """
[docs] def __init__(self, parent, pop_up_class=MutationSelectorPopUp): super(MutationSelectorLineEdit, self).__init__(parent, pop_up_class=pop_up_class) self.setReadOnly(True)
[docs] def popUpUpdated(self, mutations): """ Over-ride the popUpUpdated() method of MutationSelectorLineEdit. Callback that passes the selected mutations back to the line edit. :param mutations: The mutations selected by the user in the pop up :type mutations: set of str """ self.setSelectedMutations(mutations)
[docs] def setSelectedMutations(self, mutations): """ Set the mutations that should be selected. :param mutations: The mutations selected by the user in the pop up :type mutations: set of str """ mutation_text = ", ".join(sorted(list(mutations))) self.setText(mutation_text)
[docs] def getSelectedMutations(self): """ Get the mutations that have been selected by the user in the popup :rtype: set of str :return: Set of strings representing the user's residue mutation selections """ return set(muts_str_to_list(self.text()))
[docs] def disableResidues(self, disable_residues): """ :type disable_residues: List of (str, str) tuples. :param disable_residues: List of residues to disable. Each item is a tuple of (res_name, tooltip). """ self._pop_up.disableResidues(disable_residues)
[docs]class MutationSelectorComboBox(pop_up_widgets.ComboBoxWithPopUp): """ This combo box will show a residue type selection pop-up box when clicked. """
[docs] def __init__(self, parent, pop_up_class=MutationSelectorPopUp): super(MutationSelectorComboBox, self).__init__(parent, pop_up_class) self.addItem("")
[docs] def popUpUpdated(self, mutations): """ Over-ride the popUpUpdated() method of MutationSelectorComboBox. Callback that passes the selected mutations back to the line edit. :param mutations: The mutations selected by the user in the pop up :type mutations: set of str """ mutation_text = ", ".join(sorted(list(mutations))) self.setItemText(0, mutation_text)
[docs] def setSelectedMutations(self, mutations): """ Set the mutations that should be selected. :param mutations: The mutations selected by the user in the pop up :type mutations: set of str """ self._pop_up.setSelectedMutations(set(mutations))
[docs] def getSelectedMutations(self): """ Get the mutations that have been selected by the user in the popup :rtype: set of str :return: Set of strings representing the user's residue mutation selections """ return set(muts_str_to_list(self.currentText()))
[docs] def disableResidues(self, disable_residues): """ :type disable_residues: List of (str, str) tuples. :param disable_residues: List of residues to disable. Each item is a tuple of (res_name, tooltip). """ self._pop_up.disableResidues(disable_residues)
[docs]class CustomResMutationSelectorMixin(MutationSelectorMixin): """ A mutation selector mixin that allows custom residues. """
[docs] def setupToggles(self): super().setupToggles() self._setupStructures()
def _setupStructures(self): """ Setup residue attribute for custom residue toggles. """ residues = nsr.get_db_residues() residues = nsr.filter_residues_mutating_to_custom(residues) for residue in residues: self.getResidueToggle(residue.name).setResidue(residue)
[docs]class CustomResMutationSelectorFrame(CustomResMutationSelectorMixin, QtWidgets.QFrame): """ Mutation selector frame that allows custom residues. Used in the "Residue Selection..." dialog and by `CustomResMutationSelectorPopUp` (the 2 other residue selectors). """
[docs]class CustomResMutationSelectorPopUp(CustomResMutationSelectorMixin, MutationSelectorPopUp): """ Custom mutation pop up that allows custom residues. """