Skip to content

Commit 0f2e705

Browse files
committed
canvas: Indicate runtime state on links
1 parent b050abc commit 0f2e705

File tree

5 files changed

+125
-28
lines changed

5 files changed

+125
-28
lines changed

Orange/canvas/canvas/items/linkitem.py

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from .nodeitem import SHADOW_COLOR
1818
from .utils import stroke_path
1919

20+
from ...scheme import SchemeLink
21+
2022

2123
class LinkCurveItem(QGraphicsPathItem):
2224
"""
@@ -117,21 +119,30 @@ class LinkAnchorIndicator(QGraphicsEllipseItem):
117119
"""
118120
def __init__(self, *args):
119121
QGraphicsEllipseItem.__init__(self, *args)
120-
self.setRect(-3, -3, 6, 6)
122+
self.setRect(-3.5, -3.5, 7., 7.)
121123
self.setPen(QPen(Qt.NoPen))
122-
self.normalBrush = QBrush(QColor("#9CACB4"))
123-
self.hoverBrush = QBrush(QColor("#7D7D7D"))
124-
self.setBrush(self.normalBrush)
125124
self.__hover = False
125+
self.__brush = QBrush(QColor("#9CACB4"))
126+
super().setBrush(self.__brush)
126127

127128
def setHoverState(self, state):
128129
"""The hover state is set by the LinkItem.
129130
"""
130131
self.__hover = state
131132
if state:
132-
self.setBrush(self.hoverBrush)
133+
brush = QBrush(self.__brush.color().darker(110))
133134
else:
134-
self.setBrush(self.normalBrush)
135+
brush = self.__brush
136+
super().setBrush(brush)
137+
138+
def setBrush(self, brush):
139+
brush = QBrush(brush)
140+
if self.__brush != brush:
141+
self.__brush = brush
142+
super().setBrush(brush)
143+
144+
def brush(self):
145+
return QBrush(self.__brush)
135146

136147

137148
class LinkItem(QGraphicsObject):
@@ -151,6 +162,17 @@ class LinkItem(QGraphicsObject):
151162
#: Z value of the item
152163
Z_VALUE = 0
153164

165+
#: Runtime link state value
166+
#: These are pulled from SchemeLink.State for ease of binding to it's
167+
#: state
168+
State = SchemeLink.State
169+
#: Link is empty; the source node does not have any value on output
170+
Empty = SchemeLink.Empty
171+
#: Link is active; the source node has a valid value on output
172+
Active = SchemeLink.Active
173+
#: The link is pending; the sink node is scheduled for update
174+
Pending = SchemeLink.Pending
175+
154176
def __init__(self, *args):
155177
self.__boundingRect = None
156178
QGraphicsObject.__init__(self, *args)
@@ -179,7 +201,7 @@ def __init__(self, *args):
179201

180202
self.__dynamic = False
181203
self.__dynamicEnabled = False
182-
204+
self.__state = LinkItem.Empty
183205
self.hover = False
184206

185207
self.prepareGeometryChange()
@@ -500,6 +522,32 @@ def isDynamic(self):
500522
"""
501523
return self.__dynamic
502524

525+
def setRuntimeState(self, state):
526+
"""
527+
Style the link appropriate to the LinkItem.State
528+
529+
Parameters
530+
----------
531+
state : LinkItem.State
532+
"""
533+
if self.__state != state:
534+
self.__state = state
535+
536+
if state & LinkItem.Active:
537+
self.sourceIndicator.setBrush(QBrush(Qt.green))
538+
else:
539+
self.sourceIndicator.setBrush(QBrush(Qt.gray))
540+
541+
if state & LinkItem.Pending:
542+
self.sinkIndicator.setBrush(QBrush(Qt.yellow))
543+
elif state & LinkItem.Active:
544+
self.sinkIndicator.setBrush(QBrush(Qt.green))
545+
else:
546+
self.sinkIndicator.setBrush(QBrush(Qt.gray))
547+
548+
def runtimeState(self):
549+
return self.__state
550+
503551
def __updatePen(self):
504552
self.prepareGeometryChange()
505553
self.__boundingRect = None

Orange/canvas/canvas/scene.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,9 @@ def add_link(self, scheme_link):
442442
item.setDynamicEnabled(scheme_link.dynamic_enabled)
443443
scheme_link.dynamic_enabled_changed.connect(item.setDynamicEnabled)
444444

445+
item.setRuntimeState(scheme_link.runtime_state())
446+
scheme_link.state_changed.connect(item.setRuntimeState)
447+
445448
self.add_link_item(item)
446449
self.__item_for_link[scheme_link] = item
447450
return item
@@ -513,7 +516,7 @@ def remove_link(self, scheme_link):
513516
scheme_link.dynamic_enabled_changed.disconnect(
514517
item.setDynamicEnabled
515518
)
516-
519+
scheme_link.state_changed.disconnect(item.setRuntimeState)
517520
self.remove_link_item(item)
518521

519522
def link_items(self):

Orange/canvas/scheme/link.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
===========
55
66
"""
7+
import enum
78

89
from PyQt4.QtCore import QObject
910
from PyQt4.QtCore import pyqtSignal as Signal
@@ -76,6 +77,23 @@ class SchemeLink(QObject):
7677
#: The link dynamic enabled state has changed.
7778
dynamic_enabled_changed = Signal(bool)
7879

80+
#: Runtime link state has changed
81+
state_changed = Signal(int)
82+
83+
class State(enum.IntEnum):
84+
"""
85+
Flags indicating the runtime state of a link
86+
"""
87+
#: A link is empty when it has no value on it
88+
Empty = 0
89+
#: A link is active when the source node provides a value on output
90+
Active = 1
91+
#: A link is pending when it's sink node has not yet been notified
92+
#: of a change (note that Empty|Pending is a valid state)
93+
Pending = 2
94+
95+
Empty, Active, Pending = State
96+
7997
def __init__(self, source_node, source_channel,
8098
sink_node, sink_channel, enabled=True, properties=None,
8199
parent=None):
@@ -108,6 +126,7 @@ def __init__(self, source_node, source_channel,
108126

109127
self.__enabled = enabled
110128
self.__dynamic_enabled = False
129+
self.__state = SchemeLink.Empty
111130
self.__tool_tip = ""
112131
self.properties = properties or {}
113132

@@ -166,6 +185,26 @@ def dynamic_enabled(self):
166185
dynamic_enabled = Property(bool, fget=dynamic_enabled,
167186
fset=set_dynamic_enabled)
168187

188+
def set_runtime_state(self, state):
189+
"""
190+
Set the link's runtime state.
191+
192+
Parameters
193+
----------
194+
state : SchemeLink.State
195+
"""
196+
if self.__state != state:
197+
self.__state = state
198+
self.state_changed.emit(state)
199+
200+
def runtime_state(self):
201+
"""
202+
Returns
203+
-------
204+
state : SchemeLink.State
205+
"""
206+
return self.__state
207+
169208
def set_tool_tip(self, tool_tip):
170209
"""
171210
Set the link tool tip.

Orange/canvas/scheme/signalmanager.py

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from PyQt4.QtCore import pyqtSignal as Signal
2121

2222

23-
from .scheme import SchemeNode
23+
from .scheme import SchemeNode, SchemeLink
2424
from functools import reduce
2525

2626
log = logging.getLogger(__name__)
@@ -185,6 +185,7 @@ def on_node_added(self, node):
185185

186186
def link_added(self, link):
187187
# push all current source values to the sink
188+
link.set_runtime_state(SchemeLink.Empty)
188189
if link.enabled:
189190
log.info("Link added (%s). Scheduling signal data update.", link)
190191
self._schedule(self.signals_on_link(link))
@@ -196,6 +197,7 @@ def link_removed(self, link):
196197
# purge all values in sink's queue
197198
log.info("Link removed (%s). Scheduling signal data purge.", link)
198199
self.purge_link(link)
200+
link.enabled_changed.disconnect(self.link_enabled_changed)
199201

200202
def link_enabled_changed(self, enabled):
201203
if enabled:
@@ -223,7 +225,15 @@ def link_contents(self, link):
223225
"""
224226
node, channel = link.source_node, link.source_channel
225227

226-
return self._node_outputs[node][channel]
228+
if node in self._node_outputs:
229+
return self._node_outputs[node][channel]
230+
else:
231+
# if the the node was already removed it's tracked outputs in
232+
# _node_outputs are cleared, however the final 'None' signal
233+
# deliveries for the link are left in the _input_queue.
234+
pending = [sig for sig in self._input_queue
235+
if sig.link is link]
236+
return {sig.id: sig.value for sig in pending}
227237

228238
def send(self, node, channel, value, id):
229239
"""
@@ -260,28 +270,20 @@ def _schedule(self, signals):
260270
"""
261271
self._input_queue.extend(signals)
262272

273+
for link in {sig.link for sig in signals}:
274+
# update the SchemeLink's runtime state flags
275+
contents = self.link_contents(link)
276+
if any(value is not None for value in contents.values()):
277+
state = SchemeLink.Active
278+
else:
279+
state = SchemeLink.Empty
280+
link.set_runtime_state(state | SchemeLink.Pending)
281+
263282
if signals:
264283
self.updatesPending.emit()
265284

266285
self._update()
267286

268-
def _update_links(self, source_node=None, source_channel=None,
269-
sink_node=None, sink_channel=None):
270-
"""
271-
Schedule update of all enabled links matching the query.
272-
273-
See :ref:`Scheme.find_links` for description of parameters.
274-
275-
"""
276-
links = self.scheme().find_links(source_node=source_node,
277-
source_channel=source_channel,
278-
sink_node=sink_node,
279-
sink_channel=sink_channel)
280-
links = list(filter(is_enabled, links))
281-
282-
signals = reduce(add, self.signals_on_link, [])
283-
self._schedule(signals)
284-
285287
def _update_link(self, link):
286288
"""
287289
Schedule update of a single link.
@@ -328,7 +330,12 @@ def process_node(self, node):
328330

329331
log.debug("Processing %r, sending %i signals.",
330332
node.title, len(signals_in))
333+
# Clear the link's pending flag.
334+
for link in {sig.link for sig in signals_in}:
335+
link.set_runtime_state(link.runtime_state() & ~SchemeLink.Pending)
331336

337+
assert ({sig.link for sig in self._input_queue}
338+
.intersection({sig.link for sig in signals_in}) == set([]))
332339
self.processingStarted.emit()
333340
self.processingStarted[SchemeNode].emit(node)
334341
try:

Orange/canvas/scheme/widgetsscheme.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from PyQt4.QtCore import pyqtSignal as Signal
3434

3535
from .signalmanager import SignalManager, compress_signals, can_enable_dynamic
36-
from .scheme import Scheme, SchemeNode
36+
from .scheme import Scheme, SchemeNode, SchemeLink
3737
from .node import UserMessage
3838
from ..utils import name_lookup
3939
from ..resources import icon_loader

0 commit comments

Comments
 (0)