""" Copyright 2008, 2009 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GNU Radio Companion is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ from . import odict from Element import Element from Block import Block from Connection import Connection from .. gui import Messages class FlowGraph(Element): def __init__(self, platform): """ Make a flow graph from the arguments. @param platform a platforms with blocks and contrcutors @return the flow graph object """ #initialize Element.__init__(self, platform) #inital blank import self.import_data() def _get_unique_id(self, base_id=''): """ Get a unique id starting with the base id. @param base_id the id starts with this and appends a count @return a unique id """ index = 0 while True: id = '%s_%d'%(base_id, index) index = index + 1 #make sure that the id is not used by another block if not filter(lambda b: b.get_id() == id, self.get_blocks()): return id def __str__(self): return 'FlowGraph - %s(%s)'%(self.get_option('title'), self.get_option('id')) def get_option(self, key): """ Get the option for a given key. The option comes from the special options block. @param key the param key for the options block @return the value held by that param """ return self._options_block.get_param(key).get_evaluated() def is_flow_graph(self): return True ############################################## ## Access Elements ############################################## def get_block(self, id): return filter(lambda b: b.get_id() == id, self.get_blocks())[0] def get_blocks(self): return filter(lambda e: e.is_block(), self.get_elements()) def get_connections(self): return filter(lambda e: e.is_connection(), self.get_elements()) def get_elements(self): """ Get a list of all the elements. Always ensure that the options block is in the list (only once). @return the element list """ options_block_count = self._elements.count(self._options_block) if not options_block_count: self._elements.append(self._options_block) for i in range(options_block_count-1): self._elements.remove(self._options_block) return self._elements def get_enabled_blocks(self): """ Get a list of all blocks that are enabled. @return a list of blocks """ return filter(lambda b: b.get_enabled(), self.get_blocks()) def get_enabled_connections(self): """ Get a list of all connections that are enabled. @return a list of connections """ return filter(lambda c: c.get_enabled(), self.get_connections()) def get_new_block(self, key): """ Get a new block of the specified key. Add the block to the list of elements. @param key the block key @return the new block or None if not found """ self.flag() if key not in self.get_parent().get_block_keys(): return None block = self.get_parent().get_new_block(self, key) self.get_elements().append(block) return block def connect(self, porta, portb): """ Create a connection between porta and portb. @param porta a port @param portb another port @throw Exception bad connection @return the new connection """ self.flag() connection = self.get_parent().Connection(self, porta, portb) self.get_elements().append(connection) return connection def remove_element(self, element): """ Remove the element from the list of elements. If the element is a port, remove the whole block. If the element is a block, remove its connections. If the element is a connection, just remove the connection. """ self.flag() if element not in self.get_elements(): return #found a port, set to parent signal block if element.is_port(): element = element.get_parent() #remove block, remove all involved connections if element.is_block(): for port in element.get_ports(): map(self.remove_element, port.get_connections()) self.get_elements().remove(element) def evaluate(self, expr): """ Evaluate the expression. @param expr the string expression @throw NotImplementedError """ raise NotImplementedError def validate(self): """ Validate the flow graph. All connections and blocks must be valid. """ Element.validate(self) for c in self.get_elements(): try: c.validate() assert c.is_valid() except AssertionError: self.add_error_message('Element "%s" is not valid.'%c) ############################################## ## Import/Export Methods ############################################## def export_data(self): """ Export this flow graph to nested data. Export all block and connection data. @return a nested data odict """ import time n = odict() n['timestamp'] = time.ctime() n['block'] = [block.export_data() for block in self.get_blocks()] n['connection'] = [connection.export_data() for connection in self.get_connections()] return odict({'flow_graph': n}) def import_data(self, n=None): """ Import blocks and connections into this flow graph. Clear this flowgraph of all previous blocks and connections. Any blocks or connections in error will be ignored. @param n the nested data odict """ #remove previous elements self._elements = list() #use blank data if none provided fg_n = n and n.find('flow_graph') or odict() blocks_n = fg_n.findall('block') connections_n = fg_n.findall('connection') #create option block self._options_block = self.get_parent().get_new_block(self, 'options') #build the blocks for block_n in blocks_n: key = block_n.find('key') if key == 'options': block = self._options_block else: block = self.get_new_block(key) #only load the block when the block key was valid if block: block.import_data(block_n) else: Messages.send_error_load('Block key "%s" not found in %s'%(key, self.get_parent())) self.validate() #validate all blocks before connections are made (in case of nports) #build the connections for connection_n in connections_n: #try to make the connection try: #get the block ids source_block_id = connection_n.find('source_block_id') sink_block_id = connection_n.find('sink_block_id') #get the port keys source_key = connection_n.find('source_key') sink_key = connection_n.find('sink_key') #verify the blocks block_ids = map(lambda b: b.get_id(), self.get_blocks()) assert(source_block_id in block_ids) assert(sink_block_id in block_ids) #get the blocks source_block = self.get_block(source_block_id) sink_block = self.get_block(sink_block_id) #verify the ports assert(source_key in source_block.get_source_keys()) assert(sink_key in sink_block.get_sink_keys()) #get the ports source = source_block.get_source(source_key) sink = sink_block.get_sink(sink_key) #build the connection self.connect(source, sink) 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))