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

"""Module Documentation here

"""
#=========================================================================================
# 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:09 +0000 (Tue, February 01, 2022) $"
__version__ = "$Revision: 3.0.4 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: Luca Mureddu $"
__date__ = "$Date: 2017-04-07 10:28:42 +0000 (Fri, April 07, 2017) $"
#=========================================================================================
# Start of code
#=========================================================================================

from PyQt5 import QtCore, QtGui, QtWidgets
from pyqtgraph.dockarea.DockArea import DockArea
from pyqtgraph.dockarea.DockDrop import DockDrop
from pyqtgraph.dockarea.Dock import DockLabel, Dock, VerticalLabel
from pyqtgraph.dockarea.Container import SplitContainer

from ccpn.ui.gui.widgets.Menu import Menu
from ccpn.ui.gui.lib.GuiGenerator import generateWidget
from ccpn.ui.gui.widgets.Frame import Frame
from ccpn.ui.gui.widgets.Button import Button
from ccpn.ui.gui.widgets.CheckBox import CheckBox
from ccpn.ui.gui.widgets.ColourDialog import ColourDialog
from ccpn.ui.gui.widgets.DoubleSpinbox import DoubleSpinbox, ScientificDoubleSpinBox
from ccpn.ui.gui.widgets.Label import Label
from ccpn.ui.gui.widgets.LineEdit import LineEdit
from ccpn.ui.gui.widgets.PulldownList import PulldownList
from ccpn.ui.gui.widgets.RadioButton import RadioButton
from ccpn.ui.gui.widgets.RadioButtons import RadioButtons
from ccpn.ui.gui.widgets.Slider import Slider
from ccpn.ui.gui.widgets.Spinbox import Spinbox
from ccpn.ui.gui.widgets.TextEditor import TextEditor
from ccpn.ui.gui.widgets.FileDialog import LineEditButtonDialog
from ccpn.ui.gui.widgets.Widget import Widget
from ccpn.ui.gui.popups.PickPeaks1DPopup import ExcludeRegions
from ccpn.ui.gui.widgets.Icon import Icon
from ccpn.ui.gui.widgets.Base import Base
from ccpn.ui.gui.widgets.ListWidget import ListWidget
from collections import OrderedDict
from ccpn.framework.lib.pipeline.PipelineBase import Pipeline
from ccpn.ui.gui.widgets.GLLinearRegionsPlot import GLTargetButtonSpinBoxes
from ccpn.util.Logging import getLogger
from ccpn.framework.lib.pipeline.PipeBase import PIPE_CATEGORIES, PIPE_CATEGORY
from functools import partial
from ccpn.ui.gui.widgets.Font import getFont
from ccpn.ui.gui.guiSettings import getColours, LABEL_FOREGROUND
from ccpn.util.Colour import hexToRgb

commonWidgets = {
    CheckBox.__name__               : ('get', 'setChecked'),
    ColourDialog.__name__           : ('getColor', 'setColor'),
    DoubleSpinbox.__name__          : ('value', 'setValue'),
    ScientificDoubleSpinBox.__name__: ('value', 'setValue'),
    Label.__name__                  : ('get', 'setText'),
    LineEdit.__name__               : ('get', 'setText'),
    LineEditButtonDialog.__name__   : ('get', 'setText'),
    PulldownList.__name__           : ('currentText', 'set'),
    RadioButton.__name__            : ('get', 'set'),
    RadioButtons.__name__           : ('get', 'set'),
    Slider.__name__                 : ('get', 'setValue'),
    Spinbox.__name__                : ('value', 'set'),
    TextEditor.__name__             : ('get', 'setText'),
    GLTargetButtonSpinBoxes.__name__: ('get', 'setValues'),
    ExcludeRegions.__name__         : ('_getExcludedRegions', '_set'),

    # ObjectTable.__name__:    ('getSelectedRows',         '_highLightObjs'), works only with objs
    }


def _getWidgetByAtt(cls, name):
    """

    :param cls: the class where the widget lives
    :param name: widget variable name
    :return: widget obj
    """
    w = getattr(cls, name, None)
    if w is not None:
        return w


PipelineBoxDragStyle = """Dock > QWidget {border: 1px solid #78FF00; border-radius: 1px;}"""

PipelineBoxLabelStyle = """PipelineBoxLabel{
                                                  background-color : #60B41D;
                                                  color : #000000;
                                                  border-top-right-radius: 1r;
                                                  border-top-left-radius: 1r;
                                                  border-bottom-right-radius: 0px;
                                                  border-bottom-left-radius: 0px;
                                                  border-width: 0px;
                                                  border-bottom: 0px;
                                                  padding-left: 1px;
                                                  padding-right: 1px;
                                                  }"""


class _VContainer(SplitContainer):
    def __init__(self, area):
        SplitContainer.__init__(self, area, QtCore.Qt.Vertical)

    def type(self):
        return 'vertical'

    def updateStretch(self):
        x = 0
        y = 0
        sizes = []
        for i in range(self.count()):
            wx, wy = self.widget(i).stretch()
            y += wy
            x = max(x, wx)
            sizes.append(wy)
        self.setStretch(x, y)

        tot = float(sum(sizes))
        if tot == 0:
            scale = 1.0
        else:
            scale = self.height() / tot

        self.setSizes([int(s * scale) for s in sizes])
        self.setChildrenCollapsible(False)
        self.setChildrenCollapsible(False)


class _PipelineDropAreaOverlay(Widget):
    """Overlay widget that draws drop areas during a drag-drop operation"""

    def __init__(self, parent):
        super().__init__(parent)
        self.dropArea = None
        self.hide()
        # self.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)

    def setDropArea(self, area):
        self.dropArea = area
        if area is None:
            self.hide()
        else:
            prgn = self.getParent().rect()
            rgn = QtCore.QRect(prgn)
            w = min(10, prgn.width() / 3.)
            h = min(30, prgn.height() / 3.)

            if self.dropArea == 'top':
                rgn.setHeight(h)
            elif self.dropArea == 'bottom':
                rgn.setTop(rgn.top() + prgn.height() - h)
            else:
                rgn.setHeight(0)

            self.setGeometry(rgn)
            self.show()

        self.update()
        self.update()

    def paintEvent(self, ev):
        if self.dropArea is None:
            return
        p = QtGui.QPainter(self)
        rgn = self.rect()

        #TODO:LUCA insert stylesheet
        p.setBrush(QtGui.QBrush(QtGui.QColor(120, 255, 0, 50)))
        p.setPen(QtGui.QPen(QtGui.QColor(123, 245, 150), 3))
        p.drawRect(rgn)



[docs]class PipelineDropArea(DockArea): def __init__(self, parent, guiPipeline, mainWindow=None, **kwds): super().__init__() self.setStyleSheet("""QSplitter{background-color: transparent;} QSplitter::handle:vertical {background-color: transparent;height: 1px;}""") self.parent = parent self.guiPipeline = guiPipeline self.mainWindow = mainWindow self.inputData = None self.overlay = _PipelineDropAreaOverlay(self) self.textLabel = 'Drop Pipes' colours = getColours() self.fontLabel = getFont(size='LARGE') self.colourLabel = hexToRgb(colours[LABEL_FOREGROUND]) self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) self.layout.setAlignment(QtCore.Qt.AlignTop) @property def currentGuiPipes(self) -> list: 'return all current Pipes in area' if self is not None: Pipes = list(self.findAll()[1].values()) return Pipes @property def currentPipesClassNames(self) -> list: 'return the name of all current modules in area' if self is not None: classNames = [guiPipe.__class__.__name__ for guiPipe in self.currentGuiPipes] return classNames @property def currentPipesNames(self) -> list: 'return the name of all current modules in area' if self is not None: pipesNames = list(self.findAll()[1].keys()) return pipesNames @property def currentPipesNamesAndClasses(self): d = [] for pipeName, guiPipe in self.findAll()[1].items(): d.append((pipeName, guiPipe.__class__.__name__)) return d @property def guiPipesState(self): d = [] for pipeName, guiPipe in self.findAll()[1].items(): d.append((guiPipe.__class__.__name__, pipeName, guiPipe.widgetsState, guiPipe.isActive)) return d
[docs] def dragEnterEvent(self, ev): src = ev.source() if isinstance(src, PipesTree) or isinstance(src, ListWidget): ev.accept() else: ev.ignore()
def _paint(self, ev, paintLabel=True): QPainter = QtGui.QPainter painter = QPainter(self) # set font painter.setFont(self.fontLabel) painter.setPen(QtGui.QColor(*self.colourLabel)) rgn = self.contentsRect() rgn = QtCore.QRect(rgn.left(), rgn.top(), rgn.width(), rgn.height()) align = QtCore.Qt.AlignVCenter | QtCore.Qt.AlignHCenter if paintLabel: self.hint = painter.drawText(rgn, align, self.textLabel) painter.setRenderHint(QPainter.Antialiasing) rectPath = QtGui.QPainterPath() height = self.height() - 2 rectPath.addRoundedRect(QtCore.QRectF(1, 1, self.width() - 2, height), 1, 1) painter.drawPath(rectPath) painter.end()
[docs] def paintEvent(self, ev): """ Draws central label """ if not self.currentPipesNames: self._paint(ev) else: self._paint(ev, paintLabel=False)
[docs] def dropEvent(self, ev): src = ev.source() if isinstance(src, PipesTree) or isinstance(src, ListWidget): selectedList = src.selectedItems() names = [i.text() for i in selectedList] for sel in names: guiPipeName = self.guiPipeline._getSerialName(str(sel)) self.guiPipeline._addGuiPipe(guiPipeName, sel, position=self.dropArea) ev.accept() self.dropArea = None self.overlay.setDropArea(self.dropArea)
[docs] def dragMoveEvent(self, ev): # print "drag move" ld = ev.pos().x() rd = self.width() - ld td = ev.pos().y() bd = self.height() - td mn = min(ld, rd, td, bd) if (ld == mn or td == mn) and mn > self.height() / 3.: self.dropArea = "top" # self.dropArea = "center" elif (rd == mn or ld == mn) and mn > self.width() / 3.: self.dropArea = "bottom" elif rd == mn and td > bd: self.dropArea = "bottom" elif rd == mn and td < bd: self.dropArea = "top" elif ld == mn and td > bd: self.dropArea = "bottom" elif ld == mn and td < bd: self.dropArea = "top" elif ld == mn: self.dropArea = "left" elif td == mn: self.dropArea = "top" elif bd == mn: self.dropArea = "bottom" if ev.source() is self and self.dropArea == 'center': self.dropArea = None ev.ignore() elif self.dropArea not in self.allowedAreas: self.dropArea = None ev.ignore() else: # print " ok" ev.accept() self.overlay.setDropArea(self.dropArea)
[docs] def addBox(self, box=None, position='bottom', relativeTo=None, **kwds): """With these settings the user can close all the boxes from the label 'close box' or pop up and when re-add a new box it makes sure there is a container available. """ if box is None: box = GuiPipe(name='New GuiPipe', **kwds) if position is None: position = 'bottom' neededContainer = {'bottom': 'vertical', 'top': 'vertical', }[position] if relativeTo is None: neighbor = None container = self.addContainer(neededContainer, self.topContainer) if relativeTo is None or relativeTo is self: if self.topContainer is None: container = self neighbor = None else: container = self.topContainer neighbor = None else: if isinstance(relativeTo, str): relativeTo = self.boxes[relativeTo] container = self.getContainer(relativeTo) neighbor = relativeTo if neededContainer != container.type() and container.type() == 'tab': neighbor = container container = container.container() if neededContainer != container.type(): if neighbor is None: container = self.addContainer(neededContainer, self.topContainer) else: container = self.addContainer(neededContainer, neighbor) insertPos = {'bottom': 'after', 'top': 'before', }[position] if container is not None: container.insert(box, insertPos, neighbor) else: container = self.topContainer container.insert(box, insertPos, neighbor) box.area = self self.docks[box.name()] = box return box
[docs] def makeContainer(self, typ): new = _VContainer(self) new.setChildrenCollapsible(False) return new
[docs] def apoptose(self): pass
[docs] def orderedBoxes(self, obj): if isinstance(obj, Dock): return (obj) else: boxes = [] for i in range(obj.count()): boxes.append(self.orderedBoxes(obj.widget(i))) return boxes
def _restoreState(self, state): try: self.restoreState(state) except Exception as err: getLogger().warning('Error restoring pipeline. %s' %err)
[docs] def closeAll(self): for guiPipe in self.currentGuiPipes: guiPipe.close()
[docs]class GuiPipeDrop(DockDrop): """Provides dock-dropping methods""" def __init__(self, allowedAreas=None): DockDrop.__init__(self)
[docs] def dropEvent(self, ev): super(DockDrop, self).dropEvent(ev)
[docs]class GuiPipe(Dock, GuiPipeDrop): preferredPipe = True pipeName = '' pipe = None info = "Pipe details not available yet." #This will appear as toolTip when hovering the GuiPipe Label. _alreadyOpened = False #Use this to open the guiPipe only once. Inside the GuiPipe do: MyGuiPipe._alreadyOpened = True def __init__(self, parent, name, project=None, widgetsParams=None, **kwds): """ :param parent: guiPipeline :param name: string for the new GuiPipe :param params: dict of all widgets variable names and their values :param project: ccpn Project :param kwds: any other """ super(GuiPipe, self).__init__(name, self) self._pulldownSGHeaderText = '-- Select SG --' self._warningIcon = Icon('icons/warning') self.ccpnModule = False self.pipelineBox = True self.autoOrient = False self.isAutoGenerated = False self._parent = parent self._allChildren = set() self.inputData = [] if isinstance(self._parent, Pipeline): self.inputData = self._parent.inputData self.spectrumGroups = self._parent.spectrumGroups if name is None: name = 'New Pipe' self.pipeName = name self._updateLabel(name) self.dragStyle = PipelineBoxDragStyle self.overlay = _PipelineDropAreaOverlay(self) self.project = None if project is not None: self.project = project if self._parent is not None: try: self.application = self._parent.application self.current = self.application.current except: pass self._widgetsState = None if widgetsParams is not None: self.restoreWidgetsState(**widgetsParams) ###### pipeLayout self.pipeFrame = Frame(self, setLayout=False) self.pipeLayout = QtWidgets.QGridLayout() self.pipeFrame.setLayout(self.pipeLayout) self.layout.addWidget(self.pipeFrame) # self.layout.setAlignment(self.pipeFrame, QtCore.Qt.AlignTop) # self.layout.setContentsMargins(5,5,5,5) self._kwargs = None if self.pipe is not None: self.pipe._kwargs = self._kwargs # def initialiseGui(self): # """Define this function on the new pipe file""" # pass # def updatePipeParams(self): # for key, value in self.getParams().items(): # self.pipe._updateRunArgs(key, value)
[docs] def name(self): return self.pipeName
@property def widgetsState(self): return self._widgetsState # @widgetsState.setter # def widgetsState(self, value): # self._widgetsState = value @widgetsState.getter def widgetsState(self): """return {"variableName":"value"} of all gui Variables """ widgetsState = {} for varName, varObj in vars(self).items(): if varObj.__class__.__name__ in commonWidgets.keys(): try: # try because widgets can be dinamically deleted widgetsState[varName] = getattr(varObj, commonWidgets[varObj.__class__.__name__][0])() except Exception as e: getLogger().debug('Error %s' % str(e)) self._kwargs = widgetsState return widgetsState
[docs] def restoreWidgetsState(self, **widgetsState): 'Restore the gui params. To Call it: _setParams(**{"variableName":"value"}) ' for variableName, value in widgetsState.items(): try: widget = getattr(self, str(variableName)) if widget.__class__.__name__ in commonWidgets.keys(): setWidget = getattr(widget, commonWidgets[widget.__class__.__name__][1]) setWidget(value) except Exception as e: getLogger().debug('Impossible to restore %s value for %s. (%s)' % (variableName, self.pipeName, str(e)))
def _findChildren(self, widget): for i in widget.children(): self._allChildren.update({i}) self._findChildren(i) def _formatLabelWidgets(self): """ Replace the underscore with empty spaces in all labels """ self._findChildren(self) for num, w in enumerate(self._allChildren): if w.__class__.__name__ == Label.__name__: text = w.get() if '_' in text: newText = text.replace('_', ' ') w.setText(newText) def _updateWidgets(self): """ Override this method to update the widgets that are fed by input data """ pass
[docs] def implements(self, name=None): if name is None: return ['GuiPipe'] else: return name == 'GuiPipe'
def _updateLabel(self, name): self.label.deleteLater() # delete original Label self.label = PipelineBoxLabel(name.upper(), self) self.label.closeButton.clicked.connect(self._closePipe) # self.label.arrowDownButton.clicked.connect(self.moveBoxDown) # self.label.arrowUpButton.clicked.connect(self.moveBoxUp) self.moveLabel = True self.orientation = 'horizontal'
[docs] def closePipe(self): self.setParent(None) self.label.setParent(None)
def _closePipe(self): self.closePipe() def _setSpectrumGroupPullDowns(self, widgetVariables, headerText='', headerEnabled=False, headerIcon=None): """ Used to set the spectrum groups pid in the pulldowns. Called from various guiPipes""" spectrumGroups = list(self.spectrumGroups) if len(spectrumGroups) > 0: for widgetVariable in widgetVariables: selected = _getWidgetByAtt(self, widgetVariable).get() # getLogger().debug(selected) _getWidgetByAtt(self, widgetVariable).setData(texts=[sg.pid for sg in spectrumGroups], objects=spectrumGroups, headerText=headerText, headerEnabled=headerEnabled, headerIcon=headerIcon) # Below a mechanism to autoselect spectrumGroups Pulldown based on the pid. Eg autoselect the Control SG pulldown if in the inputData there is a SG with the name Control in its pid. if selected == self._pulldownSGHeaderText: texts = [sg.pid for sg in spectrumGroups] for text in texts: if len(widgetVariable.split('_'))>0: for subText in widgetVariable.split('_'): if subText.lower() in text.lower(): selected = text continue _getWidgetByAtt(self, widgetVariable).select(selected) else: for widgetVariable in widgetVariables: _getWidgetByAtt(self, widgetVariable)._clear() def _setMaxValueRefPeakList(self, widgetVariable): """ Used to set the reference PeakList limits. Called from various guiPipes""" data = list(self.inputData) if len(data) > 0: for spectrum in data: if spectrum is not None: if spectrum.peakLists: pls = spectrum.peakLists _getWidgetByAtt(self, widgetVariable).setMaximum(len(pls) - 1) else: _getWidgetByAtt(self, widgetVariable).setMaximum(0)
[docs] def rename(self, newName): self.label.name = newName
def _moveBoxDown(self): name = self.name() boxes = self.pipelineArea.childState(self.pipelineArea.topContainer)[1] boxesNames = [] for i, box in enumerate(boxes): boxesNames.append(box[1]) i = boxesNames.index(name) count = len(boxes) j = i while j < count - 1: next = self.pipelineArea.docks[str(boxesNames[i + 1])] self.pipelineArea.moveDock(self, 'bottom', next) j += 1 def _moveBoxUp(self): name = self.name() boxes = self.pipelineArea.childState(self.pipelineArea.topContainer)[1] boxesNames = [] for i, box in enumerate(boxes): boxesNames.append(box[1]) i = boxesNames.index(name) j = i while j > 0: next = self.pipelineArea.docks[str(boxesNames[i - 1])] self.pipelineArea.moveDock(self, 'top', next) j = j - 1
[docs] def startDrag(self): # if len(self.pipelineArea.findAll()[1].keys()) > 1: self.drag = QtGui.QDrag(self) mime = QtCore.QMimeData() self.drag.setMimeData(mime) dragPixmap = self.grab() # self.drag.setPixmap( # dragPixmap.scaledToWidth(128) if dragPixmap.width() < dragPixmap.height() else dragPixmap.scaledToHeight( # 128)) # make sure that the dragPixmap is not too big self.drag.setPixmap(dragPixmap.scaledToWidth(max(32, min(128, dragPixmap.width()))) if dragPixmap.width() < dragPixmap.height() else dragPixmap.scaledToHeight(max(32, min(128, dragPixmap.height())))) self.widgetArea.setStyleSheet(self.dragStyle) self.update() action = self.drag.exec_() self.updateStyle()
[docs] def dragEnterEvent(self, ev): src = ev.source() if hasattr(src, 'implements') and src.implements('GuiPipe'): ev.accept() elif isinstance(src, PipesTree) or isinstance(src, ListWidget): ev.accept() else: ev.ignore()
# def dragMoveEvent(self, *args): # DockDrop.dragMoveEvent(self, *args)
[docs] def dragMoveEvent(self, ev): # print "drag move" ld = ev.pos().x() rd = self.width() - ld td = ev.pos().y() bd = self.height() - td mn = min(ld, rd, td, bd) if (ld == mn or td == mn) and mn > self.height() / 3.: self.dropArea = "top" # self.dropArea = "center" elif (rd == mn or ld == mn) and mn > self.width() / 3.: self.dropArea = "bottom" elif rd == mn and td > bd: self.dropArea = "bottom" elif rd == mn and td < bd: self.dropArea = "top" elif ld == mn and td > bd: self.dropArea = "bottom" elif ld == mn and td < bd: self.dropArea = "top" elif ld == mn: self.dropArea = "left" elif td == mn: self.dropArea = "top" elif bd == mn: self.dropArea = "bottom" # DockDrop.dragMoveEvent(self, *args) if ev.source() is self and self.dropArea == 'center': self.dropArea = None ev.ignore() elif self.dropArea not in self.allowedAreas: self.dropArea = None ev.ignore() else: # print " ok" ev.accept() self.overlay.setDropArea(self.dropArea)
[docs] def dragLeaveEvent(self, *args): DockDrop.dragLeaveEvent(self, *args)
[docs] def dropEvent(self, *args): if len(args)>0: ev = args[0] src = ev.source() src = ev.source() if isinstance(src, PipesTree) or isinstance(src, ListWidget): selectedList = src.selectedItems() names = [i.text() for i in selectedList] for name in names: position = self.dropArea guiPipeName = self._parent._getSerialName(str(name)) self._parent._addGuiPipe(guiPipeName, name, position=position, relativeTo=self) self.dropArea = None self.overlay.setDropArea(self.dropArea) ev.accept() else: DockDrop.dropEvent(self, *args)
[docs] def setActive(self, state): self.label.checkBox.setChecked(state)
@property def isActive(self): checkBox = self.label.checkBox if checkBox.isChecked(): return True else: return False
[docs]class AutoGeneratedGuiPipe(GuiPipe): """ GuiPipe step base class. For Autogeneration of Gui: In order to genenerate a (crude) gui, you'll need to populate the params class variable. First, make it an iterable: params = [] Now, add variables in the order you want the input boxes to show up. Every variable is declared in a mapping (generally a dictionary) with two required keys: 'variable' : The keyward parameter that will be used when the function is called. 'value' : the possible values. See below. In addition to the required keys, several optional keys can be used: label : the label used. If this is left out, the variable name will be used instead. default : the default value stepsize : the stepsize for spinboxes. If you include this for non-spinboxes it will be ignored The 'value' entry: The type of widget generated is controled by the value of this entry, if the value is an iterable, the type of widget is controlled by the first item in the iterable strings are not considered iterables here. value type : type of widget string : LineEdit boolean : Checkbox Iterable(strings) : PulldownList Iterable(int, int) : Spinbox Iterable(float, float) : DoubleSpinbox Iterable(Iterables(str, object)) : PulldownList where the object is passed instead of the string """ pipeName = '' autoGuiParams = {} def __init__(self, parent, name, **kwds): """ :param parent: guiPipeline :param name: string for the new GuiPipe :param params: dict of all widgets variable names and their values :param project: ccpn Project :param kwds: any other """ GuiPipe.__init__(self, parent=parent, name=name) self._kwargs = {} self.pipeName = name self.isAutoGenerated = True if self.autoGuiParams is not None: self.guiPipe = generateWidget(self.autoGuiParams, widget=self, argsDict=self._kwargs, columns=4) else: self.guiPipe = self
[docs]class PipelineBoxLabel(DockLabel, VerticalLabel): def __init__(self, name, *args): super(PipelineBoxLabel, self).__init__(name, showCloseButton=True, *args) QtWidgets.QLabel.__init__(self) self.updateStyle() self.setExtraButtons() self.name = name # This is a terrible way to get the parent. Need to check the hierarchy classes from QtGraph and fix this if len(args) > 0: if isinstance(args[0], GuiPipe): self._parent = args[0] if self._parent: self.setToolTip(self._parent.info)
[docs] def updateStyle(self): self.hStyle = PipelineBoxLabelStyle self.setStyleSheet(self.hStyle) pass
[docs] def setExtraButtons(self): self.checkBox = CheckBox(self, text='Active', callback=None) self.checkBox.setMaximumHeight(15) # self.checkBox.setStyleSheet("""QCheckBox {background-color: transparent;}""") self.closeButton = Button(self) self.closeButton.setStyleSheet("""QPushButton {background-color: transparent; color:black; border: 0px solid transparent}""") self.closeButton.setIcon(QtWidgets.QApplication.style().standardIcon(QtGui.QStyle.SP_TitleBarCloseButton)) self.closeButton.setMaximumHeight(15)
# def checkActiveBox(self): # self.checkBox.setChecked(not self.checkBox.isChecked())
[docs] def mousePressEvent(self, ev): if ev.button() == QtCore.Qt.LeftButton: self.pressPos = ev.pos() self.startedDrag = False ev.accept() if ev.button() == QtCore.Qt.RightButton: menu = self._createContextMenu() if menu: menu.move(ev.globalPos().x(), ev.globalPos().y() + 10) menu.exec()
[docs] def mouseDoubleClickEvent(self, ev): if ev.button() == QtCore.Qt.LeftButton: pass
[docs] def resizeEvent(self, ev): size = ev.size().height() pos = QtCore.QPoint(ev.size().width() - 60, 0) # self.activeLabel.move(pos) # self.lineEdit.move(pos) pos = QtCore.QPoint(ev.size().width() - 80, 0) self.checkBox.move(pos) pos = QtCore.QPoint(ev.size().width() - 20, 0) self.closeButton.move(pos) super(DockLabel, self).resizeEvent(ev)
[docs] def mouseMoveEvent(self, ev): if not self.startedDrag and (ev.pos()).manhattanLength() > QtWidgets.QApplication.startDragDistance(): self.dock.startDrag() ev.accept()
[docs] def paintEvent(self, ev): p = QtGui.QPainter(self) rgn = self.contentsRect() align = self.alignment() self.hint = p.drawText(rgn, align, self.name) p.end() self.setMaximumHeight(self.hint.height())
# self.setMinimumHeight(15) # self.setMaximumWidth(16777215) def _createContextMenu(self): contextMenu = Menu('', self, isFloatWidget=True) contextMenu.addAction('Close', self._parent.closePipe) contextMenu.addAction('Close All', self._parent._parent._closeAllGuiPipes) return contextMenu
[docs]class pipeTreeItem(QtWidgets.QTreeWidgetItem): def __init__(self, parent, name, isPipeCategory=False, draggable=False, expanded=True, **kwds): super().__init__(parent) self.pipeName = name self.isPipeCategory = isPipeCategory self.setText(0, name) if draggable: self.setFlags(self.flags() | QtCore.Qt.ItemIsDragEnabled) else: self.setFlags(self.flags() ^ QtCore.Qt.ItemIsDragEnabled) self.setExpanded(expanded)
[docs] def text(self): return self.pipeName
[docs]class PipesTree(QtWidgets.QTreeWidget, Base): def __init__(self, parent, guiPipeline, **kwds): super().__init__(parent) Base._init(self, acceptDrops=False, **kwds) self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) self.setDragEnabled(True) self.itemDoubleClicked.connect(self._itemDoubleClickCallback) self.parent = parent self.guiPipeline = guiPipeline self.setHeaderLabel('Double click or drag&drop ->') self._selectableItems = [] self._availablePipeNames = [] def _addPipesToTree(self): """ Add the pipes to the tree based on the category. """ self.clear() from collections import defaultdict allPipes = [(p.pipeCategory, p.pipeName) for p in self.guiPipeline.pipes if hasattr(p, PIPE_CATEGORY)] d = defaultdict(list) for k, v in allPipes: d[k].append(v) dd = OrderedDict([(el, d[el]) for el in PIPE_CATEGORIES]) self.buildTree(dd)
[docs] def buildTree(self, dd): for categoryName, pipeNames in dd.items(): if isinstance(pipeNames, list): if len(pipeNames)>0: # add a branch only if there are available pipes for that category categoryItem = pipeTreeItem(self, categoryName, isPipeCategory=True) for pipeName in sorted(pipeNames): pipeItem = pipeTreeItem(categoryItem, pipeName, draggable=True) # pipeItem.setIcon(0, Icon('icons/mario-pipe')) self._selectableItems.append(pipeItem) self._availablePipeNames.append(pipeName)
[docs] def mouseReleaseEvent(self, event): """Re-implementation of the mouse press event so right click can be used to delete items from the sidebar. """ if event.button() == QtCore.Qt.RightButton: for item in self.selectedItems(): if item is not None: if not item.isPipeCategory: self._raiseContextMenu(event, item) event.accept() else: super().mouseReleaseEvent(event)
def _itemDoubleClickCallback(self, item): if not item.isPipeCategory: self.guiPipeline.addPipe(item.pipeName) def _infoCallback(self, pipeName): from ccpn.ui.gui.widgets.MessageDialog import showInfo pipe = self.guiPipeline.getPipeFromName(pipeName) showInfo(pipeName, 'Not implemented yet') def _raiseContextMenu(self, event, item): contextMenu = Menu('', self, isFloatWidget=True) contextMenu.addItem("Add to pipeline ", callback=self._addToPipelineCallback) contextMenu.addItem("Clear pipeline", callback=self.guiPipeline._closeAllGuiPipes) contextMenu.addSeparator() contextMenu.addItem("Info", callback=partial(self._infoCallback, item.pipeName)) contextMenu.move(event.globalPos().x(), event.globalPos().y() + 10) contextMenu.exec_() def _addToPipelineCallback(self): selectedItems = self.selectedItems() names = [i.pipeName for i in selectedItems if not i.isPipeCategory] for name in names: self.guiPipeline.addPipe(name)
[docs] def selectItems(self, names): items = [] for name in names: found = self.findItems(name, QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive) items.extend(found) if items: self.clearSelection() for item in items: item.setSelected(True) if item.parent(): item.parent().setExpanded(True) self.scrollToItem(item)
[docs]def testGuiPipe(GuiPipe): """ :param GuiPipe: :return: Open the Gui pipe in a mock Gui pipeline """ from PyQt5 import QtWidgets from ccpn.ui.gui.widgets.Application import TestApplication app = TestApplication() win = QtWidgets.QMainWindow() pipeline = PipelineDropArea(win) demoGuiPipe = GuiPipe(parent=pipeline) pipeline.addDock(demoGuiPipe) win.setCentralWidget(pipeline) win.resize(1000, 500) win.show() app.start()