source: orange/Orange/OrangeCanvas/scheme/link.py @ 11267:fcf6723f3c9c

Revision 11267:fcf6723f3c9c, 5.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 15 months ago (diff)

Better str methods, error messages.

Line 
1"""
2===========
3Scheme Link
4===========
5
6"""
7
8from PyQt4.QtCore import QObject
9from PyQt4.QtCore import pyqtSignal as Signal
10from PyQt4.QtCore import pyqtProperty as Property
11
12from .utils import name_lookup
13from .errors import IncompatibleChannelTypeError
14
15
16def compatible_channels(source_channel, sink_channel):
17    """Do the channels in link have compatible types, i.e. can they be
18    connected based on their type.
19
20    """
21    source_type = name_lookup(source_channel.type)
22    sink_type = name_lookup(sink_channel.type)
23    ret = issubclass(source_type, sink_type)
24    if source_channel.dynamic:
25        ret = ret or issubclass(sink_type, source_type)
26    return ret
27
28
29def can_connect(source_node, sink_node):
30    """Return True if any output from `source_node` can be connected to
31    any input of `sink_node`.
32
33    """
34    return bool(possible_links(source_node, sink_node))
35
36
37def possible_links(source_node, sink_node):
38    """Return a list of (OutputSignal, InputSignal) tuples, that
39    can connect the two nodes.
40
41    """
42    possible = []
43    for source in source_node.output_channels():
44        for sink in sink_node.input_channels():
45            if compatible_channels(source, sink):
46                possible.append((source, sink))
47    return possible
48
49
50class SchemeLink(QObject):
51    """A instantiation of a link between two widget nodes in the scheme.
52
53    Parameters
54    ----------
55    source_node : `SchemeNode`
56        Source node.
57    source_channel : `OutputSignal`
58        The source widget's signal.
59    sink_node : `SchemeNode`
60        The sink node.
61    sink_channel : `InputSignal`
62        The sink widget's input signal.
63    properties : `dict`
64        Additional link properties.
65
66    """
67
68    enabled_changed = Signal(bool)
69    dynamic_enabled_changed = Signal(bool)
70
71    def __init__(self, source_node, source_channel,
72                 sink_node, sink_channel, enabled=True, properties=None,
73                 parent=None):
74        QObject.__init__(self, parent)
75        self.source_node = source_node
76
77        if isinstance(source_channel, basestring):
78            source_channel = source_node.output_channel(source_channel)
79        elif source_channel not in source_node.output_channels():
80            raise ValueError("%r not in in nodes output channels." \
81                             % source_channel)
82
83        self.source_channel = source_channel
84
85        self.sink_node = sink_node
86
87        if isinstance(sink_channel, basestring):
88            sink_channel = sink_node.input_channel(sink_channel)
89        elif sink_channel not in sink_node.input_channels():
90            raise ValueError("%r not in in nodes input channels." \
91                             % source_channel)
92
93        self.sink_channel = sink_channel
94
95        if not compatible_channels(source_channel, sink_channel):
96            raise IncompatibleChannelTypeError(
97                    "Cannot connect %r to %r" \
98                    % (source_channel, sink_channel)
99                )
100
101        self.__enabled = enabled
102        self.__dynamic_enabled = False
103        self.__tool_tip = ""
104        self.properties = properties or {}
105
106    def source_type(self):
107        """Return the type of the source channel.
108        """
109        return name_lookup(self.source_channel.type)
110
111    def sink_type(self):
112        """Return the type of the sink channel.
113        """
114        return name_lookup(self.sink_channel.type)
115
116    def is_dynamic(self):
117        """Is this link dynamic.
118        """
119        return self.source_channel.dynamic and \
120            issubclass(self.sink_type(), self.source_type()) and \
121            not (self.sink_type() is self.source_type())
122
123    def enabled(self):
124        """Is this link enabled.
125        """
126        return self.__enabled
127
128    def set_enabled(self, enabled):
129        """Enable/disable the link.
130        """
131        if self.__enabled != enabled:
132            self.__enabled = enabled
133            self.enabled_changed.emit(enabled)
134
135    enabled = Property(bool, fget=enabled, fset=set_enabled)
136
137    def set_dynamic_enabled(self, enabled):
138        """Enable/disable the dynamic link. Has no effect if
139        the link is not dynamic.
140
141        """
142        if self.is_dynamic() and self.__dynamic_enabled != enabled:
143            self.__dynamic_enabled = enabled
144            self.dynamic_enabled_changed.emit(enabled)
145
146    def dynamic_enabled(self):
147        """Is this dynamic link and `dynamic_enabled` set to True
148        """
149        return self.is_dynamic() and self.__dynamic_enabled
150
151    dynamic_enabled = Property(bool, fget=dynamic_enabled,
152                                 fset=set_dynamic_enabled)
153
154    def set_tool_tip(self, tool_tip):
155        """Set the link tool tip.
156        """
157        if self.__tool_tip != tool_tip:
158            self.__tool_tip = tool_tip
159
160    def tool_tip(self):
161        """Return the link's tool tip
162        """
163        return self.__tool_tip
164
165    tool_tip = Property(str, fget=tool_tip,
166                          fset=set_tool_tip)
167
168    def __str__(self):
169        return u"{0}(({1}, {2}) -> ({3}, {4}))".format(
170                    type(self).__name__,
171                    self.source_node.title,
172                    self.source_channel.name,
173                    self.sink_node.title,
174                    self.sink_channel.name
175                )
Note: See TracBrowser for help on using the repository browser.