94c786d03507610e83d996bbb6cf64032c5a1fcd
[debian/gnuradio] / grc / src / grc / elements / FlowGraph.py
1 """
2 Copyright 2008 Free Software Foundation, Inc.
3 This file is part of GNU Radio
4
5 GNU Radio Companion is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 GNU Radio Companion is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18 """
19 ##@package grc.elements.FlowGraph
20 #Primative flow graph.
21
22 from grc import Utils
23 from grc.Utils import odict
24 from grc.elements.Element import Element
25 from grc.elements.Block import Block
26 from grc.elements.Connection import Connection
27
28 from grc import Messages
29
30 class FlowGraph(Element):
31
32         def __init__(self, platform):
33                 """!
34                 Make a flow graph from the arguments.
35                 @param platform a platforms with blocks and contrcutors
36                 @return the flow graph object
37                 """
38                 #hold connections and blocks
39                 self._elements = list()
40                 #initialize
41                 Element.__init__(self, platform)
42                 #inital blank import
43                 self.import_data({'flow_graph': {}})
44
45         def __str__(self): return 'FlowGraph - "%s"'%self.get_option('name')
46
47         def get_option(self, key):
48                 """!
49                 Get the option for a given key.
50                 The option comes from the special options block.
51                 @param key the param key for the options block
52                 @return the value held by that param
53                 """
54                 return self._options_block.get_param(key).evaluate()
55
56         def is_flow_graph(self): return True
57
58         ##############################################
59         ## Access Elements
60         ##############################################
61         def get_block(self, id): return filter(lambda b: b.get_id() == id, self.get_blocks())[0]
62         def get_blocks(self): return filter(lambda e: e.is_block(), self.get_elements())
63         def get_connections(self): return filter(lambda e: e.is_connection(), self.get_elements())
64         def get_elements(self):
65                 """!
66                 Get a list of all the elements.
67                 Always ensure that the options block is in the list.
68                 @return the element list
69                 """
70                 if self._options_block not in self._elements: self._elements.append(self._options_block)
71                 #ensure uniqueness of the elements list
72                 element_set = set()
73                 element_list = list()
74                 for element in self._elements:
75                         if element not in element_set: element_list.append(element)
76                         element_set.add(element)
77                 #store cleaned up list
78                 self._elements = element_list
79                 return self._elements
80
81         def get_enabled_blocks(self):
82                 """!
83                 Get a list of all blocks that are enabled.
84                 @return a list of blocks
85                 """
86                 return filter(lambda b: b.get_enabled(), self.get_blocks())
87
88         def get_enabled_connections(self):
89                 """!
90                 Get a list of all connections that are enabled.
91                 @return a list of connections
92                 """
93                 return filter(lambda c: c.get_enabled(), self.get_connections())
94
95         def get_new_block(self, key):
96                 """!
97                 Get a new block of the specified key.
98                 Add the block to the list of elements.
99                 @param key the block key
100                 @return the new block or None if not found
101                 """
102                 self.flag()
103                 if key not in self.get_parent().get_block_keys(): return None
104                 block = self.get_parent().get_new_block(self, key)
105                 self.get_elements().append(block)
106                 return block
107
108         def connect(self, porta, portb):
109                 """!
110                 Create a connection between porta and portb.
111                 @param porta a port
112                 @param portb another port
113                 @throw Exception bad connection
114                 @return the new connection
115                 """
116                 self.flag()
117                 connection = self.get_parent().Connection(self, porta, portb)
118                 self.get_elements().append(connection)
119                 return connection
120
121         def remove_element(self, element):
122                 """!
123                 Remove the element from the list of elements.
124                 If the element is a port, remove the whole block.
125                 If the element is a block, remove its connections.
126                 If the element is a connection, just remove the connection.
127                 """
128                 self.flag()
129                 if element not in self.get_elements(): return
130                 #found a port, set to parent signal block
131                 if element.is_port():
132                         element = element.get_parent()
133                 #remove block, remove all involved connections
134                 if element.is_block():
135                         for port in element.get_ports():
136                                 map(lambda c: self.remove_element(c), port.get_connections())
137                 #remove a connection
138                 elif element.is_connection(): pass
139                 self.get_elements().remove(element)
140
141         def evaluate(self, expr):
142                 """!
143                 Evaluate the expression.
144                 @param expr the string expression
145                 @throw NotImplementedError
146                 """
147                 raise NotImplementedError
148
149         def validate(self):
150                 """
151                 Validate the flow graph.
152                 All connections and blocks must be valid.
153                 """
154                 for c in self.get_elements():
155                         try: assert(c.is_valid())
156                         except AssertionError: self._add_error_message('Element "%s" is not valid.'%c)
157
158         ##############################################
159         ## Import/Export Methods
160         ##############################################
161         def export_data(self):
162                 """
163                 Export this flow graph to nested data.
164                 Export all block and connection data.
165                 @return a nested data odict
166                 """
167                 import time
168                 n = odict()
169                 n['timestamp'] = time.ctime()
170                 n['block'] = [block.export_data() for block in self.get_blocks()]
171                 n['connection'] = [connection.export_data() for connection in self.get_connections()]
172                 return {'flow_graph': n}
173
174         def import_data(self, n):
175                 """
176                 Import blocks and connections into this flow graph.
177                 Clear this flowgraph of all previous blocks and connections.
178                 Any blocks or connections in error will be ignored.
179                 @param n the nested data odict
180                 """
181                 #remove previous elements
182                 self._elements = list()
183                 #the flow graph tag must exists, or use blank data
184                 if 'flow_graph' in n.keys(): fg_n = n['flow_graph']
185                 else:
186                         Messages.send_error_load('Flow graph data not found, loading blank flow graph.')
187                         fg_n = {}
188                 blocks_n = Utils.listify(fg_n, 'block')
189                 connections_n = Utils.listify(fg_n, 'connection')
190                 #create option block
191                 self._options_block = self.get_parent().get_new_block(self, 'options')
192                 self._options_block.get_param('id').set_value('options')
193                 #build the blocks
194                 for block_n in blocks_n:
195                         key = block_n['key']
196                         if key == 'options': block = self._options_block
197                         else: block = self.get_new_block(key)
198                         #only load the block when the block key was valid
199                         if block: block.import_data(block_n)
200                         else: Messages.send_error_load('Block key "%s" not found in %s'%(key, self.get_parent()))
201                 #build the connections
202                 for connection_n in connections_n:
203                         #test that the data tags exist
204                         try:
205                                 assert('source_block_id' in connection_n.keys())
206                                 assert('sink_block_id' in connection_n.keys())
207                                 assert('source_key' in connection_n.keys())
208                                 assert('sink_key' in connection_n.keys())
209                         except AssertionError: continue
210                         #try to make the connection
211                         try:
212                                 #get the block ids
213                                 source_block_id = connection_n['source_block_id']
214                                 sink_block_id = connection_n['sink_block_id']
215                                 #get the port keys
216                                 source_key = connection_n['source_key']
217                                 sink_key = connection_n['sink_key']
218                                 #verify the blocks
219                                 block_ids = map(lambda b: b.get_id(), self.get_blocks())
220                                 assert(source_block_id in block_ids)
221                                 assert(sink_block_id in block_ids)
222                                 #get the blocks
223                                 source_block = self.get_block(source_block_id)
224                                 sink_block = self.get_block(sink_block_id)
225                                 #verify the ports
226                                 assert(source_key in source_block.get_source_keys())
227                                 assert(sink_key in sink_block.get_sink_keys())
228                                 #get the ports
229                                 source = source_block.get_source(source_key)
230                                 sink = sink_block.get_sink(sink_key)
231                                 #build the connection
232                                 self.connect(source, sink)
233                         except AssertionError: Messages.send_error_load('Connection between %s(%s) and %s(%s) could not be made.'%(source_block_id, source_key, sink_block_id, sink_key))
234                 self.validate()
235