Source code for ccpn.ui.gui.widgets.PlaneToolbar

"""
This module defines a specific Toolbar class for the strip display 
The NmrResidueLabel allows drag and drop of the ids displayed in them

"""

#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (https://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 https://ccpn.ac.uk/software/licensing/")
__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: 2022-02-16 10:07:26 +0000 (Wed, February 16, 2022) $"
__version__ = "$Revision: 3.1.0 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: CCPN $"
__date__ = "$Date: 2017-04-07 10:28:41 +0000 (Fri, April 07, 2017) $"
#=========================================================================================
# Start of code
#=========================================================================================

from functools import partial
from PyQt5.QtCore import pyqtSlot
# import json
from ccpn.ui.gui.widgets.Button import Button
from ccpn.ui.gui.widgets.DoubleSpinbox import DoubleSpinbox
from ccpn.ui.gui.widgets.Label import Label, ActiveLabel
from ccpn.ui.gui.widgets.Spinbox import Spinbox
# from ccpn.ui.gui.widgets.ToolBar import ToolBar
# from ccpn.ui.gui.widgets.Widget import Widget
from ccpn.ui.gui.widgets.Frame import Frame, OpenGLOverlayFrame
from ccpn.ui.gui.guiSettings import CCPNGLWIDGET_HEXHIGHLIGHT, CCPNGLWIDGET_HEXFOREGROUND, ZPlaneNavigationModes
# from ccpn.ui.gui.guiSettings import textFont, textFontLarge
from ccpn.ui.gui.widgets.Font import getFontHeight
from ccpn.ui.gui.widgets.DropBase import DropBase
from ccpn.ui.gui.lib.mouseEvents import getMouseEventDict
from PyQt5 import QtWidgets, QtCore
from ccpn.core.Peak import Peak
from ccpn.core.NmrResidue import NmrResidue
from ccpn.core.lib.Notifiers import Notifier
from ccpn.ui.gui.lib.GuiNotifier import GuiNotifier
from ccpn.ui.gui.lib.mouseEvents import makeDragEvent
from ccpn.ui.gui.widgets.Menu import Menu
from ccpn.ui.gui.widgets.Spacer import Spacer
from ccpn.ui.gui.widgets.Icon import Icon
from ccpn.util.Logging import getLogger
from ccpn.util.Constants import AXISUNIT_POINT


STRIPLABEL_CONNECTDIR = '_connectDir'
STRIPLABEL_CONNECTNONE = 'none'
SINGLECLICK = 'click'
DOUBLECLICK = 'doubleClick'


class _StripLabel(ActiveLabel):  #  VerticalLabel): could use Vertical label so that the strips can flip
    """
    Specific Label to be used in Strip displays
    """

    # ED: This class contains the best current method for handling single and double click events
    # without any clashes between events, and creating a dragged item
    DOUBLECLICKENABLED = False

    def __init__(self, parent, mainWindow, strip=None, text=None, dragKey=DropBase.PIDS, stripArrangement=None, **kwds):

        super().__init__(parent, text, **kwds)
        # The text of the label can be dragged; it will be passed on in the dict under key dragKey

        self._parent = parent
        self.strip = strip
        self.spectrumDisplay = self.strip.spectrumDisplay if strip else None
        self.mainWindow = mainWindow
        self.application = mainWindow.application
        self.project = mainWindow.project

        self._dragKey = dragKey
        self.setAcceptDrops(True)
        # self.setDragEnabled(True)           # not possible for Label

        self._lastClick = None
        self._mousePressed = False
        self.stripArrangement = stripArrangement
        # self.setOrientation('vertical' if stripArrangement == 'X' else 'horizontal')

        # disable any drop event callback's until explicitly defined later
        self.setDropEventCallback(None)

    def _createDragEvent(self, mouseDict):
        """
        Re-implementation of the mouse press event to enable a NmrResidue label to be dragged as a json object
        containing its id and a modifier key to encode the direction to drop the strip.
        """
        if not self.project.getByPid(self.text()):
            # label does not point to an object
            getLogger().warning('%s is not a draggable object' % self.text())
            return

        # mimeData = QtCore.QMimeData()
        # create the dataDict
        dataDict = {self._dragKey: [self.text()],
                    DropBase.TEXT: self.text()
                    }
        connectDir = self._connectDir if hasattr(self, STRIPLABEL_CONNECTDIR) else STRIPLABEL_CONNECTNONE
        dataDict[STRIPLABEL_CONNECTDIR] = connectDir

        # update the dataDict with all mouseEvents{"controlRightMouse": false, "text": "NR:@-.@27.", "leftMouse": true, "controlShiftMiddleMouse": false, "middleMouse": false, "controlMiddleMouse": false, "controlShiftLeftMouse": false, "controlShiftRightMouse": false, "shiftMiddleMouse": false, "_connectDir": "isRight", "controlLeftMouse": false, "rightMouse": false, "shiftLeftMouse": false, "shiftRightMouse": false}
        dataDict.update(mouseDict)

        makeDragEvent(self, dataDict, [self.text()], self.text())

    def event(self, event):
        """
        Process all events in the event handler
        Not sure if this is the best solution, but doesn't interfere with _processDroppedItems
        and allows changing of the cursor (cursor not always changing properly in pyqt5) - ejb
        """
        if event.type() == QtCore.QEvent.MouseButtonPress:
            # process the single click event
            self._mousePressEvent(event)
            return True

        if event.type() == QtCore.QEvent.MouseButtonDblClick:
            # process the doubleClick event
            self._mouseButtonDblClick(event)
            return True

        if event.type() == QtCore.QEvent.MouseButtonRelease:
            # process the mouse release event
            self._mouseReleaseEvent(event)
            return True

        return super().event(event)

    def _mousePressEvent(self, event):
        """Handle mouse press event for single click and beginning of mouse drag event
        """
        self._mousePressed = True
        if not self._lastClick:
            self._lastClick = SINGLECLICK

        if self._lastClick == SINGLECLICK:
            mouseDict = getMouseEventDict(event)

            # set up a singleShot event, but a bit quicker than the normal interval (which seems a little long)
            QtCore.QTimer.singleShot(QtWidgets.QApplication.instance().doubleClickInterval() // 2,
                                     partial(self._handleMouseClicked, mouseDict))

        elif self._lastClick == DOUBLECLICK:
            self._lastClick = None

    def _mouseButtonDblClick(self, event):
        """Handle mouse doubleCLick
        """
        self._lastClick = DOUBLECLICK
        if self.DOUBLECLICKENABLED:
            self._processDoubleClick(self.text())

    def _mouseReleaseEvent(self, event):
        """Handle mouse release
        """
        self._mousePressed = False
        if event.button() == QtCore.Qt.RightButton:
            pass
            # NOTE:ED - popup 'close headers' not required now
            # self._rightButtonPressed(event)

    def _handleMouseClicked(self, mouseDict):
        """handle a single mouse event, but ignore double click events
        """
        if self._lastClick == SINGLECLICK and self._mousePressed:
            self._createDragEvent(mouseDict)
        self._lastClick = None

    def _processDoubleClick(self, obj):
        """Process the doubleClick event, action the clicked stripHeader in the selected strip
        """
        from ccpn.ui.gui.lib.SpectrumDisplay import navigateToPeakInStrip, navigateToNmrResidueInStrip

        obj = self.project.getByPid(obj) if isinstance(obj, str) else obj
        if obj:
            spectrumDisplays = self.spectrumDisplay._spectrumDisplaySettings.displaysWidget._getDisplays()
            for specDisplay in spectrumDisplays:

                if specDisplay.strips:
                    if isinstance(obj, Peak):
                        navigateToPeakInStrip(specDisplay, specDisplay.strips[0], obj)

                    elif isinstance(obj, NmrResidue):
                        navigateToNmrResidueInStrip(specDisplay, specDisplay.strips[0], obj)

    def _rightButtonPressed(self, event):
        """Handle pressing the right mouse button
        """
        menu = self._createContextMenu(self)
        if menu:
            menu.move(event.globalPos().x(), event.globalPos().y() + 10)
            menu.exec()

    def _createContextMenu(self, button: QtWidgets.QToolButton):
        """Creates a context menu to close headers.
        """
        contextMenu = Menu('', self, isFloatWidget=True)

        contextMenu.addSeparator()
        contextMenu.addAction('Close Strip Header', self._closeStrip)
        contextMenu.addAction('Close All Strip Headers in SpectrumDisplay', self._closeSpectrumDisplay)
        contextMenu.addAction('Close All Headers in All SpectrumDisplays', self._closeAll)
        return contextMenu

    def _closeStrip(self):
        """Close header in this strip
        """
        self.strip.header.reset()

    def _closeSpectrumDisplay(self):
        """Close all headers in this spectrumDisplay
        """
        for strip in self.spectrumDisplay.strips:
            strip.header.reset()

    def _closeAll(self):
        """Close all headers in all spectrumDisplays
        """
        displays = self.mainWindow.spectrumDisplays
        for display in displays:
            for strip in display.strips:
                strip.header.reset()


#TODO:GEERTEN: complete this and replace
[docs]class PlaneSelectorWidget(Frame): """ This widget contains the buttons and entry boxes for selection of the plane """ def __init__(self, qtParent, mainWindow=None, strip=None, axis=None, **kwds): """Setup the buttons and callbacks for axis """ super().__init__(parent=qtParent, setLayout=True, **kwds) self.mainWindow = mainWindow self.project = mainWindow.project self.strip = strip self.axis = axis self._linkedSpinBox = None self._linkedPlaneCount = None _size = getFontHeight(size='MEDIUM') self._mainWidget = Frame(self, setLayout=True, showBorder=False, grid=(0, 0)) self.previousPlaneButton = Button(parent=self._mainWidget, text='<', grid=(0, 0), callback=self._previousPlane) self.spinBox = DoubleSpinbox(parent=self._mainWidget, showButtons=False, grid=(0, 1), decimals=3, objectName='PlaneSelectorWidget_planeDepth', ) self.spinBox.setFixedWidth(_size * 5) self.spinBox.returnPressed.connect(self._spinBoxChanged) self.spinBox.wheelChanged.connect(self._spinBoxChanged) self.nextPlaneButton = Button(parent=self._mainWidget, text='>', grid=(0, 2), callback=self._nextPlane) self.planeCountSpinBox = Spinbox(parent=self._mainWidget, showButtons=False, grid=(0, 3), min=1, max=1000, objectName='PlaneSelectorWidget_planeCount' ) self.planeCountSpinBox.setFixedWidth(_size * 2.5) self.planeCountSpinBox.returnPressed.connect(self._planeCountChanged) self.planeCountSpinBox.wheelChanged.connect(self._planeCountChanged) self.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) def _initialise(self, strip=None, axis=None): """Set the initial values for the plane selector """ from ccpn.ui.gui.lib.GuiStrip import GuiStrip strip = self.project.getByPid(strip) if isinstance(strip, str) else strip if not isinstance(strip, GuiStrip): raise TypeError("%s is not of type Strip" % str(strip)) if not isinstance(axis, int): raise TypeError("%s is not of type int" % str(axis)) if not (0 <= axis < len(strip.axisCodes)): raise ValueError("axis %s is out of range (0, %i)" % (str(axis), len(self.axisCodes) - 1)) self.strip = strip self.axis = axis self.spinBox.setToolTip(str(self.strip.axisCodes[self.axis]))
[docs] def setCallbacks(self, callbacks): "callbacks a dict with (key, callbackFunction) items" self._callbacks = callbacks
def _planeCountChanged(self, value: int = 1): """Changes the number of planes displayed simultaneously. """ if self.strip: self._callbacks['_planeCountChanged'](value) def _nextPlane(self, *args): """Increases axis ppm position by one plane """ self.project._buildWithProfile = False if self.strip: self._callbacks['_nextPlane'](*args) def _previousPlane(self, *args): """Decreases axis ppm position by one plane """ if self.strip: self._callbacks['_previousPlane'](*args) def _spinBoxChanged(self, value: float): """Sets the value of the axis plane position box if the specified value is within the displayable limits. """ if self.strip: self._callbacks['_spinBoxChanged'](value) def _wheelEvent(self, event): if event.angleDelta().y() > 0: if self.strip.prevPlaneCallback: self.strip.prevPlaneCallback(self.axis) else: if self.strip.nextPlaneCallback: self.strip.nextPlaneCallback(self.axis) self.strip.refresh()
[docs] def setPosition(self, ppmPosition, ppmWidth): """Set the new ppmPosition/ppmWidth """ with self.blockWidgetSignals(): self.spinBox.setValue(ppmPosition)
[docs] def setPlaneValues(self, planeSize=None, minValue=None, maxValue=None, value=None, unit=None): """Set new values for the plane selector """ with self.blockWidgetSignals(root=self._mainWidget): # block signals while setting contents self.spinBox.setSingleStep(planeSize) if maxValue is not None: self.spinBox.setMaximum(maxValue) if minValue is not None: self.spinBox.setMinimum(minValue) # override the spinBox to only allow integer points self.spinBox.setDecimals(0 if unit == AXISUNIT_POINT else 3) if value is not None: self.spinBox.setValue(value)
[docs] def getPlaneValues(self): """Return the current settings for this axis :returns: (minValue, maxValue, stepSize, value, planeCount) tuple """ return self.spinBox.minimum(), self.spinBox.maximum(), self.spinBox.singleStep(), self.spinBox.value(), self.planeCount
@property def planeCount(self): """Return the plane count for this axis """ return self.planeCountSpinBox.value()
class _OpenGLFrameABC(OpenGLOverlayFrame): """ OpenGL ABC for items to overlay the GL frame (until a nicer way can by found) BUTTONS is a tuple of tuples of the form: ((attributeName, widgetType, init function, set attrib function) ... ) attributeName is a string defining the attribute in the container widgetType is the type of widget, e.g. see PlaneAxisWidget init functions are called after instantiation of widgets - typically static functions of the form <name>(self, widget, ...) self is the container class, widget is the widget object attrib functions are called from _populate to populate the widgets after changes (or possibly revert - not fully implemented yet) """ BUTTONS = tuple() AUTOFILLBACKGROUND = True def __init__(self, qtParent, mainWindow, *args, **kwds): super().__init__(parent=qtParent, setLayout=True, **kwds) self.mainWindow = mainWindow self.project = mainWindow.project self._initFuncList = () self._setFuncList = () if not self.BUTTONS: # MUST BE SUBCLASSED raise NotImplementedError("Code error: BUTTONS not implemented") # build the list of widgets in frame row = col = 0 for buttonDef in self.BUTTONS: if buttonDef: widgetName, widgetType, initFunc, setFunc, grid, gridSpan = buttonDef if not widgetType: raise TypeError('Error: button widget not defined') # if widget is given then add to the container widget = widgetType(self, mainWindow=mainWindow, grid=grid, gridSpan=gridSpan) #grid=(row, col), gridSpan=(1, 1)) self._setStyle(widget) if initFunc: self._initFuncList += ((initFunc, self, widget),) if setFunc: self._setFuncList += ((setFunc, self, widget),) # add the widget here setattr(self, widgetName, widget) col += 1 else: # else, move to the next row (simple newLine) row += 1 col = 0 # add an expanding widget to the end of the row Spacer(self, 2, 2, QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Minimum, grid=(0, col + 1), gridSpan=(1, 1)) # initialise the widgets for func, klass, widget in self._initFuncList: func(klass, widget, *args) self.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) def _attachButton(self, buttonName): """Reattach a button to the parent widget """ for buttonDef in self.BUTTONS: if buttonDef: widgetName, widgetType, initFunc, setFunc, grid, gridSpan = buttonDef if not widgetType: raise TypeError('Error: button widget not defined') if widgetName == buttonName: button = getattr(self, buttonName, None) if button: button.layout().addWidget(button._mainWidget, 0, 0) button._mainWidget.setParent(button) def _populate(self): for pp, klass, widget in self._setFuncList: pp(self, klass, widget) EMITSOURCE = 'source' EMITCLICKED = 'clicked' EMITIGNORESOURCE = 'ignoreSource'
[docs]class PlaneAxisWidget(_OpenGLFrameABC): """ Need Frame: AxisCode label AxisValue Frame Change arrow value box Change arrow planes box """ def _setAxisCode(self, *args): pass def _setAxisPosition(self, *args): pass def _setPlaneCount(self, *args): pass def _setPlaneSelection(self, *args): pass def _initAxisCode(self, widget, strip, axis): pass def _initAxisPosition(self, widget, strip, axis): pass def _initPlaneCount(self, widget, strip, axis): pass def _initPlaneSelection(self, widget, strip, axis): self.__postInit__(widget, strip, axis) # define the buttons for the Plane axis widget BUTTONS = (('_axisLabel', ActiveLabel, _initAxisCode, _setAxisCode, (0, 0), (1, 1)), ('_axisPpmPosition', ActiveLabel, _initAxisPosition, _setAxisPosition, (0, 1), (1, 1)), ('_axisPlaneCount', ActiveLabel, _initPlaneCount, _setPlaneCount, (0, 2), (1, 1)), ('_axisSelector', PlaneSelectorWidget, _initPlaneSelection, _setPlaneSelection, (0, 3), (2, 1)) ) def __init__(self, qtParent, mainWindow, strip, axis, **kwds): super().__init__(qtParent, mainWindow, strip, axis, **kwds) self.strip = strip self.axis = axis axisButtons = (self._axisLabel, self._axisPpmPosition, self._axisPlaneCount, self._axisSelector) axisButtons[0].setSelectionCallback(partial(self._selectAxisCallback, axisButtons)) for button in axisButtons[1:3]: button.setSelectionCallback(partial(self._selectPositionCallback, axisButtons)) self._axisLabel.setVisible(True) self._axisPpmPosition.setVisible(True) self._axisPlaneCount.setVisible(True) self._axisSelector.setVisible(False) # self._axisSelector.spinBox.installEventFilter(self) # connect strip changed events to here self.strip.optionsChanged.connect(self._optionsChanged) self.mainWindow = mainWindow def __postInit__(self, widget, strip, axis): """Seems an awkward way of getting a generic post init function but can't think of anything else yet """ # assume that nothing has been set yet self._axisSelector._initialise(strip, axis) self._axisLabel.setText(strip.axisCodes[axis] + ':') self._axisLabel.setToolTip(strip.axisCodes[axis]) callbacks = { '_previousPlane' : self._previousPlane, '_spinBoxChanged' : self._spinBoxChanged, '_nextPlane' : self._nextPlane, '_planeCountChanged' : self._planeCountChanged, '_wheelEvent' : self._wheelEvent } self._axisSelector.setCallbacks(callbacks) # self._axisSelector.setCallbacks((self._previousPlane, # self._spinBoxChanged, # self._nextPlane, # self._planeCountChanged, # self._wheelEvent # )) self._resize()
[docs] def scrollPpmPosition(self, event): """Pass the wheel mouse event to the ppmPosition widget """ self._axisSelector.spinBox._externalWheelEvent(event)
@pyqtSlot(dict) def _optionsChanged(self, aDict): """Respond to signals from other frames in the strip """ # may be required to select/de-select rows source = aDict.get(EMITSOURCE) ignore = aDict.get(EMITIGNORESOURCE) if source and not ((source == self) and ignore): # change settings here self._setLabelBorder(source == self) def _setLabelBorder(self, value): for label in (self._axisLabel, self._axisPpmPosition, self._axisPlaneCount): if value: self._setStyle(label, foregroundColour=CCPNGLWIDGET_HEXHIGHLIGHT) else: self._setStyle(label, foregroundColour=CCPNGLWIDGET_HEXFOREGROUND) label.highlighted = value def _hideAxisSelector(self): widgets = (self._axisLabel, self._axisPpmPosition, self._axisPlaneCount, self._axisSelector) widgets[3].setVisible(False) widgets[2].setVisible(True) widgets[1].setVisible(True) self._resize() def _showAxisSelector(self): widgets = (self._axisLabel, self._axisPpmPosition, self._axisPlaneCount, self._axisSelector) widgets[3].setVisible(True) widgets[2].setVisible(False) widgets[1].setVisible(False) self._resize() def _selectAxisCallback(self, widgets): # if the first widget is clicked then change the selected axis if self.strip.spectrumDisplay.zPlaneNavigationMode == ZPlaneNavigationModes.INSTRIP.label: if widgets[3].isVisible(): widgets[3].setVisible(False) widgets[2].setVisible(True) widgets[1].setVisible(True) self._setLabelBorder(True) self.strip.activePlaneAxis = self.axis self.strip.optionsChanged.emit({EMITSOURCE : self, EMITCLICKED : True, EMITIGNORESOURCE: True}) self._resize() def _selectPositionCallback(self, widgets): # if the other widgets are clicked then toggle the planeToolbar buttons if self.strip.spectrumDisplay.zPlaneNavigationMode == ZPlaneNavigationModes.INSTRIP.label: if widgets[3].isVisible(): widgets[3].setVisible(False) widgets[2].setVisible(True) widgets[1].setVisible(True) else: widgets[1].setVisible(False) widgets[2].setVisible(False) widgets[3].setVisible(True) self._setLabelBorder(True) self.strip.activePlaneAxis = self.axis self.strip.optionsChanged.emit({EMITSOURCE : self, EMITCLICKED : True, EMITIGNORESOURCE: True}) self._resize()
[docs] def setPosition(self, ppmPosition, ppmWidth): """Set the new ppmPosition/ppmWidth """ self._axisSelector.setPosition(ppmPosition, ppmWidth) self._axisPpmPosition.setText('%.3f' % ppmPosition)
[docs] def getPlaneValues(self): """Return the current settings for this axis :returns: ppmValue, maximum ppmValue, ppmStepSize, ppmPosition, planeCount """ # self._axisSelector is a PlaneSelectorWidget return self._axisSelector.getPlaneValues()
[docs] def setPlaneValues(self, planeSize=None, minValue=None, maxValue=None, value=None, unit=None): """Set new values for the plane selector """ # self._axisSelector is a PlaneSelectorWidget planeSelectorWidget = self._axisSelector planeSelectorWidget.setPlaneValues(planeSize, minValue, maxValue, value, unit) self._axisPpmPosition.setText('%.3f' % value) self._axisPlaneCount.setText('[' + str(planeSelectorWidget.planeCount) + ']')
@property def planeCount(self) -> int: return self._axisSelector.planeCount def _planeCountChanged(self, value: int = 1): """Changes the number of planes displayed simultaneously. """ if self.strip: self.strip._changePlane(self.axis, planeIncrement=0, planeCount=self.planeCount) def _nextPlane(self, *args): """Increases axis position by one plane """ if self.strip: self.strip._changePlane(self.axis, planeIncrement=-1, planeCount=self.planeCount) def _previousPlane(self, *args): """Decreases axis position by one plane """ if self.strip: self.strip._changePlane(self.axis, planeIncrement=1, planeCount=self.planeCount) def _spinBoxChanged(self, *args): """Sets the value of the axis plane position box if the specified value is within the displayable limits. """ if self.strip: planeLabel = self._axisSelector.spinBox value = planeLabel.value() if planeLabel.minimum() <= value <= planeLabel.maximum(): self.strip._setAxisPositionAndWidth(self.axis, position=value) def _wheelEvent(self, event): if event.angleDelta().y() > 0: if self.strip.prevPlaneCallback: self.strip.prevPlaneCallback(self.axis) else: if self.strip.nextPlaneCallback: self.strip.nextPlaneCallback(self.axis) self.strip.refresh()
[docs] def hideWidgets(self): """Hide the planeToolbar if opened """ if self.strip.spectrumDisplay.zPlaneNavigationMode == ZPlaneNavigationModes.INSTRIP.label: axisButtons = (self._axisLabel, self._axisPpmPosition, self._axisPlaneCount, self._axisSelector) # if the other widgets are clicked then toggle the planeToolbar buttons if axisButtons[3].isVisible(): axisButtons[3].setVisible(False) axisButtons[2].setVisible(True) axisButtons[1].setVisible(True) self._resize()
[docs]class ZPlaneToolbar(Frame): """ Class to contain the widgets for zPlane navigation """ def __init__(self, qtParent, mainWindow, displayOrStrip, showHeader=False, showLabels=False, **kwds): super().__init__(parent=qtParent, setLayout=True, **kwds) self._strip = None self.getLayout().setSizeConstraint(QtWidgets.QLayout.SetMinimumSize) self.setVisible(False) self.mainWindow = mainWindow self._header = Label(self, text='zPlaneWidget', grid=(0, 0)) self._header.setVisible(showHeader) self.labels = [] # axisCodes = spectrumDisplay.axisCodes # _axisCount = len(axisCodes) - 2 for ii, axisCode in enumerate(displayOrStrip.axisCodes[2:]): lbl = Label(self, text=axisCode, grid=(0, 1 + (ii * 2)), bold=True) lbl.setVisible(showLabels) self.labels.append(lbl)
[docs] def setHeaderText(self, value): """Set the header text """ if not isinstance(value, str): raise TypeError('{} is not a string'.format(value)) self._header.setText(value)
[docs] def setLabels(self, value): """Set the labels for the dimensions """ if not isinstance(value, (tuple, list)): raise TypeError('{} must be tuple/list'.format(value)) if len(value) != len(self._strip.axisCodes) - 2: raise TypeError('{} must be tuple/list of length {}'.format(value, len(self._strip) - 2)) if not all(isinstance(val, str) for val in value): raise TypeError('{} must be tuple/list of strings'.format(value)) for lbl, val in zip(self.labels, value): lbl.setText(value)
[docs] def setHeaderVisible(self, value): """Set the visibility of the header """ if not isinstance(value, bool): raise TypeError('{} must be a True/False') self._header.setVisible(value)
[docs] def setLabelsVisible(self, value): """Set the visibility of the labels """ if not isinstance(value, bool): raise TypeError('{} must be a True/False') for lbl in self.labels: lbl.setVisible(value)
[docs] def attachZPlaneWidgets(self, strip): """Attach new widgets to the zPlane toolbar """ layout = self.getLayout() # if strip != self._strip: - causing it to skip on undo/redo self.removeZPlaneWidgets() for col, fr in enumerate(strip.planeAxisBars): index = layout.indexOf(fr._axisSelector) if index == -1: layout.addWidget(fr._axisSelector._mainWidget, 0, 2 + col * 2, 1, 1) fr._axisSelector._mainWidget.setParent(self) fr._axisSelector.setVisible(True) fr._resize() self._header.setText(strip.pid) self._strip = strip self.setVisible(True) self.update()
[docs] def removeZPlaneWidgets(self): """Remove existing widgets from the zPlane toolbar """ layout = self.getLayout() if self._strip and hasattr(self._strip, 'planeAxisBars'): for pl in self._strip.planeAxisBars: # reattach the widget to the in strip container pl._attachButton('_axisSelector') pl._hideAxisSelector()
# class ZPlaneToolbar(Frame): # """ # Class to contain the widgets for zPlane navigation # """ # # def __init__(self, qtParent, mainWindow, strip, showHeader=False, showLabels=False, **kwds): # # super().__init__(parent=qtParent, setLayout=True, **kwds) # # self._strip = None # self.getLayout().setSizeConstraint(QtWidgets.QLayout.SetMinimumSize) # self.setVisible(False) # self.mainWindow = mainWindow # self._header = Label(self, text='zPlaneWidget', grid=(0, 0)) # self._header.setVisible(showHeader) # # self.labels = [] # axisCodes = strip.axisCodes # _axisCount = len(axisCodes) - 2 # for ii in range(_axisCount): # lbl = Label(self, text=axisCodes[ii + 2], grid=(0, 1 + (ii * 2)), bold=True) # lbl.setVisible(showLabels) # self.labels.append(lbl) # # def setHeaderText(self, value): # """Set the header text # """ # if not isinstance(value, str): # raise TypeError('{} is not a string'.format(value)) # self._header.setText(value) # # def setLabels(self, value): # """Set the labels for the dimensions # """ # if not isinstance(value, (tuple, list)): # raise TypeError('{} must be tuple/list'.format(value)) # if len(value) != len(self._strip.axisCodes) - 2: # raise TypeError('{} must be tuple/list of length {}'.format(value, len(self._strip) - 2)) # if not all(isinstance(val, str) for val in value): # raise TypeError('{} must be tuple/list of strings'.format(value)) # # for lbl, val in zip(self.labels, value): # lbl.setText(value) # # def setHeaderVisible(self, value): # """Set the visibility of the header # """ # if not isinstance(value, bool): # raise TypeError('{} must be a True/False') # self._header.setVisible(value) # # def setLabelsVisible(self, value): # """Set the visibility of the labels # """ # if not isinstance(value, bool): # raise TypeError('{} must be a True/False') # for lbl in self.labels: # lbl.setVisible(value) # # def attachZPlaneWidgets(self, strip): # """Attach new widgets to the zPlane toolbar # """ # layout = self.getLayout() # # # if strip != self._strip: - causing it to skip on undo/redo # self.removeZPlaneWidgets() # # for col, fr in enumerate(strip.planeAxisBars): # index = layout.indexOf(fr._axisSelector) # if index == -1: # layout.addWidget(fr._axisSelector._mainWidget, 0, 2 + col * 2, 1, 1) # fr._axisSelector._mainWidget.setParent(self) # fr._axisSelector.setVisible(True) # fr._resize() # # self._header.setText(strip.pid) # self._strip = strip # # self.setVisible(True) # self.update() # # def removeZPlaneWidgets(self): # """Remove existing widgets from the zPlane toolbar # """ # layout = self.getLayout() # # if self._strip and hasattr(self._strip, 'planeAxisBars'): # for pl in self._strip.planeAxisBars: # # reattach the widget to the in strip container # pl._attachButton('_axisSelector') # pl._hideAxisSelector() STRIPCONNECT_LEFT = 'isLeft' STRIPCONNECT_RIGHT = 'isRight' STRIPCONNECT_NONE = 'noneConnect' STRIPCONNECT_DIRS = (STRIPCONNECT_NONE, STRIPCONNECT_LEFT, STRIPCONNECT_RIGHT) STRIPPOSITION_MINUS = 'minus' STRIPPOSITION_PLUS = 'plus' STRIPPOSITION_LEFT = 'l' STRIPPOSITION_CENTRE = 'c' STRIPPOSITION_RIGHT = 'r' STRIPPOSITIONS = (STRIPPOSITION_MINUS, STRIPPOSITION_PLUS, STRIPPOSITION_LEFT, STRIPPOSITION_CENTRE, STRIPPOSITION_RIGHT) # STRIPDICT = 'stripHeaderDict' STRIPTEXT = 'stripText' STRIPCOLOUR = 'stripColour' STRIPLABELPOS = 'StripLabelPos' STRIPICONNAME = 'stripIconName' STRIPICONSIZE = 'stripIconSize' STRIPOBJECT = 'stripObject' STRIPCONNECT = 'stripConnect' STRIPVISIBLE = 'stripVisible' STRIPENABLED = 'stripEnabled' STRIPTRUE = 1 STRIPFALSE = 0 STRIPSTOREINDEX = [STRIPTEXT, STRIPOBJECT, STRIPCONNECT, STRIPVISIBLE, STRIPENABLED] STRIPHEADERVISIBLE = 'stripHeaderVisible' STRIPHANDLE = 'stripHandle' DEFAULTCOLOUR = CCPNGLWIDGET_HEXFOREGROUND
[docs]class StripHeaderWidget(_OpenGLFrameABC): def _initIcon(self, widget, strip): self.__postIconInit__(widget, strip) def _initStripHeader(self, widget, strip): self.__postHeaderInit__(widget, strip) BUTTONS = (('_nmrChainLeft', _StripLabel, None, None, (0, 0), (2, 1)), ('_nmrChainRight', _StripLabel, _initIcon, None, (0, 5), (2, 1)), ('_stripDirection', _StripLabel, None, None, (0, 2), (1, 2)), ('_stripLabel', _StripLabel, None, None, (1, 2), (1, 1)), ('_stripPercent', _StripLabel, _initStripHeader, None, (1, 3), (1, 2)), ) def __postIconInit__(self, widget, strip): """Seems an awkward way of getting a generic post init function but can't think of anything else yet """ # assume that nothing has been set yet pass def __postHeaderInit__(self, widget, strip): """Seems an awkward way of getting a generic post init function but can't think of anything else yet """ # assume that nothing has been set yet # add gui notifiers here instead of in backboneAssignment # NOTE:ED could replace this with buttons instead GuiNotifier(self._nmrChainLeft, [GuiNotifier.DROPEVENT], [DropBase.TEXT], self._processDroppedLabel, toLabel=self._stripDirection, plusChain=False) GuiNotifier(self._nmrChainRight, [GuiNotifier.DROPEVENT], [DropBase.TEXT], self._processDroppedLabel, toLabel=self._stripDirection, plusChain=True) self._resize() def _processDroppedLabel(self, data, toLabel=None, plusChain=None): """Not a very elegant way of running backboneAssignment code from the strip headers Should be de-coupled from the backboneAssignment module """ if toLabel and toLabel.text(): dest = toLabel.text() nmrResidue = self.project.getByPid(dest) if nmrResidue: guiModules = self.mainWindow.modules for guiModule in guiModules: if guiModule.className == 'BackboneAssignmentModule': guiModule._processDroppedNmrResidue(data, nmrResidue=nmrResidue, plusChain=plusChain) def __init__(self, qtParent, mainWindow, strip, stripArrangement=None, **kwds): super().__init__(qtParent, mainWindow, strip, **kwds) self._parent = qtParent self.mainWindow = mainWindow self.strip = strip self.setAutoFillBackground(False) self._labels = dict((strip, widget) for strip, widget in zip(STRIPPOSITIONS, (self._nmrChainLeft, self._nmrChainRight, self._stripLabel, self._stripDirection, self._stripPercent))) # set the visible state of the header self.strip._setInternalParameter(STRIPHEADERVISIBLE, False) self.setVisible(False) # labelsVisible = False for stripPos in STRIPPOSITIONS: # read the current strip header values headerText = self._getPositionParameter(stripPos, STRIPTEXT, '') headerIconName = self._getPositionParameter(stripPos, STRIPICONNAME, '') headerIconSize = self._getPositionParameter(stripPos, STRIPICONSIZE, (18, 18)) # not sure this is required headerObject = self.strip.project.getByPid(self._getPositionParameter(stripPos, STRIPOBJECT, None)) headerConnect = self._getPositionParameter(stripPos, STRIPCONNECT, STRIPCONNECT_NONE) # headerVisible = self._getPositionParameter(stripPos, STRIPVISIBLE, False) # headerEnabled = self._getPositionParameter(stripPos, STRIPENABLED, True) self._labels[stripPos].obj = headerObject self._labels[stripPos]._connectDir = headerConnect # self._labels[stripPos].setVisible(True if headerText else False) # self._labels[stripPos].setVisible(headerVisible) # labelsVisible = labelsVisible or headerVisible # self._labels[stripPos].setEnabled(headerEnabled) if headerIconName: self.setLabelIcon(headerIconName, headerIconSize, stripPos) else: self.setLabelText(headerText, stripPos) self._resize() # Notifier for change of stripLabel self._nmrResidueNotifier = Notifier(self.project, [Notifier.RENAME], 'NmrResidue', self._processNotifier, onceOnly=True)
[docs] def setEnabledLeftDrop(self, value): # set the icon the first time if not loaded iconLeft = self._getPositionParameter(STRIPPOSITION_MINUS, STRIPICONNAME, '') if value and not iconLeft: self.setLabelIcon('icons/down-left', (18, 18), STRIPPOSITION_MINUS) self._resize()
[docs] def setEnabledRightDrop(self, value): # set the icon the first time if not loaded iconRight = self._getPositionParameter(STRIPPOSITION_PLUS, STRIPICONNAME, '') if value and not iconRight: self.setLabelIcon('icons/down-right', (18, 18), STRIPPOSITION_PLUS) self._resize()
def _setPositionParameter(self, stripPos, subParameterName, value): """Set the item in the position dict """ if self.strip.isDeleted or self.strip._flaggedForDelete: return params = self.strip._getInternalParameter(stripPos) if not params: params = {} # fix for bad characters in the XML if isinstance(value, str): if '<<<' in value: value = 'MINUS' elif '>>>' in value: value = 'PLUS' # currently writes directly into _ccpnInternal params.update({subParameterName: value}) self.strip._setInternalParameter(stripPos, params) def _getPositionParameter(self, stripPos, subParameterName, default): """Return the item from the position dict """ params = self.strip._getInternalParameter(stripPos) if params: if subParameterName in params: value = params.get(subParameterName) # fix for bad characters in the XML # Could ignore here, so that needs doubleClick in backboneAssignment to restart if isinstance(value, str): if 'MINUS' in value: # value = '<<<' value = '' elif 'PLUS' in value: # value = '>>>' value = '' return value return default
[docs] def reset(self): """Clear all header labels """ # self.setVisible(False) for stripPos in STRIPPOSITIONS: self._labels[stripPos].obj = None self._labels[stripPos]._connectDir = STRIPCONNECT_NONE self._labels[stripPos].setEnabled(True) self.setLabelVisible(stripPos, False) # clear the header store params = {STRIPTEXT : '', STRIPICONNAME: '', STRIPOBJECT : None, STRIPCONNECT : STRIPCONNECT_NONE, STRIPVISIBLE : False, STRIPENABLED : True } self.strip._setInternalParameter(stripPos, params) self.strip._setInternalParameter(STRIPHANDLE, None) self._resize()
[docs] def getLabelObject(self, position=STRIPPOSITION_CENTRE): """Return the object attached to the header label at the given position """ if position in STRIPPOSITIONS: return self._labels[position].obj else: raise ValueError('Error: %s is not a valid position' % str(position))
[docs] def setLabelObject(self, obj=None, position=STRIPPOSITION_CENTRE): """Set the object attached to the header label at the given position and store its pid """ # NOTE:ED not sure I need this now - check rename nmrResidue, etc. self.show() if position in STRIPPOSITIONS: self._labels[position].obj = obj # SHOULD have a pid if an object if obj and hasattr(obj, 'pid'): self._setPositionParameter(position, STRIPOBJECT, str(obj.pid)) else: raise ValueError('Error: %s is not a valid position' % str(position)) self._resize()
[docs] def getLabelText(self, position=STRIPPOSITION_CENTRE): """Return the text for header label at the given position """ if position in STRIPPOSITIONS: return self._labels[position].text() else: raise ValueError('Error: %s is not a valid position' % str(position))
[docs] def setLabelText(self, text=None, position=STRIPPOSITION_CENTRE): """Set the text for header label at the given position """ self.show() if position in STRIPPOSITIONS: self._labels[position].setText(str(text)) self._setPositionParameter(position, STRIPTEXT, str(text)) self.setLabelVisible(position, True if text else False) else: raise ValueError('Error: %s is not a valid position' % str(position)) self._resize()
[docs] def getLabel(self, position=STRIPPOSITION_CENTRE): """Return the header label widget at the given position """ if position in STRIPPOSITIONS: return self._labels[position] else: raise ValueError('Error: %s is not a valid position' % str(position))
[docs] def getLabelVisible(self, position=STRIPPOSITION_CENTRE): """Return if the widget at the given position is visible """ if position in STRIPPOSITIONS: return self._labels[position].isVisible() else: raise ValueError('Error: %s is not a valid position' % str(position))
[docs] def setLabelVisible(self, position=STRIPPOSITION_CENTRE, visible: bool = True): """show/hide the header label at the given position """ if position in STRIPPOSITIONS: # if visible: # self.show() # else: # self.hide() self._labels[position].setVisible(visible) self._setPositionParameter(position, STRIPVISIBLE, visible) else: raise ValueError('Error: %s is not a valid position' % str(position)) lv = [self._getPositionParameter(pp, STRIPVISIBLE, False) for pp in STRIPPOSITIONS] headerVisible = any(lv) self.strip._setInternalParameter(STRIPHEADERVISIBLE, headerVisible) self.setVisible(headerVisible) self._resize()
[docs] def setLabelEnabled(self, position=STRIPPOSITION_CENTRE, enable: bool = True): """Enable/disable the header label at the given position """ if position in STRIPPOSITIONS: self._labels[position].setEnabled(enable) self._setPositionParameter(position, STRIPENABLED, enable) else: raise ValueError('Error: %s is not a valid position' % str(position)) self._resize()
[docs] def getLabelEnabled(self, position=STRIPPOSITION_CENTRE): """Return if the widget at the given position is enabled """ if position in STRIPPOSITIONS: return self._labels[position].isEnabled() else: raise ValueError('Error: %s is not a valid position' % str(position))
[docs] def getLabelConnectDir(self, position=STRIPPOSITION_CENTRE): """Return the connectDir attribute of the header label at the given position """ if position in STRIPPOSITIONS: return self._labels[position]._connectDir else: raise ValueError('Error: %s is not a valid position' % str(position))
[docs] def setLabelConnectDir(self, position=STRIPPOSITION_CENTRE, connectDir: str = STRIPCONNECT_NONE): """set the connectDir attribute of the header label at the given position """ if position in STRIPPOSITIONS: self._labels[position]._connectDir = connectDir self._setPositionParameter(position, STRIPCONNECT, connectDir) else: raise ValueError('Error: %s is not a valid position' % str(position)) self._resize()
[docs] def setLabelIcon(self, iconName=None, iconSize=(18, 18), position=STRIPPOSITION_CENTRE): """Set the text for header label at the given position """ self.show() if position in STRIPPOSITIONS: self._labels[position].setPixmap(Icon(iconName).pixmap(*iconSize)) self._setPositionParameter(position, STRIPICONNAME, str(iconName)) self.setLabelVisible(position, True if iconName else False) else: raise ValueError('Error: %s is not a valid position' % str(position)) self._resize()
def _processNotifier(self, data): """Process the notifiers for the strip header """ trigger = data[Notifier.TRIGGER] obj = data[Notifier.OBJECT] if trigger == 'rename': oldPid = data[Notifier.OLDPID] for stripPos in STRIPPOSITIONS: # change the label text if oldPid in self.getLabelText(stripPos): self.setLabelText(position=stripPos, text=str(obj.pid)) # change the object text if self.getLabelObject(stripPos) is obj: self.setLabelObject(position=stripPos, obj=obj) @property def headerVisible(self): return self.strip._getInternalParameter(STRIPHEADERVISIBLE) @headerVisible.setter def headerVisible(self, visible): self.strip._setInternalParameter(STRIPHEADERVISIBLE, visible) self.setVisible(visible) self._resize() @property def handle(self): return self.strip._getInternalParameter(STRIPHANDLE) @handle.setter def handle(self, value): self.strip._setInternalParameter(STRIPHANDLE, value) self._resize()
[docs]class StripLabelWidget(_OpenGLFrameABC): def _setStripLabel(self, *args): """Set the label of the strip, called from _populate """ self.setLabelText(self.strip.pid if self.strip else '') self._resize() BUTTONS = (('_stripLabel', _StripLabel, None, _setStripLabel, (0, 0), (1, 1)), ) def _processDroppedLabel(self, data, toLabel=None, plusChain=None): """Not a very elegant way of running backboneAssignment code from the strip headers Should be de-coupled from the backboneAssignment module """ pass def __init__(self, qtParent, mainWindow, strip, **kwds): super().__init__(qtParent, mainWindow, strip, **kwds) self._parent = qtParent self.mainWindow = mainWindow self.strip = strip self.setAutoFillBackground(False) # read the current strip header values headerText = self._getPositionParameter(STRIPTEXT, '') headerColour = self._getPositionParameter(STRIPCOLOUR, DEFAULTCOLOUR) self.setLabel(headerText, headerColour) # get the visible state of the header self.setVisible(True) self._resize() def _setPositionParameter(self, subParameterName, value): """Set the item in the position dict """ params = self.strip._getInternalParameter(STRIPLABELPOS) if not params: params = {} # currently writes directly into _ccpnInternal params.update({subParameterName: value}) self.strip._setInternalParameter(STRIPLABELPOS, params) def _getPositionParameter(self, subParameterName, default): """Return the item from the position dict """ params = self.strip._getInternalParameter(STRIPLABELPOS) if params: if subParameterName in params: value = params.get(subParameterName) return value return default
[docs] def reset(self): """Clear stripLabel """ # clear the store params = {STRIPTEXT: '', } self.strip._setInternalParameter(STRIPLABELPOS, params) self._resize()
[docs] def setLabel(self, text='', colour=DEFAULTCOLOUR): """Set the text for stripLabel """ self.show() self._stripLabel.setText(str(text)) self._setStyle(self._stripLabel, foregroundColour=colour) self._setPositionParameter(STRIPTEXT, str(text)) self._setPositionParameter(STRIPCOLOUR, colour) self._stripLabel.setVisible(True if text else False) self._resize()
[docs] def setLabelText(self, text=None): """Set the text for stripLabel """ self.show() self._stripLabel.setText(str(text)) self._setPositionParameter(STRIPTEXT, str(text)) self._stripLabel.setVisible(True if text else False) self._resize()
[docs] def setLabelColour(self, colour=DEFAULTCOLOUR): """Set the colour for stripLabel """ self.show() self._setStyle(self._stripLabel, foregroundColour=colour) self._setPositionParameter(STRIPCOLOUR, colour) self._resize()
[docs] def setHighlighted(self, value): self._stripLabel.highlighted = value
[docs]class TestPopup(Frame): def __init__(self): super().__init__() self.setWindowFlags(QtCore.Qt.CustomizeWindowHint) self.setLayout(QtWidgets.QHBoxLayout()) Button_close = QtWidgets.QPushButton('close') self.layout().addWidget(QtWidgets.QLabel("HI")) self.layout().addWidget(Button_close) Button_close.clicked.connect(self.close) self.exec_() print("clicked")
[docs] def mousePressEvent(self, event): self.oldPos = event.globalPos()
[docs] def mouseMoveEvent(self, event): delta = QtCore.QPoint(event.globalPos() - self.oldPos) #print(delta) self.move(self.x() + delta.x(), self.y() + delta.y()) self.oldPos = event.globalPos()