diff options
Diffstat (limited to 'grc')
-rw-r--r-- | grc/CMakeLists.txt | 10 | ||||
-rw-r--r-- | grc/__main__.py | 20 | ||||
-rw-r--r-- | grc/base/Block.py | 542 | ||||
-rw-r--r-- | grc/base/CMakeLists.txt | 43 | ||||
-rw-r--r-- | grc/base/Constants.py | 50 | ||||
-rw-r--r-- | grc/base/FlowGraph.py | 484 | ||||
-rw-r--r-- | grc/base/Param.py | 205 | ||||
-rw-r--r-- | grc/base/Platform.py | 271 | ||||
-rw-r--r-- | grc/base/Port.py | 136 | ||||
-rw-r--r-- | grc/base/__init__.py | 20 | ||||
-rw-r--r-- | grc/blocks/blks2_error_rate.xml | 69 | ||||
-rw-r--r-- | grc/blocks/blks2_packet_decoder.xml | 75 | ||||
-rw-r--r-- | grc/blocks/blks2_packet_encoder.xml | 119 | ||||
-rw-r--r-- | grc/blocks/blks2_selector.xml | 97 | ||||
-rw-r--r-- | grc/blocks/blks2_tcp_sink.xml | 89 | ||||
-rw-r--r-- | grc/blocks/blks2_tcp_source.xml | 89 | ||||
-rw-r--r-- | grc/blocks/blks2_valve.xml | 72 | ||||
-rw-r--r-- | grc/blocks/block_tree.xml | 17 | ||||
-rw-r--r-- | grc/blocks/xmlrpc_client.xml | 42 | ||||
-rw-r--r-- | grc/blocks/xmlrpc_server.xml | 41 | ||||
-rwxr-xr-x | grc/checks.py | 80 | ||||
-rw-r--r-- | grc/core/Block.py | 846 | ||||
-rw-r--r-- | grc/core/CMakeLists.txt (renamed from grc/examples/CMakeLists.txt) | 24 | ||||
-rw-r--r-- | grc/core/Config.py | 55 | ||||
-rw-r--r-- | grc/core/Connection.py (renamed from grc/base/Connection.py) | 83 | ||||
-rw-r--r-- | grc/core/Constants.py (renamed from grc/python/Constants.py) | 85 | ||||
-rw-r--r-- | grc/core/Element.py (renamed from grc/base/Element.py) | 53 | ||||
-rw-r--r-- | grc/core/FlowGraph.py | 594 | ||||
-rw-r--r-- | grc/core/Messages.py (renamed from grc/gui/Messages.py) | 53 | ||||
-rw-r--r-- | grc/core/Param.py | 740 | ||||
-rw-r--r-- | grc/core/ParseXML.py (renamed from grc/base/ParseXML.py) | 22 | ||||
-rw-r--r-- | grc/core/Platform.py | 319 | ||||
-rw-r--r-- | grc/core/Port.py | 404 | ||||
-rw-r--r-- | grc/core/__init__.py (renamed from grc/grc_gnuradio/__init__.py) | 0 | ||||
-rw-r--r-- | grc/core/block.dtd (renamed from grc/python/block.dtd) | 0 | ||||
-rw-r--r-- | grc/core/block_tree.dtd (renamed from grc/base/block_tree.dtd) | 0 | ||||
-rw-r--r-- | grc/core/default_flow_graph.grc (renamed from grc/python/default_flow_graph.grc) | 0 | ||||
-rw-r--r-- | grc/core/domain.dtd (renamed from grc/base/domain.dtd) | 0 | ||||
-rw-r--r-- | grc/core/flow_graph.dtd (renamed from grc/base/flow_graph.dtd) | 0 | ||||
-rw-r--r-- | grc/core/generator/CMakeLists.txt (renamed from grc/grc_gnuradio/CMakeLists.txt) | 17 | ||||
-rw-r--r-- | grc/core/generator/FlowGraphProxy.py | 126 | ||||
-rw-r--r-- | grc/core/generator/Generator.py (renamed from grc/python/Generator.py) | 195 | ||||
-rw-r--r-- | grc/core/generator/__init__.py | 18 | ||||
-rw-r--r-- | grc/core/generator/flow_graph.tmpl (renamed from grc/python/flow_graph.tmpl) | 9 | ||||
-rw-r--r-- | grc/core/utils/CMakeLists.txt (renamed from grc/grc_gnuradio/blks2/__init__.py) | 16 | ||||
-rw-r--r-- | grc/core/utils/__init__.py | 22 | ||||
-rw-r--r-- | grc/core/utils/complexity.py | 49 | ||||
-rw-r--r-- | grc/core/utils/epy_block_io.py (renamed from grc/python/epy_block_io.py) | 15 | ||||
-rw-r--r-- | grc/core/utils/expr_utils.py (renamed from grc/python/expr_utils.py) | 57 | ||||
-rw-r--r-- | grc/core/utils/extract_docs.py (renamed from grc/python/extract_docs.py) | 47 | ||||
-rw-r--r-- | grc/core/utils/odict.py (renamed from grc/base/odict.py) | 18 | ||||
-rw-r--r-- | grc/cpp/README | 5 | ||||
-rw-r--r-- | grc/examples/simple/variable_config.grc | 561 | ||||
-rw-r--r-- | grc/examples/xmlrpc/readme.txt | 18 | ||||
-rw-r--r-- | grc/examples/xmlrpc/xmlrpc_client.grc | 428 | ||||
-rw-r--r-- | grc/examples/xmlrpc/xmlrpc_client_script.py | 23 | ||||
-rw-r--r-- | grc/examples/xmlrpc/xmlrpc_server.grc | 908 | ||||
-rw-r--r-- | grc/grc_gnuradio/README | 11 | ||||
-rw-r--r-- | grc/grc_gnuradio/blks2/error_rate.py | 140 | ||||
-rw-r--r-- | grc/grc_gnuradio/blks2/packet.py | 257 | ||||
-rw-r--r-- | grc/grc_gnuradio/blks2/selector.py | 142 | ||||
-rw-r--r-- | grc/grc_gnuradio/blks2/tcp.py | 70 | ||||
-rw-r--r-- | grc/gui/ActionHandler.py | 111 | ||||
-rw-r--r-- | grc/gui/Actions.py | 8 | ||||
-rw-r--r-- | grc/gui/Bars.py | 1 | ||||
-rw-r--r-- | grc/gui/Block.py | 48 | ||||
-rw-r--r-- | grc/gui/BlockTreeWindow.py | 58 | ||||
-rw-r--r-- | grc/gui/CMakeLists.txt | 32 | ||||
-rw-r--r-- | grc/gui/Colors.py | 3 | ||||
-rw-r--r-- | grc/gui/Config.py | 74 | ||||
-rw-r--r-- | grc/gui/Connection.py | 18 | ||||
-rw-r--r-- | grc/gui/Constants.py | 53 | ||||
-rw-r--r-- | grc/gui/Dialogs.py | 33 | ||||
-rw-r--r-- | grc/gui/DrawingArea.py | 27 | ||||
-rw-r--r-- | grc/gui/Element.py | 16 | ||||
-rw-r--r-- | grc/gui/Executor.py | 123 | ||||
-rw-r--r-- | grc/gui/FileDialogs.py | 15 | ||||
-rw-r--r-- | grc/gui/FlowGraph.py | 83 | ||||
-rw-r--r-- | grc/gui/MainWindow.py | 30 | ||||
-rw-r--r-- | grc/gui/NotebookPage.py | 6 | ||||
-rw-r--r-- | grc/gui/Param.py | 15 | ||||
-rw-r--r-- | grc/gui/Platform.py | 53 | ||||
-rw-r--r-- | grc/gui/Port.py | 43 | ||||
-rw-r--r-- | grc/gui/Preferences.py | 6 | ||||
-rw-r--r-- | grc/gui/PropsDialog.py | 38 | ||||
-rwxr-xr-x | grc/main.py | 55 | ||||
-rw-r--r-- | grc/python/Block.py | 323 | ||||
-rw-r--r-- | grc/python/CMakeLists.txt | 44 | ||||
-rw-r--r-- | grc/python/Connection.py | 45 | ||||
-rw-r--r-- | grc/python/FlowGraph.py | 338 | ||||
-rw-r--r-- | grc/python/Param.py | 433 | ||||
-rw-r--r-- | grc/python/Platform.py | 174 | ||||
-rw-r--r-- | grc/python/Port.py | 268 | ||||
-rw-r--r-- | grc/python/__init__.py | 1 | ||||
-rw-r--r-- | grc/scripts/CMakeLists.txt | 2 | ||||
-rw-r--r-- | grc/scripts/freedesktop/CMakeLists.txt (renamed from grc/freedesktop/CMakeLists.txt) | 0 | ||||
-rw-r--r-- | grc/scripts/freedesktop/README (renamed from grc/freedesktop/README) | 0 | ||||
-rwxr-xr-x | grc/scripts/freedesktop/convert.sh (renamed from grc/freedesktop/convert.sh) | 0 | ||||
-rw-r--r-- | grc/scripts/freedesktop/gnuradio-grc.desktop (renamed from grc/freedesktop/gnuradio-grc.desktop) | 0 | ||||
-rw-r--r-- | grc/scripts/freedesktop/gnuradio-grc.xml (renamed from grc/freedesktop/gnuradio-grc.xml) | 0 | ||||
-rw-r--r-- | grc/scripts/freedesktop/gnuradio_logo_icon-square.svg (renamed from grc/freedesktop/gnuradio_logo_icon-square.svg) | 0 | ||||
-rw-r--r-- | grc/scripts/freedesktop/grc-icon-128.png (renamed from grc/freedesktop/grc-icon-128.png) | bin | 4758 -> 4758 bytes | |||
-rw-r--r-- | grc/scripts/freedesktop/grc-icon-16.png (renamed from grc/freedesktop/grc-icon-16.png) | bin | 537 -> 537 bytes | |||
-rw-r--r-- | grc/scripts/freedesktop/grc-icon-24.png (renamed from grc/freedesktop/grc-icon-24.png) | bin | 840 -> 840 bytes | |||
-rw-r--r-- | grc/scripts/freedesktop/grc-icon-256.png (renamed from grc/freedesktop/grc-icon-256.png) | bin | 9762 -> 9762 bytes | |||
-rw-r--r-- | grc/scripts/freedesktop/grc-icon-32.png (renamed from grc/freedesktop/grc-icon-32.png) | bin | 1148 -> 1148 bytes | |||
-rw-r--r-- | grc/scripts/freedesktop/grc-icon-48.png (renamed from grc/freedesktop/grc-icon-48.png) | bin | 1796 -> 1796 bytes | |||
-rw-r--r-- | grc/scripts/freedesktop/grc-icon-64.png (renamed from grc/freedesktop/grc-icon-64.png) | bin | 2355 -> 2355 bytes | |||
-rw-r--r-- | grc/scripts/freedesktop/grc_setup_freedesktop.in (renamed from grc/freedesktop/grc_setup_freedesktop.in) | 0 | ||||
-rwxr-xr-x | grc/scripts/gnuradio-companion | 124 | ||||
-rw-r--r-- | grc/todo.txt | 69 |
111 files changed, 4290 insertions, 7481 deletions
diff --git a/grc/CMakeLists.txt b/grc/CMakeLists.txt index 859b9e9045..4c782a7f7d 100644 --- a/grc/CMakeLists.txt +++ b/grc/CMakeLists.txt @@ -96,8 +96,10 @@ install( COMPONENT "grc" ) +file(GLOB py_files "*.py") + GR_PYTHON_INSTALL( - FILES __init__.py + FILES ${py_files} DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc COMPONENT "grc" ) @@ -133,13 +135,9 @@ endif(WIN32) ######################################################################## # Add subdirectories ######################################################################## -add_subdirectory(base) add_subdirectory(blocks) -add_subdirectory(freedesktop) -add_subdirectory(grc_gnuradio) add_subdirectory(gui) -add_subdirectory(python) +add_subdirectory(core) add_subdirectory(scripts) -add_subdirectory(examples) endif(ENABLE_GRC) diff --git a/grc/__main__.py b/grc/__main__.py new file mode 100644 index 0000000000..899d0195d1 --- /dev/null +++ b/grc/__main__.py @@ -0,0 +1,20 @@ +# Copyright 2016 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 .main import main + +main() diff --git a/grc/base/Block.py b/grc/base/Block.py deleted file mode 100644 index 77c3145173..0000000000 --- a/grc/base/Block.py +++ /dev/null @@ -1,542 +0,0 @@ -""" -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 -""" - -from . import odict -from . Constants import ADVANCED_PARAM_TAB, DEFAULT_PARAM_TAB -from . Constants import BLOCK_FLAG_THROTTLE, BLOCK_FLAG_DISABLE_BYPASS -from . Constants import BLOCK_ENABLED, BLOCK_BYPASSED, BLOCK_DISABLED -from Element import Element - -from Cheetah.Template import Template -from UserDict import UserDict -from itertools import imap - - -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 python data type. - """ - - def __init__(self, param): - UserDict.__init__(self) - self._param = param - if param.is_enum(): - for key in param.get_opt_keys(): - self[key] = str(param.get_opt(key)) - - def __str__(self): - return str(self._param.to_code()) - - def __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 "%s" not found in %s.'%(key, _get_keys(lst)) - - -class Block(Element): - - 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 - """ - #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 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 "%s" already exists in params'%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 "%s" already exists in sources'%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 "%s" already exists in sinks'%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 - }) - )) - - 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 - %s - %s(%s)'%(self.get_id(), self.get_name(), self.get_key()) - - def get_id(self): return self.get_param('id').get_value() - def is_block(self): return True - def get_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_doc(self): return '' - 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: %s\n %s" % (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 port_controller_modify(self, direction): - """ - Change the port controller. - - Args: - direction: +1 or -1 - - Returns: - true for change - """ - return False - - 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 (not 'bus' 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 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 - """ - get_hash = lambda: hash(tuple(map(hash, self.get_params()))) - my_hash = 0 - while 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 = 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/base/CMakeLists.txt b/grc/base/CMakeLists.txt deleted file mode 100644 index bdc8a5006f..0000000000 --- a/grc/base/CMakeLists.txt +++ /dev/null @@ -1,43 +0,0 @@ -# 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 - odict.py - ParseXML.py - Block.py - Connection.py - Constants.py - Element.py - FlowGraph.py - Param.py - Platform.py - Port.py - __init__.py - DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/base - COMPONENT "grc" -) - -install(FILES - block_tree.dtd - domain.dtd - flow_graph.dtd - DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/base - COMPONENT "grc" -) diff --git a/grc/base/Constants.py b/grc/base/Constants.py deleted file mode 100644 index 1e83de63b5..0000000000 --- a/grc/base/Constants.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Copyright 2008, 2009 Free Software Foundation, Inc. -This file is part of GNU Radio - -GNU Radio Companion is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -GNU Radio Companion is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA -""" - -import os - -#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') - -# 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 diff --git a/grc/base/FlowGraph.py b/grc/base/FlowGraph.py deleted file mode 100644 index 0398dfd011..0000000000 --- a/grc/base/FlowGraph.py +++ /dev/null @@ -1,484 +0,0 @@ -""" -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 time -from operator import methodcaller -from itertools import ifilter - -from .. gui import Messages - -from . import odict -from .Element import Element -from .Constants import FLOW_GRAPH_FILE_FORMAT_VERSION - - -class FlowGraph(Element): - - def __init__(self, platform): - """ - Make a flow graph from the arguments. - - Args: - platform: a platforms with blocks and contrcutors - - Returns: - the flow graph object - """ - #initialize - Element.__init__(self, platform) - self._elements = [] - self._timestamp = time.ctime() - #inital blank import - self.import_data() - - def _get_unique_id(self, base_id=''): - """ - Get a unique id starting with the base id. - - Args: - base_id: the id starts with this and appends a count - - Returns: - a unique id - """ - index = 0 - while True: - id = '%s_%d' % (base_id, index) - index += 1 - #make sure that the id is not used by another block - if not filter(lambda b: b.get_id() == id, self.get_blocks()): return id - - def __str__(self): - return 'FlowGraph - %s(%s)' % (self.get_option('title'), self.get_option('id')) - - def get_complexity(self): - """ - Determines the complexity of a flowgraph - """ - dbal = 0 - block_list = self.get_blocks() - for block in block_list: - # 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) - - elements = float(len(self.get_elements())) - connections = float(len(self.get_connections())) - disabled_connections = len(filter(lambda c: not c.get_enabled(), self.get_connections())) - blocks = float(len(block_list)) - variables = elements - blocks - connections - enabled = float(len(self.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 - - def rewrite(self): - def refactor_bus_structure(): - - for block in self.get_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); - - for child in self.get_children(): child.rewrite() - refactor_bus_structure() - - 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() - - def is_flow_graph(self): return True - - ############################################## - ## Access Elements - ############################################## - def get_block(self, id): - for block in self.iter_blocks(): - if block.get_id() == id: - return block - raise KeyError('No block with ID {0!r}'.format(id)) - - def iter_blocks(self): - return ifilter(methodcaller('is_block'), self.get_elements()) - - def get_blocks(self): - return list(self.iter_blocks()) - - def iter_connections(self): - return ifilter(methodcaller('is_connection'), self.get_elements()) - - def get_connections(self): - return list(self.iter_connections()) - - 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._elements.count(self._options_block) - if not options_block_count: - self._elements.append(self._options_block) - for i in range(options_block_count-1): - self._elements.remove(self._options_block) - return self._elements - - get_children = get_elements - - def iter_enabled_blocks(self): - """ - Get an iterator of all blocks that are enabled and not bypassed. - """ - return ifilter(methodcaller('get_enabled'), self.iter_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.iter_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.get_connections()) - - 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 - """ - if key not in self.get_parent().get_block_keys(): return None - block = self.get_parent().get_new_block(self, key) - self.get_elements().append(block); - if block._bussify_sink: - block.bussify({'name':'bus','type':'bus'}, 'sink') - if block._bussify_source: - block.bussify({'name':'bus','type':'bus'}, 'source') - 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.get_parent().Connection(flow_graph=self, porta=porta, portb=portb) - self.get_elements().append(connection) - return connection - - def remove_element(self, element): - """ - Remove the element from the list of elements. - If the element is a port, remove the whole block. - If the element is a block, remove its connections. - If the element is a connection, just remove the connection. - """ - if element not in self.get_elements(): return - #found a port, set to parent signal block - if element.is_port(): - element = element.get_parent() - #remove block, remove all involved connections - if element.is_block(): - for port in element.get_ports(): - map(self.remove_element, port.get_connections()) - if element.is_connection(): - 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.get_elements().remove(element) - - def evaluate(self, expr): - """ - Evaluate the expression. - - Args: - expr: the string expression - @throw NotImplementedError - """ - raise NotImplementedError - - ############################################## - ## 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.iter_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.get_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 - self._elements = list() # remove previous elements - # 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: # 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) - Messages.send_error_msg_load('Block key "%s" not found' % key) - - block.import_data(block_n) - - self.rewrite() # evaluate stuff like nports before adding connections - - # 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 = _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 %s(%s) and %s(%s) could not be made.\n\t%s' % ( - source_block_id, source_key, sink_block_id, sink_key, e)) - errors = True - - self.rewrite() # global rewrite - return errors - - -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'}) - block.get_params().append(block.get_parent().get_parent().Param(block=block, n=new_param_n)) - - -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/base/Param.py b/grc/base/Param.py deleted file mode 100644 index 34dd36e790..0000000000 --- a/grc/base/Param.py +++ /dev/null @@ -1,205 +0,0 @@ -""" -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 -""" - -from . import odict -from Element import Element - -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 "%s" not found in %s.'%(key, _get_keys(lst)) - -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 "%s" into key:value'%opt - #test against repeated keys - if self._opts.has_key(key): - raise Exception, 'Key "%s" already exists in option'%key - #store the option - self._opts[key] = value - - def __str__(self): return 'Option %s(%s)'%(self.get_name(), self.get_key()) - def get_name(self): return self._name - def get_key(self): return self._key - - ############################################## - # Access Opts - ############################################## - def get_opt_keys(self): return self._opts.keys() - def get_opt(self, key): return self._opts[key] - def get_opts(self): return self._opts.values() - -class Param(Element): - - 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 not self._tab_label 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() - 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 "%s" already exists in options'%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 "%s" are not unique.'%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 "%s" are not identical across all options.'%opt_keys - #if a value is specified, it must be in the options keys - self._value = value if value or value in self.get_option_keys() else self.get_option_keys()[0] - if self.get_value() not in self.get_option_keys(): - raise Exception, 'The value "%s" is not in the possible values of "%s".'%(self.get_value(), self.get_option_keys()) - else: self._value = value or '' - self._default = value - - 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 "%s" is not a possible type.'%self.get_type()) - - def get_evaluated(self): raise NotImplementedError - - def to_code(self): - """ - Convert the value to code. - @throw NotImplementedError - """ - raise NotImplementedError - - def get_types(self): - """ - Get a list of all possible param types. - @throw NotImplementedError - """ - raise NotImplementedError - - def get_color(self): return '#FFFFFF' - def __str__(self): return 'Param - %s(%s)'%(self.get_name(), self.get_key()) - def is_param(self): return True - def get_name(self): return self.get_parent().resolve_dependencies(self._name).strip() - def get_key(self): return self._key - def get_hide(self): return self.get_parent().resolve_dependencies(self._hide).strip() - - def get_value(self): - value = self._value - if self.is_enum() and value not in self.get_option_keys(): - value = self.get_option_keys()[0] - self.set_value(value) - return value - - def set_value(self, value): self._value = str(value) #must be a string - - def set_default(self, value): - if self._default == self._value: - self.set_value(value) - self._default = str(value) - - def get_type(self): return self.get_parent().resolve_dependencies(self._type) - def get_tab_label(self): return self._tab_label - def is_enum(self): return self._type == 'enum' - - def __repr__(self): - """ - Get the repr (nice string format) for this param. - Just return the value (special case enum). - Derived classes can handle complex formatting. - - Returns: - the string representation - """ - if self.is_enum(): return self.get_option(self.get_value()).get_name() - return self.get_value() - - ############################################## - # 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/base/Platform.py b/grc/base/Platform.py deleted file mode 100644 index 0cc3fcf1dd..0000000000 --- a/grc/base/Platform.py +++ /dev/null @@ -1,271 +0,0 @@ -""" -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 os -import sys -from .. base import ParseXML, odict -from Element import Element as _Element -from FlowGraph import FlowGraph as _FlowGraph -from Connection import Connection as _Connection -from Block import Block as _Block -from Port import Port as _Port -from Param import Param as _Param -from Constants import BLOCK_TREE_DTD, FLOW_GRAPH_DTD, DOMAIN_DTD - - -class Platform(_Element): - def __init__(self, name, version, key, - block_paths, block_dtd, default_flow_graph, generator, - license='', website=None, colors=None): - """ - Make a platform from the arguments. - - 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 - - Returns: - a platform object - """ - _Element.__init__(self) - self._name = name - # Save the verion string to the first - 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 = key - self._license = license - self._website = website - self._block_paths = list(set(block_paths)) - self._block_dtd = block_dtd - self._default_flow_graph = default_flow_graph - self._generator = generator - self._colors = colors or [] - #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() - - 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%s\n\tIgnoring: %s' % (e, xml_file) - - def iter_xml_files(self): - """Iterator for block descriptions and category trees""" - get_path = lambda x: os.path.abspath(os.path.expanduser(x)) - for block_path in map(get_path, 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 "%s" already exists.\n\tIgnoring: %s' % (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: %s' % xml_file - return - if key in self.get_domains(): # test against repeated keys - print >> sys.stderr, 'Warning: Domain with key "%s" already exists.\n\tIgnoring: %s' % (key, xml_file) - return - - to_bool = lambda s, d: d if s is None else \ - s.lower() not in ('false', 'off', '0', '') - - 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 "%s" for domain "%s" ' % (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%s' % xml_file - elif key in self._connection_templates: - print >> sys.stderr, 'Warning: Connection template "%s" already exists.\n\t%s' % (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 "%s" not found when loading category tree.' % (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 - %s(%s)'%(self.get_key(), self.get_name()) - - def is_platform(self): return True - - 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 - - ############################################## - # Constructors - ############################################## - FlowGraph = _FlowGraph - Connection = _Connection - Block = _Block - Port = _Port - Param = _Param diff --git a/grc/base/Port.py b/grc/base/Port.py deleted file mode 100644 index d1c35163f5..0000000000 --- a/grc/base/Port.py +++ /dev/null @@ -1,136 +0,0 @@ -""" -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 -""" - -from Element import Element -from . Constants import GR_STREAM_DOMAIN, GR_MESSAGE_DOMAIN - -class Port(Element): - - 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 source or sink - """ - #build the port - Element.__init__(self, block) - #grab the data - self._name = n['name'] - self._key = n['key'] - self._type = n['type'] or '' - self._domain = n['domain'] - self._hide = n.find('hide') or '' - self._dir = dir - self._hide_evaluated = False # updated on rewrite() - - def validate(self): - """ - Validate the port. - The port must be non-empty and type must a possible type. - """ - Element.validate(self) - if self.get_type() not in self.get_types(): - self.add_error_message('Type "%s" is not a possible type.' % 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 "%s" is not registered.' % self.get_domain()) - - def rewrite(self): - """resolve dependencies in for type and hide""" - 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 __str__(self): - if self.is_source(): - return 'Source - %s(%s)'%(self.get_name(), self.get_key()) - if self.is_sink(): - return 'Sink - %s(%s)'%(self.get_name(), self.get_key()) - - def get_types(self): - """ - Get a list of all possible port types. - @throw NotImplementedError - """ - raise NotImplementedError - - def is_port(self): return True - def get_color(self): return '#FFFFFF' - 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 - def is_sink(self): return self._dir == 'sink' - 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().get_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/base/__init__.py b/grc/base/__init__.py deleted file mode 100644 index 2682db8125..0000000000 --- a/grc/base/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -Copyright 2009 Free Software Foundation, Inc. -This file is part of GNU Radio - -GNU Radio Companion is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -GNU Radio Companion is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA -""" - -from odict import odict diff --git a/grc/blocks/blks2_error_rate.xml b/grc/blocks/blks2_error_rate.xml deleted file mode 100644 index 91a303206d..0000000000 --- a/grc/blocks/blks2_error_rate.xml +++ /dev/null @@ -1,69 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Error Rate: -## Custom blks2 block -################################################### - --> -<block> - <name>Error Rate</name> - <key>blks2_error_rate</key> - <import>from grc_gnuradio import blks2 as grc_blks2</import> - <make>grc_blks2.error_rate( - type=$type, - win_size=$win_size, - bits_per_symbol=$bits_per_symbol, -)</make> - <param> - <name>Type</name> - <key>type</key> - <type>enum</type> - <option> - <name>Bit Error Rate</name> - <key>'BER'</key> - <opt>hide_bps:</opt> - </option> - <option> - <name>Symbol Error Rate</name> - <key>'SER'</key> - <opt>hide_bps:all</opt> - </option> - </param> - <param> - <name>Window Size</name> - <key>win_size</key> - <value>1000</value> - <type>int</type> - </param> - <param> - <name>Bits per Symbol</name> - <key>bits_per_symbol</key> - <value>2</value> - <type>int</type> - <hide>$type.hide_bps</hide> - </param> - <sink> - <name>ref</name> - <type>byte</type> - </sink> - <sink> - <name>in</name> - <type>byte</type> - </sink> - <source> - <name>out</name> - <type>float</type> - </source> - <doc> -Calculate the bit error rate (BER) or the symbol error rate (SER) over a number of samples given by the window size. \ -The actual window size will start at size one and grow to the full window size as new samples arrive. \ -Once the window has reached full size, old samples are shifted out of the window and new samples shfited in. - -The error block compares the input byte stream to the reference byte stream. \ -For example, the reference byte stream could be the input to a modulator, \ -and the input byte stream could be the output of a modulator. - -Each byte in the incoming stream represents one symbol. \ -The bits per symbol parameter is only useful for calculating the BER. - </doc> -</block> diff --git a/grc/blocks/blks2_packet_decoder.xml b/grc/blocks/blks2_packet_decoder.xml deleted file mode 100644 index 07b0d1f2eb..0000000000 --- a/grc/blocks/blks2_packet_decoder.xml +++ /dev/null @@ -1,75 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Packet Decoder -################################################### - --> -<block> - <name>Packet Decoder</name> - <key>blks2_packet_decoder</key> - <import>from grc_gnuradio import blks2 as grc_blks2</import> - <make>grc_blks2.packet_demod_$(type.fcn)(grc_blks2.packet_decoder( - access_code=$access_code, - threshold=$threshold, - callback=lambda ok, payload: self.$(id).recv_pkt(ok, payload), - ), -)</make> - <param> - <name>Output Type</name> - <key>type</key> - <value>float</value> - <type>enum</type> - <option> - <name>Complex</name> - <key>complex</key> - <opt>fcn:c</opt> - </option> - <option> - <name>Float</name> - <key>float</key> - <opt>fcn:f</opt> - </option> - <option> - <name>Int</name> - <key>int</key> - <opt>fcn:i</opt> - </option> - <option> - <name>Short</name> - <key>short</key> - <opt>fcn:s</opt> - </option> - <option> - <name>Byte</name> - <key>byte</key> - <opt>fcn:b</opt> - </option> - </param> - <param> - <name>Access Code</name> - <key>access_code</key> - <value></value> - <type>string</type> - </param> - <param> - <name>Threshold</name> - <key>threshold</key> - <value>-1</value> - <type>int</type> - </param> - <sink> - <name>in</name> - <type>byte</type> - </sink> - <source> - <name>out</name> - <type>$type</type> - </source> - <doc> -Packet decoder block, for use with the gnuradio demodulator blocks: gmsk, psk, qam. - -Access Code: string of 1's and 0's, leave blank for automatic. - -Threshold: -1 for automatic. - </doc> -</block> diff --git a/grc/blocks/blks2_packet_encoder.xml b/grc/blocks/blks2_packet_encoder.xml deleted file mode 100644 index 88e1ba350c..0000000000 --- a/grc/blocks/blks2_packet_encoder.xml +++ /dev/null @@ -1,119 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Packet Encoder -################################################### - --> -<block> - <name>Packet Encoder</name> - <key>blks2_packet_encoder</key> - <import>from grc_gnuradio import blks2 as grc_blks2</import> - <make>grc_blks2.packet_mod_$(type.fcn)(grc_blks2.packet_encoder( - samples_per_symbol=$samples_per_symbol, - bits_per_symbol=$bits_per_symbol, - preamble=$preamble, - access_code=$access_code, - pad_for_usrp=$pad_for_usrp, - ), - payload_length=$payload_length, -)</make> - <param> - <name>Input Type</name> - <key>type</key> - <value>float</value> - <type>enum</type> - <option> - <name>Complex</name> - <key>complex</key> - <opt>fcn:c</opt> - </option> - <option> - <name>Float</name> - <key>float</key> - <opt>fcn:f</opt> - </option> - <option> - <name>Int</name> - <key>int</key> - <opt>fcn:i</opt> - </option> - <option> - <name>Short</name> - <key>short</key> - <opt>fcn:s</opt> - </option> - <option> - <name>Byte</name> - <key>byte</key> - <opt>fcn:b</opt> - </option> - </param> - <param> - <name>Samples/Symbol</name> - <key>samples_per_symbol</key> - <type>int</type> - </param> - <param> - <name>Bits/Symbol</name> - <key>bits_per_symbol</key> - <type>int</type> - </param> - <param> - <name>Preamble</name> - <key>preamble</key> - <value></value> - <type>string</type> - </param> - <param> - <name>Access Code</name> - <key>access_code</key> - <value></value> - <type>string</type> - </param> - <param> - <name>Pad for USRP</name> - <key>pad_for_usrp</key> - <type>enum</type> - <option> - <name>Yes</name> - <key>True</key> - </option> - <option> - <name>No</name> - <key>False</key> - </option> - </param> - <param> - <name>Payload Length</name> - <key>payload_length</key> - <value>0</value> - <type>int</type> - </param> - <sink> - <name>in</name> - <type>$type</type> - </sink> - <source> - <name>out</name> - <type>byte</type> - </source> - <doc> -Packet encoder block, for use with the gnuradio modulator blocks: gmsk, dpsk, qam. - -Preamble: string of 1's and 0's, leave blank for automatic. - -Access Code: string of 1's and 0's, leave blank for automatic. - -Payload Length: 0 for automatic. - -Bits/Symbol should be set accordingly: - gmsk -> 1 - dbpsk -> 1 - dqpsk -> 2 - d8psk -> 3 - qam8 -> 3 - qam16 -> 4 - qam64 -> 6 - qam256 -> 8 - </doc> -</block> diff --git a/grc/blocks/blks2_selector.xml b/grc/blocks/blks2_selector.xml deleted file mode 100644 index 2d89df1860..0000000000 --- a/grc/blocks/blks2_selector.xml +++ /dev/null @@ -1,97 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Selector: -## Custom blks2 block -################################################### - --> -<block> - <name>Selector</name> - <key>blks2_selector</key> - <import>from grc_gnuradio import blks2 as grc_blks2</import> - <make>grc_blks2.selector( - item_size=$type.size*$vlen, - num_inputs=$num_inputs, - num_outputs=$num_outputs, - input_index=$input_index, - output_index=$output_index, -)</make> - <callback>set_input_index(int($input_index))</callback> - <callback>set_output_index(int($output_index))</callback> - <param> - <name>Type</name> - <key>type</key> - <type>enum</type> - <option> - <name>Complex</name> - <key>complex</key> - <opt>size:gr.sizeof_gr_complex</opt> - </option> - <option> - <name>Float</name> - <key>float</key> - <opt>size:gr.sizeof_float</opt> - </option> - <option> - <name>Int</name> - <key>int</key> - <opt>size:gr.sizeof_int</opt> - </option> - <option> - <name>Short</name> - <key>short</key> - <opt>size:gr.sizeof_short</opt> - </option> - <option> - <name>Byte</name> - <key>byte</key> - <opt>size:gr.sizeof_char</opt> - </option> - </param> - <param> - <name>Num Inputs</name> - <key>num_inputs</key> - <value>2</value> - <type>int</type> - </param> - <param> - <name>Num Outputs</name> - <key>num_outputs</key> - <value>2</value> - <type>int</type> - </param> - <param> - <name>Input Index</name> - <key>input_index</key> - <value>0</value> - <type>int</type> - </param> - <param> - <name>Output Index</name> - <key>output_index</key> - <value>0</value> - <type>int</type> - </param> - <param> - <name>Vec Length</name> - <key>vlen</key> - <value>1</value> - <type>int</type> - </param> - <check>$vlen > 0</check> - <sink> - <name>in</name> - <type>$type</type> - <vlen>$vlen</vlen> - <nports>$num_inputs</nports> - </sink> - <source> - <name>out</name> - <type>$type</type> - <vlen>$vlen</vlen> - <nports>$num_outputs</nports> - </source> - <doc> -Connect the sink at input index to the source at output index. Leave all other ports disconnected. - </doc> -</block> diff --git a/grc/blocks/blks2_tcp_sink.xml b/grc/blocks/blks2_tcp_sink.xml deleted file mode 100644 index cfe7b42d84..0000000000 --- a/grc/blocks/blks2_tcp_sink.xml +++ /dev/null @@ -1,89 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##TCP Sink: Custom blks2 block -################################################### - --> -<block> - <name>TCP Sink</name> - <key>blks2_tcp_sink</key> - <import>from grc_gnuradio import blks2 as grc_blks2</import> - <make>grc_blks2.tcp_sink( - itemsize=$type.size*$vlen, - addr=$addr, - port=$port, - server=$server, -)</make> - <param> - <name>Input Type</name> - <key>type</key> - <type>enum</type> - <option> - <name>Complex</name> - <key>complex</key> - <opt>size:gr.sizeof_gr_complex</opt> - </option> - <option> - <name>Float</name> - <key>float</key> - <opt>size:gr.sizeof_float</opt> - </option> - <option> - <name>Int</name> - <key>int</key> - <opt>size:gr.sizeof_int</opt> - </option> - <option> - <name>Short</name> - <key>short</key> - <opt>size:gr.sizeof_short</opt> - </option> - <option> - <name>Byte</name> - <key>byte</key> - <opt>size:gr.sizeof_char</opt> - </option> - </param> - <param> - <name>Address</name> - <key>addr</key> - <value>127.0.0.1</value> - <type>string</type> - </param> - <param> - <name>Port</name> - <key>port</key> - <value>0</value> - <type>int</type> - </param> - <param> - <name>Mode</name> - <key>server</key> - <value>False</value> - <type>enum</type> - <option> - <name>Server</name> - <key>True</key> - </option> - <option> - <name>Client</name> - <key>False</key> - </option> - </param> - <param> - <name>Vec Length</name> - <key>vlen</key> - <value>1</value> - <type>int</type> - </param> - <check>$vlen > 0</check> - <sink> - <name>in</name> - <type>$type</type> - <vlen>$vlen</vlen> - </sink> - <doc> -In client mode, we attempt to connect to a server at the given address and port. \ -In server mode, we bind a socket to the given address and port and accept the first client. - </doc> -</block> diff --git a/grc/blocks/blks2_tcp_source.xml b/grc/blocks/blks2_tcp_source.xml deleted file mode 100644 index 6bf742aa00..0000000000 --- a/grc/blocks/blks2_tcp_source.xml +++ /dev/null @@ -1,89 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##TCP Source: Custom blks2 block -################################################### - --> -<block> - <name>TCP Source</name> - <key>blks2_tcp_source</key> - <import>from grc_gnuradio import blks2 as grc_blks2</import> - <make>grc_blks2.tcp_source( - itemsize=$type.size*$vlen, - addr=$addr, - port=$port, - server=$server, -)</make> - <param> - <name>Output Type</name> - <key>type</key> - <type>enum</type> - <option> - <name>Complex</name> - <key>complex</key> - <opt>size:gr.sizeof_gr_complex</opt> - </option> - <option> - <name>Float</name> - <key>float</key> - <opt>size:gr.sizeof_float</opt> - </option> - <option> - <name>Int</name> - <key>int</key> - <opt>size:gr.sizeof_int</opt> - </option> - <option> - <name>Short</name> - <key>short</key> - <opt>size:gr.sizeof_short</opt> - </option> - <option> - <name>Byte</name> - <key>byte</key> - <opt>size:gr.sizeof_char</opt> - </option> - </param> - <param> - <name>Address</name> - <key>addr</key> - <value>127.0.0.1</value> - <type>string</type> - </param> - <param> - <name>Port</name> - <key>port</key> - <value>0</value> - <type>int</type> - </param> - <param> - <name>Mode</name> - <key>server</key> - <value>True</value> - <type>enum</type> - <option> - <name>Server</name> - <key>True</key> - </option> - <option> - <name>Client</name> - <key>False</key> - </option> - </param> - <param> - <name>Vec Length</name> - <key>vlen</key> - <value>1</value> - <type>int</type> - </param> - <check>$vlen > 0</check> - <source> - <name>out</name> - <type>$type</type> - <vlen>$vlen</vlen> - </source> - <doc> -In client mode, we attempt to connect to a server at the given address and port. \ -In server mode, we bind a socket to the given address and port and accept the first client. - </doc> -</block> diff --git a/grc/blocks/blks2_valve.xml b/grc/blocks/blks2_valve.xml deleted file mode 100644 index 47c553523f..0000000000 --- a/grc/blocks/blks2_valve.xml +++ /dev/null @@ -1,72 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Valve: -## Custom blks2 block -################################################### - --> -<block> - <name>Valve</name> - <key>blks2_valve</key> - <import>from grc_gnuradio import blks2 as grc_blks2</import> - <make>grc_blks2.valve(item_size=$type.size*$vlen, open=bool($open))</make> - <callback>set_open(bool($open))</callback> - <param> - <name>Type</name> - <key>type</key> - <type>enum</type> - <option> - <name>Complex</name> - <key>complex</key> - <opt>size:gr.sizeof_gr_complex</opt> - </option> - <option> - <name>Float</name> - <key>float</key> - <opt>size:gr.sizeof_float</opt> - </option> - <option> - <name>Int</name> - <key>int</key> - <opt>size:gr.sizeof_int</opt> - </option> - <option> - <name>Short</name> - <key>short</key> - <opt>size:gr.sizeof_short</opt> - </option> - <option> - <name>Byte</name> - <key>byte</key> - <opt>size:gr.sizeof_char</opt> - </option> - </param> - <param> - <name>Open</name> - <key>open</key> - <value>0</value> - <type>raw</type> - </param> - <param> - <name>Vec Length</name> - <key>vlen</key> - <value>1</value> - <type>int</type> - </param> - <check>$vlen > 0</check> - <sink> - <name>in</name> - <type>$type</type> - <vlen>$vlen</vlen> - <nports>$num_inputs</nports> - </sink> - <source> - <name>out</name> - <type>$type</type> - <vlen>$vlen</vlen> - <nports>$num_outputs</nports> - </source> - <doc> -Connect output to input when valve is closed (not open). - </doc> -</block> diff --git a/grc/blocks/block_tree.xml b/grc/blocks/block_tree.xml index d07c52e9c5..a8775d6872 100644 --- a/grc/blocks/block_tree.xml +++ b/grc/blocks/block_tree.xml @@ -20,23 +20,6 @@ <block>note</block> <block>import</block> - - <block>blks2_selector</block> - <block>blks2_valve</block> - <block>blks2_error_rate</block> - - <block>xmlrpc_server</block> - <block>xmlrpc_client</block> - </cat> - <cat> - <name>Networking Tools</name> - <block>blks2_tcp_source</block> - <block>blks2_tcp_sink</block> - </cat> - <cat> - <name>Packet Operators</name> - <block>blks2_packet_decoder</block> - <block>blks2_packet_encoder</block> </cat> <cat> <name>Variables</name> diff --git a/grc/blocks/xmlrpc_client.xml b/grc/blocks/xmlrpc_client.xml deleted file mode 100644 index dc4d154d14..0000000000 --- a/grc/blocks/xmlrpc_client.xml +++ /dev/null @@ -1,42 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Simple XMLRPC Client -################################################### - --> -<block> - <name>XMLRPC Client</name> - <key>xmlrpc_client</key> - <import>import xmlrpclib</import> - <make>xmlrpclib.Server('http://$(addr()):$(port)')</make> - <callback>$(callback())($variable)</callback> - <param> - <name>Address</name> - <key>addr</key> - <value>localhost</value> - <type>string</type> - </param> - <param> - <name>Port</name> - <key>port</key> - <value>8080</value> - <type>int</type> - </param> - <param> - <name>Callback</name> - <key>callback</key> - <value>set_</value> - <type>string</type> - </param> - <param> - <name>Variable</name> - <key>variable</key> - <type>raw</type> - </param> - <doc> -This block will create an XMLRPC client. \ -The client will execute the callback on the server when the variable is changed. \ -The callback should be a the name of a function registered on the server. \ -The variable should be an expression containing a the name of a variable in flow graph. - </doc> -</block> diff --git a/grc/blocks/xmlrpc_server.xml b/grc/blocks/xmlrpc_server.xml deleted file mode 100644 index 602d444161..0000000000 --- a/grc/blocks/xmlrpc_server.xml +++ /dev/null @@ -1,41 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Simple XMLRPC Server -################################################### - --> -<block> - <name>XMLRPC Server</name> - <key>xmlrpc_server</key> - <import>import SimpleXMLRPCServer</import> - <import>import threading</import> - <make>SimpleXMLRPCServer.SimpleXMLRPCServer(($addr, $port), allow_none=True) -self.$(id).register_instance(self) -self.$(id)_thread = threading.Thread(target=self.$(id).serve_forever) -self.$(id)_thread.daemon = True -self.$(id)_thread.start()</make> - <param> - <name>Address</name> - <key>addr</key> - <value>localhost</value> - <type>string</type> - </param> - <param> - <name>Port</name> - <key>port</key> - <value>8080</value> - <type>int</type> - </param> - <doc> -This block will start an XMLRPC server. \ -The server provides access to the run, start, stop, wait functions of the flow graph. \ -The server also provides access to the variable callbacks in the flow graph. \ -Ex: If the variable is called freq, the function provided by the server will be called set_freq(new_freq). - -Example client in python: - -import xmlrpclib -s = xmlrpclib.Server('http://localhost:8080') -s.set_freq(5000) - </doc> -</block> diff --git a/grc/checks.py b/grc/checks.py new file mode 100755 index 0000000000..fd0e5de06a --- /dev/null +++ b/grc/checks.py @@ -0,0 +1,80 @@ +# Copyright 2009-2016 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 warnings + + +GR_IMPORT_ERROR_MESSAGE = """\ +Cannot import gnuradio. + +Is the model path environment variable set correctly? + All OS: PYTHONPATH + +Is the library path environment variable set correctly? + Linux: LD_LIBRARY_PATH + Windows: PATH + MacOSX: DYLD_LIBRARY_PATH +""" + + +def die(error, message): + msg = "{0}\n\n({1})".format(message, error) + try: + import gtk + d = gtk.MessageDialog( + type=gtk.MESSAGE_ERROR, + buttons=gtk.BUTTONS_CLOSE, + message_format=msg, + ) + d.set_title(type(error).__name__) + d.run() + exit(1) + except ImportError: + exit(type(error).__name__ + '\n\n' + msg) + + +def check_gtk(): + try: + warnings.filterwarnings("error") + import pygtk + pygtk.require('2.0') + import gtk + gtk.init_check() + warnings.filterwarnings("always") + except Exception as err: + die(err, "Failed to initialize GTK. If you are running over ssh, " + "did you enable X forwarding and start ssh with -X?") + + +def check_gnuradio_import(): + try: + from gnuradio import gr + except ImportError as err: + die(err, GR_IMPORT_ERROR_MESSAGE) + + +def check_blocks_path(): + if 'GR_DONT_LOAD_PREFS' in os.environ and not os.environ.get('GRC_BLOCKS_PATH', ''): + die(EnvironmentError("No block definitions available"), + "Can't find block definitions. Use config.conf or GRC_BLOCKS_PATH.") + + +def do_all(): + check_gnuradio_import() + check_gtk() + check_blocks_path() diff --git a/grc/core/Block.py b/grc/core/Block.py new file mode 100644 index 0000000000..cb4eb0db61 --- /dev/null +++ b/grc/core/Block.py @@ -0,0 +1,846 @@ +""" +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 .utils import epy_block_io, odict +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 . Element import Element +from . FlowGraph import _variable_matcher + + +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((self.get_id(), 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] = 'import ' + self.get_id() + self._make = '{0}.{1}({2})'.format(self.get_id(), 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: + try: + param = params[key] + param.set_default(value) + except KeyError: # need to make a new param + 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((param.get_key(), param.template_arg) + for param in self.get_params()) # TODO: cache that + 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/examples/CMakeLists.txt b/grc/core/CMakeLists.txt index a218dbe500..51b0dacba6 100644 --- a/grc/examples/CMakeLists.txt +++ b/grc/core/CMakeLists.txt @@ -17,21 +17,21 @@ # the Free Software Foundation, Inc., 51 Franklin Street, # Boston, MA 02110-1301, USA. -# SIMPLE -install( - FILES - simple/variable_config.grc - DESTINATION ${GR_PKG_DATA_DIR}/examples/grc/simple +file(GLOB py_files "*.py") + +GR_PYTHON_INSTALL( + FILES ${py_files} + DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/core COMPONENT "grc" ) -# XMLRPC +file(GLOB dtd_files "*.dtd") + install( - FILES - xmlrpc/readme.txt - xmlrpc/xmlrpc_client.grc - xmlrpc/xmlrpc_client_script.py - xmlrpc/xmlrpc_server.grc - DESTINATION ${GR_PKG_DATA_DIR}/examples/grc/xmlrpc + FILES ${dtd_files} default_flow_graph.grc + DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/core COMPONENT "grc" ) + +add_subdirectory(generator) +add_subdirectory(utils) diff --git a/grc/core/Config.py b/grc/core/Config.py new file mode 100644 index 0000000000..ac38d9978c --- /dev/null +++ b/grc/core/Config.py @@ -0,0 +1,55 @@ +""" +Copyright 2016 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, normpath, expandvars, exists + + +class Config(object): + + key = 'grc' + name = 'GNU Radio Companion (no gui)' + license = __doc__.strip() + website = 'http://gnuradio.org' + + hier_block_lib_dir = os.environ.get('GRC_HIER_PATH', expanduser('~/.grc_gnuradio')) + + def __init__(self, prefs_file, version, version_parts=None): + self.prefs = prefs_file + self.version = version + self.version_parts = version_parts or version[1:].split('-', 1)[0].split('.')[:3] + + @property + def block_paths(self): + path_list_sep = {'/': ':', '\\': ';'}[os.path.sep] + + paths_sources = ( + self.hier_block_lib_dir, + os.environ.get('GRC_BLOCKS_PATH', ''), + self.prefs.get_string('grc', 'local_blocks_path', ''), + self.prefs.get_string('grc', 'global_blocks_path', ''), + ) + + collected_paths = sum((paths.split(path_list_sep) + for paths in paths_sources), []) + + valid_paths = [normpath(expanduser(expandvars(path))) + for path in collected_paths if exists(path)] + + return valid_paths diff --git a/grc/base/Connection.py b/grc/core/Connection.py index bf3c75277c..3aa32ef183 100644 --- a/grc/base/Connection.py +++ b/grc/core/Connection.py @@ -1,5 +1,5 @@ """ -Copyright 2008-2011 Free Software Foundation, Inc. +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 @@ -17,11 +17,15 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -from Element import Element -from . import odict +from . import Constants +from .Element import Element +from .utils 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. @@ -37,72 +41,88 @@ class Connection(Element): """ Element.__init__(self, flow_graph) source = sink = None - #separate the source and sink + # Separate the source and sink for port in (porta, portb): - if port.is_source(): source = port - if port.is_sink(): 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 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 self.get_parent().get_connections(): + 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(); + sources = source.get_associated_ports() + sinks = sink.get_associated_ports() for i in range(len(sources)): try: - flow_graph.connect(sources[i], sinks[i]); + flow_graph.connect(sources[i], sinks[i]) except: pass def __str__(self): - return 'Connection (\n\t%s\n\t\t%s\n\t%s\n\t\t%s\n)'%( + 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_connection(self): return True + 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 "%s", "%s"' - % (source_domain, sink_domain)) + if (source_domain, sink_domain) not in platform.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 + not platform.domains.get(source_domain, []).get('multiple_sinks', False) 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 + not platform.domains.get(sink_domain, []).get('multiple_sources', False) and len(self.get_sink().get_enabled_connections()) > 1 ) if too_many_other_sinks: self.add_error_message( - 'Domain "%s" can have only one downstream block' % source_domain) + 'Domain "{}" can have only one downstream block'.format(source_domain)) if too_many_other_sources: self.add_error_message( - 'Domain "%s" can have only one upstream block' % sink_domain) + '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): """ @@ -117,11 +137,14 @@ class Connection(Element): ############################# # Access Ports ############################# - def get_sink(self): return self._sink - def get_source(self): return self._source + def get_sink(self): + return self._sink + + def get_source(self): + return self._source ############################################## - ## Import/Export Methods + # Import/Export Methods ############################################## def export_data(self): """ diff --git a/grc/python/Constants.py b/grc/core/Constants.py index b7a370cad7..9fe805f854 100644 --- a/grc/python/Constants.py +++ b/grc/core/Constants.py @@ -1,5 +1,5 @@ """ -Copyright 2008-2011 Free Software Foundation, Inc. +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 @@ -18,49 +18,53 @@ 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] - -# 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 - -# data files +# Data files DATA_DIR = os.path.dirname(__file__) -FLOW_GRAPH_TEMPLATE = os.path.join(DATA_DIR, 'flow_graph.tmpl') +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') +DOMAIN_DTD = os.path.join(DATA_DIR, 'domain.dtd') + +# 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 +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_wx_gui' + +# Block States +BLOCK_DISABLED = 0 +BLOCK_ENABLED = 1 +BLOCK_BYPASSED = 2 + +# 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 +# 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 + 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) @@ -87,7 +91,6 @@ 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), @@ -108,23 +111,25 @@ CORE_TYPES = ( # name, key, sizeof, color ) 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), + '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 +# Coloring COMPLEX_COLOR_SPEC = '#3399FF' FLOAT_COLOR_SPEC = '#FF8C69' INT_COLOR_SPEC = '#00FF99' diff --git a/grc/base/Element.py b/grc/core/Element.py index b0f94d0183..b96edb0a72 100644 --- a/grc/base/Element.py +++ b/grc/core/Element.py @@ -1,5 +1,5 @@ """ -Copyright 2008, 2009 Free Software Foundation, Inc. +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 @@ -17,6 +17,7 @@ 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): @@ -32,7 +33,8 @@ class Element(object): Call this base method before adding error messages in the subclass. """ del self._error_messages[:] - for child in self.get_children(): child.validate() + for child in self.get_children(): + child.validate() def is_valid(self): """ @@ -61,10 +63,10 @@ class Element(object): Returns: a list of error message strings """ - error_messages = list(self._error_messages) #make a copy + error_messages = list(self._error_messages) # Make a copy for child in filter(lambda c: c.get_enabled() and not c.get_bypassed(), self.get_children()): for msg in child.get_error_messages(): - error_messages.append("%s:\n\t%s"%(child, msg.replace("\n", "\n\t"))) + error_messages.append("{}:\n\t{}".format(child, msg.replace("\n", "\n\t"))) return error_messages def rewrite(self): @@ -72,27 +74,36 @@ class Element(object): 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() + for child in self.get_children(): + child.rewrite() + + def get_enabled(self): + return True - def get_enabled(self): return True - def get_bypassed(self): return False + def get_bypassed(self): + return False ############################################## - ## Tree-like API + # Tree-like API ############################################## - def get_parent(self): return self._parent - def get_children(self): return list() + def get_parent(self): + return self._parent + + def get_children(self): + return list() ############################################## - ## Type testing methods + # Type testing ############################################## - def is_element(self): return True - def is_platform(self): return False - def is_flow_graph(self): return False - def is_connection(self): return False - def is_block(self): return False - def is_dummy_block(self): return False - def is_source(self): return False - def is_sink(self): return False - def is_port(self): return False - def is_param(self): return False + 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..313af3107a --- /dev/null +++ b/grc/core/FlowGraph.py @@ -0,0 +1,594 @@ +# 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 imp +import time +from itertools import ifilter, chain +from operator import methodcaller + +import re + +from . import Messages +from .Constants import FLOW_GRAPH_FILE_FORMAT_VERSION +from .Element import Element +from .utils import odict, expr_utils + +_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._options_block = self.new_block('options') + + 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 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': '.'.join(self.get_parent().config.version_parts), + 'format': FLOW_GRAPH_FILE_FORMAT_VERSION, + }) + return odict({'flow_graph': n, '_instructions': instructions}) + + def import_data(self, n): + """ + Import blocks and connections into this flow graph. + Clear this flowgraph of all previous blocks and connections. + Any blocks or connections in error will be ignored. + + Args: + n: the nested data odict + """ + # 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.new_block('options') + for block_n in fg_n.findall('block'): + key = block_n.find('key') + block = self._options_block if key == 'options' else self.new_block(key) + + if not block: + # we're before the initial fg update(), so no evaluated values! + # --> use raw value instead + path_param = self._options_block.get_param('hier_block_src_path') + file_path = self.platform.find_file_in_paths( + filename=key + '.grc', + paths=path_param.get_value(), + cwd=self.grc_file_path + ) + if file_path: # grc file found. load and get block + self.platform.load_and_generate_flow_graph(file_path) + block = self.new_block(key) # can be None + + if not block: # looks like this block key cannot be found + # create a dummy block instead + block = self.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) + + self.rewrite() # evaluate stuff like nports before adding connections + + # 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 + + errors = False + 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 = _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/gui/Messages.py b/grc/core/Messages.py index 551a8ce753..da50487e5b 100644 --- a/grc/gui/Messages.py +++ b/grc/core/Messages.py @@ -1,21 +1,20 @@ -""" -Copyright 2007 Free Software Foundation, Inc. -This file is part of GNU Radio +# Copyright 2007, 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 -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 traceback import sys @@ -59,20 +58,12 @@ register_messenger(sys.stdout.write) # Special functions for specific program functionalities ########################################################################### def send_init(platform): - p = platform - - def get_paths(x): - return os.path.abspath(os.path.expanduser(x)), x - - send('\n'.join([ - "<<< Welcome to %s %s >>>" % (p.get_name(), p.get_version()), - "", - "Preferences file: " + p.get_prefs_file(), - "Block paths:" - ] + [ - "\t%s" % path + (" (%s)" % opath if opath != path else "") - for path, opath in map(get_paths, p.get_block_paths()) - ]) + "\n") + msg = "<<< Welcome to {config.name} {config.version} >>>\n\n" \ + "Block paths:\n\t{paths}\n" + send(msg.format( + config=platform.config, + paths="\n\t".join(platform.config.block_paths)) + ) def send_page_switch(file_path): diff --git a/grc/core/Param.py b/grc/core/Param.py new file mode 100644 index 0000000000..d155800c43 --- /dev/null +++ b/grc/core/Param.py @@ -0,0 +1,740 @@ +""" +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 weakref +import re + +from . import Constants +from .Constants import VECTOR_TYPES, COMPLEX_TYPES, REAL_TYPES, INT_TYPES +from .Element import Element +from .utils import odict + +# Blacklist certain ids, its not complete, but should help +import __builtin__ + + +ID_BLACKLIST = ['self', 'options', 'gr', 'blks2', 'wxgui', 'wx', 'math', 'forms', 'firdes'] + dir(__builtin__) +try: + from gnuradio import gr + ID_BLACKLIST.extend(attr for attr in dir(gr.top_block()) if not attr.startswith('_')) +except ImportError: + pass + +_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 """ + def eng_notation(value, fmt='g'): + """Convert a number to a string in engineering notation. E.g., 5e-9 -> 5n""" + template = '{:' + fmt + '}{}' + magnitude = abs(value) + for exp, symbol in zip(range(9, -15-1, -3), 'GMk munpf'): + factor = 10 ** exp + if magnitude >= factor: + return template.format(value / factor, symbol.strip()) + return template.format(value, '') + + if isinstance(num, COMPLEX_TYPES): + num = complex(num) # Cast to python complex + if num == 0: + return '0' + output = eng_notation(num.real) if num.real else '' + output += eng_notation(num.imag, '+g' if output else 'g') + 'j' if num.imag else '' + return output + 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 TemplateArg(object): + """ + 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 python data type. + """ + + def __init__(self, param): + self._param = weakref.proxy(param) + + def __getitem__(self, item): + return str(self._param.get_opt(item)) if self._param.is_enum() else NotImplemented + + def __str__(self): + return str(self._param.to_code()) + + def __call__(self): + return self._param.get_evaluated() + + +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() + self.template_arg = TemplateArg(self) + + 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 set_default(self, value): + if self._default == self._value: + self.set_value(value) + self._default = str(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/base/ParseXML.py b/grc/core/ParseXML.py index e05fc1428d..c9f6541ee7 100644 --- a/grc/base/ParseXML.py +++ b/grc/core/ParseXML.py @@ -1,5 +1,5 @@ """ -Copyright 2008 Free Software Foundation, Inc. +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 @@ -18,7 +18,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ from lxml import etree -from . import odict + +from .utils import odict xml_failures = {} etree.set_default_parser(etree.XMLParser(remove_comments=True)) @@ -42,7 +43,7 @@ def validate_dtd(xml_file, dtd_file=None): dtd_file: the optional dtd file @throws Exception validation fails """ - # perform parsing, use dtd validation if dtd file is not specified + # 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) @@ -51,7 +52,7 @@ def validate_dtd(xml_file, dtd_file=None): if parser.error_log: raise XMLSyntaxError(parser.error_log) - # perform dtd validation if the dtd file is specified + # Perform dtd validation if the dtd file is specified if not dtd_file: return try: @@ -100,9 +101,11 @@ def _from_file(xml): nested_data = odict() for elem in xml: key, value = _from_file(elem).items()[0] - if nested_data.has_key(key): nested_data[key].append(value) - else: nested_data[key] = [value] - # delistify if the length of values is 1 + 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] @@ -120,7 +123,8 @@ def to_file(nested_data, xml_file): """ xml_data = "" instructions = nested_data.pop('_instructions', None) - if instructions: # create the processing instruction from the array + # 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()) @@ -143,7 +147,7 @@ def _to_file(nested_data): """ nodes = list() for key, values in nested_data.iteritems(): - # listify the values if not a list + # Listify the values if not a list if not isinstance(values, (list, set, tuple)): values = [values] for value in values: diff --git a/grc/core/Platform.py b/grc/core/Platform.py new file mode 100644 index 0000000000..4c1e6f471a --- /dev/null +++ b/grc/core/Platform.py @@ -0,0 +1,319 @@ +""" +Copyright 2008-2016 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 . import ParseXML, Messages, Constants + +from .Config import Config +from .Element import Element +from .generator import Generator +from .FlowGraph import FlowGraph +from .Connection import Connection +from .Block import Block +from .Port import Port +from .Param import Param + +from .utils import odict, extract_docs + + +class Platform(Element): + + Config = Config + Generator = Generator + FlowGraph = FlowGraph + Connection = Connection + Block = Block + Port = Port + Param = Param + + is_platform = True + + def __init__(self, *args, **kwargs): + """ Make a platform for GNU Radio """ + Element.__init__(self) + + self.config = self.Config(*args, **kwargs) + + 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() + ) + + self._block_dtd = Constants.BLOCK_DTD + self._default_flow_graph = Constants.DEFAULT_FLOW_GRAPH + + # Create a dummy flow graph for the blocks + self._flow_graph = Element(self) + self._flow_graph.connections = [] + + self.blocks = None + self._blocks_n = None + self._category_trees_n = None + self.domains = {} + self.connection_templates = {} + + self._auto_hier_block_generate_chain = set() + + self.load_blocks() + + def __str__(self): + return 'Platform - {}({})'.format(self.config.key, self.config.name) + + @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 flow graph 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.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""" + self._docstring_extractor.start() + # 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) + + self._docstring_extractor.finish() + # self._docstring_extractor.wait() + + def iter_xml_files(self): + """Iterator for block descriptions and category trees""" + for block_path in self.config.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 + + self._docstring_extractor.query( + block.get_key(), + block.get_imports(raw=True), + block.get_make(raw=True) + ) + + def load_category_tree_xml(self, xml_file): + """Validate and parse category tree file and add it to list""" + ParseXML.validate_dtd(xml_file, Constants.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, Constants.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: {}'.format(xml_file) + return + if key in self.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 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.blocks: + print >> sys.stderr, 'Warning: Block key "{}" not found when loading category tree.'.format(block_key) + continue + block = self.blocks[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.blocks.itervalues(): + # Blocks with empty categories are hidden + if not block.get_category(): + continue + block_tree.add_block(block.get_category(), block) + + 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 + + ############################################## + # Access + ############################################## + + 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, Constants.FLOW_GRAPH_DTD) + return ParseXML.from_file(flow_graph_file) + + def get_new_flow_graph(self): + return self.FlowGraph(platform=self) + + 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_colors(self): + return [(name, color) for name, key, sizeof, color in Constants.CORE_TYPES] diff --git a/grc/core/Port.py b/grc/core/Port.py new file mode 100644 index 0000000000..6a8f484082 --- /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'] or '' + 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.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/grc_gnuradio/__init__.py b/grc/core/__init__.py index 8b13789179..8b13789179 100644 --- a/grc/grc_gnuradio/__init__.py +++ b/grc/core/__init__.py diff --git a/grc/python/block.dtd b/grc/core/block.dtd index 145f4d8610..145f4d8610 100644 --- a/grc/python/block.dtd +++ b/grc/core/block.dtd diff --git a/grc/base/block_tree.dtd b/grc/core/block_tree.dtd index 9e23576477..9e23576477 100644 --- a/grc/base/block_tree.dtd +++ b/grc/core/block_tree.dtd diff --git a/grc/python/default_flow_graph.grc b/grc/core/default_flow_graph.grc index 059509d34b..059509d34b 100644 --- a/grc/python/default_flow_graph.grc +++ b/grc/core/default_flow_graph.grc diff --git a/grc/base/domain.dtd b/grc/core/domain.dtd index b5b0b8bf39..b5b0b8bf39 100644 --- a/grc/base/domain.dtd +++ b/grc/core/domain.dtd diff --git a/grc/base/flow_graph.dtd b/grc/core/flow_graph.dtd index bdfe1dc059..bdfe1dc059 100644 --- a/grc/base/flow_graph.dtd +++ b/grc/core/flow_graph.dtd diff --git a/grc/grc_gnuradio/CMakeLists.txt b/grc/core/generator/CMakeLists.txt index e992a60a39..4bdd59a7a2 100644 --- a/grc/grc_gnuradio/CMakeLists.txt +++ b/grc/core/generator/CMakeLists.txt @@ -17,19 +17,16 @@ # the Free Software Foundation, Inc., 51 Franklin Street, # Boston, MA 02110-1301, USA. -######################################################################## +file(GLOB py_files "*.py") + GR_PYTHON_INSTALL( - FILES __init__.py - DESTINATION ${GR_PYTHON_DIR}/grc_gnuradio + FILES ${py_files} + DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/core/generator COMPONENT "grc" ) -GR_PYTHON_INSTALL(FILES - blks2/__init__.py - blks2/error_rate.py - blks2/packet.py - blks2/selector.py - blks2/tcp.py - DESTINATION ${GR_PYTHON_DIR}/grc_gnuradio/blks2 +install(FILES + flow_graph.tmpl + DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/core/generator COMPONENT "grc" ) diff --git a/grc/core/generator/FlowGraphProxy.py b/grc/core/generator/FlowGraphProxy.py new file mode 100644 index 0000000000..3723005576 --- /dev/null +++ b/grc/core/generator/FlowGraphProxy.py @@ -0,0 +1,126 @@ +# Copyright 2016 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 FlowGraphProxy(object): + + def __init__(self, fg): + self._fg = fg + + def __getattr__(self, item): + return getattr(self._fg, item) + + def get_hier_block_stream_io(self, direction): + """ + Get a list of stream io signatures for this flow graph. + + Args: + direction: a string of 'in' or 'out' + + Returns: + a list of dicts with: type, label, vlen, size, optional + """ + return filter(lambda p: p['type'] != "message", self.get_hier_block_io(direction)) + + def get_hier_block_message_io(self, direction): + """ + Get a list of message io signatures for this flow graph. + + Args: + direction: a string of 'in' or 'out' + + Returns: + a list of dicts with: type, label, vlen, size, optional + """ + return filter(lambda p: p['type'] == "message", self.get_hier_block_io(direction)) + + def get_hier_block_io(self, direction): + """ + Get a list of io ports for this flow graph. + + Args: + direction: a string of 'in' or 'out' + + Returns: + a list of dicts with: type, label, vlen, size, optional + """ + pads = self.get_pad_sources() if direction in ('sink', 'in') else \ + self.get_pad_sinks() if direction in ('source', 'out') else [] + ports = [] + for pad in pads: + master = { + 'label': str(pad.get_param('label').get_evaluated()), + 'type': str(pad.get_param('type').get_evaluated()), + 'vlen': str(pad.get_param('vlen').get_value()), + 'size': pad.get_param('type').get_opt('size'), + 'optional': bool(pad.get_param('optional').get_evaluated()), + } + num_ports = pad.get_param('num_streams').get_evaluated() + if num_ports > 1: + for i in xrange(num_ports): + clone = master.copy() + clone['label'] += str(i) + ports.append(clone) + else: + ports.append(master) + return ports + + def get_pad_sources(self): + """ + Get a list of pad source blocks sorted by id order. + + Returns: + a list of pad source blocks in this flow graph + """ + pads = filter(lambda b: b.get_key() == 'pad_source', self.get_enabled_blocks()) + return sorted(pads, lambda x, y: cmp(x.get_id(), y.get_id())) + + def get_pad_sinks(self): + """ + Get a list of pad sink blocks sorted by id order. + + Returns: + a list of pad sink blocks in this flow graph + """ + pads = filter(lambda b: b.get_key() == 'pad_sink', self.get_enabled_blocks()) + return sorted(pads, lambda x, y: cmp(x.get_id(), y.get_id())) + + def get_pad_port_global_key(self, port): + """ + Get the key for a port of a pad source/sink to use in connect() + This takes into account that pad blocks may have multiple ports + + Returns: + the key (str) + """ + key_offset = 0 + pads = self.get_pad_sources() if port.is_source else self.get_pad_sinks() + for pad in pads: + # using the block param 'type' instead of the port domain here + # to emphasize that hier block generation is domain agnostic + is_message_pad = pad.get_param('type').get_evaluated() == "message" + if port.get_parent() == pad: + if is_message_pad: + key = pad.get_param('label').get_value() + else: + key = str(key_offset + int(port.get_key())) + return key + else: + # assuming we have either only sources or sinks + if not is_message_pad: + key_offset += len(pad.get_ports()) + return -1
\ No newline at end of file diff --git a/grc/python/Generator.py b/grc/core/generator/Generator.py index 56e3a6e78f..91671072d6 100644 --- a/grc/python/Generator.py +++ b/grc/core/generator/Generator.py @@ -1,41 +1,37 @@ -""" -Copyright 2008-2011 Free Software Foundation, Inc. -This file is part of GNU Radio +# Copyright 2008-2016 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 -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 codecs 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 .. gui import Messages -from .. base import ParseXML -from .. base import odict -from .. base.Constants import BLOCK_FLAG_NEED_QT_GUI +from .FlowGraphProxy import FlowGraphProxy +from .. import ParseXML, Messages +from ..Constants import ( + TOP_BLOCK_FILE_MODE, BLOCK_FLAG_NEED_QT_GUI, + HIER_BLOCK_FILE_MODE, BLOCK_DTD +) +from ..utils import expr_utils, odict -from . Constants import TOP_BLOCK_FILE_MODE, FLOW_GRAPH_TEMPLATE, \ - XTERM_EXECUTABLE, HIER_BLOCK_FILE_MODE, HIER_BLOCKS_LIB_DIR, BLOCK_DTD -from . import expr_utils +DATA_DIR = os.path.dirname(__file__) +FLOW_GRAPH_TEMPLATE = os.path.join(DATA_DIR, 'flow_graph.tmpl') class Generator(object): @@ -50,19 +46,16 @@ class Generator(object): 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': + self.generate_options = flow_graph.get_option('generate_options') + if self.generate_options == 'hb': generator_cls = HierBlockGenerator - elif self._generate_options == 'hb_qt_gui': + 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) @@ -78,23 +71,23 @@ class TopBlockGenerator(object): flow_graph: the flow graph object file_path: the path to write the file to """ - self._flow_graph = flow_graph + self._flow_graph = FlowGraphProxy(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 + # 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) + self.file_path = os.path.join(dirname, filename) def get_file_path(self): - return self._file_path + return self.file_path def write(self): """generate output and write it to files""" - # do throttle warning + # 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: " @@ -109,48 +102,16 @@ class TopBlockGenerator(object): "e.g. a hardware source or sink. " "This is usually undesired. Consider " "removing the throttle block.") - # generate + # 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(): + if filename == self.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. @@ -167,21 +128,21 @@ class TopBlockGenerator(object): parameters = fg.get_parameters() monitors = fg.get_monitors() - # list of blocks not including variables and imports and parameters and disabled + # 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 + 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 + 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.iter_blocks()), + 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) @@ -198,7 +159,8 @@ class TopBlockGenerator(object): output.append((file_path, src)) # Filter out virtual sink connections - cf = lambda c: not (c.is_bus() or c.is_msg() or c.get_sink().get_parent().is_virtual_sink()) + 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 @@ -220,8 +182,7 @@ class TopBlockGenerator(object): for block in bypassed_blocks: # Get the upstream connection (off of the sink ports) # Use *connections* not get_connections() - get_source_connection = lambda c: c.get_sink() == block.get_sinks()[0] - source_connection = filter(get_source_connection, 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) @@ -229,8 +190,7 @@ class TopBlockGenerator(object): source_port = source_connection[0].get_source() # Loop through all the downstream connections - get_sink_connections = lambda c: c.get_source() == block.get_sources()[0] - for sink in filter(get_sink_connections, connections): + for sink in filter(lambda c: c.get_source() == block.get_sources()[0], connections): if not sink.get_enabled(): # Ignore disabled connections continue @@ -248,23 +208,23 @@ class TopBlockGenerator(object): c.get_source().get_parent().get_id(), c.get_sink().get_parent().get_id() )) - connection_templates = fg.get_parent().get_connection_templates() + connection_templates = fg.get_parent().connection_templates msgs = filter(lambda c: c.is_msg(), fg.get_enabled_connections()) - # list of variable names + # List of variable names var_ids = [var.get_id() for var in parameters + variables] - # prepend self. + # Prepend self. replace_dict = dict([(var_id, 'self.%s' % var_id) for var_id in var_ids]) - # list of callbacks + # 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 + ] + # 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 + # Load the namespace namespace = { 'title': title, 'imports': imports, @@ -279,9 +239,9 @@ class TopBlockGenerator(object): 'generate_options': self._generate_options, 'var_id2cbs': var_id2cbs, } - # build the template + # Build the template t = Template(open(FLOW_GRAPH_TEMPLATE, 'r').read(), namespace) - output.append((self.get_file_path(), str(t))) + output.append((self.file_path, str(t))) return output @@ -297,10 +257,15 @@ class HierBlockGenerator(TopBlockGenerator): file_path: where to write the py file (the xml goes into HIER_BLOCK_LIB_DIR) """ TopBlockGenerator.__init__(self, flow_graph, file_path) + platform = flow_graph.get_parent() + + hier_block_lib_dir = platform.config.hier_block_lib_dir + if not os.path.exists(hier_block_lib_dir): + os.mkdir(hier_block_lib_dir) + 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' + self.file_path = os.path.join(hier_block_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 @@ -322,7 +287,7 @@ class HierBlockGenerator(TopBlockGenerator): Returns: a xml node tree """ - # extract info from the flow graph + # Extract info from the flow graph block_key = self._flow_graph.get_option('id') parameters = self._flow_graph.get_parameters() @@ -331,7 +296,7 @@ class HierBlockGenerator(TopBlockGenerator): return "$"+name return name - # build the nested data + # 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() @@ -339,7 +304,7 @@ class HierBlockGenerator(TopBlockGenerator): 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 + # Make data if parameters: block_n['make'] = '{cls}(\n {kwargs},\n)'.format( cls=block_key, @@ -349,7 +314,7 @@ class HierBlockGenerator(TopBlockGenerator): ) else: block_n['make'] = '{cls}()'.format(cls=block_key) - # callback data + # Callback data block_n['callback'] = [ 'set_{key}(${key})'.format(key=param.get_id()) for param in parameters ] @@ -364,13 +329,13 @@ class HierBlockGenerator(TopBlockGenerator): param_n['type'] = 'raw' block_n['param'].append(param_n) - # bus stuff + # 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 + # Sink/source ports for direction in ('sink', 'source'): block_n[direction] = list() for port in self._flow_graph.get_hier_block_io(direction): @@ -383,7 +348,7 @@ class HierBlockGenerator(TopBlockGenerator): port_n['optional'] = '1' block_n[direction].append(port_n) - # more bus stuff + # 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() @@ -391,11 +356,11 @@ class HierBlockGenerator(TopBlockGenerator): if bus_struct_src: block_n['bus_structure_source'] = bus_struct_src[0].get_param('struct').get_value() - # documentation + # 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() + self.file_path ) if field) block_n['grc_source'] = str(self._flow_graph.grc_file_path) @@ -427,21 +392,3 @@ class QtHierBlockGenerator(HierBlockGenerator): "\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..f44b94a85d --- /dev/null +++ b/grc/core/generator/__init__.py @@ -0,0 +1,18 @@ +# 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 Generator import Generator diff --git a/grc/python/flow_graph.tmpl b/grc/core/generator/flow_graph.tmpl index bd8025b676..ecdb89390e 100644 --- a/grc/python/flow_graph.tmpl +++ b/grc/core/generator/flow_graph.tmpl @@ -274,8 +274,8 @@ gr.io_signaturev($(len($io_sigs)), $(len($io_sigs)), [$(', '.join($size_strs))]) 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): @@ -336,7 +336,12 @@ $short_id#slurp def argument_parser(): - parser = OptionParser(option_class=eng_option, usage="%prog: [options]") + #set $desc_args = 'usage="%prog: [options]", option_class=eng_option' + #if $flow_graph.get_option('description') + #set $desc_args += ', description=description' + description = $repr($flow_graph.get_option('description')) + #end if + parser = OptionParser($desc_args) #for $param in $parameters #set $type = $param.get_param('type').get_value() #if $type diff --git a/grc/grc_gnuradio/blks2/__init__.py b/grc/core/utils/CMakeLists.txt index e6941ab91b..2528fbc43c 100644 --- a/grc/grc_gnuradio/blks2/__init__.py +++ b/grc/core/utils/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright 2008-2011 Free Software Foundation, Inc. +# Copyright 2015 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -16,11 +16,11 @@ # 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. -# -from selector import selector, valve -from packet import options, packet_encoder, packet_decoder, \ - packet_mod_b, packet_mod_s, packet_mod_i, packet_mod_f, packet_mod_c, \ - packet_demod_b, packet_demod_s, packet_demod_i, packet_demod_f, packet_demod_c -from error_rate import error_rate -from tcp import tcp_source, tcp_sink +file(GLOB py_files "*.py") + +GR_PYTHON_INSTALL( + FILES ${py_files} + DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/core/utils + COMPONENT "grc" +) diff --git a/grc/core/utils/__init__.py b/grc/core/utils/__init__.py new file mode 100644 index 0000000000..6b23da2723 --- /dev/null +++ b/grc/core/utils/__init__.py @@ -0,0 +1,22 @@ +# 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 expr_utils +import epy_block_io +import extract_docs + +from odict import odict 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 diff --git a/grc/python/epy_block_io.py b/grc/core/utils/epy_block_io.py index e089908a01..df3a4bbc3e 100644 --- a/grc/python/epy_block_io.py +++ b/grc/core/utils/epy_block_io.py @@ -2,9 +2,6 @@ import inspect import collections -from gnuradio import gr -import pmt - TYPE_MAP = { 'complex64': 'complex', 'complex': 'complex', @@ -31,21 +28,27 @@ def _ports(sigs, msgs): return ports -def _blk_class(source_code): +def _find_block_class(source_code, cls): 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): + if inspect.isclass(var) and issubclass(var, cls): return var raise ValueError('No python block class found in code') def extract(cls): + try: + from gnuradio import gr + import pmt + except ImportError: + raise EnvironmentError("Can't import GNU Radio") + if not inspect.isclass(cls): - cls = _blk_class(cls) + cls = _find_block_class(cls, gr.gateway.gateway_block) spec = inspect.getargspec(cls.__init__) defaults = map(repr, spec.defaults or ()) diff --git a/grc/python/expr_utils.py b/grc/core/utils/expr_utils.py index 9e0b2a4a0a..66911757d6 100644 --- a/grc/python/expr_utils.py +++ b/grc/core/utils/expr_utils.py @@ -20,6 +20,7 @@ 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. @@ -30,13 +31,16 @@ class graph(object): def __str__(self): return str(self._graph) def add_node(self, node_key): - if self._graph.has_key(node_key): return + if node_key in self._graph: + return self._graph[node_key] = set() def remove_node(self, node_key): - if not self._graph.has_key(node_key): return + if node_key not in self._graph: + return for edges in self._graph.values(): - if node_key in edges: edges.remove(node_key) + if node_key in edges: + edges.remove(node_key) self._graph.pop(node_key) def add_edge(self, src_node_key, dest_node_key): @@ -45,9 +49,12 @@ class graph(object): 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_nodes(self): + return self._graph.keys() + + def get_edges(self, node_key): + return self._graph[node_key] - def get_edges(self, node_key): return self._graph[node_key] def expr_split(expr): """ @@ -66,7 +73,8 @@ def expr_split(expr): quote = '' for char in expr: if quote or char in VAR_CHARS: - if char == quote: quote = '' + if char == quote: + quote = '' tok += char elif char in ("'", '"'): toks.append(tok) @@ -79,6 +87,7 @@ def expr_split(expr): 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. @@ -96,6 +105,7 @@ def expr_replace(expr, replace_dict): 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. @@ -110,6 +120,7 @@ def get_variable_dependencies(expr, vars): 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 @@ -121,14 +132,17 @@ def get_graph(exprs): a graph of variable deps """ vars = exprs.keys() - #get dependencies for each expression, load into graph + # Get dependencies for each expression, load into graph var_graph = graph() - for var in vars: var_graph.add_node(var) + 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) + 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. @@ -142,17 +156,20 @@ def sort_variables(exprs): """ var_graph = get_graph(exprs) sorted_vars = list() - #determine dependency order + # Determine dependency order while var_graph.get_nodes(): - #get a list of nodes with no edges + # 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 + 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) + # 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. @@ -166,12 +183,14 @@ def sort_objects(objects, get_id, get_expr): a list of sorted objects """ id2obj = dict([(get_id(obj), obj) for obj in objects]) - #map obj id to expression code + # Map obj id to expression code id2expr = dict([(get_id(obj), get_expr(obj)) for obj in objects]) - #sort according to dependency + # Sort according to dependency sorted_ids = sort_variables(id2expr) - #return list of sorted objects + # 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 + 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/python/extract_docs.py b/grc/core/utils/extract_docs.py index d8dc4f4e8f..a6e0bc971e 100644 --- a/grc/python/extract_docs.py +++ b/grc/core/utils/extract_docs.py @@ -1,5 +1,5 @@ """ -Copyright 2008-2011 Free Software Foundation, Inc. +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 @@ -32,8 +32,8 @@ import itertools ############################################################################### def docstring_guess_from_key(key): - """Extract the documentation from the python __doc__ strings - + """ + Extract the documentation from the python __doc__ strings By guessing module and constructor names from key Args: @@ -65,12 +65,10 @@ def docstring_guess_from_key(key): else: return doc_strings - pattern = re.compile( - '^' + init_name.replace('_', '_*').replace('x', r'\w') + r'\w*$' - ) + 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__.strip() + doc_strings[match] = getattr(module, match).__doc__ except AttributeError: continue @@ -78,8 +76,8 @@ def docstring_guess_from_key(key): def docstring_from_make(key, imports, make): - """Extract the documentation from the python __doc__ strings - + """ + Extract the documentation from the python __doc__ strings By importing it and checking a truncated make Args: @@ -95,12 +93,10 @@ def docstring_from_make(key, imports, make): 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): @@ -114,10 +110,11 @@ def docstring_from_make(key, imports, make): ############################################################################### class SubprocessLoader(object): - """Start and manage docstring extraction process - + """ + 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 @@ -134,7 +131,7 @@ class SubprocessLoader(object): self._last_cmd = None def start(self): - """Start the worker process handler thread""" + """ Start the worker process handler thread """ if self._thread is not None: return self._shutdown.clear() @@ -143,7 +140,7 @@ class SubprocessLoader(object): thread.start() def run_worker(self): - """Read docstring back from worker stdout and execute callback.""" + """ Read docstring back from worker stdout and execute callback. """ for _ in range(self.RESTART): if self._shutdown.is_set(): break @@ -173,7 +170,7 @@ class SubprocessLoader(object): self.callback_finished() def _handle_worker(self): - """Send commands and responses back from worker.""" + """ 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 @@ -182,13 +179,13 @@ class SubprocessLoader(object): self._handle_response(cmd, args) def _send(self, cmd, args): - """send a command to worker's stdin""" + """ 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""" + """ Receive response from worker's stdout """ for line in iter(self._worker.stdout.readline, ''): try: key, cmd, args = json.loads(line, encoding='utf-8') @@ -201,7 +198,7 @@ class SubprocessLoader(object): raise IOError("Can't read worker response") def _handle_response(self, cmd, args): - """Handle response from worker, call the callback""" + """ Handle response from worker, call the callback """ if cmd == 'result': key, docs = args self.callback_query_result(key, docs) @@ -211,7 +208,7 @@ class SubprocessLoader(object): print >> sys.stderr, "Unknown response:", cmd, args def query(self, key, imports=None, make=None): - """request docstring extraction for a certain key""" + """ Request docstring extraction for a certain key """ if self._thread is None: self.start() if imports and make: @@ -220,16 +217,16 @@ class SubprocessLoader(object): self._queue.put(('query_key_only', (key,))) def finish(self): - """signal end of requests""" + """ Signal end of requests """ self._queue.put(self.DONE) def wait(self): - """Wait for the handler thread to die""" + """ Wait for the handler thread to die """ if self._thread: self._thread.join() def terminate(self): - """Terminate the worker and wait""" + """ Terminate the worker and wait """ self._shutdown.set() try: self._worker.terminate() @@ -243,8 +240,8 @@ class SubprocessLoader(object): ############################################################################### def worker_main(): - """Main entry point for the docstring extraction process. - + """ + 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. """ diff --git a/grc/base/odict.py b/grc/core/utils/odict.py index 70ab67d053..20970e947c 100644 --- a/grc/base/odict.py +++ b/grc/core/utils/odict.py @@ -1,5 +1,5 @@ """ -Copyright 2008-2011 Free Software Foundation, Inc. +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 @@ -19,6 +19,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from UserDict import DictMixin + class odict(DictMixin): def __init__(self, d={}): @@ -57,7 +58,8 @@ class odict(DictMixin): 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 "%s" already exists'%str(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 @@ -72,7 +74,8 @@ class odict(DictMixin): 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 "%s" already exists'%str(key)) + if key in self._keys: + raise KeyError('Cannot insert, key "{}" already exists'.format(str(key))) self._keys.insert(index, key) self._data[key] = val @@ -86,7 +89,8 @@ class odict(DictMixin): Returns: the value or None """ - if self.has_key(key): return self[key] + if key in self: + return self[key] return None def findall(self, key): @@ -100,6 +104,8 @@ class odict(DictMixin): a list of values or empty list """ obj = self.find(key) - if obj is None: obj = list() - if isinstance(obj, list): return obj + if obj is None: + obj = list() + if isinstance(obj, list): + return obj return [obj] diff --git a/grc/cpp/README b/grc/cpp/README deleted file mode 100644 index 3eccc5dbf7..0000000000 --- a/grc/cpp/README +++ /dev/null @@ -1,5 +0,0 @@ -GRC could be used to generate c++ based flowgraphs: - -* A few base and gui classes would be overridden. -* Block info could be extracted from the doxygen xml. -* New flowgraph templates would be designed. diff --git a/grc/examples/simple/variable_config.grc b/grc/examples/simple/variable_config.grc deleted file mode 100644 index 0b60abc813..0000000000 --- a/grc/examples/simple/variable_config.grc +++ /dev/null @@ -1,561 +0,0 @@ -<?xml version='1.0' encoding='ASCII'?> -<flow_graph> - <timestamp>Sat Jul 12 16:15:51 2014</timestamp> - <block> - <key>options</key> - <param> - <key>id</key> - <value>variable_config_demo</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>title</key> - <value>Variable Config Block Demonstration</value> - </param> - <param> - <key>author</key> - <value>Example</value> - </param> - <param> - <key>description</key> - <value>Save/Load freq from a config file.</value> - </param> - <param> - <key>window_size</key> - <value>1280, 1024</value> - </param> - <param> - <key>generate_options</key> - <value>qt_gui</value> - </param> - <param> - <key>category</key> - <value>Custom</value> - </param> - <param> - <key>run_options</key> - <value>prompt</value> - </param> - <param> - <key>run</key> - <value>True</value> - </param> - <param> - <key>max_nouts</key> - <value>0</value> - </param> - <param> - <key>realtime_scheduling</key> - <value></value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(-1, 2)</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>_enabled</key> - <value>True</value> - </param> - <param> - <key>value</key> - <value>32000</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(-1, 125)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>analog_sig_source_x</key> - <param> - <key>id</key> - <value>analog_sig_source_x_0</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>type</key> - <value>complex</value> - </param> - <param> - <key>samp_rate</key> - <value>samp_rate</value> - </param> - <param> - <key>waveform</key> - <value>analog.GR_COS_WAVE</value> - </param> - <param> - <key>freq</key> - <value>freq</value> - </param> - <param> - <key>amp</key> - <value>1</value> - </param> - <param> - <key>offset</key> - <value>0</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>affinity</key> - <value></value> - </param> - <param> - <key>minoutbuf</key> - <value>0</value> - </param> - <param> - <key>maxoutbuf</key> - <value>0</value> - </param> - <param> - <key>_coordinate</key> - <value>(173, 201)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>blocks_throttle</key> - <param> - <key>id</key> - <value>blocks_throttle_0</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>type</key> - <value>complex</value> - </param> - <param> - <key>samples_per_second</key> - <value>samp_rate</value> - </param> - <param> - <key>vlen</key> - <value>1</value> - </param> - <param> - <key>ignoretag</key> - <value>True</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>affinity</key> - <value></value> - </param> - <param> - <key>minoutbuf</key> - <value>0</value> - </param> - <param> - <key>maxoutbuf</key> - <value>0</value> - </param> - <param> - <key>_coordinate</key> - <value>(392, 233)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>qtgui_freq_sink_x</key> - <param> - <key>id</key> - <value>qtgui_freq_sink_x_0</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>type</key> - <value>complex</value> - </param> - <param> - <key>name</key> - <value>QT GUI Plot</value> - </param> - <param> - <key>fftsize</key> - <value>1024</value> - </param> - <param> - <key>wintype</key> - <value>firdes.WIN_BLACKMAN_hARRIS</value> - </param> - <param> - <key>fc</key> - <value>0</value> - </param> - <param> - <key>bw</key> - <value>samp_rate</value> - </param> - <param> - <key>autoscale</key> - <value>False</value> - </param> - <param> - <key>average</key> - <value>1.0</value> - </param> - <param> - <key>ymin</key> - <value>-140</value> - </param> - <param> - <key>ymax</key> - <value>10</value> - </param> - <param> - <key>nconnections</key> - <value>1</value> - </param> - <param> - <key>update_time</key> - <value>0.10</value> - </param> - <param> - <key>gui_hint</key> - <value></value> - </param> - <param> - <key>label1</key> - <value></value> - </param> - <param> - <key>width1</key> - <value>1</value> - </param> - <param> - <key>color1</key> - <value>"blue"</value> - </param> - <param> - <key>alpha1</key> - <value>1.0</value> - </param> - <param> - <key>label2</key> - <value></value> - </param> - <param> - <key>width2</key> - <value>1</value> - </param> - <param> - <key>color2</key> - <value>"red"</value> - </param> - <param> - <key>alpha2</key> - <value>1.0</value> - </param> - <param> - <key>label3</key> - <value></value> - </param> - <param> - <key>width3</key> - <value>1</value> - </param> - <param> - <key>color3</key> - <value>"green"</value> - </param> - <param> - <key>alpha3</key> - <value>1.0</value> - </param> - <param> - <key>label4</key> - <value></value> - </param> - <param> - <key>width4</key> - <value>1</value> - </param> - <param> - <key>color4</key> - <value>"black"</value> - </param> - <param> - <key>alpha4</key> - <value>1.0</value> - </param> - <param> - <key>label5</key> - <value></value> - </param> - <param> - <key>width5</key> - <value>1</value> - </param> - <param> - <key>color5</key> - <value>"cyan"</value> - </param> - <param> - <key>alpha5</key> - <value>1.0</value> - </param> - <param> - <key>label6</key> - <value></value> - </param> - <param> - <key>width6</key> - <value>1</value> - </param> - <param> - <key>color6</key> - <value>"magenta"</value> - </param> - <param> - <key>alpha6</key> - <value>1.0</value> - </param> - <param> - <key>label7</key> - <value></value> - </param> - <param> - <key>width7</key> - <value>1</value> - </param> - <param> - <key>color7</key> - <value>"yellow"</value> - </param> - <param> - <key>alpha7</key> - <value>1.0</value> - </param> - <param> - <key>label8</key> - <value></value> - </param> - <param> - <key>width8</key> - <value>1</value> - </param> - <param> - <key>color8</key> - <value>"dark red"</value> - </param> - <param> - <key>alpha8</key> - <value>1.0</value> - </param> - <param> - <key>label9</key> - <value></value> - </param> - <param> - <key>width9</key> - <value>1</value> - </param> - <param> - <key>color9</key> - <value>"dark green"</value> - </param> - <param> - <key>alpha9</key> - <value>1.0</value> - </param> - <param> - <key>label10</key> - <value></value> - </param> - <param> - <key>width10</key> - <value>1</value> - </param> - <param> - <key>color10</key> - <value>"dark blue"</value> - </param> - <param> - <key>alpha10</key> - <value>1.0</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>affinity</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(643, 221)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>variable_qtgui_range</key> - <param> - <key>id</key> - <value>freq</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>label</key> - <value>Frequency (Hz)</value> - </param> - <param> - <key>value</key> - <value>1e3</value> - </param> - <param> - <key>start</key> - <value>-samp_rate/2</value> - </param> - <param> - <key>stop</key> - <value>samp_rate/2</value> - </param> - <param> - <key>step</key> - <value>1</value> - </param> - <param> - <key>widget</key> - <value>counter_slider</value> - </param> - <param> - <key>orient</key> - <value>Qt.Horizontal</value> - </param> - <param> - <key>min_len</key> - <value>200</value> - </param> - <param> - <key>gui_hint</key> - <value></value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(339, 9)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>variable_config</key> - <param> - <key>id</key> - <value>freq_init</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>value</key> - <value>1000</value> - </param> - <param> - <key>type</key> - <value>real</value> - </param> - <param> - <key>config_file</key> - <value>/home/mbant/.gnuradio/config.conf</value> - </param> - <param> - <key>section</key> - <value>main</value> - </param> - <param> - <key>option</key> - <value>freq</value> - </param> - <param> - <key>writeback</key> - <value>freq</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(168, 0)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <connection> - <source_block_id>analog_sig_source_x_0</source_block_id> - <sink_block_id>blocks_throttle_0</sink_block_id> - <source_key>0</source_key> - <sink_key>0</sink_key> - </connection> - <connection> - <source_block_id>blocks_throttle_0</source_block_id> - <sink_block_id>qtgui_freq_sink_x_0</sink_block_id> - <source_key>0</source_key> - <sink_key>0</sink_key> - </connection> -</flow_graph> diff --git a/grc/examples/xmlrpc/readme.txt b/grc/examples/xmlrpc/readme.txt deleted file mode 100644 index 056ad1e823..0000000000 --- a/grc/examples/xmlrpc/readme.txt +++ /dev/null @@ -1,18 +0,0 @@ -################################################## -# XMLRPC example -################################################## - -XMLRPC allows software to make remote function calls over http. -In the case of GRC, one can use XMLRPC to modify variables in a running flow graph. -See http://www.xmlrpc.com/ - ---- Server Example --- -Place an "XMLRPC Server" block inside of any flow graph. -The server will provide set functions for every variable in the flow graph. -If a variable is called "freq", the server will provide a function set_freq(new_freq). -Run the server example and experiment with the example client script. - --- Client Example -- -The "XMLRPC Client" block will give a variable control over one remove function. -In the example client, there is one client block and gui control per variable. -This technique can be used to remotely control a flow graph, perhaps running on a non-gui machine. diff --git a/grc/examples/xmlrpc/xmlrpc_client.grc b/grc/examples/xmlrpc/xmlrpc_client.grc deleted file mode 100644 index 45d8af2824..0000000000 --- a/grc/examples/xmlrpc/xmlrpc_client.grc +++ /dev/null @@ -1,428 +0,0 @@ -<?xml version='1.0' encoding='ASCII'?> -<flow_graph> - <timestamp>Sat Jul 12 17:10:55 2014</timestamp> - <block> - <key>options</key> - <param> - <key>id</key> - <value>client_block</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>title</key> - <value>XMLRPC Client</value> - </param> - <param> - <key>author</key> - <value>Example</value> - </param> - <param> - <key>description</key> - <value>example flow graph</value> - </param> - <param> - <key>window_size</key> - <value>1280, 1024</value> - </param> - <param> - <key>generate_options</key> - <value>qt_gui</value> - </param> - <param> - <key>category</key> - <value>Custom</value> - </param> - <param> - <key>run_options</key> - <value>prompt</value> - </param> - <param> - <key>run</key> - <value>True</value> - </param> - <param> - <key>max_nouts</key> - <value>0</value> - </param> - <param> - <key>realtime_scheduling</key> - <value></value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(-2, 0)</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>_enabled</key> - <value>True</value> - </param> - <param> - <key>value</key> - <value>32000</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(13, 172)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>xmlrpc_client</key> - <param> - <key>id</key> - <value>xmlrpc_client</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>addr</key> - <value>localhost</value> - </param> - <param> - <key>port</key> - <value>1234</value> - </param> - <param> - <key>callback</key> - <value>set_freq</value> - </param> - <param> - <key>variable</key> - <value>freq</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(177, 0)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>xmlrpc_client</key> - <param> - <key>id</key> - <value>xmlrpc_client0</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>addr</key> - <value>localhost</value> - </param> - <param> - <key>port</key> - <value>1234</value> - </param> - <param> - <key>callback</key> - <value>set_ampl</value> - </param> - <param> - <key>variable</key> - <value>ampl</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(308, 0)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>xmlrpc_client</key> - <param> - <key>id</key> - <value>xmlrpc_client1</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>addr</key> - <value>localhost</value> - </param> - <param> - <key>port</key> - <value>1234</value> - </param> - <param> - <key>callback</key> - <value>set_offset</value> - </param> - <param> - <key>variable</key> - <value>offset*ampl</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(440, 4)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>variable_qtgui_range</key> - <param> - <key>id</key> - <value>freq</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>label</key> - <value>Frequency (Hz)</value> - </param> - <param> - <key>value</key> - <value>1000</value> - </param> - <param> - <key>start</key> - <value>0</value> - </param> - <param> - <key>stop</key> - <value>5e3</value> - </param> - <param> - <key>step</key> - <value>1</value> - </param> - <param> - <key>widget</key> - <value>counter_slider</value> - </param> - <param> - <key>orient</key> - <value>Qt.Horizontal</value> - </param> - <param> - <key>min_len</key> - <value>200</value> - </param> - <param> - <key>gui_hint</key> - <value>0,0,1,2</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(209, 165)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>variable_qtgui_range</key> - <param> - <key>id</key> - <value>ampl</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>label</key> - <value>Amplitude</value> - </param> - <param> - <key>value</key> - <value>1</value> - </param> - <param> - <key>start</key> - <value>0</value> - </param> - <param> - <key>stop</key> - <value>2</value> - </param> - <param> - <key>step</key> - <value>.1</value> - </param> - <param> - <key>widget</key> - <value>counter_slider</value> - </param> - <param> - <key>orient</key> - <value>Qt.Horizontal</value> - </param> - <param> - <key>min_len</key> - <value>200</value> - </param> - <param> - <key>gui_hint</key> - <value>1,0,1,2</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(367, 158)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>variable_qtgui_chooser</key> - <param> - <key>id</key> - <value>offset</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>label</key> - <value>Offset</value> - </param> - <param> - <key>type</key> - <value>int</value> - </param> - <param> - <key>num_opts</key> - <value>3</value> - </param> - <param> - <key>value</key> - <value>0</value> - </param> - <param> - <key>options</key> - <value>[0, 1, 2]</value> - </param> - <param> - <key>labels</key> - <value>[]</value> - </param> - <param> - <key>option0</key> - <value>-1</value> - </param> - <param> - <key>label0</key> - <value>neg</value> - </param> - <param> - <key>option1</key> - <value>0</value> - </param> - <param> - <key>label1</key> - <value>zero</value> - </param> - <param> - <key>option2</key> - <value>1</value> - </param> - <param> - <key>label2</key> - <value>pos</value> - </param> - <param> - <key>option3</key> - <value>3</value> - </param> - <param> - <key>label3</key> - <value></value> - </param> - <param> - <key>option4</key> - <value>4</value> - </param> - <param> - <key>label4</key> - <value></value> - </param> - <param> - <key>widget</key> - <value>combo_box</value> - </param> - <param> - <key>orient</key> - <value>Qt.QVBoxLayout</value> - </param> - <param> - <key>gui_hint</key> - <value>2,0,1,2</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(531, 145)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> -</flow_graph> diff --git a/grc/examples/xmlrpc/xmlrpc_client_script.py b/grc/examples/xmlrpc/xmlrpc_client_script.py deleted file mode 100644 index e96c4cbf83..0000000000 --- a/grc/examples/xmlrpc/xmlrpc_client_script.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python - -import time -import random -import xmlrpclib - -#create server object -s = xmlrpclib.Server("http://localhost:1234") - -#randomly change parameters of the sinusoid -for i in range(10): - #generate random values - new_freq = random.uniform(0, 5000) - new_ampl = random.uniform(0, 2) - new_offset = random.uniform(-1, 1) - #set new values - time.sleep(1) - s.set_freq(new_freq) - time.sleep(1) - s.set_ampl(new_ampl) - time.sleep(1) - s.set_offset(new_offset) - diff --git a/grc/examples/xmlrpc/xmlrpc_server.grc b/grc/examples/xmlrpc/xmlrpc_server.grc deleted file mode 100644 index d210b2694e..0000000000 --- a/grc/examples/xmlrpc/xmlrpc_server.grc +++ /dev/null @@ -1,908 +0,0 @@ -<?xml version='1.0' encoding='ASCII'?> -<flow_graph> - <timestamp>Sat Jul 12 17:11:40 2014</timestamp> - <block> - <key>options</key> - <param> - <key>id</key> - <value>server_block</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>title</key> - <value>XMLRPC Server</value> - </param> - <param> - <key>author</key> - <value>Example</value> - </param> - <param> - <key>description</key> - <value>example flow graph</value> - </param> - <param> - <key>window_size</key> - <value>1280, 1024</value> - </param> - <param> - <key>generate_options</key> - <value>qt_gui</value> - </param> - <param> - <key>category</key> - <value>Custom</value> - </param> - <param> - <key>run_options</key> - <value>prompt</value> - </param> - <param> - <key>run</key> - <value>True</value> - </param> - <param> - <key>max_nouts</key> - <value>0</value> - </param> - <param> - <key>realtime_scheduling</key> - <value></value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(0, -1)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>variable</key> - <param> - <key>id</key> - <value>ampl</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>value</key> - <value>1</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(4, 291)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>variable</key> - <param> - <key>id</key> - <value>freq</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>value</key> - <value>1000</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(2, 213)</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>_enabled</key> - <value>True</value> - </param> - <param> - <key>value</key> - <value>32000</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(2, 136)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>variable</key> - <param> - <key>id</key> - <value>offset</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>value</key> - <value>0</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(3, 366)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>analog_sig_source_x</key> - <param> - <key>id</key> - <value>analog_sig_source_x_0</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>type</key> - <value>float</value> - </param> - <param> - <key>samp_rate</key> - <value>samp_rate</value> - </param> - <param> - <key>waveform</key> - <value>analog.GR_COS_WAVE</value> - </param> - <param> - <key>freq</key> - <value>freq</value> - </param> - <param> - <key>amp</key> - <value>ampl</value> - </param> - <param> - <key>offset</key> - <value>offset</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>affinity</key> - <value></value> - </param> - <param> - <key>minoutbuf</key> - <value>0</value> - </param> - <param> - <key>maxoutbuf</key> - <value>0</value> - </param> - <param> - <key>_coordinate</key> - <value>(175, 0)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>blocks_throttle</key> - <param> - <key>id</key> - <value>blocks_throttle</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>type</key> - <value>float</value> - </param> - <param> - <key>samples_per_second</key> - <value>samp_rate</value> - </param> - <param> - <key>vlen</key> - <value>1</value> - </param> - <param> - <key>ignoretag</key> - <value>True</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>affinity</key> - <value></value> - </param> - <param> - <key>minoutbuf</key> - <value>0</value> - </param> - <param> - <key>maxoutbuf</key> - <value>0</value> - </param> - <param> - <key>_coordinate</key> - <value>(399, 35)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>xmlrpc_server</key> - <param> - <key>id</key> - <value>xmlrpc_server</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>addr</key> - <value>localhost</value> - </param> - <param> - <key>port</key> - <value>1234</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(129, 137)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>qtgui_time_sink_x</key> - <param> - <key>id</key> - <value>qtgui_time_sink_x_0</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>type</key> - <value>float</value> - </param> - <param> - <key>name</key> - <value>Scope Plot</value> - </param> - <param> - <key>size</key> - <value>1024</value> - </param> - <param> - <key>srate</key> - <value>samp_rate</value> - </param> - <param> - <key>autoscale</key> - <value>False</value> - </param> - <param> - <key>ymin</key> - <value>-1</value> - </param> - <param> - <key>ymax</key> - <value>1</value> - </param> - <param> - <key>nconnections</key> - <value>1</value> - </param> - <param> - <key>update_time</key> - <value>0.10</value> - </param> - <param> - <key>entags</key> - <value>True</value> - </param> - <param> - <key>gui_hint</key> - <value>0, 0, 2, 4</value> - </param> - <param> - <key>tr_mode</key> - <value>qtgui.TRIG_MODE_FREE</value> - </param> - <param> - <key>tr_slope</key> - <value>qtgui.TRIG_SLOPE_POS</value> - </param> - <param> - <key>tr_level</key> - <value>0.0</value> - </param> - <param> - <key>tr_delay</key> - <value>0</value> - </param> - <param> - <key>tr_chan</key> - <value>0</value> - </param> - <param> - <key>tr_tag</key> - <value>""</value> - </param> - <param> - <key>label1</key> - <value></value> - </param> - <param> - <key>width1</key> - <value>1</value> - </param> - <param> - <key>color1</key> - <value>"blue"</value> - </param> - <param> - <key>style1</key> - <value>1</value> - </param> - <param> - <key>marker1</key> - <value>-1</value> - </param> - <param> - <key>alpha1</key> - <value>1.0</value> - </param> - <param> - <key>label2</key> - <value></value> - </param> - <param> - <key>width2</key> - <value>1</value> - </param> - <param> - <key>color2</key> - <value>"red"</value> - </param> - <param> - <key>style2</key> - <value>1</value> - </param> - <param> - <key>marker2</key> - <value>-1</value> - </param> - <param> - <key>alpha2</key> - <value>1.0</value> - </param> - <param> - <key>label3</key> - <value></value> - </param> - <param> - <key>width3</key> - <value>1</value> - </param> - <param> - <key>color3</key> - <value>"green"</value> - </param> - <param> - <key>style3</key> - <value>1</value> - </param> - <param> - <key>marker3</key> - <value>-1</value> - </param> - <param> - <key>alpha3</key> - <value>1.0</value> - </param> - <param> - <key>label4</key> - <value></value> - </param> - <param> - <key>width4</key> - <value>1</value> - </param> - <param> - <key>color4</key> - <value>"black"</value> - </param> - <param> - <key>style4</key> - <value>1</value> - </param> - <param> - <key>marker4</key> - <value>-1</value> - </param> - <param> - <key>alpha4</key> - <value>1.0</value> - </param> - <param> - <key>label5</key> - <value></value> - </param> - <param> - <key>width5</key> - <value>1</value> - </param> - <param> - <key>color5</key> - <value>"cyan"</value> - </param> - <param> - <key>style5</key> - <value>1</value> - </param> - <param> - <key>marker5</key> - <value>-1</value> - </param> - <param> - <key>alpha5</key> - <value>1.0</value> - </param> - <param> - <key>label6</key> - <value></value> - </param> - <param> - <key>width6</key> - <value>1</value> - </param> - <param> - <key>color6</key> - <value>"magenta"</value> - </param> - <param> - <key>style6</key> - <value>1</value> - </param> - <param> - <key>marker6</key> - <value>-1</value> - </param> - <param> - <key>alpha6</key> - <value>1.0</value> - </param> - <param> - <key>label7</key> - <value></value> - </param> - <param> - <key>width7</key> - <value>1</value> - </param> - <param> - <key>color7</key> - <value>"yellow"</value> - </param> - <param> - <key>style7</key> - <value>1</value> - </param> - <param> - <key>marker7</key> - <value>-1</value> - </param> - <param> - <key>alpha7</key> - <value>1.0</value> - </param> - <param> - <key>label8</key> - <value></value> - </param> - <param> - <key>width8</key> - <value>1</value> - </param> - <param> - <key>color8</key> - <value>"dark red"</value> - </param> - <param> - <key>style8</key> - <value>1</value> - </param> - <param> - <key>marker8</key> - <value>-1</value> - </param> - <param> - <key>alpha8</key> - <value>1.0</value> - </param> - <param> - <key>label9</key> - <value></value> - </param> - <param> - <key>width9</key> - <value>1</value> - </param> - <param> - <key>color9</key> - <value>"dark green"</value> - </param> - <param> - <key>style9</key> - <value>1</value> - </param> - <param> - <key>marker9</key> - <value>-1</value> - </param> - <param> - <key>alpha9</key> - <value>1.0</value> - </param> - <param> - <key>label10</key> - <value></value> - </param> - <param> - <key>width10</key> - <value>1</value> - </param> - <param> - <key>color10</key> - <value>"blue"</value> - </param> - <param> - <key>style10</key> - <value>1</value> - </param> - <param> - <key>marker10</key> - <value>-1</value> - </param> - <param> - <key>alpha10</key> - <value>1.0</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>affinity</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(644, 13)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>qtgui_freq_sink_x</key> - <param> - <key>id</key> - <value>qtgui_freq_sink_x_0</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>type</key> - <value>float</value> - </param> - <param> - <key>name</key> - <value>Spectrum Plot</value> - </param> - <param> - <key>fftsize</key> - <value>1024</value> - </param> - <param> - <key>wintype</key> - <value>firdes.WIN_BLACKMAN_hARRIS</value> - </param> - <param> - <key>fc</key> - <value>0</value> - </param> - <param> - <key>bw</key> - <value>samp_rate</value> - </param> - <param> - <key>autoscale</key> - <value>False</value> - </param> - <param> - <key>average</key> - <value>1.0</value> - </param> - <param> - <key>ymin</key> - <value>-140</value> - </param> - <param> - <key>ymax</key> - <value>10</value> - </param> - <param> - <key>nconnections</key> - <value>1</value> - </param> - <param> - <key>update_time</key> - <value>0.10</value> - </param> - <param> - <key>gui_hint</key> - <value>2, 0, 2, 4</value> - </param> - <param> - <key>label1</key> - <value></value> - </param> - <param> - <key>width1</key> - <value>1</value> - </param> - <param> - <key>color1</key> - <value>"blue"</value> - </param> - <param> - <key>alpha1</key> - <value>1.0</value> - </param> - <param> - <key>label2</key> - <value></value> - </param> - <param> - <key>width2</key> - <value>1</value> - </param> - <param> - <key>color2</key> - <value>"red"</value> - </param> - <param> - <key>alpha2</key> - <value>1.0</value> - </param> - <param> - <key>label3</key> - <value></value> - </param> - <param> - <key>width3</key> - <value>1</value> - </param> - <param> - <key>color3</key> - <value>"green"</value> - </param> - <param> - <key>alpha3</key> - <value>1.0</value> - </param> - <param> - <key>label4</key> - <value></value> - </param> - <param> - <key>width4</key> - <value>1</value> - </param> - <param> - <key>color4</key> - <value>"black"</value> - </param> - <param> - <key>alpha4</key> - <value>1.0</value> - </param> - <param> - <key>label5</key> - <value></value> - </param> - <param> - <key>width5</key> - <value>1</value> - </param> - <param> - <key>color5</key> - <value>"cyan"</value> - </param> - <param> - <key>alpha5</key> - <value>1.0</value> - </param> - <param> - <key>label6</key> - <value></value> - </param> - <param> - <key>width6</key> - <value>1</value> - </param> - <param> - <key>color6</key> - <value>"magenta"</value> - </param> - <param> - <key>alpha6</key> - <value>1.0</value> - </param> - <param> - <key>label7</key> - <value></value> - </param> - <param> - <key>width7</key> - <value>1</value> - </param> - <param> - <key>color7</key> - <value>"yellow"</value> - </param> - <param> - <key>alpha7</key> - <value>1.0</value> - </param> - <param> - <key>label8</key> - <value></value> - </param> - <param> - <key>width8</key> - <value>1</value> - </param> - <param> - <key>color8</key> - <value>"dark red"</value> - </param> - <param> - <key>alpha8</key> - <value>1.0</value> - </param> - <param> - <key>label9</key> - <value></value> - </param> - <param> - <key>width9</key> - <value>1</value> - </param> - <param> - <key>color9</key> - <value>"dark green"</value> - </param> - <param> - <key>alpha9</key> - <value>1.0</value> - </param> - <param> - <key>label10</key> - <value></value> - </param> - <param> - <key>width10</key> - <value>1</value> - </param> - <param> - <key>color10</key> - <value>"dark blue"</value> - </param> - <param> - <key>alpha10</key> - <value>1.0</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>affinity</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(644, 126)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <connection> - <source_block_id>analog_sig_source_x_0</source_block_id> - <sink_block_id>blocks_throttle</sink_block_id> - <source_key>0</source_key> - <sink_key>0</sink_key> - </connection> - <connection> - <source_block_id>blocks_throttle</source_block_id> - <sink_block_id>qtgui_time_sink_x_0</sink_block_id> - <source_key>0</source_key> - <sink_key>0</sink_key> - </connection> - <connection> - <source_block_id>blocks_throttle</source_block_id> - <sink_block_id>qtgui_freq_sink_x_0</sink_block_id> - <source_key>0</source_key> - <sink_key>0</sink_key> - </connection> -</flow_graph> diff --git a/grc/grc_gnuradio/README b/grc/grc_gnuradio/README deleted file mode 100644 index 897eed65ca..0000000000 --- a/grc/grc_gnuradio/README +++ /dev/null @@ -1,11 +0,0 @@ -This is the grc_gnuradio module. -It contains supplemental python modules that grc uses at runtime. -The supplemental modules are meant to mimic modules in gnuradio. -These will be phased-out as new functionaility is merged into gnuradio. - -The blk2s module wraps many blocks in blks2 and gives them streaming outputs. -Will be phased-out by new message passing implementations. -Other blks2 blocks will hopefully make their way into blks2impl. - -The wxgui module contains a top_block + wxgui frame. -Will be phased-out by gui.py in wxgui and a new top block template. diff --git a/grc/grc_gnuradio/blks2/error_rate.py b/grc/grc_gnuradio/blks2/error_rate.py deleted file mode 100644 index 9bf387030a..0000000000 --- a/grc/grc_gnuradio/blks2/error_rate.py +++ /dev/null @@ -1,140 +0,0 @@ -# Copyright 2008 Free Software Foundation, Inc. -# -# This file is part of GNU Radio -# -# GNU Radio is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3, or (at your option) -# any later version. -# -# GNU Radio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Radio; see the file COPYING. If not, write to -# the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. -# - -default_win_size = 1000 - -from gnuradio import gr -from gnuradio import blocks -import gnuradio.gr.gr_threading as _threading -import numpy - -#generate 1s counts array -_1s_counts = [sum([1&(i>>j) for j in range(8)]) for i in range(2**8)] - -class input_watcher(_threading.Thread): - """ - Read samples from the message queue and hand them to the callback. - """ - - def __init__(self, msgq, callback): - self._msgq = msgq - self._callback = callback - _threading.Thread.__init__(self) - self.setDaemon(1) - self.keep_running = True - self.start() - - def run(self): - r = '' - while True: - msg = self._msgq.delete_head() - itemsize = int(msg.arg1()) - nitems = int(msg.arg2()) - s = r + msg.to_string() - i = (nitems-nitems%2)*itemsize - r = s[i:] - s = s[:i] - samples = numpy.fromstring(s, numpy.int8) - self._callback(samples) - -class error_rate(gr.hier_block2): - """ - Sample the incoming data streams (byte) and calculate the bit or symbol error rate. - Write the running rate to the output data stream (float). - """ - - def __init__(self, type='BER', win_size=default_win_size, bits_per_symbol=2): - """ - Error rate constructor. - - Args: - type: a string 'BER' or 'SER' - win_size: the number of samples to calculate over - bits_per_symbol: the number of information bits per symbol (BER only) - """ - #init - gr.hier_block2.__init__( - self, 'error_rate', - gr.io_signature(2, 2, gr.sizeof_char), - gr.io_signature(1, 1, gr.sizeof_float), - ) - assert type in ('BER', 'SER') - self._max_samples = win_size - self._bits_per_symbol = bits_per_symbol - #setup message queue - msg_source = blocks.message_source(gr.sizeof_float, 1) - self._msgq_source = msg_source.msgq() - msgq_sink = gr.msg_queue(2) - msg_sink = blocks.message_sink(gr.sizeof_char, msgq_sink, False) #False -> blocking - inter = blocks.interleave(gr.sizeof_char) - #start thread - self._num_errs = 0 - self._err_index = 0 - self._num_samps = 0 - self._err_array = numpy.zeros(self._max_samples, numpy.int8) - if type == 'BER': - input_watcher(msgq_sink, self._handler_ber) - elif type == 'SER': - input_watcher(msgq_sink, self._handler_ser) - #connect - self.connect(msg_source, self) - self.connect((self, 0), (inter, 0)) - self.connect((self, 1), (inter, 1)) - self.connect(inter, msg_sink) - - def _handler_ber(self, samples): - num = len(samples)/2 - arr = numpy.zeros(num, numpy.float32) - for i in range(num): - old_err = self._err_array[self._err_index] - #record error - self._err_array[self._err_index] = _1s_counts[samples[i*2] ^ samples[i*2 + 1]] - self._num_errs = self._num_errs + self._err_array[self._err_index] - old_err - #increment index - self._err_index = (self._err_index + 1)%self._max_samples - self._num_samps = min(self._num_samps + 1, self._max_samples) - #write sample - arr[i] = float(self._num_errs)/float(self._num_samps*self._bits_per_symbol) - #write message - msg = gr.message_from_string(arr.tostring(), 0, gr.sizeof_float, num) - self._msgq_source.insert_tail(msg) - - def _handler_ser(self, samples): - num = len(samples)/2 - arr = numpy.zeros(num, numpy.float32) - for i in range(num): - old_err = self._err_array[self._err_index] - #record error - ref = samples[i*2] - res = samples[i*2 + 1] - if ref == res: - self._err_array[self._err_index] = 0 - else: - self._err_array[self._err_index] = 1 - #update number of errors - self._num_errs = self._num_errs + self._err_array[self._err_index] - old_err - #increment index - self._err_index = (self._err_index + 1)%self._max_samples - self._num_samps = min(self._num_samps + 1, self._max_samples) - #write sample - arr[i] = float(self._num_errs)/float(self._num_samps) - #write message - msg = gr.message_from_string(arr.tostring(), 0, gr.sizeof_float, num) - self._msgq_source.insert_tail(msg) diff --git a/grc/grc_gnuradio/blks2/packet.py b/grc/grc_gnuradio/blks2/packet.py deleted file mode 100644 index ef79afde64..0000000000 --- a/grc/grc_gnuradio/blks2/packet.py +++ /dev/null @@ -1,257 +0,0 @@ -# Copyright 2008,2009,2012-2013 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. -# - -from gnuradio import gr, digital -from gnuradio import blocks -from gnuradio.digital import packet_utils -import gnuradio.gr.gr_threading as _threading - -##payload length in bytes -DEFAULT_PAYLOAD_LEN = 512 - -##how many messages in a queue -DEFAULT_MSGQ_LIMIT = 2 - -##threshold for unmaking packets -DEFAULT_THRESHOLD = 12 - -################################################## -## Options Class for OFDM -################################################## -class options(object): - def __init__(self, **kwargs): - for key, value in kwargs.iteritems(): setattr(self, key, value) - -################################################## -## Packet Encoder -################################################## -class _packet_encoder_thread(_threading.Thread): - - def __init__(self, msgq, payload_length, send): - self._msgq = msgq - self._payload_length = payload_length - self._send = send - _threading.Thread.__init__(self) - self.setDaemon(1) - self.keep_running = True - self.start() - - def run(self): - sample = '' #residual sample - while self.keep_running: - msg = self._msgq.delete_head() #blocking read of message queue - sample = sample + msg.to_string() #get the body of the msg as a string - while len(sample) >= self._payload_length: - payload = sample[:self._payload_length] - sample = sample[self._payload_length:] - self._send(payload) - -class packet_encoder(gr.hier_block2): - """ - Hierarchical block for wrapping packet-based modulators. - """ - - def __init__(self, samples_per_symbol, bits_per_symbol, preamble='', access_code='', pad_for_usrp=True): - """ - packet_mod constructor. - - Args: - samples_per_symbol: number of samples per symbol - bits_per_symbol: number of bits per symbol - preamble: string of ascii 0's and 1's - access_code: AKA sync vector - pad_for_usrp: If true, packets are padded such that they end up a multiple of 128 samples - """ - #setup parameters - self._samples_per_symbol = samples_per_symbol - self._bits_per_symbol = bits_per_symbol - self._pad_for_usrp = pad_for_usrp - if not preamble: #get preamble - preamble = packet_utils.default_preamble - if not access_code: #get access code - access_code = packet_utils.default_access_code - if not packet_utils.is_1_0_string(preamble): - raise ValueError, "Invalid preamble %r. Must be string of 1's and 0's" % (preamble,) - if not packet_utils.is_1_0_string(access_code): - raise ValueError, "Invalid access_code %r. Must be string of 1's and 0's" % (access_code,) - self._preamble = preamble - self._access_code = access_code - self._pad_for_usrp = pad_for_usrp - #create blocks - msg_source = blocks.message_source(gr.sizeof_char, DEFAULT_MSGQ_LIMIT) - self._msgq_out = msg_source.msgq() - #initialize hier2 - gr.hier_block2.__init__( - self, - "packet_encoder", - gr.io_signature(0, 0, 0), # Input signature - gr.io_signature(1, 1, gr.sizeof_char) # Output signature - ) - #connect - self.connect(msg_source, self) - - def send_pkt(self, payload): - """ - Wrap the payload in a packet and push onto the message queue. - - Args: - payload: string, data to send - """ - packet = packet_utils.make_packet( - payload, - self._samples_per_symbol, - self._bits_per_symbol, - self._preamble, - self._access_code, - self._pad_for_usrp - ) - msg = gr.message_from_string(packet) - self._msgq_out.insert_tail(msg) - -################################################## -## Packet Decoder -################################################## -class _packet_decoder_thread(_threading.Thread): - - def __init__(self, msgq, callback): - _threading.Thread.__init__(self) - self.setDaemon(1) - self._msgq = msgq - self.callback = callback - self.keep_running = True - self.start() - - def run(self): - while self.keep_running: - msg = self._msgq.delete_head() - ok, payload = packet_utils.unmake_packet(msg.to_string(), int(msg.arg1())) - if self.callback: - self.callback(ok, payload) - -class packet_decoder(gr.hier_block2): - """ - Hierarchical block for wrapping packet-based demodulators. - """ - - def __init__(self, access_code='', threshold=-1, callback=None): - """ - packet_demod constructor. - - Args: - access_code: AKA sync vector - threshold: detect access_code with up to threshold bits wrong (0 -> use default) - callback: a function of args: ok, payload - """ - #access code - if not access_code: #get access code - access_code = packet_utils.default_access_code - if not packet_utils.is_1_0_string(access_code): - raise ValueError, "Invalid access_code %r. Must be string of 1's and 0's" % (access_code,) - self._access_code = access_code - #threshold - if threshold < 0: threshold = DEFAULT_THRESHOLD - self._threshold = threshold - #blocks - msgq = gr.msg_queue(DEFAULT_MSGQ_LIMIT) #holds packets from the PHY - correlator = digital.correlate_access_code_bb(self._access_code, self._threshold) - framer_sink = digital.framer_sink_1(msgq) - #initialize hier2 - gr.hier_block2.__init__( - self, - "packet_decoder", - gr.io_signature(1, 1, gr.sizeof_char), # Input signature - gr.io_signature(0, 0, 0) # Output signature - ) - #connect - self.connect(self, correlator, framer_sink) - #start thread - _packet_decoder_thread(msgq, callback) - -################################################## -## Packet Mod for OFDM Mod and Packet Encoder -################################################## -class packet_mod_base(gr.hier_block2): - """ - Hierarchical block for wrapping packet source block. - """ - - def __init__(self, packet_source=None, payload_length=0): - if not payload_length: #get payload length - payload_length = DEFAULT_PAYLOAD_LEN - if payload_length%self._item_size_in != 0: #verify that packet length is a multiple of the stream size - raise ValueError, 'The payload length: "%d" is not a mutiple of the stream size: "%d".'%(payload_length, self._item_size_in) - #initialize hier2 - gr.hier_block2.__init__( - self, - "ofdm_mod", - gr.io_signature(1, 1, self._item_size_in), # Input signature - gr.io_signature(1, 1, packet_source.output_signature().sizeof_stream_item(0)) # Output signature - ) - #create blocks - msgq = gr.msg_queue(DEFAULT_MSGQ_LIMIT) - msg_sink = blocks.message_sink(self._item_size_in, msgq, False) #False -> blocking - #connect - self.connect(self, msg_sink) - self.connect(packet_source, self) - #start thread - _packet_encoder_thread(msgq, payload_length, packet_source.send_pkt) - -class packet_mod_b(packet_mod_base): _item_size_in = gr.sizeof_char -class packet_mod_s(packet_mod_base): _item_size_in = gr.sizeof_short -class packet_mod_i(packet_mod_base): _item_size_in = gr.sizeof_int -class packet_mod_f(packet_mod_base): _item_size_in = gr.sizeof_float -class packet_mod_c(packet_mod_base): _item_size_in = gr.sizeof_gr_complex - -################################################## -## Packet Demod for OFDM Demod and Packet Decoder -################################################## -class packet_demod_base(gr.hier_block2): - """ - Hierarchical block for wrapping packet sink block. - """ - - def __init__(self, packet_sink=None): - #initialize hier2 - gr.hier_block2.__init__( - self, - "ofdm_mod", - gr.io_signature(1, 1, packet_sink.input_signature().sizeof_stream_item(0)), # Input signature - gr.io_signature(1, 1, self._item_size_out) # Output signature - ) - #create blocks - msg_source = blocks.message_source(self._item_size_out, DEFAULT_MSGQ_LIMIT) - self._msgq_out = msg_source.msgq() - #connect - self.connect(self, packet_sink) - self.connect(msg_source, self) - if packet_sink.output_signature().sizeof_stream_item(0): - self.connect(packet_sink, - blocks.null_sink(packet_sink.output_signature().sizeof_stream_item(0))) - - def recv_pkt(self, ok, payload): - msg = gr.message_from_string(payload, 0, self._item_size_out, - len(payload)/self._item_size_out) - if ok: self._msgq_out.insert_tail(msg) - -class packet_demod_b(packet_demod_base): _item_size_out = gr.sizeof_char -class packet_demod_s(packet_demod_base): _item_size_out = gr.sizeof_short -class packet_demod_i(packet_demod_base): _item_size_out = gr.sizeof_int -class packet_demod_f(packet_demod_base): _item_size_out = gr.sizeof_float -class packet_demod_c(packet_demod_base): _item_size_out = gr.sizeof_gr_complex diff --git a/grc/grc_gnuradio/blks2/selector.py b/grc/grc_gnuradio/blks2/selector.py deleted file mode 100644 index 24e3844658..0000000000 --- a/grc/grc_gnuradio/blks2/selector.py +++ /dev/null @@ -1,142 +0,0 @@ -# -# Copyright 2008,2013 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. -# - -from gnuradio import gr -from gnuradio import blocks - -class selector(gr.hier_block2): - """A hier2 block with N inputs and M outputs, where data is only forwarded through input n to output m.""" - def __init__(self, item_size, num_inputs, num_outputs, input_index, output_index): - """ - Selector constructor. - - Args: - item_size: the size of the gr data stream in bytes - num_inputs: the number of inputs (integer) - num_outputs: the number of outputs (integer) - input_index: the index for the source data - output_index: the index for the destination data - """ - gr.hier_block2.__init__( - self, 'selector', - gr.io_signature(num_inputs, num_inputs, item_size), - gr.io_signature(num_outputs, num_outputs, item_size), - ) - #terminator blocks for unused inputs and outputs - self.input_terminators = [blocks.null_sink(item_size) for i in range(num_inputs)] - self.output_terminators = [blocks.head(item_size, 0) for i in range(num_outputs)] - self.copy = blocks.copy(item_size) - #connections - for i in range(num_inputs): self.connect((self, i), self.input_terminators[i]) - for i in range(num_outputs): self.connect(blocks.null_source(item_size), - self.output_terminators[i], (self, i)) - self.item_size = item_size - self.input_index = input_index - self.output_index = output_index - self.num_inputs = num_inputs - self.num_outputs = num_outputs - self._connect_current() - - def _indexes_valid(self): - """ - Are the input and output indexes within range of the number of inputs and outputs? - - Returns: - true if input index and output index are in range - """ - return self.input_index in range(self.num_inputs) and self.output_index in range(self.num_outputs) - - def _connect_current(self): - """If the input and output indexes are valid: - disconnect the blocks at the input and output index from their terminators, - and connect them to one another. Then connect the terminators to one another.""" - if self._indexes_valid(): - self.disconnect((self, self.input_index), self.input_terminators[self.input_index]) - self.disconnect(self.output_terminators[self.output_index], (self, self.output_index)) - self.connect((self, self.input_index), self.copy) - self.connect(self.copy, (self, self.output_index)) - self.connect(self.output_terminators[self.output_index], self.input_terminators[self.input_index]) - - def _disconnect_current(self): - """If the input and output indexes are valid: - disconnect the blocks at the input and output index from one another, - and the terminators at the input and output index from one another. - Reconnect the blocks to the terminators.""" - if self._indexes_valid(): - self.disconnect((self, self.input_index), self.copy) - self.disconnect(self.copy, (self, self.output_index)) - self.disconnect(self.output_terminators[self.output_index], self.input_terminators[self.input_index]) - self.connect((self, self.input_index), self.input_terminators[self.input_index]) - self.connect(self.output_terminators[self.output_index], (self, self.output_index)) - - def set_input_index(self, input_index): - """ - Change the block to the new input index if the index changed. - - Args: - input_index: the new input index - """ - if self.input_index != input_index: - self.lock() - self._disconnect_current() - self.input_index = input_index - self._connect_current() - self.unlock() - - def set_output_index(self, output_index): - """ - Change the block to the new output index if the index changed. - - Args: - output_index: the new output index - """ - if self.output_index != output_index: - self.lock() - self._disconnect_current() - self.output_index = output_index - self._connect_current() - self.unlock() - -class valve(selector): - """Wrapper for selector with 1 input and 1 output.""" - - def __init__(self, item_size, open): - """ - Constructor for valve. - - Args: - item_size: the size of the gr data stream in bytes - open: true if initial valve state is open - """ - if open: output_index = -1 - else: output_index = 0 - selector.__init__(self, item_size, 1, 1, 0, output_index) - - def set_open(self, open): - """ - Callback to set open state. - - Args: - open: true to set valve state to open - """ - if open: output_index = -1 - else: output_index = 0 - self.set_output_index(output_index) diff --git a/grc/grc_gnuradio/blks2/tcp.py b/grc/grc_gnuradio/blks2/tcp.py deleted file mode 100644 index aee90fad2c..0000000000 --- a/grc/grc_gnuradio/blks2/tcp.py +++ /dev/null @@ -1,70 +0,0 @@ -# -# Copyright 2009 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. -# - -from gnuradio import gr, blocks -import socket -import os - -def _get_sock_fd(addr, port, server): - """ - Get the file descriptor for the socket. - As a client, block on connect, dup the socket descriptor. - As a server, block on accept, dup the client descriptor. - - Args: - addr: the ip address string - port: the tcp port number - server: true for server mode, false for client mode - - Returns: - the file descriptor number - """ - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - if server: - sock.bind((addr, port)) - sock.listen(1) - clientsock, address = sock.accept() - return os.dup(clientsock.fileno()) - else: - sock.connect((addr, port)) - return os.dup(sock.fileno()) - -class tcp_source(gr.hier_block2): - def __init__(self, itemsize, addr, port, server=True): - #init hier block - gr.hier_block2.__init__( - self, 'tcp_source', - gr.io_signature(0, 0, 0), - gr.io_signature(1, 1, itemsize), - ) - fd = _get_sock_fd(addr, port, server) - self.connect(blocks.file_descriptor_source(itemsize, fd), self) - -class tcp_sink(gr.hier_block2): - def __init__(self, itemsize, addr, port, server=False): - #init hier block - gr.hier_block2.__init__( - self, 'tcp_sink', - gr.io_signature(1, 1, itemsize), - gr.io_signature(0, 0, 0), - ) - fd = _get_sock_fd(addr, port, server) - self.connect(self, blocks.file_descriptor_sink(itemsize, fd)) diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 7766a0a853..077786d4b4 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -17,26 +17,21 @@ 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 subprocess -from threading import Thread -import pygtk -pygtk.require('2.0') -import gtk import gobject +import gtk +import os +import subprocess -from .. base import ParseXML, Constants -from .. python.Constants import XTERM_EXECUTABLE - -from . import Dialogs, Messages, Preferences, Actions -from .ParserErrorsDialog import ParserErrorsDialog -from .MainWindow import MainWindow -from .PropsDialog import PropsDialog +from . import Dialogs, Preferences, Actions, Executor, Constants from .FileDialogs import (OpenFlowGraphFileDialog, SaveFlowGraphFileDialog, - SaveReportsFileDialog, SaveImageFileDialog, + SaveReportsFileDialog, SaveScreenShotDialog, OpenQSSFileDialog) -from .Constants import DEFAULT_CANVAS_SIZE, IMAGE_FILE_EXTENSION, GR_PREFIX +from .MainWindow import MainWindow +from .ParserErrorsDialog import ParserErrorsDialog +from .PropsDialog import PropsDialog + +from ..core import ParseXML, Messages gobject.threads_init() @@ -74,8 +69,6 @@ class ActionHandler: #initialize self.init_file_paths = file_paths Actions.APPLICATION_INITIALIZE() - #enter the mainloop - gtk.main() def _handle_key_press(self, widget, event): """ @@ -143,6 +136,7 @@ class ActionHandler: Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB, Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY, Actions.FLOW_GRAPH_OPEN_QSS_THEME, + Actions.SELECT_ALL, ): action.set_sensitive(True) if hasattr(action, 'load_from_preferences'): @@ -162,6 +156,8 @@ class ActionHandler: pass #do nothing, update routines below elif action == Actions.NOTHING_SELECT: self.get_flow_graph().unselect() + elif action == Actions.SELECT_ALL: + self.get_flow_graph().select_all() ################################################## # Enable/Disable ################################################## @@ -230,7 +226,7 @@ class ActionHandler: if y < y_min: y_min = y - for connection in block.get_connections(): + for connection in block.connections: # Get id of connected blocks source_id = connection.get_source().get_parent().get_id() @@ -373,7 +369,8 @@ class ActionHandler: # Window stuff ################################################## elif action == Actions.ABOUT_WINDOW_DISPLAY: - Dialogs.AboutDialog(self.get_flow_graph().get_parent()) + platform = self.get_flow_graph().get_parent() + Dialogs.AboutDialog(platform.config) elif action == Actions.HELP_WINDOW_DISPLAY: Dialogs.HelpDialog() elif action == Actions.TYPES_WINDOW_DISPLAY: @@ -489,12 +486,13 @@ class ActionHandler: self.main_window.menu_bar.refresh_submenus() elif action == Actions.FLOW_GRAPH_OPEN_QSS_THEME: - file_paths = OpenQSSFileDialog(GR_PREFIX + '/share/gnuradio/themes/').run() + file_paths = OpenQSSFileDialog(self.platform.config.install_prefix + + '/share/gnuradio/themes/').run() if file_paths: try: - from gnuradio import gr - gr.prefs().set_string("qtgui", "qss", file_paths[0]) - gr.prefs().save() + prefs = self.platform.config.prefs + prefs.set_string("qtgui", "qss", file_paths[0]) + prefs.save() except Exception as e: Messages.send("Failed to save QSS preference: " + str(e)) elif action == Actions.FLOW_GRAPH_CLOSE: @@ -521,10 +519,10 @@ class ActionHandler: self.main_window.tool_bar.refresh_submenus() self.main_window.menu_bar.refresh_submenus() elif action == Actions.FLOW_GRAPH_SCREEN_CAPTURE: - file_path = SaveImageFileDialog(self.get_page().get_file_path()).run() + file_path, background_transparent = SaveScreenShotDialog(self.get_page().get_file_path()).run() if file_path is not None: - pixbuf = self.get_flow_graph().get_drawing_area().get_pixbuf() - pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:]) + pixbuf = self.get_flow_graph().get_drawing_area().get_screenshot(background_transparent) + pixbuf.save(file_path, Constants.IMAGE_FILE_EXTENSION[1:]) ################################################## # Gen/Exec/Stop ################################################## @@ -542,12 +540,13 @@ class ActionHandler: elif action == Actions.FLOW_GRAPH_EXEC: if not self.get_page().get_proc(): Actions.FLOW_GRAPH_GEN() - if Preferences.xterm_missing() != XTERM_EXECUTABLE: - if not os.path.exists(XTERM_EXECUTABLE): - Dialogs.MissingXTermDialog(XTERM_EXECUTABLE) - Preferences.xterm_missing(XTERM_EXECUTABLE) + xterm = self.platform.config.xterm_executable + if Preferences.xterm_missing() != xterm: + if not os.path.exists(xterm): + Dialogs.MissingXTermDialog(xterm) + Preferences.xterm_missing(xterm) if self.get_page().get_saved() and self.get_page().get_file_path(): - ExecFlowGraphThread(self) + Executor.ExecFlowGraphThread(self) elif action == Actions.FLOW_GRAPH_KILL: if self.get_page().get_proc(): try: @@ -560,7 +559,8 @@ class ActionHandler: self.platform.load_blocks() self.main_window.btwin.clear() self.platform.load_block_tree(self.main_window.btwin) - Actions.XML_PARSER_ERRORS_DISPLAY.set_sensitive(bool(ParseXML.xml_failures)) + Actions.XML_PARSER_ERRORS_DISPLAY.set_sensitive(bool( + ParseXML.xml_failures)) Messages.send_xml_errors_if_any(ParseXML.xml_failures) # Force a redraw of the graph, by getting the current state and re-importing it self.main_window.update_pages() @@ -635,7 +635,7 @@ class ActionHandler: self.main_window.update() try: #set the size of the flow graph area (if changed) new_size = (self.get_flow_graph().get_option('window_size') or - DEFAULT_CANVAS_SIZE) + self.platform.config.default_canvas_size) if self.get_flow_graph().get_size() != tuple(new_size): self.get_flow_graph().set_size(*new_size) except: pass @@ -653,48 +653,3 @@ class ActionHandler: Actions.FLOW_GRAPH_GEN.set_sensitive(sensitive) Actions.FLOW_GRAPH_EXEC.set_sensitive(sensitive) Actions.FLOW_GRAPH_KILL.set_sensitive(self.get_page().get_proc() is not None) - -class ExecFlowGraphThread(Thread): - """Execute the flow graph as a new process and wait on it to finish.""" - - def __init__ (self, action_handler): - """ - ExecFlowGraphThread constructor. - - Args: - action_handler: an instance of an ActionHandler - """ - Thread.__init__(self) - self.update_exec_stop = action_handler.update_exec_stop - self.flow_graph = action_handler.get_flow_graph() - #store page and dont use main window calls in run - self.page = action_handler.get_page() - #get the popen - try: - self.p = self.page.get_generator().get_popen() - self.page.set_proc(self.p) - #update - self.update_exec_stop() - self.start() - except Exception, e: - Messages.send_verbose_exec(str(e)) - Messages.send_end_exec() - - def run(self): - """ - Wait on the executing process by reading from its stdout. - Use gobject.idle_add when calling functions that modify gtk objects. - """ - #handle completion - r = "\n" - while r: - gobject.idle_add(Messages.send_verbose_exec, r) - r = os.read(self.p.stdout.fileno(), 1024) - self.p.poll() - gobject.idle_add(self.done) - - def done(self): - """Perform end of execution tasks.""" - Messages.send_end_exec(self.p.returncode) - self.page.set_proc(None) - self.update_exec_stop() diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index 9b32b3e601..354e536a82 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -226,6 +226,12 @@ FLOW_GRAPH_REDO = Action( keypresses=(gtk.keysyms.y, gtk.gdk.CONTROL_MASK), ) NOTHING_SELECT = Action() +SELECT_ALL = Action( + label='Select _All', + tooltip='Select all blocks and connections in the flow graph', + stock_id=gtk.STOCK_SELECT_ALL, + keypresses=(gtk.keysyms.a, gtk.gdk.CONTROL_MASK), +) ELEMENT_SELECT = Action() ELEMENT_CREATE = Action() ELEMENT_DELETE = Action( @@ -386,7 +392,7 @@ FLOW_GRAPH_KILL = Action( keypresses=(gtk.keysyms.F7, NO_MODS_MASK), ) FLOW_GRAPH_SCREEN_CAPTURE = Action( - label='Sc_reen Capture', + label='Screen Ca_pture', tooltip='Create a screen capture of the flow graph', stock_id=gtk.STOCK_PRINT, keypresses=(gtk.keysyms.Print, NO_MODS_MASK), diff --git a/grc/gui/Bars.py b/grc/gui/Bars.py index 19f041f508..259aa6ed8b 100644 --- a/grc/gui/Bars.py +++ b/grc/gui/Bars.py @@ -82,6 +82,7 @@ MENU_BAR_LIST = ( Actions.BLOCK_COPY, Actions.BLOCK_PASTE, Actions.ELEMENT_DELETE, + Actions.SELECT_ALL, None, Actions.BLOCK_ROTATE_CCW, Actions.BLOCK_ROTATE_CW, diff --git a/grc/gui/Block.py b/grc/gui/Block.py index 67b80695fa..95135310b8 100644 --- a/grc/gui/Block.py +++ b/grc/gui/Block.py @@ -17,22 +17,24 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -from Element import Element -import Utils -import Colors -from .. base import odict -from .. python.Param import num_to_str -from Constants import BORDER_PROXIMITY_SENSITIVITY -from Constants import ( - BLOCK_LABEL_PADDING, PORT_SPACING, PORT_SEPARATION, LABEL_SEPARATION, - PORT_BORDER_SEPARATION, POSSIBLE_ROTATIONS, BLOCK_FONT, PARAM_FONT -) -import Actions import pygtk pygtk.require('2.0') import gtk import pango +from . import Actions, Colors, Utils + +from .Constants import ( + BLOCK_LABEL_PADDING, PORT_SPACING, PORT_SEPARATION, LABEL_SEPARATION, + PORT_BORDER_SEPARATION, POSSIBLE_ROTATIONS, BLOCK_FONT, PARAM_FONT, + BORDER_PROXIMITY_SENSITIVITY +) +from . Element import Element +from ..core.Param import num_to_str +from ..core.utils import odict +from ..core.utils.complexity import calculate_flowgraph_complexity +from ..core.Block import Block as _Block + BLOCK_MARKUP_TMPL="""\ #set $foreground = $block.is_valid() and 'black' or 'red' <span foreground="$foreground" font_desc="$font"><b>$encode($block.get_name())</b></span>""" @@ -52,15 +54,16 @@ COMMENT_COMPLEXITY_MARKUP_TMPL="""\ """ - -class Block(Element): +class Block(Element, _Block): """The graphical signal block.""" - def __init__(self): + def __init__(self, flow_graph, n): """ Block contructor. Add graphics related params to the block. """ + _Block.__init__(self, flow_graph, n) + self.W = 0 self.H = 0 #add the position param @@ -135,10 +138,10 @@ class Block(Element): delta_coor: requested delta coordinate (dX, dY) to move Returns: - The delta coordinate possible to move while keeping the block on the canvas + The delta coordinate possible to move while keeping the block on the canvas or the input (dX, dY) on failure """ - dX, dY = delta_coor + dX, dY = delta_coor try: fgW, fgH = self.get_parent().get_size() @@ -147,7 +150,7 @@ class Block(Element): sW, sH = self.W, self.H else: sW, sH = self.H, self.W - + if x + dX < 0: dX = -x elif dX + x + sW >= fgW: @@ -159,7 +162,7 @@ class Block(Element): except: pass - return ( dX, dY ) + return ( dX, dY ) def get_rotation(self): """ @@ -193,7 +196,7 @@ class Block(Element): def create_labels(self): """Create the labels for the signal block.""" Element.create_labels(self) - self._bg_color = self.is_dummy_block() and Colors.MISSING_BLOCK_BACKGROUND_COLOR or \ + self._bg_color = self.is_dummy_block and Colors.MISSING_BLOCK_BACKGROUND_COLOR or \ self.get_bypassed() and Colors.BLOCK_BYPASSED_COLOR or \ self.get_enabled() and Colors.BLOCK_ENABLED_COLOR or Colors.BLOCK_DISABLED_COLOR @@ -204,7 +207,7 @@ class Block(Element): layout.set_markup(Utils.parse_template(BLOCK_MARKUP_TMPL, block=self, font=BLOCK_FONT)) self.label_width, self.label_height = layout.get_pixel_size() #display the params - if self.is_dummy_block(): + if self.is_dummy_block: markups = [ '<span foreground="black" font_desc="{font}"><b>key: </b>{key}</span>'.format(font=PARAM_FONT, key=self._key) ] @@ -271,7 +274,8 @@ class Block(Element): # Show the flowgraph complexity on the top block if enabled if Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY.get_active() and self.get_key() == "options": - complexity = "Complexity: {}bal".format(num_to_str(self.get_parent().get_complexity())) + complexity = calculate_flowgraph_complexity(self.get_parent()) + complexity = "Complexity: {}bal".format(num_to_str(complexity)) layout = gtk.DrawingArea().create_pango_layout('') layout.set_markup(Utils.parse_template(COMMENT_COMPLEXITY_MARKUP_TMPL, @@ -311,7 +315,7 @@ class Block(Element): Element.draw( self, gc, window, bg_color=self._bg_color, border_color=self.is_highlighted() and Colors.HIGHLIGHT_COLOR or - self.is_dummy_block() and Colors.MISSING_BLOCK_BORDER_COLOR or Colors.BORDER_COLOR, + self.is_dummy_block and Colors.MISSING_BLOCK_BORDER_COLOR or Colors.BORDER_COLOR, ) #draw label image if self.is_horizontal(): diff --git a/grc/gui/BlockTreeWindow.py b/grc/gui/BlockTreeWindow.py index 6b3ebf7807..4279e8c61d 100644 --- a/grc/gui/BlockTreeWindow.py +++ b/grc/gui/BlockTreeWindow.py @@ -30,12 +30,27 @@ KEY_INDEX = 1 DOC_INDEX = 2 DOC_MARKUP_TMPL = """\ -#if $doc -#if len($doc) > 1000 -#set $doc = $doc[:1000] + '...' +#set $docs = [] +#if $doc.get('') + #set $docs += $doc.pop('').splitlines() + [''] #end if -$encode($doc)#slurp -#else +#for b, d in $doc.iteritems() + #set $docs += ['--- {0} ---'.format(b)] + d.splitlines() + [''] +#end for +#set $len_out = 0 +#for $n, $line in $enumerate($docs[:-1]) +#if $n + +#end if +$encode($line)#slurp +#set $len_out += $len($line) +#if $n > 10 or $len_out > 500 + +...#slurp +#break +#end if +#end for +#if $len_out == 0 undocumented#slurp #end if""" @@ -129,8 +144,10 @@ class BlockTreeWindow(gtk.VBox): category: the category list or path string block: the block object or None """ - if treestore is None: treestore = self.treestore - if categories is None: categories = self._categories + if treestore is None: + treestore = self.treestore + if categories is None: + categories = self._categories if isinstance(category, (str, unicode)): category = category.split('/') category = tuple(filter(lambda x: x, category)) # tuple is hashable @@ -138,17 +155,18 @@ class BlockTreeWindow(gtk.VBox): for i, cat_name in enumerate(category): sub_category = category[:i+1] if sub_category not in categories: - iter = treestore.insert_before(categories[sub_category[:-1]], None) - treestore.set_value(iter, NAME_INDEX, '[ %s ]'%cat_name) - treestore.set_value(iter, KEY_INDEX, '') - treestore.set_value(iter, DOC_INDEX, Utils.parse_template(CAT_MARKUP_TMPL, cat=cat_name)) - categories[sub_category] = iter + iter_ = treestore.insert_before(categories[sub_category[:-1]], None) + treestore.set_value(iter_, NAME_INDEX, cat_name) + treestore.set_value(iter_, KEY_INDEX, '') + treestore.set_value(iter_, DOC_INDEX, Utils.parse_template(CAT_MARKUP_TMPL, cat=cat_name)) + categories[sub_category] = iter_ # add block - if block is None: return - iter = treestore.insert_before(categories[category], None) - treestore.set_value(iter, NAME_INDEX, block.get_name()) - treestore.set_value(iter, KEY_INDEX, block.get_key()) - treestore.set_value(iter, DOC_INDEX, Utils.parse_template(DOC_MARKUP_TMPL, doc=block.get_doc())) + if block is None: + return + iter_ = treestore.insert_before(categories[category], None) + treestore.set_value(iter_, NAME_INDEX, block.get_name()) + treestore.set_value(iter_, KEY_INDEX, block.get_key()) + treestore.set_value(iter_, DOC_INDEX, Utils.parse_template(DOC_MARKUP_TMPL, doc=block.get_doc())) def update_docs(self): """Update the documentation column of every block""" @@ -157,7 +175,7 @@ class BlockTreeWindow(gtk.VBox): if model.iter_has_child(iter_): return # category node, no doc string key = model.get_value(iter_, KEY_INDEX) - block = self.platform.get_block(key) + block = self.platform.blocks[key] doc = Utils.parse_template(DOC_MARKUP_TMPL, doc=block.get_doc()) model.set_value(iter_, DOC_INDEX, doc) @@ -210,8 +228,8 @@ class BlockTreeWindow(gtk.VBox): self.treeview.set_model(self.treestore) self.treeview.collapse_all() else: - blocks = self.get_flow_graph().get_parent().get_blocks() - matching_blocks = filter(lambda b: key in b.get_key().lower() or key in b.get_name().lower(), blocks) + matching_blocks = filter(lambda b: key in b.get_key().lower() or key in b.get_name().lower(), + self.platform.blocks.values()) self.treestore_search.clear() self._categories_search = {tuple(): None} diff --git a/grc/gui/CMakeLists.txt b/grc/gui/CMakeLists.txt index 99140df7c4..aa9592b351 100644 --- a/grc/gui/CMakeLists.txt +++ b/grc/gui/CMakeLists.txt @@ -17,34 +17,10 @@ # the Free Software Foundation, Inc., 51 Franklin Street, # Boston, MA 02110-1301, USA. -######################################################################## -GR_PYTHON_INSTALL(FILES - external_editor.py - Block.py - Colors.py - Constants.py - Connection.py - Element.py - FlowGraph.py - Param.py - Platform.py - Port.py - Utils.py - ActionHandler.py - Actions.py - Bars.py - BlockTreeWindow.py - Dialogs.py - DrawingArea.py - FileDialogs.py - MainWindow.py - Messages.py - NotebookPage.py - ParserErrorsDialog.py - PropsDialog.py - Preferences.py - StateCache.py - __init__.py +file(GLOB py_files "*.py") + +GR_PYTHON_INSTALL( + FILES ${py_files} DESTINATION ${GR_PYTHON_DIR}/gnuradio/grc/gui COMPONENT "grc" ) diff --git a/grc/gui/Colors.py b/grc/gui/Colors.py index 52c95e8edf..050b363cdd 100644 --- a/grc/gui/Colors.py +++ b/grc/gui/Colors.py @@ -33,8 +33,9 @@ try: PARAM_ENTRY_TEXT_COLOR = get_color('black') ENTRYENUM_CUSTOM_COLOR = get_color('#EEEEEE') #flow graph color constants - FLOWGRAPH_BACKGROUND_COLOR = get_color('#FFF9FF') + FLOWGRAPH_BACKGROUND_COLOR = get_color('#FFFFFF') COMMENT_BACKGROUND_COLOR = get_color('#F3F3F3') + FLOWGRAPH_EDGE_COLOR = COMMENT_BACKGROUND_COLOR #block color constants BLOCK_ENABLED_COLOR = get_color('#F1ECFF') BLOCK_DISABLED_COLOR = get_color('#CCCCCC') diff --git a/grc/gui/Config.py b/grc/gui/Config.py new file mode 100644 index 0000000000..9b0c5d4afe --- /dev/null +++ b/grc/gui/Config.py @@ -0,0 +1,74 @@ +""" +Copyright 2016 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 os +from ..core.Config import Config as _Config +from . import Constants + + +class Config(_Config): + + name = 'GNU Radio Companion' + + gui_prefs_file = os.environ.get( + 'GRC_PREFS_PATH', os.path.expanduser('~/.gnuradio/grc.conf')) + + def __init__(self, install_prefix, *args, **kwargs): + _Config.__init__(self, *args, **kwargs) + self.install_prefix = install_prefix + Constants.update_font_size(self.font_size) + + @property + def editor(self): + return self.prefs.get_string('grc', 'editor', '') + + @editor.setter + def editor(self, value): + self.prefs.get_string('grc', 'editor', value) + self.prefs.save() + + @property + def xterm_executable(self): + return self.prefs.get_string('grc', 'xterm_executable', 'xterm') + + @property + def default_canvas_size(self): + try: # ugly, but matches current code style + raw = self.prefs.get_string('grc', 'canvas_default_size', '1280, 1024') + value = tuple(int(x.strip('() ')) for x in raw.split(',')) + if len(value) != 2 or not all(300 < x < 4096 for x in value): + raise Exception() + return value + except: + print >> sys.stderr, "Error: invalid 'canvas_default_size' setting." + return Constants.DEFAULT_CANVAS_SIZE_DEFAULT + + @property + def font_size(self): + try: # ugly, but matches current code style + font_size = self.prefs.get_long('grc', 'canvas_font_size', + Constants.DEFAULT_FONT_SIZE) + if font_size <= 0: + raise Exception() + except: + font_size = Constants.DEFAULT_FONT_SIZE + print >> sys.stderr, "Error: invalid 'canvas_font_size' setting." + + return font_size diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py index badf8e8a82..50361c19d0 100644 --- a/grc/gui/Connection.py +++ b/grc/gui/Connection.py @@ -17,15 +17,18 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -import Utils -from Element import Element +import gtk + import Colors +import Utils from Constants import CONNECTOR_ARROW_BASE, CONNECTOR_ARROW_HEIGHT -import gtk +from Element import Element + +from ..core.Constants import GR_MESSAGE_DOMAIN +from ..core.Connection import Connection as _Connection -from .. base.Constants import GR_MESSAGE_DOMAIN -class Connection(Element): +class Connection(Element, _Connection): """ A graphical connection for ports. The connection has 2 parts, the arrow and the wire. @@ -35,8 +38,9 @@ class Connection(Element): The arrow coloring exposes the enabled and valid states. """ - def __init__(self): + def __init__(self, **kwargs): Element.__init__(self) + _Connection.__init__(self, **kwargs) # can't use Colors.CONNECTION_ENABLED_COLOR here, might not be defined (grcc) self._bg_color = self._arrow_color = self._color = None @@ -88,7 +92,7 @@ class Connection(Element): if not source_domain == sink_domain == GR_MESSAGE_DOMAIN \ else gtk.gdk.LINE_ON_OFF_DASH get_domain_color = lambda d: Colors.get_color(( - self.get_parent().get_parent().get_domain(d) or {} + self.get_parent().get_parent().domains.get(d, {}) ).get('color') or Colors.DEFAULT_DOMAIN_COLOR_CODE) self._color = get_domain_color(source_domain) self._bg_color = get_domain_color(sink_domain) diff --git a/grc/gui/Constants.py b/grc/gui/Constants.py index 741c6fda95..e267c6ca02 100644 --- a/grc/gui/Constants.py +++ b/grc/gui/Constants.py @@ -17,18 +17,10 @@ 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 pygtk -pygtk.require('2.0') import gtk -from gnuradio import gr +from ..core.Constants import * -prefs = gr.prefs() -GR_PREFIX = gr.prefix() -EDITOR = prefs.get_string('grc', 'editor', '') # default path for the open/save dialogs DEFAULT_FILE_PATH = os.getcwd() @@ -50,28 +42,12 @@ MIN_DIALOG_HEIGHT = 500 DEFAULT_BLOCKS_WINDOW_WIDTH = 100 DEFAULT_REPORTS_WINDOW_WIDTH = 100 -try: # ugly, but matches current code style - raw = prefs.get_string('grc', 'canvas_default_size', '1280, 1024') - DEFAULT_CANVAS_SIZE = tuple(int(x.strip('() ')) for x in raw.split(',')) - if len(DEFAULT_CANVAS_SIZE) != 2 or not all(300 < x < 4096 for x in DEFAULT_CANVAS_SIZE): - raise Exception() -except: - DEFAULT_CANVAS_SIZE = 1280, 1024 - print >> sys.stderr, "Error: invalid 'canvas_default_size' setting." - -# flow-graph canvas fonts -try: # ugly, but matches current code style - FONT_SIZE = prefs.get_long('grc', 'canvas_font_size', 8) - if FONT_SIZE <= 0: - raise Exception() -except: - FONT_SIZE = 8 - print >> sys.stderr, "Error: invalid 'canvas_font_size' setting." -FONT_FAMILY = "Sans" -BLOCK_FONT = "%s %f" % (FONT_FAMILY, FONT_SIZE) -PORT_FONT = BLOCK_FONT -PARAM_FONT = "%s %f" % (FONT_FAMILY, FONT_SIZE - 0.5) +DEFAULT_CANVAS_SIZE_DEFAULT = 1280, 1024 +FONT_SIZE = DEFAULT_FONT_SIZE = 8 +FONT_FAMILY = "Sans" +BLOCK_FONT = PORT_FONT = "Sans 8" +PARAM_FONT = "Sans 7.5" # size of the state saving cache in the flow graph (undo/redo functionality) STATE_CACHE_SIZE = 42 @@ -90,8 +66,7 @@ CANVAS_GRID_SIZE = 8 # port constraint dimensions PORT_BORDER_SEPARATION = 8 PORT_SPACING = 2 * PORT_BORDER_SEPARATION -PORT_SEPARATION = PORT_SPACING + 2 * PORT_LABEL_PADDING + int(1.5 * FONT_SIZE) -PORT_SEPARATION += -PORT_SEPARATION % (2 * CANVAS_GRID_SIZE) # even multiple +PORT_SEPARATION = 32 PORT_MIN_WIDTH = 20 PORT_LABEL_HIDDEN_WIDTH = 10 @@ -120,3 +95,17 @@ SCROLL_DISTANCE = 15 # How close the mouse click can be to a line and register a connection select. LINE_SELECT_SENSITIVITY = 5 + + +def update_font_size(font_size): + global PORT_SEPARATION, BLOCK_FONT, PORT_FONT, PARAM_FONT, FONT_SIZE + + FONT_SIZE = font_size + BLOCK_FONT = "%s %f" % (FONT_FAMILY, font_size) + PORT_FONT = BLOCK_FONT + PARAM_FONT = "%s %f" % (FONT_FAMILY, font_size - 0.5) + + PORT_SEPARATION = PORT_SPACING + 2 * PORT_LABEL_PADDING + int(1.5 * font_size) + PORT_SEPARATION += -PORT_SEPARATION % (2 * CANVAS_GRID_SIZE) # even multiple + +update_font_size(DEFAULT_FONT_SIZE) diff --git a/grc/gui/Dialogs.py b/grc/gui/Dialogs.py index f2941250a8..6cfdd50a34 100644 --- a/grc/gui/Dialogs.py +++ b/grc/gui/Dialogs.py @@ -17,15 +17,13 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -import pygtk -pygtk.require('2.0') import gtk import sys from distutils.spawn import find_executable - -from . import Utils, Actions, Constants, Messages +from . import Utils, Actions +from ..core import Messages class SimpleTextDisplay(gtk.TextView): @@ -177,14 +175,14 @@ def ErrorsDialog(flowgraph): MessageDialogHelper( class AboutDialog(gtk.AboutDialog): """A cute little about dialog.""" - def __init__(self, platform): + def __init__(self, config): """AboutDialog constructor.""" gtk.AboutDialog.__init__(self) - self.set_name(platform.get_name()) - self.set_version(platform.get_version()) - self.set_license(platform.get_license()) - self.set_copyright(platform.get_license().splitlines()[0]) - self.set_website(platform.get_website()) + self.set_name(config.name) + self.set_version(config.version) + self.set_license(config.license) + self.set_copyright(config.license.splitlines()[0]) + self.set_website(config.website) self.run() self.destroy() @@ -240,7 +238,7 @@ def MissingXTermDialog(xterm): ) -def ChooseEditorDialog(): +def ChooseEditorDialog(config): # Give the option to either choose an editor or use the default # Always return true/false so the caller knows it was successful buttons = ( @@ -266,10 +264,7 @@ def ChooseEditorDialog(): file_dialog.set_current_folder('/usr/bin') try: if file_dialog.run() == gtk.RESPONSE_OK: - file_path = file_dialog.get_filename() - Constants.prefs.set_string('grc', 'editor', file_path) - Constants.prefs.save() - Constants.EDITOR = file_path + config.editor = file_path = file_dialog.get_filename() file_dialog.destroy() return file_path finally: @@ -287,16 +282,12 @@ def ChooseEditorDialog(): if process is None: raise ValueError("Can't find default editor executable") # Save - Constants.prefs.set_string('grc', 'editor', process) - Constants.prefs.save() - Constants.EDITOR = process + config.editor = process return process except Exception: Messages.send('>>> Unable to load the default editor. Please choose an editor.\n') # Just reset of the constant and force the user to select an editor the next time - Constants.prefs.set_string('grc', 'editor', '') - Constants.prefs.save() - Constants.EDITOR = "" + config.editor = '' return Messages.send('>>> No editor selected.\n') diff --git a/grc/gui/DrawingArea.py b/grc/gui/DrawingArea.py index 4412129809..6a1df27a8c 100644 --- a/grc/gui/DrawingArea.py +++ b/grc/gui/DrawingArea.py @@ -20,7 +20,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA import pygtk pygtk.require('2.0') import gtk + from Constants import MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT, DND_TARGETS +import Colors + class DrawingArea(gtk.DrawingArea): """ @@ -68,13 +71,21 @@ class DrawingArea(gtk.DrawingArea): self.set_flags(gtk.CAN_FOCUS) # self.set_can_focus(True) self.connect('focus-out-event', self._handle_focus_lost_event) - def new_pixmap(self, width, height): return gtk.gdk.Pixmap(self.window, width, height, -1) - def get_pixbuf(self): - width, height = self._pixmap.get_size() - pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, width, height) - pixbuf.get_from_drawable(self._pixmap, self._pixmap.get_colormap(), 0, 0, 0, 0, width, height) + def new_pixmap(self, width, height): + return gtk.gdk.Pixmap(self.window, width, height, -1) + + def get_screenshot(self, transparent_bg=False): + pixmap = self._pixmap + W, H = pixmap.get_size() + pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, W, H) + pixbuf.fill(0xFF + Colors.FLOWGRAPH_BACKGROUND_COLOR.pixel << 8) + pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, W-1, H-1) + if transparent_bg: + bgc = Colors.FLOWGRAPH_BACKGROUND_COLOR + pixbuf = pixbuf.add_alpha(True, bgc.red, bgc.green, bgc.blue) return pixbuf + ########################################################################## ## Handlers ########################################################################## @@ -149,6 +160,12 @@ class DrawingArea(gtk.DrawingArea): gc = self.window.new_gc() self._flow_graph.draw(gc, self._pixmap) self.window.draw_drawable(gc, self._pixmap, 0, 0, 0, 0, -1, -1) + # draw a light grey line on the bottom and right end of the canvas. + # this is useful when the theme uses the same panel bg color as the canvas + W, H = self._pixmap.get_size() + gc.set_foreground(Colors.FLOWGRAPH_EDGE_COLOR) + self.window.draw_line(gc, 0, H-1, W, H-1) + self.window.draw_line(gc, W-1, 0, W-1, H) def _handle_focus_lost_event(self, widget, event): # don't clear selection while context menu is active diff --git a/grc/gui/Element.py b/grc/gui/Element.py index 18fb321929..9385424772 100644 --- a/grc/gui/Element.py +++ b/grc/gui/Element.py @@ -132,14 +132,14 @@ class Element(object): """ self.coor = coor - def get_parent(self): - """ - Get the parent of this element. - - Returns: - the parent - """ - return self.parent + # def get_parent(self): + # """ + # Get the parent of this element. + # + # Returns: + # the parent + # """ + # return self.parent def set_highlighted(self, highlighted): """ diff --git a/grc/gui/Executor.py b/grc/gui/Executor.py new file mode 100644 index 0000000000..f75f514cdb --- /dev/null +++ b/grc/gui/Executor.py @@ -0,0 +1,123 @@ +# Copyright 2016 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 gobject +import os +import threading +import shlex +import subprocess +import sys +import re +from distutils.spawn import find_executable + +from ..core import Messages + + +class ExecFlowGraphThread(threading.Thread): + """Execute the flow graph as a new process and wait on it to finish.""" + + def __init__(self, action_handler): + """ + ExecFlowGraphThread constructor. + + Args: + action_handler: an instance of an ActionHandler + """ + threading.Thread.__init__(self) + self.update_exec_stop = action_handler.update_exec_stop + self.flow_graph = action_handler.get_flow_graph() + self.xterm_executable = action_handler.platform.config.xterm_executable + #store page and dont use main window calls in run + self.page = action_handler.get_page() + #get the popen + try: + self.p = self._popen() + self.page.set_proc(self.p) + #update + self.update_exec_stop() + self.start() + except Exception, e: + Messages.send_verbose_exec(str(e)) + Messages.send_end_exec() + + def _popen(self): + """ + Execute this python flow graph. + """ + run_command = self.flow_graph.get_option('run_command') + generator = self.page.get_generator() + + try: + run_command = run_command.format( + python=shlex_quote(sys.executable), + filename=shlex_quote(generator.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(self.xterm_executable) + if generator.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 run(self): + """ + Wait on the executing process by reading from its stdout. + Use gobject.idle_add when calling functions that modify gtk objects. + """ + #handle completion + r = "\n" + while r: + gobject.idle_add(Messages.send_verbose_exec, r) + r = os.read(self.p.stdout.fileno(), 1024) + self.p.poll() + gobject.idle_add(self.done) + + def done(self): + """Perform end of execution tasks.""" + Messages.send_end_exec(self.p.returncode) + self.page.set_proc(None) + self.update_exec_stop() + + +########################################################### +# 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/gui/FileDialogs.py b/grc/gui/FileDialogs.py index 730ac6fba0..4b5770ad21 100644 --- a/grc/gui/FileDialogs.py +++ b/grc/gui/FileDialogs.py @@ -210,3 +210,18 @@ class SaveFlowGraphFileDialog(FileDialog): type = SAVE_FLOW_GRAPH class OpenQSSFileDialog(FileDialog): type = OPEN_QSS_THEME class SaveReportsFileDialog(FileDialog): type = SAVE_REPORTS class SaveImageFileDialog(FileDialog): type = SAVE_IMAGE + + +class SaveScreenShotDialog(SaveImageFileDialog): + + def __init__(self, current_file_path=''): + SaveImageFileDialog.__init__(self, current_file_path) + self._button = button = gtk.CheckButton('_Background transparent') + self._button.set_active(Preferences.screen_shot_background_transparent()) + self.set_extra_widget(button) + + def run(self): + filename = SaveImageFileDialog.run(self) + bg_transparent = self._button.get_active() + Preferences.screen_shot_background_transparent(bg_transparent) + return filename, bg_transparent diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index 2053e86454..357f87c894 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -17,39 +17,43 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -import random import functools -from itertools import chain -from operator import methodcaller +import random from distutils.spawn import find_executable +from itertools import chain, count +from operator import methodcaller import gobject -from . import Actions, Colors, Constants, Utils, Messages, Bars, Dialogs -from . Element import Element -from . Constants import SCROLL_PROXIMITY_SENSITIVITY, SCROLL_DISTANCE -from . external_editor import ExternalEditor +from . import Actions, Colors, Constants, Utils, Bars, Dialogs +from .Constants import SCROLL_PROXIMITY_SENSITIVITY, SCROLL_DISTANCE +from .Element import Element +from .external_editor import ExternalEditor +from ..core.FlowGraph import FlowGraph as _Flowgraph +from ..core import Messages -class FlowGraph(Element): + +class FlowGraph(Element, _Flowgraph): """ FlowGraph is the data structure to store graphical signal blocks, graphical inputs and outputs, and the connections between inputs and outputs. """ - def __init__(self): + def __init__(self, **kwargs): """ FlowGraph constructor. Create a list for signal blocks and connections. Connect mouse handlers. """ Element.__init__(self) + _Flowgraph.__init__(self, **kwargs) #when is the flow graph selected? (used by keyboard event handler) self.is_selected = lambda: bool(self.get_selected_elements()) #important vars dealing with mouse event tracking self.element_moved = False self.mouse_pressed = False - self.unselect() + self._selected_elements = [] self.press_coor = (0, 0) #selected ports self._old_selected_port = None @@ -62,14 +66,31 @@ class FlowGraph(Element): self._external_updaters = {} + def _get_unique_id(self, base_id=''): + """ + Get a unique id starting with the base id. + + Args: + base_id: the id starts with this and appends a count + + Returns: + a unique id + """ + for index in count(): + block_id = '{}_{}'.format(base_id, index) + if block_id not in (b.get_id() for b in self.blocks): + break + return block_id + def install_external_editor(self, param): target = (param.get_parent().get_id(), param.get_key()) if target in self._external_updaters: editor = self._external_updaters[target] else: - editor = (find_executable(Constants.EDITOR) or - Dialogs.ChooseEditorDialog()) + config = self.get_parent().config + editor = (find_executable(config.editor) or + Dialogs.ChooseEditorDialog(config)) if not editor: return updater = functools.partial( @@ -86,9 +107,7 @@ class FlowGraph(Element): # Problem launching the editor. Need to select a new editor. Messages.send('>>> Error opening an external editor. Please select a different editor.\n') # Reset the editor to force the user to select a new one. - Constants.prefs.set_string('grc', 'editor', '') - Constants.prefs.save() - Constants.EDITOR = "" + self.get_parent().config.editor = '' def handle_external_editor_change(self, new_value, target): try: @@ -131,7 +150,7 @@ class FlowGraph(Element): int(random.uniform(.25, .75)*v_adj.page_size + v_adj.get_value()), ) #get the new block - block = self.get_new_block(key) + block = self.new_block(key) block.set_coordinate(coor) block.set_rotation(0) block.get_param('id').set_value(id) @@ -160,7 +179,7 @@ class FlowGraph(Element): #get connections between selected blocks connections = filter( lambda c: c.get_source().get_parent() in blocks and c.get_sink().get_parent() in blocks, - self.get_connections(), + self.connections, ) clipboard = ( (x_min, y_min), @@ -190,7 +209,7 @@ class FlowGraph(Element): for block_n in blocks_n: block_key = block_n.find('key') if block_key == 'options': continue - block = self.get_new_block(block_key) + block = self.new_block(block_key) if not block: continue # unknown block was pasted (e.g. dummy block) selected.add(block) @@ -206,7 +225,7 @@ class FlowGraph(Element): if param_key == 'id': old_id2block[param_value] = block #if the block id is not unique, get a new block id - if param_value in [blk.get_id() for blk in self.get_blocks()]: + if param_value in (blk.get_id() for blk in self.blocks): param_value = self._get_unique_id(param_value) #set value to key block.get_param(param_key).set_value(param_value) @@ -350,7 +369,7 @@ class FlowGraph(Element): # draw comments first if Actions.TOGGLE_SHOW_BLOCK_COMMENTS.get_active(): - for block in self.iter_blocks(): + for block in self.blocks: if block.get_enabled(): block.draw_comment(gc, window) #draw multi select rectangle @@ -368,8 +387,8 @@ class FlowGraph(Element): window.draw_rectangle(gc, False, x, y, w, h) #draw blocks on top of connections hide_disabled_blocks = Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active() - blocks = sorted(self.iter_blocks(), key=methodcaller('get_enabled')) - for element in chain(self.iter_connections(), blocks): + blocks = sorted(self.blocks, key=methodcaller('get_enabled')) + for element in chain(self.connections, blocks): if hide_disabled_blocks and not element.get_enabled(): continue # skip hidden disabled blocks and connections element.draw(gc, window) @@ -432,6 +451,10 @@ class FlowGraph(Element): """ self._selected_elements = [] + def select_all(self): + """Select all blocks in the flow graph""" + self._selected_elements = list(self.get_elements()) + def what_is_selected(self, coor, coor_m=None): """ What is selected? @@ -456,13 +479,13 @@ class FlowGraph(Element): if not selected_element: continue # hidden disabled connections, blocks and their ports can not be selected if Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active() and ( - selected_element.is_block() and not selected_element.get_enabled() or - selected_element.is_connection() and not selected_element.get_enabled() or - selected_element.is_port() and not selected_element.get_parent().get_enabled() + selected_element.is_block and not selected_element.get_enabled() or + selected_element.is_connection and not selected_element.get_enabled() or + selected_element.is_port and not selected_element.get_parent().get_enabled() ): continue #update the selected port information - if selected_element.is_port(): + if selected_element.is_port: if not coor_m: selected_port = selected_element selected_element = selected_element.get_parent() selected.add(selected_element) @@ -486,7 +509,8 @@ class FlowGraph(Element): """ selected = set() for selected_element in self.get_selected_elements(): - if selected_element.is_connection(): selected.add(selected_element) + if selected_element.is_connection: + selected.add(selected_element) return list(selected) def get_selected_blocks(self): @@ -498,7 +522,8 @@ class FlowGraph(Element): """ selected = set() for selected_element in self.get_selected_elements(): - if selected_element.is_block(): selected.add(selected_element) + if selected_element.is_block: + selected.add(selected_element) return list(selected) def get_selected_block(self): @@ -673,7 +698,7 @@ class FlowGraph(Element): adj.set_value(adj_val-SCROLL_DISTANCE) adj.emit('changed') #remove the connection if selected in drag event - if len(self.get_selected_elements()) == 1 and self.get_selected_element().is_connection(): + if len(self.get_selected_elements()) == 1 and self.get_selected_element().is_connection: Actions.ELEMENT_DELETE() #move the selected elements and record the new coordinate if not self.get_ctrl_mask(): diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py index a340bcc817..6da240d85b 100644 --- a/grc/gui/MainWindow.py +++ b/grc/gui/MainWindow.py @@ -17,21 +17,19 @@ 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 \ - NEW_FLOGRAPH_TITLE, DEFAULT_REPORTS_WINDOW_WIDTH -import Actions -import pygtk -pygtk.require('2.0') -import gtk -import Bars -from BlockTreeWindow import BlockTreeWindow -from Dialogs import TextDisplay, MessageDialogHelper -from NotebookPage import NotebookPage -import Preferences -import Messages -import Utils import os +import gtk + +from . import Bars, Actions, Preferences, Utils +from .BlockTreeWindow import BlockTreeWindow +from .Constants import \ + NEW_FLOGRAPH_TITLE, DEFAULT_REPORTS_WINDOW_WIDTH +from .Dialogs import TextDisplay, MessageDialogHelper +from .NotebookPage import NotebookPage + +from ..core import Messages + MAIN_WINDOW_TITLE_TMPL = """\ #if not $saved *#slurp @@ -71,11 +69,13 @@ class MainWindow(gtk.Window): Setup the menu, toolbar, flowgraph editor notebook, block selection window... """ self._platform = platform - gen_opts = platform.get_block('options').get_param('generate_options') + + gen_opts = platform.blocks['options'].get_param('generate_options') generate_mode_default = gen_opts.get_value() generate_modes = [ (o.get_key(), o.get_name(), o.get_key() == generate_mode_default) for o in gen_opts.get_options()] + # load preferences Preferences.load(platform) #setup window @@ -283,7 +283,7 @@ class MainWindow(gtk.Window): new_flowgraph_title=NEW_FLOGRAPH_TITLE, read_only=self.get_page().get_read_only(), saved=self.get_page().get_saved(), - platform_name=self._platform.get_name(), + platform_name=self._platform.config.name, ) ) #set tab titles diff --git a/grc/gui/NotebookPage.py b/grc/gui/NotebookPage.py index 4c112154af..6614649c89 100644 --- a/grc/gui/NotebookPage.py +++ b/grc/gui/NotebookPage.py @@ -102,10 +102,8 @@ class NotebookPage(gtk.HBox): Returns: generator """ - return self.get_flow_graph().get_parent().get_generator()( - self.get_flow_graph(), - self.get_file_path(), - ) + platform = self.get_flow_graph().get_parent() + return platform.Generator(self.get_flow_graph(), self.get_file_path()) def _handle_button(self, button): """ diff --git a/grc/gui/Param.py b/grc/gui/Param.py index 6884d6530a..bf0a59b96b 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -23,8 +23,10 @@ import pygtk pygtk.require('2.0') import gtk -from . import Colors, Utils, Constants, Dialogs -from . Element import Element +from . import Colors, Utils, Constants +from .Element import Element + +from ..core.Param import Param as _Param class InputParam(gtk.HBox): @@ -317,7 +319,9 @@ class FileParam(EntryParam): if self.param.get_key() == 'qt_qss_theme': dirname = os.path.dirname(dirname) # trim filename if not os.path.exists(dirname): - dirname = os.path.join(Constants.GR_PREFIX, '/share/gnuradio/themes') + platform = self.param.get_parent().get_parent().get_parent() + dirname = os.path.join(platform.config.install_prefix, + '/share/gnuradio/themes') if not os.path.exists(dirname): dirname = os.getcwd() # fix bad paths @@ -378,11 +382,12 @@ Error: #end if""" -class Param(Element): +class Param(Element, _Param): """The graphical parameter.""" - def __init__(self): + def __init__(self, **kwargs): Element.__init__(self) + _Param.__init__(self, **kwargs) def get_input(self, *args, **kwargs): """ diff --git a/grc/gui/Platform.py b/grc/gui/Platform.py index eda28a0e94..500df1cce4 100644 --- a/grc/gui/Platform.py +++ b/grc/gui/Platform.py @@ -17,12 +17,55 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -from Element import Element +import os +import sys -class Platform(Element): - def __init__(self, prefs_file): +from ..core.Platform import Platform as _Platform + +from .Config import Config as _Config +from .Block import Block as _Block +from .Connection import Connection as _Connection +from .Element import Element +from .FlowGraph import FlowGraph as _FlowGraph +from .Param import Param as _Param +from .Port import Port as _Port + + +class Platform(Element, _Platform): + + def __init__(self, *args, **kwargs): Element.__init__(self) + _Platform.__init__(self, *args, **kwargs) + + # Ensure conf directories + gui_prefs_file = self.config.gui_prefs_file + if not os.path.exists(os.path.dirname(gui_prefs_file)): + os.mkdir(os.path.dirname(gui_prefs_file)) + + self._move_old_pref_file() + + def get_prefs_file(self): + return self.config.gui_prefs_file - self._prefs_file = prefs_file + def _move_old_pref_file(self): + gui_prefs_file = self.config.gui_prefs_file + old_gui_prefs_file = os.environ.get( + 'GRC_PREFS_PATH', os.path.expanduser('~/.grc')) + if gui_prefs_file == old_gui_prefs_file: + return # prefs file overridden with env var + if os.path.exists(old_gui_prefs_file) and not os.path.exists(gui_prefs_file): + try: + import shutil + shutil.move(old_gui_prefs_file, gui_prefs_file) + except Exception as e: + print >> sys.stderr, e - def get_prefs_file(self): return self._prefs_file + ############################################## + # Constructors + ############################################## + FlowGraph = _FlowGraph + Connection = _Connection + Block = _Block + Port = _Port + Param = _Param + Config = _Config diff --git a/grc/gui/Port.py b/grc/gui/Port.py index ae1a1d223f..6314b7ede8 100644 --- a/grc/gui/Port.py +++ b/grc/gui/Port.py @@ -17,32 +17,33 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -from Element import Element -from Constants import ( - PORT_SEPARATION, PORT_SPACING, CONNECTOR_EXTENSION_MINIMAL, - CONNECTOR_EXTENSION_INCREMENT, CANVAS_GRID_SIZE, - PORT_LABEL_PADDING, PORT_MIN_WIDTH, PORT_LABEL_HIDDEN_WIDTH, PORT_FONT -) -from .. base.Constants import DEFAULT_DOMAIN, GR_MESSAGE_DOMAIN -import Utils -import Actions -import Colors import pygtk pygtk.require('2.0') import gtk +from . import Actions, Colors, Utils +from .Constants import ( + PORT_SEPARATION, PORT_SPACING, CONNECTOR_EXTENSION_MINIMAL, + CONNECTOR_EXTENSION_INCREMENT, PORT_LABEL_PADDING, PORT_MIN_WIDTH, PORT_LABEL_HIDDEN_WIDTH, PORT_FONT +) +from .Element import Element +from ..core.Constants import DEFAULT_DOMAIN, GR_MESSAGE_DOMAIN + +from ..core.Port import Port as _Port + PORT_MARKUP_TMPL="""\ <span foreground="black" font_desc="$font">$encode($port.get_name())</span>""" -class Port(Element): +class Port(_Port, Element): """The graphical port.""" - def __init__(self): + def __init__(self, block, n, dir): """ Port contructor. Create list of connector coordinates. """ + _Port.__init__(self, block, n, dir) Element.__init__(self) self.W = self.H = self.w = self.h = 0 self._connector_coordinate = (0, 0) @@ -63,7 +64,7 @@ class Port(Element): rotation = self.get_rotation() #get all sibling ports ports = self.get_parent().get_sources_gui() \ - if self.is_source() else self.get_parent().get_sinks_gui() + if self.is_source else self.get_parent().get_sinks_gui() ports = filter(lambda p: not p.get_hide(), ports) #get the max width self.W = max([port.W for port in ports] + [PORT_MIN_WIDTH]) @@ -81,27 +82,27 @@ class Port(Element): index = length-index-1 port_separation = PORT_SEPARATION \ - if not self.get_parent().has_busses[self.is_source()] \ + if not self.get_parent().has_busses[self.is_source] \ else max([port.H for port in ports]) + PORT_SPACING offset = (self.get_parent().H - (length-1)*port_separation - self.H)/2 #create areas and connector coordinates - if (self.is_sink() and rotation == 0) or (self.is_source() and rotation == 180): + if (self.is_sink and rotation == 0) or (self.is_source and rotation == 180): x = -W y = port_separation*index+offset self.add_area((x, y), (W, self.H)) self._connector_coordinate = (x-1, y+self.H/2) - elif (self.is_source() and rotation == 0) or (self.is_sink() and rotation == 180): + elif (self.is_source and rotation == 0) or (self.is_sink and rotation == 180): x = self.get_parent().W y = port_separation*index+offset self.add_area((x, y), (W, self.H)) self._connector_coordinate = (x+1+W, y+self.H/2) - elif (self.is_source() and rotation == 90) or (self.is_sink() and rotation == 270): + elif (self.is_source and rotation == 90) or (self.is_sink and rotation == 270): y = -W x = port_separation*index+offset self.add_area((x, y), (self.H, W)) self._connector_coordinate = (x+self.H/2, y-1) - elif (self.is_sink() and rotation == 90) or (self.is_source() and rotation == 270): + elif (self.is_sink and rotation == 90) or (self.is_source and rotation == 270): y = self.get_parent().W x = port_separation*index+offset self.add_area((x, y), (self.H, W)) @@ -144,7 +145,7 @@ class Port(Element): Element.draw( self, gc, window, bg_color=self._bg_color, border_color=self.is_highlighted() and Colors.HIGHLIGHT_COLOR or - self.get_parent().is_dummy_block() and Colors.MISSING_BLOCK_BORDER_COLOR or + self.get_parent().is_dummy_block and Colors.MISSING_BLOCK_BORDER_COLOR or Colors.BORDER_COLOR, ) if not self._areas_list or self._label_hidden(): @@ -176,8 +177,8 @@ class Port(Element): Returns: the direction in degrees """ - if self.is_source(): return self.get_rotation() - elif self.is_sink(): return (self.get_rotation() + 180)%360 + if self.is_source: return self.get_rotation() + elif self.is_sink: return (self.get_rotation() + 180)%360 def get_connector_length(self): """ diff --git a/grc/gui/Preferences.py b/grc/gui/Preferences.py index 3ebee24345..1a194fd8c5 100644 --- a/grc/gui/Preferences.py +++ b/grc/gui/Preferences.py @@ -35,7 +35,7 @@ _config_parser = ConfigParser.SafeConfigParser() def file_extension(): - return '.'+_platform.get_key() + return '.grc' def load(platform): @@ -150,3 +150,7 @@ def blocks_window_position(pos=None): def xterm_missing(cmd=None): return entry('xterm_missing', cmd, default='INVALID_XTERM_SETTING') + + +def screen_shot_background_transparent(transparent=None): + return entry('screen_shot_background_transparent', transparent, default=False) diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py index bf7d31d391..7c66a77a54 100644 --- a/grc/gui/PropsDialog.py +++ b/grc/gui/PropsDialog.py @@ -97,7 +97,8 @@ class PropsDialog(gtk.Dialog): self._params_boxes.append((tab, label, vbox)) # Docs for the block - self._docs_text_display = SimpleTextDisplay() + self._docs_text_display = doc_view = SimpleTextDisplay() + doc_view.get_buffer().create_tag('b', weight=pango.WEIGHT_BOLD) self._docs_box = gtk.ScrolledWindow() self._docs_box.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self._docs_box.add_with_viewport(self._docs_text_display) @@ -200,10 +201,43 @@ class PropsDialog(gtk.Dialog): messages = '\n\n'.join(self._block.get_error_messages()) self._error_messages_text_display.set_text(messages) # update the docs box - self._docs_text_display.set_text(self._block.get_doc()) + self._update_docs_page() # update the generated code self._update_generated_code_page() + def _update_docs_page(self): + """Show documentation from XML and try to display best matching docstring""" + buffer = self._docs_text_display.get_buffer() + buffer.delete(buffer.get_start_iter(), buffer.get_end_iter()) + pos = buffer.get_end_iter() + + docstrings = self._block.get_doc() + if not docstrings: + return + + # show documentation string from block xml + from_xml = docstrings.pop('', '') + for line in from_xml.splitlines(): + if line.lstrip() == line and line.endswith(':'): + buffer.insert_with_tags_by_name(pos, line + '\n', 'b') + else: + buffer.insert(pos, line + '\n') + if from_xml: + buffer.insert(pos, '\n') + + # if given the current parameters an exact match can be made + block_constructor = self._block.get_make().rsplit('.', 2)[-1] + block_class = block_constructor.partition('(')[0].strip() + if block_class in docstrings: + docstrings = {block_class: docstrings[block_class]} + + # show docstring(s) extracted from python sources + for cls_name, docstring in docstrings.iteritems(): + buffer.insert_with_tags_by_name(pos, cls_name + '\n', 'b') + buffer.insert(pos, docstring + '\n\n') + pos.backward_chars(2) + buffer.delete(pos, buffer.get_end_iter()) + def _update_generated_code_page(self): if not self._code_text_display: return # user disabled code preview diff --git a/grc/main.py b/grc/main.py new file mode 100755 index 0000000000..ae7a0ce115 --- /dev/null +++ b/grc/main.py @@ -0,0 +1,55 @@ +# Copyright 2009-2016 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 optparse + +import gtk +from gnuradio import gr + +from .gui.Platform import Platform +from .gui.ActionHandler import ActionHandler + + +VERSION_AND_DISCLAIMER_TEMPLATE = """\ +GNU Radio Companion %s + +This program is part of GNU Radio +GRC comes with ABSOLUTELY NO WARRANTY. +This is free software, and you are welcome to redistribute it. +""" + + +def main(): + parser = optparse.OptionParser( + usage='usage: %prog [options] [saved flow graphs]', + version=VERSION_AND_DISCLAIMER_TEMPLATE % gr.version()) + options, args = parser.parse_args() + + try: + gtk.window_set_default_icon(gtk.IconTheme().load_icon('gnuradio-grc', 256, 0)) + except: + pass + + platform = Platform( + prefs_file=gr.prefs(), + version=gr.version(), + version_parts=(gr.major_version(), gr.api_version(), gr.minor_version()), + install_prefix=gr.prefix() + ) + ActionHandler(args, platform) + gtk.main() + diff --git a/grc/python/Block.py b/grc/python/Block.py deleted file mode 100644 index 782893fd8f..0000000000 --- a/grc/python/Block.py +++ /dev/null @@ -1,323 +0,0 @@ -""" -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 itertools -import collections - -from .. base.Constants import BLOCK_FLAG_NEED_QT_GUI, BLOCK_FLAG_NEED_WX_GUI -from .. base.odict import odict - -from .. base.Block import Block as _Block -from .. gui.Block import Block as _GUIBlock - -from . FlowGraph import _variable_matcher -from . import epy_block_io - - -class Block(_Block, _GUIBlock): - - 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 - _Block.__init__( - self, - flow_graph=flow_graph, - n=n, - ) - _GUIBlock.__init__(self) - - self._epy_source_hash = -1 # for epy blocks - self._epy_reload_error = None - - 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 '' - 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. - """ - _Block.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 "%s" failed.'%check) - except: self.add_error_message('Check "%s" did not evaluate.'%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 "%s" cannot be evaluated:\n%s' % (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 " + - 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. - """ - _Block.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/-msg 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() - extracted_docs = platform.block_docstrings.get(self._key, '') - return (self._doc + '\n\n' + extracted_docs).strip() - - def get_category(self): - return _Block.get_category(self) - - 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.%s.%s'%(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') - doc_end_tag = 'Block Documentation:' - - src = param_src.get_value() - src_hash = hash((self.get_id(), 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 = self._doc.split(doc_end_tag)[0] + doc_end_tag + '\n' + blk_io.doc - self._imports[0] = 'import ' + self.get_id() - self._make = '{0}.{1}({2})'.format(self.get_id(), 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: - try: - param = params[key] - param.set_default(value) - except KeyError: # need to make a new param - 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') - _Block.rewrite(self) diff --git a/grc/python/CMakeLists.txt b/grc/python/CMakeLists.txt deleted file mode 100644 index 3f9e273146..0000000000 --- a/grc/python/CMakeLists.txt +++ /dev/null @@ -1,44 +0,0 @@ -# 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" -) diff --git a/grc/python/Connection.py b/grc/python/Connection.py deleted file mode 100644 index 822876a0ae..0000000000 --- a/grc/python/Connection.py +++ /dev/null @@ -1,45 +0,0 @@ -""" -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 Constants -from .. base.Connection import Connection as _Connection -from .. gui.Connection import Connection as _GUIConnection - -class Connection(_Connection, _GUIConnection): - - def __init__(self, **kwargs): - _Connection.__init__(self, **kwargs) - _GUIConnection.__init__(self) - - 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. - """ - _Connection.validate(self) - 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 "%s" does not match sink IO size "%s".'%(source_size, sink_size)) diff --git a/grc/python/FlowGraph.py b/grc/python/FlowGraph.py deleted file mode 100644 index b2a1d27859..0000000000 --- a/grc/python/FlowGraph.py +++ /dev/null @@ -1,338 +0,0 @@ -""" -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 re -import imp -from operator import methodcaller - -from . import expr_utils -from .. base.FlowGraph import FlowGraph as _FlowGraph -from .. gui.FlowGraph import FlowGraph as _GUIFlowGraph - -_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(_FlowGraph, _GUIFlowGraph): - - def __init__(self, **kwargs): - self.grc_file_path = '' - _FlowGraph.__init__(self, **kwargs) - _GUIFlowGraph.__init__(self) - self.n = {} - self.n_hash = -1 - self._renew_eval_ns = True - self._eval_cache = {} - - def _eval(self, code, namespace, namespace_hash): - """ - Evaluate the code with the given namespace. - - Args: - code: a string with python code - namespace: a dict representing the namespace - namespace_hash: a unique hash for the namespace - - Returns: - the resultant object - """ - if not code: raise Exception, 'Cannot evaluate empty statement.' - my_hash = hash(code) ^ namespace_hash - #cache if does not exist - if not self._eval_cache.has_key(my_hash): - self._eval_cache[my_hash] = eval(code, namespace, namespace) - #return from cache - return self._eval_cache[my_hash] - - def get_hier_block_stream_io(self, direction): - """ - Get a list of stream io signatures for this flow graph. - - Args: - direction: a string of 'in' or 'out' - - Returns: - a list of dicts with: type, label, vlen, size, optional - """ - return filter(lambda p: p['type'] != "message", - self.get_hier_block_io(direction)) - - def get_hier_block_message_io(self, direction): - """ - Get a list of message io signatures for this flow graph. - - Args: - direction: a string of 'in' or 'out' - - Returns: - a list of dicts with: type, label, vlen, size, optional - """ - return filter(lambda p: p['type'] == "message", - self.get_hier_block_io(direction)) - - def get_hier_block_io(self, direction): - """ - Get a list of io ports for this flow graph. - - Args: - direction: a string of 'in' or 'out' - - Returns: - a list of dicts with: type, label, vlen, size, optional - """ - pads = self.get_pad_sources() if direction in ('sink', 'in') else \ - self.get_pad_sinks() if direction in ('source', 'out') else [] - ports = [] - for pad in pads: - master = { - 'label': str(pad.get_param('label').get_evaluated()), - 'type': str(pad.get_param('type').get_evaluated()), - 'vlen': str(pad.get_param('vlen').get_value()), - 'size': pad.get_param('type').get_opt('size'), - 'optional': bool(pad.get_param('optional').get_evaluated()), - } - num_ports = pad.get_param('num_streams').get_evaluated() - if num_ports > 1: - for i in xrange(num_ports): - clone = master.copy() - clone['label'] += str(i) - ports.append(clone) - else: - ports.append(master) - return ports - - def get_pad_sources(self): - """ - Get a list of pad source blocks sorted by id order. - - Returns: - a list of pad source blocks in this flow graph - """ - pads = filter(lambda b: b.get_key() == 'pad_source', self.get_enabled_blocks()) - return sorted(pads, lambda x, y: cmp(x.get_id(), y.get_id())) - - def get_pad_sinks(self): - """ - Get a list of pad sink blocks sorted by id order. - - Returns: - a list of pad sink blocks in this flow graph - """ - pads = filter(lambda b: b.get_key() == 'pad_sink', self.get_enabled_blocks()) - return sorted(pads, lambda x, y: cmp(x.get_id(), y.get_id())) - - def get_pad_port_global_key(self, port): - """ - Get the key for a port of a pad source/sink to use in connect() - This takes into account that pad blocks may have multiple ports - - Returns: - the key (str) - """ - key_offset = 0 - pads = self.get_pad_sources() if port.is_source() else self.get_pad_sinks() - for pad in pads: - # using the block param 'type' instead of the port domain here - # to emphasize that hier block generation is domain agnostic - is_message_pad = pad.get_param('type').get_evaluated() == "message" - if port.get_parent() == pad: - if is_message_pad: - key = pad.get_param('label').get_value() - else: - key = str(key_offset + int(port.get_key())) - return key - else: - # assuming we have either only sources or sinks - if not is_message_pad: - key_offset += len(pad.get_ports()) - return -1 - - 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 rewrite(self): - """ - Flag the namespace to be renewed. - """ - def reconnect_bus_blocks(): - for block in self.get_blocks(): - - 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); - self._renew_eval_ns = True - _FlowGraph.rewrite(self); - reconnect_bus_blocks(); - - def evaluate(self, expr): - """ - Evaluate the expression. - - Args: - expr: the string expression - @throw Exception bad expression - - Returns: - the evaluated data - """ - if self._renew_eval_ns: - self._renew_eval_ns = False - #reload namespace - n = dict() - #load imports - for code in self.get_imports(): - try: exec code in n - except: pass - - for id, code in self.get_python_modules(): - try: - module = imp.new_module(id) - exec code in module.__dict__ - n[id] = module - except: - pass - - #load parameters - np = dict() - for parameter in self.get_parameters(): - try: - e = eval(parameter.get_param('value').to_code(), n, n) - np[parameter.get_id()] = e - except: pass - n.update(np) #merge param namespace - #load variables - for variable in self.get_variables(): - try: - e = eval(variable.get_var_value(), n, n) - n[variable.get_id()] = e - except: pass - #make namespace public - self.n = n - self.n_hash = hash(str(n)) - #evaluate - e = self._eval(expr, self.n, self.n_hash) - return e - - def get_new_block(self, key): - """Try to auto-generate the block from file if missing""" - block = _FlowGraph.get_new_block(self, 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 = _FlowGraph.get_new_block(self, key) # can be None - return block diff --git a/grc/python/Param.py b/grc/python/Param.py deleted file mode 100644 index e60f613f00..0000000000 --- a/grc/python/Param.py +++ /dev/null @@ -1,433 +0,0 @@ -""" -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 ast -import re - -from gnuradio import gr - -from .. base.Param import Param as _Param -from .. gui.Param import Param as _GUIParam - -import Constants -from Constants import VECTOR_TYPES, COMPLEX_TYPES, REAL_TYPES, INT_TYPES - -from gnuradio import eng_notation - -_check_id_matcher = re.compile('^[a-z|A-Z]\w*$') -_show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook|epy_module)$') - - -#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__) - - -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' #value is zero - elif num.imag == 0: return '%s'%eng_notation.num_to_str(num.real) #value is real - elif num.real == 0: return '%sj'%eng_notation.num_to_str(num.imag) #value is imaginary - elif num.imag < 0: return '%s-%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(abs(num.imag))) - else: return '%s+%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(num.imag)) - else: return str(num) - - -class Param(_Param, _GUIParam): - - def __init__(self, **kwargs): - _Param.__init__(self, **kwargs) - _GUIParam.__init__(self) - 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 - ################################################## - truncate = 0 #default center truncate - 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: - dt_str = self.get_value() #large vectors use code - truncate = 1 - else: dt_str = ', '.join(map(num_to_str, e)) #small vectors use eval - elif t in ('file_open', 'file_save'): - dt_str = self.get_value() - truncate = -1 - else: dt_str = str(e) #other types - ################################################## - # done - ################################################## - return _truncate(dt_str, truncate) - - 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 _Param.get_color(self) - - 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 = _Param.get_hide(self) - 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. - A test evaluation is performed - """ - _Param.validate(self) #checks 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 "%s" cannot be evaluated:\n%s'%(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 "%s" is invalid for type complex.'%str(e) - return e - elif t == 'real' or t == 'float': - if not isinstance(e, REAL_TYPES): - raise Exception, 'Expression "%s" is invalid for type float.'%str(e) - return e - elif t == 'int': - if not isinstance(e, INT_TYPES): - raise Exception, 'Expression "%s" is invalid for type integer.'%str(e) - return e - elif t == 'hex': return hex(e) - elif t == 'bool': - if not isinstance(e, bool): - raise Exception, 'Expression "%s" is invalid for type bool.'%str(e) - return e - else: raise TypeError, 'Type "%s" not handled'%t - ######################### - # Numeric Vector Types - ######################### - elif t in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'): - if not v: v = '()' #turn a blank string into an empty list, so it will eval - #raise exception if python cannot evaluate this value - try: e = self.get_parent().get_parent().evaluate(v) - except Exception, e: raise Exception, 'Value "%s" cannot be evaluated:\n%s'%(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 "%s" is invalid for type complex vector.'%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 "%s" is invalid for type float vector.'%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 "%s" is invalid for type integer vector.'%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 "%s" must begin with a letter and may contain letters, numbers, and underscores.'%v - ids = [param.get_value() for param in self.get_all_params(t)] - if ids.count(v) > 1: #id should only appear once, or zero times if block is disabled - raise Exception, 'ID "%s" is not unique.'%v - if v in ID_BLACKLIST: - raise Exception, 'ID "%s" is blacklisted.'%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(): - if ids.count(v) > 1: #id should only appear once, or zero times if block is disabled - raise Exception, 'Stream ID "%s" is not unique.'%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 "%s" is not found.'%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 = '?' - - 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 and 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: return '' #allow for empty grid pos - 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 "%s", cell "%s".'%(str(parent), str(cell)) - return e - ######################### - # Notebook Page Type - ######################### - elif t == 'notebook': - if not v: return '' #allow for empty notebook - #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 "%s" is not an existing notebook id.'%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 "%s" is not a valid index number.'%page_index - return notebook_id, page_index - ######################### - # Import Type - ######################### - elif t == 'import': - n = dict() #new namespace - try: exec v in n - except ImportError: raise Exception, 'Import "%s" failed.'%v - except Exception: raise Exception, 'Bad import syntax: "%s".'%v - return filter(lambda k: str(k) != '__builtins__', n.keys()) - ######################### - else: raise TypeError, 'Type "%s" not handled'%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() - if t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'): # string types - if not self._init: self.evaluate() - if self._stringify_flag: return '"%s"'%v.replace('"', '\"') - else: return v - elif t in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'): #vector types - 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()], []) diff --git a/grc/python/Platform.py b/grc/python/Platform.py deleted file mode 100644 index 2a0bbf9a9e..0000000000 --- a/grc/python/Platform.py +++ /dev/null @@ -1,174 +0,0 @@ -""" -Copyright 2008-2016 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 .. base.Platform import Platform as _Platform -from .. gui.Platform import Platform as _GUIPlatform -from .. gui import Messages - -from . import extract_docs -from .FlowGraph import FlowGraph as _FlowGraph -from .Connection import Connection as _Connection -from .Block import Block as _Block -from .Port import Port as _Port -from .Param import Param as _Param -from .Generator import Generator -from .Constants import ( - HIER_BLOCKS_LIB_DIR, BLOCK_DTD, DEFAULT_FLOW_GRAPH, BLOCKS_DIRS, - PREFS_FILE, PREFS_FILE_OLD, CORE_TYPES -) - -COLORS = [(name, color) for name, key, sizeof, color in CORE_TYPES] - - -class Platform(_Platform, _GUIPlatform): - def __init__(self): - """ - Make a platform for gnuradio. - """ - # 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 = block_docstrings = dict() - self.block_docstrings_loaded_callback = lambda: None - - def setter(key, docs): - block_docstrings[key] = '\n\n'.join( - '--- {0} ---\n{1}\n'.format(b, d.replace('\n\n', '\n')) - for b, d in docs.iteritems() if d is not None - ) - - self._docstring_extractor = extract_docs.SubprocessLoader( - callback_query_result=setter, - callback_finished=lambda: self.block_docstrings_loaded_callback() - ) - - # init - _Platform.__init__( - self, - name='GNU Radio Companion', - version=(gr.version(), gr.major_version(), gr.api_version(), gr.minor_version()), - key='grc', - license=__doc__.strip(), - website='http://gnuradio.org/', - block_paths=BLOCKS_DIRS, - block_dtd=BLOCK_DTD, - default_flow_graph=DEFAULT_FLOW_GRAPH, - generator=Generator, - colors=COLORS, - ) - self._move_old_pref_file() - _GUIPlatform.__init__( - self, - prefs_file=PREFS_FILE - ) - self._auto_hier_block_generate_chain = set() - - @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() - _Platform.load_blocks(self) - self._docstring_extractor.finish() - # self._docstring_extractor.wait() - - def load_block_xml(self, xml_file): - block = _Platform.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: %r: %s\n' % (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: %r\n' % file_path) - generator = self.get_generator()(flow_graph, file_path) - generator.write() - except Exception as e: - Messages.send('>>> Generate Error: %r: %s\n' % (file_path, str(e))) - return False - - self.load_block_xml(generator.get_file_path_xml()) - return True - - ############################################## - # Constructors - ############################################## - FlowGraph = _FlowGraph - Connection = _Connection - Block = _Block - Port = _Port - Param = _Param diff --git a/grc/python/Port.py b/grc/python/Port.py deleted file mode 100644 index 249d7aed71..0000000000 --- a/grc/python/Port.py +++ /dev/null @@ -1,268 +0,0 @@ -""" -Copyright 2008-2012 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 .. base.Port import Port as _Port -from .. base.Constants import DEFAULT_DOMAIN, GR_MESSAGE_DOMAIN -from .. gui.Port import Port as _GUIPort -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 %s'%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 %s'%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 %s'%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: return _get_sink_from_virtual_sink_port( - vsp.get_enabled_connections()[0].get_sink()) # Could have many connections, but use first - except: raise Exception, 'Could not resolve source for virtual source port %s'%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 %s'%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 %s'%vsp - -class Port(_Port, _GUIPort): - - 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 - _Port.__init__( - self, - block=block, - n=n, - dir=dir, - ) - _GUIPort.__init__(self) - 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 get_types(self): return Constants.TYPE_TO_SIZEOF.keys() - - def is_type_empty(self): return not self._n['type'] - - def validate(self): - _Port.validate(self) - 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 = '' - _Port.rewrite(self) - - 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) - return '#%.2x%.2x%.2x'%(r, g, b) - except: return _Port.get_color(self) - - 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' - if not self._key.isdigit(): # also update key for none stream ports - self._key = self._name - - # Prepare a copy of the odict for the clone - n = self._n.copy() - if 'nports' in n: n.pop('nports') # remove nports from the key so the copy cannot be a duplicator - n['name'] = self._n['name'] + str(len(self._clones) + 1) - n['key'] = '99999' if self._key.isdigit() else n['name'] # dummy value 99999 will be fixed later - - port = self.__class__(self.get_parent(), n, self._dir) # clone - 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'] - if not self._key.isdigit(): # also update key for none stream ports - self._key = self._name diff --git a/grc/python/__init__.py b/grc/python/__init__.py deleted file mode 100644 index 8b13789179..0000000000 --- a/grc/python/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/grc/scripts/CMakeLists.txt b/grc/scripts/CMakeLists.txt index e905892308..6cc78c3cf3 100644 --- a/grc/scripts/CMakeLists.txt +++ b/grc/scripts/CMakeLists.txt @@ -23,3 +23,5 @@ GR_PYTHON_INSTALL( DESTINATION ${GR_RUNTIME_DIR} COMPONENT "grc" ) + +add_subdirectory(freedesktop) diff --git a/grc/freedesktop/CMakeLists.txt b/grc/scripts/freedesktop/CMakeLists.txt index 47e836f697..47e836f697 100644 --- a/grc/freedesktop/CMakeLists.txt +++ b/grc/scripts/freedesktop/CMakeLists.txt diff --git a/grc/freedesktop/README b/grc/scripts/freedesktop/README index 0857ecc224..0857ecc224 100644 --- a/grc/freedesktop/README +++ b/grc/scripts/freedesktop/README diff --git a/grc/freedesktop/convert.sh b/grc/scripts/freedesktop/convert.sh index e2cba264a6..e2cba264a6 100755 --- a/grc/freedesktop/convert.sh +++ b/grc/scripts/freedesktop/convert.sh diff --git a/grc/freedesktop/gnuradio-grc.desktop b/grc/scripts/freedesktop/gnuradio-grc.desktop index 39beeca1b8..39beeca1b8 100644 --- a/grc/freedesktop/gnuradio-grc.desktop +++ b/grc/scripts/freedesktop/gnuradio-grc.desktop diff --git a/grc/freedesktop/gnuradio-grc.xml b/grc/scripts/freedesktop/gnuradio-grc.xml index a5cb95d9fd..a5cb95d9fd 100644 --- a/grc/freedesktop/gnuradio-grc.xml +++ b/grc/scripts/freedesktop/gnuradio-grc.xml diff --git a/grc/freedesktop/gnuradio_logo_icon-square.svg b/grc/scripts/freedesktop/gnuradio_logo_icon-square.svg index 3b54bf4001..3b54bf4001 100644 --- a/grc/freedesktop/gnuradio_logo_icon-square.svg +++ b/grc/scripts/freedesktop/gnuradio_logo_icon-square.svg diff --git a/grc/freedesktop/grc-icon-128.png b/grc/scripts/freedesktop/grc-icon-128.png Binary files differindex 13efe806ba..13efe806ba 100644 --- a/grc/freedesktop/grc-icon-128.png +++ b/grc/scripts/freedesktop/grc-icon-128.png diff --git a/grc/freedesktop/grc-icon-16.png b/grc/scripts/freedesktop/grc-icon-16.png Binary files differindex bdd1823b3d..bdd1823b3d 100644 --- a/grc/freedesktop/grc-icon-16.png +++ b/grc/scripts/freedesktop/grc-icon-16.png diff --git a/grc/freedesktop/grc-icon-24.png b/grc/scripts/freedesktop/grc-icon-24.png Binary files differindex a124768125..a124768125 100644 --- a/grc/freedesktop/grc-icon-24.png +++ b/grc/scripts/freedesktop/grc-icon-24.png diff --git a/grc/freedesktop/grc-icon-256.png b/grc/scripts/freedesktop/grc-icon-256.png Binary files differindex 077688eac5..077688eac5 100644 --- a/grc/freedesktop/grc-icon-256.png +++ b/grc/scripts/freedesktop/grc-icon-256.png diff --git a/grc/freedesktop/grc-icon-32.png b/grc/scripts/freedesktop/grc-icon-32.png Binary files differindex a345aace3c..a345aace3c 100644 --- a/grc/freedesktop/grc-icon-32.png +++ b/grc/scripts/freedesktop/grc-icon-32.png diff --git a/grc/freedesktop/grc-icon-48.png b/grc/scripts/freedesktop/grc-icon-48.png Binary files differindex c522a5d0ec..c522a5d0ec 100644 --- a/grc/freedesktop/grc-icon-48.png +++ b/grc/scripts/freedesktop/grc-icon-48.png diff --git a/grc/freedesktop/grc-icon-64.png b/grc/scripts/freedesktop/grc-icon-64.png Binary files differindex df4f6dc07b..df4f6dc07b 100644 --- a/grc/freedesktop/grc-icon-64.png +++ b/grc/scripts/freedesktop/grc-icon-64.png diff --git a/grc/freedesktop/grc_setup_freedesktop.in b/grc/scripts/freedesktop/grc_setup_freedesktop.in index 87a388e2ec..87a388e2ec 100644 --- a/grc/freedesktop/grc_setup_freedesktop.in +++ b/grc/scripts/freedesktop/grc_setup_freedesktop.in diff --git a/grc/scripts/gnuradio-companion b/grc/scripts/gnuradio-companion index 203a8c773d..04a1cb44e7 100755 --- a/grc/scripts/gnuradio-companion +++ b/grc/scripts/gnuradio-companion @@ -1,6 +1,6 @@ #!/usr/bin/env python """ -Copyright 2009-2015 Free Software Foundation, Inc. +Copyright 2016 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -20,111 +20,19 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA import os import sys -import optparse -import warnings - -GR_IMPORT_ERROR_MESSAGE = """\ -Cannot import gnuradio. - -Is the python path environment variable set correctly? - All OS: PYTHONPATH - -Is the library path environment variable set correctly? - Linux: LD_LIBRARY_PATH - Windows: PATH - MacOSX: DYLD_LIBRARY_PATH -""" - -VERSION_AND_DISCLAIMER_TEMPLATE = """\ -GNU Radio Companion %s - -This program is part of GNU Radio -GRC comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it. -""" - - -def die(error, message): - msg = "{0}\n\n({1})".format(message, error) - try: - import gtk - d = gtk.MessageDialog( - type=gtk.MESSAGE_ERROR, - buttons=gtk.BUTTONS_CLOSE, - message_format=msg, - ) - d.set_title(type(error).__name__) - d.run() - exit(1) - except ImportError: - exit(type(error).__name__ + '\n\n' + msg) - - -def check_gtk(): - try: - warnings.filterwarnings("error") - import pygtk - pygtk.require('2.0') - import gtk - gtk.init_check() - warnings.filterwarnings("always") - except Exception as err: - die(err, "Failed to initialize GTK. If you are running over ssh, " - "did you enable X forwarding and start ssh with -X?") - - -def check_gnuradio_import(): - try: - from gnuradio import gr - except ImportError as err: - die(err, GR_IMPORT_ERROR_MESSAGE) - - -def check_blocks_path(): - if 'GR_DONT_LOAD_PREFS' in os.environ and not os.environ.get('GRC_BLOCKS_PATH', ''): - die(EnvironmentError("No block definitions available"), - "Can't find block definitions. Use config.conf or GRC_BLOCKS_PATH.") - - -def get_source_tree_root(): - source_tree_subpath = "/grc/scripts" - script_path = os.path.dirname(os.path.abspath(__file__)) - if script_path.endswith(source_tree_subpath): - return script_path[:-len(source_tree_subpath)] - - -def main(): - check_gnuradio_import() - - from gnuradio import gr - parser = optparse.OptionParser( - usage='usage: %prog [options] [saved flow graphs]', - version=VERSION_AND_DISCLAIMER_TEMPLATE % gr.version()) - options, args = parser.parse_args() - - check_gtk() - check_blocks_path() - source_tree_root = get_source_tree_root() - if not source_tree_root: - # run the installed version - from gnuradio.grc.python.Platform import Platform - from gnuradio.grc.gui.ActionHandler import ActionHandler - - else: - print("Running from source tree") - sys.path.insert(1, source_tree_root) - from grc.python.Platform import Platform - from grc.gui.ActionHandler import ActionHandler - - try: - import gtk - gtk.window_set_default_icon(gtk.IconTheme().load_icon('gnuradio-grc', 256, 0)) - except: - pass - - ActionHandler(args, Platform()) - - -if __name__ == '__main__': - main() +script_path = os.path.dirname(os.path.abspath(__file__)) +source_tree_subpath = "/grc/scripts" + +if not script_path.endswith(source_tree_subpath): + # run the installed version + from gnuradio.grc.main import main + from gnuradio.grc import checks +else: + print("Running from source tree") + sys.path.insert(1, script_path[:-len(source_tree_subpath)]) + from grc.main import main + from grc import checks + +checks.do_all() +exit(main()) diff --git a/grc/todo.txt b/grc/todo.txt deleted file mode 100644 index cedea72aa3..0000000000 --- a/grc/todo.txt +++ /dev/null @@ -1,69 +0,0 @@ -################################################## -# Examples -################################################## -* Push-to-Talk example -* Start/Stop the flow graph - -################################################## -# Blocks -################################################## -* probe: also non-float outputs -* log slider gui control -* packet mod: whitening offset -* wx min window size in options block -* gr_adaptive_fir_ccf -* size params for the graphical sinks -* callbacks for set average on fft, waterfall, number sinks -* add units to params: Sps, Hz, dB... -* add bool type to command line option store_true or store_false -* messages for packet blocks and probe blocks - -################################################## -# Features -################################################## -* extract category from doxygen - * fix up block tree to mirror current doxygen group - * remove blocks in block tree covered by doxygen -* param editor, expand entry boxes in focus -* change param dialog to panel within main window -* gui grid editor for configuring grid params/placing wxgui plots and controls -* drag from one port to another to connect -* per parameter docs - * extract individual param docs from doxygen - * doc tag in param for handwritten notes -* separate generated code into top block and gui class - * use gui.py in gr-wxgui and remove custom top_block_gui -* configuration option for adding block paths -* orientations for ports (top, right, bottom, left) - * source defaults to right, sink defaults to left -* separation of variables and gui controls -* speedup w/ background layer and animation layer -* multiple doxygen directories (doc_dir becomes doc_path) -* use pango markup in tooltips for params -* use get_var_make to determine if it is a variable, not regexp -* concept of a project, or project flow graph - * collection of blocks, hier and top - * system-wide, default/work, and user created -* use templates/macros to generate the repetative stuff in the xml - -################################################## -# Problems -################################################## -* msg ports dont work with virtual connections - * dont fix this until pmts are used? -* hier block generation - * auto generate hier library on changes - * auto clean hier library when block removed - * add hier blocks to tree without restart -* dont generate py files in saved flowgraph dir -* save/restore cwd -* threads dont die on exit in probe and variable sink -* align param titles in properties dialog -* weird grid params misbehaving -* gr hier blocks have more diverse IO capabilities than we allow for - -################################################## -# Future -################################################## -* require pygtk 2.12 for treeview tooltips - * remove try/except in BlockTreeWindow.py |