summaryrefslogtreecommitdiff
path: root/grc
diff options
context:
space:
mode:
authorSebastian Koslowski <koslowski@kit.edu>2016-06-10 15:15:16 +0200
committerSebastian Koslowski <koslowski@kit.edu>2016-07-13 16:30:36 +0200
commit893b74c770b81f2c09094577e2de720155f84b61 (patch)
treece430f554cf032c70eab8a95228731afbffc6efd /grc
parent7ac7cf6246e4d984d36c64df10ba4d2b2f6b2204 (diff)
parent6fb0ff274a05daf2f2677af14337704fb88081f7 (diff)
Merge remote-tracking branch 'grcwg/next_grcwg' into gtk3
Diffstat (limited to 'grc')
-rw-r--r--grc/blocks/block_tree.xml11
-rw-r--r--grc/blocks/epy_block.xml1
-rw-r--r--grc/blocks/epy_module.xml1
-rw-r--r--grc/blocks/options.xml6
-rw-r--r--grc/blocks/parameter.xml6
-rw-r--r--grc/blocks/variable_function_probe.xml18
-rw-r--r--grc/blocks/variable_struct.xml.py1
-rw-r--r--grc/core/Block.py8
-rw-r--r--grc/core/Constants.py1
-rw-r--r--grc/core/Element.pyi54
-rw-r--r--grc/core/Platform.py76
-rw-r--r--grc/core/generator/Generator.py35
-rw-r--r--grc/core/generator/flow_graph.tmpl23
-rw-r--r--grc/core/utils/expr_utils.py10
-rw-r--r--grc/gui/ActionHandler.py5
-rw-r--r--grc/gui/BlockTreeWindow.py82
-rw-r--r--grc/gui/Constants.py7
-rwxr-xr-xgrc/main.py13
-rw-r--r--grc/test/.cache/v/cache/lastfailed3
19 files changed, 210 insertions, 151 deletions
diff --git a/grc/blocks/block_tree.xml b/grc/blocks/block_tree.xml
index a8775d6872..3125864d4d 100644
--- a/grc/blocks/block_tree.xml
+++ b/grc/blocks/block_tree.xml
@@ -1,11 +1,6 @@
<?xml version="1.0"?>
-<!--
-###################################################
-##Block Tree for platform gnuradio python.
-###################################################
- -->
<cat>
- <name></name> <!-- Blank for Root Name -->
+ <name>[Core]</name>
<cat>
<name>Misc</name>
<block>pad_source</block>
@@ -18,12 +13,16 @@
<block>bus_structure_sink</block>
<block>bus_structure_source</block>
+ <block>epy_block</block>
+ <block>epy_module</block>
+
<block>note</block>
<block>import</block>
</cat>
<cat>
<name>Variables</name>
<block>variable</block>
+ <block>variable_struct</block>
<block>variable_config</block>
<block>variable_function_probe</block>
<block>parameter</block>
diff --git a/grc/blocks/epy_block.xml b/grc/blocks/epy_block.xml
index 3fd5aa84f1..65e78c4062 100644
--- a/grc/blocks/epy_block.xml
+++ b/grc/blocks/epy_block.xml
@@ -2,7 +2,6 @@
<block>
<name>Python Block</name>
<key>epy_block</key>
- <category>Misc</category>
<import></import>
<make></make>
<param><!-- Cache the last working block IO to keep FG sane -->
diff --git a/grc/blocks/epy_module.xml b/grc/blocks/epy_module.xml
index 6d6d71804c..fa3e5f91f4 100644
--- a/grc/blocks/epy_module.xml
+++ b/grc/blocks/epy_module.xml
@@ -2,7 +2,6 @@
<block>
<name>Python Module</name>
<key>epy_module</key>
- <category>Misc</category>
<import>import $id # embedded python module</import>
<make></make>
<param>
diff --git a/grc/blocks/options.xml b/grc/blocks/options.xml
index 937cfe82ea..55f411884d 100644
--- a/grc/blocks/options.xml
+++ b/grc/blocks/options.xml
@@ -20,8 +20,8 @@ from PyQt4 import Qt
import sys
#end if
#if not $generate_options().startswith('hb')
-from optparse import OptionParser
-from gnuradio.eng_option import eng_option
+from argparse import ArgumentParser
+from gnuradio.eng_arg import eng_float, intx
from gnuradio import eng_notation
#end if</import>
<make></make>
@@ -84,7 +84,7 @@ else: self.stop(); self.wait()</callback>
<param>
<name>Category</name>
<key>category</key>
- <value>Custom</value>
+ <value>[GRC Hier Blocks]</value>
<type>string</type>
<hide>#if $generate_options().startswith('hb') then 'none' else 'all'#</hide>
</param>
diff --git a/grc/blocks/parameter.xml b/grc/blocks/parameter.xml
index e35b8f4d1d..b0713218fd 100644
--- a/grc/blocks/parameter.xml
+++ b/grc/blocks/parameter.xml
@@ -55,9 +55,11 @@
</option>
<option>
<name>String</name>
- <key>string</key>
+ <key>str</key>
<opt>type:string</opt>
- </option>
+ </option>
+ <!-- Do not forget to add option value type handler import into
+ grc/python/flow_graph.tmpl for each new type. -->
<!-- not supported yet in tmpl
<option>
<name>Boolean</name>
diff --git a/grc/blocks/variable_function_probe.xml b/grc/blocks/variable_function_probe.xml
index baa996c0ec..47c11b29fe 100644
--- a/grc/blocks/variable_function_probe.xml
+++ b/grc/blocks/variable_function_probe.xml
@@ -10,7 +10,7 @@
<import>import time</import>
<import>import threading</import>
<var_make>self.$(id) = $(id) = $value</var_make>
- <make>#slurp
+ <make>
def _$(id)_probe():
while True:
#set $obj = 'self' + ('.' + $block_id() if $block_id() else '')
@@ -22,15 +22,10 @@ def _$(id)_probe():
time.sleep(1.0 / ($poll_rate))
_$(id)_thread = threading.Thread(target=_$(id)_probe)
_$(id)_thread.daemon = True
-_$(id)_thread.start()</make>
+_$(id)_thread.start()
+ </make>
<callback>self.set_$(id)($value)</callback>
<param>
- <name>Value</name>
- <key>value</key>
- <value>0</value>
- <type>raw</type>
- </param>
- <param>
<name>Block ID</name>
<key>block_id</key>
<value>my_block_0</value>
@@ -55,6 +50,13 @@ _$(id)_thread.start()</make>
<value>10</value>
<type>real</type>
</param>
+ <param>
+ <name>Initial Value</name>
+ <key>value</key>
+ <value>0</value>
+ <type>raw</type>
+ <hide>part</hide>
+ </param>
<doc>
Periodically probe a function and set its value to this variable.
diff --git a/grc/blocks/variable_struct.xml.py b/grc/blocks/variable_struct.xml.py
index e43200828b..de4411e975 100644
--- a/grc/blocks/variable_struct.xml.py
+++ b/grc/blocks/variable_struct.xml.py
@@ -6,7 +6,6 @@ HEADER = """\
<block>
<name>Struct Variable</name>
<key>variable_struct</key>
- <category>Variables</category>
<import>def struct(data): return type('Struct', (object,), data)()</import>
<var_make>self.$id = $id = struct({{#slurp
#for $i in range({0}):
diff --git a/grc/core/Block.py b/grc/core/Block.py
index ff7f041dc0..9fff5afcb7 100644
--- a/grc/core/Block.py
+++ b/grc/core/Block.py
@@ -67,7 +67,7 @@ class Block(Element):
self._name = n['name']
self._key = n['key']
- self._category = n.get('category', '')
+ self.category = [cat.strip() for cat in n.get('category', '').split('/') if cat.strip()]
self._flags = n.get('flags', '')
self._doc = n.get('doc', '').strip('\n').replace('\\\n', '')
@@ -491,12 +491,6 @@ class Block(Element):
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()
diff --git a/grc/core/Constants.py b/grc/core/Constants.py
index 8a99f8b256..992d5e7d83 100644
--- a/grc/core/Constants.py
+++ b/grc/core/Constants.py
@@ -41,6 +41,7 @@ FLOW_GRAPH_FILE_FORMAT_VERSION = 1
# Param tabs
DEFAULT_PARAM_TAB = "General"
ADVANCED_PARAM_TAB = "Advanced"
+DEFAULT_BLOCK_MODULE_NAME = '(no module specified)'
# Port domains
GR_STREAM_DOMAIN = "gr_stream"
diff --git a/grc/core/Element.pyi b/grc/core/Element.pyi
new file mode 100644
index 0000000000..c81180a33e
--- /dev/null
+++ b/grc/core/Element.pyi
@@ -0,0 +1,54 @@
+# Copyright 2008, 2009, 2015, 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 . import Platform, FlowGraph, Block
+
+def lazy_property(func):
+ return func
+
+
+class Element(object):
+
+ def __init__(self, parent=None):
+ ...
+
+ @property
+ def parent(self):
+ ...
+
+ def get_parent_by_type(self, cls):
+ parent = self.parent
+ if parent is None:
+ return None
+ elif isinstance(parent, cls):
+ return parent
+ else:
+ return parent.get_parent_by_type(cls)
+
+ @lazy_property
+ def parent_platform(self): -> Platform.Platform
+ ...
+
+ @lazy_property
+ def parent_flowgraph(self): -> FlowGraph.FlowGraph
+ ...
+
+ @lazy_property
+ def parent_block(self): -> Block.Block
+ ...
+
+
diff --git a/grc/core/Platform.py b/grc/core/Platform.py
index 069870d389..be7b60ca59 100644
--- a/grc/core/Platform.py
+++ b/grc/core/Platform.py
@@ -69,7 +69,7 @@ class Platform(Element):
self.blocks = {}
self._blocks_n = {}
- self._category_trees_n = []
+ self._block_categories = {}
self.domains = {}
self.connection_templates = {}
@@ -79,7 +79,8 @@ class Platform(Element):
self._flow_graph = Element.__new__(FlowGraph)
Element.__init__(self._flow_graph, self)
self._flow_graph.connections = []
- self.load_blocks()
+
+ self.build_block_library()
def __str__(self):
return 'Platform - {}({})'.format(self.config.key, self.config.name)
@@ -137,14 +138,14 @@ class Platform(Element):
self.load_block_xml(generator.file_path_xml)
return True
- def load_blocks(self):
+ def build_block_library(self):
"""load the blocks and block tree from the search paths"""
self._docstring_extractor.start()
# Reset
self.blocks.clear()
self._blocks_n.clear()
- del self._category_trees_n[:]
+ self._block_categories.clear()
self.domains.clear()
self.connection_templates.clear()
ParseXML.xml_failures.clear()
@@ -162,9 +163,21 @@ class Platform(Element):
# print >> sys.stderr, 'Warning: Block validation failed:\n\t%s\n\tIgnoring: %s' % (e, xml_file)
pass
except Exception as e:
- raise
print('Warning: XML parsing failed:\n\t%r\n\tIgnoring: %s' % (e, xml_file), file=sys.stderr)
+ # Add blocks to block tree
+ for key, block in six.iteritems(self.blocks):
+ category = self._block_categories.get(key, block.category)
+ # Blocks with empty categories are hidden
+ if not category:
+ continue
+ root = category[0]
+ if root.startswith('[') and root.endswith(']'):
+ category[0] = root[1:-1]
+ else:
+ category.insert(0, Constants.DEFAULT_BLOCK_MODULE_NAME)
+ block.category = category
+
self._docstring_extractor.finish()
# self._docstring_extractor.wait()
@@ -202,8 +215,19 @@ class Platform(Element):
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).get('cat', {})
- self._category_trees_n.append(n)
+ xml = ParseXML.from_file(xml_file)
+ path = []
+
+ def load_category(cat_n):
+ path.append(cat_n.get('name').strip())
+ for block_key in cat_n.get('block', []):
+ if block_key not in self._block_categories:
+ self._block_categories[block_key] = list(path)
+ for sub_cat_n in cat_n.get('cat', []):
+ load_category(sub_cat_n)
+ path.pop()
+
+ load_category(xml.get('cat', {}))
def load_domain_xml(self, xml_file):
"""Load a domain properties and connection templates from XML"""
@@ -248,44 +272,6 @@ class Platform(Element):
else:
self.connection_templates[key] = connection_n.get('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.get('name')]
- block_tree.add_block(parent)
- # Recursive call to load sub categories
- for cat in cat_n.get('cat', []):
- load_category(cat, parent)
- # Add blocks in this category
- for block_key in cat_n.get('block', []):
- if block_key not in self.blocks:
- print('Warning: Block key "{}" not found when loading category tree.'.format(block_key), file=sys.stderr)
- 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 six.itervalues(self.blocks):
- # 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 six.iteritems(docstrings):
diff --git a/grc/core/generator/Generator.py b/grc/core/generator/Generator.py
index c3308d6c32..9c7d07e76b 100644
--- a/grc/core/generator/Generator.py
+++ b/grc/core/generator/Generator.py
@@ -144,16 +144,16 @@ class TopBlockGenerator(object):
pass
return code
- blocks = expr_utils.sort_objects(
+ blocks_all = expr_utils.sort_objects(
[b for b in fg.blocks if b.get_enabled() and not b.get_bypassed()],
operator.methodcaller('get_id'), _get_block_sort_text
)
- deprecated_block_keys = set(block.get_name() for block in blocks if block.is_deprecated)
+ deprecated_block_keys = set(b.get_name() for b in blocks_all if b.is_deprecated)
for key in deprecated_block_keys:
Messages.send_warning("The block {!r} is deprecated.".format(key))
# List of regular blocks (all blocks minus the special ones)
- blocks = [b for b in blocks if b not in imports and b not in parameters]
+ blocks = [b for b in blocks_all if b not in imports and b not in parameters]
for block in blocks:
key = block.get_key()
@@ -220,18 +220,20 @@ class TopBlockGenerator(object):
# List of variable names
var_ids = [var.get_id() for var in parameters + variables]
- # Prepend self.
- replace_dict = dict([(var_id, 'self.%s' % var_id) for var_id in var_ids])
- # List of callbacks
- callbacks = [
- expr_utils.expr_replace(cb, replace_dict)
- for cb in sum([block.get_callbacks() for block in fg.get_enabled_blocks()], [])
- ]
+ replace_dict = dict((var_id, 'self.' + var_id) for var_id in var_ids)
+ callbacks_all = []
+ for block in blocks_all:
+ callbacks_all.extend(expr_utils.expr_replace(cb, replace_dict) for cb in block.get_callbacks())
+
# Map var id to callbacks
- var_id2cbs = dict([
- (var_id, [c for c in callbacks if expr_utils.get_variable_dependencies(c, [var_id])])
- for var_id in var_ids
- ])
+ def uses_var_id():
+ used = expr_utils.get_variable_dependencies(callback, [var_id])
+ return used and 'self.' + var_id in callback # callback might contain var_id itself
+
+ callbacks = {}
+ for var_id in var_ids:
+ callbacks[var_id] = [callback for callback in callbacks_all if uses_var_id()]
+
# Load the namespace
namespace = {
'title': title,
@@ -245,7 +247,7 @@ class TopBlockGenerator(object):
'connection_templates': connection_templates,
'msgs': msgs,
'generate_options': self._generate_options,
- 'var_id2cbs': var_id2cbs,
+ 'callbacks': callbacks,
}
# Build the template
t = Template(open(FLOW_GRAPH_TEMPLATE, 'r').read(), namespace)
@@ -265,8 +267,9 @@ 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.parent
- hier_block_lib_dir = flow_graph.parent_platform.config.hier_block_lib_dir
+ 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)
diff --git a/grc/core/generator/flow_graph.tmpl b/grc/core/generator/flow_graph.tmpl
index c86808455b..5eef3d2042 100644
--- a/grc/core/generator/flow_graph.tmpl
+++ b/grc/core/generator/flow_graph.tmpl
@@ -13,7 +13,7 @@
##@param connections the connections
##@param msgs the msg type connections
##@param generate_options the type of flow graph
-##@param var_id2cbs variable id map to callback strings
+##@param callbacks variable id map to callback strings
########################################################
#def indent($code)
#set $code = '\n '.join(str($code).splitlines())
@@ -301,12 +301,12 @@ gr.io_signaturev($(len($io_sigs)), $(len($io_sigs)), [$(', '.join($size_strs))])
#if $flow_graph.get_option('thread_safe_setters')
with self._lock:
self.$id = $id
- #for $callback in $var_id2cbs[$id]
+ #for $callback in $callbacks[$id]
$indent($callback)
#end for
#else
self.$id = $id
- #for $callback in $var_id2cbs[$id]
+ #for $callback in $callbacks[$id]
$indent($callback)
#end for
#end if
@@ -336,19 +336,22 @@ $short_id#slurp
def argument_parser():
- #set $desc_args = 'usage="%prog: [options]", option_class=eng_option'
+ #set $arg_parser_args = ''
#if $flow_graph.get_option('description')
- #set $desc_args += ', description=description'
+ #set $arg_parser_args = 'description=description'
description = $repr($flow_graph.get_option('description'))
#end if
- parser = OptionParser($desc_args)
+ parser = ArgumentParser($arg_parser_args)
#for $param in $parameters
#set $type = $param.get_param('type').get_value()
#if $type
#silent $params_eq_list.append('%s=options.%s'%($param.get_id(), $param.get_id()))
- parser.add_option(
- "$make_short_id($param)", "--$param.get_id().replace('_', '-')", dest="$param.get_id()", type="$type", default=$make_default($type, $param),
- help="Set $($param.get_param('label').get_evaluated() or $param.get_id()) [default=%default]")
+ parser.add_argument(
+ #if $make_short_id($param)
+ "$make_short_id($param)", #slurp
+ #end if
+ "--$param.get_id().replace('_', '-')", dest="$param.get_id()", type=$type, default=$make_default($type, $param),
+ help="Set $($param.get_param('label').get_evaluated() or $param.get_id()) [default=%(default)r]")
#end if
#end for
return parser
@@ -358,7 +361,7 @@ def argument_parser():
def main(top_block_cls=$(class_name), options=None):
#if $parameters
if options is None:
- options, _ = argument_parser().parse_args()
+ options = argument_parser().parse_args()
#end if
#if $flow_graph.get_option('realtime_scheduling')
if gr.enable_realtime_scheduling() != gr.RT_OK:
diff --git a/grc/core/utils/expr_utils.py b/grc/core/utils/expr_utils.py
index 0577f06a75..555bd709b1 100644
--- a/grc/core/utils/expr_utils.py
+++ b/grc/core/utils/expr_utils.py
@@ -61,7 +61,7 @@ class graph(object):
return self._graph[node_key]
-def expr_split(expr):
+def expr_split(expr, var_chars=VAR_CHARS):
"""
Split up an expression by non alphanumeric characters, including underscore.
Leave strings in-tact.
@@ -77,7 +77,7 @@ def expr_split(expr):
tok = ''
quote = ''
for char in expr:
- if quote or char in VAR_CHARS:
+ if quote or char in var_chars:
if char == quote:
quote = ''
tok += char
@@ -104,7 +104,7 @@ def expr_replace(expr, replace_dict):
Returns:
a new expression with the prepend
"""
- expr_splits = expr_split(expr)
+ expr_splits = expr_split(expr, var_chars=VAR_CHARS + '.')
for i, es in enumerate(expr_splits):
if es in list(replace_dict.keys()):
expr_splits[i] = replace_dict[es]
@@ -195,7 +195,3 @@ def sort_objects(objects, get_id, get_expr):
# Return list of sorted objects
return [id2obj[id] for id in sorted_ids]
-
-if __name__ == '__main__':
- for i in sort_variables({'x': '1', 'y': 'x+1', 'a': 'x+y', 'b': 'y+1', 'c': 'a+b+x+y'}):
- print(i)
diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py
index 8d4dc7841f..25c779b4d2 100644
--- a/grc/gui/ActionHandler.py
+++ b/grc/gui/ActionHandler.py
@@ -608,9 +608,8 @@ class ActionHandler:
elif action == Actions.PAGE_CHANGE: # pass and run the global actions
pass
elif action == Actions.RELOAD_BLOCKS:
- self.platform.load_blocks()
- main.btwin.clear()
- self.platform.load_block_tree(main.btwin)
+ self.platform.build_block_library()
+ main.btwin.repopulate()
Actions.XML_PARSER_ERRORS_DISPLAY.set_sensitive(bool(
ParseXML.xml_failures))
Messages.send_xml_errors_if_any(ParseXML.xml_failures)
diff --git a/grc/gui/BlockTreeWindow.py b/grc/gui/BlockTreeWindow.py
index 26086f58e9..89aac53a0e 100644
--- a/grc/gui/BlockTreeWindow.py
+++ b/grc/gui/BlockTreeWindow.py
@@ -27,7 +27,8 @@ from gi.repository import Gdk
from gi.repository import GObject
from . import Actions, Utils
-from .Constants import DEFAULT_BLOCKS_WINDOW_WIDTH, DND_TARGETS
+from . import Constants
+
NAME_INDEX, KEY_INDEX, DOC_INDEX = range(3)
@@ -51,6 +52,18 @@ def _format_doc(doc):
return out or 'undocumented'
+def _format_cat_tooltip(category):
+ tooltip = '{}: {}'.format('Category' if len(category) > 1 else 'Module', category[-1])
+
+ if category == ('Core',):
+ tooltip += '\n\nThis subtree is meant for blocks included with GNU Radio (in-tree).'
+
+ elif category == (Constants.DEFAULT_BLOCK_MODULE_NAME,):
+ tooltip += '\n\n' + Constants.DEFAULT_BLOCK_MODULE_TOOLTIP
+
+ return tooltip
+
+
class BlockTreeWindow(Gtk.VBox):
"""The block selection panel."""
@@ -100,66 +113,68 @@ class BlockTreeWindow(Gtk.VBox):
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn('Blocks', renderer, text=NAME_INDEX)
self.treeview.append_column(column)
- # try to enable the tooltips (available in pygtk 2.12 and above)
- try:
- self.treeview.set_tooltip_column(DOC_INDEX)
- except:
- pass
+ self.treeview.set_tooltip_column(DOC_INDEX)
# setup sort order
column.set_sort_column_id(0)
self.treestore.set_sort_column_id(0, Gtk.SortType.ASCENDING)
# setup drag and drop
- self.treeview.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, DND_TARGETS, Gdk.DragAction.COPY)
+ self.treeview.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, Constants.DND_TARGETS, Gdk.DragAction.COPY)
self.treeview.connect('drag-data-get', self._handle_drag_get_data)
# make the scrolled window to hold the tree view
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scrolled_window.add_with_viewport(self.treeview)
- scrolled_window.set_size_request(DEFAULT_BLOCKS_WINDOW_WIDTH, -1)
+ scrolled_window.set_size_request(Constants.DEFAULT_BLOCKS_WINDOW_WIDTH, -1)
self.pack_start(scrolled_window, True, True, 0)
# map categories to iters, automatic mapping for root
self._categories = {tuple(): None}
self._categories_search = {tuple(): None}
- # add blocks and categories
- self.platform.load_block_tree(self)
self.platform.block_docstrings_loaded_callback = self.update_docs
+ self.repopulate()
def clear(self):
- self.treestore.clear();
- self._categories = {tuple(): None}
+ self.treestore.clear()
+ self._categories = {(): None}
+
+ def repopulate(self):
+ self.clear()
+ for block in six.itervalues(self.platform.blocks):
+ if block.category:
+ self.add_block(block)
+ self.expand_module_in_tree()
+
+ def expand_module_in_tree(self, module_name='Core'):
+ self.treeview.collapse_all()
+ core_module_iter = self._categories.get((module_name,))
+ if core_module_iter:
+ self.treeview.expand_row(self.treestore.get_path(core_module_iter), False)
############################################################
## Block Tree Methods
############################################################
- def add_block(self, category, block=None, treestore=None, categories=None):
+ def add_block(self, block, treestore=None, categories=None):
"""
Add a block with category to this selection window.
Add only the category when block is None.
Args:
- 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 isinstance(category, (str, six.text_type)):
- category = category.split('/')
- category = tuple(x for x in category if x) # tuple is hashable
+ treestore = treestore or self.treestore
+ categories = categories or self._categories
+
+ category = tuple(filter(str, block.category)) # tuple is hashable, remove empty cats
+
# add category and all sub categories
- 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, cat_name)
+ for level, parent_cat_name in enumerate(category, 1):
+ parent_category = category[:level]
+ if parent_category not in categories:
+ iter_ = treestore.insert_before(categories[parent_category[:-1]], None)
+ treestore.set_value(iter_, NAME_INDEX, parent_cat_name)
treestore.set_value(iter_, KEY_INDEX, '')
- treestore.set_value(iter_, DOC_INDEX, 'Category: ' + Utils.encode(cat_name))
- categories[sub_category] = iter_
+ treestore.set_value(iter_, DOC_INDEX, _format_cat_tooltip(parent_cat_name))
+ categories[parent_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())
@@ -212,7 +227,7 @@ class BlockTreeWindow(Gtk.VBox):
key = widget.get_text().lower()
if not key:
self.treeview.set_model(self.treestore)
- self.treeview.collapse_all()
+ self.expand_module_in_tree()
else:
matching_blocks = [b for b in list(self.platform.blocks.values())
if key in b.get_key().lower() or key in b.get_name().lower()]
@@ -220,8 +235,7 @@ class BlockTreeWindow(Gtk.VBox):
self.treestore_search.clear()
self._categories_search = {tuple(): None}
for block in matching_blocks:
- self.add_block(block.get_category() or 'None', block,
- self.treestore_search, self._categories_search)
+ self.add_block(block, self.treestore_search, self._categories_search)
self.treeview.set_model(self.treestore_search)
self.treeview.expand_all()
diff --git a/grc/gui/Constants.py b/grc/gui/Constants.py
index 8bb15acc09..035a7f8ca9 100644
--- a/grc/gui/Constants.py
+++ b/grc/gui/Constants.py
@@ -96,6 +96,13 @@ SCROLL_DISTANCE = 15
# How close the mouse click can be to a line and register a connection select.
LINE_SELECT_SENSITIVITY = 5
+DEFAULT_BLOCK_MODULE_TOOLTIP = """\
+This subtree holds all blocks (from OOT modules) that specify no module name. \
+The module name is the root category enclosed in square brackets.
+
+Please consider contacting OOT module maintainer for any block in here \
+and kindly ask to update their GRC Block Descriptions or Block Tree to include a module name."""
+
def update_font_size(font_size):
global PORT_SEPARATION, BLOCK_FONT, PORT_FONT, PARAM_FONT, FONT_SIZE
diff --git a/grc/main.py b/grc/main.py
index 810ac7c66f..ff0811e22a 100755
--- a/grc/main.py
+++ b/grc/main.py
@@ -15,8 +15,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-from __future__ import absolute_import
-import optparse
+import argparse
import gi
gi.require_version('Gtk', '3.0')
@@ -38,10 +37,10 @@ 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()
+ parser = argparse.ArgumentParser(
+ description=VERSION_AND_DISCLAIMER_TEMPLATE % gr.version())
+ parser.add_argument('flow_graphs', nargs='*')
+ args = parser.parse_args()
try:
Gtk.window_set_default_icon(Gtk.IconTheme().load_icon('gnuradio-grc', 256, 0))
@@ -54,6 +53,6 @@ def main():
version_parts=(gr.major_version(), gr.api_version(), gr.minor_version()),
install_prefix=gr.prefix()
)
- ActionHandler(args, platform)
+ ActionHandler(args.flow_graphs, platform)
Gtk.main()
diff --git a/grc/test/.cache/v/cache/lastfailed b/grc/test/.cache/v/cache/lastfailed
new file mode 100644
index 0000000000..9c73bf7055
--- /dev/null
+++ b/grc/test/.cache/v/cache/lastfailed
@@ -0,0 +1,3 @@
+{
+ "test_no_re_cheetah_find.py::test_nested": true
+} \ No newline at end of file