1616 QGraphicsView , QGraphicsWidget , QGraphicsRectItem ,
1717 QGraphicsLineItem , QGraphicsTextItem , QGraphicsLayoutItem ,
1818 QGraphicsLinearLayout , QGraphicsGridLayout , QGraphicsPixmapItem ,
19- QGraphicsDropShadowEffect , QSizePolicy
19+ QGraphicsDropShadowEffect , QSizePolicy , QGraphicsItem
20+ )
21+ from AnyQt .QtGui import (
22+ QPalette , QPen , QPainter , QIcon , QColor , QPainterPathStroker
2023)
21- from AnyQt .QtGui import QPalette , QPen , QPainter , QIcon
22-
2324from AnyQt .QtCore import (
24- Qt , QObject , QSize , QSizeF , QPointF , QRectF , QT_VERSION
25+ Qt , QObject , QSize , QSizeF , QPointF , QRectF , QT_VERSION ,
2526)
2627from AnyQt .QtCore import pyqtSignal as Signal
2728
2829from ..scheme import compatible_channels
29- # 'Unused' imports are used in docstrings
30- from ..registry import InputSignal , OutputSignal # pylint: disable=unused-import
30+ from ..registry import InputSignal , OutputSignal
3131
3232from ..resources import icon_loader
3333
@@ -120,6 +120,7 @@ def __onGeometryChanged(self):
120120 left , top , right , bottom = self .getContentsMargins ()
121121 self .view .setFixedSize (size .toSize () + \
122122 QSize (left + right + 4 , top + bottom + 4 ))
123+ self .view .setSceneRect (self .scene .editWidget .geometry ())
123124
124125
125126def find_item_at (scene , pos , order = Qt .DescendingOrder , type = None ,
@@ -141,8 +142,7 @@ def find_item_at(scene, pos, order=Qt.DescendingOrder, type=None,
141142 item .objectName () != name :
142143 continue
143144 return item
144- else :
145- return None
145+ return None
146146
147147
148148class LinksEditScene (QGraphicsScene ):
@@ -251,10 +251,11 @@ def mousePressEvent(self, event):
251251 if event .button () == Qt .LeftButton :
252252 startItem = find_item_at (self .scene (), event .pos (),
253253 type = ChannelAnchor )
254- if startItem is not None :
254+ if startItem is not None and startItem . isEnabled () :
255255 # Start a connection line drag.
256256 self .__dragStartItem = startItem
257257 self .__tmpLine = None
258+
258259 event .accept ()
259260 return
260261
@@ -278,11 +279,12 @@ def mouseMoveEvent(self, event):
278279 (downPos - event .pos ()).manhattanLength () > \
279280 QApplication .instance ().startDragDistance ():
280281 # Start a line drag
281- line = QGraphicsLineItem (self )
282+ line = LinkLineItem (self )
282283 start = self .__dragStartItem .boundingRect ().center ()
283284 start = self .mapFromItem (self .__dragStartItem , start )
284- line .setLine (start .x (), start .y (),
285- event .pos ().x (), event .pos ().y ())
285+
286+ eventPos = event .pos ()
287+ line .setLine (start .x (), start .y (), eventPos .x (), eventPos .y ())
286288
287289 pen = QPen (self .palette ().color (QPalette .Foreground ), 4 )
288290 pen .setCapStyle (Qt .RoundCap )
@@ -291,16 +293,35 @@ def mouseMoveEvent(self, event):
291293
292294 self .__tmpLine = line
293295
296+ if self .__dragStartItem in self .sourceNodeWidget .channelAnchors :
297+ for anchor in self .sinkNodeWidget .channelAnchors :
298+ self .__updateAnchorState (anchor , [self .__dragStartItem ])
299+ else :
300+ for anchor in self .sourceNodeWidget .channelAnchors :
301+ self .__updateAnchorState (anchor , [self .__dragStartItem ])
302+
294303 if self .__tmpLine :
295304 # Update the temp line
296305 line = self .__tmpLine .line ()
297- line .setP2 (event .pos ())
306+
307+ maybe_anchor = find_item_at (self .scene (), event .scenePos (),
308+ type = ChannelAnchor )
309+ # If hovering over anchor
310+ if maybe_anchor is not None and maybe_anchor .isEnabled ():
311+ target_pos = maybe_anchor .boundingRect ().center ()
312+ target_pos = self .mapFromItem (maybe_anchor , target_pos )
313+ line .setP2 (target_pos )
314+ else :
315+ target_pos = event .pos ()
316+ line .setP2 (target_pos )
317+
298318 self .__tmpLine .setLine (line )
299319
300320 QGraphicsWidget .mouseMoveEvent (self , event )
301321
302322 def mouseReleaseEvent (self , event ):
303323 if event .button () == Qt .LeftButton and self .__tmpLine :
324+ self .__resetAnchorStates ()
304325 endItem = find_item_at (self .scene (), event .scenePos (),
305326 type = ChannelAnchor )
306327
@@ -312,6 +333,7 @@ def mouseReleaseEvent(self, event):
312333
313334 # Make sure the drag was from input to output (or reversed) and
314335 # not between input -> input or output -> output
336+ # pylint: disable=unidiomatic-typecheck
315337 if type (startChannel ) != type (endChannel ):
316338 if isinstance (startChannel , InputSignal ):
317339 startChannel , endChannel = endChannel , startChannel
@@ -350,7 +372,7 @@ def addLink(self, output, input):
350372 if s2 == input :
351373 self .removeLink (s1 , s2 )
352374
353- line = QGraphicsLineItem (self )
375+ line = LinkLineItem (self )
354376
355377 source_anchor = self .sourceNodeWidget .anchor (output )
356378 sink_anchor = self .sinkNodeWidget .anchor (input )
@@ -360,11 +382,7 @@ def addLink(self, output, input):
360382
361383 sink_pos = sink_anchor .boundingRect ().center ()
362384 sink_pos = self .mapFromItem (sink_anchor , sink_pos )
363- line .setLine (source_pos .x (), source_pos .y (),
364- sink_pos .x (), sink_pos .y ())
365- pen = QPen (self .palette ().color (QPalette .Foreground ), 4 )
366- pen .setCapStyle (Qt .RoundCap )
367- line .setPen (pen )
385+ line .setLine (source_pos .x (), source_pos .y (), sink_pos .x (), sink_pos .y ())
368386
369387 self .__links .append (_Link (output , input , line ))
370388
@@ -450,6 +468,61 @@ def __updateState(self):
450468 self .sourceNodeTitle = left_title
451469 self .sinkNodeTitle = right_title
452470
471+ self .__resetAnchorStates ()
472+
473+ # AnchorHover hover over anchor before hovering over line
474+ class AnchorHover (QGraphicsRectItem ):
475+ def __init__ (self , anchor , parent = None ):
476+ QGraphicsRectItem .__init__ (self , parent = parent )
477+ self .setAcceptHoverEvents (True )
478+
479+ self .anchor = anchor
480+ self .setRect (anchor .boundingRect ())
481+
482+ self .setPos (self .mapFromScene (anchor .scenePos ()))
483+ self .setFlag (QGraphicsItem .ItemHasNoContents , True )
484+
485+ def hoverEnterEvent (self , event ):
486+ if self .anchor .isEnabled ():
487+ self .anchor .hoverEnterEvent (event )
488+ else :
489+ event .ignore ()
490+
491+ def hoverLeaveEvent (self , event ):
492+ if self .anchor .isEnabled ():
493+ self .anchor .hoverLeaveEvent (event )
494+ else :
495+ event .ignore ()
496+
497+ for anchor in left_node .channelAnchors + right_node .channelAnchors :
498+ anchor_hover = AnchorHover (anchor , parent = self )
499+ anchor_hover .setZValue (2.0 )
500+
501+ def __resetAnchorStates (self ):
502+ source_anchors = self .sourceNodeWidget .channelAnchors
503+ sink_anchors = self .sinkNodeWidget .channelAnchors
504+ for anchor in source_anchors :
505+ self .__updateAnchorState (anchor , sink_anchors )
506+ for anchor in sink_anchors :
507+ self .__updateAnchorState (anchor , source_anchors )
508+
509+ def __updateAnchorState (self , anchor , opposite_anchors ):
510+ first_channel = anchor .channel ()
511+ for opposite_anchor in opposite_anchors :
512+ second_channel = opposite_anchor .channel ()
513+ if isinstance (first_channel , OutputSignal ) and \
514+ compatible_channels (first_channel , second_channel ) or \
515+ isinstance (first_channel , InputSignal ) and \
516+ compatible_channels (second_channel , first_channel ):
517+ anchor .setEnabled (True )
518+ anchor .setToolTip ("Click and drag to connect widgets!" )
519+ return
520+ if isinstance (first_channel , OutputSignal ):
521+ anchor .setToolTip ("No compatible input channel." )
522+ else :
523+ anchor .setToolTip ("No compatible output channel." )
524+ anchor .setEnabled (False )
525+
453526 if QT_VERSION < 0x40700 :
454527 geometryChanged = Signal ()
455528
@@ -490,7 +563,7 @@ def __init__(self, parent=None, direction=Qt.LeftToRight,
490563 self .__iconLayoutItem = GraphicsItemLayoutItem (item = self .__iconItem )
491564
492565 self .__channelLayout = QGraphicsGridLayout ()
493- self .__channelAnchors = []
566+ self .channelAnchors = []
494567
495568 if self .__direction == Qt .LeftToRight :
496569 self .layout ().addItem (self .__iconLayoutItem )
@@ -576,7 +649,7 @@ def setSchemeNode(self, node):
576649 label_row = 1
577650 anchor_row = 0
578651
579- self .__channelAnchors = []
652+ self .channelAnchors = []
580653 grid = self .__channelLayout
581654
582655 for i , channel in enumerate (channels ):
@@ -595,20 +668,17 @@ def setSchemeNode(self, node):
595668 anchor = ChannelAnchor (self , channel = channel ,
596669 rect = QRectF (0 , 0 , 20 , 20 ))
597670
598- anchor .setBrush (self .palette ().brush (QPalette .Mid ))
599-
600671 layout_item = GraphicsItemLayoutItem (grid , item = anchor )
601672 grid .addItem (layout_item , i , anchor_row ,
602673 alignment = anchor_alignment )
603- anchor .setToolTip (escape (channel .type ))
604674
605- self .__channelAnchors .append (anchor )
675+ self .channelAnchors .append (anchor )
606676
607677 def anchor (self , channel ):
608678 """
609679 Return the anchor item for the `channel` name.
610680 """
611- for anchor in self .__channelAnchors :
681+ for anchor in self .channelAnchors :
612682 if anchor .channel () == channel :
613683 return anchor
614684
@@ -672,10 +742,12 @@ class ChannelAnchor(QGraphicsRectItem):
672742 """
673743 def __init__ (self , parent = None , channel = None , rect = None , ** kwargs ):
674744 QGraphicsRectItem .__init__ (self , ** kwargs )
675- self .setAcceptHoverEvents (True )
676745 self .setAcceptedMouseButtons (Qt .NoButton )
677746 self .__channel = None
678747
748+ # AnchorHover handles hover events
749+ # self.setAcceptHoverEvents(True)
750+
679751 if rect is None :
680752 rect = QRectF (0 , 0 , 20 , 20 )
681753
@@ -684,10 +756,13 @@ def __init__(self, parent=None, channel=None, rect=None, **kwargs):
684756 if channel :
685757 self .setChannel (channel )
686758
687- self .__shadow = QGraphicsDropShadowEffect (blurRadius = 5 ,
688- offset = QPointF (0 , 0 ))
689- self .setGraphicsEffect (self .__shadow )
690- self .__shadow .setEnabled (False )
759+ self .__default_pen = QPen (QColor ('#000000' ), 1 )
760+ self .__hover_pen = QPen (QColor ('#000000' ), 2 )
761+ self .setPen (self .__default_pen )
762+
763+ self .enabledBrush = QColor ('#FFFFFF' )
764+ self .disabledBrush = QColor ('#BBBBBB' )
765+ self .setBrush (self .enabledBrush )
691766
692767 def setChannel (self , channel ):
693768 """
@@ -696,23 +771,32 @@ def setChannel(self, channel):
696771 if channel != self .__channel :
697772 self .__channel = channel
698773
699- if hasattr (channel , "description" ):
700- self .setToolTip (channel .description )
701- # TODO: Should also include name, type, flags, dynamic in the
702- # tool tip as well as add visual clues to the anchor
703-
704774 def channel (self ):
705775 """
706776 Return the channel description.
707777 """
708778 return self .__channel
709779
780+ def setEnabled (self , enabled ):
781+ QGraphicsRectItem .setEnabled (self , enabled )
782+ if enabled :
783+ self .setBrush (self .enabledBrush )
784+ else :
785+ self .setBrush (self .disabledBrush )
786+
787+ def paint (self , painter , option , widget = None ):
788+ QGraphicsRectItem .paint (self , painter , option , widget )
789+ # if disabled, draw X over box
790+ if not self .isEnabled ():
791+ painter .drawLine (self .rect ().topLeft (), self .rect ().bottomRight ())
792+ painter .drawLine (self .rect ().topRight (), self .rect ().bottomLeft ())
793+
710794 def hoverEnterEvent (self , event ):
711- self .__shadow . setEnabled ( True )
795+ self .setPen ( self . __hover_pen )
712796 QGraphicsRectItem .hoverEnterEvent (self , event )
713797
714798 def hoverLeaveEvent (self , event ):
715- self .__shadow . setEnabled ( False )
799+ self .setPen ( self . __default_pen )
716800 QGraphicsRectItem .hoverLeaveEvent (self , event )
717801
718802
@@ -783,3 +867,59 @@ def setDocument(self, doc):
783867 def _onDocumentSizeChanged (self , size ):
784868 """The doc size has changed"""
785869 self .updateGeometry ()
870+
871+
872+ class LinkLineItem (QGraphicsLineItem ):
873+ """
874+ A line connecting two Channel Anchors.
875+ """
876+
877+ def __init__ (self , parent = None ):
878+ QGraphicsLineItem .__init__ (self , parent )
879+ self .setAcceptHoverEvents (True )
880+
881+ self .__shape = None
882+
883+ self .__default_pen = QPen (QColor ('#383838' ), 4 )
884+ self .__default_pen .setCapStyle (Qt .RoundCap )
885+ self .__hover_pen = QPen (QColor ('#000000' ), 4 )
886+ self .__hover_pen .setCapStyle (Qt .RoundCap )
887+ self .setPen (self .__default_pen )
888+
889+ self .__shadow = QGraphicsDropShadowEffect (
890+ blurRadius = 10 , color = QColor ('#9CACB4' ),
891+ offset = QPointF (0 , 0 )
892+ )
893+
894+ self .setGraphicsEffect (self .__shadow )
895+ self .prepareGeometryChange ()
896+ self .__shadow .setEnabled (False )
897+
898+ def setLine (self , * args , ** kwargs ):
899+ super ().setLine (* args , ** kwargs )
900+
901+ # extends mouse hit area
902+ stroke_path = QPainterPathStroker ()
903+ stroke_path .setCapStyle (Qt .RoundCap )
904+
905+ stroke_path .setWidth (10 )
906+ self .__shape = stroke_path .createStroke (super ().shape ())
907+
908+ def shape (self ):
909+ if self .__shape is None :
910+ return QGraphicsLineItem .shape (self )
911+ return self .__shape
912+
913+ def hoverEnterEvent (self , event ):
914+ self .prepareGeometryChange ()
915+ self .__shadow .setEnabled (True )
916+ self .setPen (self .__hover_pen )
917+ self .setZValue (1.0 )
918+ QGraphicsLineItem .hoverEnterEvent (self , event )
919+
920+ def hoverLeaveEvent (self , event ):
921+ self .prepareGeometryChange ()
922+ self .__shadow .setEnabled (False )
923+ self .setPen (self .__default_pen )
924+ self .setZValue (0.0 )
925+ QGraphicsLineItem .hoverLeaveEvent (self , event )
0 commit comments