summaryrefslogtreecommitdiff
path: root/grc/src/platforms/base
diff options
context:
space:
mode:
Diffstat (limited to 'grc/src/platforms/base')
-rw-r--r--grc/src/platforms/base/Block.py237
-rw-r--r--grc/src/platforms/base/Connection.py88
-rw-r--r--grc/src/platforms/base/Constants.py.in44
-rw-r--r--grc/src/platforms/base/Element.py93
-rw-r--r--grc/src/platforms/base/FlowGraph.py231
-rw-r--r--grc/src/platforms/base/Makefile.am47
-rw-r--r--grc/src/platforms/base/Param.py218
-rw-r--r--grc/src/platforms/base/Platform.py144
-rw-r--r--grc/src/platforms/base/Port.py106
-rw-r--r--grc/src/platforms/base/__init__.py1
10 files changed, 1209 insertions, 0 deletions
diff --git a/grc/src/platforms/base/Block.py b/grc/src/platforms/base/Block.py
new file mode 100644
index 000000000..e3ef84d94
--- /dev/null
+++ b/grc/src/platforms/base/Block.py
@@ -0,0 +1,237 @@
+"""
+Copyright 2008 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 utils
+from ... utils import odict
+from Element import Element
+from Param import Param
+from Port import Port
+
+from Cheetah.Template import Template
+from UserDict import UserDict
+
+class TemplateArg(UserDict):
+ """
+ A cheetah template argument created from a param.
+ The str of this class evaluates to the param's to code method.
+ The use of this class as a dictionary (enum only) will reveal the enum opts.
+ The eval method can return the param evaluated to a raw python data type.
+ """
+
+ def __init__(self, param):
+ UserDict.__init__(self)
+ self._param = param
+ if param.is_enum():
+ for key in param.get_opt_keys():
+ self[key] = str(param.get_opt(key))
+
+ def __str__(self):
+ return str(self._param.to_code())
+
+ def eval(self):
+ return self._param.evaluate()
+
+class Block(Element):
+
+ def __init__(self, flow_graph, n):
+ """
+ Make a new block from nested data.
+ @param flow graph the parent element
+ @param n the nested odict
+ @return block a new block
+ """
+ #grab the data
+ name = n['name']
+ key = n['key']
+ category = utils.exists_or_else(n, 'category', '')
+ params = utils.listify(n, 'param')
+ sources = utils.listify(n, 'source')
+ sinks = utils.listify(n, 'sink')
+ #build the block
+ Element.__init__(self, flow_graph)
+ #store the data
+ self._name = name
+ self._key = key
+ self._category = category
+ #create the param objects
+ self._params = odict()
+ #add the id param
+ self._params['id'] = self.get_parent().get_parent().Param(
+ self,
+ {
+ 'name': 'ID',
+ 'key': 'id',
+ 'type': 'id',
+ }
+ )
+ self._params['_enabled'] = self.get_parent().get_parent().Param(
+ self,
+ {
+ 'name': 'Enabled',
+ 'key': '_enabled',
+ 'type': 'raw',
+ 'value': 'True',
+ 'hide': 'all',
+ }
+ )
+ for param in map(lambda n: self.get_parent().get_parent().Param(self, n), params):
+ key = param.get_key()
+ #test against repeated keys
+ try: assert(key not in self.get_param_keys())
+ except AssertionError: self._exit_with_error('Key "%s" already exists in params'%key)
+ #store the param
+ self._params[key] = param
+ #create the source objects
+ self._sources = odict()
+ for source in map(lambda n: self.get_parent().get_parent().Source(self, n), sources):
+ key = source.get_key()
+ #test against repeated keys
+ try: assert(key not in self.get_source_keys())
+ except AssertionError: self._exit_with_error('Key "%s" already exists in sources'%key)
+ #store the port
+ self._sources[key] = source
+ #create the sink objects
+ self._sinks = odict()
+ for sink in map(lambda n: self.get_parent().get_parent().Sink(self, n), sinks):
+ key = sink.get_key()
+ #test against repeated keys
+ try: assert(key not in self.get_sink_keys())
+ except AssertionError: self._exit_with_error('Key "%s" already exists in sinks'%key)
+ #store the port
+ self._sinks[key] = sink
+ #begin the testing
+ self.test()
+
+ def test(self):
+ """
+ Call test on all children.
+ """
+ map(lambda c: c.test(), self.get_params() + self.get_sinks() + self.get_sources())
+
+ def get_enabled(self):
+ """
+ Get the enabled state of the block.
+ @return true for enabled
+ """
+ try: return eval(self.get_param('_enabled').get_value())
+ except: return True
+
+ def set_enabled(self, enabled):
+ """
+ Set the enabled state of the block.
+ @param enabled true for enabled
+ """
+ self.get_param('_enabled').set_value(str(enabled))
+
+ def validate(self):
+ """
+ Validate the block.
+ All ports and params must be valid.
+ All checks must evaluate to true.
+ """
+ if not self.get_enabled(): return
+ for c in self.get_params() + self.get_sinks() + self.get_sources():
+ try: assert(c.is_valid())
+ except AssertionError:
+ for msg in c.get_error_messages():
+ self._add_error_message('%s: %s'%(c, msg))
+
+ def __str__(self): return 'Block - %s - %s(%s)'%(self.get_id(), self.get_name(), self.get_key())
+
+ def get_id(self): return self.get_param('id').get_value()
+
+ def is_block(self): return True
+
+ def get_doc(self): return self._doc
+
+ def get_name(self): return self._name
+
+ def get_key(self): return self._key
+
+ def get_category(self): return self._category
+
+ def get_doc(self): return ''
+
+ def get_ports(self): return self.get_sources() + self.get_sinks()
+
+ ##############################################
+ # Access Params
+ ##############################################
+ def get_param_keys(self): return self._params.keys()
+ def get_param(self, key): return self._params[key]
+ def get_params(self): return self._params.values()
+
+ ##############################################
+ # Access Sinks
+ ##############################################
+ def get_sink_keys(self): return self._sinks.keys()
+ def get_sink(self, key): return self._sinks[key]
+ def get_sinks(self): return self._sinks.values()
+
+ ##############################################
+ # Access Sources
+ ##############################################
+ def get_source_keys(self): return self._sources.keys()
+ def get_source(self, key): return self._sources[key]
+ def get_sources(self): return self._sources.values()
+
+ def get_connections(self):
+ return sum([port.get_connections() for port in self.get_ports()], [])
+
+ def resolve_dependencies(self, tmpl):
+ """
+ Resolve a paramater dependency with cheetah templates.
+ @param tmpl the string with dependencies
+ @return the resolved value
+ """
+ tmpl = str(tmpl)
+ if '$' not in tmpl: return tmpl
+ n = dict((p.get_key(), TemplateArg(p)) for p in self.get_params())
+ try: return str(Template(tmpl, n))
+ except Exception, e: return "-------->\n%s: %s\n<--------"%(e, tmpl)
+
+ ##############################################
+ ## Import/Export Methods
+ ##############################################
+ def export_data(self):
+ """
+ Export this block's params to nested data.
+ @return a nested data odict
+ """
+ n = odict()
+ n['key'] = self.get_key()
+ n['param'] = map(lambda p: p.export_data(), self.get_params())
+ return n
+
+ def import_data(self, n):
+ """
+ Import this block's params from nested data.
+ Any param keys that do not exist will be ignored.
+ @param n the nested data odict
+ """
+ params_n = utils.listify(n, 'param')
+ for param_n in params_n:
+ #key and value must exist in the n data
+ if 'key' in param_n.keys() and 'value' in param_n.keys():
+ key = param_n['key']
+ value = param_n['value']
+ #the key must exist in this block's params
+ if key in self.get_param_keys():
+ self.get_param(key).set_value(value)
+ self.validate()
diff --git a/grc/src/platforms/base/Connection.py b/grc/src/platforms/base/Connection.py
new file mode 100644
index 000000000..3c0b42d78
--- /dev/null
+++ b/grc/src/platforms/base/Connection.py
@@ -0,0 +1,88 @@
+"""
+Copyright 2008 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 Element import Element
+from ... utils import odict
+
+class Connection(Element):
+
+ def __init__(self, flow_graph, porta, portb):
+ """
+ Make a new connection given the parent and 2 ports.
+ @param flow_graph the parent of this element
+ @param porta a port (any direction)
+ @param portb a port (any direction)
+ @throws Error cannot make connection
+ @return a new connection
+ """
+ Element.__init__(self, flow_graph)
+ source = sink = None
+ #separate the source and sink
+ for port in (porta, portb):
+ if port.is_source(): source = port
+ if port.is_sink(): sink = port
+ #verify the source and sink
+ assert(source and sink)
+ assert(not source.is_full())
+ assert(not sink.is_full())
+ self._source = source
+ self._sink = sink
+
+ def __str__(self): return 'Connection (%s -> %s)'%(self.get_source(), self.get_sink())
+
+ def is_connection(self): return True
+
+ def validate(self):
+ """
+ Validate the connections.
+ The ports must match in type.
+ """
+ source_type = self.get_source().get_type()
+ sink_type = self.get_sink().get_type()
+ try: assert source_type == sink_type
+ except AssertionError: self._add_error_message('Source type "%s" does not match sink type "%s".'%(source_type, sink_type))
+
+ def get_enabled(self):
+ """
+ Get the enabled state of this connection.
+ @return true if source and sink blocks are enabled
+ """
+ return self.get_source().get_parent().get_enabled() and \
+ self.get_sink().get_parent().get_enabled()
+
+ #############################
+ # Access Ports
+ #############################
+ def get_sink(self): return self._sink
+ def get_source(self): return self._source
+
+ ##############################################
+ ## Import/Export Methods
+ ##############################################
+ def export_data(self):
+ """
+ Export this connection's info.
+ @return a nested data odict
+ """
+ n = odict()
+ n['source_block_id'] = self.get_source().get_parent().get_id()
+ n['sink_block_id'] = self.get_sink().get_parent().get_id()
+ n['source_key'] = self.get_source().get_key()
+ n['sink_key'] = self.get_sink().get_key()
+ return n
diff --git a/grc/src/platforms/base/Constants.py.in b/grc/src/platforms/base/Constants.py.in
new file mode 100644
index 000000000..26ba72d97
--- /dev/null
+++ b/grc/src/platforms/base/Constants.py.in
@@ -0,0 +1,44 @@
+"""
+Copyright 2008 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
+"""
+
+import os
+
+##The current version of this code
+VERSION = '@VERSION@'
+
+##Location of external data files.
+DATA_DIR = '@datadir@'
+
+##DTD validator for saved flow graphs.
+FLOW_GRAPH_DTD = os.path.join(DATA_DIR, 'flow_graph.dtd')
+
+##The default file extension for flow graphs.
+FLOW_GRAPH_FILE_EXTENSION = '.grc'
+
+##The default file extension for saving flow graph snap shots.
+IMAGE_FILE_EXTENSION = '.png'
+
+##The default path for the open/save dialogs.
+DEFAULT_FILE_PATH = os.getcwd()
+
+##The default icon for the gtk windows.
+PY_GTK_ICON = os.path.join(DATA_DIR, 'grc-icon-256.png')
+
+##The users home directory.
+HOME_DIR = os.path.expanduser('~')
diff --git a/grc/src/platforms/base/Element.py b/grc/src/platforms/base/Element.py
new file mode 100644
index 000000000..b6602a314
--- /dev/null
+++ b/grc/src/platforms/base/Element.py
@@ -0,0 +1,93 @@
+"""
+Copyright 2008 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
+"""
+
+class Element(object):
+
+ def __init__(self, parent=None):
+ self._parent = parent
+ self._error_messages = []
+ self.flag()
+
+ def test(self):
+ """
+ Test the element against failures.
+ Overload this method in sub-classes.
+ """
+ pass
+
+ def validate(self):
+ """
+ Validate the data in this element.
+ Set the error message non blank for errors.
+ Overload this method in sub-classes.
+ """
+ pass
+
+ def is_valid(self):
+ self._error_messages = []#reset err msgs
+ try: self.validate()
+ except: pass
+ return not self.get_error_messages()
+
+ def _add_error_message(self, msg):
+ self._error_messages.append(msg)
+
+ def get_error_messages(self):
+ return self._error_messages
+
+ def get_parent(self):
+ return self._parent
+
+ def _exit_with_error(self, error):
+ parent = self
+ #build hier list of elements
+ elements = list()
+ while(parent):
+ elements.insert(0, parent)
+ parent = parent.get_parent()
+ #build error string
+ err_str = ">>> Error:"
+ for i, element in enumerate(elements + [error]):
+ err_str = err_str + '\n' + ''.join(' '*(i+2)) + str(element)
+ err_str = err_str + '\n'
+ exit(err_str)
+
+ ##############################################
+ ## Update flagging
+ ##############################################
+ def is_flagged(self): return self._flag
+ def flag(self):
+ self._flag = True
+ if self.get_parent(): self.get_parent().flag()
+ def deflag(self):
+ self._flag = False
+ if self.get_parent(): self.get_parent().deflag()
+
+ ##############################################
+ ## Type testing methods
+ ##############################################
+ def is_element(self): return True
+ def is_platform(self): return False
+ def is_flow_graph(self): return False
+ def is_connection(self): return False
+ def is_block(self): return False
+ def is_source(self): return False
+ def is_sink(self): return False
+ def is_port(self): return False
+ def is_param(self): return False
diff --git a/grc/src/platforms/base/FlowGraph.py b/grc/src/platforms/base/FlowGraph.py
new file mode 100644
index 000000000..bb20c61d0
--- /dev/null
+++ b/grc/src/platforms/base/FlowGraph.py
@@ -0,0 +1,231 @@
+"""
+Copyright 2008 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 utils
+from ... utils 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
+ """
+ #hold connections and blocks
+ self._elements = list()
+ #initialize
+ Element.__init__(self, platform)
+ #inital blank import
+ self.import_data({'flow_graph': {}})
+
+ def __str__(self): return 'FlowGraph - "%s"'%self.get_option('name')
+
+ 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).evaluate()
+
+ 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.
+ @return the element list
+ """
+ if self._options_block not in self._elements: self._elements.append(self._options_block)
+ #ensure uniqueness of the elements list
+ element_set = set()
+ element_list = list()
+ for element in self._elements:
+ if element not in element_set: element_list.append(element)
+ element_set.add(element)
+ #store cleaned up list
+ self._elements = element_list
+ 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(lambda c: self.remove_element(c), port.get_connections())
+ #remove a connection
+ elif element.is_connection(): pass
+ 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.
+ """
+ for c in self.get_elements():
+ try: 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 {'flow_graph': n}
+
+ def import_data(self, n):
+ """
+ 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()
+ #the flow graph tag must exists, or use blank data
+ if 'flow_graph' in n.keys(): fg_n = n['flow_graph']
+ else:
+ Messages.send_error_load('Flow graph data not found, loading blank flow graph.')
+ fg_n = {}
+ blocks_n = utils.listify(fg_n, 'block')
+ connections_n = utils.listify(fg_n, 'connection')
+ #create option block
+ self._options_block = self.get_parent().get_new_block(self, 'options')
+ self._options_block.get_param('id').set_value('options')
+ #build the blocks
+ for block_n in blocks_n:
+ key = block_n['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()))
+ #build the connections
+ for connection_n in connections_n:
+ #test that the data tags exist
+ try:
+ assert('source_block_id' in connection_n.keys())
+ assert('sink_block_id' in connection_n.keys())
+ assert('source_key' in connection_n.keys())
+ assert('sink_key' in connection_n.keys())
+ except AssertionError: continue
+ #try to make the connection
+ try:
+ #get the block ids
+ source_block_id = connection_n['source_block_id']
+ sink_block_id = connection_n['sink_block_id']
+ #get the port keys
+ source_key = connection_n['source_key']
+ sink_key = connection_n['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))
+ self.validate()
diff --git a/grc/src/platforms/base/Makefile.am b/grc/src/platforms/base/Makefile.am
new file mode 100644
index 000000000..dca53b8b5
--- /dev/null
+++ b/grc/src/platforms/base/Makefile.am
@@ -0,0 +1,47 @@
+#
+# Copyright 2008 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio 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 3, or (at your option)
+# any later version.
+#
+# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+include $(top_srcdir)/grc/Makefile.inc
+
+ourpythondir = $(grc_src_prefix)/platforms/base
+
+ourpython_PYTHON = \
+ Block.py \
+ Connection.py \
+ Constants.py \
+ Element.py \
+ FlowGraph.py \
+ Param.py \
+ Platform.py \
+ Port.py \
+ __init__.py
+
+BUILT_SOURCES = Constants.py
+
+Constants.py: Makefile $(srcdir)/Constants.py.in
+ sed \
+ -e 's|@VERSION[@]|$(VERSION)|g' \
+ -e 's|@datadir[@]|$(grc_base_data_dir)|g' \
+ $(srcdir)/Constants.py.in > $@
+
+EXTRA_DIST = Constants.py.in
+
+MOSTLYCLEANFILES = $(BUILT_SOURCES)
diff --git a/grc/src/platforms/base/Param.py b/grc/src/platforms/base/Param.py
new file mode 100644
index 000000000..3a8d98c30
--- /dev/null
+++ b/grc/src/platforms/base/Param.py
@@ -0,0 +1,218 @@
+"""
+Copyright 2008 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 utils
+from ... utils import odict
+from Element import Element
+
+class Option(Element):
+
+ def __init__(self, param, name, key, opts):
+ Element.__init__(self, param)
+ self._name = name
+ self._key = key
+ self._opts = dict()
+ for opt in opts:
+ #separate the key:value
+ try: key, value = opt.split(':')
+ except: self._exit_with_error('Error separating "%s" into key:value'%opt)
+ #test against repeated keys
+ try: assert(not self._opts.has_key(key))
+ except AssertionError: self._exit_with_error('Key "%s" already exists in option'%key)
+ #store the option
+ self._opts[key] = value
+
+ def __str__(self): return 'Option %s(%s)'%(self.get_name(), self.get_key())
+
+ def get_name(self): return self._name
+
+ def get_key(self): return self._key
+
+ ##############################################
+ # Access Opts
+ ##############################################
+ def get_opt_keys(self): return self._opts.keys()
+ def get_opt(self, key): return self._opts[key]
+ def get_opts(self): return self._opts.values()
+
+ ##############################################
+ ## Static Make Methods
+ ##############################################
+ def make_option_from_n(param, n):
+ """
+ Make a new option from nested data.
+ @param param the parent element
+ @param n the nested odict
+ @return a new option
+ """
+ #grab the data
+ name = n['name']
+ key = n['key']
+ opts = utils.listify(n, 'opt')
+ #build the option
+ return Option(
+ param=param,
+ name=name,
+ key=key,
+ opts=opts,
+ )
+ make_option_from_n = staticmethod(make_option_from_n)
+
+class Param(Element):
+
+ ##possible param types
+ TYPES = ['enum', 'raw']
+
+ def __init__(self, block, n):
+ """
+ Make a new param from nested data.
+ @param block the parent element
+ @param n the nested odict
+ @return a new param
+ """
+ #grab the data
+ name = n['name']
+ key = n['key']
+ value = utils.exists_or_else(n, 'value', '')
+ type = n['type']
+ hide = utils.exists_or_else(n, 'hide', '')
+ options = utils.listify(n, 'option')
+ #build the param
+ Element.__init__(self, block)
+ self._name = name
+ self._key = key
+ self._type = type
+ self._hide = hide
+ #create the Option objects from the n data
+ self._options = odict()
+ for option in map(lambda o: Option.make_option_from_n(self, o), options):
+ key = option.get_key()
+ #test against repeated keys
+ try: assert(key not in self.get_option_keys())
+ except AssertionError: self._exit_with_error('Key "%s" already exists in options'%key)
+ #store the option
+ self._options[key] = option
+ #test the enum options
+ if self._options or self.is_enum():
+ #test against bad combos of type and enum
+ try: assert(self._options)
+ except AssertionError: self._exit_with_error('At least one option must exist when type "enum" is set.')
+ try: assert(self.is_enum())
+ except AssertionError: self._exit_with_error('Type "enum" must be set when options are present.')
+ #test against options with identical keys
+ try: assert(len(set(self.get_option_keys())) == len(self._options))
+ except AssertionError: self._exit_with_error('Options keys "%s" are not unique.'%self.get_option_keys())
+ #test against inconsistent keys in options
+ opt_keys = self._options.values()[0].get_opt_keys()
+ for option in self._options.values():
+ try: assert(set(opt_keys) == set(option.get_opt_keys()))
+ except AssertionError: self._exit_with_error('Opt keys "%s" are not identical across all options.'%opt_keys)
+ #if a value is specified, it must be in the options keys
+ self._value = value or self.get_option_keys()[0]
+ try: assert(self.get_value() in self.get_option_keys())
+ except AssertionError: self._exit_with_error('The value "%s" is not in the possible values of "%s".'%(self.get_value(), self.get_option_keys()))
+ else: self._value = value or ''
+
+ def test(self):
+ """
+ call test on all children
+ """
+ map(lambda c: c.test(), self.get_options())
+
+ def validate(self):
+ """
+ Validate the param.
+ The value must be evaluated and type must a possible type.
+ """
+ try:
+ assert(self.get_type() in self.TYPES)
+ try: self.evaluate()
+ except:
+ #if the evaluate failed but added no error messages, add the generic one below
+ if not self.get_error_messages():
+ self._add_error_message('Value "%s" cannot be evaluated.'%self.get_value())
+ except AssertionError: self._add_error_message('Type "%s" is not a possible type.'%self.get_type())
+
+ def evaluate(self):
+ """
+ Evaluate the value of this param.
+ @throw NotImplementedError
+ """
+ raise NotImplementedError
+
+ def to_code(self):
+ """
+ Convert the value to code.
+ @throw NotImplementedError
+ """
+ raise NotImplementedError
+
+ def __str__(self): return 'Param - %s(%s)'%(self.get_name(), self.get_key())
+
+ def is_param(self): return True
+
+ def get_name(self): return self._name
+
+ def get_key(self): return self._key
+
+ def get_hide(self): return self.get_parent().resolve_dependencies(self._hide)
+
+ def get_value(self):
+ value = self._value
+ if self.is_enum() and value not in self.get_option_keys():
+ value = self.get_option_keys()[0]
+ self.set_value(value)
+ return value
+
+ def set_value(self, value):
+ self.flag()
+ self._value = str(value) #must be a string
+
+ def get_type(self): return self.get_parent().resolve_dependencies(self._type)
+
+ def is_enum(self): return self._type == 'enum'
+
+ def is_type_dependent(self): return '$' in self._type
+
+ ##############################################
+ # Access Options
+ ##############################################
+ def get_option_keys(self): return self._options.keys()
+ def get_option(self, key): return self._options[key]
+ def get_options(self): return self._options.values()
+
+ ##############################################
+ # Access Opts
+ ##############################################
+ def get_opt_keys(self): return self._options[self.get_value()].get_opt_keys()
+ def get_opt(self, key): return self._options[self.get_value()].get_opt(key)
+ def get_opts(self): return self._options[self.get_value()].get_opts()
+
+ ##############################################
+ ## Import/Export Methods
+ ##############################################
+ def export_data(self):
+ """
+ Export this param's key/value.
+ @return a nested data odict
+ """
+ n = odict()
+ n['key'] = self.get_key()
+ n['value'] = self.get_value()
+ return n
diff --git a/grc/src/platforms/base/Platform.py b/grc/src/platforms/base/Platform.py
new file mode 100644
index 000000000..c25b4a050
--- /dev/null
+++ b/grc/src/platforms/base/Platform.py
@@ -0,0 +1,144 @@
+"""
+Copyright 2008 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
+"""
+
+import os
+from ... utils import ParseXML
+from ... import utils
+from Element import Element as _Element
+from FlowGraph import FlowGraph as _FlowGraph
+from Connection import Connection as _Connection
+from Block import Block as _Block
+from Port import Port as _Port
+from Param import Param as _Param
+from Constants import DATA_DIR
+
+class Platform(_Element):
+
+ def __init__(self, name, key, block_paths, block_dtd, block_tree, default_flow_graph, generator):
+ """
+ Make a platform from the arguments.
+ @param name the platform name
+ @param key the unique platform key
+ @param block_paths the file paths to blocks in this platform
+ @param block_dtd the dtd validator for xml block wrappers
+ @param block_tree the nested tree of block keys and categories
+ @param default_flow_graph the default flow graph file path
+ @param load_one a single file to load into this platform or None
+ @return a platform object
+ """
+ _Element.__init__(self)
+ self._name = name
+ self._key = key
+ self._block_paths = block_paths
+ self._block_dtd = block_dtd
+ self._block_tree = block_tree
+ self._default_flow_graph = default_flow_graph
+ self._generator = generator
+ #create a dummy flow graph for the blocks
+ self._flow_graph = _Element(self)
+ #load the blocks
+ self._blocks = dict()
+ self._blocks_n = dict()
+ for block_path in self._block_paths:
+ if os.path.isfile(block_path): self._load_block(block_path)
+ elif os.path.isdir(block_path):
+ for dirpath, dirnames, filenames in os.walk(block_path):
+ for filename in filter(lambda f: f.endswith('.xml'), filenames):
+ self._load_block(os.path.join(dirpath, filename))
+
+ def get_prefs_block(self): return self.get_new_flow_graph().get_new_block('preferences')
+
+ def _load_block(self, f):
+ """
+ Load the block wrapper from the file path.
+ The block wrapper must pass validation, and have a unique block key.
+ If any of the checks fail, exit with error.
+ @param f the file path
+ """
+ try: ParseXML.validate_dtd(f, self._block_dtd)
+ except ParseXML.XMLSyntaxError, e: self._exit_with_error('Block definition "%s" failed: \n\t%s'%(f, e))
+ n = ParseXML.from_file(f)['block']
+ block = self.Block(self._flow_graph, n)
+ key = block.get_key()
+ #test against repeated keys
+ try: assert(key not in self.get_block_keys())
+ except AssertionError: self._exit_with_error('Key "%s" already exists in blocks'%key)
+ #store the block
+ self._blocks[key] = block
+ self._blocks_n[key] = n
+
+ def load_block_tree(self, block_tree):
+ """
+ Load a block tree with categories and blocks.
+ Step 1: Load all blocks from the xml specification.
+ Step 2: Load blocks with builtin category specifications.
+ @param block_tree the block tree object
+ """
+ #recursive function to load categories and blocks
+ def load_category(cat_n, parent=''):
+ #add this category
+ parent = '%s/%s'%(parent, cat_n['name'])
+ block_tree.add_block(parent)
+ #recursive call to load sub categories
+ map(lambda c: load_category(c, parent), utils.listify(cat_n, 'cat'))
+ #add blocks in this category
+ for block_key in utils.listify(cat_n, 'block'):
+ block_tree.add_block(parent, self.get_block(block_key))
+ #load the block tree
+ f = self._block_tree
+ try: ParseXML.validate_dtd(f, os.path.join(DATA_DIR, 'block_tree.dtd'))
+ except ParseXML.XMLSyntaxError, e: self._exit_with_error('Block tree "%s" failed: \n\t%s'%(f, e))
+ #add all blocks in the tree
+ load_category(ParseXML.from_file(f)['cat'])
+ #add all other blocks, use the catgory
+ for block in self.get_blocks():
+ #blocks with empty categories are in the xml block tree or hidden
+ if block.get_category(): block_tree.add_block(block.get_category(), block)
+
+ def __str__(self): return 'Platform - %s(%s)'%(self.get_key(), self.get_name())
+
+ def is_platform(self): return True
+
+ def get_new_flow_graph(self): return self.FlowGraph(self)
+
+ def get_default_flow_graph(self): return self._default_flow_graph
+
+ def get_generator(self): return self._generator
+
+ ##############################################
+ # Access Blocks
+ ##############################################
+ def get_block_keys(self): return self._blocks.keys()
+ def get_block(self, key): return self._blocks[key]
+ def get_blocks(self): return self._blocks.values()
+ def get_new_block(self, flow_graph, key): return self.Block(flow_graph, n=self._blocks_n[key])
+
+ def get_name(self): return self._name
+
+ def get_key(self): return self._key
+
+ ##############################################
+ # Constructors
+ ##############################################
+ FlowGraph = _FlowGraph
+ Connection = _Connection
+ Block = _Block
+ Source = _Port
+ Sink = _Port
+ Param = _Param
diff --git a/grc/src/platforms/base/Port.py b/grc/src/platforms/base/Port.py
new file mode 100644
index 000000000..61134791c
--- /dev/null
+++ b/grc/src/platforms/base/Port.py
@@ -0,0 +1,106 @@
+"""
+Copyright 2008 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 utils
+from Element import Element
+
+class Port(Element):
+
+ ##possible port types
+ TYPES = []
+
+ def __init__(self, block, n):
+ """
+ Make a new port from nested data.
+ @param block the parent element
+ @param n the nested odict
+ @return a new port
+ """
+ #grab the data
+ name = n['name']
+ key = n['key']
+ type = n['type']
+ #build the port
+ Element.__init__(self, block)
+ self._name = name
+ self._key = key
+ self._type = type
+
+ def validate(self):
+ """
+ Validate the port.
+ The port must be non-empty and type must a possible type.
+ """
+ try: assert(not self.is_empty())
+ except AssertionError: self._add_error_message('is empty.')
+ try: assert(self.get_type() in self.TYPES)
+ except AssertionError: self._add_error_message('Type "%s" is not a possible type.'%self.get_type())
+
+ def __str__(self):
+ if self.is_source():
+ return 'Source - %s(%s)'%(self.get_name(), self.get_key())
+ if self.is_sink():
+ return 'Sink - %s(%s)'%(self.get_name(), self.get_key())
+
+ def is_port(self): return True
+
+ def get_color(self): return '#FFFFFF'
+
+ def get_name(self): return self._name
+
+ def get_key(self): return self._key
+
+ def is_sink(self): return self in self.get_parent().get_sinks()
+
+ def is_source(self): return self in self.get_parent().get_sources()
+
+ def get_type(self): return self.get_parent().resolve_dependencies(self._type)
+
+ def get_connections(self):
+ """
+ Get all connections that use this port.
+ @return a list of connection objects
+ """
+ connections = self.get_parent().get_parent().get_connections()
+ connections = filter(lambda c: c.get_source() is self or c.get_sink() is self, connections)
+ return connections
+
+ def is_connected(self):
+ """
+ Is this port connected?
+ @return true if at least one connection
+ """
+ return bool(self.get_connections())
+
+ def is_full(self):
+ """
+ Is this port full of connections?
+ Generally a sink can handle one connection and a source can handle many.
+ @return true if the port is full
+ """
+ if self.is_source(): return False
+ if self.is_sink(): return bool(self.get_connections())
+
+ def is_empty(self):
+ """
+ Is this port empty?
+ An empty port has no connections.
+ @return true if empty
+ """
+ return not self.get_connections()
diff --git a/grc/src/platforms/base/__init__.py b/grc/src/platforms/base/__init__.py
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/grc/src/platforms/base/__init__.py
@@ -0,0 +1 @@
+