Source code for ccpn.ui.gui.widgets.SpectrumToolBar
"""Module Documentation here
"""
#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (http://www.ccpn.ac.uk) 2014 - 2021"
__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: Ed Brooksbank $"
__dateModified__ = "$dateModified: 2021-12-23 17:50:23 +0000 (Thu, December 23, 2021) $"
__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 contextlib import contextmanager
from PyQt5 import QtCore, QtGui, QtWidgets
from ccpn.ui.gui.widgets.Menu import Menu
from ccpn.ui.gui.widgets.ToolBar import ToolBar
from functools import partial
from ccpn.core.lib.Notifiers import Notifier
from collections import OrderedDict
from ccpn.ui.gui.widgets.MessageDialog import showWarning
from ccpn.ui.gui.widgets.Font import setWidgetFont, getFontHeight
from ccpn.ui._implementation.PeakListView import PeakListView
from ccpn.ui._implementation.IntegralListView import IntegralListView
from ccpn.ui._implementation.MultipletListView import MultipletListView
from ccpn.core.PeakList import PeakList
from ccpn.core.IntegralList import IntegralList
from ccpn.core.MultipletList import MultipletList
from ccpn.ui.gui.lib.GuiSpectrumView import _spectrumViewHasChanged
from ccpn.ui.gui.popups.SpectrumPropertiesPopup import SpectrumPropertiesPopup
from ccpn.core.lib import Pid
from ccpn.ui.gui.lib.GuiStripContextMenus import _SCMitem, ItemTypes, ITEM, _addMenuItems, _createMenu, _separator
from ccpn.util import Colour
[docs]class SpectrumToolBar(ToolBar):
def __init__(self, parent=None, widget=None, **kwds):
super().__init__(parent=parent, **kwds)
self.widget = widget
self._parent = parent
self.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
self.setMouseTracking(True)
self._spectrumToolBarBlockingLevel = 0
self._firstButton = 0
@property
def isBlocked(self):
"""True if spectrumToolBar is blocked
"""
# read state from widget blocking
return self.widgetIsBlocked
def _paintButtonToMove(self, button):
pixmap = button.grab() # makes a "ghost" of the button as we drag
# below makes the pixmap half transparent
painter = QtGui.QPainter(pixmap)
painter.setCompositionMode(painter.CompositionMode_DestinationIn)
painter.fillRect(pixmap.rect(), QtGui.QColor(0, 0, 0, 127))
painter.end()
return pixmap
def _addSubMenusToContext(self, contextMenu, button):
with self.blockWidgetSignals(recursive=False):
dd = OrderedDict([(PeakList, PeakListView), (IntegralList, IntegralListView), (MultipletList, MultipletListView)])
spectrum = self.widget.project.getByPid(button.actions()[0].objectName())
if spectrum:
for coreObj, viewObj in dd.items():
smenu = contextMenu.addMenu(coreObj.className)
allViews = []
# list to check for duplicate objectLists
_found = []
vv = getattr(self.widget, viewObj._pluralLinkName)
if vv:
for view in vv:
s = coreObj.className
o = getattr(view, s[0].lower() + s[1:])
if o and o._parent == spectrum and o not in _found:
allViews.append(view)
_found.append(o)
views = list(set(allViews))
smenu.setEnabled(len(views) > 0)
menuItems = [_SCMitem(name='Show All',
typeItem=ItemTypes.get(ITEM), icon='icons/null',
callback=partial(self._setVisibleAllFromList, True, smenu, views)),
_SCMitem(name='Hide All',
typeItem=ItemTypes.get(ITEM), icon='icons/null',
callback=partial(self._setVisibleAllFromList, False, smenu, views)),
]
_addMenuItems(self.widget, smenu, menuItems)
# smenu.addAction('Show All', partial(self._setVisibleAllFromList, True, smenu, views))
# smenu.addAction('Hide All', partial(self._setVisibleAllFromList, False, smenu, views))
smenu.addSeparator()
for view in sorted(views, reverse=False):
# print(f' menu views {coreObj} {viewObj} {view}')
ccpnObj = view._childClass
strip = view._parent._parent
toolTip = 'Toggle {0} {1} on strip {2}'.format(coreObj.className, ccpnObj._key, strip.id)
if ccpnObj:
if len(self.widget.strips) > 1: #add shows in which strips the view is
currentTxt = '' # add in which strip is current
if self.widget.current.strip == strip:
currentTxt = ' Current'
action = smenu.addItem('{0} ({1}{2})'.format(ccpnObj.id, strip.id, currentTxt), toolTip=toolTip)
else:
action = smenu.addItem(ccpnObj.id, toolTip=toolTip)
action.setCheckable(True)
if view.isDisplayed:
action.setChecked(True)
action.toggled.connect(view.setDisplayed)
action.toggled.connect(self._updateGl)
def _spectrumToolBarItem(self, button):
spectrum = self.widget.project.getByPid(button.actions()[0].objectName())
specViews = [spv for spv in self.widget.spectrumViews
if spv.spectrum == spectrum]
if len(specViews) >= 1:
return _SCMitem(name='Contours',
typeItem=ItemTypes.get(ITEM), toolTip='Toggle Spectrum Contours On/Off',
callback=partial(self._toggleSpectrumContours, button.actions()[0], specViews[0]),
checkable=True,
checked=specViews[0]._showContours)
def _toggleSpectrumContours(self, buttonAction, specView):
from ccpn.ui.gui.lib.OpenGL.CcpnOpenGL import GLNotifier
specView._showContours = not specView._showContours
_spectrumViewHasChanged({Notifier.OBJECT: specView})
GLSignals = GLNotifier(parent=self)
GLSignals.emitPaintEvent()
def _createToolBarContextMenu(self):
"""
Creates a context menu for the toolbar with general actions.
"""
contextMenu = Menu('', self.widget, isFloatWidget=True)
dd = OrderedDict([(PeakList, PeakListView), (IntegralList, IntegralListView), (MultipletList, MultipletListView)])
for coreObj, viewObj in dd.items():
smenuItems = []
smenu = contextMenu.addMenu(coreObj.className)
labelsBools = OrderedDict([('Show All', True),
('Hide All', False)])
for label, abool in labelsBools.items():
item = _SCMitem(name=label,
typeItem=ItemTypes.get(ITEM), icon='icons/null',
callback=partial(self._toggleAllViews, viewObj._pluralLinkName, abool))
smenuItems.append(item)
_addMenuItems(self.widget, smenu, smenuItems)
return contextMenu
def _toggleAllViews(self, viewPluralLinkName, abool=True):
"""
:param viewPluralLinkName: peakListViews etc which is defined in the self.widget
:param abool: True or False
:return: Toggles all Views on display
"""
for view in getattr(self.widget, viewPluralLinkName):
view.setVisible(abool)
def _createContextMenu(self, button: QtWidgets.QToolButton):
"""
Creates a context menu containing a command to delete the spectrum from the display and its
button from the toolbar.
"""
if not button:
return None
if len(button.actions()) < 1:
return None
contextMenu = Menu('', self.widget, isFloatWidget=True)
_addMenuItems(self.widget, contextMenu, [self._spectrumToolBarItem(button)])
contextMenu.addSeparator()
self._addSubMenusToContext(contextMenu, button)
contextMenu.addSeparator()
menuItems = [_SCMitem(name='Jump on SideBar',
typeItem=ItemTypes.get(ITEM), icon='icons/null',
callback=partial(self._jumpOnSideBar, button)),
_SCMitem(name='Properties...',
typeItem=ItemTypes.get(ITEM), icon='icons/null',
callback=partial(self._showSpectrumProperties, button)),
_SCMitem(name='Remove Spectrum',
typeItem=ItemTypes.get(ITEM), icon='icons/null',
callback=partial(self._removeSpectrum, button)),
]
_addMenuItems(self.widget, contextMenu, menuItems)
return contextMenu
def _jumpOnSideBar(self, button):
spectrum = self.widget.project.getByPid(button.actions()[0].objectName())
if spectrum:
sideBar = self.widget.application.ui.mainWindow.sideBar
sideBar.selectPid(spectrum.pid)
def _showSpectrumProperties(self, button):
spectrum = self.widget.project.getByPid(button.actions()[0].objectName())
mainWindow = self.widget.application.ui.mainWindow
if spectrum: #not sure why It needs parent = mainWindow. copied from SideBar
popup = SpectrumPropertiesPopup(parent=mainWindow, mainWindow=mainWindow, spectrum=spectrum)
popup.exec_()
popup.raise_()
def _updateGl(self, ):
from ccpn.ui.gui.lib.OpenGL.CcpnOpenGL import GLNotifier
GLSignals = GLNotifier(parent=self)
# GLSignals.emitPaintEvent()
GLSignals._emitAxisUnitsChanged(source=None, strip=self.widget.strips[0], dataDict={})
def _getSpectrumViewFromButton(self, button):
spvs = []
key = [key for key, value in self.widget.spectrumActionDict.items() if value == button.actions()[0]][0]
for spectrumView in self.widget.spectrumViews:
if spectrumView.spectrum == key:
spvs.append(spectrumView)
if len(spvs) == 1:
return spvs[0]
def _setVisibleAllFromList(self, abool, menu, views):
"""
:param abool: T or F
:param menu:
:param views: any of Views obj _pluralLinkName
:return:
"""
if views:
for view in views:
view.setVisible(abool)
for action in menu.actions():
if action.text() == view.pid:
action.setChecked(abool)
def _spectrumRename(self, data):
"""Rename the spectrum name in the toolbar from a notifier callback
"""
spectrum = data[Notifier.OBJECT]
trigger = data[Notifier.TRIGGER]
if spectrum and trigger in [Notifier.RENAME]:
oldPid = Pid.Pid(data[Notifier.OLDPID])
oldId = oldPid.id
validActions = [action for action in self.actions() if action.text() == oldId]
for action in validActions:
action.setText(spectrum.id)
action.setObjectName(spectrum.pid)
# setWidgetFont(action, size='SMALL')
def _removeSpectrum(self, button: QtWidgets.QToolButton):
"""
Removes the spectrum from the display and its button from the toolbar.
"""
# event called from right-mouse menu
key = [key for key, value in self.widget.spectrumActionDict.items() if value == button.actions()[0]][0]
for spectrumView in self.widget.spectrumViews:
if spectrumView.spectrum == key:
self.widget.removeSpectrum(spectrumView.spectrum)
break
else:
showWarning('Spectrum', 'Spectrum not found in toolbar')
def _dragButton(self, event):
toolButton = self.childAt(event.pos())
self._buttonBeingDragged = toolButton
allActionsTexts = []
if toolButton:
pixmap = self._paintButtonToMove(toolButton)
mimeData = QtCore.QMimeData()
mimeData.setText('%d,%d' % (event.x(), event.y()))
drag = QtGui.QDrag(self)
drag.setMimeData(mimeData)
drag.setPixmap(pixmap)
# start the drag operation
allActionsTexts = [action.text() for action in self.actions()]
if drag.exec_(QtCore.Qt.MoveAction) == QtCore.Qt.MoveAction:
point = QtGui.QCursor.pos()
nextButton = QtWidgets.QApplication.widgetAt(point)
if isinstance(nextButton, QtWidgets.QToolButton) \
and nextButton.actions() \
and nextButton.actions()[0] in self.actions():
# don't need to move
if nextButton == toolButton:
return
# get the new index and move the spectrum
startInd = allActionsTexts.index(toolButton.text())
endInd = allActionsTexts.index(nextButton.text())
self.widget.moveSpectrumByIndex(startInd, endInd)
return
event.ignore()
[docs] def event(self, event: QtCore.QEvent) -> bool:
"""
Use the event handler to process events
"""
if event.type() == QtCore.QEvent.MouseButtonPress:
# if event.button() == QtCore.Qt.LeftButton: Can't make it working!!!
if event.button() == QtCore.Qt.RightButton:
toolButton = self.childAt(event.pos())
if toolButton:
menu = self._createContextMenu(toolButton)
if menu:
menu.move(event.globalPos().x(), event.globalPos().y() + 10)
menu.exec_()
else:
menu = self._createToolBarContextMenu()
if menu:
menu.move(event.globalPos().x(), event.globalPos().y() + 10)
menu.exec_()
if event.button() == QtCore.Qt.MiddleButton:
self._dragButton(event)
return super().event(event)
def _addNewButton(self, spectrumView):
_actions = {act.objectName(): act for act in self.actions()}
# create new action for any spectrumViews without an action (create spectrumView)
if spectrumView.spectrum.pid not in _actions:
self._setupAction(spectrumView)
[docs] def reorderButtons(self, spectrumViews):
"""Reorder the buttons to match the spectrumView ordering
"""
_actions = {act.objectName(): act for act in self.actions()}
# print(f' reorderButtons {spectrumViews}')
# create new action for any spectrumViews without an action (create spectrumView)
for specView in spectrumViews:
if specView.spectrum.pid not in _actions:
self._setupAction(specView)
# apply the new ordering
_newOrder = [_actions[sv.spectrum.pid] for sv in spectrumViews if sv.spectrum.pid in _actions]
self.insertActions(None, _newOrder)
# reset the sizes of the action widgets - not sure why they change :|
for action in _actions.values():
# get the attached widget
widget = self.widgetForAction(action)
_height1 = max(getFontHeight(size='SMALL') or 12, 12)
widget.setIconSize(QtCore.QSize(_height1 * 10, _height1))
self._setSizes(action)
[docs] def setButtonsFromSpectrumViews(self, spectrumViews):
"""Set up the spectrumDisplay buttons when initialising a display
"""
with self.blockWidgetSignals(recursive=False):
# clear the old actions
# _actions = {act: act.objectName for act in self.actions}
for act in list(self.actions()):
self.removeAction(act)
for specView in spectrumViews:
self._setupAction(specView)
def _setupAction(self, spectrumView):
"""Create and setup a new action attached to the spectrum
"""
import traceback
spectrum = spectrumView.spectrum
spectrumName = spectrum.name
# print(f' _setupAction {spectrumView} {spectrumName}')
# print(traceback.print_stack())
# create new action
_actions = [action for action in self.actions() if action and action.objectName() == spectrum.pid]
if len(_actions) > 1:
raise RuntimeError('Too many toolbar actions with the same name')
if len(_actions) == 1:
action = _actions[0]
else:
action = self.addAction(spectrumName)
action.setObjectName(spectrum.pid)
action.setCheckable(True)
action.setChecked(spectrumView.isDisplayed)
action.setToolTip(spectrum.name)
# get the attached widget
widget = self.widgetForAction(action)
_height1 = max(getFontHeight(size='SMALL') or 12, 12)
widget.setIconSize(QtCore.QSize(_height1 * 10, _height1))
self._setSizes(action)
widget.spectrumView = spectrumView
action.spectrumViewPid = spectrumView.pid
self.widget.spectrumActionDict[spectrum] = action
# NOTE:ED - UPDATE, the following call sets the icon colours:
_spectrumViewHasChanged({Notifier.OBJECT: spectrumView})
action.toggled.connect(partial(self._toggleSpectrumViews, spectrum, action))
setWidgetFont(action, size='SMALL')
def _toggleSpectrumViews(self, spectrum, action, state):
"""Toggle visibility of spectrumViews attached to this spectrum in the same spectrumDisplay
"""
specViews = [sv for sv in self.widget.spectrumViews if sv.spectrum == spectrum]
for sv in specViews:
sv.setVisible(state)
def _addSpectrumViewToolButtons(self, spectrumView):
_strip = spectrumView.strip
spectrumDisplay = _strip.spectrumDisplay
if spectrumDisplay.isGrouped:
return
# print(f' _addSpectrumViewToolButtons {spectrumView} {spectrumView.isDisplayed}')
with self.blockWidgetSignals(recursive=False):
spectrumDisplay = spectrumView.strip.spectrumDisplay
spectrum = spectrumView.spectrum
action = spectrumDisplay.spectrumActionDict.get(spectrum)
if not action:
# add toolbar action (button)
spectrumName = spectrum.name
# This is a bug, it changes the name of button and crashes when moving them across
# if len(spectrumName) > 12:
# spectrumName = spectrumName[:12]+'.....'
actionList = self.actions()
try:
# try and find the spectrumView in the orderedlist - for undo function
# oldList = spectrumDisplay.orderedSpectrumViews(spectrumDisplay.spectrumViews)
# NOTE:ED - not tested as gui undo disabled
oldList = _strip.getSpectrumViews()
if spectrumView in oldList:
oldIndex = oldList.index(spectrumView)
else:
oldIndex = len(oldList)
if actionList and oldIndex < len(actionList):
nextAction = actionList[oldIndex]
# create a new action and move it to the correct place in the list
action = self.addAction(spectrumName)
action.setObjectName(spectrum.pid)
self.insertAction(nextAction, action)
else:
action = self.addAction(spectrumName)
action.setObjectName(spectrum.pid)
except Exception as es:
action = self.addAction(spectrumName)
action.setObjectName(spectrum.pid)
action.setCheckable(True)
action.setChecked(spectrumView.isDisplayed)
action.setToolTip(spectrum.name)
widget = self.widgetForAction(action)
_height1 = max(getFontHeight(size='SMALL') or 12, 12)
widget.setIconSize(QtCore.QSize(_height1 * 10, _height1))
# widget.setIconSize(QtCore.QSize(120, 10))
self._setSizes(action)
# WHY _wrappedData and not spectrumView?
widget.spectrumView = spectrumView
action.spectrumViewPid = spectrumView.pid
spectrumDisplay.spectrumActionDict[spectrum] = action
# The following call sets the icon colours:
_spectrumViewHasChanged({Notifier.OBJECT: spectrumView})
# if spectrumDisplay.is1D:
# action.toggled.connect(spectrumView.plot.setVisible)
action.toggled.connect(spectrumView.setVisible)
setWidgetFont(action, size='SMALL')
return action
def _setSizes(self, action):
widget = self.widgetForAction(action)
_height1 = max(getFontHeight(size='SMALL') or 12, 12)
_height2 = max(getFontHeight(size='VLARGE') or 30, 30)
widget.setIconSize(QtCore.QSize(_height1 * 10, _height1))
widget.setFixedSize(_height2 * 2.5, _height2)
[docs] def setSpectrumGroupActionIcon(self, action, spectrumGroup):
self._addActionIcon(action, spectrumGroup)
[docs] def setSpectrumActionIcon(self, action, spectrum):
_sv = [sv for sv in self.spectrumDisplay.spectrumViews if sv.spectrum == spectrum]
if len(_sv) != 1:
return
specView = _sv[0]
self._addActionIcon(action, specView)
def _addActionIcon(self, action, obj):
_iconX = int(60 / self.spectrumDisplay.devicePixelRatio())
_iconY = int(10 / self.spectrumDisplay.devicePixelRatio())
pix = QtGui.QPixmap(QtCore.QSize(_iconX, _iconY))
# print(f' _addActionIcon {action} {obj}')
if getattr(obj, '_showContours', True) or self.spectrumDisplay.isGrouped:
if self.spectrumDisplay.is1D:
_col = obj.sliceColour
else:
_col = obj.positiveContourColour
if _col and _col.startswith('#'):
pix.fill(QtGui.QColor(_col))
elif _col in Colour.colorSchemeTable:
colourList = Colour.colorSchemeTable[_col]
step = _iconX
stepX = _iconX
stepY = len(colourList) - 1
jj = 0
painter = QtGui.QPainter(pix)
for ii in range(_iconX):
_interp = (stepX - step) / stepX
_intCol = Colour.interpolateColourHex(colourList[min(jj, stepY)], colourList[min(jj + 1, stepY)],
_interp)
painter.setPen(QtGui.QColor(_intCol))
painter.drawLine(ii, 0, ii, _iconY)
step -= stepY
while step < 0:
step += stepX
jj += 1
painter.end()
else:
pix.fill(QtGui.QColor('gray'))
else:
pix.fill(QtGui.QColor('gray'))
action.setIcon(QtGui.QIcon(pix))