Source code for ccpn.ui.gui.modules.ResidueInformation
#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (http://www.ccpn.ac.uk) 2014 - 2022"
__credits__ = ("Ed Brooksbank, Joanna Fox, Victoria A Higman, Luca Mureddu, Eliza Płoskoń",
"Timothy J Ragan, Brian O Smith, Gary S Thompson & Geerten W Vuister")
__licence__ = ("CCPN licence. See http://www.ccpn.ac.uk/v3-software/downloads/license")
__reference__ = ("Skinner, S.P., Fogh, R.H., Boucher, W., Ragan, T.J., Mureddu, L.G., & Vuister, G.W.",
"CcpNmr AnalysisAssign: a flexible platform for integrated NMR analysis",
"J.Biomol.Nmr (2016), 66, 111-124, http://doi.org/10.1007/s10858-016-0060-y")
#=========================================================================================
# Last code modification
#=========================================================================================
__modifiedBy__ = "$modifiedBy: Geerten Vuister $"
__dateModified__ = "$dateModified: 2022-02-01 15:30:07 +0000 (Tue, February 01, 2022) $"
__version__ = "$Revision: 3.0.4 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: CCPN $"
__date__ = "$Date: 2017-04-07 10:28:41 +0000 (Fri, April 07, 2017) $"
#=========================================================================================
# Start of code
#=========================================================================================
from PyQt5 import QtGui, QtWidgets
from functools import partial
from ccpn.core.lib.AssignmentLib import CCP_CODES
from ccpn.core.lib.Notifiers import Notifier
from ccpn.ui.gui.modules.CcpnModule import CcpnModule
from ccpn.ui.gui.widgets.Label import Label, ActiveLabel
from ccpn.ui.gui.widgets.Button import Button
from ccpn.ui.gui.widgets.Spacer import Spacer
from ccpn.ui.gui.widgets.Widget import Widget
from ccpn.ui.gui.widgets.PulldownList import PulldownList
from ccpn.ui.gui.widgets.ScrollArea import ScrollArea
from ccpn.ui.gui.widgets.Splitter import Splitter
from ccpn.ui.gui.widgets.Frame import Frame
from ccpn.ui.gui.widgets.SettingsWidgets import StripPlot
from ccpn.ui.gui.widgets.MessageDialog import showWarning
from ccpn.ui.gui.widgets.PulldownListsForObjects import ChainPulldown
from ccpn.ui.gui.widgets.VLine import VLine
from ccpn.ui.gui.widgets.SequenceWidget import SequenceWidget
from ccpn.core.Chain import Chain
from ccpn.ui.gui.guiSettings import getColours
from ccpn.ui.gui.guiSettings import LABEL_SELECTEDBACKGROUND, LABEL_SELECTEDFOREGROUND, LABEL_HIGHLIGHT
from ccpn.ui.gui.lib.StripLib import navigateToNmrResidueInDisplay, navigateToNmrAtomsInStrip, _getCurrentZoomRatio
from ccpn.util.Logging import getLogger
from ccpn.ui.gui.widgets.Font import setWidgetFont
logger = getLogger()
ALL = '<all>'
LINKTOPULLDOWNCLASS = 'linkToPulldownClass'
[docs]class ResidueInformation(CcpnModule):
"""
This class implements the module for a residue table and sequence module
"""
includeSettingsWidget = True
maxSettingsState = 2
settingsPosition = 'left'
className = 'ResidueInformation'
includeDisplaySettings = False
includeSequentialStrips = False
includePeakLists = False
includeNmrChains = False
includeSpectrumTable = False
activePulldownClass = Chain
_residueWidth = '3'
textOptions = ['1', '3', '5', '7']
textColumns = {'1': ([1], [0, 2]),
'3': ([0, 2, 4], (1, 3)),
'5': ([0, 1, 3, 5, 6], (2, 4)),
'7': ([0, 1, 2, 4, 6, 7, 8], (3, 5)),
}
def __init__(self, mainWindow, name='Residue Information', chain=None, **kwds):
CcpnModule.__init__(self, mainWindow=mainWindow, name=name)
# Derive application, project, and current from mainWindow
self.mainWindow = mainWindow
if self.mainWindow:
self.application = mainWindow.application
self.project = mainWindow.application.project
self.current = mainWindow.application.current
else:
self.application = None
self.project = None
self.current = None
self._moduleSettings = StripPlot(parent=self.settingsWidget, mainWindow=self.mainWindow,
includeDisplaySettings=self.includeDisplaySettings,
includeSequentialStrips=self.includeSequentialStrips,
includePeakLists=self.includePeakLists,
includeNmrChains=self.includeNmrChains,
includeSpectrumTable=self.includeSpectrumTable,
activePulldownClass=self.activePulldownClass,
grid=(0, 0))
# add a splitter to contain the residue table and the sequence module
self.splitter = Splitter(self.mainWidget, horizontal=False)
self._sequenceWidgetFrame = Frame(None, setLayout=True)
# self._SequenceGraphFrame = Frame(self.splitter, setLayout=True)
self.mainWidget.getLayout().addWidget(self.splitter, 1, 0)
# initialise the sequence module
self.thisSequenceWidget = SequenceWidget(moduleParent=self,
parent=self._sequenceWidgetFrame,
mainWindow=mainWindow,
chains=self.project.chains)
# add a scroll area to contain the residue table
self._widgetScrollArea = ScrollArea(parent=self.mainWidget, grid=(0, 0), scrollBarPolicies=('asNeeded', 'asNeeded'), **kwds)
self._widgetScrollArea.setWidgetResizable(True)
self._widget = Widget(parent=self._widgetScrollArea, setLayout=True)
self._widgetScrollArea.setWidget(self._widget)
self._widget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
# insert into the mainWidget
self.splitter.addWidget(self._widgetScrollArea)
self.splitter.addWidget(self._sequenceWidgetFrame)
self.splitter.setStretchFactor(0, 5)
self.splitter.setChildrenCollapsible(False)
# make a frame to contain the pulldown widgets
self._pulldownFrame = Frame(self._widget, setLayout=True, showBorder=False, grid=(0, 0))
# # insert into pulldownFrame
# self.chainLabel = Label(self._pulldownFrame, text='Chain', grid=(0, 0))
# # self.layout.addWidget(chainLabel, 0, 0)
# chainPulldown = PulldownList(self._pulldownFrame, callback=self._setChain, grid=(0, 1))
# chainPulldownData = [chain.pid for chain in self.project.chains]
# # chainPulldownData.append(ALL)
# chainPulldown.setData(chainPulldownData)
self.chainPulldown = ChainPulldown(parent=self._pulldownFrame,
mainWindow=self.mainWindow, default=None, #first Chain in project (if present)
grid=(0, 0), gridSpan=(1, 1), minimumWidths=(0, 100),
showSelectName=True,
sizeAdjustPolicy=QtWidgets.QComboBox.AdjustToContents,
callback=self._selectionPulldownCallback
)
self.selectedChain = None #self.project.getByPid(self.chainPulldown.currentText())
self.residueLabel = Label(self._pulldownFrame, text='Residue', grid=(0, 3))
self.colourScheme = self.application._colourScheme
self.residuePulldown = PulldownList(self._pulldownFrame, callback=self._setCurrentResidue,
grid=(0, 4))
self._residueWidthLabel = Label(self._pulldownFrame, text='Residue window width', grid=(0, 5))
self._residueWidthData = PulldownList(self._pulldownFrame, grid=(0, 6))
self._residueWidthData.setData(texts=self.textOptions)
self._residueWidthData.set(self._residueWidth)
self.residuePulldown.setData(sorted(CCP_CODES))
self.selectedResidueType = self.residuePulldown.currentText()
# set the callback after populating
self._residueWidthData.setCallback(self._setResidueWidth)
# add under the pulldownFrame
self.residueWidget = Widget(self._widget, setLayout=True,
grid=(1, 0), gridSpan=(1, 2))
self.spacer = Spacer(self._widget, 5, 5,
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding,
grid=(2, 3), gridSpan=(1, 1))
self._pulldownFrame.setContentsMargins(0, 5, 5, 5)
self._widget.setContentsMargins(5, 0, 5, 0)
if chain is not None:
self._selectChain(chain)
self._getResidues()
# set the notifies for current chain
# TODO: put into subclass
self._activePulldownClass = None
self._activeCheckbox = None
self._setCurrentPulldownNotifier = None
if self.activePulldownClass:
self._setCurrentPulldownNotifier = Notifier(self.current,
[Notifier.CURRENT],
targetName=self.activePulldownClass._pluralLinkName,
callback=self._selectCurrentPulldownClass)
# put these in a smaller additional class
if self.activePulldownClass:
self._activePulldownClass = self.activePulldownClass
self._activeCheckbox = getattr(self._moduleSettings, LINKTOPULLDOWNCLASS, None)
def _selectCurrentPulldownClass(self, data):
"""Respond to change in current activePulldownClass
"""
if self.activePulldownClass and self._activeCheckbox and self._activeCheckbox.isChecked() and self.current.chain:
self._selectChain(self.current.chain)
def _selectChain(self, chain=None):
"""Manually select a Chain from the pullDown
"""
if chain is None:
# logger.warning('select: No Chain selected')
# raise ValueError('select: No Chain selected')
self.chainPulldown.selectFirstItem()
else:
if not isinstance(chain, Chain):
logger.warning('select: Object is not of type Chain')
raise TypeError('select: Object is not of type Chain')
else:
for widgetObj in self.chainPulldown.textList:
if chain.pid == widgetObj:
self.selectedChain = chain
self.chainPulldown.select(self.selectedChain.pid)
def _setResidueWidth(self, *args):
self._residueWidth = self._residueWidthData.get()
self._getResidues()
def _selectionPulldownCallback(self, item):
"""Sets the selected chain to the specified value and updates the module.
"""
if item == ALL:
self.selectedChain = 'All'
else:
self.selectedChain = self.project.getByPid(item)
if self._activePulldownClass and self._activeCheckbox and \
self.selectedChain != self.current.chain and self._activeCheckbox.isChecked():
self.current.chain = self.selectedChain
self._getResidues()
def _setCurrentResidue(self, value: str):
"""Sets the selected residue to the specified value and updates the module.
"""
self.selectedResidueType = value
self._getResidues()
def _setWidgetColour(self, widget):
"""Set the colour for the label
"""
palette = widget.palette()
palette.setColor(QtGui.QPalette.Foreground, QtGui.QColor(LABEL_SELECTEDFOREGROUND))
palette.setColor(QtGui.QPalette.Background, QtGui.QColor(LABEL_SELECTEDBACKGROUND))
widget.setPalette(palette)
def _removeWidget(self, widget, removeTopWidget=False):
"""Destroy a widget and all it's contents
"""
def deleteItems(layout):
if layout is not None:
while layout.count():
item = layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.setVisible(False)
widget.setParent(None)
del widget
deleteItems(widget.getLayout())
if removeTopWidget:
del widget
def _getResidues(self):
"""Finds all residues of the selected type along with one flanking residue either side and displays
this information in the module.
"""
colours = getColours()
stylesheet = """Label { background-color: %s; color: %s;}
Label::hover { background-color: %s}""" % (colours[LABEL_SELECTEDBACKGROUND],
colours[LABEL_SELECTEDFOREGROUND],
colours[LABEL_HIGHLIGHT])
# # self.setDefaultTextColor(QtGui.QColor(self.colours[GUINMRRESIDUE]))
#
# if self.colourScheme == 'dark':
# # stylesheet = 'Label {background-color: #f7ffff; color: #2a3358;}'
# stylesheet = """Label { background-color: %s; color: %s;}
# Label::hover { background-color: %s}""" % (colours[LABEL_SELECTEDBACKGROUND],
# colours[LABEL_SELECTEDFOREGROUND],
# colours[LABEL_SELECTEDFOREGROUND])
# elif self.colourScheme == 'light':
# # stylesheet = 'Label {background-color: #bd8413; color: #fdfdfc;}'
# stylesheet = """Label { background-color: %s; color: %s;}
# Label::hover { background-color: %s}""" % (colours[LABEL_SELECTEDBACKGROUND],
# colours[LABEL_SELECTEDFOREGROUND],
# colours[LABEL_SELECTEDFOREGROUND])
foundResidues = []
if self.selectedChain == 'All':
residues = self.project.residues
else:
if self.selectedChain is not None:
residues = self.selectedChain.residues
else:
residues = []
self._removeWidget(self.residueWidget, removeTopWidget=False)
if residues:
width = int(self._residueWidthData.get()) // 2
for resInd, residue in enumerate(residues):
if residue.residueType == self.selectedResidueType.upper():
# add the previous and next residue chains to the visible list for this residue
resList = [residue]
leftRes = residue
rightRes = residue
for count in range(width):
if leftRes:
resList.insert(0, leftRes.previousResidue)
leftRes = leftRes.previousResidue
else:
resList.insert(0, None)
for count in range(width):
if rightRes:
resList.append(rightRes.nextResidue)
rightRes = rightRes.nextResidue
else:
resList.append(None)
foundResidues.append(resList)
i = rr = 0
textCols = self.textColumns[self._residueWidth][0]
columnCols = self.textColumns[self._residueWidth][1]
for i, checkResidues in enumerate(foundResidues):
for rr in range(int(self._residueWidth)):
if rr >= 0 and rr < len(checkResidues):
if checkResidues[rr] is not None:
item = ActiveLabel(self, mainWindow=self.mainWindow,
text=checkResidues[rr].id,
hAlign='c')
setWidgetFont(item)
item.setSelectionCallback(partial(self._residueClicked, checkResidues[rr]))
if checkResidues[rr].nmrResidue is not None:
item.setStyleSheet(stylesheet)
item.setActionCallback(partial(self._residueDoubleClicked, checkResidues[rr]))
self.residueWidget.layout().addWidget(item, i, textCols[rr])
if i and rr:
# put a couple of lines marking the centre section of the table
for col in columnCols:
item = VLine(self.residueWidget, width=10, grid=(0, col), gridSpan=(i + 1, 1))
self.spacer = Spacer(self.residueWidget, 5, 5,
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding,
grid=(i + 1, textCols[-1] + 1), gridSpan=(1, 1))
def _residueClicked(self, residue):
"""Handle cicking a residue in the table
"""
self.current.residue = residue
def _residueDoubleClicked(self, residue):
"""Handle double-cicking a residue in the table
"""
nmrResidue = residue.nmrResidue
# just copied from nmrResidueTable
from ccpn.core.lib.CallBack import CallBack
data = CallBack(theObject=self.project,
object=nmrResidue,
targetName=nmrResidue.className,
trigger=CallBack.DOUBLECLICK,
)
# handle a single nmrResidue - should always contain an object
objs = data[CallBack.OBJECT]
if not objs:
return
if isinstance(objs, (tuple, list)):
nmrResidue = objs[0]
else:
nmrResidue = objs
logger.debug('nmrResidue=%s' % str(nmrResidue.id if nmrResidue else None))
_settings = self._moduleSettings
displays = []
if self.current.strip:
displays.append(self.current.strip.spectrumDisplay)
if len(displays) == 0 and self._moduleSettings.displaysWidget:
logger.warning('Undefined display module(s); select in settings first')
showWarning('startAssignment', 'Undefined display module(s);\nselect in settings first')
return
from ccpn.core.lib.ContextManagers import undoBlockWithoutSideBar
with undoBlockWithoutSideBar():
# optionally clear the marks
if _settings.autoClearMarksWidget.checkBox.isChecked():
self.application.ui.mainWindow.clearMarks()
newWidths = []
for specDisplay in displays:
if self.current.strip in specDisplay.strips:
# just navigate to this strip
navigateToNmrAtomsInStrip(self.current.strip,
nmrResidue.nmrAtoms,
widths=newWidths,
markPositions=_settings.markPositionsWidget.checkBox.isChecked(),
setNmrResidueLabel=True)
else:
#navigate to the specDisplay (and remove excess strips)
if len(specDisplay.strips) > 0:
newWidths = []
navigateToNmrResidueInDisplay(nmrResidue, specDisplay, stripIndex=0,
widths=newWidths, #['full'] * len(display.strips[0].axisCodes),
showSequentialResidues=(len(specDisplay.axisCodes) > 2) and
self.includeSequentialStrips and
_settings.sequentialStripsWidget.checkBox.isChecked(),
markPositions=_settings.markPositionsWidget.checkBox.isChecked()
)
# open the other headers to match
for strip in specDisplay.strips:
if strip != self.current.strip and not strip.header.headerVisible:
strip.header.reset()
strip.header.headerVisible = True