summaryrefslogtreecommitdiff
path: root/grc/core
diff options
context:
space:
mode:
Diffstat (limited to 'grc/core')
-rw-r--r--grc/core/Block.py871
-rw-r--r--grc/core/CMakeLists.txt46
-rw-r--r--grc/core/Connection.py164
-rw-r--r--grc/core/Constants.py168
-rw-r--r--grc/core/Element.py106
-rw-r--r--grc/core/FlowGraph.py595
-rw-r--r--grc/core/Param.py714
-rw-r--r--grc/core/ParseXML.py158
-rw-r--r--grc/core/Platform.py404
-rw-r--r--grc/core/Port.py404
-rw-r--r--grc/core/__init__.py1
-rw-r--r--grc/core/block.dtd69
-rw-r--r--grc/core/block_tree.dtd26
-rw-r--r--grc/core/default_flow_graph.grc43
-rw-r--r--grc/core/domain.dtd35
-rw-r--r--grc/core/epy_block_io.py95
-rw-r--r--grc/core/expr_utils.py196
-rw-r--r--grc/core/extract_docs.py293
-rw-r--r--grc/core/flow_graph.dtd38
-rw-r--r--grc/core/generator/Generator.py451
-rw-r--r--grc/core/generator/__init__.py1
-rw-r--r--grc/core/generator/flow_graph.tmpl439
-rw-r--r--grc/core/odict.py111
-rw-r--r--grc/core/utils/__init__.py6
-rw-r--r--grc/core/utils/complexity.py49
25 files changed, 5483 insertions, 0 deletions
diff --git a/grc/core/Block.py b/grc/core/Block.py
new file mode 100644
index 0000000000..8af3e98456
--- /dev/null
+++ b/grc/core/Block.py
@@ -0,0 +1,871 @@
+"""
+Copyright 2008-2015 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 collections
+import itertools
+
+from Cheetah.Template import Template
+from UserDict import UserDict
+
+from . Constants import (
+ BLOCK_FLAG_NEED_QT_GUI, BLOCK_FLAG_NEED_WX_GUI,
+ ADVANCED_PARAM_TAB, DEFAULT_PARAM_TAB,
+ BLOCK_FLAG_THROTTLE, BLOCK_FLAG_DISABLE_BYPASS,
+ BLOCK_ENABLED, BLOCK_BYPASSED, BLOCK_DISABLED
+)
+
+from . import epy_block_io
+from . odict import odict
+from . FlowGraph import _variable_matcher
+from . Element import Element
+
+
+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 __call__ or () method can return the param evaluated to a raw model 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 __call__(self):
+ return self._param.get_evaluated()
+
+
+def _get_keys(lst):
+ return [elem.get_key() for elem in lst]
+
+
+def _get_elem(lst, key):
+ try:
+ return lst[_get_keys(lst).index(key)]
+ except ValueError:
+ raise ValueError('Key "{}" not found in {}.'.format(key, _get_keys(lst)))
+
+
+class Block(Element):
+
+ is_block = True
+
+ def __init__(self, flow_graph, n):
+ """
+ Make a new block from nested data.
+
+ Args:
+ flow: graph the parent element
+ n: the nested odict
+
+ Returns:
+ block a new block
+ """
+ # Grab the data
+ self._doc = (n.find('doc') or '').strip('\n').replace('\\\n', '')
+ self._imports = map(lambda i: i.strip(), n.findall('import'))
+ self._make = n.find('make')
+ self._var_make = n.find('var_make')
+ self._checks = n.findall('check')
+ self._callbacks = n.findall('callback')
+ self._bus_structure_source = n.find('bus_structure_source') or ''
+ self._bus_structure_sink = n.find('bus_structure_sink') or ''
+ self.port_counters = [itertools.count(), itertools.count()]
+
+ # Build the block
+ Element.__init__(self, flow_graph)
+
+ # Grab the data
+ params = n.findall('param')
+ sources = n.findall('source')
+ sinks = n.findall('sink')
+ self._name = n.find('name')
+ self._key = n.find('key')
+ self._category = n.find('category') or ''
+ self._flags = n.find('flags') or ''
+ # Backwards compatibility
+ if n.find('throttle') and BLOCK_FLAG_THROTTLE not in self._flags:
+ self._flags += BLOCK_FLAG_THROTTLE
+ self._grc_source = n.find('grc_source') or ''
+ self._block_wrapper_path = n.find('block_wrapper_path')
+ self._bussify_sink = n.find('bus_sink')
+ self._bussify_source = n.find('bus_source')
+ self._var_value = n.find('var_value') or '$value'
+
+ # Get list of param tabs
+ n_tabs = n.find('param_tab_order') or None
+ self._param_tab_labels = n_tabs.findall('tab') if n_tabs is not None else [DEFAULT_PARAM_TAB]
+
+ # Create the param objects
+ self._params = list()
+
+ # Add the id param
+ self.get_params().append(self.get_parent().get_parent().Param(
+ block=self,
+ n=odict({
+ 'name': 'ID',
+ 'key': 'id',
+ 'type': 'id',
+ })
+ ))
+ self.get_params().append(self.get_parent().get_parent().Param(
+ block=self,
+ n=odict({
+ 'name': 'Enabled',
+ 'key': '_enabled',
+ 'type': 'raw',
+ 'value': 'True',
+ 'hide': 'all',
+ })
+ ))
+ for param in itertools.imap(lambda n: self.get_parent().get_parent().Param(block=self, n=n), params):
+ key = param.get_key()
+ # Test against repeated keys
+ if key in self.get_param_keys():
+ raise Exception('Key "{}" already exists in params'.format(key))
+ # Store the param
+ self.get_params().append(param)
+ # Create the source objects
+ self._sources = list()
+ for source in map(lambda n: self.get_parent().get_parent().Port(block=self, n=n, dir='source'), sources):
+ key = source.get_key()
+ # Test against repeated keys
+ if key in self.get_source_keys():
+ raise Exception('Key "{}" already exists in sources'.format(key))
+ # Store the port
+ self.get_sources().append(source)
+ self.back_ofthe_bus(self.get_sources())
+ # Create the sink objects
+ self._sinks = list()
+ for sink in map(lambda n: self.get_parent().get_parent().Port(block=self, n=n, dir='sink'), sinks):
+ key = sink.get_key()
+ # Test against repeated keys
+ if key in self.get_sink_keys():
+ raise Exception('Key "{}" already exists in sinks'.format(key))
+ # Store the port
+ self.get_sinks().append(sink)
+ self.back_ofthe_bus(self.get_sinks())
+ self.current_bus_structure = {'source': '', 'sink': ''}
+
+ # Virtual source/sink and pad source/sink blocks are
+ # indistinguishable from normal GR blocks. Make explicit
+ # checks for them here since they have no work function or
+ # buffers to manage.
+ is_virtual_or_pad = self._key in (
+ "virtual_source", "virtual_sink", "pad_source", "pad_sink")
+ is_variable = self._key.startswith('variable')
+
+ # Disable blocks that are virtual/pads or variables
+ if is_virtual_or_pad or is_variable:
+ self._flags += BLOCK_FLAG_DISABLE_BYPASS
+
+ if not (is_virtual_or_pad or is_variable or self._key == 'options'):
+ self.get_params().append(self.get_parent().get_parent().Param(
+ block=self,
+ n=odict({'name': 'Block Alias',
+ 'key': 'alias',
+ 'type': 'string',
+ 'hide': 'part',
+ 'tab': ADVANCED_PARAM_TAB
+ })
+ ))
+
+ if (len(sources) or len(sinks)) and not is_virtual_or_pad:
+ self.get_params().append(self.get_parent().get_parent().Param(
+ block=self,
+ n=odict({'name': 'Core Affinity',
+ 'key': 'affinity',
+ 'type': 'int_vector',
+ 'hide': 'part',
+ 'tab': ADVANCED_PARAM_TAB
+ })
+ ))
+ if len(sources) and not is_virtual_or_pad:
+ self.get_params().append(self.get_parent().get_parent().Param(
+ block=self,
+ n=odict({'name': 'Min Output Buffer',
+ 'key': 'minoutbuf',
+ 'type': 'int',
+ 'hide': 'part',
+ 'value': '0',
+ 'tab': ADVANCED_PARAM_TAB
+ })
+ ))
+ self.get_params().append(self.get_parent().get_parent().Param(
+ block=self,
+ n=odict({'name': 'Max Output Buffer',
+ 'key': 'maxoutbuf',
+ 'type': 'int',
+ 'hide': 'part',
+ 'value': '0',
+ 'tab': ADVANCED_PARAM_TAB
+ })
+ ))
+
+ self.get_params().append(self.get_parent().get_parent().Param(
+ block=self,
+ n=odict({'name': 'Comment',
+ 'key': 'comment',
+ 'type': '_multiline',
+ 'hide': 'part',
+ 'value': '',
+ 'tab': ADVANCED_PARAM_TAB
+ })
+ ))
+
+ self._epy_source_hash = -1 # for epy blocks
+ self._epy_reload_error = None
+
+ if self._bussify_sink:
+ self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
+ if self._bussify_source:
+ self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
+
+ def get_bus_structure(self, direction):
+ if direction == 'source':
+ bus_structure = self._bus_structure_source
+ else:
+ bus_structure = self._bus_structure_sink
+
+ bus_structure = self.resolve_dependencies(bus_structure)
+
+ if not bus_structure:
+ return '' # TODO: Don't like empty strings. should change this to None eventually
+
+ try:
+ clean_bus_structure = self.get_parent().evaluate(bus_structure)
+ return clean_bus_structure
+ except:
+ return ''
+
+ def validate(self):
+ """
+ Validate this block.
+ Call the base class validate.
+ Evaluate the checks: each check must evaluate to True.
+ """
+ Element.validate(self)
+ # Evaluate the checks
+ for check in self._checks:
+ check_res = self.resolve_dependencies(check)
+ try:
+ if not self.get_parent().evaluate(check_res):
+ self.add_error_message('Check "{}" failed.'.format(check))
+ except:
+ self.add_error_message('Check "{}" did not evaluate.'.format(check))
+
+ # For variables check the value (only if var_value is used
+ if _variable_matcher.match(self.get_key()) and self._var_value != '$value':
+ value = self._var_value
+ try:
+ value = self.get_var_value()
+ self.get_parent().evaluate(value)
+ except Exception as err:
+ self.add_error_message('Value "{}" cannot be evaluated:\n{}'.format(value, err))
+
+ # check if this is a GUI block and matches the selected generate option
+ current_generate_option = self.get_parent().get_option('generate_options')
+
+ def check_generate_mode(label, flag, valid_options):
+ block_requires_mode = (
+ flag in self.get_flags() or
+ self.get_name().upper().startswith(label)
+ )
+ if block_requires_mode and current_generate_option not in valid_options:
+ self.add_error_message("Can't generate this block in mode: {} ".format(
+ repr(current_generate_option)))
+
+ check_generate_mode('WX GUI', BLOCK_FLAG_NEED_WX_GUI, ('wx_gui',))
+ check_generate_mode('QT GUI', BLOCK_FLAG_NEED_QT_GUI, ('qt_gui', 'hb_qt_gui'))
+ if self._epy_reload_error:
+ self.get_param('_source_code').add_error_message(str(self._epy_reload_error))
+
+ def rewrite(self):
+ """
+ Add and remove ports to adjust for the nports.
+ """
+ Element.rewrite(self)
+ # Check and run any custom rewrite function for this block
+ getattr(self, 'rewrite_' + self._key, lambda: None)()
+
+ # Adjust nports, disconnect hidden ports
+ for ports in (self.get_sources(), self.get_sinks()):
+ for i, master_port in enumerate(ports):
+ nports = master_port.get_nports() or 1
+ num_ports = 1 + len(master_port.get_clones())
+ if master_port.get_hide():
+ for connection in master_port.get_connections():
+ self.get_parent().remove_element(connection)
+ if not nports and num_ports == 1: # Not a master port and no left-over clones
+ continue
+ # Remove excess cloned ports
+ for port in master_port.get_clones()[nports-1:]:
+ # Remove excess connections
+ for connection in port.get_connections():
+ self.get_parent().remove_element(connection)
+ master_port.remove_clone(port)
+ ports.remove(port)
+ # Add more cloned ports
+ for j in range(num_ports, nports):
+ port = master_port.add_clone()
+ ports.insert(ports.index(master_port) + j, port)
+
+ self.back_ofthe_bus(ports)
+ # Renumber non-message/message ports
+ domain_specific_port_index = collections.defaultdict(int)
+ for port in filter(lambda p: p.get_key().isdigit(), ports):
+ domain = port.get_domain()
+ port._key = str(domain_specific_port_index[domain])
+ domain_specific_port_index[domain] += 1
+
+ def port_controller_modify(self, direction):
+ """
+ Change the port controller.
+
+ Args:
+ direction: +1 or -1
+
+ Returns:
+ true for change
+ """
+ changed = False
+ # Concat the nports string from the private nports settings of all ports
+ nports_str = ' '.join([port._nports for port in self.get_ports()])
+ # Modify all params whose keys appear in the nports string
+ for param in self.get_params():
+ if param.is_enum() or param.get_key() not in nports_str:
+ continue
+ # Try to increment the port controller by direction
+ try:
+ value = param.get_evaluated()
+ value = value + direction
+ if 0 < value:
+ param.set_value(value)
+ changed = True
+ except:
+ pass
+ return changed
+
+ def get_doc(self):
+ platform = self.get_parent().get_parent()
+ documentation = platform.block_docstrings.get(self._key, {})
+ from_xml = self._doc.strip()
+ if from_xml:
+ documentation[''] = from_xml
+ return documentation
+
+ def get_imports(self, raw=False):
+ """
+ Resolve all import statements.
+ Split each import statement at newlines.
+ Combine all import statments into a list.
+ Filter empty imports.
+
+ Returns:
+ a list of import statements
+ """
+ if raw:
+ return self._imports
+ return filter(lambda i: i, sum(map(lambda i: self.resolve_dependencies(i).split('\n'), self._imports), []))
+
+ def get_make(self, raw=False):
+ if raw:
+ return self._make
+ return self.resolve_dependencies(self._make)
+
+ def get_var_make(self):
+ return self.resolve_dependencies(self._var_make)
+
+ def get_var_value(self):
+ return self.resolve_dependencies(self._var_value)
+
+ def get_callbacks(self):
+ """
+ Get a list of function callbacks for this block.
+
+ Returns:
+ a list of strings
+ """
+ def make_callback(callback):
+ callback = self.resolve_dependencies(callback)
+ if 'self.' in callback:
+ return callback
+ return 'self.{}.{}'.format(self.get_id(), callback)
+ return map(make_callback, self._callbacks)
+
+ def is_virtual_sink(self):
+ return self.get_key() == 'virtual_sink'
+
+ def is_virtual_source(self):
+ return self.get_key() == 'virtual_source'
+
+ ###########################################################################
+ # Custom rewrite functions
+ ###########################################################################
+
+ def rewrite_epy_block(self):
+ flowgraph = self.get_parent()
+ platform = flowgraph.get_parent()
+ param_blk = self.get_param('_io_cache')
+ param_src = self.get_param('_source_code')
+
+ src = param_src.get_value()
+ src_hash = hash(src)
+ if src_hash == self._epy_source_hash:
+ return
+
+ try:
+ blk_io = epy_block_io.extract(src)
+
+ except Exception as e:
+ self._epy_reload_error = ValueError(str(e))
+ try: # Load last working block io
+ blk_io = epy_block_io.BlockIO(*eval(param_blk.get_value()))
+ except:
+ return
+ else:
+ self._epy_reload_error = None # Clear previous errors
+ param_blk.set_value(repr(tuple(blk_io)))
+
+ # print "Rewriting embedded python block {!r}".format(self.get_id())
+
+ self._epy_source_hash = src_hash
+ self._name = blk_io.name or blk_io.cls
+ self._doc = blk_io.doc
+ self._imports[0] = 'from {} import {}'.format(self.get_id(), blk_io.cls)
+ self._make = '{}({})'.format(blk_io.cls, ', '.join(
+ '{0}=${0}'.format(key) for key, _ in blk_io.params))
+
+ params = {}
+ for param in list(self._params):
+ if hasattr(param, '__epy_param__'):
+ params[param.get_key()] = param
+ self._params.remove(param)
+
+ for key, value in blk_io.params:
+ if key in params:
+ param = params[key]
+ if not param.value_is_default():
+ param.set_value(value)
+ else:
+ name = key.replace('_', ' ').title()
+ n = odict(dict(name=name, key=key, type='raw', value=value))
+ param = platform.Param(block=self, n=n)
+ setattr(param, '__epy_param__', True)
+ self._params.append(param)
+
+ def update_ports(label, ports, port_specs, direction):
+ ports_to_remove = list(ports)
+ iter_ports = iter(ports)
+ ports_new = []
+ port_current = next(iter_ports, None)
+ for key, port_type in port_specs:
+ reuse_port = (
+ port_current is not None and
+ port_current.get_type() == port_type and
+ (key.isdigit() or port_current.get_key() == key)
+ )
+ if reuse_port:
+ ports_to_remove.remove(port_current)
+ port, port_current = port_current, next(iter_ports, None)
+ else:
+ n = odict(dict(name=label + str(key), type=port_type, key=key))
+ if port_type == 'message':
+ n['name'] = key
+ n['optional'] = '1'
+ port = platform.Port(block=self, n=n, dir=direction)
+ ports_new.append(port)
+ # replace old port list with new one
+ del ports[:]
+ ports.extend(ports_new)
+ # remove excess port connections
+ for port in ports_to_remove:
+ for connection in port.get_connections():
+ flowgraph.remove_element(connection)
+
+ update_ports('in', self.get_sinks(), blk_io.sinks, 'sink')
+ update_ports('out', self.get_sources(), blk_io.sources, 'source')
+ self.rewrite()
+
+ def back_ofthe_bus(self, portlist):
+ portlist.sort(key=lambda p: p._type == 'bus')
+
+ def filter_bus_port(self, ports):
+ buslist = [p for p in ports if p._type == 'bus']
+ return buslist or ports
+
+ # Main functions to get and set the block state
+ # Also kept get_enabled and set_enabled to keep compatibility
+ def get_state(self):
+ """
+ Gets the block's current state.
+
+ Returns:
+ ENABLED - 0
+ BYPASSED - 1
+ DISABLED - 2
+ """
+ try:
+ return int(eval(self.get_param('_enabled').get_value()))
+ except:
+ return BLOCK_ENABLED
+
+ def set_state(self, state):
+ """
+ Sets the state for the block.
+
+ Args:
+ ENABLED - 0
+ BYPASSED - 1
+ DISABLED - 2
+ """
+ if state in [BLOCK_ENABLED, BLOCK_BYPASSED, BLOCK_DISABLED]:
+ self.get_param('_enabled').set_value(str(state))
+ else:
+ self.get_param('_enabled').set_value(str(BLOCK_ENABLED))
+
+ # Enable/Disable Aliases
+ def get_enabled(self):
+ """
+ Get the enabled state of the block.
+
+ Returns:
+ true for enabled
+ """
+ return not (self.get_state() == BLOCK_DISABLED)
+
+ def set_enabled(self, enabled):
+ """
+ Set the enabled state of the block.
+
+ Args:
+ enabled: true for enabled
+
+ Returns:
+ True if block changed state
+ """
+ old_state = self.get_state()
+ new_state = BLOCK_ENABLED if enabled else BLOCK_DISABLED
+ self.set_state(new_state)
+ return old_state != new_state
+
+ # Block bypassing
+ def get_bypassed(self):
+ """
+ Check if the block is bypassed
+ """
+ return self.get_state() == BLOCK_BYPASSED
+
+ def set_bypassed(self):
+ """
+ Bypass the block
+
+ Returns:
+ True if block chagnes state
+ """
+ if self.get_state() != BLOCK_BYPASSED and self.can_bypass():
+ self.set_state(BLOCK_BYPASSED)
+ return True
+ return False
+
+ def can_bypass(self):
+ """ Check the number of sinks and sources and see if this block can be bypassed """
+ # Check to make sure this is a single path block
+ # Could possibly support 1 to many blocks
+ if len(self.get_sources()) != 1 or len(self.get_sinks()) != 1:
+ return False
+ if not (self.get_sources()[0].get_type() == self.get_sinks()[0].get_type()):
+ return False
+ if self.bypass_disabled():
+ return False
+ return True
+
+ def __str__(self):
+ return 'Block - {} - {}({})'.format(self.get_id(), self.get_name(), self.get_key())
+
+ def get_id(self):
+ return self.get_param('id').get_value()
+
+ def get_name(self):
+ return self._name
+
+ def get_key(self):
+ return self._key
+
+ def get_category(self):
+ return self._category
+
+ def set_category(self, cat):
+ self._category = cat
+
+ def get_ports(self):
+ return self.get_sources() + self.get_sinks()
+
+ def get_ports_gui(self):
+ return self.filter_bus_port(self.get_sources()) + self.filter_bus_port(self.get_sinks())
+
+ def get_children(self):
+ return self.get_ports() + self.get_params()
+
+ def get_children_gui(self):
+ return self.get_ports_gui() + self.get_params()
+
+ def get_block_wrapper_path(self):
+ return self._block_wrapper_path
+
+ def get_comment(self):
+ return self.get_param('comment').get_value()
+
+ def get_flags(self):
+ return self._flags
+
+ def throtteling(self):
+ return BLOCK_FLAG_THROTTLE in self._flags
+
+ def bypass_disabled(self):
+ return BLOCK_FLAG_DISABLE_BYPASS in self._flags
+
+ ##############################################
+ # Access Params
+ ##############################################
+ def get_param_tab_labels(self):
+ return self._param_tab_labels
+
+ def get_param_keys(self):
+ return _get_keys(self._params)
+
+ def get_param(self, key):
+ return _get_elem(self._params, key)
+
+ def get_params(self):
+ return self._params
+
+ def has_param(self, key):
+ try:
+ _get_elem(self._params, key)
+ return True
+ except:
+ return False
+
+ ##############################################
+ # Access Sinks
+ ##############################################
+ def get_sink_keys(self):
+ return _get_keys(self._sinks)
+
+ def get_sink(self, key):
+ return _get_elem(self._sinks, key)
+
+ def get_sinks(self):
+ return self._sinks
+
+ def get_sinks_gui(self):
+ return self.filter_bus_port(self.get_sinks())
+
+ ##############################################
+ # Access Sources
+ ##############################################
+ def get_source_keys(self):
+ return _get_keys(self._sources)
+
+ def get_source(self, key):
+ return _get_elem(self._sources, key)
+
+ def get_sources(self):
+ return self._sources
+
+ def get_sources_gui(self):
+ return self.filter_bus_port(self.get_sources())
+
+ 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.
+
+ Args:
+ tmpl: the string with dependencies
+
+ Returns:
+ 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 as err:
+ return "Template error: {}\n {}".format(tmpl, err)
+
+ ##############################################
+ # Controller Modify
+ ##############################################
+ def type_controller_modify(self, direction):
+ """
+ Change the type controller.
+
+ Args:
+ direction: +1 or -1
+
+ Returns:
+ true for change
+ """
+ changed = False
+ type_param = None
+ for param in filter(lambda p: p.is_enum(), self.get_params()):
+ children = self.get_ports() + self.get_params()
+ # Priority to the type controller
+ if param.get_key() in ' '.join(map(lambda p: p._type, children)): type_param = param
+ # Use param if type param is unset
+ if not type_param:
+ type_param = param
+ if type_param:
+ # Try to increment the enum by direction
+ try:
+ keys = type_param.get_option_keys()
+ old_index = keys.index(type_param.get_value())
+ new_index = (old_index + direction + len(keys)) % len(keys)
+ type_param.set_value(keys[new_index])
+ changed = True
+ except:
+ pass
+ return changed
+
+ def form_bus_structure(self, direc):
+ if direc == 'source':
+ get_p = self.get_sources
+ get_p_gui = self.get_sources_gui
+ bus_structure = self.get_bus_structure('source')
+ else:
+ get_p = self.get_sinks
+ get_p_gui = self.get_sinks_gui
+ bus_structure = self.get_bus_structure('sink')
+
+ struct = [range(len(get_p()))]
+ if True in map(lambda a: isinstance(a.get_nports(), int), get_p()):
+ structlet = []
+ last = 0
+ for j in [i.get_nports() for i in get_p() if isinstance(i.get_nports(), int)]:
+ structlet.extend(map(lambda a: a+last, range(j)))
+ last = structlet[-1] + 1
+ struct = [structlet]
+ if bus_structure:
+
+ struct = bus_structure
+
+ self.current_bus_structure[direc] = struct
+ return struct
+
+ def bussify(self, n, direc):
+ if direc == 'source':
+ get_p = self.get_sources
+ get_p_gui = self.get_sources_gui
+ bus_structure = self.get_bus_structure('source')
+ else:
+ get_p = self.get_sinks
+ get_p_gui = self.get_sinks_gui
+ bus_structure = self.get_bus_structure('sink')
+
+ for elt in get_p():
+ for connect in elt.get_connections():
+ self.get_parent().remove_element(connect)
+
+ if ('bus' not in map(lambda a: a.get_type(), get_p())) and len(get_p()) > 0:
+ struct = self.form_bus_structure(direc)
+ self.current_bus_structure[direc] = struct
+ if get_p()[0].get_nports():
+ n['nports'] = str(1)
+
+ for i in range(len(struct)):
+ n['key'] = str(len(get_p()))
+ n = odict(n)
+ port = self.get_parent().get_parent().Port(block=self, n=n, dir=direc)
+ get_p().append(port)
+ elif 'bus' in map(lambda a: a.get_type(), get_p()):
+ for elt in get_p_gui():
+ get_p().remove(elt)
+ self.current_bus_structure[direc] = ''
+
+ ##############################################
+ # Import/Export Methods
+ ##############################################
+ def export_data(self):
+ """
+ Export this block's params to nested data.
+
+ Returns:
+ a nested data odict
+ """
+ n = odict()
+ n['key'] = self.get_key()
+ n['param'] = map(lambda p: p.export_data(), sorted(self.get_params(), key=str))
+ if 'bus' in map(lambda a: a.get_type(), self.get_sinks()):
+ n['bus_sink'] = str(1)
+ if 'bus' in map(lambda a: a.get_type(), self.get_sources()):
+ n['bus_source'] = str(1)
+ return n
+
+ def get_hash(self):
+ return hash(tuple(map(hash, self.get_params())))
+
+ def import_data(self, n):
+ """
+ Import this block's params from nested data.
+ Any param keys that do not exist will be ignored.
+ Since params can be dynamically created based another param,
+ call rewrite, and repeat the load until the params stick.
+ This call to rewrite will also create any dynamic ports
+ that are needed for the connections creation phase.
+
+ Args:
+ n: the nested data odict
+ """
+ my_hash = 0
+ while self.get_hash() != my_hash:
+ params_n = n.findall('param')
+ for param_n in params_n:
+ key = param_n.find('key')
+ value = param_n.find('value')
+ # The key must exist in this block's params
+ if key in self.get_param_keys():
+ self.get_param(key).set_value(value)
+ # Store hash and call rewrite
+ my_hash = self.get_hash()
+ self.rewrite()
+ bussinks = n.findall('bus_sink')
+ if len(bussinks) > 0 and not self._bussify_sink:
+ self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
+ elif len(bussinks) > 0:
+ self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
+ self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
+ bussrcs = n.findall('bus_source')
+ if len(bussrcs) > 0 and not self._bussify_source:
+ self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
+ elif len(bussrcs) > 0:
+ self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
+ self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
diff --git a/grc/core/CMakeLists.txt b/grc/core/CMakeLists.txt
new file mode 100644
index 0000000000..123bad2674
--- /dev/null
+++ b/grc/core/CMakeLists.txt
@@ -0,0 +1,46 @@
+# Copyright 2011 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.
+
+########################################################################
+GR_PYTHON_INSTALL(FILES
+ expr_utils.py
+ extract_docs.py
+ epy_block_io.py
+ Block.py
+ Connection.py
+ Constants.py
+ FlowGraph.py
+ Generator.py
+ Param.py
+ Platform.py
+ Port.py
+ __init__.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/python
+ COMPONENT "grc"
+)
+
+install(FILES
+ block.dtd
+ default_flow_graph.grc
+ flow_graph.tmpl
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/python
+ COMPONENT "grc"
+)
+
+add_subdirectory(base)
diff --git a/grc/core/Connection.py b/grc/core/Connection.py
new file mode 100644
index 0000000000..a7b428dfe6
--- /dev/null
+++ b/grc/core/Connection.py
@@ -0,0 +1,164 @@
+"""
+Copyright 2008-2015 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 Constants
+
+from .Element import Element
+from .odict import odict
+
+
+class Connection(Element):
+
+ is_connection = True
+
+ def __init__(self, flow_graph, porta, portb):
+ """
+ Make a new connection given the parent and 2 ports.
+
+ Args:
+ flow_graph: the parent of this element
+ porta: a port (any direction)
+ portb: a port (any direction)
+ @throws Error cannot make connection
+
+ Returns:
+ 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
+ else:
+ sink = port
+ if not source:
+ raise ValueError('Connection could not isolate source')
+ if not sink:
+ raise ValueError('Connection could not isolate sink')
+ busses = len(filter(lambda a: a.get_type() == 'bus', [source, sink])) % 2
+ if not busses == 0:
+ raise ValueError('busses must get with busses')
+
+ if not len(source.get_associated_ports()) == len(sink.get_associated_ports()):
+ raise ValueError('port connections must have same cardinality')
+ # Ensure that this connection (source -> sink) is unique
+ for connection in flow_graph.connections:
+ if connection.get_source() is source and connection.get_sink() is sink:
+ raise LookupError('This connection between source and sink is not unique.')
+ self._source = source
+ self._sink = sink
+ if source.get_type() == 'bus':
+
+ sources = source.get_associated_ports()
+ sinks = sink.get_associated_ports()
+
+ for i in range(len(sources)):
+ try:
+ flow_graph.connect(sources[i], sinks[i])
+ except:
+ pass
+
+ def __str__(self):
+ return 'Connection (\n\t{}\n\t\t{}\n\t{}\n\t\t{}\n)'.format(
+ self.get_source().get_parent(),
+ self.get_source(),
+ self.get_sink().get_parent(),
+ self.get_sink(),
+ )
+
+ def is_msg(self):
+ return self.get_source().get_type() == self.get_sink().get_type() == 'msg'
+
+ def is_bus(self):
+ return self.get_source().get_type() == self.get_sink().get_type() == 'bus'
+
+ def validate(self):
+ """
+ Validate the connections.
+ The ports must match in io size.
+ """
+ """
+ Validate the connections.
+ The ports must match in type.
+ """
+ Element.validate(self)
+ platform = self.get_parent().get_parent()
+ source_domain = self.get_source().get_domain()
+ sink_domain = self.get_sink().get_domain()
+ if (source_domain, sink_domain) not in platform.get_connection_templates():
+ self.add_error_message('No connection known for domains "{}", "{}"'.format(
+ source_domain, sink_domain))
+ too_many_other_sinks = (
+ source_domain in platform.get_domains() and
+ not platform.get_domain(key=source_domain)['multiple_sinks'] and
+ len(self.get_source().get_enabled_connections()) > 1
+ )
+ too_many_other_sources = (
+ sink_domain in platform.get_domains() and
+ not platform.get_domain(key=sink_domain)['multiple_sources'] and
+ len(self.get_sink().get_enabled_connections()) > 1
+ )
+ if too_many_other_sinks:
+ self.add_error_message(
+ 'Domain "{}" can have only one downstream block'.format(source_domain))
+ if too_many_other_sources:
+ self.add_error_message(
+ 'Domain "{}" can have only one upstream block'.format(sink_domain))
+
+ source_size = Constants.TYPE_TO_SIZEOF[self.get_source().get_type()] * self.get_source().get_vlen()
+ sink_size = Constants.TYPE_TO_SIZEOF[self.get_sink().get_type()] * self.get_sink().get_vlen()
+ if source_size != sink_size:
+ self.add_error_message('Source IO size "{}" does not match sink IO size "{}".'.format(source_size, sink_size))
+
+ def get_enabled(self):
+ """
+ Get the enabled state of this connection.
+
+ Returns:
+ 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.
+
+ Returns:
+ 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/core/Constants.py b/grc/core/Constants.py
new file mode 100644
index 0000000000..f1dae1d953
--- /dev/null
+++ b/grc/core/Constants.py
@@ -0,0 +1,168 @@
+"""
+Copyright 2008-2015 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 os.path import expanduser
+import numpy
+import stat
+from gnuradio import gr
+
+_gr_prefs = gr.prefs()
+
+# Setup paths
+PATH_SEP = {'/': ':', '\\': ';'}[os.path.sep]
+
+HIER_BLOCKS_LIB_DIR = os.environ.get('GRC_HIER_PATH', expanduser('~/.grc_gnuradio'))
+
+PREFS_FILE = os.environ.get('GRC_PREFS_PATH', expanduser('~/.gnuradio/grc.conf'))
+PREFS_FILE_OLD = os.environ.get('GRC_PREFS_PATH', expanduser('~/.grc'))
+
+BLOCKS_DIRS = filter( # filter blank strings
+ lambda x: x, PATH_SEP.join([
+ os.environ.get('GRC_BLOCKS_PATH', ''),
+ _gr_prefs.get_string('grc', 'local_blocks_path', ''),
+ _gr_prefs.get_string('grc', 'global_blocks_path', ''),
+ ]).split(PATH_SEP),
+) + [HIER_BLOCKS_LIB_DIR]
+
+# Data files
+DATA_DIR = os.path.dirname(__file__)
+FLOW_GRAPH_DTD = os.path.join(DATA_DIR, 'flow_graph.dtd')
+BLOCK_TREE_DTD = os.path.join(DATA_DIR, 'block_tree.dtd')
+BLOCK_DTD = os.path.join(DATA_DIR, 'block.dtd')
+DEFAULT_FLOW_GRAPH = os.path.join(DATA_DIR, 'default_flow_graph.grc')
+
+# File format versions:
+# 0: undefined / legacy
+# 1: non-numeric message port keys (label is used instead)
+FLOW_GRAPH_FILE_FORMAT_VERSION = 1
+
+# Param tabs
+DEFAULT_PARAM_TAB = "General"
+ADVANCED_PARAM_TAB = "Advanced"
+
+# Port domains
+DOMAIN_DTD = os.path.join(DATA_DIR, 'domain.dtd')
+GR_STREAM_DOMAIN = "gr_stream"
+GR_MESSAGE_DOMAIN = "gr_message"
+DEFAULT_DOMAIN = GR_STREAM_DOMAIN
+
+BLOCK_FLAG_THROTTLE = 'throttle'
+BLOCK_FLAG_DISABLE_BYPASS = 'disable_bypass'
+BLOCK_FLAG_NEED_QT_GUI = 'need_qt_gui'
+BLOCK_FLAG_NEED_WX_GUI = 'need_ex_gui'
+
+# Block States
+BLOCK_DISABLED = 0
+BLOCK_ENABLED = 1
+BLOCK_BYPASSED = 2
+
+# User settings
+XTERM_EXECUTABLE = _gr_prefs.get_string('grc', 'xterm_executable', 'xterm')
+
+# File creation modes
+TOP_BLOCK_FILE_MODE = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | \
+ stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH
+HIER_BLOCK_FILE_MODE = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH
+
+# Define types, native python + numpy
+VECTOR_TYPES = (tuple, list, set, numpy.ndarray)
+COMPLEX_TYPES = [complex, numpy.complex, numpy.complex64, numpy.complex128]
+REAL_TYPES = [float, numpy.float, numpy.float32, numpy.float64]
+INT_TYPES = [int, long, numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.uint64,
+ numpy.uint, numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64]
+# Cast to tuple for isinstance, concat subtypes
+COMPLEX_TYPES = tuple(COMPLEX_TYPES + REAL_TYPES + INT_TYPES)
+REAL_TYPES = tuple(REAL_TYPES + INT_TYPES)
+INT_TYPES = tuple(INT_TYPES)
+
+# Updating colors. Using the standard color pallette from:
+# http://www.google.com/design/spec/style/color.html#color-color-palette
+# Most are based on the main, primary color standard. Some are within
+# that color's spectrum when it was deemed necessary.
+GRC_COLOR_BROWN = '#795548'
+GRC_COLOR_BLUE = '#2196F3'
+GRC_COLOR_LIGHT_GREEN = '#8BC34A'
+GRC_COLOR_GREEN = '#4CAF50'
+GRC_COLOR_AMBER = '#FFC107'
+GRC_COLOR_PURPLE = '#9C27B0'
+GRC_COLOR_CYAN = '#00BCD4'
+GRC_COLOR_GR_ORANGE = '#FF6905'
+GRC_COLOR_ORANGE = '#F57C00'
+GRC_COLOR_LIME = '#CDDC39'
+GRC_COLOR_TEAL = '#009688'
+GRC_COLOR_YELLOW = '#FFEB3B'
+GRC_COLOR_PINK = '#F50057'
+GRC_COLOR_LIGHT_PURPLE = '#E040FB'
+GRC_COLOR_DARK_GREY = '#72706F'
+GRC_COLOR_GREY = '#BDBDBD'
+GRC_COLOR_WHITE = '#FFFFFF'
+
+CORE_TYPES = ( # name, key, sizeof, color
+ ('Complex Float 64', 'fc64', 16, GRC_COLOR_BROWN),
+ ('Complex Float 32', 'fc32', 8, GRC_COLOR_BLUE),
+ ('Complex Integer 64', 'sc64', 16, GRC_COLOR_LIGHT_GREEN),
+ ('Complex Integer 32', 'sc32', 8, GRC_COLOR_GREEN),
+ ('Complex Integer 16', 'sc16', 4, GRC_COLOR_AMBER),
+ ('Complex Integer 8', 'sc8', 2, GRC_COLOR_PURPLE),
+ ('Float 64', 'f64', 8, GRC_COLOR_CYAN),
+ ('Float 32', 'f32', 4, GRC_COLOR_ORANGE),
+ ('Integer 64', 's64', 8, GRC_COLOR_LIME),
+ ('Integer 32', 's32', 4, GRC_COLOR_TEAL),
+ ('Integer 16', 's16', 2, GRC_COLOR_YELLOW),
+ ('Integer 8', 's8', 1, GRC_COLOR_LIGHT_PURPLE),
+ ('Message Queue', 'msg', 0, GRC_COLOR_DARK_GREY),
+ ('Async Message', 'message', 0, GRC_COLOR_GREY),
+ ('Bus Connection', 'bus', 0, GRC_COLOR_WHITE),
+ ('Wildcard', '', 0, GRC_COLOR_WHITE),
+)
+
+ALIAS_TYPES = {
+ 'complex': (8, GRC_COLOR_BLUE),
+ 'float': (4, GRC_COLOR_ORANGE),
+ 'int': (4, GRC_COLOR_TEAL),
+ 'short': (2, GRC_COLOR_YELLOW),
+ 'byte': (1, GRC_COLOR_LIGHT_PURPLE),
+}
+
+TYPE_TO_COLOR = dict()
+TYPE_TO_SIZEOF = dict()
+
+for name, key, sizeof, color in CORE_TYPES:
+ TYPE_TO_COLOR[key] = color
+ TYPE_TO_SIZEOF[key] = sizeof
+
+for key, (sizeof, color) in ALIAS_TYPES.iteritems():
+ TYPE_TO_COLOR[key] = color
+ TYPE_TO_SIZEOF[key] = sizeof
+
+# Coloring
+COMPLEX_COLOR_SPEC = '#3399FF'
+FLOAT_COLOR_SPEC = '#FF8C69'
+INT_COLOR_SPEC = '#00FF99'
+SHORT_COLOR_SPEC = '#FFFF66'
+BYTE_COLOR_SPEC = '#FF66FF'
+COMPLEX_VECTOR_COLOR_SPEC = '#3399AA'
+FLOAT_VECTOR_COLOR_SPEC = '#CC8C69'
+INT_VECTOR_COLOR_SPEC = '#00CC99'
+SHORT_VECTOR_COLOR_SPEC = '#CCCC33'
+BYTE_VECTOR_COLOR_SPEC = '#CC66CC'
+ID_COLOR_SPEC = '#DDDDDD'
+WILDCARD_COLOR_SPEC = '#FFFFFF'
+MSG_COLOR_SPEC = '#777777'
diff --git a/grc/core/Element.py b/grc/core/Element.py
new file mode 100644
index 0000000000..c999d6704f
--- /dev/null
+++ b/grc/core/Element.py
@@ -0,0 +1,106 @@
+"""
+Copyright 2008, 2009, 2015 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 = list()
+
+ ##################################################
+ # Element Validation API
+ ##################################################
+ def validate(self):
+ """
+ Validate this element and call validate on all children.
+ Call this base method before adding error messages in the subclass.
+ """
+ del self._error_messages[:]
+ for child in self.get_children():
+ child.validate()
+
+ def is_valid(self):
+ """
+ Is this element valid?
+
+ Returns:
+ true when the element is enabled and has no error messages
+ """
+ return not self.get_error_messages() or not self.get_enabled()
+
+ def add_error_message(self, msg):
+ """
+ Add an error message to the list of errors.
+
+ Args:
+ msg: the error message string
+ """
+ self._error_messages.append(msg)
+
+ def get_error_messages(self):
+ """
+ Get the list of error messages from this element and all of its children.
+ Do not include the error messages from disabled children.
+ Cleverly indent the children error messages for printing purposes.
+
+ Returns:
+ a list of error message strings
+ """
+ error_messages = list(self._error_messages) # Make a copy
+ for child in filter(lambda c: c.get_enabled(), self.get_children()):
+ for msg in child.get_error_messages():
+ error_messages.append("{}:\n\t{}".format(child, msg.replace("\n", "\n\t")))
+ return error_messages
+
+ def rewrite(self):
+ """
+ Rewrite this element and call rewrite on all children.
+ Call this base method before rewriting the element.
+ """
+ for child in self.get_children():
+ child.rewrite()
+
+ def get_enabled(self):
+ return True
+
+ ##############################################
+ # Tree-like API
+ ##############################################
+ def get_parent(self):
+ return self._parent
+
+ def get_children(self):
+ return list()
+
+ ##############################################
+ # Type testing
+ ##############################################
+ is_platform = False
+
+ is_flow_graph = False
+
+ is_block = False
+ is_dummy_block = False
+
+ is_connection = False
+
+ is_port = False
+
+ is_param = False
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
new file mode 100644
index 0000000000..fd391c6b32
--- /dev/null
+++ b/grc/core/FlowGraph.py
@@ -0,0 +1,595 @@
+# Copyright 2008-2015 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 re
+import imp
+import time
+from operator import methodcaller
+from itertools import ifilter, chain
+
+from ..gui import Messages
+
+from . import expr_utils
+from .odict import odict
+from .Element import Element
+from .Constants import FLOW_GRAPH_FILE_FORMAT_VERSION
+
+_variable_matcher = re.compile('^(variable\w*)$')
+_parameter_matcher = re.compile('^(parameter)$')
+_monitors_searcher = re.compile('(ctrlport_monitor)')
+_bussink_searcher = re.compile('^(bus_sink)$')
+_bussrc_searcher = re.compile('^(bus_source)$')
+_bus_struct_sink_searcher = re.compile('^(bus_structure_sink)$')
+_bus_struct_src_searcher = re.compile('^(bus_structure_source)$')
+
+
+class FlowGraph(Element):
+
+ is_flow_graph = True
+
+ def __init__(self, platform):
+ """
+ Make a flow graph from the arguments.
+
+ Args:
+ platform: a platforms with blocks and contrcutors
+
+ Returns:
+ the flow graph object
+ """
+ Element.__init__(self, platform)
+ self._elements = []
+ self._timestamp = time.ctime()
+
+ self.platform = platform # todo: make this a lazy prop
+ self.blocks = []
+ self.connections = []
+
+ self._eval_cache = {}
+ self.namespace = {}
+
+ self.grc_file_path = ''
+
+ self.import_data()
+
+ def __str__(self):
+ return 'FlowGraph - {}({})'.format(self.get_option('title'), self.get_option('id'))
+
+ ##############################################
+ # TODO: Move these to new generator package
+ ##############################################
+ def get_imports(self):
+ """
+ Get a set of all import statments in this flow graph namespace.
+
+ Returns:
+ a set of import statements
+ """
+ imports = sum([block.get_imports() for block in self.get_enabled_blocks()], [])
+ imports = sorted(set(imports))
+ return imports
+
+ def get_variables(self):
+ """
+ Get a list of all variables in this flow graph namespace.
+ Exclude paramterized variables.
+
+ Returns:
+ a sorted list of variable blocks in order of dependency (indep -> dep)
+ """
+ variables = filter(lambda b: _variable_matcher.match(b.get_key()), self.iter_enabled_blocks())
+ return expr_utils.sort_objects(variables, methodcaller('get_id'), methodcaller('get_var_make'))
+
+ def get_parameters(self):
+ """
+ Get a list of all paramterized variables in this flow graph namespace.
+
+ Returns:
+ a list of paramterized variables
+ """
+ parameters = filter(lambda b: _parameter_matcher.match(b.get_key()), self.iter_enabled_blocks())
+ return parameters
+
+ def get_monitors(self):
+ """
+ Get a list of all ControlPort monitors
+ """
+ monitors = filter(lambda b: _monitors_searcher.search(b.get_key()),
+ self.iter_enabled_blocks())
+ return monitors
+
+ def get_python_modules(self):
+ """Iterate over custom code block ID and Source"""
+ for block in self.iter_enabled_blocks():
+ if block.get_key() == 'epy_module':
+ yield block.get_id(), block.get_param('source_code').get_value()
+
+ def get_bussink(self):
+ bussink = filter(lambda b: _bussink_searcher.search(b.get_key()), self.get_enabled_blocks())
+
+ for i in bussink:
+ for j in i.get_params():
+ if j.get_name() == 'On/Off' and j.get_value() == 'on':
+ return True
+ return False
+
+ def get_bussrc(self):
+ bussrc = filter(lambda b: _bussrc_searcher.search(b.get_key()), self.get_enabled_blocks())
+
+ for i in bussrc:
+ for j in i.get_params():
+ if j.get_name() == 'On/Off' and j.get_value() == 'on':
+ return True
+ return False
+
+ def get_bus_structure_sink(self):
+ bussink = filter(lambda b: _bus_struct_sink_searcher.search(b.get_key()), self.get_enabled_blocks())
+ return bussink
+
+ def get_bus_structure_src(self):
+ bussrc = filter(lambda b: _bus_struct_src_searcher.search(b.get_key()), self.get_enabled_blocks())
+ return bussrc
+
+ def iter_enabled_blocks(self):
+ """
+ Get an iterator of all blocks that are enabled and not bypassed.
+ """
+ return ifilter(methodcaller('get_enabled'), self.blocks)
+
+ def get_enabled_blocks(self):
+ """
+ Get a list of all blocks that are enabled and not bypassed.
+
+ Returns:
+ a list of blocks
+ """
+ return list(self.iter_enabled_blocks())
+
+ def get_bypassed_blocks(self):
+ """
+ Get a list of all blocks that are bypassed.
+
+ Returns:
+ a list of blocks
+ """
+ return filter(methodcaller('get_bypassed'), self.blocks)
+
+ def get_enabled_connections(self):
+ """
+ Get a list of all connections that are enabled.
+
+ Returns:
+ a list of connections
+ """
+ return filter(methodcaller('get_enabled'), self.connections)
+
+ def get_option(self, key):
+ """
+ Get the option for a given key.
+ The option comes from the special options block.
+
+ Args:
+ key: the param key for the options block
+
+ Returns:
+ the value held by that param
+ """
+ return self._options_block.get_param(key).get_evaluated()
+
+ ##############################################
+ # Access Elements
+ ##############################################
+ def get_block(self, id):
+ for block in self.blocks:
+ if block.get_id() == id:
+ return block
+ raise KeyError('No block with ID {!r}'.format(id))
+
+ def get_elements(self):
+ """
+ Get a list of all the elements.
+ Always ensure that the options block is in the list (only once).
+
+ Returns:
+ the element list
+ """
+ options_block_count = self.blocks.count(self._options_block)
+ if not options_block_count:
+ self.blocks.append(self._options_block)
+ for i in range(options_block_count-1):
+ self.blocks.remove(self._options_block)
+
+ return self.blocks + self.connections
+
+ get_children = get_elements
+
+ def rewrite(self):
+ """
+ Flag the namespace to be renewed.
+ """
+
+ self.renew_namespace()
+ for child in chain(self.blocks, self.connections):
+ child.rewrite()
+
+ self.bus_ports_rewrite()
+
+ def renew_namespace(self):
+ namespace = {}
+ # Load imports
+ for expr in self.get_imports():
+ try:
+ exec expr in namespace
+ except:
+ pass
+
+ for id, expr in self.get_python_modules():
+ try:
+ module = imp.new_module(id)
+ exec expr in module.__dict__
+ namespace[id] = module
+ except:
+ pass
+
+ # Load parameters
+ np = {} # params don't know each other
+ for parameter in self.get_parameters():
+ try:
+ value = eval(parameter.get_param('value').to_code(), namespace)
+ np[parameter.get_id()] = value
+ except:
+ pass
+ namespace.update(np) # Merge param namespace
+
+ # Load variables
+ for variable in self.get_variables():
+ try:
+ value = eval(variable.get_var_value(), namespace)
+ namespace[variable.get_id()] = value
+ except:
+ pass
+
+ self.namespace.clear()
+ self._eval_cache.clear()
+ self.namespace.update(namespace)
+
+ def evaluate(self, expr):
+ """
+ Evaluate the expression.
+
+ Args:
+ expr: the string expression
+ @throw Exception bad expression
+
+ Returns:
+ the evaluated data
+ """
+ # Evaluate
+ if not expr:
+ raise Exception('Cannot evaluate empty statement.')
+ return self._eval_cache.setdefault(expr, eval(expr, self.namespace))
+
+ ##############################################
+ # Add/remove stuff
+ ##############################################
+
+ def get_new_block(self, key):
+ """
+ Get a new block of the specified key.
+ Add the block to the list of elements.
+
+ Args:
+ key: the block key
+
+ Returns:
+ the new block or None if not found
+ """
+ try:
+ block = self.platform.get_new_block(self, key)
+ self.blocks.append(block)
+ except KeyError:
+ block = None
+ return block
+
+ def connect(self, porta, portb):
+ """
+ Create a connection between porta and portb.
+
+ Args:
+ porta: a port
+ portb: another port
+ @throw Exception bad connection
+
+ Returns:
+ the new connection
+ """
+
+ connection = self.platform.Connection(
+ flow_graph=self, porta=porta, portb=portb)
+ self.connections.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.
+ """
+ if element.is_port:
+ # Found a port, set to parent signal block
+ element = element.get_parent()
+
+ if element in self.blocks:
+ # Remove block, remove all involved connections
+ for port in element.get_ports():
+ map(self.remove_element, port.get_connections())
+ self.blocks.remove(element)
+
+ elif element in self.connections:
+ if element.is_bus():
+ cons_list = []
+ for i in map(lambda a: a.get_connections(), element.get_source().get_associated_ports()):
+ cons_list.extend(i)
+ map(self.remove_element, cons_list)
+ self.connections.remove(element)
+
+ ##############################################
+ # Import/Export Methods
+ ##############################################
+ def export_data(self):
+ """
+ Export this flow graph to nested data.
+ Export all block and connection data.
+
+ Returns:
+ a nested data odict
+ """
+ # sort blocks and connections for nicer diffs
+ blocks = sorted(self.blocks, key=lambda b: (
+ b.get_key() != 'options', # options to the front
+ not b.get_key().startswith('variable'), # then vars
+ str(b)
+ ))
+ connections = sorted(self.connections, key=str)
+ n = odict()
+ n['timestamp'] = self._timestamp
+ n['block'] = [b.export_data() for b in blocks]
+ n['connection'] = [c.export_data() for c in connections]
+ instructions = odict({
+ 'created': self.get_parent().get_version_short(),
+ 'format': FLOW_GRAPH_FILE_FORMAT_VERSION,
+ })
+ return odict({'flow_graph': n, '_instructions': instructions})
+
+ 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.
+
+ Args:
+ n: the nested data odict
+ """
+ errors = False
+ # Remove previous elements
+ del self.blocks[:]
+ del self.connections[:]
+ # set file format
+ try:
+ instructions = n.find('_instructions') or {}
+ file_format = int(instructions.get('format', '0')) or _guess_file_format_1(n)
+ except:
+ file_format = 0
+
+ fg_n = n and n.find('flow_graph') or odict() # use blank data if none provided
+ self._timestamp = fg_n.find('timestamp') or time.ctime()
+
+ # build the blocks
+ self._options_block = self.get_parent().get_new_block(self, 'options')
+ for block_n in fg_n.findall('block'):
+ key = block_n.find('key')
+ block = self._options_block if key == 'options' else self.get_new_block(key)
+
+ if not block:
+ platform = self.get_parent()
+ # we're before the initial fg rewrite(), so no evaluated values!
+ # --> use raw value instead
+ path_param = self._options_block.get_param('hier_block_src_path')
+ file_path = platform.find_file_in_paths(
+ filename=key + '.' + platform.get_key(),
+ paths=path_param.get_value(),
+ cwd=self.grc_file_path
+ )
+ if file_path: # grc file found. load and get block
+ platform.load_and_generate_flow_graph(file_path)
+ block = self.get_new_block(key) # can be None
+
+ if not block: # looks like this block key cannot be found
+ # create a dummy block instead
+ block = self.get_new_block('dummy_block')
+ # Ugly ugly ugly
+ _initialize_dummy_block(block, block_n)
+ print('Block key "%s" not found' % key)
+
+ block.import_data(block_n)
+
+ # build the connections
+ def verify_and_get_port(key, block, dir):
+ ports = block.get_sinks() if dir == 'sink' else block.get_sources()
+ for port in ports:
+ if key == port.get_key():
+ break
+ if not key.isdigit() and port.get_type() == '' and key == port.get_name():
+ break
+ else:
+ if block.is_dummy_block():
+ port = _dummy_block_add_port(block, key, dir)
+ else:
+ raise LookupError('%s key %r not in %s block keys' % (dir, key, dir))
+ return port
+
+ for connection_n in fg_n.findall('connection'):
+ # get the block ids and port keys
+ source_block_id = connection_n.find('source_block_id')
+ sink_block_id = connection_n.find('sink_block_id')
+ source_key = connection_n.find('source_key')
+ sink_key = connection_n.find('sink_key')
+ try:
+ source_block = self.get_block(source_block_id)
+ sink_block = self.get_block(sink_block_id)
+
+ # fix old, numeric message ports keys
+ if file_format < 1:
+ source_key, sink_key = self._update_old_message_port_keys(
+ source_key, sink_key, source_block, sink_block)
+
+ # build the connection
+ source_port = verify_and_get_port(source_key, source_block, 'source')
+ sink_port = verify_and_get_port(sink_key, sink_block, 'sink')
+ self.connect(source_port, sink_port)
+ except LookupError as e:
+ Messages.send_error_load(
+ 'Connection between {}({}) and {}({}) could not be made.\n\t{}'.format(
+ source_block_id, source_key, sink_block_id, sink_key, e))
+ errors = True
+
+ self.rewrite() # global rewrite
+ return errors
+
+ ##############################################
+ # Needs to go
+ ##############################################
+ def bus_ports_rewrite(self):
+ # todo: move to block.rewrite()
+ for block in self.blocks:
+ for direc in ['source', 'sink']:
+ if direc == 'source':
+ get_p = block.get_sources
+ get_p_gui = block.get_sources_gui
+ bus_structure = block.form_bus_structure('source')
+ else:
+ get_p = block.get_sinks
+ get_p_gui = block.get_sinks_gui
+ bus_structure = block.form_bus_structure('sink')
+
+ if 'bus' in map(lambda a: a.get_type(), get_p_gui()):
+ if len(get_p_gui()) > len(bus_structure):
+ times = range(len(bus_structure), len(get_p_gui()))
+ for i in times:
+ for connect in get_p_gui()[-1].get_connections():
+ block.get_parent().remove_element(connect)
+ get_p().remove(get_p_gui()[-1])
+ elif len(get_p_gui()) < len(bus_structure):
+ n = {'name': 'bus', 'type': 'bus'}
+ if True in map(
+ lambda a: isinstance(a.get_nports(), int),
+ get_p()):
+ n['nports'] = str(1)
+
+ times = range(len(get_p_gui()), len(bus_structure))
+
+ for i in times:
+ n['key'] = str(len(get_p()))
+ n = odict(n)
+ port = block.get_parent().get_parent().Port(
+ block=block, n=n, dir=direc)
+ get_p().append(port)
+
+ if 'bus' in map(lambda a: a.get_type(),
+ block.get_sources_gui()):
+ for i in range(len(block.get_sources_gui())):
+ if len(block.get_sources_gui()[
+ i].get_connections()) > 0:
+ source = block.get_sources_gui()[i]
+ sink = []
+
+ for j in range(len(source.get_connections())):
+ sink.append(
+ source.get_connections()[j].get_sink())
+ for elt in source.get_connections():
+ self.remove_element(elt)
+ for j in sink:
+ self.connect(source, j)
+
+
+def _update_old_message_port_keys(source_key, sink_key, source_block, sink_block):
+ """
+ Backward compatibility for message port keys
+
+ Message ports use their names as key (like in the 'connect' method).
+ Flowgraph files from former versions still have numeric keys stored for
+ message connections. These have to be replaced by the name of the
+ respective port. The correct message port is deduced from the integer
+ value of the key (assuming the order has not changed).
+
+ The connection ends are updated only if both ends translate into a
+ message port.
+ """
+ try:
+ # get ports using the "old way" (assuming liner indexed keys)
+ source_port = source_block.get_sources()[int(source_key)]
+ sink_port = sink_block.get_sinks()[int(sink_key)]
+ if source_port.get_type() == "message" and sink_port.get_type() == "message":
+ source_key, sink_key = source_port.get_key(), sink_port.get_key()
+ except (ValueError, IndexError):
+ pass
+ return source_key, sink_key # do nothing
+
+
+def _guess_file_format_1(n):
+ """
+ Try to guess the file format for flow-graph files without version tag
+ """
+ try:
+ has_non_numeric_message_keys = any(not (
+ connection_n.find('source_key').isdigit() and
+ connection_n.find('sink_key').isdigit()
+ ) for connection_n in n.find('flow_graph').findall('connection'))
+ if has_non_numeric_message_keys:
+ return 1
+ except:
+ pass
+ return 0
+
+
+def _initialize_dummy_block(block, block_n):
+ """
+ This is so ugly... dummy-fy a block
+ Modify block object to get the behaviour for a missing block
+ """
+
+ block._key = block_n.find('key')
+ block.is_dummy_block = lambda: True
+ block.is_valid = lambda: False
+ block.get_enabled = lambda: False
+ for param_n in block_n.findall('param'):
+ if param_n['key'] not in block.get_param_keys():
+ new_param_n = odict({'key': param_n['key'], 'name': param_n['key'], 'type': 'string'})
+ params = block.get_parent().get_parent().Param(block=block, n=new_param_n)
+ block.get_params().append(params)
+
+
+def _dummy_block_add_port(block, key, dir):
+ """ This is so ugly... Add a port to a dummy-field block """
+ port_n = odict({'name': '?', 'key': key, 'type': ''})
+ port = block.get_parent().get_parent().Port(block=block, n=port_n, dir=dir)
+ if port.is_source():
+ block.get_sources().append(port)
+ else:
+ block.get_sinks().append(port)
+ return port
diff --git a/grc/core/Param.py b/grc/core/Param.py
new file mode 100644
index 0000000000..f064097256
--- /dev/null
+++ b/grc/core/Param.py
@@ -0,0 +1,714 @@
+"""
+Copyright 2008-2015 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 ast
+
+import re
+from gnuradio import eng_notation
+from gnuradio import gr
+
+import Constants
+from Constants import VECTOR_TYPES, COMPLEX_TYPES, REAL_TYPES, INT_TYPES
+
+from .odict import odict
+from .Element import Element
+
+# Blacklist certain ids, its not complete, but should help
+import __builtin__
+
+
+ID_BLACKLIST = ['self', 'options', 'gr', 'blks2', 'wxgui', 'wx', 'math', 'forms', 'firdes'] + \
+ filter(lambda x: not x.startswith('_'), dir(gr.top_block())) + dir(__builtin__)
+
+_check_id_matcher = re.compile('^[a-z|A-Z]\w*$')
+_show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook)$')
+
+
+def _get_keys(lst):
+ return [elem.get_key() for elem in lst]
+
+
+def _get_elem(lst, key):
+ try:
+ return lst[_get_keys(lst).index(key)]
+ except ValueError:
+ raise ValueError('Key "{}" not found in {}.'.format(key, _get_keys(lst)))
+
+
+def num_to_str(num):
+ """ Display logic for numbers """
+ if isinstance(num, COMPLEX_TYPES):
+ num = complex(num) # Cast to python complex
+ if num == 0:
+ return '0'
+ elif num.imag == 0:
+ # Value is real
+ return '{}'.format(eng_notation.num_to_str(num.real))
+ elif num.real == 0:
+ # Value is imaginary
+ return '{}j'.format(eng_notation.num_to_str(num.imag))
+ elif num.imag < 0:
+ return '{}-{}j'.format(eng_notation.num_to_str(num.real),
+ eng_notation.num_to_str(abs(num.imag)))
+ else:
+ return '{}+{}j'.format(eng_notation.num_to_str(num.real),
+ eng_notation.num_to_str(num.imag))
+ else:
+ return str(num)
+
+
+class Option(Element):
+
+ def __init__(self, param, n):
+ Element.__init__(self, param)
+ self._name = n.find('name')
+ self._key = n.find('key')
+ self._opts = dict()
+ opts = n.findall('opt')
+ # Test against opts when non enum
+ if not self.get_parent().is_enum() and opts:
+ raise Exception('Options for non-enum types cannot have sub-options')
+ # Extract opts
+ for opt in opts:
+ # Separate the key:value
+ try:
+ key, value = opt.split(':')
+ except:
+ raise Exception('Error separating "{}" into key:value'.format(opt))
+ # Test against repeated keys
+ if key in self._opts:
+ raise Exception('Key "{}" already exists in option'.format(key))
+ # Store the option
+ self._opts[key] = value
+
+ def __str__(self):
+ return 'Option {}({})'.format(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()
+
+
+class Param(Element):
+
+ is_param = True
+
+ def __init__(self, block, n):
+ """
+ Make a new param from nested data.
+
+ Args:
+ block: the parent element
+ n: the nested odict
+ """
+ # If the base key is a valid param key, copy its data and overlay this params data
+ base_key = n.find('base_key')
+ if base_key and base_key in block.get_param_keys():
+ n_expanded = block.get_param(base_key)._n.copy()
+ n_expanded.update(n)
+ n = n_expanded
+ # Save odict in case this param will be base for another
+ self._n = n
+ # Parse the data
+ self._name = n.find('name')
+ self._key = n.find('key')
+ value = n.find('value') or ''
+ self._type = n.find('type') or 'raw'
+ self._hide = n.find('hide') or ''
+ self._tab_label = n.find('tab') or block.get_param_tab_labels()[0]
+ if self._tab_label not in block.get_param_tab_labels():
+ block.get_param_tab_labels().append(self._tab_label)
+ # Build the param
+ Element.__init__(self, block)
+ # Create the Option objects from the n data
+ self._options = list()
+ self._evaluated = None
+ for option in map(lambda o: Option(param=self, n=o), n.findall('option')):
+ key = option.get_key()
+ # Test against repeated keys
+ if key in self.get_option_keys():
+ raise Exception('Key "{}" already exists in options'.format(key))
+ # Store the option
+ self.get_options().append(option)
+ # Test the enum options
+ if self.is_enum():
+ # Test against options with identical keys
+ if len(set(self.get_option_keys())) != len(self.get_options()):
+ raise Exception('Options keys "{}" are not unique.'.format(self.get_option_keys()))
+ # Test against inconsistent keys in options
+ opt_keys = self.get_options()[0].get_opt_keys()
+ for option in self.get_options():
+ if set(opt_keys) != set(option.get_opt_keys()):
+ raise Exception('Opt keys "{}" are not identical across all options.'.format(opt_keys))
+ # If a value is specified, it must be in the options keys
+ if value or value in self.get_option_keys():
+ self._value = value
+ else:
+ self._value = self.get_option_keys()[0]
+ if self.get_value() not in self.get_option_keys():
+ raise Exception('The value "{}" is not in the possible values of "{}".'.format(self.get_value(), self.get_option_keys()))
+ else:
+ self._value = value or ''
+ self._default = value
+ self._init = False
+ self._hostage_cells = list()
+
+ def get_types(self):
+ return (
+ 'raw', 'enum',
+ 'complex', 'real', 'float', 'int',
+ 'complex_vector', 'real_vector', 'float_vector', 'int_vector',
+ 'hex', 'string', 'bool',
+ 'file_open', 'file_save', '_multiline', '_multiline_python_external',
+ 'id', 'stream_id',
+ 'grid_pos', 'notebook', 'gui_hint',
+ 'import',
+ )
+
+ def __repr__(self):
+ """
+ Get the repr (nice string format) for this param.
+
+ Returns:
+ the string representation
+ """
+ ##################################################
+ # Truncate helper method
+ ##################################################
+ def _truncate(string, style=0):
+ max_len = max(27 - len(self.get_name()), 3)
+ if len(string) > max_len:
+ if style < 0: # Front truncate
+ string = '...' + string[3-max_len:]
+ elif style == 0: # Center truncate
+ string = string[:max_len/2 - 3] + '...' + string[-max_len/2:]
+ elif style > 0: # Rear truncate
+ string = string[:max_len-3] + '...'
+ return string
+
+ ##################################################
+ # Simple conditions
+ ##################################################
+ if not self.is_valid():
+ return _truncate(self.get_value())
+ if self.get_value() in self.get_option_keys():
+ return self.get_option(self.get_value()).get_name()
+
+ ##################################################
+ # Split up formatting by type
+ ##################################################
+ # Default center truncate
+ truncate = 0
+ e = self.get_evaluated()
+ t = self.get_type()
+ if isinstance(e, bool):
+ return str(e)
+ elif isinstance(e, COMPLEX_TYPES):
+ dt_str = num_to_str(e)
+ elif isinstance(e, VECTOR_TYPES):
+ # Vector types
+ if len(e) > 8:
+ # Large vectors use code
+ dt_str = self.get_value()
+ truncate = 1
+ else:
+ # Small vectors use eval
+ dt_str = ', '.join(map(num_to_str, e))
+ elif t in ('file_open', 'file_save'):
+ dt_str = self.get_value()
+ truncate = -1
+ else:
+ # Other types
+ dt_str = str(e)
+
+ # Done
+ return _truncate(dt_str, truncate)
+
+ def __repr2__(self):
+ """
+ Get the repr (nice string format) for this param.
+
+ Returns:
+ the string representation
+ """
+ if self.is_enum():
+ return self.get_option(self.get_value()).get_name()
+ return self.get_value()
+
+ def __str__(self):
+ return 'Param - {}({})'.format(self.get_name(), self.get_key())
+
+ def get_color(self):
+ """
+ Get the color that represents this param's type.
+
+ Returns:
+ a hex color code.
+ """
+ try:
+ return {
+ # Number types
+ 'complex': Constants.COMPLEX_COLOR_SPEC,
+ 'real': Constants.FLOAT_COLOR_SPEC,
+ 'float': Constants.FLOAT_COLOR_SPEC,
+ 'int': Constants.INT_COLOR_SPEC,
+ # Vector types
+ 'complex_vector': Constants.COMPLEX_VECTOR_COLOR_SPEC,
+ 'real_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
+ 'float_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
+ 'int_vector': Constants.INT_VECTOR_COLOR_SPEC,
+ # Special
+ 'bool': Constants.INT_COLOR_SPEC,
+ 'hex': Constants.INT_COLOR_SPEC,
+ 'string': Constants.BYTE_VECTOR_COLOR_SPEC,
+ 'id': Constants.ID_COLOR_SPEC,
+ 'stream_id': Constants.ID_COLOR_SPEC,
+ 'grid_pos': Constants.INT_VECTOR_COLOR_SPEC,
+ 'notebook': Constants.INT_VECTOR_COLOR_SPEC,
+ 'raw': Constants.WILDCARD_COLOR_SPEC,
+ }[self.get_type()]
+ except:
+ return '#FFFFFF'
+
+ def get_hide(self):
+ """
+ Get the hide value from the base class.
+ Hide the ID parameter for most blocks. Exceptions below.
+ If the parameter controls a port type, vlen, or nports, return part.
+ If the parameter is an empty grid position, return part.
+ These parameters are redundant to display in the flow graph view.
+
+ Returns:
+ hide the hide property string
+ """
+ hide = self.get_parent().resolve_dependencies(self._hide).strip()
+ if hide:
+ return hide
+ # Hide ID in non variable blocks
+ if self.get_key() == 'id' and not _show_id_matcher.match(self.get_parent().get_key()):
+ return 'part'
+ # Hide port controllers for type and nports
+ if self.get_key() in ' '.join(map(lambda p: ' '.join([p._type, p._nports]),
+ self.get_parent().get_ports())):
+ return 'part'
+ # Hide port controllers for vlen, when == 1
+ if self.get_key() in ' '.join(map(
+ lambda p: p._vlen, self.get_parent().get_ports())
+ ):
+ try:
+ if int(self.get_evaluated()) == 1:
+ return 'part'
+ except:
+ pass
+ # Hide empty grid positions
+ if self.get_key() in ('grid_pos', 'notebook') and not self.get_value():
+ return 'part'
+ return hide
+
+ def validate(self):
+ """
+ Validate the param.
+ The value must be evaluated and type must a possible type.
+ """
+ Element.validate(self)
+ if self.get_type() not in self.get_types():
+ self.add_error_message('Type "{}" is not a possible type.'.format(self.get_type()))
+
+ self._evaluated = None
+ try:
+ self._evaluated = self.evaluate()
+ except Exception, e:
+ self.add_error_message(str(e))
+
+ def get_evaluated(self):
+ return self._evaluated
+
+ def evaluate(self):
+ """
+ Evaluate the value.
+
+ Returns:
+ evaluated type
+ """
+ self._init = True
+ self._lisitify_flag = False
+ self._stringify_flag = False
+ self._hostage_cells = list()
+ t = self.get_type()
+ v = self.get_value()
+
+ #########################
+ # Enum Type
+ #########################
+ if self.is_enum():
+ return v
+
+ #########################
+ # Numeric Types
+ #########################
+ elif t in ('raw', 'complex', 'real', 'float', 'int', 'hex', 'bool'):
+ # Raise exception if python cannot evaluate this value
+ try:
+ e = self.get_parent().get_parent().evaluate(v)
+ except Exception, e:
+ raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e))
+ # Raise an exception if the data is invalid
+ if t == 'raw':
+ return e
+ elif t == 'complex':
+ if not isinstance(e, COMPLEX_TYPES):
+ raise Exception('Expression "{}" is invalid for type complex.'.format(str(e)))
+ return e
+ elif t == 'real' or t == 'float':
+ if not isinstance(e, REAL_TYPES):
+ raise Exception('Expression "{}" is invalid for type float.'.format(str(e)))
+ return e
+ elif t == 'int':
+ if not isinstance(e, INT_TYPES):
+ raise Exception('Expression "{}" is invalid for type integer.'.format(str(e)))
+ return e
+ elif t == 'hex':
+ return hex(e)
+ elif t == 'bool':
+ if not isinstance(e, bool):
+ raise Exception('Expression "{}" is invalid for type bool.'.format(str(e)))
+ return e
+ else:
+ raise TypeError('Type "{}" not handled'.format(t))
+ #########################
+ # Numeric Vector Types
+ #########################
+ elif t in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'):
+ if not v:
+ # Turn a blank string into an empty list, so it will eval
+ v = '()'
+ # Raise exception if python cannot evaluate this value
+ try:
+ e = self.get_parent().get_parent().evaluate(v)
+ except Exception, e:
+ raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e))
+ # Raise an exception if the data is invalid
+ if t == 'complex_vector':
+ if not isinstance(e, VECTOR_TYPES):
+ self._lisitify_flag = True
+ e = [e]
+ if not all([isinstance(ei, COMPLEX_TYPES) for ei in e]):
+ raise Exception('Expression "{}" is invalid for type complex vector.'.format(str(e)))
+ return e
+ elif t == 'real_vector' or t == 'float_vector':
+ if not isinstance(e, VECTOR_TYPES):
+ self._lisitify_flag = True
+ e = [e]
+ if not all([isinstance(ei, REAL_TYPES) for ei in e]):
+ raise Exception('Expression "{}" is invalid for type float vector.'.format(str(e)))
+ return e
+ elif t == 'int_vector':
+ if not isinstance(e, VECTOR_TYPES):
+ self._lisitify_flag = True
+ e = [e]
+ if not all([isinstance(ei, INT_TYPES) for ei in e]):
+ raise Exception('Expression "{}" is invalid for type integer vector.'.format(str(e)))
+ return e
+ #########################
+ # String Types
+ #########################
+ elif t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'):
+ # Do not check if file/directory exists, that is a runtime issue
+ try:
+ e = self.get_parent().get_parent().evaluate(v)
+ if not isinstance(e, str):
+ raise Exception()
+ except:
+ self._stringify_flag = True
+ e = str(v)
+ if t == '_multiline_python_external':
+ ast.parse(e) # Raises SyntaxError
+ return e
+ #########################
+ # Unique ID Type
+ #########################
+ elif t == 'id':
+ # Can python use this as a variable?
+ if not _check_id_matcher.match(v):
+ raise Exception('ID "{}" must begin with a letter and may contain letters, numbers, and underscores.'.format(v))
+ ids = [param.get_value() for param in self.get_all_params(t)]
+
+ # Id should only appear once, or zero times if block is disabled
+ if ids.count(v) > 1:
+ raise Exception('ID "{}" is not unique.'.format(v))
+ if v in ID_BLACKLIST:
+ raise Exception('ID "{}" is blacklisted.'.format(v))
+ return v
+
+ #########################
+ # Stream ID Type
+ #########################
+ elif t == 'stream_id':
+ # Get a list of all stream ids used in the virtual sinks
+ ids = [param.get_value() for param in filter(
+ lambda p: p.get_parent().is_virtual_sink(),
+ self.get_all_params(t),
+ )]
+ # Check that the virtual sink's stream id is unique
+ if self.get_parent().is_virtual_sink():
+ # Id should only appear once, or zero times if block is disabled
+ if ids.count(v) > 1:
+ raise Exception('Stream ID "{}" is not unique.'.format(v))
+ # Check that the virtual source's steam id is found
+ if self.get_parent().is_virtual_source():
+ if v not in ids:
+ raise Exception('Stream ID "{}" is not found.'.format(v))
+ return v
+
+ #########################
+ # GUI Position/Hint
+ #########################
+ elif t == 'gui_hint':
+ if ':' in v:
+ tab, pos = v.split(':')
+ elif '@' in v:
+ tab, pos = v, ''
+ else:
+ tab, pos = '', v
+
+ if '@' in tab:
+ tab, index = tab.split('@')
+ else:
+ index = '?'
+
+ # TODO: Problem with this code. Produces bad tabs
+ widget_str = ({
+ (True, True): 'self.%(tab)s_grid_layout_%(index)s.addWidget(%(widget)s, %(pos)s)',
+ (True, False): 'self.%(tab)s_layout_%(index)s.addWidget(%(widget)s)',
+ (False, True): 'self.top_grid_layout.addWidget(%(widget)s, %(pos)s)',
+ (False, False): 'self.top_layout.addWidget(%(widget)s)',
+ }[bool(tab), bool(pos)]) % {'tab': tab, 'index': index, 'widget': '%s', 'pos': pos}
+
+ # FIXME: Move replace(...) into the make template of the qtgui blocks
+ # Return a string here
+ class GuiHint(object):
+ def __init__(self, ws):
+ self._ws = ws
+
+ def __call__(self, w):
+ return (self._ws.replace('addWidget', 'addLayout') if 'layout' in w else self._ws) % w
+
+ def __str__(self):
+ return self._ws
+ return GuiHint(widget_str)
+ #########################
+ # Grid Position Type
+ #########################
+ elif t == 'grid_pos':
+ if not v:
+ # Allow for empty grid pos
+ return ''
+ e = self.get_parent().get_parent().evaluate(v)
+ if not isinstance(e, (list, tuple)) or len(e) != 4 or not all([isinstance(ei, int) for ei in e]):
+ raise Exception('A grid position must be a list of 4 integers.')
+ row, col, row_span, col_span = e
+ # Check row, col
+ if row < 0 or col < 0:
+ raise Exception('Row and column must be non-negative.')
+ # Check row span, col span
+ if row_span <= 0 or col_span <= 0:
+ raise Exception('Row and column span must be greater than zero.')
+ # Get hostage cell parent
+ try:
+ my_parent = self.get_parent().get_param('notebook').evaluate()
+ except:
+ my_parent = ''
+ # Calculate hostage cells
+ for r in range(row_span):
+ for c in range(col_span):
+ self._hostage_cells.append((my_parent, (row+r, col+c)))
+ # Avoid collisions
+ params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
+ for param in params:
+ for parent, cell in param._hostage_cells:
+ if (parent, cell) in self._hostage_cells:
+ raise Exception('Another graphical element is using parent "{}", cell "{}".'.format(str(parent), str(cell)))
+ return e
+ #########################
+ # Notebook Page Type
+ #########################
+ elif t == 'notebook':
+ if not v:
+ # Allow for empty notebook
+ return ''
+
+ # Get a list of all notebooks
+ notebook_blocks = filter(lambda b: b.get_key() == 'notebook', self.get_parent().get_parent().get_enabled_blocks())
+ # Check for notebook param syntax
+ try:
+ notebook_id, page_index = map(str.strip, v.split(','))
+ except:
+ raise Exception('Bad notebook page format.')
+ # Check that the notebook id is valid
+ try:
+ notebook_block = filter(lambda b: b.get_id() == notebook_id, notebook_blocks)[0]
+ except:
+ raise Exception('Notebook id "{}" is not an existing notebook id.'.format(notebook_id))
+
+ # Check that page index exists
+ if int(page_index) not in range(len(notebook_block.get_param('labels').evaluate())):
+ raise Exception('Page index "{}" is not a valid index number.'.format(page_index))
+ return notebook_id, page_index
+
+ #########################
+ # Import Type
+ #########################
+ elif t == 'import':
+ # New namespace
+ n = dict()
+ try:
+ exec v in n
+ except ImportError:
+ raise Exception('Import "{}" failed.'.format(v))
+ except Exception:
+ raise Exception('Bad import syntax: "{}".'.format(v))
+ return filter(lambda k: str(k) != '__builtins__', n.keys())
+
+ #########################
+ else:
+ raise TypeError('Type "{}" not handled'.format(t))
+
+ def to_code(self):
+ """
+ Convert the value to code.
+ For string and list types, check the init flag, call evaluate().
+ This ensures that evaluate() was called to set the xxxify_flags.
+
+ Returns:
+ a string representing the code
+ """
+ v = self.get_value()
+ t = self.get_type()
+ # String types
+ if t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'):
+ if not self._init:
+ self.evaluate()
+ if self._stringify_flag:
+ return '"%s"' % v.replace('"', '\"')
+ else:
+ return v
+ # Vector types
+ elif t in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'):
+ if not self._init:
+ self.evaluate()
+ if self._lisitify_flag:
+ return '(%s, )' % v
+ else:
+ return '(%s)' % v
+ else:
+ return v
+
+ def get_all_params(self, type):
+ """
+ Get all the params from the flowgraph that have the given type.
+
+ Args:
+ type: the specified type
+
+ Returns:
+ a list of params
+ """
+ return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])
+
+ def is_enum(self):
+ return self._type == 'enum'
+
+ 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):
+ # Must be a string
+ self._value = str(value)
+
+ def value_is_default(self):
+ return self._default == self._value
+
+ def get_type(self):
+ return self.get_parent().resolve_dependencies(self._type)
+
+ def get_tab_label(self):
+ return self._tab_label
+
+ def get_name(self):
+ return self.get_parent().resolve_dependencies(self._name).strip()
+
+ def get_key(self):
+ return self._key
+
+ ##############################################
+ # Access Options
+ ##############################################
+ def get_option_keys(self):
+ return _get_keys(self.get_options())
+
+ def get_option(self, key):
+ return _get_elem(self.get_options(), key)
+
+ def get_options(self):
+ return self._options
+
+ ##############################################
+ # Access Opts
+ ##############################################
+ def get_opt_keys(self):
+ return self.get_option(self.get_value()).get_opt_keys()
+
+ def get_opt(self, key):
+ return self.get_option(self.get_value()).get_opt(key)
+
+ def get_opts(self):
+ return self.get_option(self.get_value()).get_opts()
+
+ ##############################################
+ # Import/Export Methods
+ ##############################################
+ def export_data(self):
+ """
+ Export this param's key/value.
+
+ Returns:
+ a nested data odict
+ """
+ n = odict()
+ n['key'] = self.get_key()
+ n['value'] = self.get_value()
+ return n
diff --git a/grc/core/ParseXML.py b/grc/core/ParseXML.py
new file mode 100644
index 0000000000..adf5cc97b7
--- /dev/null
+++ b/grc/core/ParseXML.py
@@ -0,0 +1,158 @@
+"""
+Copyright 2008, 2015 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 lxml import etree
+from .odict import odict
+
+xml_failures = {}
+
+
+class XMLSyntaxError(Exception):
+ def __init__(self, error_log):
+ self._error_log = error_log
+ xml_failures[error_log.last_error.filename] = error_log
+
+ def __str__(self):
+ return '\n'.join(map(str, self._error_log.filter_from_errors()))
+
+
+def validate_dtd(xml_file, dtd_file=None):
+ """
+ Validate an xml file against its dtd.
+
+ Args:
+ xml_file: the xml file
+ dtd_file: the optional dtd file
+ @throws Exception validation fails
+ """
+ # Perform parsing, use dtd validation if dtd file is not specified
+ parser = etree.XMLParser(dtd_validation=not dtd_file)
+ try:
+ xml = etree.parse(xml_file, parser=parser)
+ except etree.LxmlError:
+ pass
+ if parser.error_log:
+ raise XMLSyntaxError(parser.error_log)
+
+ # Perform dtd validation if the dtd file is specified
+ if not dtd_file:
+ return
+ try:
+ dtd = etree.DTD(dtd_file)
+ if not dtd.validate(xml.getroot()):
+ raise XMLSyntaxError(dtd.error_log)
+ except etree.LxmlError:
+ raise XMLSyntaxError(dtd.error_log)
+
+
+def from_file(xml_file):
+ """
+ Create nested data from an xml file using the from xml helper.
+ Also get the grc version information.
+
+ Args:
+ xml_file: the xml file path
+
+ Returns:
+ the nested data with grc version information
+ """
+ xml = etree.parse(xml_file)
+ nested_data = _from_file(xml.getroot())
+
+ # Get the embedded instructions and build a dictionary item
+ nested_data['_instructions'] = {}
+ xml_instructions = xml.xpath('/processing-instruction()')
+ for inst in filter(lambda i: i.target == 'grc', xml_instructions):
+ nested_data['_instructions'] = odict(inst.attrib)
+ return nested_data
+
+
+def _from_file(xml):
+ """
+ Recursively parse the xml tree into nested data format.
+
+ Args:
+ xml: the xml tree
+
+ Returns:
+ the nested data
+ """
+ tag = xml.tag
+ if not len(xml):
+ return odict({tag: xml.text or ''}) # store empty tags (text is None) as empty string
+ nested_data = odict()
+ for elem in xml:
+ key, value = _from_file(elem).items()[0]
+ if key in nested_data:
+ nested_data[key].append(value)
+ else:
+ nested_data[key] = [value]
+ # Delistify if the length of values is 1
+ for key, values in nested_data.iteritems():
+ if len(values) == 1:
+ nested_data[key] = values[0]
+
+ return odict({tag: nested_data})
+
+
+def to_file(nested_data, xml_file):
+ """
+ Write to an xml file and insert processing instructions for versioning
+
+ Args:
+ nested_data: the nested data
+ xml_file: the xml file path
+ """
+ xml_data = ""
+ instructions = nested_data.pop('_instructions', None)
+ # Create the processing instruction from the array
+ if instructions:
+ xml_data += etree.tostring(etree.ProcessingInstruction(
+ 'grc', ' '.join(
+ "{0}='{1}'".format(*item) for item in instructions.iteritems())
+ ), xml_declaration=True, pretty_print=True, encoding='utf-8')
+ xml_data += etree.tostring(_to_file(nested_data)[0],
+ pretty_print=True, encoding='utf-8')
+ with open(xml_file, 'w') as fp:
+ fp.write(xml_data)
+
+
+def _to_file(nested_data):
+ """
+ Recursively parse the nested data into xml tree format.
+
+ Args:
+ nested_data: the nested data
+
+ Returns:
+ the xml tree filled with child nodes
+ """
+ nodes = list()
+ for key, values in nested_data.iteritems():
+ # Listify the values if not a list
+ if not isinstance(values, (list, set, tuple)):
+ values = [values]
+ for value in values:
+ node = etree.Element(key)
+ if isinstance(value, (str, unicode)):
+ node.text = unicode(value)
+ else:
+ node.extend(_to_file(value))
+ nodes.append(node)
+ return nodes
diff --git a/grc/core/Platform.py b/grc/core/Platform.py
new file mode 100644
index 0000000000..f04dd04e90
--- /dev/null
+++ b/grc/core/Platform.py
@@ -0,0 +1,404 @@
+"""
+Copyright 2008-2015 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
+import sys
+from gnuradio import gr
+
+from . import ParseXML, extract_docs
+from .Constants import (
+ BLOCK_TREE_DTD, FLOW_GRAPH_DTD, DOMAIN_DTD,
+ HIER_BLOCKS_LIB_DIR, BLOCK_DTD, DEFAULT_FLOW_GRAPH, BLOCKS_DIRS,
+ PREFS_FILE, CORE_TYPES, PREFS_FILE_OLD,
+)
+from .Element import Element
+from .odict import odict
+from ..gui import Messages
+from .generator import Generator
+
+
+class Platform(Element):
+
+ is_platform = True
+
+ def __init__(self):
+ """
+ Make a platform for gnuradio.
+
+ Args:
+ name: the platform name
+ version: the version string
+ key: the unique platform key
+ block_paths: the file paths to blocks in this platform
+ block_dtd: the dtd validator for xml block wrappers
+ default_flow_graph: the default flow graph file path
+ generator: the generator class for this platform
+ colors: a list of title, color_spec tuples
+ license: a multi-line license (first line is copyright)
+ website: the website url for this platform
+ """
+
+ # Ensure hier and conf directories
+ if not os.path.exists(HIER_BLOCKS_LIB_DIR):
+ os.mkdir(HIER_BLOCKS_LIB_DIR)
+ if not os.path.exists(os.path.dirname(PREFS_FILE)):
+ os.mkdir(os.path.dirname(PREFS_FILE))
+
+ self.block_docstrings = {}
+ self.block_docstrings_loaded_callback = lambda: None # dummy to be replaced by BlockTreeWindow
+
+ self._docstring_extractor = extract_docs.SubprocessLoader(
+ callback_query_result=self._save_docstring_extraction_result,
+ callback_finished=lambda: self.block_docstrings_loaded_callback()
+ )
+
+ Element.__init__(self)
+ self._name = 'GNU Radio Companion'
+ # Save the verion string to the first
+ version = (gr.version(), gr.major_version(), gr.api_version(), gr.minor_version())
+ self._version = version[0]
+ self._version_major = version[1]
+ self._version_api = version[2]
+ self._version_minor = version[3]
+ self._version_short = version[1] + "." + version[2] + "." + version[3]
+
+ self._key = 'grc'
+ self._license = __doc__.strip()
+ self._website = 'http://gnuradio.org'
+ self._block_paths = list(set(BLOCKS_DIRS))
+ self._block_dtd = BLOCK_DTD
+ self._default_flow_graph = DEFAULT_FLOW_GRAPH
+ self._generator = Generator
+ self._colors = [(name, color) for name, key, sizeof, color in CORE_TYPES]
+ # Create a dummy flow graph for the blocks
+ self._flow_graph = Element(self)
+
+ self._blocks = None
+ self._blocks_n = None
+ self._category_trees_n = None
+ self._domains = dict()
+ self._connection_templates = dict()
+ self.load_blocks()
+
+ self._auto_hier_block_generate_chain = set()
+
+ def _save_docstring_extraction_result(self, key, docstrings):
+ docs = {}
+ for match, docstring in docstrings.iteritems():
+ if not docstring or match.endswith('_sptr'):
+ continue
+ docstring = docstring.replace('\n\n', '\n').strip()
+ docs[match] = docstring
+ self.block_docstrings[key] = docs
+
+ @staticmethod
+ def _move_old_pref_file():
+ if PREFS_FILE == PREFS_FILE_OLD:
+ return # prefs file overridden with env var
+ if os.path.exists(PREFS_FILE_OLD) and not os.path.exists(PREFS_FILE):
+ try:
+ import shutil
+ shutil.move(PREFS_FILE_OLD, PREFS_FILE)
+ except Exception as e:
+ print >> sys.stderr, e
+
+ def load_blocks(self):
+ self._docstring_extractor.start()
+ self._load_blocks()
+ self._docstring_extractor.finish()
+ # self._docstring_extractor.wait()
+
+ def load_block_xml(self, xml_file):
+ block = self._load_block_xml(self, xml_file)
+ self._docstring_extractor.query(
+ block.get_key(),
+ block.get_imports(raw=True),
+ block.get_make(raw=True)
+ )
+ return block
+
+ @staticmethod
+ def find_file_in_paths(filename, paths, cwd):
+ """Checks the provided paths relative to cwd for a certain filename"""
+ if not os.path.isdir(cwd):
+ cwd = os.path.dirname(cwd)
+ if isinstance(paths, str):
+ paths = (p for p in paths.split(':') if p)
+
+ for path in paths:
+ path = os.path.expanduser(path)
+ if not os.path.isabs(path):
+ path = os.path.normpath(os.path.join(cwd, path))
+ file_path = os.path.join(path, filename)
+ if os.path.exists(os.path.normpath(file_path)):
+ return file_path
+
+ def load_and_generate_flow_graph(self, file_path):
+ """Loads a flowgraph from file and generates it"""
+ Messages.set_indent(len(self._auto_hier_block_generate_chain))
+ Messages.send('>>> Loading: %r\n' % file_path)
+ if file_path in self._auto_hier_block_generate_chain:
+ Messages.send(' >>> Warning: cyclic hier_block dependency\n')
+ return False
+ self._auto_hier_block_generate_chain.add(file_path)
+ try:
+ flow_graph = self.get_new_flow_graph()
+ flow_graph.grc_file_path = file_path
+ # Other, nested higiter_blocks might be auto-loaded here
+ flow_graph.import_data(self.parse_flow_graph(file_path))
+ flow_graph.rewrite()
+ flow_graph.validate()
+ if not flow_graph.is_valid():
+ raise Exception('Flowgraph invalid')
+ except Exception as e:
+ Messages.send('>>> Load Error: {}: {}\n'.format(file_path, str(e)))
+ return False
+ finally:
+ self._auto_hier_block_generate_chain.discard(file_path)
+ Messages.set_indent(len(self._auto_hier_block_generate_chain))
+
+ try:
+ Messages.send('>>> Generating: {}\n'.format(file_path))
+ generator = self.get_generator()(flow_graph, file_path)
+ generator.write()
+ except Exception as e:
+ Messages.send('>>> Generate Error: {}: {}\n'.format(file_path, str(e)))
+ return False
+
+ self.load_block_xml(generator.get_file_path_xml())
+ return True
+
+ def _load_blocks(self):
+ """load the blocks and block tree from the search paths"""
+ # Reset
+ self._blocks = odict()
+ self._blocks_n = odict()
+ self._category_trees_n = list()
+ self._domains.clear()
+ self._connection_templates.clear()
+ ParseXML.xml_failures.clear()
+ # Try to parse and load blocks
+ for xml_file in self.iter_xml_files():
+ try:
+ if xml_file.endswith("block_tree.xml"):
+ self.load_category_tree_xml(xml_file)
+ elif xml_file.endswith('domain.xml'):
+ self.load_domain_xml(xml_file)
+ else:
+ self._load_block_xml(xml_file)
+ except ParseXML.XMLSyntaxError as e:
+ # print >> sys.stderr, 'Warning: Block validation failed:\n\t%s\n\tIgnoring: %s' % (e, xml_file)
+ pass
+ except Exception as e:
+ print >> sys.stderr, 'Warning: XML parsing failed:\n\t%r\n\tIgnoring: %s' % (e, xml_file)
+
+ def iter_xml_files(self):
+ """Iterator for block descriptions and category trees"""
+ for block_path in map(lambda x: os.path.abspath(os.path.expanduser(x)), self._block_paths):
+ if os.path.isfile(block_path):
+ yield block_path
+ elif os.path.isdir(block_path):
+ for dirpath, dirnames, filenames in os.walk(block_path):
+ for filename in sorted(filter(lambda f: f.endswith('.xml'), filenames)):
+ yield os.path.join(dirpath, filename)
+
+ def _load_block_xml(self, xml_file):
+ """Load block description from xml file"""
+ # Validate and import
+ ParseXML.validate_dtd(xml_file, self._block_dtd)
+ n = ParseXML.from_file(xml_file).find('block')
+ n['block_wrapper_path'] = xml_file # inject block wrapper path
+ # Get block instance and add it to the list of blocks
+ block = self.Block(self._flow_graph, n)
+ key = block.get_key()
+ if key in self._blocks:
+ print >> sys.stderr, 'Warning: Block with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file)
+ else: # Store the block
+ self._blocks[key] = block
+ self._blocks_n[key] = n
+ return block
+
+ def load_category_tree_xml(self, xml_file):
+ """Validate and parse category tree file and add it to list"""
+ ParseXML.validate_dtd(xml_file, BLOCK_TREE_DTD)
+ n = ParseXML.from_file(xml_file).find('cat')
+ self._category_trees_n.append(n)
+
+ def load_domain_xml(self, xml_file):
+ """Load a domain properties and connection templates from XML"""
+ ParseXML.validate_dtd(xml_file, DOMAIN_DTD)
+ n = ParseXML.from_file(xml_file).find('domain')
+
+ key = n.find('key')
+ if not key:
+ print >> sys.stderr, 'Warning: Domain with emtpy key.\n\tIgnoring: {}'.foramt(xml_file)
+ return
+ if key in self.get_domains(): # test against repeated keys
+ print >> sys.stderr, 'Warning: Domain with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file)
+ return
+
+ #to_bool = lambda s, d: d if s is None else s.lower() not in ('false', 'off', '0', '')
+ def to_bool(s, d):
+ if s is not None:
+ return s.lower() not in ('false', 'off', '0', '')
+ return d
+
+ color = n.find('color') or ''
+ try:
+ import gtk # ugly but handy
+ gtk.gdk.color_parse(color)
+ except (ValueError, ImportError):
+ if color: # no color is okay, default set in GUI
+ print >> sys.stderr, 'Warning: Can\'t parse color code "{}" for domain "{}" '.format(color, key)
+ color = None
+
+ self._domains[key] = dict(
+ name=n.find('name') or key,
+ multiple_sinks=to_bool(n.find('multiple_sinks'), True),
+ multiple_sources=to_bool(n.find('multiple_sources'), False),
+ color=color
+ )
+ for connection_n in n.findall('connection'):
+ key = (connection_n.find('source_domain'), connection_n.find('sink_domain'))
+ if not all(key):
+ print >> sys.stderr, 'Warning: Empty domain key(s) in connection template.\n\t{}'.format(xml_file)
+ elif key in self._connection_templates:
+ print >> sys.stderr, 'Warning: Connection template "{}" already exists.\n\t{}'.format(key, xml_file)
+ else:
+ self._connection_templates[key] = connection_n.find('make') or ''
+
+ def parse_flow_graph(self, flow_graph_file):
+ """
+ Parse a saved flow graph file.
+ Ensure that the file exists, and passes the dtd check.
+
+ Args:
+ flow_graph_file: the flow graph file
+
+ Returns:
+ nested data
+ @throws exception if the validation fails
+ """
+ flow_graph_file = flow_graph_file or self._default_flow_graph
+ open(flow_graph_file, 'r') # Test open
+ ParseXML.validate_dtd(flow_graph_file, FLOW_GRAPH_DTD)
+ return ParseXML.from_file(flow_graph_file)
+
+ 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.
+
+ Args:
+ block_tree: the block tree object
+ """
+ # Recursive function to load categories and blocks
+ def load_category(cat_n, parent=None):
+ # Add this category
+ parent = (parent or []) + [cat_n.find('name')]
+ block_tree.add_block(parent)
+ # Recursive call to load sub categories
+ map(lambda c: load_category(c, parent), cat_n.findall('cat'))
+ # Add blocks in this category
+ for block_key in cat_n.findall('block'):
+ if block_key not in self.get_block_keys():
+ print >> sys.stderr, 'Warning: Block key "{}" not found when loading category tree.'.format(block_key)
+ continue
+ block = self.get_block(block_key)
+ # If it exists, the block's category shall not be overridden by the xml tree
+ if not block.get_category():
+ block.set_category(parent)
+
+ # Recursively load the category trees and update the categories for each block
+ for category_tree_n in self._category_trees_n:
+ load_category(category_tree_n)
+
+ # Add blocks to block tree
+ for block in self.get_blocks():
+ # Blocks with empty categories are hidden
+ if not block.get_category():
+ continue
+ block_tree.add_block(block.get_category(), block)
+
+ def __str__(self):
+ return 'Platform - {}({})'.format(self.get_key(), self.get_name())
+
+ def get_new_flow_graph(self):
+ return self.FlowGraph(platform=self)
+
+ 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_domains(self):
+ return self._domains
+
+ def get_domain(self, key):
+ return self._domains.get(key)
+
+ def get_connection_templates(self):
+ return self._connection_templates
+
+ def get_name(self):
+ return self._name
+
+ def get_version(self):
+ return self._version
+
+ def get_version_major(self):
+ return self._version_major
+
+ def get_version_api(self):
+ return self._version_api
+
+ def get_version_minor(self):
+ return self._version_minor
+
+ def get_version_short(self):
+ return self._version_short
+
+ def get_key(self):
+ return self._key
+
+ def get_license(self):
+ return self._license
+
+ def get_website(self):
+ return self._website
+
+ def get_colors(self):
+ return self._colors
+
+ def get_block_paths(self):
+ return self._block_paths
diff --git a/grc/core/Port.py b/grc/core/Port.py
new file mode 100644
index 0000000000..bfa48102a7
--- /dev/null
+++ b/grc/core/Port.py
@@ -0,0 +1,404 @@
+"""
+Copyright 2008-2015 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 .Constants import DEFAULT_DOMAIN, GR_STREAM_DOMAIN, GR_MESSAGE_DOMAIN
+from .Element import Element
+
+from . import Constants
+
+
+def _get_source_from_virtual_sink_port(vsp):
+ """
+ Resolve the source port that is connected to the given virtual sink port.
+ Use the get source from virtual source to recursively resolve subsequent ports.
+ """
+ try:
+ return _get_source_from_virtual_source_port(
+ vsp.get_enabled_connections()[0].get_source())
+ except:
+ raise Exception('Could not resolve source for virtual sink port {}'.format(vsp))
+
+
+def _get_source_from_virtual_source_port(vsp, traversed=[]):
+ """
+ Recursively resolve source ports over the virtual connections.
+ Keep track of traversed sources to avoid recursive loops.
+ """
+ if not vsp.get_parent().is_virtual_source():
+ return vsp
+ if vsp in traversed:
+ raise Exception('Loop found when resolving virtual source {}'.format(vsp))
+ try:
+ return _get_source_from_virtual_source_port(
+ _get_source_from_virtual_sink_port(
+ filter( # Get all virtual sinks with a matching stream id
+ lambda vs: vs.get_param('stream_id').get_value() == vsp.get_parent().get_param('stream_id').get_value(),
+ filter( # Get all enabled blocks that are also virtual sinks
+ lambda b: b.is_virtual_sink(),
+ vsp.get_parent().get_parent().get_enabled_blocks(),
+ ),
+ )[0].get_sinks()[0]
+ ), traversed + [vsp],
+ )
+ except:
+ raise Exception('Could not resolve source for virtual source port {}'.format(vsp))
+
+
+def _get_sink_from_virtual_source_port(vsp):
+ """
+ Resolve the sink port that is connected to the given virtual source port.
+ Use the get sink from virtual sink to recursively resolve subsequent ports.
+ """
+ try:
+ # Could have many connections, but use first
+ return _get_sink_from_virtual_sink_port(
+ vsp.get_enabled_connections()[0].get_sink())
+ except:
+ raise Exception('Could not resolve source for virtual source port {}'.format(vsp))
+
+
+def _get_sink_from_virtual_sink_port(vsp, traversed=[]):
+ """
+ Recursively resolve sink ports over the virtual connections.
+ Keep track of traversed sinks to avoid recursive loops.
+ """
+ if not vsp.get_parent().is_virtual_sink():
+ return vsp
+ if vsp in traversed:
+ raise Exception('Loop found when resolving virtual sink {}'.format(vsp))
+ try:
+ return _get_sink_from_virtual_sink_port(
+ _get_sink_from_virtual_source_port(
+ filter( # Get all virtual source with a matching stream id
+ lambda vs: vs.get_param('stream_id').get_value() == vsp.get_parent().get_param('stream_id').get_value(),
+ filter( # Get all enabled blocks that are also virtual sinks
+ lambda b: b.is_virtual_source(),
+ vsp.get_parent().get_parent().get_enabled_blocks(),
+ ),
+ )[0].get_sources()[0]
+ ), traversed + [vsp],
+ )
+ except:
+ raise Exception('Could not resolve source for virtual sink port {}'.format(vsp))
+
+
+class Port(Element):
+
+ is_port = True
+
+ def __init__(self, block, n, dir):
+ """
+ Make a new port from nested data.
+
+ Args:
+ block: the parent element
+ n: the nested odict
+ dir: the direction
+ """
+ self._n = n
+ if n['type'] == 'message':
+ n['domain'] = GR_MESSAGE_DOMAIN
+ if 'domain' not in n:
+ n['domain'] = DEFAULT_DOMAIN
+ elif n['domain'] == GR_MESSAGE_DOMAIN:
+ n['key'] = n['name']
+ n['type'] = 'message' # For port color
+ if n['type'] == 'msg':
+ n['key'] = 'msg'
+ if not n.find('key'):
+ n['key'] = str(next(block.port_counters[dir == 'source']))
+
+ # Build the port
+ Element.__init__(self, block)
+ # Grab the data
+ self._name = n['name']
+ self._key = n['key']
+ self._type = n['type']
+ self._domain = n['domain']
+ self._hide = n.find('hide') or ''
+ self._dir = dir
+ self._hide_evaluated = False # Updated on rewrite()
+
+ self._nports = n.find('nports') or ''
+ self._vlen = n.find('vlen') or ''
+ self._optional = bool(n.find('optional'))
+ self._clones = [] # References to cloned ports (for nports > 1)
+
+ def __str__(self):
+ if self.is_source:
+ return 'Source - {}({})'.format(self.get_name(), self.get_key())
+ if self.is_sink:
+ return 'Sink - {}({})'.format(self.get_name(), self.get_key())
+
+ def get_types(self):
+ return Constants.TYPE_TO_SIZEOF.keys()
+
+ def is_type_empty(self):
+ return not self._n['type']
+
+ def validate(self):
+ Element.validate(self)
+ if self.get_type() not in self.get_types():
+ self.add_error_message('Type "{}" is not a possible type.'.format(self.get_type()))
+ platform = self.get_parent().get_parent().get_parent()
+ if self.get_domain() not in platform.get_domains():
+ self.add_error_message('Domain key "{}" is not registered.'.format(self.get_domain()))
+ if not self.get_enabled_connections() and not self.get_optional():
+ self.add_error_message('Port is not connected.')
+ # Message port logic
+ if self.get_type() == 'msg':
+ if self.get_nports():
+ self.add_error_message('A port of type "msg" cannot have "nports" set.')
+ if self.get_vlen() != 1:
+ self.add_error_message('A port of type "msg" must have a "vlen" of 1.')
+
+ def rewrite(self):
+ """
+ Handle the port cloning for virtual blocks.
+ """
+ if self.is_type_empty():
+ try:
+ # Clone type and vlen
+ source = self.resolve_empty_type()
+ self._type = str(source.get_type())
+ self._vlen = str(source.get_vlen())
+ except:
+ # Reset type and vlen
+ self._type = ''
+ self._vlen = ''
+
+ Element.rewrite(self)
+ hide = self.get_parent().resolve_dependencies(self._hide).strip().lower()
+ self._hide_evaluated = False if hide in ('false', 'off', '0') else bool(hide)
+
+ # Update domain if was deduced from (dynamic) port type
+ type_ = self.get_type()
+ if self._domain == GR_STREAM_DOMAIN and type_ == "message":
+ self._domain = GR_MESSAGE_DOMAIN
+ self._key = self._name
+ if self._domain == GR_MESSAGE_DOMAIN and type_ != "message":
+ self._domain = GR_STREAM_DOMAIN
+ self._key = '0' # Is rectified in rewrite()
+
+ def resolve_virtual_source(self):
+ if self.get_parent().is_virtual_sink():
+ return _get_source_from_virtual_sink_port(self)
+ if self.get_parent().is_virtual_source():
+ return _get_source_from_virtual_source_port(self)
+
+ def resolve_empty_type(self):
+ if self.is_sink:
+ try:
+ src = _get_source_from_virtual_sink_port(self)
+ if not src.is_type_empty():
+ return src
+ except:
+ pass
+ sink = _get_sink_from_virtual_sink_port(self)
+ if not sink.is_type_empty():
+ return sink
+ if self.is_source:
+ try:
+ src = _get_source_from_virtual_source_port(self)
+ if not src.is_type_empty():
+ return src
+ except:
+ pass
+ sink = _get_sink_from_virtual_source_port(self)
+ if not sink.is_type_empty():
+ return sink
+
+ def get_vlen(self):
+ """
+ Get the vector length.
+ If the evaluation of vlen cannot be cast to an integer, return 1.
+
+ Returns:
+ the vector length or 1
+ """
+ vlen = self.get_parent().resolve_dependencies(self._vlen)
+ try:
+ return int(self.get_parent().get_parent().evaluate(vlen))
+ except:
+ return 1
+
+ def get_nports(self):
+ """
+ Get the number of ports.
+ If already blank, return a blank
+ If the evaluation of nports cannot be cast to a positive integer, return 1.
+
+ Returns:
+ the number of ports or 1
+ """
+ if self._nports == '':
+ return ''
+
+ nports = self.get_parent().resolve_dependencies(self._nports)
+ try:
+ return max(1, int(self.get_parent().get_parent().evaluate(nports)))
+ except:
+ return 1
+
+ def get_optional(self):
+ return bool(self._optional)
+
+ def get_color(self):
+ """
+ Get the color that represents this port's type.
+ Codes differ for ports where the vec length is 1 or greater than 1.
+
+ Returns:
+ a hex color code.
+ """
+ try:
+ color = Constants.TYPE_TO_COLOR[self.get_type()]
+ vlen = self.get_vlen()
+ if vlen == 1:
+ return color
+ color_val = int(color[1:], 16)
+ r = (color_val >> 16) & 0xff
+ g = (color_val >> 8) & 0xff
+ b = (color_val >> 0) & 0xff
+ dark = (0, 0, 30, 50, 70)[min(4, vlen)]
+ r = max(r-dark, 0)
+ g = max(g-dark, 0)
+ b = max(b-dark, 0)
+ # TODO: Change this to .format()
+ return '#%.2x%.2x%.2x' % (r, g, b)
+ except:
+ return '#FFFFFF'
+
+ def get_clones(self):
+ """
+ Get the clones of this master port (nports > 1)
+
+ Returns:
+ a list of ports
+ """
+ return self._clones
+
+ def add_clone(self):
+ """
+ Create a clone of this (master) port and store a reference in self._clones.
+
+ The new port name (and key for message ports) will have index 1... appended.
+ If this is the first clone, this (master) port will get a 0 appended to its name (and key)
+
+ Returns:
+ the cloned port
+ """
+ # Add index to master port name if there are no clones yet
+ if not self._clones:
+ self._name = self._n['name'] + '0'
+ # Also update key for none stream ports
+ if not self._key.isdigit():
+ self._key = self._name
+
+ # Prepare a copy of the odict for the clone
+ n = self._n.copy()
+ # Remove nports from the key so the copy cannot be a duplicator
+ if 'nports' in n:
+ n.pop('nports')
+ n['name'] = self._n['name'] + str(len(self._clones) + 1)
+ # Dummy value 99999 will be fixed later
+ n['key'] = '99999' if self._key.isdigit() else n['name']
+
+ # Clone
+ port = self.__class__(self.get_parent(), n, self._dir)
+ self._clones.append(port)
+ return port
+
+ def remove_clone(self, port):
+ """
+ Remove a cloned port (from the list of clones only)
+ Remove the index 0 of the master port name (and key9 if there are no more clones left
+ """
+ self._clones.remove(port)
+ # Remove index from master port name if there are no more clones
+ if not self._clones:
+ self._name = self._n['name']
+ # Also update key for none stream ports
+ if not self._key.isdigit():
+ self._key = self._name
+
+ def get_name(self):
+ number = ''
+ if self.get_type() == 'bus':
+ busses = filter(lambda a: a._dir == self._dir, self.get_parent().get_ports_gui())
+ number = str(busses.index(self)) + '#' + str(len(self.get_associated_ports()))
+ return self._name + number
+
+ def get_key(self):
+ return self._key
+
+ @property
+ def is_sink(self):
+ return self._dir == 'sink'
+
+ @property
+ def is_source(self):
+ return self._dir == 'source'
+
+ def get_type(self):
+ return self.get_parent().resolve_dependencies(self._type)
+
+ def get_domain(self):
+ return self._domain
+
+ def get_hide(self):
+ return self._hide_evaluated
+
+ def get_connections(self):
+ """
+ Get all connections that use this port.
+
+ Returns:
+ a list of connection objects
+ """
+ connections = self.get_parent().get_parent().connections
+ connections = filter(lambda c: c.get_source() is self or c.get_sink() is self, connections)
+ return connections
+
+ def get_enabled_connections(self):
+ """
+ Get all enabled connections that use this port.
+
+ Returns:
+ a list of connection objects
+ """
+ return filter(lambda c: c.get_enabled(), self.get_connections())
+
+ def get_associated_ports(self):
+ if not self.get_type() == 'bus':
+ return [self]
+ else:
+ if self.is_source:
+ get_ports = self.get_parent().get_sources
+ bus_structure = self.get_parent().current_bus_structure['source']
+ else:
+ get_ports = self.get_parent().get_sinks
+ bus_structure = self.get_parent().current_bus_structure['sink']
+
+ ports = [i for i in get_ports() if not i.get_type() == 'bus']
+ if bus_structure:
+ busses = [i for i in get_ports() if i.get_type() == 'bus']
+ bus_index = busses.index(self)
+ ports = filter(lambda a: ports.index(a) in bus_structure[bus_index], ports)
+ return ports
diff --git a/grc/core/__init__.py b/grc/core/__init__.py
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/grc/core/__init__.py
@@ -0,0 +1 @@
+
diff --git a/grc/core/block.dtd b/grc/core/block.dtd
new file mode 100644
index 0000000000..145f4d8610
--- /dev/null
+++ b/grc/core/block.dtd
@@ -0,0 +1,69 @@
+<!--
+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
+-->
+<!--
+ gnuradio_python.blocks.dtd
+ Josh Blum
+ The document type definition for blocks.
+ -->
+<!--
+ Top level element.
+ A block contains a name, ...parameters list, and list of IO ports.
+ -->
+<!ELEMENT block (name, key, category?, throttle?, flags?, import*, var_make?, var_value?,
+ make, callback*, param_tab_order?, param*, bus_sink?, bus_source?, check*,
+ sink*, source*, bus_structure_sink?, bus_structure_source?, doc?, grc_source?)>
+<!--
+ Sub level elements.
+ -->
+<!ELEMENT param_tab_order (tab+)>
+<!ELEMENT param (base_key?, name, key, value?, type?, hide?, option*, tab?)>
+<!ELEMENT option (name, key, opt*)>
+<!ELEMENT sink (name, type, vlen?, domain?, nports?, optional?, hide?)>
+<!ELEMENT source (name, type, vlen?, domain?, nports?, optional?, hide?)>
+<!--
+ Bottom level elements.
+ Character data only.
+ -->
+<!ELEMENT category (#PCDATA)>
+<!ELEMENT import (#PCDATA)>
+<!ELEMENT doc (#PCDATA)>
+<!ELEMENT grc_source (#PCDATA)>
+<!ELEMENT tab (#PCDATA)>
+<!ELEMENT name (#PCDATA)>
+<!ELEMENT base_key (#PCDATA)>
+<!ELEMENT key (#PCDATA)>
+<!ELEMENT check (#PCDATA)>
+<!ELEMENT bus_sink (#PCDATA)>
+<!ELEMENT bus_source (#PCDATA)>
+<!ELEMENT opt (#PCDATA)>
+<!ELEMENT type (#PCDATA)>
+<!ELEMENT hide (#PCDATA)>
+<!ELEMENT vlen (#PCDATA)>
+<!ELEMENT domain (#PCDATA)>
+<!ELEMENT nports (#PCDATA)>
+<!ELEMENT bus_structure_sink (#PCDATA)>
+<!ELEMENT bus_structure_source (#PCDATA)>
+<!ELEMENT var_make (#PCDATA)>
+<!ELEMENT var_value (#PCDATA)>
+<!ELEMENT make (#PCDATA)>
+<!ELEMENT value (#PCDATA)>
+<!ELEMENT callback (#PCDATA)>
+<!ELEMENT optional (#PCDATA)>
+<!ELEMENT throttle (#PCDATA)>
+<!ELEMENT flags (#PCDATA)>
diff --git a/grc/core/block_tree.dtd b/grc/core/block_tree.dtd
new file mode 100644
index 0000000000..9e23576477
--- /dev/null
+++ b/grc/core/block_tree.dtd
@@ -0,0 +1,26 @@
+<!--
+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
+-->
+<!--
+ block_tree.dtd
+ Josh Blum
+ The document type definition for a block tree category listing.
+ -->
+<!ELEMENT cat (name, cat*, block*)>
+<!ELEMENT name (#PCDATA)>
+<!ELEMENT block (#PCDATA)>
diff --git a/grc/core/default_flow_graph.grc b/grc/core/default_flow_graph.grc
new file mode 100644
index 0000000000..059509d34b
--- /dev/null
+++ b/grc/core/default_flow_graph.grc
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<!--
+###################################################
+##Default Flow Graph:
+## include an options block and a variable for sample rate
+###################################################
+ -->
+<flow_graph>
+ <block>
+ <key>options</key>
+ <param>
+ <key>id</key>
+ <value>top_block</value>
+ </param>
+ <param>
+ <key>_coordinate</key>
+ <value>(8, 8)</value>
+ </param>
+ <param>
+ <key>_rotation</key>
+ <value>0</value>
+ </param>
+ </block>
+ <block>
+ <key>variable</key>
+ <param>
+ <key>id</key>
+ <value>samp_rate</value>
+ </param>
+ <param>
+ <key>value</key>
+ <value>32000</value>
+ </param>
+ <param>
+ <key>_coordinate</key>
+ <value>(8, 160)</value>
+ </param>
+ <param>
+ <key>_rotation</key>
+ <value>0</value>
+ </param>
+ </block>
+</flow_graph>
diff --git a/grc/core/domain.dtd b/grc/core/domain.dtd
new file mode 100644
index 0000000000..b5b0b8bf39
--- /dev/null
+++ b/grc/core/domain.dtd
@@ -0,0 +1,35 @@
+<!--
+Copyright 2014 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
+-->
+<!ELEMENT domain (name, key, color?, multiple_sinks?, multiple_sources?, connection*)>
+<!--
+ Sub level elements.
+ -->
+<!ELEMENT connection (source_domain, sink_domain, make)>
+<!--
+ Bottom level elements.
+ Character data only.
+ -->
+<!ELEMENT name (#PCDATA)>
+<!ELEMENT key (#PCDATA)>
+<!ELEMENT multiple_sinks (#PCDATA)>
+<!ELEMENT multiple_sources (#PCDATA)>
+<!ELEMENT color (#PCDATA)>
+<!ELEMENT make (#PCDATA)>
+<!ELEMENT source_domain (#PCDATA)>
+<!ELEMENT sink_domain (#PCDATA)>
diff --git a/grc/core/epy_block_io.py b/grc/core/epy_block_io.py
new file mode 100644
index 0000000000..e089908a01
--- /dev/null
+++ b/grc/core/epy_block_io.py
@@ -0,0 +1,95 @@
+
+import inspect
+import collections
+
+from gnuradio import gr
+import pmt
+
+
+TYPE_MAP = {
+ 'complex64': 'complex', 'complex': 'complex',
+ 'float32': 'float', 'float': 'float',
+ 'int32': 'int', 'uint32': 'int',
+ 'int16': 'short', 'uint16': 'short',
+ 'int8': 'byte', 'uint8': 'byte',
+}
+
+BlockIO = collections.namedtuple('BlockIO', 'name cls params sinks sources doc')
+
+
+def _ports(sigs, msgs):
+ ports = list()
+ for i, dtype in enumerate(sigs):
+ port_type = TYPE_MAP.get(dtype.name, None)
+ if not port_type:
+ raise ValueError("Can't map {0:!r} to GRC port type".format(dtype))
+ ports.append((str(i), port_type))
+ for msg_key in msgs:
+ if msg_key == 'system':
+ continue
+ ports.append((msg_key, 'message'))
+ return ports
+
+
+def _blk_class(source_code):
+ ns = {}
+ try:
+ exec source_code in ns
+ except Exception as e:
+ raise ValueError("Can't interpret source code: " + str(e))
+ for var in ns.itervalues():
+ if inspect.isclass(var)and issubclass(var, gr.gateway.gateway_block):
+ return var
+ raise ValueError('No python block class found in code')
+
+
+def extract(cls):
+ if not inspect.isclass(cls):
+ cls = _blk_class(cls)
+
+ spec = inspect.getargspec(cls.__init__)
+ defaults = map(repr, spec.defaults or ())
+ doc = cls.__doc__ or cls.__init__.__doc__ or ''
+ cls_name = cls.__name__
+
+ if len(defaults) + 1 != len(spec.args):
+ raise ValueError("Need all __init__ arguments to have default values")
+
+ try:
+ instance = cls()
+ except Exception as e:
+ raise RuntimeError("Can't create an instance of your block: " + str(e))
+
+ name = instance.name()
+ params = list(zip(spec.args[1:], defaults))
+
+ sinks = _ports(instance.in_sig(),
+ pmt.to_python(instance.message_ports_in()))
+ sources = _ports(instance.out_sig(),
+ pmt.to_python(instance.message_ports_out()))
+
+ return BlockIO(name, cls_name, params, sinks, sources, doc)
+
+
+if __name__ == '__main__':
+ blk_code = """
+import numpy as np
+from gnuradio import gr
+import pmt
+
+class blk(gr.sync_block):
+ def __init__(self, param1=None, param2=None):
+ "Test Docu"
+ gr.sync_block.__init__(
+ self,
+ name='Embedded Python Block',
+ in_sig = (np.float32,),
+ out_sig = (np.float32,np.complex64,),
+ )
+ self.message_port_register_in(pmt.intern('msg_in'))
+ self.message_port_register_out(pmt.intern('msg_out'))
+
+ def work(self, inputs_items, output_items):
+ return 10
+ """
+ print extract(blk_code)
diff --git a/grc/core/expr_utils.py b/grc/core/expr_utils.py
new file mode 100644
index 0000000000..66911757d6
--- /dev/null
+++ b/grc/core/expr_utils.py
@@ -0,0 +1,196 @@
+"""
+Copyright 2008-2011 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 string
+VAR_CHARS = string.letters + string.digits + '_'
+
+
+class graph(object):
+ """
+ Simple graph structure held in a dictionary.
+ """
+
+ def __init__(self): self._graph = dict()
+
+ def __str__(self): return str(self._graph)
+
+ def add_node(self, node_key):
+ if node_key in self._graph:
+ return
+ self._graph[node_key] = set()
+
+ def remove_node(self, node_key):
+ if node_key not in self._graph:
+ return
+ for edges in self._graph.values():
+ if node_key in edges:
+ edges.remove(node_key)
+ self._graph.pop(node_key)
+
+ def add_edge(self, src_node_key, dest_node_key):
+ self._graph[src_node_key].add(dest_node_key)
+
+ def remove_edge(self, src_node_key, dest_node_key):
+ self._graph[src_node_key].remove(dest_node_key)
+
+ def get_nodes(self):
+ return self._graph.keys()
+
+ def get_edges(self, node_key):
+ return self._graph[node_key]
+
+
+def expr_split(expr):
+ """
+ Split up an expression by non alphanumeric characters, including underscore.
+ Leave strings in-tact.
+ #TODO ignore escaped quotes, use raw strings.
+
+ Args:
+ expr: an expression string
+
+ Returns:
+ a list of string tokens that form expr
+ """
+ toks = list()
+ tok = ''
+ quote = ''
+ for char in expr:
+ if quote or char in VAR_CHARS:
+ if char == quote:
+ quote = ''
+ tok += char
+ elif char in ("'", '"'):
+ toks.append(tok)
+ tok = char
+ quote = char
+ else:
+ toks.append(tok)
+ toks.append(char)
+ tok = ''
+ toks.append(tok)
+ return filter(lambda t: t, toks)
+
+
+def expr_replace(expr, replace_dict):
+ """
+ Search for vars in the expression and add the prepend.
+
+ Args:
+ expr: an expression string
+ replace_dict: a dict of find:replace
+
+ Returns:
+ a new expression with the prepend
+ """
+ expr_splits = expr_split(expr)
+ for i, es in enumerate(expr_splits):
+ if es in replace_dict.keys():
+ expr_splits[i] = replace_dict[es]
+ return ''.join(expr_splits)
+
+
+def get_variable_dependencies(expr, vars):
+ """
+ Return a set of variables used in this expression.
+
+ Args:
+ expr: an expression string
+ vars: a list of variable names
+
+ Returns:
+ a subset of vars used in the expression
+ """
+ expr_toks = expr_split(expr)
+ return set(filter(lambda v: v in expr_toks, vars))
+
+
+def get_graph(exprs):
+ """
+ Get a graph representing the variable dependencies
+
+ Args:
+ exprs: a mapping of variable name to expression
+
+ Returns:
+ a graph of variable deps
+ """
+ vars = exprs.keys()
+ # Get dependencies for each expression, load into graph
+ var_graph = graph()
+ for var in vars:
+ var_graph.add_node(var)
+ for var, expr in exprs.iteritems():
+ for dep in get_variable_dependencies(expr, vars):
+ if dep != var:
+ var_graph.add_edge(dep, var)
+ return var_graph
+
+
+def sort_variables(exprs):
+ """
+ Get a list of variables in order of dependencies.
+
+ Args:
+ exprs: a mapping of variable name to expression
+
+ Returns:
+ a list of variable names
+ @throws Exception circular dependencies
+ """
+ var_graph = get_graph(exprs)
+ sorted_vars = list()
+ # Determine dependency order
+ while var_graph.get_nodes():
+ # Get a list of nodes with no edges
+ indep_vars = filter(lambda var: not var_graph.get_edges(var), var_graph.get_nodes())
+ if not indep_vars:
+ raise Exception('circular dependency caught in sort_variables')
+ # Add the indep vars to the end of the list
+ sorted_vars.extend(sorted(indep_vars))
+ # Remove each edge-less node from the graph
+ for var in indep_vars:
+ var_graph.remove_node(var)
+ return reversed(sorted_vars)
+
+
+def sort_objects(objects, get_id, get_expr):
+ """
+ Sort a list of objects according to their expressions.
+
+ Args:
+ objects: the list of objects to sort
+ get_id: the function to extract an id from the object
+ get_expr: the function to extract an expression from the object
+
+ Returns:
+ a list of sorted objects
+ """
+ id2obj = dict([(get_id(obj), obj) for obj in objects])
+ # Map obj id to expression code
+ id2expr = dict([(get_id(obj), get_expr(obj)) for obj in objects])
+ # Sort according to dependency
+ sorted_ids = sort_variables(id2expr)
+ # Return list of sorted objects
+ return [id2obj[id] for id in sorted_ids]
+
+
+if __name__ == '__main__':
+ for i in sort_variables({'x': '1', 'y': 'x+1', 'a': 'x+y', 'b': 'y+1', 'c': 'a+b+x+y'}):
+ print i
diff --git a/grc/core/extract_docs.py b/grc/core/extract_docs.py
new file mode 100644
index 0000000000..a6e0bc971e
--- /dev/null
+++ b/grc/core/extract_docs.py
@@ -0,0 +1,293 @@
+"""
+Copyright 2008-2015 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 sys
+import re
+import subprocess
+import threading
+import json
+import Queue
+import random
+import itertools
+
+
+###############################################################################
+# The docstring extraction
+###############################################################################
+
+def docstring_guess_from_key(key):
+ """
+ Extract the documentation from the python __doc__ strings
+ By guessing module and constructor names from key
+
+ Args:
+ key: the block key
+
+ Returns:
+ a dict (block_name --> doc string)
+ """
+ doc_strings = dict()
+
+ in_tree = [key.partition('_')[::2] + (
+ lambda package: getattr(__import__('gnuradio.' + package), package),
+ )]
+
+ key_parts = key.split('_')
+ oot = [
+ ('_'.join(key_parts[:i]), '_'.join(key_parts[i:]), __import__)
+ for i in range(1, len(key_parts))
+ ]
+
+ for module_name, init_name, importer in itertools.chain(in_tree, oot):
+ if not module_name or not init_name:
+ continue
+ try:
+ module = importer(module_name)
+ break
+ except ImportError:
+ continue
+ else:
+ return doc_strings
+
+ pattern = re.compile('^' + init_name.replace('_', '_*').replace('x', r'\w') + r'\w*$')
+ for match in filter(pattern.match, dir(module)):
+ try:
+ doc_strings[match] = getattr(module, match).__doc__
+ except AttributeError:
+ continue
+
+ return doc_strings
+
+
+def docstring_from_make(key, imports, make):
+ """
+ Extract the documentation from the python __doc__ strings
+ By importing it and checking a truncated make
+
+ Args:
+ key: the block key
+ imports: a list of import statements (string) to execute
+ make: block constructor template
+
+ Returns:
+ a list of tuples (block_name, doc string)
+ """
+
+ try:
+ blk_cls = make.partition('(')[0].strip()
+ if '$' in blk_cls:
+ raise ValueError('Not an identifier')
+ ns = dict()
+ for _import in imports:
+ exec(_import.strip(), ns)
+ blk = eval(blk_cls, ns)
+ doc_strings = {key: blk.__doc__}
+
+ except (ImportError, AttributeError, SyntaxError, ValueError):
+ doc_strings = docstring_guess_from_key(key)
+
+ return doc_strings
+
+
+###############################################################################
+# Manage docstring extraction in separate process
+###############################################################################
+
+class SubprocessLoader(object):
+ """
+ Start and manage docstring extraction process
+ Manages subprocess and handles RPC.
+ """
+
+ BOOTSTRAP = "import runpy; runpy.run_path({!r}, run_name='__worker__')"
+ AUTH_CODE = random.random() # sort out unwanted output of worker process
+ RESTART = 5 # number of worker restarts before giving up
+ DONE = object() # sentinel value to signal end-of-queue
+
+ def __init__(self, callback_query_result, callback_finished=None):
+ self.callback_query_result = callback_query_result
+ self.callback_finished = callback_finished or (lambda: None)
+
+ self._queue = Queue.Queue()
+ self._thread = None
+ self._worker = None
+ self._shutdown = threading.Event()
+ self._last_cmd = None
+
+ def start(self):
+ """ Start the worker process handler thread """
+ if self._thread is not None:
+ return
+ self._shutdown.clear()
+ thread = self._thread = threading.Thread(target=self.run_worker)
+ thread.daemon = True
+ thread.start()
+
+ def run_worker(self):
+ """ Read docstring back from worker stdout and execute callback. """
+ for _ in range(self.RESTART):
+ if self._shutdown.is_set():
+ break
+ try:
+ self._worker = subprocess.Popen(
+ args=(sys.executable, '-uc', self.BOOTSTRAP.format(__file__)),
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE
+ )
+ self._handle_worker()
+
+ except (OSError, IOError):
+ msg = "Warning: restarting the docstring loader"
+ cmd, args = self._last_cmd
+ if cmd == 'query':
+ msg += " (crashed while loading {0!r})".format(args[0])
+ print >> sys.stderr, msg
+ continue # restart
+ else:
+ break # normal termination, return
+ finally:
+ self._worker.terminate()
+ else:
+ print >> sys.stderr, "Warning: docstring loader crashed too often"
+ self._thread = None
+ self._worker = None
+ self.callback_finished()
+
+ def _handle_worker(self):
+ """ Send commands and responses back from worker. """
+ assert '1' == self._worker.stdout.read(1)
+ for cmd, args in iter(self._queue.get, self.DONE):
+ self._last_cmd = cmd, args
+ self._send(cmd, args)
+ cmd, args = self._receive()
+ self._handle_response(cmd, args)
+
+ def _send(self, cmd, args):
+ """ Send a command to worker's stdin """
+ fd = self._worker.stdin
+ json.dump((self.AUTH_CODE, cmd, args), fd)
+ fd.write('\n'.encode())
+
+ def _receive(self):
+ """ Receive response from worker's stdout """
+ for line in iter(self._worker.stdout.readline, ''):
+ try:
+ key, cmd, args = json.loads(line, encoding='utf-8')
+ if key != self.AUTH_CODE:
+ raise ValueError('Got wrong auth code')
+ return cmd, args
+ except ValueError:
+ continue # ignore invalid output from worker
+ else:
+ raise IOError("Can't read worker response")
+
+ def _handle_response(self, cmd, args):
+ """ Handle response from worker, call the callback """
+ if cmd == 'result':
+ key, docs = args
+ self.callback_query_result(key, docs)
+ elif cmd == 'error':
+ print args
+ else:
+ print >> sys.stderr, "Unknown response:", cmd, args
+
+ def query(self, key, imports=None, make=None):
+ """ Request docstring extraction for a certain key """
+ if self._thread is None:
+ self.start()
+ if imports and make:
+ self._queue.put(('query', (key, imports, make)))
+ else:
+ self._queue.put(('query_key_only', (key,)))
+
+ def finish(self):
+ """ Signal end of requests """
+ self._queue.put(self.DONE)
+
+ def wait(self):
+ """ Wait for the handler thread to die """
+ if self._thread:
+ self._thread.join()
+
+ def terminate(self):
+ """ Terminate the worker and wait """
+ self._shutdown.set()
+ try:
+ self._worker.terminate()
+ self.wait()
+ except (OSError, AttributeError):
+ pass
+
+
+###############################################################################
+# Main worker entry point
+###############################################################################
+
+def worker_main():
+ """
+ Main entry point for the docstring extraction process.
+ Manages RPC with main process through.
+ Runs a docstring extraction for each key it read on stdin.
+ """
+ def send(cmd, args):
+ json.dump((code, cmd, args), sys.stdout)
+ sys.stdout.write('\n'.encode())
+
+ sys.stdout.write('1')
+ for line in iter(sys.stdin.readline, ''):
+ code, cmd, args = json.loads(line, encoding='utf-8')
+ try:
+ if cmd == 'query':
+ key, imports, make = args
+ send('result', (key, docstring_from_make(key, imports, make)))
+ elif cmd == 'query_key_only':
+ key, = args
+ send('result', (key, docstring_guess_from_key(key)))
+ elif cmd == 'exit':
+ break
+ except Exception as e:
+ send('error', repr(e))
+
+
+if __name__ == '__worker__':
+ worker_main()
+
+elif __name__ == '__main__':
+ def callback(key, docs):
+ print key
+ for match, doc in docs.iteritems():
+ print '-->', match
+ print doc.strip()
+ print
+ print
+
+ r = SubprocessLoader(callback)
+
+ # r.query('analog_feedforward_agc_cc')
+ # r.query('uhd_source')
+ r.query('expr_utils_graph')
+ r.query('blocks_add_cc')
+ r.query('blocks_add_cc', ['import gnuradio.blocks'], 'gnuradio.blocks.add_cc(')
+ # r.query('analog_feedforward_agc_cc')
+ # r.query('uhd_source')
+ # r.query('uhd_source')
+ # r.query('analog_feedforward_agc_cc')
+ r.finish()
+ # r.terminate()
+ r.wait()
diff --git a/grc/core/flow_graph.dtd b/grc/core/flow_graph.dtd
new file mode 100644
index 0000000000..bdfe1dc059
--- /dev/null
+++ b/grc/core/flow_graph.dtd
@@ -0,0 +1,38 @@
+<!--
+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
+-->
+<!--
+ flow_graph.dtd
+ Josh Blum
+ The document type definition for flow graph xml files.
+ -->
+<!ELEMENT flow_graph (timestamp?, block*, connection*)> <!-- optional timestamp -->
+<!ELEMENT timestamp (#PCDATA)>
+<!-- Block -->
+<!ELEMENT block (key, param*, bus_sink?, bus_source?)>
+<!ELEMENT param (key, value)>
+<!ELEMENT key (#PCDATA)>
+<!ELEMENT value (#PCDATA)>
+<!ELEMENT bus_sink (#PCDATA)>
+<!ELEMENT bus_source (#PCDATA)>
+<!-- Connection -->
+<!ELEMENT connection (source_block_id, sink_block_id, source_key, sink_key)>
+<!ELEMENT source_block_id (#PCDATA)>
+<!ELEMENT sink_block_id (#PCDATA)>
+<!ELEMENT source_key (#PCDATA)>
+<!ELEMENT sink_key (#PCDATA)>
diff --git a/grc/core/generator/Generator.py b/grc/core/generator/Generator.py
new file mode 100644
index 0000000000..b1fb73b821
--- /dev/null
+++ b/grc/core/generator/Generator.py
@@ -0,0 +1,451 @@
+"""
+Copyright 2008-2015 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
+import sys
+import subprocess
+import tempfile
+import shlex
+import codecs
+import re # for shlex_quote
+from distutils.spawn import find_executable
+
+from Cheetah.Template import Template
+
+from .. import ParseXML, expr_utils
+from ..odict import odict
+
+from ..Constants import (
+ TOP_BLOCK_FILE_MODE, BLOCK_FLAG_NEED_QT_GUI,
+ XTERM_EXECUTABLE, HIER_BLOCK_FILE_MODE, HIER_BLOCKS_LIB_DIR, BLOCK_DTD
+)
+
+from grc.gui import Messages
+
+
+DATA_DIR = os.path.dirname(__file__)
+FLOW_GRAPH_TEMPLATE = os.path.join(DATA_DIR, 'flow_graph.tmpl')
+
+
+class Generator(object):
+ """Adaptor for various generators (uses generate_options)"""
+
+ def __init__(self, flow_graph, file_path):
+ """
+ Initialize the generator object.
+ Determine the file to generate.
+
+ Args:
+ flow_graph: the flow graph object
+ file_path: the path to the grc file
+ """
+ self._generate_options = flow_graph.get_option('generate_options')
+ if self._generate_options == 'hb':
+ generator_cls = HierBlockGenerator
+ elif self._generate_options == 'hb_qt_gui':
+ generator_cls = QtHierBlockGenerator
+ else:
+ generator_cls = TopBlockGenerator
+
+ self._generator = generator_cls(flow_graph, file_path)
+
+ def get_generate_options(self):
+ return self._generate_options
+
+ def __getattr__(self, item):
+ """get all other attrib from actual generator object"""
+ return getattr(self._generator, item)
+
+
+class TopBlockGenerator(object):
+
+ def __init__(self, flow_graph, file_path):
+ """
+ Initialize the top block generator object.
+
+ Args:
+ flow_graph: the flow graph object
+ file_path: the path to write the file to
+ """
+ self._flow_graph = flow_graph
+ self._generate_options = self._flow_graph.get_option('generate_options')
+ self._mode = TOP_BLOCK_FILE_MODE
+ dirname = self._dirname = os.path.dirname(file_path)
+ # Handle the case where the directory is read-only
+ # In this case, use the system's temp directory
+ if not os.access(dirname, os.W_OK):
+ dirname = tempfile.gettempdir()
+ filename = self._flow_graph.get_option('id') + '.py'
+ self._file_path = os.path.join(dirname, filename)
+
+ def get_file_path(self):
+ return self._file_path
+
+ def write(self):
+ """generate output and write it to files"""
+ # Do throttle warning
+ throttling_blocks = filter(lambda b: b.throtteling(), self._flow_graph.get_enabled_blocks())
+ if not throttling_blocks and not self._generate_options.startswith('hb'):
+ Messages.send_warning("This flow graph may not have flow control: "
+ "no audio or RF hardware blocks found. "
+ "Add a Misc->Throttle block to your flow "
+ "graph to avoid CPU congestion.")
+ if len(throttling_blocks) > 1:
+ keys = set(map(lambda b: b.get_key(), throttling_blocks))
+ if len(keys) > 1 and 'blocks_throttle' in keys:
+ Messages.send_warning("This flow graph contains a throttle "
+ "block and another rate limiting block, "
+ "e.g. a hardware source or sink. "
+ "This is usually undesired. Consider "
+ "removing the throttle block.")
+ # Generate
+ for filename, data in self._build_python_code_from_template():
+ with codecs.open(filename, 'w', encoding='utf-8') as fp:
+ fp.write(data)
+ if filename == self.get_file_path():
+ try:
+ os.chmod(filename, self._mode)
+ except:
+ pass
+
+ def get_popen(self):
+ """
+ Execute this python flow graph.
+
+ Returns:
+ a popen object
+ """
+ run_command = self._flow_graph.get_option('run_command')
+ try:
+ run_command = run_command.format(
+ python=shlex_quote(sys.executable),
+ filename=shlex_quote(self.get_file_path()))
+ run_command_args = shlex.split(run_command)
+ except Exception as e:
+ raise ValueError("Can't parse run command {!r}: {}".format(run_command, e))
+
+ # When in no gui mode on linux, use a graphical terminal (looks nice)
+ xterm_executable = find_executable(XTERM_EXECUTABLE)
+ if self._generate_options == 'no_gui' and xterm_executable:
+ run_command_args = [xterm_executable, '-e', run_command]
+
+ # this does not reproduce a shell executable command string, if a graphical
+ # terminal is used. Passing run_command though shlex_quote would do it but
+ # it looks really ugly and confusing in the console panel.
+ Messages.send_start_exec(' '.join(run_command_args))
+
+ return subprocess.Popen(
+ args=run_command_args,
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+ shell=False, universal_newlines=True
+ )
+
+ def _build_python_code_from_template(self):
+ """
+ Convert the flow graph to python code.
+
+ Returns:
+ a string of python code
+ """
+ output = list()
+
+ fg = self._flow_graph
+ title = fg.get_option('title') or fg.get_option('id').replace('_', ' ').title()
+ imports = fg.get_imports()
+ variables = fg.get_variables()
+ parameters = fg.get_parameters()
+ monitors = fg.get_monitors()
+
+ # List of blocks not including variables and imports and parameters and disabled
+ def _get_block_sort_text(block):
+ code = block.get_make().replace(block.get_id(), ' ')
+ try:
+ code += block.get_param('notebook').get_value() # Older gui markup w/ wxgui
+ except:
+ pass
+ try:
+ code += block.get_param('gui_hint').get_value() # Newer gui markup w/ qtgui
+ except:
+ pass
+ return code
+
+ blocks = expr_utils.sort_objects(
+ filter(lambda b: b.get_enabled() and not b.get_bypassed(), fg.blocks),
+ lambda b: b.get_id(), _get_block_sort_text
+ )
+ # List of regular blocks (all blocks minus the special ones)
+ blocks = filter(lambda b: b not in (imports + parameters), blocks)
+
+ for block in blocks:
+ key = block.get_key()
+ file_path = os.path.join(self._dirname, block.get_id() + '.py')
+ if key == 'epy_block':
+ src = block.get_param('_source_code').get_value()
+ output.append((file_path, src))
+ elif key == 'epy_module':
+ src = block.get_param('source_code').get_value()
+ output.append((file_path, src))
+
+ # Filter out virtual sink connections
+ def cf(c):
+ return not (c.is_bus() or c.is_msg() or c.get_sink().get_parent().is_virtual_sink())
+ connections = filter(cf, fg.get_enabled_connections())
+
+ # Get the virtual blocks and resolve their connections
+ virtual = filter(lambda c: c.get_source().get_parent().is_virtual_source(), connections)
+ for connection in virtual:
+ source = connection.get_source().resolve_virtual_source()
+ sink = connection.get_sink()
+ resolved = fg.get_parent().Connection(flow_graph=fg, porta=source, portb=sink)
+ connections.append(resolved)
+ # Remove the virtual connection
+ connections.remove(connection)
+
+ # Bypassing blocks: Need to find all the enabled connections for the block using
+ # the *connections* object rather than get_connections(). Create new connections
+ # that bypass the selected block and remove the existing ones. This allows adjacent
+ # bypassed blocks to see the newly created connections to downstream blocks,
+ # allowing them to correctly construct bypass connections.
+ bypassed_blocks = fg.get_bypassed_blocks()
+ for block in bypassed_blocks:
+ # Get the upstream connection (off of the sink ports)
+ # Use *connections* not get_connections()
+ source_connection = filter(lambda c: c.get_sink() == block.get_sinks()[0], connections)
+ # The source connection should never have more than one element.
+ assert (len(source_connection) == 1)
+
+ # Get the source of the connection.
+ source_port = source_connection[0].get_source()
+
+ # Loop through all the downstream connections
+ for sink in filter(lambda c: c.get_source() == block.get_sources()[0], connections):
+ if not sink.get_enabled():
+ # Ignore disabled connections
+ continue
+ sink_port = sink.get_sink()
+ connection = fg.get_parent().Connection(flow_graph=fg, porta=source_port, portb=sink_port)
+ connections.append(connection)
+ # Remove this sink connection
+ connections.remove(sink)
+ # Remove the source connection
+ connections.remove(source_connection[0])
+
+ # List of connections where each endpoint is enabled (sorted by domains, block names)
+ connections.sort(key=lambda c: (
+ c.get_source().get_domain(), c.get_sink().get_domain(),
+ c.get_source().get_parent().get_id(), c.get_sink().get_parent().get_id()
+ ))
+
+ connection_templates = fg.get_parent().get_connection_templates()
+ msgs = filter(lambda c: c.is_msg(), fg.get_enabled_connections())
+ # List of variable names
+ var_ids = [var.get_id() for var in parameters + variables]
+ # Prepend self.
+ replace_dict = dict([(var_id, 'self.%s' % var_id) for var_id in var_ids])
+ # List of callbacks
+ callbacks = [
+ expr_utils.expr_replace(cb, replace_dict)
+ for cb in sum([block.get_callbacks() for block in fg.get_enabled_blocks()], [])
+ ]
+ # Map var id to callbacks
+ var_id2cbs = dict([
+ (var_id, filter(lambda c: expr_utils.get_variable_dependencies(c, [var_id]), callbacks))
+ for var_id in var_ids
+ ])
+ # Load the namespace
+ namespace = {
+ 'title': title,
+ 'imports': imports,
+ 'flow_graph': fg,
+ 'variables': variables,
+ 'parameters': parameters,
+ 'monitors': monitors,
+ 'blocks': blocks,
+ 'connections': connections,
+ 'connection_templates': connection_templates,
+ 'msgs': msgs,
+ 'generate_options': self._generate_options,
+ 'var_id2cbs': var_id2cbs,
+ }
+ # Build the template
+ t = Template(open(FLOW_GRAPH_TEMPLATE, 'r').read(), namespace)
+ output.append((self.get_file_path(), str(t)))
+ return output
+
+
+class HierBlockGenerator(TopBlockGenerator):
+ """Extends the top block generator to also generate a block XML file"""
+
+ def __init__(self, flow_graph, file_path):
+ """
+ Initialize the hier block generator object.
+
+ Args:
+ flow_graph: the flow graph object
+ file_path: where to write the py file (the xml goes into HIER_BLOCK_LIB_DIR)
+ """
+ TopBlockGenerator.__init__(self, flow_graph, file_path)
+ self._mode = HIER_BLOCK_FILE_MODE
+ self._file_path = os.path.join(HIER_BLOCKS_LIB_DIR,
+ self._flow_graph.get_option('id') + '.py')
+ self._file_path_xml = self._file_path + '.xml'
+
+ def get_file_path_xml(self):
+ return self._file_path_xml
+
+ def write(self):
+ """generate output and write it to files"""
+ TopBlockGenerator.write(self)
+ ParseXML.to_file(self._build_block_n_from_flow_graph_io(), self.get_file_path_xml())
+ ParseXML.validate_dtd(self.get_file_path_xml(), BLOCK_DTD)
+ try:
+ os.chmod(self.get_file_path_xml(), self._mode)
+ except:
+ pass
+
+ def _build_block_n_from_flow_graph_io(self):
+ """
+ Generate a block XML nested data from the flow graph IO
+
+ Returns:
+ a xml node tree
+ """
+ # Extract info from the flow graph
+ block_key = self._flow_graph.get_option('id')
+ parameters = self._flow_graph.get_parameters()
+
+ def var_or_value(name):
+ if name in map(lambda p: p.get_id(), parameters):
+ return "$"+name
+ return name
+
+ # Build the nested data
+ block_n = odict()
+ block_n['name'] = self._flow_graph.get_option('title') or \
+ self._flow_graph.get_option('id').replace('_', ' ').title()
+ block_n['key'] = block_key
+ block_n['category'] = self._flow_graph.get_option('category')
+ block_n['import'] = "from {0} import {0} # grc-generated hier_block".format(
+ self._flow_graph.get_option('id'))
+ # Make data
+ if parameters:
+ block_n['make'] = '{cls}(\n {kwargs},\n)'.format(
+ cls=block_key,
+ kwargs=',\n '.join(
+ '{key}=${key}'.format(key=param.get_id()) for param in parameters
+ ),
+ )
+ else:
+ block_n['make'] = '{cls}()'.format(cls=block_key)
+ # Callback data
+ block_n['callback'] = [
+ 'set_{key}(${key})'.format(key=param.get_id()) for param in parameters
+ ]
+
+ # Parameters
+ block_n['param'] = list()
+ for param in parameters:
+ param_n = odict()
+ param_n['name'] = param.get_param('label').get_value() or param.get_id()
+ param_n['key'] = param.get_id()
+ param_n['value'] = param.get_param('value').get_value()
+ param_n['type'] = 'raw'
+ block_n['param'].append(param_n)
+
+ # Bus stuff
+ if self._flow_graph.get_bussink():
+ block_n['bus_sink'] = '1'
+ if self._flow_graph.get_bussrc():
+ block_n['bus_source'] = '1'
+
+ # Sink/source ports
+ for direction in ('sink', 'source'):
+ block_n[direction] = list()
+ for port in self._flow_graph.get_hier_block_io(direction):
+ port_n = odict()
+ port_n['name'] = port['label']
+ port_n['type'] = port['type']
+ if port['type'] != "message":
+ port_n['vlen'] = var_or_value(port['vlen'])
+ if port['optional']:
+ port_n['optional'] = '1'
+ block_n[direction].append(port_n)
+
+ # More bus stuff
+ bus_struct_sink = self._flow_graph.get_bus_structure_sink()
+ if bus_struct_sink:
+ block_n['bus_structure_sink'] = bus_struct_sink[0].get_param('struct').get_value()
+ bus_struct_src = self._flow_graph.get_bus_structure_src()
+ if bus_struct_src:
+ block_n['bus_structure_source'] = bus_struct_src[0].get_param('struct').get_value()
+
+ # Documentation
+ block_n['doc'] = "\n".join(field for field in (
+ self._flow_graph.get_option('author'),
+ self._flow_graph.get_option('description'),
+ self.get_file_path()
+ ) if field)
+ block_n['grc_source'] = str(self._flow_graph.grc_file_path)
+
+ n = {'block': block_n}
+ return n
+
+
+class QtHierBlockGenerator(HierBlockGenerator):
+
+ def _build_block_n_from_flow_graph_io(self):
+ n = HierBlockGenerator._build_block_n_from_flow_graph_io(self)
+ block_n = n['block']
+
+ if not block_n['name'].upper().startswith('QT GUI'):
+ block_n['name'] = 'QT GUI ' + block_n['name']
+
+ block_n.insert_after('category', 'flags', BLOCK_FLAG_NEED_QT_GUI)
+
+ gui_hint_param = odict()
+ gui_hint_param['name'] = 'GUI Hint'
+ gui_hint_param['key'] = 'gui_hint'
+ gui_hint_param['value'] = ''
+ gui_hint_param['type'] = 'gui_hint'
+ gui_hint_param['hide'] = 'part'
+ block_n['param'].append(gui_hint_param)
+
+ block_n['make'] += (
+ "\n#set $win = 'self.%s' % $id"
+ "\n${gui_hint()($win)}"
+ )
+ return n
+
+
+###########################################################
+# back-port from python3
+###########################################################
+_find_unsafe = re.compile(r'[^\w@%+=:,./-]').search
+
+
+def shlex_quote(s):
+ """Return a shell-escaped version of the string *s*."""
+ if not s:
+ return "''"
+ if _find_unsafe(s) is None:
+ return s
+
+ # use single quotes, and put single quotes into double quotes
+ # the string $'b is then quoted as '$'"'"'b'
+ return "'" + s.replace("'", "'\"'\"'") + "'"
diff --git a/grc/core/generator/__init__.py b/grc/core/generator/__init__.py
new file mode 100644
index 0000000000..fb1e44120a
--- /dev/null
+++ b/grc/core/generator/__init__.py
@@ -0,0 +1 @@
+from .Generator import Generator
diff --git a/grc/core/generator/flow_graph.tmpl b/grc/core/generator/flow_graph.tmpl
new file mode 100644
index 0000000000..bd8025b676
--- /dev/null
+++ b/grc/core/generator/flow_graph.tmpl
@@ -0,0 +1,439 @@
+#if not $generate_options.startswith('hb')
+#!/usr/bin/env python2
+#end if
+# -*- coding: utf-8 -*-
+########################################################
+##Cheetah template - gnuradio_python
+##
+##@param imports the import statements
+##@param flow_graph the flow_graph
+##@param variables the variable blocks
+##@param parameters the parameter blocks
+##@param blocks the signal blocks
+##@param connections the connections
+##@param msgs the msg type connections
+##@param generate_options the type of flow graph
+##@param var_id2cbs variable id map to callback strings
+########################################################
+#def indent($code)
+#set $code = '\n '.join(str($code).splitlines())
+$code#slurp
+#end def
+#import time
+#set $DIVIDER = '#'*50
+$DIVIDER
+# GNU Radio Python Flow Graph
+# Title: $title
+#if $flow_graph.get_option('author')
+# Author: $flow_graph.get_option('author')
+#end if
+#if $flow_graph.get_option('description')
+# Description: $flow_graph.get_option('description')
+#end if
+# Generated: $time.ctime()
+$DIVIDER
+#if $flow_graph.get_option('thread_safe_setters')
+import threading
+#end if
+
+## Call XInitThreads as the _very_ first thing.
+## After some Qt import, it's too late
+#if $generate_options in ('wx_gui', 'qt_gui')
+if __name__ == '__main__':
+ import ctypes
+ import sys
+ if sys.platform.startswith('linux'):
+ try:
+ x11 = ctypes.cdll.LoadLibrary('libX11.so')
+ x11.XInitThreads()
+ except:
+ print "Warning: failed to XInitThreads()"
+
+#end if
+#
+########################################################
+##Create Imports
+########################################################
+#if $flow_graph.get_option('qt_qss_theme')
+#set imports = $sorted(set($imports + ["import os", "import sys"]))
+#end if
+#if any(imp.endswith("# grc-generated hier_block") for imp in $imports)
+import os
+import sys
+#set imports = $filter(lambda i: i not in ("import os", "import sys"), $imports)
+sys.path.append(os.environ.get('GRC_HIER_PATH', os.path.expanduser('~/.grc_gnuradio')))
+
+#end if
+#for $imp in $imports
+##$(imp.replace(" # grc-generated hier_block", ""))
+$imp
+#end for
+########################################################
+##Create Class
+## Write the class declaration for a top or hier block.
+## The parameter names are the arguments to __init__.
+## Determine the absolute icon path (wx gui only).
+## Setup the IO signature (hier block only).
+########################################################
+#set $class_name = $flow_graph.get_option('id')
+#set $param_str = ', '.join(['self'] + ['%s=%s'%(param.get_id(), param.get_make()) for param in $parameters])
+#if $generate_options == 'wx_gui'
+ #import gtk
+ #set $icon = gtk.IconTheme().lookup_icon('gnuradio-grc', 32, 0)
+
+
+class $(class_name)(grc_wxgui.top_block_gui):
+
+ def __init__($param_str):
+ grc_wxgui.top_block_gui.__init__(self, title="$title")
+ #if $icon
+ _icon_path = "$icon.get_filename()"
+ self.SetIcon(wx.Icon(_icon_path, wx.BITMAP_TYPE_ANY))
+ #end if
+#elif $generate_options == 'qt_gui'
+
+
+class $(class_name)(gr.top_block, Qt.QWidget):
+
+ def __init__($param_str):
+ gr.top_block.__init__(self, "$title")
+ Qt.QWidget.__init__(self)
+ self.setWindowTitle("$title")
+ try:
+ self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))
+ except:
+ pass
+ self.top_scroll_layout = Qt.QVBoxLayout()
+ self.setLayout(self.top_scroll_layout)
+ self.top_scroll = Qt.QScrollArea()
+ self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)
+ self.top_scroll_layout.addWidget(self.top_scroll)
+ self.top_scroll.setWidgetResizable(True)
+ self.top_widget = Qt.QWidget()
+ self.top_scroll.setWidget(self.top_widget)
+ self.top_layout = Qt.QVBoxLayout(self.top_widget)
+ self.top_grid_layout = Qt.QGridLayout()
+ self.top_layout.addLayout(self.top_grid_layout)
+
+ self.settings = Qt.QSettings("GNU Radio", "$class_name")
+ self.restoreGeometry(self.settings.value("geometry").toByteArray())
+#elif $generate_options == 'no_gui'
+
+
+class $(class_name)(gr.top_block):
+
+ def __init__($param_str):
+ gr.top_block.__init__(self, "$title")
+#elif $generate_options.startswith('hb')
+ #set $in_sigs = $flow_graph.get_hier_block_stream_io('in')
+ #set $out_sigs = $flow_graph.get_hier_block_stream_io('out')
+
+
+#if $generate_options == 'hb_qt_gui'
+class $(class_name)(gr.hier_block2, Qt.QWidget):
+#else
+class $(class_name)(gr.hier_block2):
+#end if
+#def make_io_sig($io_sigs)
+ #set $size_strs = ['%s*%s'%(io_sig['size'], io_sig['vlen']) for io_sig in $io_sigs]
+ #if len($io_sigs) == 0
+gr.io_signature(0, 0, 0)#slurp
+ #elif len($io_sigs) == 1
+gr.io_signature(1, 1, $size_strs[0])#slurp
+ #else
+gr.io_signaturev($(len($io_sigs)), $(len($io_sigs)), [$(', '.join($size_strs))])#slurp
+ #end if
+#end def
+
+ def __init__($param_str):
+ gr.hier_block2.__init__(
+ self, "$title",
+ $make_io_sig($in_sigs),
+ $make_io_sig($out_sigs),
+ )
+ #for $pad in $flow_graph.get_hier_block_message_io('in')
+ self.message_port_register_hier_in("$pad['label']")
+ #end for
+ #for $pad in $flow_graph.get_hier_block_message_io('out')
+ self.message_port_register_hier_out("$pad['label']")
+ #end for
+ #if $generate_options == 'hb_qt_gui'
+
+ Qt.QWidget.__init__(self)
+ self.top_layout = Qt.QVBoxLayout()
+ self.top_grid_layout = Qt.QGridLayout()
+ self.top_layout.addLayout(self.top_grid_layout)
+ self.setLayout(self.top_layout)
+ #end if
+#end if
+#if $flow_graph.get_option('thread_safe_setters')
+
+ self._lock = threading.RLock()
+#end if
+########################################################
+##Create Parameters
+## Set the parameter to a property of self.
+########################################################
+#if $parameters
+
+ $DIVIDER
+ # Parameters
+ $DIVIDER
+#end if
+#for $param in $parameters
+ $indent($param.get_var_make())
+#end for
+########################################################
+##Create Variables
+########################################################
+#if $variables
+
+ $DIVIDER
+ # Variables
+ $DIVIDER
+#end if
+#for $var in $variables
+ $indent($var.get_var_make())
+#end for
+########################################################
+##Create Message Queues
+########################################################
+#if $msgs
+
+ $DIVIDER
+ # Message Queues
+ $DIVIDER
+#end if
+#for $msg in $msgs
+ $(msg.get_source().get_parent().get_id())_msgq_out = $(msg.get_sink().get_parent().get_id())_msgq_in = gr.msg_queue(2)
+#end for
+########################################################
+##Create Blocks
+########################################################
+#if $blocks
+
+ $DIVIDER
+ # Blocks
+ $DIVIDER
+#end if
+#for $blk in filter(lambda b: b.get_make(), $blocks)
+ #if $blk in $variables
+ $indent($blk.get_make())
+ #else
+ self.$blk.get_id() = $indent($blk.get_make())
+ #if $blk.has_param('alias') and $blk.get_param('alias').get_evaluated()
+ (self.$blk.get_id()).set_block_alias("$blk.get_param('alias').get_evaluated()")
+ #end if
+ #if $blk.has_param('affinity') and $blk.get_param('affinity').get_evaluated()
+ (self.$blk.get_id()).set_processor_affinity($blk.get_param('affinity').get_evaluated())
+ #end if
+ #if (len($blk.get_sources())>0) and $blk.has_param('minoutbuf') and (int($blk.get_param('minoutbuf').get_evaluated()) > 0)
+ (self.$blk.get_id()).set_min_output_buffer($blk.get_param('minoutbuf').get_evaluated())
+ #end if
+ #if (len($blk.get_sources())>0) and $blk.has_param('maxoutbuf') and (int($blk.get_param('maxoutbuf').get_evaluated()) > 0)
+ (self.$blk.get_id()).set_max_output_buffer($blk.get_param('maxoutbuf').get_evaluated())
+ #end if
+ #end if
+#end for
+########################################################
+##Create Connections
+## The port name should be the id of the parent block.
+## However, port names for IO pads should be self.
+########################################################
+#def make_port_sig($port)
+ #if $port.get_parent().get_key() in ('pad_source', 'pad_sink')
+ #set block = 'self'
+ #set key = $flow_graph.get_pad_port_global_key($port)
+ #else
+ #set block = 'self.' + $port.get_parent().get_id()
+ #set key = $port.get_key()
+ #end if
+ #if not $key.isdigit()
+ #set key = repr($key)
+ #end if
+($block, $key)#slurp
+#end def
+#if $connections
+
+ $DIVIDER
+ # Connections
+ $DIVIDER
+#end if
+#for $con in $connections
+ #set global $source = $con.get_source()
+ #set global $sink = $con.get_sink()
+ #include source=$connection_templates[($source.get_domain(), $sink.get_domain())]
+
+#end for
+########################################################
+## QT sink close method reimplementation
+########################################################
+#if $generate_options == 'qt_gui'
+
+ def closeEvent(self, event):
+ self.settings = Qt.QSettings("GNU Radio", "$class_name")
+ self.settings.setValue("geometry", self.saveGeometry())
+ event.accept()
+
+ #if $flow_graph.get_option('qt_qss_theme')
+ def setStyleSheetFromFile(self, filename):
+ try:
+ if not os.path.exists(filename):
+ filename = os.path.join(
+ gr.prefix(), "share", "gnuradio", "themes", filename)
+ with open(filename) as ss:
+ self.setStyleSheet(ss.read())
+ except Exception as e:
+ print >> sys.stderr, e
+ #end if
+#end if
+########################################################
+##Create Callbacks
+## Write a set method for this variable that calls the callbacks
+########################################################
+#for $var in $parameters + $variables
+
+ #set $id = $var.get_id()
+ def get_$(id)(self):
+ return self.$id
+
+ def set_$(id)(self, $id):
+ #if $flow_graph.get_option('thread_safe_setters')
+ with self._lock:
+ self.$id = $id
+ #for $callback in $var_id2cbs[$id]
+ $indent($callback)
+ #end for
+ #else
+ self.$id = $id
+ #for $callback in $var_id2cbs[$id]
+ $indent($callback)
+ #end for
+ #end if
+#end for
+########################################################
+##Create Main
+## For top block code, generate a main routine.
+## Instantiate the top block and run as gui or cli.
+########################################################
+#def make_default($type, $param)
+ #if $type == 'eng_float'
+eng_notation.num_to_str($param.get_make())#slurp
+ #else
+$param.get_make()#slurp
+ #end if
+#end def
+#def make_short_id($param)
+ #set $short_id = $param.get_param('short_id').get_evaluated()
+ #if $short_id
+ #set $short_id = '-' + $short_id
+ #end if
+$short_id#slurp
+#end def
+#if not $generate_options.startswith('hb')
+#set $params_eq_list = list()
+#if $parameters
+
+
+def argument_parser():
+ parser = OptionParser(option_class=eng_option, usage="%prog: [options]")
+ #for $param in $parameters
+ #set $type = $param.get_param('type').get_value()
+ #if $type
+ #silent $params_eq_list.append('%s=options.%s'%($param.get_id(), $param.get_id()))
+ parser.add_option(
+ "$make_short_id($param)", "--$param.get_id().replace('_', '-')", dest="$param.get_id()", type="$type", default=$make_default($type, $param),
+ help="Set $($param.get_param('label').get_evaluated() or $param.get_id()) [default=%default]")
+ #end if
+ #end for
+ return parser
+#end if
+
+
+def main(top_block_cls=$(class_name), options=None):
+ #if $parameters
+ if options is None:
+ options, _ = argument_parser().parse_args()
+ #end if
+ #if $flow_graph.get_option('realtime_scheduling')
+ if gr.enable_realtime_scheduling() != gr.RT_OK:
+ print "Error: failed to enable real-time scheduling."
+ #end if
+
+ #if $generate_options == 'wx_gui'
+ tb = top_block_cls($(', '.join($params_eq_list)))
+ #if $flow_graph.get_option('max_nouts')
+ tb.Run($flow_graph.get_option('run'), $flow_graph.get_option('max_nouts'))
+ #else
+ tb.Start($flow_graph.get_option('run'))
+ #for $m in $monitors
+ (tb.$m.get_id()).start()
+ #end for
+ tb.Wait()
+ #end if
+ #elif $generate_options == 'qt_gui'
+ from distutils.version import StrictVersion
+ if StrictVersion(Qt.qVersion()) >= StrictVersion("4.5.0"):
+ style = gr.prefs().get_string('qtgui', 'style', 'raster')
+ Qt.QApplication.setGraphicsSystem(style)
+ qapp = Qt.QApplication(sys.argv)
+
+ tb = top_block_cls($(', '.join($params_eq_list)))
+ #if $flow_graph.get_option('run')
+ #if $flow_graph.get_option('max_nouts')
+ tb.start($flow_graph.get_option('max_nouts'))
+ #else
+ tb.start()
+ #end if
+ #end if
+ #if $flow_graph.get_option('qt_qss_theme')
+ tb.setStyleSheetFromFile($repr($flow_graph.get_option('qt_qss_theme')))
+ #end if
+ tb.show()
+
+ def quitting():
+ tb.stop()
+ tb.wait()
+ qapp.connect(qapp, Qt.SIGNAL("aboutToQuit()"), quitting)
+ #for $m in $monitors
+ if $m.has_param('en'):
+ if $m.get_param('en').get_value():
+ (tb.$m.get_id()).start()
+ else:
+ sys.stderr.write("Monitor '{0}' does not have an enable ('en') parameter.".format("tb.$m.get_id()"))
+ #end for
+ qapp.exec_()
+ #elif $generate_options == 'no_gui'
+ tb = top_block_cls($(', '.join($params_eq_list)))
+ #set $run_options = $flow_graph.get_option('run_options')
+ #if $run_options == 'prompt'
+ #if $flow_graph.get_option('max_nouts')
+ tb.start($flow_graph.get_option('max_nouts'))
+ #else
+ tb.start()
+ #end if
+ #for $m in $monitors
+ (tb.$m.get_id()).start()
+ #end for
+ try:
+ raw_input('Press Enter to quit: ')
+ except EOFError:
+ pass
+ tb.stop()
+ #elif $run_options == 'run'
+ #if $flow_graph.get_option('max_nouts')
+ tb.start($flow_graph.get_option('max_nouts'))
+ #else
+ tb.start()
+ #end if
+ #end if
+ #for $m in $monitors
+ (tb.$m.get_id()).start()
+ #end for
+ tb.wait()
+ #end if
+
+
+if __name__ == '__main__':
+ main()
+#end if
diff --git a/grc/core/odict.py b/grc/core/odict.py
new file mode 100644
index 0000000000..20970e947c
--- /dev/null
+++ b/grc/core/odict.py
@@ -0,0 +1,111 @@
+"""
+Copyright 2008-2015 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 UserDict import DictMixin
+
+
+class odict(DictMixin):
+
+ def __init__(self, d={}):
+ self._keys = list(d.keys())
+ self._data = dict(d.copy())
+
+ def __setitem__(self, key, value):
+ if key not in self._data:
+ self._keys.append(key)
+ self._data[key] = value
+
+ def __getitem__(self, key):
+ return self._data[key]
+
+ def __delitem__(self, key):
+ del self._data[key]
+ self._keys.remove(key)
+
+ def keys(self):
+ return list(self._keys)
+
+ def copy(self):
+ copy_dict = odict()
+ copy_dict._data = self._data.copy()
+ copy_dict._keys = list(self._keys)
+ return copy_dict
+
+ def insert_after(self, pos_key, key, val):
+ """
+ Insert the new key, value entry after the entry given by the position key.
+ If the positional key is None, insert at the end.
+
+ Args:
+ pos_key: the positional key
+ key: the key for the new entry
+ val: the value for the new entry
+ """
+ index = (pos_key is None) and len(self._keys) or self._keys.index(pos_key)
+ if key in self._keys:
+ raise KeyError('Cannot insert, key "{}" already exists'.format(str(key)))
+ self._keys.insert(index+1, key)
+ self._data[key] = val
+
+ def insert_before(self, pos_key, key, val):
+ """
+ Insert the new key, value entry before the entry given by the position key.
+ If the positional key is None, insert at the begining.
+
+ Args:
+ pos_key: the positional key
+ key: the key for the new entry
+ val: the value for the new entry
+ """
+ index = (pos_key is not None) and self._keys.index(pos_key) or 0
+ if key in self._keys:
+ raise KeyError('Cannot insert, key "{}" already exists'.format(str(key)))
+ self._keys.insert(index, key)
+ self._data[key] = val
+
+ def find(self, key):
+ """
+ Get the value for this key if exists.
+
+ Args:
+ key: the key to search for
+
+ Returns:
+ the value or None
+ """
+ if key in self:
+ return self[key]
+ return None
+
+ def findall(self, key):
+ """
+ Get a list of values for this key.
+
+ Args:
+ key: the key to search for
+
+ Returns:
+ a list of values or empty list
+ """
+ obj = self.find(key)
+ if obj is None:
+ obj = list()
+ if isinstance(obj, list):
+ return obj
+ return [obj]
diff --git a/grc/core/utils/__init__.py b/grc/core/utils/__init__.py
new file mode 100644
index 0000000000..805d6f4aec
--- /dev/null
+++ b/grc/core/utils/__init__.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+""""""
+
+__author__ = "Sebastian Koslowski"
+__email__ = "sebastian.koslowski@gmail.com"
+__copyright__ = "Copyright 2015, Sebastian Koslowski"
diff --git a/grc/core/utils/complexity.py b/grc/core/utils/complexity.py
new file mode 100644
index 0000000000..baa8040db4
--- /dev/null
+++ b/grc/core/utils/complexity.py
@@ -0,0 +1,49 @@
+
+def calculate_flowgraph_complexity(flowgraph):
+ """ Determines the complexity of a flowgraph """
+ dbal = 0
+ for block in flowgraph.blocks:
+ # Skip options block
+ if block.get_key() == 'options':
+ continue
+
+ # Don't worry about optional sinks?
+ sink_list = filter(lambda c: not c.get_optional(), block.get_sinks())
+ source_list = filter(lambda c: not c.get_optional(), block.get_sources())
+ sinks = float(len(sink_list))
+ sources = float(len(source_list))
+ base = max(min(sinks, sources), 1)
+
+ # Port ratio multiplier
+ if min(sinks, sources) > 0:
+ multi = sinks / sources
+ multi = (1 / multi) if multi > 1 else multi
+ else:
+ multi = 1
+
+ # Connection ratio multiplier
+ sink_multi = max(float(sum(map(lambda c: len(c.get_connections()), sink_list)) / max(sinks, 1.0)), 1.0)
+ source_multi = max(float(sum(map(lambda c: len(c.get_connections()), source_list)) / max(sources, 1.0)), 1.0)
+ dbal = dbal + (base * multi * sink_multi * source_multi)
+
+ blocks = float(len(flowgraph.blocks))
+ connections = float(len(flowgraph.connections))
+ elements = blocks + connections
+ disabled_connections = len(filter(lambda c: not c.get_enabled(), flowgraph.connections))
+ variables = elements - blocks - connections
+ enabled = float(len(flowgraph.get_enabled_blocks()))
+
+ # Disabled multiplier
+ if enabled > 0:
+ disabled_multi = 1 / (max(1 - ((blocks - enabled) / max(blocks, 1)), 0.05))
+ else:
+ disabled_multi = 1
+
+ # Connection multiplier (How many connections )
+ if (connections - disabled_connections) > 0:
+ conn_multi = 1 / (max(1 - (disabled_connections / max(connections, 1)), 0.05))
+ else:
+ conn_multi = 1
+
+ final = round(max((dbal - 1) * disabled_multi * conn_multi * connections, 0.0) / 1000000, 6)
+ return final