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