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 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 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
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 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 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 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).
"""