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