Example #1
0
 def addMenuEntry(self, name, slot=None):
     if not self._menuWidget:
         self._menuWidget = MenuWidget(self.parent(), self)
         self.setMouseTracking(True)
     return self._menuWidget.addEntry(name, slot)
Example #2
0
 def addMenuEntry(self, name, slot=None):
     if not self._menuWidget:
         self._menuWidget = MenuWidget(self.parent(), self)
         self.setMouseTracking(True)
     return self._menuWidget.addEntry(name, slot)
Example #3
0
class ConnectableWidget(VispaWidget, VispaWidgetOwner):
    """ Widget which can be connection by PortConnections to other selectable widgets.
    
    Supports showing source and sink ports.
    The widget is owner of PortWidgets.
    """

    BACKGROUND_SHAPE = 'ROUNDRECT'
    SHOW_PORT_NAMES = False
    SHOW_PORT_LINES = False

    # possible positions for port names
    PORT_NAMES_NEXT_TO_PORTS = 0
    PORT_NAMES_ABOVE_PORTS = 1

    # default position for port names
    PORT_NAMES_POSITION = PORT_NAMES_NEXT_TO_PORTS

    NO_VALID_PORT_NAMES_POSITION_MESSAGE = "No valid position for port names was set."

    PORT_LINES_TARGET_X = -1  # See setShowPortNames()
    PORT_LINES_TARGET_Y = -1

    def __init__(self, parent=None, name=None):
        """ Constructor.
        """
        self._ports = []
        self._showPortNames = False
        self._portNamesPosition = None
        self._showPortLines = False
        self._menuWidget = None
        VispaWidget.__init__(self, parent)
        self.setShowPortNames(self.SHOW_PORT_NAMES)
        self.setPortNamesPosition(self.PORT_NAMES_POSITION)
        self.setShowPortLines(self.SHOW_PORT_LINES)

        if name:
            self.setTitle(name)

    def setShowPortNames(self, show):
        """ If True the port name's will be drawn.
        
        The port names wont be on the port itself.
        Instead they will appear next to the port icons on the ConnectableWidget.
        """
        self._showPortNames = show

    def setPortNamesPosition(self, position):
        """ Sets position where port names will be shown.
        
        Possible values are self.PORT_NAMES_NEXT_TO_PORTS and self.PORT_NAMES_ABOVE_PORTS.
        """
        self._portNamesPosition = position

    def setShowPortLines(self, show):
        """ If True lines from all ports to a specific target point are drawn.
        
        The target point is defined by PORT_LINES_TARGET_X and PORT_LINES_TARGET_Y.
        If both of these values are -1 the target point is set to the widget's centre.
        """
        self._showPortLines = show

    def getPortsHeight(self, portType):
        """ Returns height of all ports of given type.
        
        portType can either be 'sink" or 'source'.
        """
        if portType == "sink":
            ports = self.sinkPorts()
        elif portType == "source":
            ports = self.sourcePorts()
        else:
            return 0

        if len(ports) > 1:
            return ports[0].y() - ports[len(ports) - 1].y(
            ) + 0.5 * self.getEffectivePortHeight(ports[len(ports) - 1])
        elif len(ports) == 1:
            return self.getEffectivePortHeight(ports[0])
        else:
            return 0

    def sizeHint(self):
        """ Returns size needed to draw widget's content.
        """
        #logging.debug(self.__class__.__name__ + ": sizeHint()")
        # arrangePorts() needed because it will be called in rearnangeContent() after sizeHint()
        self.arrangePorts()

        neededWidth = self.getDistance('leftMargin', 1) + self.getDistance(
            'rightMargin', 1)
        neededHeight = self.getDistance('topMargin', 1) + self.getDistance(
            'bottomMargin', 1)
        imageSizeF = self.imageSizeF()

        # width
        titleWidth = 0
        if self.titleIsSet():
            titleWidth = self.getDistance('titleFieldWidth', 1)

        bodyWidth = 0
        sinkPortsWidth = 0
        sourcePortsWidth = 0
        if len(self.sinkPorts()) > 0:
            sinkPortsWidth = self.getDistance('leftMargin',
                                              1) + PortWidget.WIDTH
        if len(self.sourcePorts()) > 0:
            sourcePortsWidth = self.getDistance('rightMargin',
                                                1) + PortWidget.WIDTH

        if self._showPortNames:
            maxSinkTitleWidth = self._getMaxSinkTitleWidth()
            maxSourceTitleWidth = self._getMaxSourceTitleWidth()
            if self._portNamesPosition == self.PORT_NAMES_NEXT_TO_PORTS:
                bodyWidth += maxSinkTitleWidth + self.getDistance(
                    'rightMargin', 1) + maxSourceTitleWidth
            elif self._portNamesPosition == self.PORT_NAMES_ABOVE_PORTS:
                if maxSinkTitleWidth > PortWidget.WIDTH:
                    sinkPortsWidth = 0  #self.getDistance('leftMargin', 1)
                if maxSourceTitleWidth > PortWidget.WIDTH:
                    sourcePortsWidth = 0  #self.getDistance('rightMargin', 1)
                #bodyWidth += maxSinkTitleWidth + self.getDistance('rightMargin', 1) + maxSourceTitleWidth
                bodyWidth += maxSinkTitleWidth + maxSourceTitleWidth
            else:
                logging.waring(self.__class__.__name__ + ": sizeHint() - " +
                               self.NO_VALID_PORT_NAMES_POSITION_MESSAGE)
        bodyWidth += sinkPortsWidth + sourcePortsWidth

        if self.textFieldIsSet():
            bodyWidth += self.getDistance('textFieldWidth', 1)
        bodyWidth = max(
            imageSizeF.width() + self.getDistance("leftMargin", 1) +
            self.getDistance("rightMargin", 1), bodyWidth)

        neededWidth += max(titleWidth, bodyWidth)

        # height
        if self.titleIsSet():
            neededHeight += self.getDistance('titleFieldHeight', 1)

        sinkPortsHeight = self.getPortsHeight("sink") / self.scale()
        sourcePortsHeight = self.getPortsHeight("source") / self.scale()
        textFieldHeight = 0
        if self.textFieldIsSet():
            textFieldHeight += self.textField().getHeight()
        neededHeight += max(sinkPortsHeight, sourcePortsHeight,
                            textFieldHeight, imageSizeF.height())
        if bodyWidth != 0:
            neededHeight += self.getDistance('bottomMargin',
                                             1)  # gap between header and body
            if self._showPortNames and (len(self.sinkPorts()) > 1
                                        or len(self.sourcePorts()) > 1):
                neededHeight += self.getDistance(
                    'bottomMargin', 1)  # additional gap for port names

        return QSize(neededWidth, neededHeight)

    def defineDistances(self, keepDefaultRatio=False):
        """ Extends distances of VispaWidget by the additionally needed distances for displaying ports.
        """
        #if scale == None:
        #    scale = self.scale()
        scale = 1.0

        if not VispaWidget.defineDistances(self, keepDefaultRatio):
            return False
        if len(self.sinkPorts()) > 0:
            self.distances(
            )['textFieldX'] += PortWidget.WIDTH * scale + self.distances(
            )['leftMargin']
            self.distances()['textFieldRight'] = self.distances(
            )['textFieldX'] + self.distances()['textFieldWidth']
            if self._showPortNames:
                self.distances()['textFieldX'] += self._getMaxSinkTitleWidth(
                ) + self.distances()['leftMargin']
                self.distances(
                )['textFieldRight'] += self._getMaxSinkTitleWidth(
                ) + self.distances()['leftMargin']

        firstPortY = self.distances()['height'] - self.distances(
        )['bottomMargin'] - PortWidget.HEIGHT * scale
        self.distances()['firstSinkX'] = self.distances()['leftMargin']
        self.distances()['firstSinkY'] = firstPortY

        if self.textFieldIsSet():
            self.distances()['firstSourceX'] = self.distances(
            )['textFieldRight'] + self.distances()['leftMargin']
        #else:
        self.distances()['firstSourceX'] = self.distances(
        )['width'] - self.distances()['leftMargin'] - PortWidget.WIDTH * scale
        self.distances()['firstSourceY'] = firstPortY

        return True

#    def scaleChanged(self):
#        """ Arranges ports when scale has changed.
#        """
#        VispaWidget.scaleChanged(self)
#        self.arrangePorts()

    def setZoom(self, zoom):
        """ Arranges ports when zoom has changed.
        """
        VispaWidget.setZoom(self, zoom)
        #self.arrangePorts()

    def mousePressEvent(self, event):
        """ Makes sure event is forwarded to both base classes.
        
        If position of event is within the dropArea of a port a QMouseEvent is sent to the port. See dropArea().
        """
        dropAreaPort = self.dropAreaPort(event.pos())
        if dropAreaPort and dropAreaPort.isDragable():
            dropAreaPort.grabMouse()
            newEvent = QMouseEvent(event.type(),
                                   dropAreaPort.mapFromParent(event.pos()),
                                   event.button(), event.buttons(),
                                   event.modifiers())
            QCoreApplication.instance().sendEvent(dropAreaPort, newEvent)
        else:
            VispaWidgetOwner.mousePressEvent(self, event)
            VispaWidget.mousePressEvent(self, event)

    def mouseReleaseEvent(self, event):
        """ Calls realeseMouse() to make sure the widget does not grab the mouse.
        
        Necessary because ConnectableWidgetOwner.propagateEventUnderConnectionWidget() may call grabMouse() on this widget.
        """
        #logging.debug(self.__class__.__name__ +": mouseReleaseEvent()")
        self.releaseMouse()
        VispaWidget.mouseReleaseEvent(self, event)

    def ports(self):
        """ Returns list containing all source and sink port widgets.
        """
        return self._ports

    def addSinkPort(self, name, description=None):
        """ Adds sink port with name and optional description text.
        """
        port = SinkPort(self, name)
        self._addPort(port, description)
        return port

    def addSourcePort(self, name, description=None):
        """ Adds source port with name and optional description text.
        """
        port = SourcePort(self, name)
        self._addPort(port, description)
        return port

    def _addPort(self, port, description=None):
        self._ports.append(port)
        port.show()
        if description:
            self._ports[len(self._ports) - 1].setDescription(description)
        self.scheduleRearangeContent()

    def deleteLater(self):
        if self._menuWidget:
            self.removeMenu()
        for port in self._ports:
            port.deleteAttachedConnections()
        VispaWidget.deleteLater(self)

    def removePort(self, port):
        """ Removes given port if it is port of this widget.
        """
        if port in self._ports:
            port.deleteAttachedConnections()
            self._ports.remove(port)
            port.setParent(None)
            port.deleteLater()
            self.scheduleRearangeContent()

    def portExists(self, name, description=None):
        for port in self._ports:
            if port.name() == name and port.description() == description:
                return True
        return False

    def removePorts(self, filter=None):
        """ Remove registered ports.
        
        If filter is "sink" only sinks are removed, if it is "source" only sources are removed, otherwise all ports are removed.
        """
        if filter and (filter != "sink" and filter != "source"):
            filter = None

        parentIsWidgetOwner = False
        ports = self._ports[:]
        for port in ports:
            if not filter or port.portType() == filter:
                port.deleteAttachedConnections()
                self._ports.remove(port)
                port.setParent(None)
                port.deleteLater()
        self.scheduleRearangeContent()
        self.update()

    def sinkPorts(self):
        """ Returns list of all sink ports set.
        """
        return [port for port in self._ports if port.portType() == "sink"]

        def isSink(port):
            return port.portType() == 'sink'

        return filter(isSink, self._ports)

    def sourcePorts(self):
        """ Returns list of all source ports set.
        """
        return [port for port in self._ports if port.portType() == "source"]

        def isSource(port):
            return port.portType() == 'source'

        return filter(isSource, self._ports)

    def sinkPort(self, name):
        """ Returns sink port with given name or None if no such port is found.
        """
        return self.port(name, 'sink')

    def sourcePort(self, name):
        """ Returns source port with given name or None if no such port is found.
        """
        return self.port(name, 'source')

    def port(self, name, type):
        """ Returns port with given name and of given type.
        """
        if name == None:
            return None
        for port in self._ports:
            if port.portType() == type and port.name() == name:
                return port
        return None

    def _getMaxPortTitleWidth(self, type):
        if type == 'sink':
            ports = self.sinkPorts()
        elif type == 'source':
            ports = self.sourcePorts()
        else:
            return 0

        if len(ports) < 1:
            return 0
        return max([port.titleField().getWidth() for port in ports])

    def _getMaxSinkTitleWidth(self):
        return self._getMaxPortTitleWidth('sink')

    def _getMaxSourceTitleWidth(self):
        return self._getMaxPortTitleWidth('source')

    def getEffectivePortHeight(self, port):
        """ Returns the bigger value of the source height and the height of the port name text field.
        """
        portHeight = port.height()
        if not self._showPortNames:
            return portHeight

        titleHeight = port.titleField().getHeight() * self.scale()

        if self._portNamesPosition == self.PORT_NAMES_NEXT_TO_PORTS:
            return max(portHeight, titleHeight)
        elif self._portNamesPosition == self.PORT_NAMES_ABOVE_PORTS:
            return portHeight + titleHeight
        logging.waring(self.__class__.__name__ +
                       ": getEffectivePortHeight() - " +
                       self.NO_VALID_PORT_NAMES_POSITION_MESSAGE)
        return 0

    def rearangeContent(self):
        """ Arranges ports after content is rearranged by VispaWidget.
        """
        VispaWidget.rearangeContent(self)
        self.arrangePorts(
        )  # has to be after rearangeContent(), prevents infinite loop (..getDistance())

    def centerSinglePortVertically(self, ports, portX):
        """ Centers port vertically within body part (widget without title) of ModuleWidget.
        
        ports can either be the list of source or sink ports of ModuleWidget.
        portX specifies the designated x coordinate to be adjustable for sinks and sources.
        """
        if len(ports) != 1 or not isinstance(ports[0], PortWidget):
            logging.warning(
                self.__class__.__name__ +
                ": centerSinglePortVertically() - This method was designed for plugins with one port. Falling back to default arrangement."
            )
            return False
        ports[0].move(portX,
                      (self.height() + self.getDistance("titleFieldHeight")) *
                      0.5)
        return True

    def arrangePorts(self, filter=None):
        """ Sets positions of set ports depending on zoom factor.
        
        If filter is set it may be 'sink' or 'source'.
        """
        if filter and (filter != "sink" and filter != "source"):
            filter = None
        sinkCounter = 0
        sourceCounter = 0

        sinkX = self.getDistance('firstSinkX')
        sinkY = self.getDistance('firstSinkY')
        sourceX = self.getDistance('firstSourceX')
        sourceY = self.getDistance('firstSourceY')

        for port in self._ports:
            if port.portType() == 'sink' and (not filter or filter == "sink"):
                sinkCounter += 1
                port.move(sinkX, sinkY)
                sinkY -= self.getDistance(
                    'topMargin') + self.getEffectivePortHeight(
                        port)  #+ PortWidget.HEIGHT * self.scale()

            elif port.portType() == 'source' and (not filter
                                                  or filter == "source"):
                sourceCounter += 1
                port.move(sourceX, sourceY)
                sourceY -= self.getDistance(
                    'topMargin') + self.getEffectivePortHeight(
                        port)  # + PortWidget.HEIGHT * self.scale()

    def drawBody(self, painter):
        """ Takes care of painting widget content on given painter.
        """
        self.drawPortLines(painter)
        self.drawTextField(painter)
        self.drawImage(painter)
        self.drawPortNames(painter)

    def drawPortNames(self, painter):
        """ Paints port names next to PortWidget.
        
        See setShowPortNames().
        """
        if not self._showPortNames:
            return

        # factor should be 0.5, but text height is calculated to big
        titleHeightFactor = 0.4

        if self._portNamesPosition == self.PORT_NAMES_NEXT_TO_PORTS:
            for port in self.sinkPorts():
                if port.titleField():
                    port.titleField().paint(
                        painter,
                        port.x() + self.getDistance('rightMargin') +
                        port.width(),
                        port.y() - titleHeightFactor *
                        port.getDistance('titleFieldHeight'), self.scale())

            for port in self.sourcePorts():
                if port.titleField():
                    #logging.debug(self.__class__.__name__ +": drawPortNames() - "+ port.name() +", "+ str(port.titleField()._autoscaleFlag))
                    port.titleField().paint(
                        painter,
                        port.x() - port.getDistance('titleFieldWidth') -
                        self.getDistance('rightMargin'),
                        port.y() - titleHeightFactor *
                        port.getDistance('titleFieldHeight'), self.scale())
        elif self._portNamesPosition == self.PORT_NAMES_ABOVE_PORTS:
            painter.pen().setWidth(2)
            for port in self.sinkPorts():
                if port.titleField():
                    port.titleField().paint(
                        painter, self.getDistance('firstSinkX'),
                        port.y() - titleHeightFactor *
                        port.getDistance('titleFieldHeight') - port.height(),
                        self.scale())

            for port in self.sourcePorts():
                if port.titleField():
                    #logging.debug(self.__class__.__name__ +": drawPortNames() - "+ port.name() +", "+ str(port.titleField()._autoscaleFlag))
                    port.titleField().paint(
                        painter,
                        self.width() - port.getDistance('titleFieldWidth') -
                        port.width() * 0.5,
                        port.y() - titleHeightFactor *
                        port.getDistance('titleFieldHeight') - port.height(),
                        self.scale())
        else:
            logging.waring(self.__class__.__name__ + ": drawPortNames() - " +
                           self.NO_VALID_PORT_NAMES_POSITION_MESSAGE)

    def drawPortLines(self, painter):
        """ Draws lines from every port to a common point.
        
        See setShowPortLines().
        """
        if not self._showPortLines:
            return

        if self.PORT_LINES_TARGET_X == -1 and self.PORT_LINES_TARGET_Y == -1:
            targetPoint = QPoint(
                self.width(),
                self.height() + self.getDistance("titleFieldBottom")) * 0.5
        else:
            targetPoint = QPoint(self.PORT_LINES_TARGET_X,
                                 self.PORT_LINES_TARGET_Y) * self.scale()

        painter.setPen(QPen(QColor('black')))
        painter.pen().setWidth(1)

        for port in self.ports():
            painter.drawLine(port.connectionPoint("widget"), targetPoint)

    def dropArea(self, port):
        """ A drop area is a QRect in which the ConnectableWidget accepts dropping of PortWidgets to create connections.
        
        The area is greater than the port itself to make dropping easier.
        """
        if self._showPortNames:
            return port.frameGeometry().united(port.titleField().getDrawRect())
        topMargin = self.getDistance("topMargin")
        topMarginHalf = 0.5 * topMargin
        frameGeometry = port.frameGeometry()
        return QRect(frameGeometry.x() - topMarginHalf,
                     frameGeometry.y() - topMarginHalf,
                     frameGeometry.width() + topMargin,
                     frameGeometry.height() + topMargin)

    def dropAreaPort(self, position):
        """ If a port's drop area is associated with position the port is returned.
        
        If there is no drop area associated with the position None is returned.
        See dropArea().
        """
        for port in self._ports:
            if self.dropArea(port).contains(position):
                return port
        return None

    def mouseMoveEvent(self, event):
        if bool(event.buttons() & Qt.LeftButton):
            VispaWidget.mouseMoveEvent(self, event)
        elif self._menuWidget:
            self.positionizeMenuWidget()

        self.showMenu()

    def showMenu(self):
        if self._menuWidget:
            self._menuWidget.show()
            self._menuWidget.raise_()

    def leaveEvent(self, event):
        #logging.debug("%s: leaveEvent()" % self.__class__.__name__)
        parentCursorPos = self.parent().mapFromGlobal(self.cursor().pos())
        bottomRight = self.geometry().bottomRight()
        if ( (not self.isSelected() or (self._menuWidget and self._menuWidget.cursorHasEntered())) \
         and (self._menuWidget and self.parent().childAt(parentCursorPos) != self._menuWidget) ) \
         or (self._menuWidget and ( parentCursorPos.x() > bottomRight.x() or parentCursorPos.y() > bottomRight.y())):
            self._menuWidget.hide()

    def menu(self):
        return self._menuWidget

    def addMenuEntry(self, name, slot=None):
        if not self._menuWidget:
            self._menuWidget = MenuWidget(self.parent(), self)
            self.setMouseTracking(True)
        return self._menuWidget.addEntry(name, slot)

    def removeMenuEntry(self, entry):
        if not self._menuWidget:
            return
        self._menuWidget.removeEntry(entry)
        if self._menuWidget.len() == 0:
            self.removeMenu()

    def removeMenu(self):
        self._menuWidget.hide()
        self._menuWidget.setParent(None)
        self._menuWidget.deleteLater()
        self._menuWidget = None

    def positionizeMenuWidget(self):
        if self._menuWidget:
            headerOffset = 0
            if isinstance(self.parent(), VispaWidget):
                headerOffset = self.parent().getDistance("titleFieldBottom")
            self._menuWidget.move(
                max(0,
                    self.x() - 0.5 *
                    (self._menuWidget.width() - self.width())),
                max(0, headerOffset,
                    self.y() - self._menuWidget.height() + 1))

    def dragWidget(self, pPos):
        VispaWidget.dragWidget(self, pPos)
        self.positionizeMenuWidget()

    def select(self, sel=True, multiSelect=False):
        VispaWidget.select(self, sel, multiSelect)
        if not sel and self._menuWidget:
            self._menuWidget.hide()

    def move(self, *target):
        VispaWidget.move(self, *target)
        if self._menuWidget:
            self._menuWidget.hide()
        self.updateAttachedConnections()

    def updateAttachedConnections(self):
        for port in self._ports:
            port.updateAttachedConnections()

    def attachedConnections(self):
        connections = []
        for port in self._ports:
            connections += port.attachedConnections()
        return connections
Example #4
0
class ConnectableWidget(VispaWidget, VispaWidgetOwner):
    """ Widget which can be connection by PortConnections to other selectable widgets.
    
    Supports showing source and sink ports.
    The widget is owner of PortWidgets.
    """
    
    BACKGROUND_SHAPE = 'ROUNDRECT'
    SHOW_PORT_NAMES = False
    SHOW_PORT_LINES = False
    
    # possible positions for port names
    PORT_NAMES_NEXT_TO_PORTS = 0
    PORT_NAMES_ABOVE_PORTS = 1
    
    # default position for port names
    PORT_NAMES_POSITION = PORT_NAMES_NEXT_TO_PORTS
    
    NO_VALID_PORT_NAMES_POSITION_MESSAGE = "No valid position for port names was set."
    
    PORT_LINES_TARGET_X = -1     # See setShowPortNames()
    PORT_LINES_TARGET_Y = -1
   
    def __init__(self, parent=None, name=None):
        """ Constructor.
        """
        self._ports = []
        self._showPortNames = False
        self._portNamesPosition = None
        self._showPortLines = False
        self._menuWidget = None
        VispaWidget.__init__(self, parent)
        self.setShowPortNames(self.SHOW_PORT_NAMES)
        self.setPortNamesPosition(self.PORT_NAMES_POSITION)
        self.setShowPortLines(self.SHOW_PORT_LINES)
        
        if name:
            self.setTitle(name)
    
    def setShowPortNames(self, show):
        """ If True the port name's will be drawn.
        
        The port names wont be on the port itself.
        Instead they will appear next to the port icons on the ConnectableWidget.
        """
        self._showPortNames = show
        
    def setPortNamesPosition(self, position):
        """ Sets position where port names will be shown.
        
        Possible values are self.PORT_NAMES_NEXT_TO_PORTS and self.PORT_NAMES_ABOVE_PORTS.
        """
        self._portNamesPosition = position
    
    def setShowPortLines(self, show):
        """ If True lines from all ports to a specific target point are drawn.
        
        The target point is defined by PORT_LINES_TARGET_X and PORT_LINES_TARGET_Y.
        If both of these values are -1 the target point is set to the widget's centre.
        """
        self._showPortLines = show
        
    def getPortsHeight(self, portType):
        """ Returns height of all ports of given type.
        
        portType can either be 'sink" or 'source'.
        """
        if portType == "sink":
            ports = self.sinkPorts()
        elif portType == "source":
            ports = self.sourcePorts()
        else:
            return 0

        if len(ports) > 1:
            return ports[0].y() - ports[len(ports) -1].y() + 0.5 * self.getEffectivePortHeight(ports[len(ports) -1])
        elif len(ports) == 1:
            return self.getEffectivePortHeight(ports[0])
        else:
            return 0

    def sizeHint(self):
        """ Returns size needed to draw widget's content.
        """
        #logging.debug(self.__class__.__name__ + ": sizeHint()")
        # arrangePorts() needed because it will be called in rearnangeContent() after sizeHint()
        self.arrangePorts()
        
        neededWidth = self.getDistance('leftMargin', 1) + self.getDistance('rightMargin', 1)
        neededHeight = self.getDistance('topMargin', 1) + self.getDistance('bottomMargin', 1)
        imageSizeF = self.imageSizeF()
        
        # width
        titleWidth = 0
        if self.titleIsSet():
            titleWidth = self.getDistance('titleFieldWidth', 1)
            
        bodyWidth = 0
        sinkPortsWidth = 0
        sourcePortsWidth = 0
        if len(self.sinkPorts()) > 0:
            sinkPortsWidth = self.getDistance('leftMargin', 1) + PortWidget.WIDTH
        if len(self.sourcePorts()) > 0:
            sourcePortsWidth = self.getDistance('rightMargin', 1) + PortWidget.WIDTH
            
        if self._showPortNames:
            maxSinkTitleWidth = self._getMaxSinkTitleWidth()
            maxSourceTitleWidth = self._getMaxSourceTitleWidth()
            if self._portNamesPosition == self.PORT_NAMES_NEXT_TO_PORTS:
                bodyWidth += maxSinkTitleWidth + self.getDistance('rightMargin', 1) + maxSourceTitleWidth
            elif self._portNamesPosition == self.PORT_NAMES_ABOVE_PORTS:
                if maxSinkTitleWidth > PortWidget.WIDTH:
                    sinkPortsWidth = 0#self.getDistance('leftMargin', 1)
                if maxSourceTitleWidth > PortWidget.WIDTH:
                    sourcePortsWidth = 0#self.getDistance('rightMargin', 1)
                #bodyWidth += maxSinkTitleWidth + self.getDistance('rightMargin', 1) + maxSourceTitleWidth
                bodyWidth += maxSinkTitleWidth + maxSourceTitleWidth
            else:
                logging.waring(self.__class__.__name__ +": sizeHint() - "+ self.NO_VALID_PORT_NAMES_POSITION_MESSAGE)
        bodyWidth += sinkPortsWidth + sourcePortsWidth
                            
        if self.textFieldIsSet():
            bodyWidth += self.getDistance('textFieldWidth', 1)
        bodyWidth = max(imageSizeF.width() + self.getDistance("leftMargin", 1) + self.getDistance("rightMargin", 1), bodyWidth)
            
        neededWidth += max(titleWidth, bodyWidth)
        
        # height        
        if self.titleIsSet():
            neededHeight += self.getDistance('titleFieldHeight', 1)

        sinkPortsHeight = self.getPortsHeight("sink") / self.scale()
        sourcePortsHeight = self.getPortsHeight("source") / self.scale()
        textFieldHeight = 0
        if self.textFieldIsSet():
            textFieldHeight += self.textField().getHeight()
        neededHeight += max(sinkPortsHeight, sourcePortsHeight, textFieldHeight, imageSizeF.height())
        if bodyWidth != 0:
             neededHeight += self.getDistance('bottomMargin', 1)    # gap between header and body
             if self._showPortNames and (len(self.sinkPorts()) > 1 or len(self.sourcePorts()) > 1):
                 neededHeight += self.getDistance('bottomMargin', 1)    # additional gap for port names
        
        return QSize(neededWidth, neededHeight)
    
    def defineDistances(self, keepDefaultRatio=False):
        """ Extends distances of VispaWidget by the additionally needed distances for displaying ports.
        """
        #if scale == None:
        #    scale = self.scale()
        scale = 1.0
        
        if not VispaWidget.defineDistances(self, keepDefaultRatio):
            return False
        if len(self.sinkPorts()) > 0:
            self.distances()['textFieldX'] += PortWidget.WIDTH * scale + self.distances()['leftMargin']
            self.distances()['textFieldRight'] = self.distances()['textFieldX'] + self.distances()['textFieldWidth']
            if self._showPortNames:
                self.distances()['textFieldX'] += self._getMaxSinkTitleWidth() + self.distances()['leftMargin']
                self.distances()['textFieldRight'] += self._getMaxSinkTitleWidth() + self.distances()['leftMargin']
                
        firstPortY = self.distances()['height'] - self.distances()['bottomMargin'] - PortWidget.HEIGHT * scale
        self.distances()['firstSinkX'] = self.distances()['leftMargin']
        self.distances()['firstSinkY'] = firstPortY
        
        if self.textFieldIsSet():
            self.distances()['firstSourceX'] = self.distances()['textFieldRight'] + self.distances()['leftMargin']
        #else:
        self.distances()['firstSourceX'] = self.distances()['width'] - self.distances()['leftMargin'] - PortWidget.WIDTH * scale
        self.distances()['firstSourceY'] = firstPortY
        
        return True
        
#    def scaleChanged(self):
#        """ Arranges ports when scale has changed.
#        """
#        VispaWidget.scaleChanged(self)
#        self.arrangePorts()

    def setZoom(self, zoom):
        """ Arranges ports when zoom has changed.
        """
        VispaWidget.setZoom(self, zoom)
        #self.arrangePorts()
        
    def mousePressEvent(self, event):
        """ Makes sure event is forwarded to both base classes.
        
        If position of event is within the dropArea of a port a QMouseEvent is sent to the port. See dropArea().
        """
        dropAreaPort = self.dropAreaPort(event.pos())
        if dropAreaPort and dropAreaPort.isDragable():
            dropAreaPort.grabMouse()
            newEvent = QMouseEvent(event.type(), dropAreaPort.mapFromParent(event.pos()), event.button(), event.buttons(), event.modifiers())
            QCoreApplication.instance().sendEvent(dropAreaPort, newEvent)
        else:
            VispaWidgetOwner.mousePressEvent(self, event)
            VispaWidget.mousePressEvent(self, event)

    def mouseReleaseEvent(self, event):
        """ Calls realeseMouse() to make sure the widget does not grab the mouse.
        
        Necessary because ConnectableWidgetOwner.propagateEventUnderConnectionWidget() may call grabMouse() on this widget.
        """
        #logging.debug(self.__class__.__name__ +": mouseReleaseEvent()")
        self.releaseMouse()
        VispaWidget.mouseReleaseEvent(self, event)

    def ports(self):
        """ Returns list containing all source and sink port widgets.
        """
        return self._ports
    
    def addSinkPort(self, name, description=None):
        """ Adds sink port with name and optional description text.
        """
        port=SinkPort(self, name)
        self._addPort(port, description)
        return port
        
    def addSourcePort(self, name, description=None):
        """ Adds source port with name and optional description text.
        """
        port=SourcePort(self, name)
        self._addPort(port, description)
        return port
        
    def _addPort(self, port, description=None):
        self._ports.append(port)
        port.show()
        if description:
            self._ports[len(self._ports) - 1].setDescription(description)
        self.scheduleRearangeContent()
        
    def deleteLater(self):
        if self._menuWidget:
            self.removeMenu()
        for port in self._ports:
            port.deleteAttachedConnections()
        VispaWidget.deleteLater(self)
        
    def removePort(self, port):
        """ Removes given port if it is port of this widget.
        """
        if port in self._ports:
            port.deleteAttachedConnections()
            self._ports.remove(port)
            port.setParent(None)
            port.deleteLater()
            self.scheduleRearangeContent()
            
    def portExists(self, name, description=None):
        for port in self._ports:
            if port.name() == name and port.description() == description:
                return True
        return False

    def removePorts(self, filter=None):
        """ Remove registered ports.
        
        If filter is "sink" only sinks are removed, if it is "source" only sources are removed, otherwise all ports are removed.
        """
        if filter and (filter != "sink" and filter != "source"):
            filter = None
        
        parentIsWidgetOwner = False
        ports = self._ports[:]
        for port in ports:
            if not filter or port.portType() == filter:
                port.deleteAttachedConnections()
                self._ports.remove(port)
                port.setParent(None)
                port.deleteLater()
        self.scheduleRearangeContent()
        self.update()                    
        
    def sinkPorts(self):
        """ Returns list of all sink ports set.
        """
        return [port for port in self._ports if port.portType() == "sink"]
        def isSink(port):
            return port.portType() == 'sink'
        return filter(isSink, self._ports)
    
    def sourcePorts(self):
        """ Returns list of all source ports set.
        """
        return [port for port in self._ports if port.portType() == "source"]
        def isSource(port):
            return port.portType() == 'source'
        return filter(isSource, self._ports)
    
    def sinkPort(self, name):
        """ Returns sink port with given name or None if no such port is found.
        """
        return self.port(name, 'sink')
    
    def sourcePort(self, name):
        """ Returns source port with given name or None if no such port is found.
        """
        return self.port(name, 'source')
        
    def port(self, name, type):
        """ Returns port with given name and of given type.
        """
        if name == None:
            return None
        for port in self._ports:
            if port.portType() == type and port.name() == name:
                return port
        return None
    
    def _getMaxPortTitleWidth(self, type):
        if type == 'sink':
            ports = self.sinkPorts()
        elif type == 'source':
            ports = self.sourcePorts()
        else:
            return 0
        
        if len(ports) < 1:
            return 0
        return max([port.titleField().getWidth() for port in ports])
    
    def _getMaxSinkTitleWidth(self):
        return self._getMaxPortTitleWidth('sink')
    
    def _getMaxSourceTitleWidth(self):
        return self._getMaxPortTitleWidth('source')
    
    def getEffectivePortHeight(self, port):
        """ Returns the bigger value of the source height and the height of the port name text field.
        """ 
        portHeight = port.height()
        if not self._showPortNames:
            return portHeight
        
        titleHeight = port.titleField().getHeight() * self.scale()
        
        if self._portNamesPosition == self.PORT_NAMES_NEXT_TO_PORTS:
            return max(portHeight, titleHeight)
        elif self._portNamesPosition == self.PORT_NAMES_ABOVE_PORTS:
            return portHeight + titleHeight
        logging.waring(self.__class__.__name__ +": getEffectivePortHeight() - "+ self.NO_VALID_PORT_NAMES_POSITION_MESSAGE)
        return 0

    def rearangeContent(self):
        """ Arranges ports after content is rearranged by VispaWidget.
        """
        VispaWidget.rearangeContent(self)
        self.arrangePorts()     # has to be after rearangeContent(), prevents infinite loop (..getDistance())

    def centerSinglePortVertically(self, ports, portX):
        """ Centers port vertically within body part (widget without title) of ModuleWidget.
        
        ports can either be the list of source or sink ports of ModuleWidget.
        portX specifies the designated x coordinate to be adjustable for sinks and sources.
        """
        if len(ports) != 1 or not isinstance(ports[0], PortWidget):
            logging.warning(self.__class__.__name__ + ": centerSinglePortVertically() - This method was designed for plugins with one port. Falling back to default arrangement.")
            return False
        ports[0].move(portX, (self.height() + self.getDistance("titleFieldHeight")) * 0.5)
        return True
    
    def arrangePorts(self, filter=None):
        """ Sets positions of set ports depending on zoom factor.
        
        If filter is set it may be 'sink' or 'source'.
        """
        if filter and (filter != "sink" and filter != "source"):
            filter = None
        sinkCounter = 0
        sourceCounter = 0
        
        sinkX = self.getDistance('firstSinkX')
        sinkY = self.getDistance('firstSinkY')
        sourceX = self.getDistance('firstSourceX')
        sourceY = self.getDistance('firstSourceY')
        
        for port in self._ports:
            if port.portType() == 'sink' and (not filter or filter == "sink"):
                sinkCounter += 1
                port.move(sinkX, sinkY)
                sinkY -= self.getDistance('topMargin') + self.getEffectivePortHeight(port)  #+ PortWidget.HEIGHT * self.scale()
                
            elif port.portType() == 'source' and (not filter or filter == "source"):
                sourceCounter += 1
                port.move(sourceX, sourceY)
                sourceY -= self.getDistance('topMargin') + self.getEffectivePortHeight(port) # + PortWidget.HEIGHT * self.scale()

    def drawBody(self, painter):
        """ Takes care of painting widget content on given painter.
        """
        self.drawPortLines(painter)
        self.drawTextField(painter)
        self.drawImage(painter)
        self.drawPortNames(painter)
    
    def drawPortNames(self, painter):
        """ Paints port names next to PortWidget.
        
        See setShowPortNames().
        """
        if not self._showPortNames:
            return
        
        # factor should be 0.5, but text height is calculated to big
        titleHeightFactor = 0.4
        
        if self._portNamesPosition == self.PORT_NAMES_NEXT_TO_PORTS:
            for port in self.sinkPorts():
                if port.titleField():
                    port.titleField().paint(painter, port.x() + self.getDistance('rightMargin') + port.width(), port.y() - titleHeightFactor * port.getDistance('titleFieldHeight'), self.scale())
                
            for port in self.sourcePorts():
                if port.titleField():
                    #logging.debug(self.__class__.__name__ +": drawPortNames() - "+ port.name() +", "+ str(port.titleField()._autoscaleFlag))
                    port.titleField().paint(painter, port.x() - port.getDistance('titleFieldWidth') - self.getDistance('rightMargin'), port.y() - titleHeightFactor * port.getDistance('titleFieldHeight'), self.scale())
        elif self._portNamesPosition == self.PORT_NAMES_ABOVE_PORTS:
            painter.pen().setWidth(2)
            for port in self.sinkPorts():
                if port.titleField():
                    port.titleField().paint(painter, self.getDistance('firstSinkX'), port.y() - titleHeightFactor * port.getDistance('titleFieldHeight') - port.height(), self.scale())
                
            for port in self.sourcePorts():
                if port.titleField():
                    #logging.debug(self.__class__.__name__ +": drawPortNames() - "+ port.name() +", "+ str(port.titleField()._autoscaleFlag))
                    port.titleField().paint(painter, self.width() - port.getDistance('titleFieldWidth')- port.width()*0.5, port.y() - titleHeightFactor * port.getDistance('titleFieldHeight') - port.height(), self.scale())
        else:
            logging.waring(self.__class__.__name__ +": drawPortNames() - "+ self.NO_VALID_PORT_NAMES_POSITION_MESSAGE)

    def drawPortLines(self, painter):
        """ Draws lines from every port to a common point.
        
        See setShowPortLines().
        """
        if not self._showPortLines:
            return
        
        if self.PORT_LINES_TARGET_X == -1 and self.PORT_LINES_TARGET_Y == -1:
            targetPoint = QPoint(self.width(), self.height() + self.getDistance("titleFieldBottom")) * 0.5
        else:
            targetPoint = QPoint(self.PORT_LINES_TARGET_X, self.PORT_LINES_TARGET_Y) * self.scale()
        
        painter.setPen(QPen(QColor('black')))
        painter.pen().setWidth(1)
        
        for port in self.ports():
            painter.drawLine(port.connectionPoint("widget"), targetPoint)
    
    def dropArea(self, port):
        """ A drop area is a QRect in which the ConnectableWidget accepts dropping of PortWidgets to create connections.
        
        The area is greater than the port itself to make dropping easier.
        """
        if self._showPortNames:
            return port.frameGeometry().united(port.titleField().getDrawRect())
        topMargin = self.getDistance("topMargin")
        topMarginHalf = 0.5 * topMargin
        frameGeometry = port.frameGeometry()
        return QRect(frameGeometry.x() - topMarginHalf,
                     frameGeometry.y() - topMarginHalf,
                     frameGeometry.width() + topMargin,
                     frameGeometry.height() + topMargin)
            
    def dropAreaPort(self, position):
        """ If a port's drop area is associated with position the port is returned.
        
        If there is no drop area associated with the position None is returned.
        See dropArea().
        """
        for port in self._ports:
            if self.dropArea(port).contains(position):
                return port
        return None

    def mouseMoveEvent(self, event):
        if bool(event.buttons() & Qt.LeftButton):
            VispaWidget.mouseMoveEvent(self, event)
        elif self._menuWidget:
            self.positionizeMenuWidget()
        
        self.showMenu()
            
    def showMenu(self):            
        if self._menuWidget:
            self._menuWidget.show()
            self._menuWidget.raise_()
            
    def leaveEvent(self, event):
        #logging.debug("%s: leaveEvent()" % self.__class__.__name__)
        parentCursorPos = self.parent().mapFromGlobal(self.cursor().pos())
        bottomRight = self.geometry().bottomRight()
        if ( (not self.isSelected() or (self._menuWidget and self._menuWidget.cursorHasEntered())) \
         and (self._menuWidget and self.parent().childAt(parentCursorPos) != self._menuWidget) ) \
         or (self._menuWidget and ( parentCursorPos.x() > bottomRight.x() or parentCursorPos.y() > bottomRight.y())):
            self._menuWidget.hide()
    
    def menu(self):
        return self._menuWidget    
    
    def addMenuEntry(self, name, slot=None):
        if not self._menuWidget:
            self._menuWidget = MenuWidget(self.parent(), self)
            self.setMouseTracking(True)
        return self._menuWidget.addEntry(name, slot)

    def removeMenuEntry(self, entry):
        if not self._menuWidget:
            return
        self._menuWidget.removeEntry(entry)
        if self._menuWidget.len() == 0:
            self.removeMenu()
    
    def removeMenu(self):
        self._menuWidget.hide()
        self._menuWidget.setParent(None)
        self._menuWidget.deleteLater()
        self._menuWidget = None
        
    def positionizeMenuWidget(self):
        if self._menuWidget:
            headerOffset = 0
            if isinstance(self.parent(), VispaWidget):
                headerOffset = self.parent().getDistance("titleFieldBottom")
            self._menuWidget.move(max(0, self.x() - 0.5* (self._menuWidget.width() - self.width())), 
                                  max(0, headerOffset, self.y() - self._menuWidget.height() +1))
        
    def dragWidget(self, pPos):
        VispaWidget.dragWidget(self, pPos)
        self.positionizeMenuWidget()
        
    def select(self, sel=True, multiSelect=False):
        VispaWidget.select(self, sel, multiSelect)
        if not sel and self._menuWidget:
            self._menuWidget.hide()
    
    def move(self, *target):
        VispaWidget.move(self, *target)
        if self._menuWidget:
            self._menuWidget.hide()
        self.updateAttachedConnections()
            
    def updateAttachedConnections(self):
        for port in self._ports:
            port.updateAttachedConnections()
            
    def attachedConnections(self):
        connections = []
        for port in self._ports:
            connections += port.attachedConnections()
        return connections