def bgc_name_face(node, *args, **kargs):
    """
    This is the item generator. It must receive a node object, and
    returns a Qt4 graphics item that can be used as a node face.
    """
    # Receive an arbitrary number of arguments, in this case width and
    # Height of the faces and the information about the BGC
    width = args[0]
    height = args[1]
    # Add the popup
    interactive_face = InteractiveItem("Class : {}\nRelated MIBiG : {}\nCluster family : {}".format(args[2], args[4], args[3]), 0, 0, width, height)
    # Keep a link within the item to access node info
    interactive_face.node = node
    # Remove border around the masterItem
    interactive_face.setPen(QPen(QtCore.Qt.NoPen))
    # Add ellipse around text
    ellipse = QGraphicsEllipseItem(interactive_face.rect())
    ellipse.setParentItem(interactive_face)
    # Change ellipse color
    ellipse.setBrush(QBrush(QColor(args[6])))
    # Add node name within the ellipse
    text = QGraphicsTextItem(args[5])
    text.setTextWidth(50)
    text.setParentItem(ellipse)
    # Center text according to masterItem size
    text_width = text.boundingRect().width()
    text_height = text.boundingRect().height()
    center = interactive_face.boundingRect().center()
    text.setPos(center.x()-text_width/2, center.y()-text_height/2)
    return interactive_face
def scientific_name_face(node, *args, **kwargs):
    scientific_name_text = QGraphicsTextItem()
    #underscore = node.name.replace("_", " ")
    words = node.name.split("_")
    text = []
    if len(words) < 2:
        # some sort of acronym or bin name, leave it alone
        text = words
    elif len(words) > 2:
        if len(words) >= 5:
            text.extend(
                ['<b>' + words[0] + ' <i> ' + words[1], words[2] + ' </i> '])
            text.extend(words[3:] + ['</b>'])

        elif len(words) == 3:
            text.extend([
                ' <span style="color:grey"><i> ' + words[0],
                words[1] + words[2] + '  </i></span>'
            ])

        else:
            # assume that everything after the
            # second word is strain name
            # which should not get italicized
            text.extend([
                ' <span style="color:grey"><i> ' + words[0],
                words[1] + '  </i></span>'
            ])
            text.extend(words[2:])
    else:
        text.extend([
            ' <span style="color:grey"><i> ' + words[0],
            words[1] + ' </i></span> '
        ])

    scientific_name_text.setHtml(' '.join(text))

    # below is a bit of a hack - I've found that the height of the bounding
    # box gives a bit too much padding around the name, so I just minus 10
    # from the height and recenter it. Don't know whether this is a generally
    # applicable number to use
    #myFont = QFont()
    masterItem = QGraphicsRectItem(
        0, 0,
        scientific_name_text.boundingRect().width(),
        scientific_name_text.boundingRect().height() - 10)

    scientific_name_text.setParentItem(masterItem)
    center = masterItem.boundingRect().center()
    scientific_name_text.setPos(
        masterItem.boundingRect().x(),
        center.y() - scientific_name_text.boundingRect().height() / 2)

    # I don't want a border around the masterItem
    masterItem.setPen(QPen(QtCore.Qt.NoPen))

    return masterItem
Exemple #3
0
 def label(self):
     if self._label:
         return self._label
     label = QGraphicsTextItem("")
     label.setVisible(False)
     label.setFont(self._font)
     label.setParentItem(self)
     label.setTextInteractionFlags(Qt.TextEditorInteraction)
     label.inputMethodEvent = self.inputProcess
     self._label = label
     self._label.hide()
     return label
Exemple #4
0
 def label(self):
     if self._label:
         return self._label
     font = QFont("Times", 30, QFont.Bold)
     label = QGraphicsTextItem("Part 1")
     label.setVisible(False)
     label.setFont(font)
     label.setParentItem(self)
     label.setPos(0, -40)
     label.setTextInteractionFlags(Qt.TextEditorInteraction)
     label.inputMethodEvent = None
     self._label = label
     return label
Exemple #5
0
def scientific_name_face(node, *args, **kwargs):
    scientific_name_text = QGraphicsTextItem()
    words = node.visual_label.split()
    text = []
    if hasattr(node, 'bg_col'):
        container_div = '<div style="background-color:{};">'.format(node.bgcolor)
        text.append(container_div)
    if len(words) < 2:
        # some sort of acronym or bin name, leave it alone
        text.extend(words)

    elif len(words) > 2:
        if words[0] == 'Candidatus':
            # for candidatus names, only the Candidatus part is italicised
            # name shortening it for brevity
            text.append('<i>Ca.</i>')
            text.extend(words[1:])
        elif re.match('^[A-Z]+$', words[0]):
            # If the first word is in all caps then it is an abreviation
            # so we don't want to italicize that at all
            text.extend(words)
        else:
            # assume that everything after the second word is strain name
            # which should not get italicised
            text.extend(['<i>'+words[0],words[1]+'</i>'])
            text.extend(words[2:])
    else:
        text.extend(['<i>'+words[0],words[1]+'</i>'])

    if hasattr(node, 'bg_col'):
        text.append('</div>')
    scientific_name_text.setHtml(' '.join(text))
    #print(scientific_name_text.boundingRect().width(), scientific_name_text.boundingRect().height())

    # below is a bit of a hack - I've found that the height of the bounding
    # box gives a bit too much padding around the name, so I just minus 10
    # from the height and recenter it. Don't know whether this is a generally
    # applicable number to use
    masterItem = QGraphicsRectItem(0, 0, scientific_name_text.boundingRect().width(), scientific_name_text.boundingRect().height() - 10)


    scientific_name_text.setParentItem(masterItem)
    center = masterItem.boundingRect().center()
    scientific_name_text.setPos(masterItem.boundingRect().x(), center.y() - scientific_name_text.boundingRect().height()/2)
    # I dont want a border around the masterItem
    masterItem.setPen(QPen(QtCore.Qt.NoPen))


    return masterItem
Exemple #6
0
def trinomial_face(binom,
                   spp,
                   ftype="Verdana",
                   fsize=10,
                   fgcolor="black",
                   fstyle="normal",
                   bold=False):
    """
    Stolen from:
    https://connorskennerton.wordpress.com/2016/11/16/python-ete3-formatting-organism-names-the-way-i-want/
    """

    font = QFont(ftype, fsize)
    font.setBold(bold)
    if fstyle == "italic":
        font.setStyle(QFont.StyleItalic)
    elif fstyle == "oblique":
        font.setStyle(QFont.StyleOblique)

    text = QGraphicsTextItem()
    text.setFont(font)
    if spp is None:
        text.setHtml("<i>{}</i>".format(binom))
    else:
        text.setHtml("<i>{}</i> {}".format(binom, spp))

    rect = QGraphicsRectItem(0, 0,
                             text.boundingRect().width(),
                             text.boundingRect().height() - 10)
    text.setParentItem(rect)
    center = rect.boundingRect().center()
    text.setPos(rect.boundingRect().x(),
                center.y() - text.boundingRect().height() / 2)

    # no border
    rect.setPen(QPen(QtCore.Qt.NoPen))

    return ete3.faces.StaticItemFace(rect)
class XNodeConnection( QGraphicsPathItem ):
    """ 
    Defines the base graphics item class that is used to draw a connection
    between two nodes.
    """
    def __init__( self, scene ):
        self._visible               = True
        
        super(XNodeConnection, self).__init__()
        
        # define custom properties
        self._textItem              = None
        self._polygons              = []
        self._style                 = XConnectionStyle.Linear
        self._padding               = 20
        self._squashThreshold       = 2 * scene.cellWidth()
        self._showDirectionArrow    = False
        self._highlightPen          = QPen(QColor('yellow'))
        self._disabledPen           = QPen(QColor(100, 100, 100))
        self._disableWithLayer      = False
        self._enabled               = True
        self._dirty                 = True
        self._customData            = {}
        self._layer                 = None
        self._font                  = QApplication.instance().font()
        self._text                  = ''
        
        self._inputNode                     = None
        self._inputFixedY                   = None
        self._inputFixedX                   = None
        self._inputPoint                    = QPointF()
        self._inputLocation                 = XConnectionLocation.Left
        self._autoCalculateInputLocation    = False
        self._showInputArrow                = False
        
        self._outputNode                    = None
        self._outputFixedX                  = None
        self._outputFixedY                  = None
        self._outputPoint                   = QPointF()
        self._outputLocation                = XConnectionLocation.Right
        self._autoCalculateOutputLocation   = False
        self._showOutputArrow               = False
        
        # set standard properties
        self.setFlags( self.ItemIsSelectable )
        self.setZValue(-1)
        self.setPen( QColor('white') )
        self.setLayer( scene.currentLayer() )
        
    def autoCalculateInputLocation( self ):
        """
        :remarks    Returns whether or not to auto calculate the input
                    location based on the proximity to the output node
                    or point.
        
        :return     <bool>
        """
        return self._autoCalculateInputLocation
    
    def autoCalculateOutputLocation( self ):
        """
        :remarks    Returns whether or not to auto calculate the input
                    location based on the proximity to the output node
                    or point.
        
        :return     <bool>
        """
        return self._autoCalculateOutputLocation
    
    def connectSignals( self, node ):
        """
        :remarks    Connects to signals of the inputed node, if the node
                    is a valid XNode type.
        
        :param      node    <XNode> || None
        
        :return     <bool> success
        """
        from projexui.widgets.xnodewidget.xnode import XNode
        
        # make sure we're connecting to a valid node
        if ( not isinstance(node, XNode) ):
            return False
        
        node.dispatch.geometryChanged.connect(  self.setDirty )
        node.dispatch.visibilityChanged.connect(self.setDirty)
        node.dispatch.removed.connect(          self.forceRemove )
        return True
    
    def controlPoints( self ):
        """
        :remarks    Generates the control points for this path
        
        :return     <list> [ <tuple> ( <float> x, <float> y), .. ]
        """
        
        # calculate the positions
        outputPoint  = self.outputPoint()
        inputPoint   = self.inputPoint()
        
        points = []
        
        x0 = outputPoint.x()
        y0 = outputPoint.y()
        
        xN = inputPoint.x()
        yN = inputPoint.y()
        
        xC = (x0 + xN) / 2.0
        yC = (y0 + yN) / 2.0
        
        points.append((x0, y0))
        
        oloc    = self.outputLocation()
        iloc    = self.inputLocation()
        left    = XConnectionLocation.Left
        right   = XConnectionLocation.Right
        bot     = XConnectionLocation.Bottom
        top     = XConnectionLocation.Top
        
        # create a right-to-left
        if ( (oloc & right) and (iloc & left) ):
            if ( xN < (x0 + self.squashThreshold()) ):
                points.append((x0+self.padding(), y0))
                points.append((x0+self.padding(), yC))
                points.append((xN-self.padding(), yC))
                points.append((xN-self.padding(), yN))
            else:
                points.append((xC, y0))
                points.append((xC, yN))
        
        # create a left-to-right
        elif ( (oloc & left) and (iloc & right) ):
            if ( (x0 - self.squashThreshold()) < xN ):
                points.append((x0-self.padding(), y0))
                points.append((x0-self.padding(), yC))
                points.append((xN+self.padding(), yC))
                points.append((xN+self.padding(), yN))
            else:
                points.append((xC, y0))
                points.append((xC, yN))
        
        # create a bottom-to-top
        elif ( (oloc & bot) and (iloc & top) ):
            if ( yN < (y0 + self.squashThreshold()) ):
                points.append((x0, y0+self.padding()))
                points.append((xC, y0+self.padding()))
                points.append((xC, yN-self.padding()))
                points.append((xN, yN-self.padding()))
            else:
                points.append((x0, yC))
                points.append((xN, yC))
        
        # create a top-to-bottom
        elif ( (oloc & top) and (iloc & bot) ):
            if ( (y0 - self.squashThreshold()) < yN ):
                points.append((x0, y0-self.padding()))
                points.append((xC, y0-self.padding()))
                points.append((xC, yN+self.padding()))
                points.append((xN, yN+self.padding()))
            else:
                points.append((x0, yC))
                points.append((xN, yC))
        
        # create a left-to-left
        elif ( (oloc & left) and (iloc & left) ):
            xMin = min(x0-self.padding(), xN-self.padding())
            points.append((xMin, y0))
            points.append((xMin, yN))
        
        # create a right-to-right
        elif ( (oloc & right) and (iloc & right) ):
            xMax = max(x0+self.padding(), xN+self.padding())
            points.append((xMax, y0))
            points.append((xMax, yN))
        
        # create a bottom-to-bottom
        elif ( (oloc & top) and (iloc & top) ):
            yMin = min(y0-self.padding(), yN-self.padding())
            points.append((x0, yMin))
            points.append((xN, yMin))
        
        # create a bottom-to-bottom
        elif ( (oloc & bot) and (iloc & bot) ):
            yMax = max(y0+self.padding(), yN+self.padding())
            points.append((x0, yMax))
            points.append((xN, yMax))
        
        # create a bottom-to-left or left-to-bottom
        elif (  ((oloc & bot) and (iloc & left)) or 
                ((oloc & left) and (iloc & bot)) ):
            points.append((x0, yN))
        
        # create a bottom-to-right or right-to-bottom
        elif (  ((oloc & bot) and (iloc & right)) or 
                ((oloc & right) and (iloc & bot)) ):
            points.append((x0, y0))
        
        # create a top-to-left or left-to-top
        elif (  ((oloc & top) and (iloc & left)) or 
                ((oloc & left) and (iloc & top)) ):
            points.append((xN, yN))
            
        # create a top-to-right or right-to-top
        elif (  ((oloc & top) and (iloc & right)) or 
                ((oloc & right) and (iloc & top)) ):
            points.append((xN, y0))
        
        points.append((xN, yN))
        
        return points
    
    def customData( self, key, default = None ):
        """
        Returns custom defined data that can be tracked per connection.
        
        :param      key         <str>
        :param      default     <variant>
        
        :return     <variant>
        """
        return self._customData.get(str(key), default)
    
    def direction( self ):
        """
        Returns the output-to-input direction as a tuple of the output \
        and input locations.
        
        :return     (<XConnectionLocation> output, <XConnectionLocation> input)
        """
        return (self.outputLocation(), self.inputLocation())
    
    def disabledPen( self ):
        """
        Returns the pen that should be used when rendering a disabled \
        connection.
        
        :return     <QPen>
        """
        return self._disabledPen
    
    def disableWithLayer( self ):
        """
        Returns whether or not this connection's enabled state should be \
        affected by its layer.
        
        :return     <bool>
        """
        return self._disableWithLayer
    
    def disconnectSignals( self, node ):
        """
        Disconnects from signals of the inputed node, if the node is a \
        valid XNode type.
        
        :param      node    <XNode> || None
        
        :return     <bool> success
        """
        from projexui.widgets.xnodewidget.xnode import XNode
        
        # make sure we're disconnecting from a valid node
        if ( not isinstance(node, XNode) ):
            return False
        
        node.dispatch.geometryChanged.disconnect(   self.setDirty )
        node.dispatch.removed.disconnect(           self.forceRemove )
        return True
    
    def forceRemove( self ):
        """
        Removes the object from the scene by queuing it up for removal.
        """
        scene = self.scene()
        if ( scene ):
            scene.forceRemove(self)
    
    def font( self ):
        """
        Returns the font for this connection.
        
        :return     <QFont>
        """
        return self._font
    
    def hasCustomData( self, key ):
        """
        Returns whether or not there is the given key in the custom data.
        
        :param      key | <str>
        
        :return     <bool>
        """
        return str(key) in self._customData
    
    def highlightPen( self ):
        """
        Return the highlight pen for this connection.
        
        :return     <QPen>
        """
        return self._highlightPen
    
    def inputLocation( self ):
        """
        Returns the input location for this connection.
        
        :return     <XConnectionLocation>
        """
        if ( not self.autoCalculateInputLocation() ):
            return self._inputLocation
        
        # auto calculate directions based on the scene
        if ( self._outputNode ):
            outputRect = self._outputNode.sceneRect()
        else:
            y = self._outputPoint.y()
            outputRect = QRectF( self._outputPoint.x(), y, 0, 0 )
        
        if ( self._inputNode ):
            inputRect  = self._inputNode.sceneRect()
        else:
            y = self._inputPoint.y()
            inputRect  = QRectF( self._inputPoint.x(), y, 0, 0 )
        
        # use the input location as potential places where it can be
        iloc    = self._inputLocation
        left    = XConnectionLocation.Left
        right   = XConnectionLocation.Right
        top     = XConnectionLocation.Top
        bot     = XConnectionLocation.Bottom
        
        if ( self._inputNode == self._outputNode ):
            if ( iloc & right ):
                return right
            elif ( iloc & left ):
                return left
            elif ( iloc & top ):
                return top
            else:
                return bot
                
        elif ( (iloc & left)    and outputRect.right() < inputRect.left() ):
            return left
        elif ( (iloc & right)   and inputRect.right() < outputRect.left() ):
            return right
        elif ( (iloc & top)     and outputRect.bottom() < inputRect.top() ):
            return top
        elif ( (iloc & bot) ):
            return bot
        elif ( (iloc & left) ):
            return left
        elif ( (iloc & right) ):
            return right
        elif ( (iloc & top) ):
            return top
        else:
            return left
    
    def inputNode( self ):
        """
        Returns the input node that is connected to this connection.
        
        :return     <XNode>
        """
        return self._inputNode
    
    def inputFixedX( self ):
        """
        Returns the fixed X value for the input option
        
        :return     <float> || None
        """
        return self._inputFixedX
    
    def inputFixedY( self ):
        """
        Returns the fixed Y value for the input option.
        
        :return     <float> || None
        """
        return self._inputFixedY
    
    def inputPoint( self ):
        """
        Returns a scene space point that the connection \
        will draw to as its input target.  If the connection \
        has a node defined, then it will calculate the input \
        point based on the position of the node, factoring in \
        preference for input location and fixed information. \
        If there is no node connected, then the point defined \
        using the setInputPoint method will be used.
        
        :return     <QPointF>
        """
        node = self.inputNode()
        
        # return the set point
        if ( not node ):
            return self._inputPoint
        
        # otherwise, calculate the point based on location and fixed info
        ilocation   = self.inputLocation()
        ifixedx     = self.inputFixedX()
        ifixedy     = self.inputFixedY()
        return node.positionAt( ilocation, ifixedx, ifixedy )
    
    def isDirection( self, outputLocation, inputLocation ):
        """
        Checks to see if the output and input locations match the settings \
        for this item.
        
        :param      outputLocation      | <XConnectionLocation>
        :param      inputLocation       | <XConnectionLocation>
        
        :return     <bool>
        """
        return (self.isOutputLocation(outputLocation) and 
               self.isInputLocation(inputLocation))
    
    def isDirty( self ):
        """
        Returns whether or not this path object is dirty and needs to \
        be rebuilt.
        
        :return     <bool>
        """
        return self._dirty
    
    def isEnabled( self ):
        """
        Returns whether or not this connection is enabled.
        
        :sa     disableWithLayer
        
        :return     <bool>
        """
        if self._disableWithLayer and self._layer:
            lenabled = self._layer.isEnabled()
        elif self._inputNode and self._outputNode:
            lenabled = self._inputNode.isEnabled() and self._outputNode.isEnabled()
        else:
            lenabled = True
        
        return self._enabled and lenabled
    
    def isInputLocation( self, location ):
        """
        Returns whether or not the inputed location value matches the \
        given input location.
        
        :param      location    | <XConnectionLocation>
        
        :return     <bool>
        """
        return (self.inputLocation() & location) != 0
    
    def isOutputLocation( self, location ):
        """
        Returns whether or not the inputed location value matches the \
        given output location.
        
        :param      location    | <XConnectionLocation>
        
        :return     <bool>
        """
        return (self.outputLocation() & location) != 0
    
    def isStyle( self, style ):
        """
        Return whether or not the connection is set to a particular style.
        
        :param      style       | <XConnectionStyle>
        
        :return     <bool>
        """
        return (self._style & style) != 0
    
    def isVisible( self ):
        """
        Returns whether or not this connection is visible.  If either node it is
        connected to is hidden, then it should be as well.
        
        :return     <bool>
        """
        in_node  = self.inputNode()
        out_node = self.outputNode()
        
        if ( in_node and not in_node.isVisible() ):
            return False
        
        if ( out_node and not out_node.isVisible() ):
            return False
        
        return self._visible
    
    def layer( self ):
        """
        Returns the layer that this node is assigned to.
        
        :return     <XNodeLayer> || None
        """
        return self._layer
    
    def mappedPolygon( self, polygon, path = None, percent = 0.5 ):
        """
        Maps the inputed polygon to the inputed path \
        used when drawing items along the path.  If no \
        specific path is supplied, then this object's own \
        path will be used.  It will rotate and move the \
        polygon according to the inputed percentage.
        
        :param      polygon     <QPolygonF>
        :param      path        <QPainterPath>
        :param      percent     <float>
        
        :return     <QPolygonF> mapped_poly
        """
        translatePerc   = percent
        anglePerc       = percent
        
        # we don't want to allow the angle percentage greater than 0.85
        # or less than 0.05 or we won't get a good rotation angle
        if ( 0.95 <= anglePerc ):
            anglePerc = 0.98
        elif ( anglePerc <= 0.05 ):
            anglePerc = 0.05
        
        if ( not path ):
            path = self.path()
        if ( not (path and path.length()) ):
            return QPolygonF()
        
        # transform the polygon to the path
        point   = path.pointAtPercent(translatePerc)
        angle   = path.angleAtPercent(anglePerc)
        
        # rotate about the 0 axis
        transform   = QTransform().rotate(-angle)
        polygon     = transform.map(polygon)
        
        # move to the translation point
        transform   = QTransform().translate(point.x(), point.y())
        
        # create the rotated polygon
        mapped_poly = transform.map(polygon)
        self._polygons.append(mapped_poly)
        
        return mapped_poly
    
    def mousePressEvent( self, event ):
        """
        Overloads the mouse press event to handle special cases and \
        bypass when the scene is in view mode.
        
        :param      event   <QMousePressEvent>
        """
        # ignore events when the scene is in view mode
        scene = self.scene()
        if ( scene and scene.inViewMode() ):
            event.ignore()
            return
        
        # block the selection signals
        if ( scene ):
            scene.blockSelectionSignals(True)
            
            # clear the selection
            if ( not (self.isSelected() or 
                      event.modifiers() == Qt.ControlModifier) ):
                for item in scene.selectedItems():
                    if ( item != self ):
                        item.setSelected(False)
        
        # try to start the connection
        super(XNodeConnection, self).mousePressEvent(event)
        
    def mouseMoveEvent( self, event ):
        """
        Overloads the mouse move event to ignore the event when \
        the scene is in view mode.
        
        :param      event   <QMouseMoveEvent>
        """
        # ignore events when the scene is in view mode
        scene = self.scene()
        if ( scene and (scene.inViewMode() or scene.isConnecting()) ):
            event.ignore()
            return
        
        # call the base method
        super(XNodeConnection, self).mouseMoveEvent(event)
    
    def mouseReleaseEvent( self, event ):
        """
        Overloads the mouse release event to ignore the event when the \
        scene is in view mode, and release the selection block signal.
         
         :param     event   <QMouseReleaseEvent>
        """
        # ignore events when the scene is in view mode
        scene = self.scene()
        if ( scene and (scene.inViewMode() or scene.isConnecting()) ):
            event.ignore()
            return
        
        # emit the scene's connection menu requested signal if
        # the button was a right mouse button
        if ( event.button() == Qt.RightButton and scene ):
            scene.emitConnectionMenuRequested(self)
            event.accept()
        else:
            super(XNodeConnection, self).mouseReleaseEvent(event)
        
        # unblock the selection signals
        if ( scene ):
            scene.blockSelectionSignals(False)
    
    def opacity( self ):
        """
        Returns the opacity amount for this connection.
        
        :return     <float>
        """
        in_node     = self.inputNode()
        out_node    = self.outputNode()
        
        if ( in_node and out_node and \
             (in_node.isIsolateHidden() or out_node.isIsolateHidden()) ):
            return 0.1
        
        opacity = super(XNodeConnection, self).opacity()
        layer = self.layer()
        if ( layer ):
            return layer.opacity() * opacity
        
        return opacity
    
    def outputLocation( self ):
        """
        Returns the location for the output source position.
        
        :return     <XConnectionLocation>
        """
        if ( not self.autoCalculateOutputLocation() ):
            return self._outputLocation
        
        # auto calculate directions based on the scene
        if ( self._outputNode ):
            outputRect = self._outputNode.sceneRect()
        else:
            y = self._outputPoint.y()
            outputRect = QRectF( self._outputPoint.x(), y, 0, 0 )
        
        if ( self._inputNode ):
            inputRect  = self._inputNode.sceneRect()
        else:
            y = self._inputPoint.y()
            inputRect  = QRectF( self._inputPoint.x(), y, 0, 0 )
            
        oloc    = self._outputLocation
        left    = XConnectionLocation.Left
        right   = XConnectionLocation.Right
        top     = XConnectionLocation.Top
        bot     = XConnectionLocation.Bottom
        
        if ( self._inputNode == self._outputNode ):
            if ( oloc & right ):
                return right
            elif ( oloc & left ):
                return left
            elif ( oloc & top ):
                return top
            else:
                return bot
                
        elif ( (oloc & right)   and outputRect.right() < inputRect.left() ):
            return right
        elif ( (oloc & left)    and inputRect.right() < outputRect.left() ):
            return left
        elif ( (oloc & bot)     and outputRect.bottom() < inputRect.top() ):
            return bot
        elif ( (oloc & top) ):
            return top
        elif ( (oloc & right) ):
            return right
        elif ( (oloc & left) ):
            return left
        elif ( (oloc & bot) ):
            return bot
        else:
            return right
    
    def outputNode( self ):
        """
        Returns the output source node that this connection is currently \
        connected to.
        
        :return     <XNode> || None
        """
        return self._outputNode
    
    def outputFixedX( self ):
        """
        Returns the fixed X position for the output component of this \
        connection.
                    
        :return     <float> || None
        """
        return self._outputFixedX
    
    def outputFixedY( self ):
        """
        Returns the fixed Y position for the output component of this \
        connection.
                    
        :return     <float> || None
        """
        return self._outputFixedY
    
    def outputPoint( self ):
        """
        Returns a scene space point that the connection \
        will draw to as its output source.  If the connection \
        has a node defined, then it will calculate the output \
        point based on the position of the node, factoring in \
        preference for output location and fixed positions.  If \ 
        there is no node connected, then the point defined using \
        the setOutputPoint method will be used.
        
        :return     <QPointF>
        """
        node = self.outputNode()
        
        # return the set point
        if ( not node ):
            return self._outputPoint
        
        # otherwise, calculate the point based on location and fixed positions
        olocation   = self.outputLocation()
        ofixedx     = self.outputFixedX()
        ofixedy     = self.outputFixedY()
        return node.positionAt( olocation, ofixedx, ofixedy )
    
    def padding( self ):
        """
        Returns the amount of padding to be used when drawing a connection \
        that will be drawn backwards.
        
        :return     <float>
        """
        return self._padding
    
    def paint( self, painter, option, widget ):
        """
        Overloads the paint method from QGraphicsPathItem to \
        handle custom drawing of the path using this items \
        pens and polygons.
        
        :param      painter     <QPainter>
        :param      option      <QGraphicsItemStyleOption>
        :param      widget      <QWidget>
        """
        # following the arguments required by Qt
        # pylint: disable-msg=W0613
        
        painter.setOpacity(self.opacity())
        
        # show the connection selected
        if ( not self.isEnabled() ):
            pen = QPen(self.disabledPen())
        elif ( self.isSelected() ):
            pen = QPen(self.highlightPen())
        else:
            pen = QPen(self.pen())
        
        if ( self._textItem ):
            self._textItem.setOpacity(self.opacity())
            self._textItem.setDefaultTextColor(pen.color().darker(110))
        
        # rebuild first if necessary
        if ( self.isDirty() ):
            self.setPath(self.rebuild())
        
        # store the initial hint
        hint = painter.renderHints()
        painter.setRenderHint( painter.Antialiasing )
        
        pen.setWidthF(1.25)
        painter.setPen(pen)
        painter.drawPath(self.path())
        
        # redraw the polys to force-fill them
        for poly in self._polygons:
            if ( not poly.isClosed() ):
                continue
            
            painter.setBrush(pen.color())
            painter.drawPolygon(poly)
        
        # restore the render hints
        painter.setRenderHints(hint)
    
    def prepareToRemove( self ):
        """
        Handles any code that needs to run to cleanup the connection \
        before it gets removed from the scene.
        
        :return     <bool> success
        """
        # disconnect the signals from the input and output nodes
        for node in (self._outputNode, self._inputNode):
            self.disconnectSignals(node)
        
        # clear the pointers to the nodes
        self._inputNode     = None
        self._outputNode    = None
        
        return True
    
    def rebuild( self ):
        """
        Rebuilds the path for this connection based on the given connection \
        style parameters that have been set.
        
        :return     <QPainterPath>
        """
        # create the path
        path            = self.rebuildPath()
        self._polygons  = self.rebuildPolygons(path)
        
        if ( self._textItem ):
            point   = path.pointAtPercent(0.5)
            metrics = QFontMetrics(self._textItem.font())
            
            point.setY(point.y() - metrics.height() / 2.0)
            
            self._textItem.setPos(point)
        
        # create the path for the item
        for poly in self._polygons:
            path.addPolygon(poly)
        
        # unmark as dirty
        self.setDirty(False)
        
        return path
    
    def rebuildPath( self ):
        """
        Rebuilds the path for the given style options based on the currently \
        set parameters.
        
        :return     <QPainterPath>
        """
        # rebuild linear style
        if ( self.isStyle( XConnectionStyle.Linear ) ):
            return self.rebuildLinear()
        
        # rebuild block style
        elif ( self.isStyle( XConnectionStyle.Block ) ):
            return self.rebuildBlock()
        
        # rebuild smooth style
        elif ( self.isStyle( XConnectionStyle.Smooth ) ):
            return self.rebuildSmooth()
        
        # otherwise, we have an invalid style, or a style
        # defined by a subclass
        else:
            return QPainterPath()
    
    def rebuildPolygons( self, path ):
        """
        Rebuilds the polygons that will be used on this path.
        
        :param      path    | <QPainterPath>
        
        :return     <list> [ <QPolygonF>, .. ]
        """
        output = []
        
        # create the input arrow
        if ( self.showInputArrow() ):
            a = QPointF(-10, -3)
            b = QPointF(0, 0)
            c = QPointF(-10, 3)
            
            mpoly = self.mappedPolygon(QPolygonF([a, b, c, a]), path, 1.0 )
            output.append( mpoly )
        
        # create the direction arrow
        if ( self.showDirectionArrow() ):
            a = QPointF(-5, -3)
            b = QPointF(5, 0)
            c = QPointF(-5, 3)
            
            mpoly = self.mappedPolygon(QPolygonF([a, b, c, a]), path, 0.5 )
            output.append( mpoly )
        
        # create the output arrow
        if ( self.showOutputArrow() ):
            a = QPointF(10, -3)
            b = QPointF(0, 0)
            c = QPointF(10, 3)
            
            mpoly = self.mappedPolygon(QPolygonF([a, b, c, a]), path, 0 )
            output.append( mpoly )
        
        return output
        
    def rebuildLinear( self ):
        """ 
        Rebuilds a linear path from the output to input points.
        
        :return     <QPainterPath>
        """
        
        points = self.controlPoints()
        
        x0, y0 = points[0]
        xN, yN = points[-1]
        
        # create a simple line between the output and
        # input points
        path = QPainterPath()
        path.moveTo(x0, y0)
        path.lineTo(xN, yN)
        
        return path
    
    def rebuildBlock( self ):
        """
        Rebuilds a blocked path from the output to input points.
        
        :return     <QPainterPath>
        """
        # collect the control points
        points = self.controlPoints()
        
        # create the path
        path = QPainterPath()
        for i, point in enumerate(points):
            if ( not i ):
                path.moveTo( point[0], point[1] )
            else:
                path.lineTo( point[0], point[1] )
        
        return path
    
    def rebuildSmooth( self ):
        """
        Rebuilds a smooth path based on the inputed points and set \
        parameters for this item.
        
        :return     <QPainterPath>
        """
        # collect the control points
        points = self.controlPoints()
        
        # create the path
        path = QPainterPath()
        
        if ( len(points) == 3 ):
            x0, y0 = points[0]
            x1, y1 = points[1]
            xN, yN = points[2]
            
            path.moveTo(x0, y0)
            path.quadTo(x1, y1, xN, yN)
        
        elif ( len(points) == 4 ):
            x0, y0 = points[0]
            x1, y1 = points[1]
            x2, y2 = points[2]
            xN, yN = points[3]
            
            path.moveTo(x0, y0)
            path.cubicTo(x1, y1, x2, y2, xN, yN)
            
        elif ( len(points) == 6 ):
            x0, y0 = points[0]
            x1, y1 = points[1]
            x2, y2 = points[2]
            x3, y3 = points[3]
            x4, y4 = points[4]
            xN, yN = points[5]
            
            xC      = (x2+x3) / 2.0
            yC      = (y2+y3) / 2.0
            
            path.moveTo(x0, y0)
            path.cubicTo(x1, y1, x2, y2, xC, yC)
            path.cubicTo(x3, y3, x4, y4, xN, yN)
            
        else:
            x0, y0 = points[0]
            xN, yN = points[-1]
            
            path.moveTo(x0, y0)
            path.lineTo(xN, yN)
        
        return path
    
    def refreshVisible( self ):
        """
        Refreshes whether or not this node should be visible based on its
        current visible state.
        """
        super(XNodeConnection, self).setVisible(self.isVisible())
    
    def setAutoCalculateInputLocation( self, state = True ):
        """
        Sets whether or not to auto calculate the input location based on \
        the proximity to the output node or point.
        
        :param     state       | <bool>
        """
        self._autoCalculateInputLocation = state
        self.setDirty()
    
    def setAutoCalculateOutputLocation( self, state = True ):
        """
        Sets whether or not to auto calculate the input location based on \
        the proximity to the output node or point.
        
        :param     state       | <bool>
        """
        self._autoCalculateOutputLocation = state
        self.setDirty()
    
    def setCustomData( self, key, value ):
        """
        Stores the inputed value as custom data on this connection for \
        the given key.
        
        :param      key     | <str>
        :param      value   | <variant>
        """
        self._customData[str(key)] = value
    
    def setDirection( self, outputLocation, inputLocation ):
        """
        Sets the output-to-input direction by setting both the locations \
        at the same time.
        
        :param      outputLocation      | <XConnectionLocation>
        :param      inputLocation       | <XConnectionLocation>
        """
        self.setOutputLocation(outputLocation)
        self.setInputLocation(inputLocation)
    
    def setDirty( self, state = True ):
        """
        Flags the connection as being dirty and needing a rebuild.
        
        :param      state   | <bool>
        """
        self._dirty = state
        
        # set if this connection should be visible
        if ( self._inputNode and self._outputNode ):
            vis = self._inputNode.isVisible() and self._outputNode.isVisible()
            self.setVisible(vis)
    
    def setDisabledPen( self, pen ):
        """
        Sets the disabled pen that will be used when rendering a connection \
        in a disabled state.
        
        :param      pen | <QPen>
        """
        self._disabledPen = QPen(pen)
    
    def setDisableWithLayer( self, state ):
        """
        Sets whether or not this connection's layer's current state should \
        affect its enabled state.
        
        :param      state | <bool>
        """
        self._disableWithLayer = state
        self.setDirty()
    
    def setEnabled( self, state ):
        """
        Sets whether or not this connection is enabled or not.
        
        :param      state | <bool>
        """
        self._enabled = state
    
    def setFont( self, font ):
        """
        Sets the font for this connection to the inputed font.
        
        :param      font | <QFont>
        """
        self._font = font
    
    def setHighlightPen( self, pen ):
        """
        Sets the pen to be used when highlighting a selected connection.
        
        :param      pen     | <QPen> || <QColor>
        """
        self._highlightPen = QPen(pen)
    
    def setInputLocation( self, location ):
        """
        Sets the input location for where this connection should point to.
        
        :param      location       | <XConnectionLocation>
        """
        self._inputLocation = location
        self.setDirty()
    
    def setInputNode( self, node ):
        """
        Sets the node that will be recieving this connection as an input.
        
        :param      node    | <XNode>
        """
        # if the node already matches the current input node, ignore
        if ( self._inputNode == node ):
            return
        
        # disconnect from the existing node
        self.disconnectSignals( self._inputNode )
        
        # store the node
        self._inputNode = node
        
        # connect to the new node
        self.connectSignals( self._inputNode )
        
        # force the rebuilding of the path
        self.setPath(self.rebuild())
    
    def setInputFixedX( self, x ):
        """
        Sets the fixed x position for the input component of this connection.
        
        :param      x       | <float> || None
        """
        self._inputFixedX = x
        self.setDirty()
    
    def setInputFixedY( self, y ):
        """
        Sets the fixed y position for the input component of this connection.
        
        :param      y       | <float> || None
        """
        self._inputFixedY = y
        self.setDirty()
        
    def setInputPoint( self, point ):
        """
        Sets the scene level input point position to draw the connection to. \
        This is used mainly by the scene when drawing a user connection - \
        it will only be used when there is no connected input node.
        
        :param      point       | <QPointF>
        """
        self._inputPoint = point
        self.setPath(self.rebuild())
    
    def setLayer( self, layer ):
        """
        Sets the layer that this node is associated with to the given layer.
        
        :param      layer       | <XNodeLayer> || None
        
        :return     <bool> changed
        """
        if ( layer == self._layer ):
            return False
        
        self._layer = layer
        self.syncLayerData()
        
        return True
    
    def setOutputLocation( self, location ):
        """
        Sets the location for the output part of the connection to generate \
        from.
        
        :param      location      | <XConnectionLocation>
        """
        self._outputLocation = location
        self.setDirty()
    
    def setOutputNode( self, node ):
        """
        Sets the node that will be generating the output information for \
        this connection.
        
        :param      node         | <XNode>
        """
        # if the output node matches the current, ignore
        if ( node == self._outputNode ):
            return
        
        # disconnect from an existing node
        self.disconnectSignals( self._outputNode )
        
        # set the current node
        self._outputNode = node
        self.connectSignals( self._outputNode )
        
        # force the rebuilding of the path
        self.setPath( self.rebuild() )
    
    def setOutputFixedX( self, x ):
        """
        Sets the fixed x position for the output component of this connection.
        
        :param      x       | <float> || None
        """
        self._outputFixedX = x
        self.setDirty()
    
    def setOutputFixedY( self, y ):
        """
        Sets the fixed y position for the output component of this connection.
        
        :param      y       | <float> || None
        """
        self._outputFixedY = y
        self.setDirty()
        
    def setOutputPoint( self, point ):
        """
        Sets the scene space point for where this connection should draw \
        its output from.  This value will only be used if no output \
        node is defined.
        
        :param      point      | <QPointF>
        """
        self._outputPoint = point
        self.setPath( self.rebuild() )
    
    def setPadding( self, padding ):
        """
        Sets the padding amount that will be used when drawing a connection \
        whose points will overlap.
        
        :param      padding    | <float>
        """
        self._padding = padding
        self.setDirty()
    
    def setShowDirectionArrow( self, state = True ):
        """
        Marks whether or not an arrow in the center of the path should be \
        drawn, showing the direction that the connection is flowing in.
        
        :param      state      | <bool>
        """
        self._showDirectionArrow = state
        self.setDirty()
    
    def setShowInputArrow( self, state = True ):
        """
        :remarks    Marks whether or not an arrow should be shown pointing
                    at the input node.
        
        :param      state       <bool>
        """
        self._showInputArrow = state
        self.setDirty()
    
    def setShowOutputArrow( self, state = True ):
        """
        :remarks    Marks whether or not an arrow should be shown pointing at
                    the output node.
        
        :param      state       <bool>
        """
        self._showOutputArrow = state
        self.setDirty()
    
    def setSquashThreshold( self, amount ):
        """
        :remarks    Sets the threshold limit of when the connection should
                    start 'squashing', calculated based on the distance between
                    the input and output points when rebuilding.
        
        :param      amount      <float>
        """
        self._squashThreshold = amount
        self.setDirty()
    
    def setStyle( self, style ):
        """
        :remarks    Sets the style of the connection that will be used.
        
        :param      style       <XConnectionStyle>
        """
        self._style = style
        self.setDirty()
        self.update()
    
    def setVisible( self, state ):
        """
        Sets whether or not this connection's local visibility should be on.
        
        :param      state | ,bool>
        """
        self._visible = state
        
        super(XNodeConnection, self).setVisible(self.isVisible())
    
    def setZValue( self, value ):
        """
        Sets the z value for this connection, also updating the text item to 
        match the value if one is defined.
        
        :param      value | <int>
        """
        super(XNodeConnection, self).setZValue(value)
        
        if ( self._textItem ):
            self._textItem.setZValue(value)
    
    def showDirectionArrow( self ):
        """
        :remarks    Return whether or not the direction arrow is visible
                    for this connection.
        
        :return     <bool>
        """
        return self._showDirectionArrow
    
    def showInputArrow( self ):
        """
        :remarks    Return whether or not the input arrow is visible
                    for this connection.
        
        :return     <bool>
        """
        return self._showInputArrow
    
    def showOutputArrow( self ):
        """
        :remarks    Return whether or not the output arrow is visible
                    for this connection.
        
        :return     <bool>
        """
        return self._showOutputArrow
    
    def squashThreshold( self ):
        """
        :remarks    Returns the sqash threshold for when the line
                    should be squashed based on the input and output
                    points becoming too close together.
        
        :return     <float>
        """
        return self._squashThreshold
    
    def setText( self, text ):
        """
        Sets the text for this connection to the inputed text.
        
        :param      text | <str>
        """
        self._text = text
        
        if ( text ):
            if ( self._textItem is None ):
                self._textItem = QGraphicsTextItem()
                self._textItem.setParentItem(self)
            
            self._textItem.setPlainText(text)
            
        elif ( self._textItem ):
            self.scene().removeItem(self._textItem)
            self._textItem = None
    
    def style( self ):
        """
        :remarks    Returns the style of the connection that is being drawn.
        
        :return     style       <XConnectionStyle>
        """
        return self._style
    
    def syncLayerData( self, layerData = None ):
        """
        Syncs the layer information for this item from the given layer data.
        
        :param      layerData | <dict>
        """
        if ( not self._layer ):
            return
        
        if ( not layerData ):
            layerData = self._layer.layerData()
        
        self.setVisible( layerData.get('visible', True) )
        
        if ( layerData.get('current') ):
            # set the default parameters
            self.setFlags( self.ItemIsSelectable )
            self.setAcceptHoverEvents(True)
            self.setZValue(99)
            
        else:
            # set the default parameters
            self.setFlags(  self.GraphicsItemFlags(0) )
            self.setAcceptHoverEvents(True)
            self.setZValue(layerData.get('zValue', 0)-1)
    
    def text( self ):
        """
        Returns the text for this connection.
        
        :return     <str>
        """
        return self._text
class TaskGraphicsItem (QGraphicsEllipseItem):

    def __init__(self, rect=None):
        super(TaskGraphicsItem, self).__init__()

        if rect is not None:
            self.setRect(rect)

        self.setPen(QPen(Qt.NoPen))

        # Setup the text item
        self.textItem = QGraphicsTextItem()
        self.textItem.setParentItem(self)
        self.textItem.rotate(-90)
        self.textItem.setDefaultTextColor(QColor(255, 255, 255))

        # The dimensions to reach via a LERP.
        self.startPos = QPointF(0, 0)
        self.endPos = QPointF(0, 0)
        self.startDiameter = 1
        self.endDiameter = 1

        self.centerMark = QGraphicsEllipseItem()
        self.centerMark.setBrush(QBrush(Qt.white))
        self.centerMark.setPen(QPen(Qt.NoPen))
        self.centerMark.setParentItem(self)

        self.pid = -1

        # To determine if it is associated with an active process.
        self.used = False

    def mousePressEvent(self, event):
        print "Clicked On Ellipse at: ", self.rect().topLeft()

    def set_pid(self, pid):
        self.pid = pid

    def set_name(self, str_name):
        self.textItem.setPlainText(str_name)

    def update_name_pos(self):

        rect = self.boundingRect()
        text_rect = self.textItem.boundingRect()

        # Center text (on the x-axis) and offset (on the y-axis) so it doesn't overlap the ellipse item.
        x_text = rect.x() + rect.width()/2 - text_rect.height()/2
        y_text = rect.y() + 100 + text_rect.width() + rect.height()

        self.textItem.setPos(x_text, y_text)

    # Time step is in seconds.
    def update(self, time_step):

        diameter = self.rect().width() + self.lerp_rate(self.startDiameter, self.endDiameter, time_step)
        if diameter <= 1:
            diameter = 1

        pos = self.rect().topLeft()

        x = pos.x() + self.lerp_rate(self.startPos.x(), self.endPos.x(), time_step)
        y = pos.y() + self.lerp_rate(self.startPos.y(), self.endPos.y(), time_step)

        self.setRect(QRectF(x, y, diameter, diameter))
        self.update_name_pos()
        self.update_center_mark()

    def update_center_mark(self):
        scale = self.scene().views()[0].currentScale

        hwidth = self.rect().width() / 2.0
        diam = 2.0 / scale

        # Only mark center for large enough items.
        if hwidth * 0.2 > diam:

            self.centerMark.setVisible(True)

            hdiam = diam / 2.0
            pos = self.rect().topLeft()

            x = pos.x() - hdiam + hwidth
            y = pos.y() - hdiam + hwidth
            self.centerMark.setRect(QRectF(x, y, diam, diam))

        else:
            self.centerMark.setVisible(False)

    # Return the linear interpolation rate. Reach start to end at a rate of 'growth rate'
    @staticmethod
    def lerp_rate(start, end, time_step):
        return (end - start) * time_step