grc: pep8 formatting

Signed-off-by: Josh Morman <jmorman@gnuradio.org>
This commit is contained in:
Josh Morman 2021-11-24 12:48:20 -05:00 committed by mormj
parent edb6bc8628
commit b7828c950c
77 changed files with 999 additions and 608 deletions

View File

@ -38,7 +38,8 @@ def main(args=None):
name='GNU Radio Companion Compiler',
prefs=gr.prefs(),
version=gr.version(),
version_parts=(gr.major_version(), gr.api_version(), gr.minor_version())
version_parts=(gr.major_version(),
gr.api_version(), gr.minor_version())
)
platform.build_library()

View File

@ -94,7 +94,8 @@ def convert_block_xml(node):
data['asserts'] = [converter.to_python_dec(check_node.text)
for check_node in node.iterfind('check')] or no_value
data['templates'] = convert_templates(node, converter.to_mako, block_id) or no_value
data['templates'] = convert_templates(
node, converter.to_mako, block_id) or no_value
docs = node.findtext('doc')
if docs:
@ -103,7 +104,8 @@ def convert_block_xml(node):
data['file_format'] = current_file_format
data = OrderedDict((key, value) for key, value in data.items() if value is not no_value)
data = OrderedDict((key, value)
for key, value in data.items() if value is not no_value)
auto_hide_params_for_item_sizes(data)
return data
@ -116,8 +118,10 @@ def auto_hide_params_for_item_sizes(data):
for key in ['dtype', 'multiplicity']:
item_size_templates.append(str(port.get(key, '')))
vlen_templates.append(str(port.get('vlen', '')))
item_size_templates = ' '.join(value for value in item_size_templates if '${' in value)
vlen_templates = ' '.join(value for value in vlen_templates if '${' in value)
item_size_templates = ' '.join(
value for value in item_size_templates if '${' in value)
vlen_templates = ' '.join(
value for value in vlen_templates if '${' in value)
for param in data.get('parameters', []):
if param['id'] in item_size_templates:
@ -135,7 +139,8 @@ def convert_templates(node, convert, block_id=''):
imports = yaml.MultiLineString(imports)
templates['imports'] = imports or no_value
templates['var_make'] = convert(node.findtext('var_make') or '') or no_value
templates['var_make'] = convert(
node.findtext('var_make') or '') or no_value
make = convert(node.findtext('make') or '')
if make:
@ -160,8 +165,10 @@ def convert_param_xml(node, convert):
param['dtype'] = convert(node.findtext('type') or '')
param['default'] = node.findtext('value') or no_value
options = yaml.ListFlowing(on.findtext('key') for on in node.iterfind('option'))
option_labels = yaml.ListFlowing(on.findtext('name') for on in node.iterfind('option'))
options = yaml.ListFlowing(on.findtext('key')
for on in node.iterfind('option'))
option_labels = yaml.ListFlowing(on.findtext(
'name') for on in node.iterfind('option'))
param['options'] = options or no_value
if not all(str(o).title() == l for o, l in zip(options, option_labels)):
param['option_labels'] = option_labels
@ -192,7 +199,8 @@ def convert_port_xml(node, convert):
else:
port['dtype'] = dtype
vlen = node.findtext('vlen')
port['vlen'] = int(vlen) if vlen and vlen.isdigit() else convert(vlen) or no_value
port['vlen'] = int(vlen) if vlen and vlen.isdigit(
) else convert(vlen) or no_value
port['multiplicity'] = convert(node.findtext('nports')) or no_value
port['optional'] = bool(node.findtext('optional')) or no_value
@ -207,4 +215,5 @@ def check_mako_template(block_id, expr):
try:
Template(expr)
except Exception as error:
print(block_id, expr, type(error), error, '', sep='\n', file=sys.stderr)
print(block_id, expr, type(error), error,
'', sep='\n', file=sys.stderr)

View File

@ -18,7 +18,8 @@ cheetah_substitution = re.compile(
r'(?P<arg>[_a-zA-Z][_a-zA-Z0-9]*(?:\.[_a-zA-Z][_a-zA-Z0-9]*)?)(?P<eval>\(\))?'
r'(?(d1)\)|(?(d2)\}|(?(d3)\]|)))$'
)
cheetah_inline_if = re.compile(r'#if (?P<cond>.*) then (?P<then>.*?) ?else (?P<else>.*?) ?(#|$)')
cheetah_inline_if = re.compile(
r'#if (?P<cond>.*) then (?P<then>.*?) ?else (?P<else>.*?) ?(#|$)')
class Python(object):
@ -116,7 +117,8 @@ class Converter(object):
return spec.type(out)
def convert_hard(self, expr, spec=Python):
lines = '\n'.join(self.convert_hard_line(line, spec) for line in expr.split('\n'))
lines = '\n'.join(self.convert_hard_line(line, spec)
for line in expr.split('\n'))
if spec == Mako:
# no line-continuation before a mako control structure
lines = re.sub(r'\\\n(\s*%)', r'\n\1', lines)
@ -220,7 +222,8 @@ class Converter(object):
out.append(char)
delim_to_find = False
elif delim_to_find and char in ')]}' and extra_close(): # end of substitution
# end of substitution
elif delim_to_find and char in ')]}' and extra_close():
out.append(spec.end)
out.append(char)
delim_to_find = False
@ -235,7 +238,8 @@ class Converter(object):
out = ''.join(out)
# fix: eval stuff
out = re.sub(r'(?P<arg>' + r'|'.join(self.extended) + r')\(\)', r'\g<arg>', out)
out = re.sub(r'(?P<arg>' + r'|'.join(self.extended) +
r')\(\)', r'\g<arg>', out)
self.stats['hard'] += 1
return spec.type(out)

View File

@ -50,7 +50,8 @@ class Converter(object):
self._force = force
try:
logger.debug("Loading block cache from: {}".format(self.cache_file))
logger.debug(
"Loading block cache from: {}".format(self.cache_file))
with open(self.cache_file, encoding='utf-8') as cache_file:
self.cache = byteify(json.load(cache_file))
except (IOError, ValueError):
@ -108,7 +109,8 @@ class Converter(object):
def load_category_tree_xml(self, xml_file):
"""Validate and parse category tree file and add it to list"""
module_name = path.basename(xml_file)[:-len('block_tree.xml')].rstrip('._-')
module_name = path.basename(
xml_file)[:-len('block_tree.xml')].rstrip('._-')
yml_file = path.join(self.output_dir, module_name + '.tree.yml')
if not self.needs_conversion(xml_file, yml_file):
@ -142,7 +144,8 @@ class Converter(object):
if name.endswith(suffix):
yield path.join(root, name)
else:
logger.warning('Invalid entry in search path: {}'.format(block_path))
logger.warning(
'Invalid entry in search path: {}'.format(block_path))
def byteify(data):

View File

@ -53,7 +53,7 @@ def load_stdlib(filename, document_type_def=None):
if isinstance(filename, str):
with open(filename, 'rb') as xml_file:
data = xml_file.read().decode('utf-8')
else: # Already opened
else: # Already opened
data = filename.read().decode('utf-8')
try:

View File

@ -17,13 +17,16 @@ class Config(object):
license = __doc__.strip()
website = 'https://www.gnuradio.org/'
hier_block_lib_dir = os.environ.get('GRC_HIER_PATH', Constants.DEFAULT_HIER_BLOCK_LIB_DIR)
hier_block_lib_dir = os.environ.get(
'GRC_HIER_PATH', Constants.DEFAULT_HIER_BLOCK_LIB_DIR)
def __init__(self, version, version_parts=None, name=None, prefs=None):
self._gr_prefs = prefs if prefs else DummyPrefs()
self.version = version
self.version_parts = version_parts or version[1:].split('-', 1)[0].split('.')[:3]
self.enabled_components = self._gr_prefs.get_string('grc', 'enabled_components', '')
self.version_parts = version_parts or version[1:].split(
'-', 1)[0].split('.')[:3]
self.enabled_components = self._gr_prefs.get_string(
'grc', 'enabled_components', '')
if name:
self.name = name

View File

@ -96,12 +96,14 @@ class Connection(Element):
if source_dtype != sink_dtype and source_dtype not in ALIASES_OF.get(
sink_dtype, set()
):
self.add_error_message('Source IO type "{}" does not match sink IO type "{}".'.format(source_dtype, sink_dtype))
self.add_error_message('Source IO type "{}" does not match sink IO type "{}".'.format(
source_dtype, sink_dtype))
source_size = self.source_port.item_size
sink_size = self.sink_port.item_size
if source_size != sink_size:
self.add_error_message('Source IO size "{}" does not match sink IO size "{}".'.format(source_size, sink_size))
self.add_error_message(
'Source IO size "{}" does not match sink IO size "{}".'.format(source_size, sink_size))
##############################################
# Import/Export Methods

View File

@ -41,7 +41,7 @@ DEFAULT_DOMAIN = GR_STREAM_DOMAIN
# 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
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
PARAM_TYPE_NAMES = {
@ -50,7 +50,7 @@ PARAM_TYPE_NAMES = {
'complex_vector', 'real_vector', 'float_vector', 'int_vector',
'hex', 'string', 'bool',
'file_open', 'file_save', 'dir_select', '_multiline', '_multiline_python_external',
'id', 'stream_id','name',
'id', 'stream_id', 'name',
'gui_hint',
'import',
}
@ -89,31 +89,31 @@ GRC_COLOR_GREY = '#BDBDBD'
GRC_COLOR_WHITE = '#FFFFFF'
CORE_TYPES = ( # name, key, sizeof, color
('Complex Float 64', 'fc64', 16, GRC_COLOR_BROWN),
('Complex Float 32', 'fc32', 8, GRC_COLOR_BLUE),
('Complex Integer 64', 'sc64', 16, GRC_COLOR_LIGHT_GREEN),
('Complex Integer 32', 'sc32', 8, GRC_COLOR_GREEN),
('Complex Integer 16', 'sc16', 4, GRC_COLOR_AMBER),
('Complex Integer 8', 'sc8', 2, GRC_COLOR_PURPLE),
('Float 64', 'f64', 8, GRC_COLOR_CYAN),
('Float 32', 'f32', 4, GRC_COLOR_ORANGE),
('Integer 64', 's64', 8, GRC_COLOR_LIME),
('Integer 32', 's32', 4, GRC_COLOR_TEAL),
('Integer 16', 's16', 2, GRC_COLOR_YELLOW),
('Integer 8', 's8', 1, GRC_COLOR_PURPLE_A400),
('Bits (unpacked byte)', 'bit', 1, GRC_COLOR_PURPLE_A100),
('Async Message', 'message', 0, GRC_COLOR_GREY),
('Bus Connection', 'bus', 0, GRC_COLOR_WHITE),
('Wildcard', '', 0, GRC_COLOR_WHITE),
('Complex Float 64', 'fc64', 16, GRC_COLOR_BROWN),
('Complex Float 32', 'fc32', 8, GRC_COLOR_BLUE),
('Complex Integer 64', 'sc64', 16, GRC_COLOR_LIGHT_GREEN),
('Complex Integer 32', 'sc32', 8, GRC_COLOR_GREEN),
('Complex Integer 16', 'sc16', 4, GRC_COLOR_AMBER),
('Complex Integer 8', 'sc8', 2, GRC_COLOR_PURPLE),
('Float 64', 'f64', 8, GRC_COLOR_CYAN),
('Float 32', 'f32', 4, GRC_COLOR_ORANGE),
('Integer 64', 's64', 8, GRC_COLOR_LIME),
('Integer 32', 's32', 4, GRC_COLOR_TEAL),
('Integer 16', 's16', 2, GRC_COLOR_YELLOW),
('Integer 8', 's8', 1, GRC_COLOR_PURPLE_A400),
('Bits (unpacked byte)', 'bit', 1, GRC_COLOR_PURPLE_A100),
('Async Message', 'message', 0, GRC_COLOR_GREY),
('Bus Connection', 'bus', 0, GRC_COLOR_WHITE),
('Wildcard', '', 0, GRC_COLOR_WHITE),
)
ALIAS_TYPES = {
'complex': (8, GRC_COLOR_BLUE),
'float': (4, GRC_COLOR_ORANGE),
'int': (4, GRC_COLOR_TEAL),
'short': (2, GRC_COLOR_YELLOW),
'byte': (1, GRC_COLOR_PURPLE_A400),
'bits': (1, GRC_COLOR_PURPLE_A100),
'float': (4, GRC_COLOR_ORANGE),
'int': (4, GRC_COLOR_TEAL),
'short': (2, GRC_COLOR_YELLOW),
'byte': (1, GRC_COLOR_PURPLE_A400),
'bits': (1, GRC_COLOR_PURPLE_A100),
}
ALIASES_OF = {
@ -135,4 +135,5 @@ ALIASES_OF = {
}
TYPE_TO_SIZEOF = {key: sizeof for name, key, sizeof, color in CORE_TYPES}
TYPE_TO_SIZEOF.update((key, sizeof) for key, (sizeof, _) in ALIAS_TYPES.items())
TYPE_TO_SIZEOF.update((key, sizeof)
for key, (sizeof, _) in ALIAS_TYPES.items())

View File

@ -68,7 +68,8 @@ class FlowGraph(Element):
Returns:
a sorted list of variable blocks in order of dependency (indep -> dep)
"""
variables = [block for block in self.iter_enabled_blocks() if block.is_variable]
variables = [block for block in self.iter_enabled_blocks()
if block.is_variable]
return expr_utils.sort_objects(variables, attrgetter('name'), methodcaller('get_var_make'))
def get_parameters(self):
@ -78,7 +79,8 @@ class FlowGraph(Element):
Returns:
a list of parameterized variables
"""
parameters = [b for b in self.iter_enabled_blocks() if b.key == 'parameter']
parameters = [b for b in self.iter_enabled_blocks()
if b.key == 'parameter']
return parameters
def get_snippets(self):
@ -126,7 +128,8 @@ class FlowGraph(Element):
"""
Get a list of all ControlPort monitors
"""
monitors = [b for b in self.iter_enabled_blocks() if 'ctrlport_monitor' in b.key]
monitors = [b for b in self.iter_enabled_blocks()
if 'ctrlport_monitor' in b.key]
return monitors
def get_python_modules(self):
@ -189,7 +192,8 @@ class FlowGraph(Element):
filename=shlex.quote(file_path))
return shlex.split(run_command) if split else run_command
except Exception as e:
raise ValueError("Can't parse run command {!r}: {}".format(run_command, e))
raise ValueError(
"Can't parse run command {!r}: {}".format(run_command, e))
def get_imported_names(self):
"""
@ -241,7 +245,8 @@ class FlowGraph(Element):
# this is ok behavior, unfortunately we could be hiding other import bugs
pass
except Exception:
log.exception('Failed to evaluate import expression "{0}"'.format(expr), exc_info=True)
log.exception('Failed to evaluate import expression "{0}"'.format(
expr), exc_info=True)
pass
self.imported_names = list(namespace.keys())
@ -252,17 +257,20 @@ class FlowGraph(Element):
exec(expr, module.__dict__)
namespace[id] = module
except Exception:
log.exception('Failed to evaluate expression in module {0}'.format(id), exc_info=True)
log.exception(
'Failed to evaluate expression in module {0}'.format(id), exc_info=True)
pass
# Load parameters
np = {} # params don't know each other
for parameter_block in self.get_parameters():
try:
value = eval(parameter_block.params['value'].to_code(), namespace)
value = eval(
parameter_block.params['value'].to_code(), namespace)
np[parameter_block.name] = value
except Exception:
log.exception('Failed to evaluate parameter block {0}'.format(parameter_block.name), exc_info=True)
log.exception('Failed to evaluate parameter block {0}'.format(
parameter_block.name), exc_info=True)
pass
namespace.update(np) # Merge param namespace
@ -273,13 +281,16 @@ class FlowGraph(Element):
for variable_block in self.get_variables():
try:
variable_block.rewrite()
value = eval(variable_block.value, namespace, variable_block.namespace)
value = eval(variable_block.value, namespace,
variable_block.namespace)
namespace[variable_block.name] = value
self.namespace.update(namespace) # rewrite on subsequent blocks depends on an updated self.namespace
except TypeError: #Type Errors may happen, but that doesn't matter as they are displayed in the gui
# rewrite on subsequent blocks depends on an updated self.namespace
self.namespace.update(namespace)
except TypeError: # Type Errors may happen, but that doesn't matter as they are displayed in the gui
pass
except Exception:
log.exception('Failed to evaluate variable block {0}'.format(variable_block.name), exc_info=True)
log.exception('Failed to evaluate variable block {0}'.format(
variable_block.name), exc_info=True)
pass
self._eval_cache.clear()
@ -397,7 +408,8 @@ class FlowGraph(Element):
cwd=self.grc_file_path
)
if file_path: # grc file found. load and get block
self.parent_platform.load_and_generate_flow_graph(file_path, hier_only=True)
self.parent_platform.load_and_generate_flow_graph(
file_path, hier_only=True)
return self.new_block(block_id) # can be None
def import_data(self, data):
@ -424,7 +436,8 @@ class FlowGraph(Element):
block = (
self.new_block(block_id) or
self._build_depending_hier_block(block_id) or
self.new_block(block_id='_dummy', missing_block_id=block_id, **block_data)
self.new_block(block_id='_dummy',
missing_block_id=block_id, **block_data)
)
block.import_data(**block_data)
@ -443,7 +456,8 @@ class FlowGraph(Element):
if block.is_dummy_block:
port = block.add_missing_port(key, dir)
else:
raise LookupError('%s key %r not in %s block keys' % (dir, key, dir))
raise LookupError(
'%s key %r not in %s block keys' % (dir, key, dir))
return port
had_connect_errors = False
@ -461,8 +475,10 @@ class FlowGraph(Element):
src_port_id, snk_port_id, source_block, sink_block)
# build the connection
source_port = verify_and_get_port(src_port_id, source_block, 'source')
sink_port = verify_and_get_port(snk_port_id, sink_block, 'sink')
source_port = verify_and_get_port(
src_port_id, source_block, 'source')
sink_port = verify_and_get_port(
snk_port_id, sink_block, 'sink')
self.connect(source_port, sink_port)
@ -478,7 +494,8 @@ class FlowGraph(Element):
# Flowgraph errors depending on disabled blocks are not displayed
# in the error dialog box
# So put a message into the Property window of the dummy block
block.add_error_message('Block id "{}" not found.'.format(block.key))
block.add_error_message(
'Block id "{}" not found.'.format(block.key))
self.rewrite() # global rewrite
return had_connect_errors

View File

@ -5,7 +5,6 @@
#
import traceback
import sys
@ -43,6 +42,7 @@ def send(message):
for messenger in MESSENGERS_LIST:
messenger(_indent + message)
# register stdout by default
register_messenger(sys.stdout.write)
@ -117,7 +117,8 @@ def send_fail_save(file_path):
def send_fail_connection(msg=''):
send('>>> Error: Cannot create connection.\n' + ('\t{}\n'.format(msg) if msg else ''))
send('>>> Error: Cannot create connection.\n' +
('\t{}\n'.format(msg) if msg else ''))
def send_fail_load_preferences(prefs_file_path):

View File

@ -1 +0,0 @@

View File

@ -21,6 +21,7 @@ def register_build_in(cls):
build_ins[cls.key] = cls
return cls
from .dummy import DummyBlock
from .embedded_python import EPyBlock, EPyModule
from .virtual import VirtualSink, VirtualSource

View File

@ -79,7 +79,8 @@ def build_ports(ports_raw, direction):
port_id = port.setdefault('id', str(next(stream_port_ids)))
if port_id in port_ids:
raise Exception('Port id "{}" already exists in {}s'.format(port_id, direction))
raise Exception(
'Port id "{}" already exists in {}s'.format(port_id, direction))
port_ids.add(port_id)
ports.append(port)
@ -137,16 +138,19 @@ def _single_mako_expr(value, block_id):
return None
value = value.strip()
if not (value.startswith('${') and value.endswith('}')):
raise ValueError('{} is not a mako substitution in {}'.format(value, block_id))
raise ValueError(
'{} is not a mako substitution in {}'.format(value, block_id))
return value[2:-1].strip()
def _validate_option_attributes(param_data, block_id):
if param_data['dtype'] != 'enum':
send_warning('{} - option_attributes are for enums only, ignoring'.format(block_id))
send_warning(
'{} - option_attributes are for enums only, ignoring'.format(block_id))
del param_data['option_attributes']
else:
for key in list(param_data['option_attributes'].keys()):
if key in dir(str):
del param_data['option_attributes'][key]
send_warning('{} - option_attribute "{}" overrides str, ignoring'.format(block_id, key))
send_warning(
'{} - option_attribute "{}" overrides str, ignoring'.format(block_id, key))

View File

@ -16,12 +16,15 @@ from ..errors import TemplateError
# The utils dict contains convenience functions
# that can be called from any template
def no_quotes(string, fallback=None):
if len(string) > 2:
if str(string)[0] + str(string)[-1] in ("''", '""'):
return str(string)[1:-1]
return str(fallback if fallback else string)
utils = {'no_quotes': no_quotes}

View File

@ -22,6 +22,7 @@ from ._flags import Flags
from ..base import Element
from ..utils.descriptors import lazy_property
def _get_elem(iterable, key):
items = list(iterable)
for item in items:
@ -39,7 +40,7 @@ class Block(Element):
key = ''
label = ''
category = []
vtype = '' # This is only used for variables when we want C++ output
vtype = '' # This is only used for variables when we want C++ output
flags = Flags('')
documentation = {'': ''}
@ -67,13 +68,16 @@ class Block(Element):
if self.key == 'options':
self.params['id'].hide = 'part'
self.sinks = [port_factory(parent=self, **params) for params in self.inputs_data]
self.sources = [port_factory(parent=self, **params) for params in self.outputs_data]
self.sinks = [port_factory(parent=self, **params)
for params in self.inputs_data]
self.sources = [port_factory(parent=self, **params)
for params in self.outputs_data]
self.active_sources = [] # on rewrite
self.active_sinks = [] # on rewrite
self.states = {'state': True, 'bus_source': False, 'bus_sink': False, 'bus_structure': None}
self.states = {'state': True, 'bus_source': False,
'bus_sink': False, 'bus_structure': None}
self.block_namespace = {}
self.deprecated = self.is_deprecated()
@ -81,7 +85,8 @@ class Block(Element):
# This is a workaround to allow embedded python blocks/modules to load as there is
# currently 'cpp' in the flags by default caused by the other built-in blocks
if hasattr(self, 'cpp_templates'):
self.orig_cpp_templates = self.cpp_templates # The original template, in case we have to edit it when transpiling to C++
# The original template, in case we have to edit it when transpiling to C++
self.orig_cpp_templates = self.cpp_templates
self.current_bus_structure = {'source': None, 'sink': None}
@ -100,8 +105,8 @@ class Block(Element):
except:
return None
# region Rewrite_and_Validation
def rewrite(self):
"""
Add and remove ports to adjust for the nports.
@ -125,7 +130,8 @@ class Block(Element):
self.update_bus_logic()
# disconnect hidden ports
self.parent_flowgraph.disconnect(*[p for p in self.ports() if p.hidden])
self.parent_flowgraph.disconnect(
*[p for p in self.ports() if p.hidden])
self.active_sources = [p for p in self.sources if not p.hidden]
self.active_sinks = [p for p in self.sinks if not p.hidden]
@ -142,11 +148,12 @@ class Block(Element):
# this is ok behavior, unfortunately we could be hiding other import bugs
pass
except Exception:
self.add_error_message(f'Failed to evaluate import expression {imports!r}')
self.add_error_message(
f'Failed to evaluate import expression {imports!r}')
def update_bus_logic(self):
###############################
## Bus Logic
# Bus Logic
###############################
for direc in {'source', 'sink'}:
@ -170,13 +177,12 @@ class Block(Element):
removed_bus_connections.append(c)
ports.remove(port)
if (bus_state):
struct = self.form_bus_structure(direc)
self.current_bus_structure[direc] = struct
# Hide ports that are not part of the bus structure
#TODO: Blocks where it is desired to only have a subset
# TODO: Blocks where it is desired to only have a subset
# of ports included in the bus still has some issues
for idx, port in enumerate(ports):
if any([idx in bus for bus in self.current_bus_structure[direc]]):
@ -187,19 +193,20 @@ class Block(Element):
# Add the Bus Ports to the list of ports
for i in range(len(struct)):
# self.sinks = [port_factory(parent=self, **params) for params in self.inputs_data]
port = self.parent.parent.make_port(self, direction=direc, id=str(len(ports)), label='bus', dtype='bus', bus_struct=struct[i])
port = self.parent.parent.make_port(self, direction=direc, id=str(
len(ports)), label='bus', dtype='bus', bus_struct=struct[i])
ports.append(port)
for (saved_port, connection) in zip(removed_bus_ports, removed_bus_connections):
if port.key == saved_port.key:
self.parent_flowgraph.connections.remove(connection)
self.parent_flowgraph.connections.remove(
connection)
if saved_port.is_source:
connection.source_port = port
if saved_port.is_sink:
connection.sink_port = port
self.parent_flowgraph.connections.add(connection)
else:
self.current_bus_structure[direc] = None
@ -209,8 +216,6 @@ class Block(Element):
port.hidden = port.stored_hidden_state
port.stored_hidden_state = None
def _rewrite_nports(self, ports):
for port in ports:
if hasattr(port, 'master_port'): # Not a master port and no left-over clones
@ -218,7 +223,7 @@ class Block(Element):
port.vlen = port.master_port.vlen
continue
nports = port.multiplicity
for clone in port.clones[nports-1:]:
for clone in port.clones[nports - 1:]:
# Remove excess connections
self.parent_flowgraph.disconnect(clone)
port.remove_clone(clone)
@ -245,9 +250,11 @@ class Block(Element):
for expr in self.asserts:
try:
if not self.evaluate(expr):
self.add_error_message('Assertion "{}" failed.'.format(expr))
self.add_error_message(
'Assertion "{}" failed.'.format(expr))
except Exception:
self.add_error_message('Assertion "{}" did not evaluate.'.format(expr))
self.add_error_message(
'Assertion "{}" did not evaluate.'.format(expr))
def _validate_generate_mode_compat(self):
"""check if this is a GUI block and matches the selected generate option"""
@ -261,7 +268,8 @@ class Block(Element):
self.add_error_message("Can't generate this block in mode: {} ".format(
repr(current_generate_option)))
check_generate_mode('QT GUI', Flags.NEED_QT_GUI, ('qt_gui', 'hb_qt_gui'))
check_generate_mode('QT GUI', Flags.NEED_QT_GUI,
('qt_gui', 'hb_qt_gui'))
def _validate_output_language_compat(self):
"""check if this block supports the selected output language"""
@ -269,19 +277,23 @@ class Block(Element):
if current_output_language == 'cpp':
if 'cpp' not in self.flags:
self.add_error_message("This block does not support C++ output.")
self.add_error_message(
"This block does not support C++ output.")
if self.key == 'parameter':
if not self.params['type'].value:
self.add_error_message("C++ output requires you to choose a parameter type.")
self.add_error_message(
"C++ output requires you to choose a parameter type.")
def _validate_var_value(self):
"""or variables check the value (only if var_value is used)"""
if self.is_variable and self.value != 'value':
try:
self.parent_flowgraph.evaluate(self.value, local_namespace=self.namespace)
self.parent_flowgraph.evaluate(
self.value, local_namespace=self.namespace)
except Exception as err:
self.add_error_message('Value "{}" cannot be evaluated:\n{}'.format(self.value, err))
self.add_error_message(
'Value "{}" cannot be evaluated:\n{}'.format(self.value, err))
# endregion
# region Properties
@ -458,7 +470,7 @@ class Block(Element):
# For container types we must also determine the type of the template parameter(s)
return 'std::vector<' + get_type(str(evaluated[0]), type(evaluated[0])) + '>'
except IndexError: # empty list
except IndexError: # empty list
return 'std::vector<std::string>'
if _vtype == dict:
@ -466,9 +478,9 @@ class Block(Element):
# For container types we must also determine the type of the template parameter(s)
key = list(evaluated)[0]
val = list(evaluated.values())[0]
return 'std::map<' + get_type(str(key), type(key)) + ', ' + get_type(str(val), type(val)) +'>'
return 'std::map<' + get_type(str(key), type(key)) + ', ' + get_type(str(val), type(val)) + '>'
except IndexError: # empty dict
except IndexError: # empty dict
return 'std::map<std::string, std::string>'
else:
@ -480,12 +492,12 @@ class Block(Element):
# The r-value for these types must be transformed to create legal C++ syntax.
if self.vtype in ['bool', 'gr_complex'] or 'std::map' in self.vtype or 'std::vector' in self.vtype:
evaluated = ast.literal_eval(value)
self.cpp_templates['var_make'] = self.cpp_templates['var_make'].replace('${value}', self.get_cpp_value(evaluated))
self.cpp_templates['var_make'] = self.cpp_templates['var_make'].replace(
'${value}', self.get_cpp_value(evaluated))
if 'string' in self.vtype:
self.cpp_templates['includes'].append('#include <string>')
def get_cpp_value(self, pyval):
if type(pyval) == int or type(pyval) == float:
@ -494,8 +506,10 @@ class Block(Element):
# Check for PI and replace with C++ constant
pi_re = r'^(math|numpy|np|scipy|sp)\.pi$'
if re.match(pi_re, str(pyval)):
val_str = re.sub(pi_re, 'boost::math::constants::pi<double>()', val_str)
self.cpp_templates['includes'].append('#include <boost/math/constants/constants.hpp>')
val_str = re.sub(
pi_re, 'boost::math::constants::pi<double>()', val_str)
self.cpp_templates['includes'].append(
'#include <boost/math/constants/constants.hpp>')
return str(pyval)
@ -503,7 +517,8 @@ class Block(Element):
return str(pyval)[0].lower() + str(pyval)[1:]
elif type(pyval) == complex:
self.cpp_templates['includes'].append('#include <gnuradio/gr_complex.h>')
self.cpp_templates['includes'].append(
'#include <gnuradio/gr_complex.h>')
evaluated = ast.literal_eval(str(pyval).strip())
return '{' + str(evaluated.real) + ', ' + str(evaluated.imag) + '}'
@ -523,7 +538,8 @@ class Block(Element):
self.cpp_templates['includes'].append('#include <map>')
val_str = '{'
for key in pyval:
val_str += '{' + self.get_cpp_value(key) + ', ' + self.get_cpp_value(pyval[key]) + '}, '
val_str += '{' + self.get_cpp_value(key) + \
', ' + self.get_cpp_value(pyval[key]) + '}, '
if len(val_str) > 1:
# truncate to trim superfluous ', ' from the end
@ -535,7 +551,6 @@ class Block(Element):
self.cpp_templates['includes'].append('#include <string>')
return '"' + pyval + '"'
def is_virtual_sink(self):
return self.key == 'virtual_sink'
@ -561,8 +576,8 @@ class Block(Element):
print(exception.message)
return False
# Block bypassing
def get_bypassed(self):
"""
Check if the block is bypassed
@ -618,7 +633,8 @@ class Block(Element):
@property
def namespace(self):
# update block namespace
self.block_namespace.update({key:param.get_evaluated() for key, param in self.params.items()})
self.block_namespace.update(
{key: param.get_evaluated() for key, param in self.params.items()})
return self.block_namespace
@property
@ -721,7 +737,7 @@ class Block(Element):
struct = [range(len(ports))]
# struct = list(range(len(ports)))
#TODO for more complicated port structures, this code is needed but not working yet
# TODO for more complicated port structures, this code is needed but not working yet
if any([p.multiplicity for p in ports]):
structlet = []
last = 0
@ -736,8 +752,8 @@ class Block(Element):
continue
if p.multiplicity > 1:
cnt = p.multiplicity-1
structlet.append([idx+j for j in range(p.multiplicity)])
cnt = p.multiplicity - 1
structlet.append([idx + j for j in range(p.multiplicity)])
else:
structlet.append([idx])

View File

@ -9,6 +9,7 @@ from . import Block, register_build_in
from ._build import build_params
@register_build_in
class DummyBlock(Block):
@ -19,12 +20,14 @@ class DummyBlock(Block):
def __init__(self, parent, missing_block_id, parameters, **_):
self.key = missing_block_id
self.parameters_data = build_params([], False, False, self.flags, self.key)
self.parameters_data = build_params(
[], False, False, self.flags, self.key)
super(DummyBlock, self).__init__(parent=parent)
param_factory = self.parent_platform.make_param
for param_id in parameters:
self.params.setdefault(param_id, param_factory(parent=self, id=param_id, dtype='string'))
self.params.setdefault(param_id, param_factory(
parent=self, id=param_id, dtype='string'))
def is_valid(self):
return False

View File

@ -122,7 +122,8 @@ class EPyBlock(Block):
self.label = blk_io.name or blk_io.cls
self.documentation = {'': blk_io.doc}
self.module_name = "{}_{}".format(self.parent_flowgraph.get_option("id"), self.name)
self.module_name = "{}_{}".format(
self.parent_flowgraph.get_option("id"), self.name)
self.templates['imports'] = 'import {} as {} # embedded python block'.format(
self.module_name, self.name)
self.templates['make'] = '{mod}.{cls}({args})'.format(
@ -131,7 +132,7 @@ class EPyBlock(Block):
args=', '.join('{0}=${{ {0} }}'.format(key) for key, _ in blk_io.params))
self.templates['callbacks'] = [
'{0} = ${{ {0} }}'.format(attr) for attr in blk_io.callbacks
]
]
self._update_params(blk_io.params)
self._update_ports('in', self.sinks, blk_io.sinks, 'sink')
@ -195,7 +196,8 @@ class EPyBlock(Block):
def validate(self):
super(EPyBlock, self).validate()
if self._epy_reload_error:
self.params['_source_code'].add_error_message(str(self._epy_reload_error))
self.params['_source_code'].add_error_message(
str(self._epy_reload_error))
@register_build_in
@ -240,6 +242,7 @@ class EPyModule(Block):
def rewrite(self):
super(EPyModule, self).rewrite()
self.module_name = "{}_{}".format(self.parent_flowgraph.get_option("id"), self.name)
self.module_name = "{}_{}".format(
self.parent_flowgraph.get_option("id"), self.name)
self.templates['imports'] = 'import {} as {} # embedded python module'.format(
self.module_name, self.name)

View File

@ -21,7 +21,8 @@ class VirtualSink(Block):
flags.set('cpp')
parameters_data = build_params(
params_raw=[dict(label='Stream ID', id='stream_id', dtype='stream_id')],
params_raw=[
dict(label='Stream ID', id='stream_id', dtype='stream_id')],
have_inputs=False, have_outputs=False, flags=flags, block_id=key
)
inputs_data = [dict(domain='stream', dtype='', direction='sink', id="0")]
@ -45,10 +46,12 @@ class VirtualSource(Block):
flags.set('cpp')
parameters_data = build_params(
params_raw=[dict(label='Stream ID', id='stream_id', dtype='stream_id')],
params_raw=[
dict(label='Stream ID', id='stream_id', dtype='stream_id')],
have_inputs=False, have_outputs=False, flags=flags, block_id=key
)
outputs_data = [dict(domain='stream', dtype='', direction='source', id="0")]
outputs_data = [dict(domain='stream', dtype='',
direction='source', id="0")]
def __init__(self, parent, **kwargs):
super(VirtualSource, self).__init__(parent, **kwargs)

View File

@ -16,7 +16,7 @@ logger = logging.getLogger(__name__)
class Cache(object):
def __init__(self, filename, version = None):
def __init__(self, filename, version=None):
self.cache_file = filename
self.version = version
self.cache = {}
@ -57,7 +57,7 @@ class Cache(object):
if modtime <= self._converter_mtime:
try:
cached = self.cache[filename]
if int(cached["cached-at"]+0.5) >= modtime:
if int(cached["cached-at"] + 0.5) >= modtime:
return cached["data"]
logger.info(f"Cache for {filename} outdated, loading yaml")
except KeyError:

View File

@ -5,7 +5,6 @@
#
class GRCError(Exception):
"""Generic error class"""

View File

@ -8,6 +8,7 @@
from ..utils import expr_utils
from operator import methodcaller, attrgetter
class FlowGraphProxy(object): # TODO: move this in a refactored Generator
def __init__(self, fg):
@ -59,8 +60,8 @@ class FlowGraphProxy(object): # TODO: move this in a refactored Generator
'label': str(pad.params['label'].get_evaluated()),
'type': str(pad.params['type'].get_evaluated()),
'vlen': str(pad.params['vlen'].get_value()),
'size': type_param.options.attributes[type_param.get_value()]['size'],
'cpp_size': type_param.options.attributes[type_param.get_value()]['cpp_size'],
'size': type_param.options.attributes[type_param.get_value()]['size'],
'cpp_size': type_param.options.attributes[type_param.get_value()]['cpp_size'],
'optional': bool(pad.params['optional'].get_evaluated()),
}
num_ports = pad.params['num_streams'].get_evaluated()
@ -127,7 +128,8 @@ class FlowGraphProxy(object): # TODO: move this in a refactored Generator
Returns:
a sorted list of variable blocks in order of dependency (indep -> dep)
"""
variables = [block for block in self.iter_enabled_blocks() if block.is_variable]
variables = [block for block in self.iter_enabled_blocks()
if block.is_variable]
return expr_utils.sort_objects(variables, attrgetter('name'), methodcaller('get_cpp_var_make'))
def includes(self):
@ -157,6 +159,7 @@ class FlowGraphProxy(object): # TODO: move this in a refactored Generator
"""
return [block.cpp_templates.render('packages') for block in self.iter_enabled_blocks() if not (block.is_virtual_sink() or block.is_virtual_source())]
def get_hier_block_io(flow_graph, direction, domain=None):
"""
Get a list of io ports for this flow graph.
@ -172,7 +175,7 @@ def get_hier_block_io(flow_graph, direction, domain=None):
'label': str(pad.params['label'].get_evaluated()),
'type': str(pad.params['type'].get_evaluated()),
'vlen': str(pad.params['vlen'].get_value()),
'size': type_param.options.attributes[type_param.get_value()]['size'],
'size': type_param.options.attributes[type_param.get_value()]['size'],
'optional': bool(pad.params['optional'].get_evaluated()),
}
num_ports = pad.params['num_streams'].get_evaluated()

View File

@ -5,7 +5,6 @@
#
from .hier_block import HierBlockGenerator, QtHierBlockGenerator
from .top_block import TopBlockGenerator
from .cpp_top_block import CppTopBlockGenerator

View File

@ -8,6 +8,7 @@ from .cpp_top_block import CppTopBlockGenerator
from .. import Constants
from ..io import yaml
class CppHierBlockGenerator(CppTopBlockGenerator):
"""Extends the top block generator to also generate a block YML file"""
@ -27,7 +28,8 @@ class CppHierBlockGenerator(CppTopBlockGenerator):
os.mkdir(hier_block_lib_dir)
self._mode = Constants.HIER_BLOCK_FILE_MODE
self.file_path = os.path.join(hier_block_lib_dir, self._flow_graph.get_option('id'))
self.file_path = os.path.join(
hier_block_lib_dir, self._flow_graph.get_option('id'))
self.file_path_yml = self.file_path + '.block.yml'
def write(self):
@ -55,7 +57,6 @@ class CppHierBlockGenerator(CppTopBlockGenerator):
# Windows only supports S_IREAD and S_IWRITE, other flags are ignored
os.chmod(self.file_path_yml, self._mode)
def _build_block_n_from_flow_graph_io(self):
"""
Generate a block YML nested data from the flow graph IO
@ -136,7 +137,8 @@ class CppHierBlockGenerator(CppTopBlockGenerator):
t_cpp = data['cpp_templates'] = collections.OrderedDict()
t_cpp['includes'] = []
t_cpp['includes'].append('#include "{id}/{id}.hpp"'.format(id=self._flow_graph.get_option('id')))
t_cpp['includes'].append(
'#include "{id}/{id}.hpp"'.format(id=self._flow_graph.get_option('id')))
# Make data
if parameters:
@ -147,7 +149,8 @@ class CppHierBlockGenerator(CppTopBlockGenerator):
),
)
else:
t_cpp['make'] = 'this->${{id}} = {cls}_sptr(make_{cls}());'.format(cls=block_id)
t_cpp['make'] = 'this->${{id}} = {cls}_sptr(make_{cls}());'.format(
cls=block_id)
t_cpp['declarations'] = '{cls}_sptr ${{id}};'.format(cls=block_id)
# Callback data
@ -206,7 +209,8 @@ def get_hier_block_io(flow_graph, direction, domain=None):
Returns a list of dicts with: type, label, vlen, size, optional
"""
pads = flow_graph.get_pad_sources() if direction == 'inputs' else flow_graph.get_pad_sinks()
pads = flow_graph.get_pad_sources(
) if direction == 'inputs' else flow_graph.get_pad_sinks()
for pad in pads:
for port in (pad.sources if direction == 'inputs' else pad.sinks):

View File

@ -38,7 +38,8 @@ class CppTopBlockGenerator(object):
"""
self._flow_graph = FlowGraphProxy(flow_graph)
self._generate_options = self._flow_graph.get_option('generate_options')
self._generate_options = self._flow_graph.get_option(
'generate_options')
self._mode = TOP_BLOCK_FILE_MODE
# Handle the case where the directory is read-only
@ -48,25 +49,26 @@ class CppTopBlockGenerator(object):
filename = self._flow_graph.get_option('id')
self.file_path = os.path.join(output_dir, filename)
self.output_dir = output_dir
def _warnings(self):
throttling_blocks = [b for b in self._flow_graph.get_enabled_blocks()
if b.flags.throttle]
if b.flags.throttle]
if not throttling_blocks and not self._generate_options.startswith('hb'):
Messages.send_warning("This flow graph may not have flow control: "
"no audio or RF hardware blocks found. "
"Add a Misc->Throttle block to your flow "
"graph to avoid CPU congestion.")
"no audio or RF hardware blocks found. "
"Add a Misc->Throttle block to your flow "
"graph to avoid CPU congestion.")
if len(throttling_blocks) > 1:
keys = set([b.key for b in throttling_blocks])
if len(keys) > 1 and 'blocks_throttle' in keys:
Messages.send_warning("This flow graph contains a throttle "
"block and another rate limiting block, "
"e.g. a hardware source or sink. "
"This is usually undesired. Consider "
"removing the throttle block.")
"block and another rate limiting block, "
"e.g. a hardware source or sink. "
"This is usually undesired. Consider "
"removing the throttle block.")
deprecated_block_keys = {b.name for b in self._flow_graph.get_enabled_blocks() if b.flags.deprecated}
deprecated_block_keys = {
b.name for b in self._flow_graph.get_enabled_blocks() if b.flags.deprecated}
for key in deprecated_block_keys:
Messages.send_warning("The block {!r} is deprecated.".format(key))
@ -76,7 +78,8 @@ class CppTopBlockGenerator(object):
fg = self._flow_graph
platform = fg.parent
self.title = fg.get_option('title') or fg.get_option('id').replace('_', ' ').title()
self.title = fg.get_option('title') or fg.get_option(
'id').replace('_', ' ').title()
variables = fg.get_cpp_variables()
parameters = fg.get_parameters()
monitors = fg.get_monitors()
@ -119,7 +122,8 @@ class CppTopBlockGenerator(object):
Returns:
a string of C++ code
"""
file_path = self.file_path + '/' + self._flow_graph.get_option('id') + '.cpp'
file_path = self.file_path + '/' + \
self._flow_graph.get_option('id') + '.cpp'
output = []
@ -132,12 +136,12 @@ class CppTopBlockGenerator(object):
**self.namespace
)
# strip trailing white-space
flow_graph_code = "\n".join(line.rstrip() for line in flow_graph_code.split("\n"))
flow_graph_code = "\n".join(line.rstrip()
for line in flow_graph_code.split("\n"))
output.append((file_path, flow_graph_code))
return output
def _build_cpp_header_code_from_template(self):
"""
Convert the flow graph to a C++ header file.
@ -145,7 +149,8 @@ class CppTopBlockGenerator(object):
Returns:
a string of C++ code
"""
file_path = self.file_path + '/' + self._flow_graph.get_option('id') + '.hpp'
file_path = self.file_path + '/' + \
self._flow_graph.get_option('id') + '.hpp'
output = []
@ -158,7 +163,8 @@ class CppTopBlockGenerator(object):
**self.namespace
)
# strip trailing white-space
flow_graph_code = "\n".join(line.rstrip() for line in flow_graph_code.split("\n"))
flow_graph_code = "\n".join(line.rstrip()
for line in flow_graph_code.split("\n"))
output.append((file_path, flow_graph_code))
return output
@ -175,7 +181,7 @@ class CppTopBlockGenerator(object):
cmake_tuples = []
cmake_opt = self._flow_graph.get_option("cmake_opt")
cmake_opt = " " + cmake_opt # To make sure we get rid of the "-D"s when splitting
cmake_opt = " " + cmake_opt # To make sure we get rid of the "-D"s when splitting
for opt_string in cmake_opt.split(" -D"):
opt_string = opt_string.strip()
@ -196,7 +202,8 @@ class CppTopBlockGenerator(object):
**self.namespace
)
# strip trailing white-space
flow_graph_code = "\n".join(line.rstrip() for line in flow_graph_code.split("\n"))
flow_graph_code = "\n".join(line.rstrip()
for line in flow_graph_code.split("\n"))
output.append((file_path, flow_graph_code))
return output
@ -255,7 +262,8 @@ class CppTopBlockGenerator(object):
def _get_block_sort_text(block):
code = block.cpp_templates.render('declarations')
try:
code += block.params['gui_hint'].get_value() # Newer gui markup w/ qtgui
# Newer gui markup w/ qtgui
code += block.params['gui_hint'].get_value()
except:
pass
return code
@ -265,7 +273,8 @@ class CppTopBlockGenerator(object):
if b.enabled and not (b.get_bypassed() or b.is_import or b in parameters or b.key == 'options' or b.is_virtual_source() or b.is_virtual_sink())
]
blocks = expr_utils.sort_objects(blocks, operator.attrgetter('name'), _get_block_sort_text)
blocks = expr_utils.sort_objects(
blocks, operator.attrgetter('name'), _get_block_sort_text)
blocks_make = []
for block in blocks:
translations = block.cpp_templates.render('translations')
@ -279,7 +288,8 @@ class CppTopBlockGenerator(object):
{r"gr\.sizeof_([\w_]+)": r"sizeof(\1)"}
)
for key in translations:
make = re.sub(key.replace("\\\\", "\\"), translations[key], make)
make = re.sub(key.replace("\\\\", "\\"),
translations[key], make)
declarations = declarations.replace(key, translations[key])
if make:
blocks_make.append((block, make, declarations))
@ -293,7 +303,8 @@ class CppTopBlockGenerator(object):
fg = self._flow_graph
variables = fg.get_cpp_variables()
type_translation = {'complex': 'gr_complex', 'real': 'double', 'float': 'float', 'int': 'int', 'complex_vector': 'std::vector<gr_complex>', 'real_vector': 'std::vector<double>', 'float_vector': 'std::vector<float>', 'int_vector': 'std::vector<int>', 'string': 'std::string', 'bool': 'bool'}
type_translation = {'complex': 'gr_complex', 'real': 'double', 'float': 'float', 'int': 'int', 'complex_vector': 'std::vector<gr_complex>',
'real_vector': 'std::vector<double>', 'float_vector': 'std::vector<float>', 'int_vector': 'std::vector<int>', 'string': 'std::string', 'bool': 'bool'}
# If the type is explcitly specified, translate to the corresponding C++ type
for var in list(variables):
if var.params['value'].dtype != 'raw':
@ -304,26 +315,28 @@ class CppTopBlockGenerator(object):
# Create an executable fragment of code containing all 'raw' variables in
# order to infer the lvalue types.
#
# Note that this differs from using ast.literal_eval() as literal_eval evaluates one
# variable at a time. The code fragment below evaluates all variables together which
# Note that this differs from using ast.literal_eval() as literal_eval evaluates one
# variable at a time. The code fragment below evaluates all variables together which
# allows the variables to reference each other (i.e. a = b * c).
prog = 'def get_decl_types():\n'
prog += '\tvar_types = {}\n'
for var in variables:
prog += '\t' + str(var.params['id'].value) + '=' + str(var.params['value'].value) + '\n'
prog += '\tvar_types = {}\n';
prog += '\t' + str(var.params['id'].value) + \
'=' + str(var.params['value'].value) + '\n'
prog += '\tvar_types = {}\n'
for var in variables:
prog += '\tvar_types[\'' + str(var.params['id'].value) + '\'] = type(' + str(var.params['id'].value) + ')\n'
prog += '\tvar_types[\'' + str(var.params['id'].value) + \
'\'] = type(' + str(var.params['id'].value) + ')\n'
prog += '\treturn var_types'
# Execute the code fragment in a separate namespace and retrieve the lvalue types
var_types = {}
namespace = {}
try:
exec(prog, namespace)
var_types = namespace['get_decl_types']()
exec(prog, namespace)
var_types = namespace['get_decl_types']()
except Exception as excp:
print('Failed to get parameter lvalue types: %s' %(excp))
print('Failed to get parameter lvalue types: %s' % (excp))
# Format the rvalue of each variable expression
for var in variables:
@ -334,20 +347,22 @@ class CppTopBlockGenerator(object):
parameters = fg.get_parameters()
for param in parameters:
type_translation = {'eng_float' : 'double', 'intx' : 'int', 'str' : 'std::string', 'complex': 'gr_complex'};
type_translation = {'eng_float': 'double', 'intx': 'int',
'str': 'std::string', 'complex': 'gr_complex'}
param.vtype = type_translation[param.params['type'].value]
if param.vtype == 'gr_complex':
evaluated = ast.literal_eval(param.params['value'].value.strip())
cpp_cmplx = '{' + str(evaluated.real) + ', ' + str(evaluated.imag) + '}'
evaluated = ast.literal_eval(
param.params['value'].value.strip())
cpp_cmplx = '{' + str(evaluated.real) + \
', ' + str(evaluated.imag) + '}'
# Update the 'var_make' entry in the cpp_templates dictionary
d = param.cpp_templates
cpp_expr = d['var_make'].replace('${value}', cpp_cmplx)
d.update({'var_make':cpp_expr})
d.update({'var_make': cpp_expr})
param.cpp_templates = d
def _callbacks(self):
fg = self._flow_graph
variables = fg.get_cpp_variables()
@ -361,16 +376,19 @@ class CppTopBlockGenerator(object):
callbacks_all = []
for block in fg.iter_enabled_blocks():
if not (block.is_virtual_sink() or block.is_virtual_source()):
callbacks_all.extend(expr_utils.expr_replace(cb, replace_dict) for cb in block.get_cpp_callbacks())
callbacks_all.extend(expr_utils.expr_replace(
cb, replace_dict) for cb in block.get_cpp_callbacks())
# Map var id to callbacks
def uses_var_id(callback):
used = expr_utils.get_variable_dependencies(callback, [var_id])
return used and ('this->' + var_id in callback) # callback might contain var_id itself
# callback might contain var_id itself
return used and ('this->' + var_id in callback)
callbacks = {}
for var_id in var_ids:
callbacks[var_id] = [callback for callback in callbacks_all if uses_var_id(callback)]
callbacks[var_id] = [
callback for callback in callbacks_all if uses_var_id(callback)]
return callbacks
@ -402,14 +420,17 @@ class CppTopBlockGenerator(object):
# Get the virtual blocks and resolve their connections
connection_factory = fg.parent_platform.Connection
virtual_source_connections = [c for c in connections if isinstance(c.source_block, blocks.VirtualSource)]
virtual_source_connections = [c for c in connections if isinstance(
c.source_block, blocks.VirtualSource)]
for connection in virtual_source_connections:
sink = connection.sink_port
for source in connection.source_port.resolve_virtual_source():
resolved = connection_factory(fg.orignal_flowgraph, source, sink)
resolved = connection_factory(
fg.orignal_flowgraph, source, sink)
connections.append(resolved)
virtual_connections = [c for c in connections if (isinstance(c.source_block, blocks.VirtualSource) or isinstance(c.sink_block, blocks.VirtualSink))]
virtual_connections = [c for c in connections if (isinstance(
c.source_block, blocks.VirtualSource) or isinstance(c.sink_block, blocks.VirtualSink))]
for connection in virtual_connections:
# Remove the virtual connection
connections.remove(connection)
@ -423,7 +444,8 @@ class CppTopBlockGenerator(object):
for block in bypassed_blocks:
# Get the upstream connection (off of the sink ports)
# Use *connections* not get_connections()
source_connection = [c for c in connections if c.sink_port == block.sinks[0]]
source_connection = [
c for c in connections if c.sink_port == block.sinks[0]]
# The source connection should never have more than one element.
assert (len(source_connection) == 1)
@ -435,7 +457,8 @@ class CppTopBlockGenerator(object):
if not sink.enabled:
# Ignore disabled connections
continue
connection = connection_factory(fg.orignal_flowgraph, source_port, sink.sink_port)
connection = connection_factory(
fg.orignal_flowgraph, source_port, sink.sink_port)
connections.append(connection)
# Remove this sink connection
connections.remove(sink)
@ -451,7 +474,8 @@ class CppTopBlockGenerator(object):
template = templates[con.type]
if con.source_port.dtype != 'bus':
code = template.render(make_port_sig=make_port_sig, source=con.source_port, sink=con.sink_port)
code = template.render(
make_port_sig=make_port_sig, source=con.source_port, sink=con.sink_port)
if not self._generate_options.startswith('hb'):
code = 'this->tb->' + code
rendered.append(code)
@ -470,7 +494,8 @@ class CppTopBlockGenerator(object):
hidden_portb = portb.parent.sinks[port_num]
connection = fg.parent_platform.Connection(
parent=self, source=hidden_porta, sink=hidden_portb)
code = template.render(make_port_sig=make_port_sig, source=hidden_porta, sink=hidden_portb)
code = template.render(
make_port_sig=make_port_sig, source=hidden_porta, sink=hidden_portb)
if not self._generate_options.startswith('hb'):
code = 'this->tb->' + code
rendered.append(code)

View File

@ -128,7 +128,6 @@ class HierBlockGenerator(TopBlockGenerator):
'set_{key}(${{ {key} }})'.format(key=param_block.name) for param_block in parameters
]
# Documentation
data['documentation'] = "\n".join(field for field in (
self._flow_graph.get_option('author'),
@ -178,7 +177,8 @@ def get_hier_block_io(flow_graph, direction, domain=None):
Returns a list of blocks
"""
pads = flow_graph.get_pad_sources() if direction == 'inputs' else flow_graph.get_pad_sinks()
pads = flow_graph.get_pad_sources(
) if direction == 'inputs' else flow_graph.get_pad_sinks()
for pad in pads:
for port in (pad.sources if direction == 'inputs' else pad.sinks):

View File

@ -30,7 +30,8 @@ class TopBlockGenerator(object):
"""
self._flow_graph = FlowGraphProxy(flow_graph)
self._generate_options = self._flow_graph.get_option('generate_options')
self._generate_options = self._flow_graph.get_option(
'generate_options')
self._mode = TOP_BLOCK_FILE_MODE
# Handle the case where the directory is read-only
@ -58,7 +59,8 @@ class TopBlockGenerator(object):
"This is usually undesired. Consider "
"removing the throttle block.")
deprecated_block_keys = {b.name for b in self._flow_graph.get_enabled_blocks() if b.flags.deprecated}
deprecated_block_keys = {
b.name for b in self._flow_graph.get_enabled_blocks() if b.flags.deprecated}
for key in deprecated_block_keys:
Messages.send_warning("The block {!r} is deprecated.".format(key))
@ -67,7 +69,8 @@ class TopBlockGenerator(object):
self._warnings()
fg = self._flow_graph
self.title = fg.get_option('title') or fg.get_option('id').replace('_', ' ').title()
self.title = fg.get_option('title') or fg.get_option(
'id').replace('_', ' ').title()
variables = fg.get_variables()
parameters = fg.get_parameters()
monitors = fg.get_monitors()
@ -97,7 +100,8 @@ class TopBlockGenerator(object):
fg = self._flow_graph
platform = fg.parent
title = fg.get_option('title') or fg.get_option('id').replace('_', ' ').title()
title = fg.get_option('title') or fg.get_option(
'id').replace('_', ' ').title()
variables = fg.get_variables()
parameters = fg.get_parameters()
monitors = fg.get_monitors()
@ -110,7 +114,8 @@ class TopBlockGenerator(object):
else:
continue
file_path = os.path.join(self.output_dir, block.module_name + ".py")
file_path = os.path.join(
self.output_dir, block.module_name + ".py")
output.append((file_path, src))
self.namespace = {
@ -131,7 +136,8 @@ class TopBlockGenerator(object):
**self.namespace
)
# strip trailing white-space
flow_graph_code = "\n".join(line.rstrip() for line in flow_graph_code.split("\n"))
flow_graph_code = "\n".join(line.rstrip()
for line in flow_graph_code.split("\n"))
output.append((self.file_path, flow_graph_code))
return output
@ -142,7 +148,8 @@ class TopBlockGenerator(object):
seen = set()
output = []
need_path_hack = any(imp.endswith("# grc-generated hier_block") for imp in imports)
need_path_hack = any(imp.endswith(
"# grc-generated hier_block") for imp in imports)
if need_path_hack:
output.insert(0, textwrap.dedent("""\
import os
@ -184,7 +191,8 @@ class TopBlockGenerator(object):
def _get_block_sort_text(block):
code = block.templates.render('make').replace(block.name, ' ')
try:
code += block.params['gui_hint'].get_value() # Newer gui markup w/ qtgui
# Newer gui markup w/ qtgui
code += block.params['gui_hint'].get_value()
except KeyError:
# No gui hint
pass
@ -195,7 +203,8 @@ class TopBlockGenerator(object):
if b.enabled and not (b.get_bypassed() or b.is_import or b.is_snippet or b in parameters or b.key == 'options')
]
blocks = expr_utils.sort_objects(blocks, operator.attrgetter('name'), _get_block_sort_text)
blocks = expr_utils.sort_objects(
blocks, operator.attrgetter('name'), _get_block_sort_text)
blocks_make = []
for block in blocks:
make = block.templates.render('make')
@ -217,16 +226,19 @@ class TopBlockGenerator(object):
callbacks_all = []
for block in fg.iter_enabled_blocks():
callbacks_all.extend(expr_utils.expr_replace(cb, replace_dict) for cb in block.get_callbacks())
callbacks_all.extend(expr_utils.expr_replace(
cb, replace_dict) for cb in block.get_callbacks())
# Map var id to callbacks
def uses_var_id(callback):
used = expr_utils.get_variable_dependencies(callback, [var_id])
return used and (('self.' + var_id in callback) or ('this->' + var_id in callback)) # callback might contain var_id itself
# callback might contain var_id itself
return used and (('self.' + var_id in callback) or ('this->' + var_id in callback))
callbacks = {}
for var_id in var_ids:
callbacks[var_id] = [callback for callback in callbacks_all if uses_var_id(callback)]
callbacks[var_id] = [
callback for callback in callbacks_all if uses_var_id(callback)]
return callbacks
@ -253,14 +265,17 @@ class TopBlockGenerator(object):
# Get the virtual blocks and resolve their connections
connection_factory = fg.parent_platform.Connection
virtual_source_connections = [c for c in connections if isinstance(c.source_block, blocks.VirtualSource)]
virtual_source_connections = [c for c in connections if isinstance(
c.source_block, blocks.VirtualSource)]
for connection in virtual_source_connections:
sink = connection.sink_port
for source in connection.source_port.resolve_virtual_source():
resolved = connection_factory(fg.orignal_flowgraph, source, sink)
resolved = connection_factory(
fg.orignal_flowgraph, source, sink)
connections.append(resolved)
virtual_connections = [c for c in connections if (isinstance(c.source_block, blocks.VirtualSource) or isinstance(c.sink_block, blocks.VirtualSink))]
virtual_connections = [c for c in connections if (isinstance(
c.source_block, blocks.VirtualSource) or isinstance(c.sink_block, blocks.VirtualSink))]
for connection in virtual_connections:
# Remove the virtual connection
connections.remove(connection)
@ -274,7 +289,8 @@ class TopBlockGenerator(object):
for block in bypassed_blocks:
# Get the upstream connection (off of the sink ports)
# Use *connections* not get_connections()
source_connection = [c for c in connections if c.sink_port == block.sinks[0]]
source_connection = [
c for c in connections if c.sink_port == block.sinks[0]]
# The source connection should never have more than one element.
assert (len(source_connection) == 1)
@ -286,7 +302,8 @@ class TopBlockGenerator(object):
if not sink.enabled:
# Ignore disabled connections
continue
connection = connection_factory(fg.orignal_flowgraph, source_port, sink.sink_port)
connection = connection_factory(
fg.orignal_flowgraph, source_port, sink.sink_port)
connections.append(connection)
# Remove this sink connection
connections.remove(sink)
@ -301,7 +318,8 @@ class TopBlockGenerator(object):
for con in sorted(connections, key=by_domain_and_blocks):
template = templates[con.type]
if con.source_port.dtype != 'bus':
code = template.render(make_port_sig=make_port_sig, source=con.source_port, sink=con.sink_port)
code = template.render(
make_port_sig=make_port_sig, source=con.source_port, sink=con.sink_port)
rendered.append(code)
else:
# Bus ports need to iterate over the underlying connections and then render
@ -318,10 +336,8 @@ class TopBlockGenerator(object):
hidden_portb = portb.parent.sinks[port_num_b]
connection = fg.parent_platform.Connection(
parent=self, source=hidden_porta, sink=hidden_portb)
code = template.render(make_port_sig=make_port_sig, source=hidden_porta, sink=hidden_portb)
code = template.render(
make_port_sig=make_port_sig, source=hidden_porta, sink=hidden_portb)
rendered.append(code)
return rendered

View File

@ -11,6 +11,7 @@ import yaml
from ..params.param import attributed_str
class GRCDumper(yaml.SafeDumper):
@classmethod
def add(cls, data_type):
@ -21,7 +22,8 @@ class GRCDumper(yaml.SafeDumper):
def represent_ordered_mapping(self, data):
value = []
node = yaml.MappingNode(u'tag:yaml.org,2002:map', value, flow_style=False)
node = yaml.MappingNode(u'tag:yaml.org,2002:map',
value, flow_style=False)
if self.alias_key is not None:
self.represented_objects[self.alias_key] = node
@ -62,7 +64,8 @@ class MultiLineString(str):
GRCDumper.add_representer(OrderedDict, GRCDumper.represent_ordered_mapping)
GRCDumper.add_representer(OrderedDictFlowing, GRCDumper.represent_ordered_mapping_flowing)
GRCDumper.add_representer(
OrderedDictFlowing, GRCDumper.represent_ordered_mapping_flowing)
GRCDumper.add_representer(ListFlowing, GRCDumper.represent_list_flowing)
GRCDumper.add_representer(tuple, GRCDumper.represent_list)
GRCDumper.add_representer(MultiLineString, GRCDumper.represent_ml_string)
@ -71,7 +74,8 @@ GRCDumper.add_representer(attributed_str, GRCDumper.represent_str)
def dump(data, stream=None, **kwargs):
config = dict(stream=stream, default_flow_style=False, indent=4, Dumper=GRCDumper)
config = dict(stream=stream, default_flow_style=False,
indent=4, Dumper=GRCDumper)
config.update(kwargs)
return yaml.dump_all([data], **config)

View File

@ -16,7 +16,8 @@ from .. import Constants
ID_BLACKLIST = ['self', 'default'] + dir(builtins)
try:
from gnuradio import gr
ID_BLACKLIST.extend(attr for attr in dir(gr.top_block()) if not attr.startswith('_'))
ID_BLACKLIST.extend(attr for attr in dir(
gr.top_block()) if not attr.startswith('_'))
except (ImportError, AttributeError):
pass
@ -32,12 +33,13 @@ def validates(*dtypes):
return func
return decorator
class ValidateError(Exception):
"""Raised by validate functions"""
@validates('id')
def validate_block_id(param,black_listed_ids):
def validate_block_id(param, black_listed_ids):
value = param.value
# Can python use this as a variable?
@ -45,10 +47,11 @@ def validate_block_id(param,black_listed_ids):
raise ValidateError('ID "{}" must begin with a letter and may contain letters, numbers, '
'and underscores.'.format(value))
if value in (black_listed_ids + ID_BLACKLIST) and \
not getattr(param.parent_block, 'exempt_from_id_validation', False):
not getattr(param.parent_block, 'exempt_from_id_validation', False):
# Grant blacklist exemption to epy blocks and modules
raise ValidateError('ID "{}" is blacklisted.'.format(value))
block_names = [block.name for block in param.parent_flowgraph.iter_enabled_blocks()]
block_names = [
block.name for block in param.parent_flowgraph.iter_enabled_blocks()]
# Id should only appear once, or zero times if block is disabled
if param.key == 'id' and block_names.count(value) > 1:
raise ValidateError('ID "{}" is not unique.'.format(value))
@ -58,7 +61,7 @@ def validate_block_id(param,black_listed_ids):
@validates('name')
def validate_name(param,black_listed_ids):
def validate_name(param, black_listed_ids):
# Name of a function or other block that will be generated literally not as a string
value = param.value
@ -71,7 +74,7 @@ def validate_name(param,black_listed_ids):
@validates('stream_id')
def validate_stream_id(param,black_listed_ids):
def validate_stream_id(param, black_listed_ids):
value = param.value
stream_ids = [
block.params['stream_id'].value
@ -88,7 +91,7 @@ def validate_stream_id(param,black_listed_ids):
@validates('complex', 'real', 'float', 'int')
def validate_scalar(param,black_listed_ids):
def validate_scalar(param, black_listed_ids):
valid_types = Constants.PARAM_TYPE_MAP[param.dtype]
if not isinstance(param.get_evaluated(), valid_types):
raise ValidateError('Expression {!r} is invalid for type {!r}.'.format(
@ -96,19 +99,21 @@ def validate_scalar(param,black_listed_ids):
@validates('complex_vector', 'real_vector', 'float_vector', 'int_vector')
def validate_vector(param,black_listed_ids):
def validate_vector(param, black_listed_ids):
# todo: check vector types
if param.get_evaluated() is None:
raise ValidateError('Expression {!r} is invalid for type{!r}.'.format(param.get_evaluated(), param.dtype))
raise ValidateError('Expression {!r} is invalid for type{!r}.'.format(
param.get_evaluated(), param.dtype))
valid_types = Constants.PARAM_TYPE_MAP[param.dtype.split('_', 1)[0]]
if not all(isinstance(item, valid_types) for item in param.get_evaluated()):
raise ValidateError('Expression {!r} is invalid for type {!r}.'.format(
param.get_evaluated(), param.dtype))
@validates('gui_hint')
def validate_gui_hint(param,black_listed_ids):
def validate_gui_hint(param, black_listed_ids):
try:
param.parse_gui_hint(param.value)
except Exception as e:

View File

@ -18,6 +18,7 @@ from .template_arg import TemplateArg
attributed_str = type('attributed_str', (str,), {})
@setup_names
class Param(Element):
@ -73,12 +74,14 @@ class Param(Element):
options.attributes = collections.defaultdict(dict)
padding = [''] * max(len(values), len(labels))
attributes = {key: value + padding for key, value in attributes.items()}
attributes = {key: value + padding for key,
value in attributes.items()}
for i, option in enumerate(values):
# Test against repeated keys
if option in options:
raise KeyError('Value "{}" already exists in options'.format(option))
raise KeyError(
'Value "{}" already exists in options'.format(option))
# get label
try:
label = str(labels[i])
@ -86,7 +89,8 @@ class Param(Element):
label = str(option)
# Store the option
options[option] = label
options.attributes[option] = {attrib: values[i] for attrib, values in attributes.items()}
options.attributes[option] = {attrib: values[i]
for attrib, values in attributes.items()}
default = next(iter(options)) if options else ''
if not self.value:
@ -151,12 +155,13 @@ class Param(Element):
"""
Element.validate(self)
if self.dtype not in Constants.PARAM_TYPE_NAMES:
self.add_error_message('Type "{}" is not a possible type.'.format(self.dtype))
self.add_error_message(
'Type "{}" is not a possible type.'.format(self.dtype))
validator = dtypes.validators.get(self.dtype, None)
if self._init and validator:
try:
validator(self,self.parent_flowgraph.get_imported_names())
validator(self, self.parent_flowgraph.get_imported_names())
except dtypes.ValidateError as e:
self.add_error_message(str(e))
@ -207,12 +212,14 @@ class Param(Element):
if expr:
try:
if isinstance(expr, str) and self.is_float(expr[:-1]):
scale_factor = expr[-1:]
if scale_factor in self.scale:
expr = str(float(expr[:-1])*self.scale[scale_factor])
scale_factor = expr[-1:]
if scale_factor in self.scale:
expr = str(float(expr[:-1]) *
self.scale[scale_factor])
value = self.parent_flowgraph.evaluate(expr)
except Exception as e:
raise Exception('Value "{}" cannot be evaluated:\n{}'.format(expr, e))
raise Exception(
'Value "{}" cannot be evaluated:\n{}'.format(expr, e))
else:
value = None # No parameter value provided
if dtype == 'hex':
@ -230,7 +237,8 @@ class Param(Element):
try:
value = self.parent.parent.evaluate(expr)
except Exception as value:
raise Exception('Value "{}" cannot be evaluated:\n{}'.format(expr, value))
raise Exception(
'Value "{}" cannot be evaluated:\n{}'.format(expr, value))
if not isinstance(value, Constants.VECTOR_TYPES):
self._lisitify_flag = True
value = [value]
@ -339,7 +347,8 @@ class Param(Element):
e = self.parent_flowgraph.evaluate(pos)
if not isinstance(e, (list, tuple)) or len(e) not in (2, 4) or not all(isinstance(ei, int) for ei in e):
raise Exception('Invalid GUI Hint entered: {e!r} (Must be a list of {{2,4}} non-negative integers).'.format(e=e))
raise Exception(
'Invalid GUI Hint entered: {e!r} (Must be a list of {{2,4}} non-negative integers).'.format(e=e))
if len(e) == 2:
row, col = e
@ -348,10 +357,12 @@ class Param(Element):
row, col, row_span, col_span = e
if (row < 0) or (col < 0):
raise Exception('Invalid GUI Hint entered: {e!r} (non-negative integers only).'.format(e=e))
raise Exception(
'Invalid GUI Hint entered: {e!r} (non-negative integers only).'.format(e=e))
if (row_span < 1) or (col_span < 1):
raise Exception('Invalid GUI Hint entered: {e!r} (positive row/column span required).'.format(e=e))
raise Exception(
'Invalid GUI Hint entered: {e!r} (positive row/column span required).'.format(e=e))
return row, col, row_span, col_span
@ -360,7 +371,8 @@ class Param(Element):
if block.key == 'qtgui_tab_widget' and block.name == tab)
tab_block = next(iter(tabs), None)
if not tab_block:
raise Exception('Invalid tab name entered: {tab} (Tab name not found).'.format(tab=tab))
raise Exception(
'Invalid tab name entered: {tab} (Tab name not found).'.format(tab=tab))
tab_index_size = int(tab_block.params['num_tabs'].value)
if index >= tab_index_size:
@ -369,7 +381,8 @@ class Param(Element):
# Collision Detection
def collision_detection(row, col, row_span, col_span):
my_parent = '{tab}@{index}'.format(tab=tab, index=index) if tab else 'main'
my_parent = '{tab}@{index}'.format(tab=tab,
index=index) if tab else 'main'
# Calculate hostage cells
for r in range(row, row + row_span):
for c in range(col, col + col_span):
@ -378,7 +391,8 @@ class Param(Element):
for other in self.get_all_params('gui_hint'):
if other is self:
continue
collision = next(iter(self.hostage_cells & other.hostage_cells), None)
collision = next(
iter(self.hostage_cells & other.hostage_cells), None)
if collision:
raise Exception('Block {block!r} is also using parent {parent!r}, cell {cell!r}.'.format(
block=other.parent_block.name, parent=collision[0], cell=collision[1]
@ -390,7 +404,8 @@ class Param(Element):
if not pos:
layout = '{tab}_layout_{index}'.format(tab=tab, index=index)
else:
layout = '{tab}_grid_layout_{index}'.format(tab=tab, index=index)
layout = '{tab}_grid_layout_{index}'.format(
tab=tab, index=index)
else:
if not pos:
layout = 'top_layout'
@ -412,8 +427,8 @@ class Param(Element):
self.{layout}.setColumnStretch(c, 1)
""".strip('\n')).format(
layout=layout, widget=widget,
row=row, row_span=row_span, row_end=row+row_span,
col=col, col_span=col_span, col_end=col+col_span,
row=row, row_span=row_span, row_end=row + row_span,
col=col, col_span=col_span, col_end=col + col_span,
)
elif self.parent_flowgraph.get_option('output_language') == 'cpp':
widget_str = textwrap.dedent("""
@ -424,17 +439,19 @@ class Param(Element):
{layout}->setColumnStretch(c, 1);
""".strip('\n')).format(
layout=layout, widget=widget,
row=row, row_span=row_span, row_end=row+row_span,
col=col, col_span=col_span, col_end=col+col_span,
row=row, row_span=row_span, row_end=row + row_span,
col=col, col_span=col_span, col_end=col + col_span,
)
else:
widget_str = ''
else:
if self.parent_flowgraph.get_option('output_language') == 'python':
widget_str = 'self.{layout}.addWidget({widget})'.format(layout=layout, widget=widget)
widget_str = 'self.{layout}.addWidget({widget})'.format(
layout=layout, widget=widget)
elif self.parent_flowgraph.get_option('output_language') == 'cpp':
widget_str = '{layout}->addWidget({widget});'.format(layout=layout, widget=widget)
widget_str = '{layout}->addWidget({widget});'.format(
layout=layout, widget=widget)
else:
widget_str = ''

View File

@ -5,7 +5,6 @@
#
class TemplateArg(str):
"""
A cheetah template argument created from a param.

View File

@ -36,7 +36,8 @@ class Platform(Element):
self.config = self.Config(*args, **kwargs)
self.block_docstrings = {}
self.block_docstrings_loaded_callback = lambda: None # dummy to be replaced by BlockTreeWindow
# dummy to be replaced by BlockTreeWindow
self.block_docstrings_loaded_callback = lambda: None
self._docstring_extractor = utils.extract_docs.SubprocessLoader(
callback_query_result=self._save_docstring_extraction_result,
@ -108,7 +109,8 @@ class Platform(Element):
Messages.send('>>> Generating: {}\n'.format(generator.file_path))
generator.write()
except Exception as e:
Messages.send('>>> Generate Error: {}: {}\n'.format(file_path, str(e)))
Messages.send(
'>>> Generate Error: {}: {}\n'.format(file_path, str(e)))
return None, None
return flow_graph, generator.file_path
@ -126,8 +128,8 @@ class Platform(Element):
self.connection_templates.clear()
self.cpp_connection_templates.clear()
self._block_categories.clear()
with Cache(Constants.CACHE_FILE, version = self.config.version) as cache:
with Cache(Constants.CACHE_FILE, version=self.config.version) as cache:
for file_path in self._iter_files_in_block_path(path):
if file_path.endswith('.block.yml'):
@ -147,9 +149,11 @@ class Platform(Element):
data = cache.get_or_load(file_path)
passed = checker.run(data)
for msg in checker.messages:
logger.warning('{:<40s} {}'.format(os.path.basename(file_path), msg))
logger.warning('{:<40s} {}'.format(
os.path.basename(file_path), msg))
if not passed:
logger.info('YAML schema check failed for: ' + file_path)
logger.info(
'YAML schema check failed for: ' + file_path)
loader(data, file_path)
except Exception as error:
@ -184,8 +188,8 @@ class Platform(Element):
raise RuntimeError(errstr)
else:
# might have some cleanup to do on the options block in particular
utils.hide_bokeh_gui_options_if_not_installed(self.blocks['options'])
utils.hide_bokeh_gui_options_if_not_installed(
self.blocks['options'])
def _iter_files_in_block_path(self, path=None, ext='yml'):
"""Iterator for block descriptions and category trees"""
@ -220,13 +224,15 @@ class Platform(Element):
# don't load future block format versions
file_format = data['file_format']
if file_format < 1 or file_format > Constants.BLOCK_DESCRIPTION_FILE_FORMAT_VERSION:
log.error('Unknown format version %d in %s', file_format, file_path)
log.error('Unknown format version %d in %s',
file_format, file_path)
return
block_id = data['id'] = data['id'].rstrip('_')
if block_id in self.block_classes_build_in:
log.warning('Not overwriting build-in block %s with %s', block_id, file_path)
log.warning('Not overwriting build-in block %s with %s',
block_id, file_path)
return
if block_id in self.blocks:
log.warning('Block with id "%s" loaded from\n %s\noverwritten by\n %s',
@ -256,7 +262,8 @@ class Platform(Element):
try:
tuple(int(color[o:o + 2], 16) / 255.0 for o in range(1, 6, 2))
except ValueError:
log.warning('Cannot parse color code "%s" in %s', color, file_path)
log.warning('Cannot parse color code "%s" in %s',
color, file_path)
return
self.domains[domain_id] = self.Domain(
@ -272,8 +279,10 @@ class Platform(Element):
log.warn('Invalid connection template.')
continue
connection_id = str(source_id), str(sink_id)
self.connection_templates[connection_id] = connection.get('connect', '')
self.cpp_connection_templates[connection_id] = connection.get('cpp_connect', '')
self.connection_templates[connection_id] = connection.get(
'connect', '')
self.cpp_connection_templates[connection_id] = connection.get(
'cpp_connect', '')
def load_category_tree_description(self, data, file_path):
"""Parse category tree file and add it to list"""
@ -326,7 +335,8 @@ class Platform(Element):
# todo: try
if not is_xml:
data = yaml.safe_load(fp)
validator = schema_checker.Validator(schema_checker.FLOW_GRAPH_SCHEME)
validator = schema_checker.Validator(
schema_checker.FLOW_GRAPH_SCHEME)
validator.run(data)
if is_xml:
@ -340,13 +350,15 @@ class Platform(Element):
data = flow_graph.export_data()
try:
data['connections'] = [yaml.ListFlowing(i) for i in data['connections']]
data['connections'] = [yaml.ListFlowing(
i) for i in data['connections']]
except KeyError:
pass
try:
for d in chain([data['options']], data['blocks']):
d['states']['coordinate'] = yaml.ListFlowing(d['states']['coordinate'])
d['states']['coordinate'] = yaml.ListFlowing(
d['states']['coordinate'])
except KeyError:
pass
@ -393,7 +405,8 @@ class Platform(Element):
Connection = Connection
block_classes_build_in = blocks.build_ins
block_classes = utils.backports.ChainMap({}, block_classes_build_in) # separates build-in from loaded blocks)
# separates build-in from loaded blocks)
block_classes = utils.backports.ChainMap({}, block_classes_build_in)
port_classes = {
None: ports.Port, # default

View File

@ -2,7 +2,7 @@
# This file is part of GNU Radio
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
#
from itertools import chain
@ -28,10 +28,12 @@ def _sources_from_virtual_sink_port(sink_port, _traversed=None):
"""
source_ports_per_virtual_connection = (
# there can be multiple ports per virtual connection
_sources_from_virtual_source_port(c.source_port, _traversed) # type: list
_sources_from_virtual_source_port(
c.source_port, _traversed) # type: list
for c in sink_port.connections(enabled=True)
)
return list(chain(*source_ports_per_virtual_connection)) # concatenate generated lists of ports
# concatenate generated lists of ports
return list(chain(*source_ports_per_virtual_connection))
def _sources_from_virtual_source_port(source_port, _traversed=None):
@ -62,7 +64,8 @@ def _sources_from_virtual_source_port(source_port, _traversed=None):
_sources_from_virtual_sink_port(b.sinks[0], _traversed) # type: list
for b in connected_virtual_sink_blocks
)
return list(chain(*source_ports_per_virtual_connection)) # concatenate generated lists of ports
# concatenate generated lists of ports
return list(chain(*source_ports_per_virtual_connection))
def downstream_ports(port):
@ -82,7 +85,8 @@ def _sinks_from_virtual_source_port(source_port, _traversed=None):
_sinks_from_virtual_sink_port(c.sink_port, _traversed) # type: list
for c in source_port.connections(enabled=True)
)
return list(chain(*sink_ports_per_virtual_connection)) # concatenate generated lists of ports
# concatenate generated lists of ports
return list(chain(*sink_ports_per_virtual_connection))
def _sinks_from_virtual_sink_port(sink_port, _traversed=None):
@ -111,4 +115,5 @@ def _sinks_from_virtual_sink_port(sink_port, _traversed=None):
_sinks_from_virtual_source_port(b.sources[0], _traversed) # type: list
for b in connected_virtual_source_blocks
)
return list(chain(*sink_ports_per_virtual_connection)) # concatenate generated lists of ports
# concatenate generated lists of ports
return list(chain(*sink_ports_per_virtual_connection))

View File

@ -34,17 +34,21 @@ class Port(Element):
self._dir = direction
self.key = id
if not label:
label = id if not id.isdigit() else {'sink': 'in', 'source': 'out'}[direction]
label = id if not id.isdigit() else {'sink': 'in', 'source': 'out'}[
direction]
if dtype == 'bus':
# Look for existing busses to give proper index
busses = [p for p in self.parent.ports() if p._dir == self._dir and p.dtype == 'bus']
busses = [p for p in self.parent.ports() if p._dir ==
self._dir and p.dtype == 'bus']
bus_structure = self.parent.current_bus_structure[self._dir]
bus_index = len(busses)
if len(bus_structure) > bus_index:
number = str(len(busses)) + '#' + str(len(bus_structure[bus_index]))
number = str(len(busses)) + '#' + \
str(len(bus_structure[bus_index]))
label = dtype + number
else:
raise ValueError('Could not initialize bus port due to incompatible bus structure')
raise ValueError(
'Could not initialize bus port due to incompatible bus structure')
self.name = self._base_name = label
@ -102,7 +106,8 @@ class Port(Element):
self.add_error_message('Port is not connected.')
if self.dtype not in Constants.TYPE_TO_SIZEOF.keys():
self.add_error_message('Type "{}" is not a possible type.'.format(self.dtype))
self.add_error_message(
'Type "{}" is not a possible type.'.format(self.dtype))
try:
domain = platform.domains[self.domain]
@ -113,7 +118,8 @@ class Port(Element):
self.add_error_message('Domain "{}" can have only one downstream block'
''.format(self.domain))
except KeyError:
self.add_error_message('Domain key "{}" is not registered.'.format(self.domain))
self.add_error_message(
'Domain key "{}" is not registered.'.format(self.domain))
def rewrite(self):
del self.vlen
@ -150,9 +156,11 @@ class Port(Element):
try:
port = find_port(_virtual_connections.upstream_ports) or \
find_port(_virtual_connections.downstream_ports)
self.set_evaluated('dtype', port.dtype) # we don't want to override the template
self.set_evaluated('vlen', port.vlen) # we don't want to override the template
find_port(_virtual_connections.downstream_ports)
# we don't want to override the template
self.set_evaluated('dtype', port.dtype)
# we don't want to override the template
self.set_evaluated('vlen', port.vlen)
self.domain = port.domain
except AttributeError:
self.domain = Constants.DEFAULT_DOMAIN
@ -206,7 +214,7 @@ class Port(Element):
enabled: None for all, True for enabled only, False for disabled only
"""
for con in self.parent_flowgraph.connections:
#TODO clean this up - but how to get past this validation
# TODO clean this up - but how to get past this validation
# things don't compare simply with an x in y because
# bus ports are created differently.
port_in_con = False
@ -242,5 +250,6 @@ class Port(Element):
if bus_structure:
busses = [i for i in get_ports if i.dtype == 'bus']
bus_index = busses.index(self)
ports = filter(lambda a: ports.index(a) in bus_structure[bus_index], ports)
ports = filter(lambda a: ports.index(
a) in bus_structure[bus_index], ports)
return ports

View File

@ -12,7 +12,8 @@ PARAM_SCHEME = expand(
options=list,
option_labels=list,
option_attributes=Spec(types=dict, required=False, item_scheme=(str, list)),
option_attributes=Spec(types=dict, required=False,
item_scheme=(str, list)),
hide=str,
)
@ -59,7 +60,8 @@ BLOCK_SCHEME = expand(
value=str,
templates=Spec(types=dict, required=False, item_scheme=TEMPLATES_SCHEME),
cpp_templates=Spec(types=dict, required=False, item_scheme=CPP_TEMPLATES_SCHEME),
cpp_templates=Spec(types=dict, required=False,
item_scheme=CPP_TEMPLATES_SCHEME),
documentation=str,
grc_source=str,

View File

@ -30,16 +30,19 @@ class ChainMap(MutableMapping):
def __getitem__(self, key):
for mapping in self.maps:
try:
return mapping[key] # can't use 'key in mapping' with defaultdict
# can't use 'key in mapping' with defaultdict
return mapping[key]
except KeyError:
pass
return self.__missing__(key) # support subclasses that define __missing__
# support subclasses that define __missing__
return self.__missing__(key)
def get(self, key, default=None):
return self[key] if key in self else default
def __len__(self):
return len(set().union(*self.maps)) # reuses stored hash values if possible
# reuses stored hash values if possible
return len(set().union(*self.maps))
def __iter__(self):
return iter(set().union(*self.maps))
@ -85,7 +88,8 @@ class ChainMap(MutableMapping):
try:
del self.maps[0][key]
except KeyError:
raise KeyError('Key not found in the first mapping: {!r}'.format(key))
raise KeyError(
'Key not found in the first mapping: {!r}'.format(key))
def popitem(self):
"""Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty."""
@ -99,7 +103,8 @@ class ChainMap(MutableMapping):
try:
return self.maps[0].pop(key, *args)
except KeyError:
raise KeyError('Key not found in the first mapping: {!r}'.format(key))
raise KeyError(
'Key not found in the first mapping: {!r}'.format(key))
def clear(self):
"""Clear maps[0], leaving maps[1:] intact."""

View File

@ -23,6 +23,7 @@ class lazy_property(object):
def nop_write(prop):
"""Make this a property with a nop setter"""
def nop(self, value):
pass
return prop.setter(nop)

View File

@ -23,7 +23,8 @@ class Evaluated(object):
value = instance.parent_block.evaluate(raw)
except Exception as error:
if raw:
instance.add_error_message("Failed to eval '{}': {}".format(raw, error))
instance.add_error_message(
"Failed to eval '{}': {}".format(raw, error))
return self.default
if not isinstance(value, self.expected_type):
@ -68,13 +69,15 @@ class EvaluatedEnum(Evaluated):
if isinstance(allowed_values, str):
allowed_values = set(allowed_values.split())
self.allowed_values = allowed_values
default = default if default is not None else next(iter(self.allowed_values))
default = default if default is not None else next(
iter(self.allowed_values))
super(EvaluatedEnum, self).__init__(str, default, name)
def default_eval_func(self, instance):
value = super(EvaluatedEnum, self).default_eval_func(instance)
if value not in self.allowed_values:
instance.add_error_message("Value '{}' not in allowed values".format(value))
instance.add_error_message(
"Value '{}' not in allowed values".format(value))
return self.default
return value

View File

@ -12,7 +12,8 @@ TYPE_MAP = {
'int8': 'byte', 'uint8': 'byte',
}
BlockIO = collections.namedtuple('BlockIO', 'name cls params sinks sources doc callbacks')
BlockIO = collections.namedtuple(
'BlockIO', 'name cls params sinks sources doc callbacks')
def _ports(sigs, msgs):
@ -72,11 +73,13 @@ def extract(cls):
def settable(attr):
try:
return callable(getattr(cls, attr).fset) # check for a property with setter
# check for a property with setter
return callable(getattr(cls, attr).fset)
except AttributeError:
return attr in instance.__dict__ # not dir() - only the instance attribs
callbacks = [attr for attr in dir(instance) if attr in init_args and settable(attr)]
callbacks = [attr for attr in dir(
instance) if attr in init_args and settable(attr)]
sinks = _ports(instance.in_sig(),
pmt.to_python(instance.message_ports_in()))

View File

@ -7,6 +7,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
"""
import ast
import string
@ -64,12 +65,10 @@ def sort_objects(objects, get_id, get_expr):
return [id2obj[id] for id in sorted_ids]
import ast
def dependencies(expr, names=None):
node = ast.parse(expr, mode='eval')
used_ids = frozenset([n.id for n in ast.walk(node) if isinstance(n, ast.Name)])
used_ids = frozenset(
[n.id for n in ast.walk(node) if isinstance(n, ast.Name)])
return used_ids & names if names else used_ids
@ -93,8 +92,6 @@ def sort_objects2(objects, id_getter, expr_getter, check_circular=True):
return objects
VAR_CHARS = string.ascii_letters + string.digits + '_'
@ -205,7 +202,8 @@ def _sort_variables(exprs):
# Determine dependency order
while var_graph.get_nodes():
# Get a list of nodes with no edges
indep_vars = [var for var in var_graph.get_nodes() if not var_graph.get_edges(var)]
indep_vars = [var for var in var_graph.get_nodes()
if not var_graph.get_edges(var)]
if not indep_vars:
raise Exception('circular dependency caught in sort_variables')
# Add the indep vars to the end of the list

View File

@ -55,7 +55,8 @@ 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__
@ -135,7 +136,8 @@ class SubprocessLoader(object):
break
try:
self._worker = subprocess.Popen(
args=(sys.executable, '-uc', self.BOOTSTRAP.format(__file__)),
args=(sys.executable, '-uc',
self.BOOTSTRAP.format(__file__)),
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
@ -286,7 +288,8 @@ elif __name__ == '__main__':
# r.query('uhd_source')
r.query('expr_utils_graph')
r.query('blocks_add_cc')
r.query('blocks_add_cc', ['import gnuradio.blocks'], 'gnuradio.blocks.add_cc(')
r.query('blocks_add_cc', ['import gnuradio.blocks'],
'gnuradio.blocks.add_cc(')
# r.query('analog_feedforward_agc_cc')
# r.query('uhd_source')
# r.query('uhd_source')

View File

@ -37,17 +37,20 @@ def calculate(flowgraph):
# Disabled multiplier
if enabled > 0:
disabled_multi = 1 / (max(1 - ((blocks - enabled) / max(blocks, 1)), 0.05))
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))
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)
final = round(max((dbal - 1) * disabled_multi *
conn_multi * connections, 0.0) / 1000000, 6)
return final
except Exception:

View File

@ -85,10 +85,10 @@ class Namespace(object):
key = "{}.{}".format(prefix, name)
if prefix == "app":
pass
#self.app.add_action(action)
# self.app.add_action(action)
elif prefix == "win":
pass
#self.win.add_action(action)
# self.win.add_action(action)
#log.debug("Registering action as '{}'".format(key))
self._actions[key] = action
@ -132,7 +132,7 @@ class Namespace(object):
class Action(Gio.SimpleAction):
# Change these to normal python properties.
#prefs_name
# prefs_name
def __init__(self,
name,
@ -295,7 +295,7 @@ FLOW_GRAPH_DUPLICATE = actions.register(
"app.flowgraph.duplicate",
label='_Duplicate',
tooltip='Create a duplicate of current flow graph',
#stock_id=Gtk.STOCK_COPY,
# stock_id=Gtk.STOCK_COPY,
keypresses=["<Ctrl><Shift>d"],
)
FLOW_GRAPH_CLOSE = actions.register(
@ -433,26 +433,27 @@ BLOCK_BYPASS = actions.register(
keypresses=["b"],
)
ZOOM_IN = actions.register("win.zoom_in",
label='Zoom In',
tooltip='Increase the canvas zoom level',
keypresses=["<Ctrl>plus","<Ctrl>equal","<Ctrl>KP_Add"],
)
label='Zoom In',
tooltip='Increase the canvas zoom level',
keypresses=["<Ctrl>plus",
"<Ctrl>equal", "<Ctrl>KP_Add"],
)
ZOOM_OUT = actions.register("win.zoom_out",
label='Zoom Out',
tooltip='Decrease the canvas zoom level',
keypresses=["<Ctrl>minus","<Ctrl>KP_Subtract"],
)
label='Zoom Out',
tooltip='Decrease the canvas zoom level',
keypresses=["<Ctrl>minus", "<Ctrl>KP_Subtract"],
)
ZOOM_RESET = actions.register("win.zoom_reset",
label='Reset Zoom',
tooltip='Reset the canvas zoom level',
keypresses=["<Ctrl>0","<Ctrl>KP_0"],
)
label='Reset Zoom',
tooltip='Reset the canvas zoom level',
keypresses=["<Ctrl>0", "<Ctrl>KP_0"],
)
TOGGLE_SNAP_TO_GRID = actions.register("win.snap_to_grid",
label='_Snap to grid',
tooltip='Snap blocks to a grid for an easier connection alignment',
preference_name='snap_to_grid',
default=True,
)
label='_Snap to grid',
tooltip='Snap blocks to a grid for an easier connection alignment',
preference_name='snap_to_grid',
default=True,
)
TOGGLE_HIDE_DISABLED_BLOCKS = actions.register(
"win.hide_disabled",
label='Hide _Disabled Blocks',
@ -493,8 +494,7 @@ TOGGLE_SHOW_BLOCK_IDS = actions.register(
TOGGLE_FLOW_GRAPH_VAR_EDITOR = actions.register(
"win.toggle_variable_editor",
label='Show _Variable Editor',
tooltip=
'Show the variable editor. Modify variables and imports in this flow graph',
tooltip='Show the variable editor. Modify variables and imports in this flow graph',
icon_name='accessories-text-editor',
default=True,
keypresses=["<Ctrl>e"],

View File

@ -7,7 +7,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
"""
import logging
import os
import subprocess
@ -65,7 +64,8 @@ class Application(Gtk.Application):
self.set_accels_for_action(x, keypress)
# Initialize
self.init_file_paths = [os.path.abspath(file_path) for file_path in file_paths]
self.init_file_paths = [os.path.abspath(
file_path) for file_path in file_paths]
self.init = False
def do_startup(self):
@ -80,7 +80,7 @@ class Application(Gtk.Application):
self.main_window.connect('delete-event', self._quit)
self.get_focus_flag = self.main_window.get_focus_flag
#setup the messages
# setup the messages
Messages.register_messenger(self.main_window.add_console_line)
Messages.send_init(self.platform)
@ -117,7 +117,8 @@ class Application(Gtk.Application):
file_path_to_show = self.config.file_open()
for file_path in (self.init_file_paths or self.config.get_open_files()):
if os.path.exists(file_path):
main.new_page(file_path, show=file_path_to_show == file_path)
main.new_page(
file_path, show=file_path_to_show == file_path)
if not main.current_page:
main.new_page() # ensure that at least a blank page exists
@ -190,9 +191,12 @@ class Application(Gtk.Application):
action.load_from_preferences()
# Hide the panels *IF* it's saved in preferences
main.update_panel_visibility(main.BLOCKS, Actions.TOGGLE_BLOCKS_WINDOW.get_active())
main.update_panel_visibility(main.CONSOLE, Actions.TOGGLE_CONSOLE_WINDOW.get_active())
main.update_panel_visibility(main.VARIABLES, Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR.get_active())
main.update_panel_visibility(
main.BLOCKS, Actions.TOGGLE_BLOCKS_WINDOW.get_active())
main.update_panel_visibility(
main.CONSOLE, Actions.TOGGLE_CONSOLE_WINDOW.get_active())
main.update_panel_visibility(
main.VARIABLES, Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR.get_active())
# Force an update on the current page to match loaded preferences.
# In the future, change the __init__ order to load preferences first
@ -210,7 +214,7 @@ class Application(Gtk.Application):
# Selections
##################################################
elif action == Actions.ELEMENT_SELECT:
pass #do nothing, update routines below
pass # do nothing, update routines below
elif action == Actions.NOTHING_SELECT:
flow_graph.unselect()
elif action == Actions.SELECT_ALL:
@ -271,7 +275,6 @@ class Application(Gtk.Application):
x_min = min(block.coordinate[0] for block in selected_blocks)
y_min = min(block.coordinate[1] for block in selected_blocks)
for connection in flow_graph.connections:
# Get id of connected blocks
@ -283,7 +286,8 @@ class Application(Gtk.Application):
pads.append({
'key': connection.sink_port.key,
'coord': source.coordinate,
'block_index': selected_blocks.index(sink) + 1, # Ignore the options block
# Ignore the options block
'block_index': selected_blocks.index(sink) + 1,
'direction': 'source'
})
@ -292,7 +296,8 @@ class Application(Gtk.Application):
pads.append({
'key': connection.source_port.key,
'coord': sink.coordinate,
'block_index': selected_blocks.index(source) + 1, # Ignore the options block
# Ignore the options block
'block_index': selected_blocks.index(source) + 1,
'direction': 'sink'
})
@ -335,7 +340,8 @@ class Application(Gtk.Application):
pad_block = flow_graph.get_block(pad_id)
pad_sink = pad_block.sinks[0]
source_block = flow_graph.get_block(flow_graph.blocks[pad['block_index']].name)
source_block = flow_graph.get_block(
flow_graph.blocks[pad['block_index']].name)
source = source_block.get_source(pad['key'])
# ensure the port types match
@ -350,13 +356,15 @@ class Application(Gtk.Application):
new_connection = flow_graph.connect(source, pad_sink)
elif pad['direction'] == 'source':
pad_id = flow_graph.add_new_block('pad_source', pad['coord'])
pad_id = flow_graph.add_new_block(
'pad_source', pad['coord'])
# setup the references to the sink and source
pad_block = flow_graph.get_block(pad_id)
pad_source = pad_block.sources[0]
sink_block = flow_graph.get_block(flow_graph.blocks[pad['block_index']].name)
sink_block = flow_graph.get_block(
flow_graph.blocks[pad['block_index']].name)
sink = sink_block.get_sink(pad['key'])
# ensure the port types match
@ -496,7 +504,8 @@ class Application(Gtk.Application):
# to be visible.
varedit = Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR
if active:
log.debug("Variables are hidden. Forcing the variable panel to be visible.")
log.debug(
"Variables are hidden. Forcing the variable panel to be visible.")
varedit.disable()
else:
varedit.enable()
@ -548,8 +557,9 @@ class Application(Gtk.Application):
while response == Gtk.ResponseType.APPLY: # rerun the dialog if Apply was hit
response = self.dialog.run()
if response in (Gtk.ResponseType.APPLY, Gtk.ResponseType.ACCEPT):
page.state_cache.save_new_state(flow_graph.export_data())
### Following line forces a complete update of io ports
page.state_cache.save_new_state(
flow_graph.export_data())
# Following line forces a complete update of io ports
flow_graph_update()
page.saved = False
if response in (Gtk.ResponseType.REJECT, Gtk.ResponseType.ACCEPT):
@ -600,18 +610,21 @@ class Application(Gtk.Application):
main.new_page()
args = (GLib.Variant('s', 'qt_gui'),)
flow_graph = main.current_page.flow_graph
flow_graph.options_block.params['generate_options'].set_value(str(args[0])[1:-1])
flow_graph.options_block.params['generate_options'].set_value(str(args[0])[
1:-1])
flow_graph.options_block.params['author'].set_value(getuser())
flow_graph_update(flow_graph)
elif action == Actions.FLOW_GRAPH_NEW_TYPE:
main.new_page()
if args:
flow_graph = main.current_page.flow_graph
flow_graph.options_block.params['generate_options'].set_value(str(args[0])[1:-1])
flow_graph.options_block.params['generate_options'].set_value(str(args[0])[
1:-1])
flow_graph_update(flow_graph)
elif action == Actions.FLOW_GRAPH_OPEN:
file_paths = args[0] if args[0] else FileDialogs.OpenFlowGraph(main, page.file_path).run()
if file_paths: # Open a new page for each file, show only the first
file_paths = args[0] if args[0] else FileDialogs.OpenFlowGraph(
main, page.file_path).run()
if file_paths: # Open a new page for each file, show only the first
for i, file_path in enumerate(file_paths):
main.new_page(file_path, show=(i == 0))
self.config.add_recent_file(file_path)
@ -631,10 +644,10 @@ class Application(Gtk.Application):
main.tool_bar.refresh_submenus()
main.menu.refresh_submenus()
elif action == Actions.FLOW_GRAPH_SAVE:
#read-only or undefined file path, do save-as
# read-only or undefined file path, do save-as
if page.get_read_only() or not page.file_path:
Actions.FLOW_GRAPH_SAVE_AS()
#otherwise try to save
# otherwise try to save
else:
try:
self.platform.save_flow_graph(page.file_path, flow_graph)
@ -670,18 +683,25 @@ class Application(Gtk.Application):
Actions.FLOW_GRAPH_SAVE_AS()
else:
dup_file_path = page.file_path
dup_file_name = '.'.join(dup_file_path.split('.')[:-1]) + "_copy" # Assuming .grc extension at the end of file_path
# Assuming .grc extension at the end of file_path
dup_file_name = '.'.join(
dup_file_path.split('.')[:-1]) + "_copy"
dup_file_path_temp = dup_file_name + Constants.FILE_EXTENSION
count = 1
while os.path.exists(dup_file_path_temp):
dup_file_path_temp = '{}({}){}'.format(dup_file_name, count, Constants.FILE_EXTENSION)
dup_file_path_temp = '{}({}){}'.format(
dup_file_name, count, Constants.FILE_EXTENSION)
count += 1
dup_file_path_user = FileDialogs.SaveFlowGraph(main, dup_file_path_temp).run()
dup_file_path_user = FileDialogs.SaveFlowGraph(
main, dup_file_path_temp).run()
if dup_file_path_user is not None:
self.platform.save_flow_graph(dup_file_path_user, flow_graph)
Messages.send('Saved Copy to: "' + dup_file_path_user + '"\n')
self.platform.save_flow_graph(
dup_file_path_user, flow_graph)
Messages.send('Saved Copy to: "' +
dup_file_path_user + '"\n')
except IOError:
Messages.send_fail_save("Can not create a copy of the flowgraph\n")
Messages.send_fail_save(
"Can not create a copy of the flowgraph\n")
elif action == Actions.FLOW_GRAPH_DUPLICATE:
previous = flow_graph
# Create a new page
@ -694,10 +714,12 @@ class Application(Gtk.Application):
page.state_cache.save_new_state(new_flow_graph.export_data())
page.saved = False
elif action == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
file_path, background_transparent = FileDialogs.SaveScreenShot(main, page.file_path).run()
file_path, background_transparent = FileDialogs.SaveScreenShot(
main, page.file_path).run()
if file_path is not None:
try:
Utils.make_screenshot(flow_graph, file_path, background_transparent)
Utils.make_screenshot(
flow_graph, file_path, background_transparent)
except ValueError:
Messages.send('Failed to generate screen shot\n')
##################################################
@ -717,7 +739,6 @@ class Application(Gtk.Application):
except Exception as e:
Messages.send_fail_gen(e)
elif action == Actions.FLOW_GRAPH_EXEC:
if not page.process:
Actions.FLOW_GRAPH_GEN()
@ -734,7 +755,7 @@ class Application(Gtk.Application):
flow_graph_page=page,
xterm_executable=xterm,
callback=self.update_exec_stop
)
)
elif action == Actions.FLOW_GRAPH_KILL:
if page.process:
try:
@ -748,7 +769,7 @@ class Application(Gtk.Application):
self.platform.build_library()
main.btwin.repopulate()
#todo: implement parser error dialog for YAML
# todo: implement parser error dialog for YAML
# Force a redraw of the graph, by getting the current state and re-importing it
main.update_pages()
@ -791,21 +812,21 @@ class Application(Gtk.Application):
selected_blocks = list(flow_graph.selected_blocks())
selected_block = selected_blocks[0] if selected_blocks else None
#update general buttons
# update general buttons
Actions.ERRORS_WINDOW_DISPLAY.set_enabled(not flow_graph.is_valid())
Actions.ELEMENT_DELETE.set_enabled(bool(flow_graph.selected_elements))
Actions.BLOCK_PARAM_MODIFY.set_enabled(bool(selected_block))
Actions.BLOCK_ROTATE_CCW.set_enabled(bool(selected_blocks))
Actions.BLOCK_ROTATE_CW.set_enabled(bool(selected_blocks))
#update alignment options
# update alignment options
for act in Actions.BLOCK_ALIGNMENTS:
if act:
act.set_enabled(len(selected_blocks) > 1)
#update cut/copy/paste
# update cut/copy/paste
Actions.BLOCK_CUT.set_enabled(bool(selected_blocks))
Actions.BLOCK_COPY.set_enabled(bool(selected_blocks))
Actions.BLOCK_PASTE.set_enabled(bool(self.clipboard))
#update enable/disable/bypass
# update enable/disable/bypass
can_enable = any(block.state != 'enabled'
for block in selected_blocks)
can_disable = any(block.state != 'disabled'

View File

@ -42,11 +42,14 @@ TOOLBAR_LIST = [
(Actions.FLOW_GRAPH_OPEN, 'flow_graph_recent'),
Actions.FLOW_GRAPH_SAVE, Actions.FLOW_GRAPH_CLOSE],
[Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR, Actions.FLOW_GRAPH_SCREEN_CAPTURE],
[Actions.BLOCK_CUT, Actions.BLOCK_COPY, Actions.BLOCK_PASTE, Actions.ELEMENT_DELETE],
[Actions.BLOCK_CUT, Actions.BLOCK_COPY,
Actions.BLOCK_PASTE, Actions.ELEMENT_DELETE],
[Actions.FLOW_GRAPH_UNDO, Actions.FLOW_GRAPH_REDO],
[Actions.ERRORS_WINDOW_DISPLAY, Actions.FLOW_GRAPH_GEN, Actions.FLOW_GRAPH_EXEC, Actions.FLOW_GRAPH_KILL],
[Actions.ERRORS_WINDOW_DISPLAY, Actions.FLOW_GRAPH_GEN,
Actions.FLOW_GRAPH_EXEC, Actions.FLOW_GRAPH_KILL],
[Actions.BLOCK_ROTATE_CCW, Actions.BLOCK_ROTATE_CW],
[Actions.BLOCK_ENABLE, Actions.BLOCK_DISABLE, Actions.BLOCK_BYPASS, Actions.TOGGLE_HIDE_DISABLED_BLOCKS],
[Actions.BLOCK_ENABLE, Actions.BLOCK_DISABLE,
Actions.BLOCK_BYPASS, Actions.TOGGLE_HIDE_DISABLED_BLOCKS],
[Actions.FIND_BLOCKS, Actions.RELOAD_BLOCKS, Actions.OPEN_HIER]
]
@ -56,23 +59,28 @@ MENU_BAR_LIST = [
('_File', [
[(Actions.FLOW_GRAPH_NEW, 'flow_graph_new_type'), Actions.FLOW_GRAPH_DUPLICATE,
Actions.FLOW_GRAPH_OPEN, (Actions.FLOW_GRAPH_OPEN_RECENT, 'flow_graph_recent')],
[Actions.FLOW_GRAPH_SAVE, Actions.FLOW_GRAPH_SAVE_AS, Actions.FLOW_GRAPH_SAVE_COPY],
[Actions.FLOW_GRAPH_SAVE, Actions.FLOW_GRAPH_SAVE_AS,
Actions.FLOW_GRAPH_SAVE_COPY],
[Actions.FLOW_GRAPH_SCREEN_CAPTURE],
[Actions.FLOW_GRAPH_CLOSE, Actions.APPLICATION_QUIT]
]),
('_Edit', [
[Actions.FLOW_GRAPH_UNDO, Actions.FLOW_GRAPH_REDO],
[Actions.BLOCK_CUT, Actions.BLOCK_COPY, Actions.BLOCK_PASTE, Actions.ELEMENT_DELETE, Actions.SELECT_ALL],
[Actions.BLOCK_ROTATE_CCW, Actions.BLOCK_ROTATE_CW, ('_Align', Actions.BLOCK_ALIGNMENTS)],
[Actions.BLOCK_CUT, Actions.BLOCK_COPY, Actions.BLOCK_PASTE,
Actions.ELEMENT_DELETE, Actions.SELECT_ALL],
[Actions.BLOCK_ROTATE_CCW, Actions.BLOCK_ROTATE_CW,
('_Align', Actions.BLOCK_ALIGNMENTS)],
[Actions.BLOCK_ENABLE, Actions.BLOCK_DISABLE, Actions.BLOCK_BYPASS],
[Actions.BLOCK_PARAM_MODIFY]
]),
('_View', [
[Actions.TOGGLE_BLOCKS_WINDOW],
[Actions.TOGGLE_CONSOLE_WINDOW, Actions.TOGGLE_SCROLL_LOCK, Actions.SAVE_CONSOLE, Actions.CLEAR_CONSOLE],
[Actions.TOGGLE_CONSOLE_WINDOW, Actions.TOGGLE_SCROLL_LOCK,
Actions.SAVE_CONSOLE, Actions.CLEAR_CONSOLE],
[Actions.TOGGLE_HIDE_VARIABLES, Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR, Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR,
Actions.TOGGLE_SHOW_PARAMETER_EXPRESSION, Actions.TOGGLE_SHOW_PARAMETER_EVALUATION],
[Actions.TOGGLE_HIDE_DISABLED_BLOCKS, Actions.TOGGLE_AUTO_HIDE_PORT_LABELS, Actions.TOGGLE_SNAP_TO_GRID, Actions.TOGGLE_SHOW_BLOCK_COMMENTS, Actions.TOGGLE_SHOW_BLOCK_IDS,],
[Actions.TOGGLE_HIDE_DISABLED_BLOCKS, Actions.TOGGLE_AUTO_HIDE_PORT_LABELS,
Actions.TOGGLE_SNAP_TO_GRID, Actions.TOGGLE_SHOW_BLOCK_COMMENTS, Actions.TOGGLE_SHOW_BLOCK_IDS, ],
[Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB],
[Actions.ZOOM_IN],
[Actions.ZOOM_OUT],
@ -87,15 +95,18 @@ MENU_BAR_LIST = [
[Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY]
]),
('_Help', [
[Actions.HELP_WINDOW_DISPLAY, Actions.TYPES_WINDOW_DISPLAY, Actions.KEYBOARD_SHORTCUTS_WINDOW_DISPLAY, Actions.XML_PARSER_ERRORS_DISPLAY],
[Actions.HELP_WINDOW_DISPLAY, Actions.TYPES_WINDOW_DISPLAY,
Actions.KEYBOARD_SHORTCUTS_WINDOW_DISPLAY, Actions.XML_PARSER_ERRORS_DISPLAY],
[Actions.GET_INVOLVED_WINDOW_DISPLAY, Actions.ABOUT_WINDOW_DISPLAY]
])]
# The list of actions for the context menu.
CONTEXT_MENU_LIST = [
[Actions.BLOCK_CUT, Actions.BLOCK_COPY, Actions.BLOCK_PASTE, Actions.ELEMENT_DELETE],
[Actions.BLOCK_ROTATE_CCW, Actions.BLOCK_ROTATE_CW, Actions.BLOCK_ENABLE, Actions.BLOCK_DISABLE, Actions.BLOCK_BYPASS],
[Actions.BLOCK_CUT, Actions.BLOCK_COPY,
Actions.BLOCK_PASTE, Actions.ELEMENT_DELETE],
[Actions.BLOCK_ROTATE_CCW, Actions.BLOCK_ROTATE_CW,
Actions.BLOCK_ENABLE, Actions.BLOCK_DISABLE, Actions.BLOCK_BYPASS],
[("_More", [
[Actions.BLOCK_CREATE_HIER, Actions.OPEN_HIER],
[Actions.BUSSIFY_SOURCES, Actions.BUSSIFY_SINKS]
@ -181,7 +192,8 @@ class MenuHelper(SubMenuHelper):
target = "{}.{}".format(parent.prefix, parent.name)
menuitem = Gio.MenuItem.new(label, None)
if hasattr(parent, "icon_name"):
menuitem.set_icon(Gio.Icon.new_for_string(parent.icon_name))
menuitem.set_icon(
Gio.Icon.new_for_string(parent.icon_name))
# Create the new submenu
if isinstance(child, list):
@ -216,6 +228,7 @@ class MenuHelper(SubMenuHelper):
parent_obj.remove(obj_idx)
parent_obj.insert_item(obj_idx, obj)
class ToolbarHelper(SubMenuHelper):
"""
Builds a toolbar from a given list of actions.
@ -275,6 +288,7 @@ class ToolbarHelper(SubMenuHelper):
create_func, parent_obj, _, obj, set_func = self.submenus[name]
set_func(obj, create_func())
class Menu(Gio.Menu, MenuHelper):
""" Main Menu """
@ -309,7 +323,7 @@ class Toolbar(Gtk.Toolbar, ToolbarHelper):
ToolbarHelper.__init__(self)
self.set_style(Gtk.ToolbarStyle.ICONS)
#self.get_style_context().add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR)
# self.get_style_context().add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR)
#SubMenuCreator.__init__(self)
# SubMenuCreator.__init__(self)
self.build_toolbar(TOOLBAR_LIST, self)

View File

@ -34,7 +34,8 @@ def _format_doc(doc):
def _format_cat_tooltip(category):
tooltip = '{}: {}'.format('Category' if len(category) > 1 else 'Module', category[-1])
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).'
@ -68,26 +69,33 @@ class BlockTreeWindow(Gtk.VBox):
# search entry
self.search_entry = Gtk.Entry()
try:
self.search_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY, 'edit-find')
self.search_entry.set_icon_activatable(Gtk.EntryIconPosition.PRIMARY, False)
self.search_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, 'window-close')
self.search_entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.PRIMARY, 'edit-find')
self.search_entry.set_icon_activatable(
Gtk.EntryIconPosition.PRIMARY, False)
self.search_entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.SECONDARY, 'window-close')
self.search_entry.connect('icon-release', self._handle_icon_event)
except AttributeError:
pass # no icon for old pygtk
self.search_entry.connect('changed', self._update_search_tree)
self.search_entry.connect('key-press-event', self._handle_search_key_press)
self.search_entry.connect(
'key-press-event', self._handle_search_key_press)
self.pack_start(self.search_entry, False, False, 0)
# make the tree model for holding blocks and a temporary one for search results
self.treestore = Gtk.TreeStore(GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING)
self.treestore_search = Gtk.TreeStore(GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING)
self.treestore = Gtk.TreeStore(
GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING)
self.treestore_search = Gtk.TreeStore(
GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING)
self.treeview = Gtk.TreeView(model=self.treestore)
self.treeview.set_enable_search(False) # disable pop up search box
self.treeview.set_search_column(-1) # really disable search
self.treeview.set_headers_visible(False)
self.treeview.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
self.treeview.connect('button-press-event', self._handle_mouse_button_press)
self.treeview.connect('button-press-event',
self._handle_mouse_button_press)
self.treeview.connect('key-press-event', self._handle_search_key_press)
self.treeview.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
@ -99,13 +107,16 @@ class BlockTreeWindow(Gtk.VBox):
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, Constants.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.set_policy(
Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scrolled_window.add(self.treeview)
scrolled_window.set_size_request(Constants.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}
@ -128,10 +139,11 @@ class BlockTreeWindow(Gtk.VBox):
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)
self.treeview.expand_row(
self.treestore.get_path(core_module_iter), False)
############################################################
## Block Tree Methods
# Block Tree Methods
############################################################
def add_block(self, block, treestore=None, categories=None):
"""
@ -144,16 +156,19 @@ class BlockTreeWindow(Gtk.VBox):
treestore = treestore or self.treestore
categories = categories or self._categories
category = tuple(filter(str, block.category)) # tuple is hashable, remove empty cats
# tuple is hashable, remove empty cats
category = tuple(filter(str, block.category))
# add category and all sub categories
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)
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, _format_cat_tooltip(parent_category))
treestore.set_value(
iter_, DOC_INDEX, _format_cat_tooltip(parent_category))
categories[parent_category] = iter_
# add block
iter_ = treestore.insert_before(categories[category], None)
@ -175,7 +190,7 @@ class BlockTreeWindow(Gtk.VBox):
self.treestore_search.foreach(update_doc)
############################################################
## Helper Methods
# Helper Methods
############################################################
def _get_selected_block_key(self):
"""
@ -195,7 +210,7 @@ class BlockTreeWindow(Gtk.VBox):
self.treeview.expand_to_path(path)
############################################################
## Event Handlers
# Event Handlers
############################################################
def _handle_icon_event(self, widget, icon, event):
if icon == Gtk.EntryIconPosition.PRIMARY:
@ -216,7 +231,8 @@ class BlockTreeWindow(Gtk.VBox):
self.treestore_search.clear()
self._categories_search = {tuple(): None}
for block in matching_blocks:
self.add_block(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()
@ -232,7 +248,8 @@ class BlockTreeWindow(Gtk.VBox):
selected = self.treestore_search.iter_children(selected)
if selected is not None:
key = self.treestore_search.get_value(selected, KEY_INDEX)
if key: self.emit('create_new_block', key)
if key:
self.emit('create_new_block', key)
elif widget == self.treeview:
key = self._get_selected_block_key()
if key:
@ -248,7 +265,7 @@ class BlockTreeWindow(Gtk.VBox):
self.search_entry.hide()
elif (event.get_state() & Gdk.ModifierType.CONTROL_MASK and event.keyval == Gdk.KEY_f) \
or event.keyval == Gdk.KEY_slash:
or event.keyval == Gdk.KEY_slash:
# propagation doesn't work although treeview search is disabled =(
# manually trigger action...
Actions.FIND_BLOCKS.activate()
@ -258,7 +275,7 @@ class BlockTreeWindow(Gtk.VBox):
Actions.TOGGLE_BLOCKS_WINDOW.activate()
else:
return False # propagate event
return False # propagate event
return True

View File

@ -142,7 +142,8 @@ class Config(CoreConfig):
def get_recent_files(self):
""" Gets recent files, removes any that do not exist and re-saves it """
files = list(filter(os.path.exists, self.get_file_list('files_recent')))
files = list(
filter(os.path.exists, self.get_file_list('files_recent')))
self.set_recent_files(files)
return files
@ -168,9 +169,9 @@ class Config(CoreConfig):
# Figure out default
if sidebar:
_, h = self.main_window_size()
return self.entry('variable_editor_sidebar_position', pos, default=int(h*0.7))
return self.entry('variable_editor_sidebar_position', pos, default=int(h * 0.7))
else:
return self.entry('variable_editor_position', pos, default=int(self.blocks_window_position()*0.5))
return self.entry('variable_editor_position', pos, default=int(self.blocks_window_position() * 0.5))
def variable_editor_sidebar(self, pos=None):
return self.entry('variable_editor_sidebar', pos, default=False)

View File

@ -7,17 +7,15 @@ SPDX-License-Identifier: GPL-2.0-or-later
"""
from ..core import Messages
from .Dialogs import TextDisplay, MessageDialogWrapper
from .Constants import DEFAULT_CONSOLE_WINDOW_WIDTH
from gi.repository import Gtk, Gdk, GObject
import os
import logging
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GObject
from .Constants import DEFAULT_CONSOLE_WINDOW_WIDTH
from .Dialogs import TextDisplay, MessageDialogWrapper
from ..core import Messages
log = logging.getLogger(__name__)

View File

@ -91,7 +91,8 @@ and kindly ask to update their GRC Block Descriptions or Block Tree to include a
# _SCREEN = Gdk.Screen.get_default()
# _SCREEN_RESOLUTION = _SCREEN.get_resolution() if _SCREEN else -1
# DPI_SCALING = _SCREEN_RESOLUTION / 96.0 if _SCREEN_RESOLUTION > 0 else 1.0
DPI_SCALING = 1.0 # todo: figure out the GTK3 way (maybe cairo does this for us
# todo: figure out the GTK3 way (maybe cairo does this for us
DPI_SCALING = 1.0
# Gtk-themes classified as dark
GTK_DARK_THEMES = [
@ -113,7 +114,10 @@ def update_font_size(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
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)

View File

@ -237,7 +237,8 @@ class ErrorsDialog(Gtk.Dialog):
aspect = "Connection to '{}'".format(element.sink_block.name)
elif element.is_port:
src = element.parent_block.name
aspect = "{} '{}'".format('Sink' if element.is_sink else 'Source', element.name)
aspect = "{} '{}'".format(
'Sink' if element.is_sink else 'Source', element.name)
elif element.is_param:
src = element.parent_block.name
aspect = "Param '{}'".format(element.name)
@ -279,7 +280,7 @@ def show_about(parent, config):
except GLib.Error:
Messages.send("Failed to set window logo\n")
#ad.set_comments("")
# ad.set_comments("")
ad.set_copyright(config.license.splitlines()[0])
ad.set_website(config.website)
@ -311,6 +312,7 @@ def show_help(parent):
parent, Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE, title='Help', markup=markup
).run_and_destroy()
def show_keyboard_shortcuts(parent):
""" Display keyboard shortcut-keys. """
markup = textwrap.dedent("""\
@ -371,7 +373,8 @@ def show_get_involved(parent):
def show_types(parent):
""" Display information about standard data types. """
colors = [(name, color) for name, key, sizeof, color in Constants.CORE_TYPES]
colors = [(name, color)
for name, key, sizeof, color in Constants.CORE_TYPES]
max_len = 10 + max(len(name) for name, code in colors)
message = '\n'.join(
@ -424,7 +427,8 @@ def choose_editor(parent, config):
file_dialog = Gtk.FileChooserDialog(
'Select an Editor...', None,
Gtk.FileChooserAction.OPEN,
('gtk-cancel', Gtk.ResponseType.CANCEL, 'gtk-open', Gtk.ResponseType.OK),
('gtk-cancel', Gtk.ResponseType.CANCEL,
'gtk-open', Gtk.ResponseType.OK),
transient_for=parent
)
file_dialog.set_select_multiple(False)
@ -449,7 +453,8 @@ def choose_editor(parent, config):
# Save
editor = config.editor = process
except Exception:
Messages.send('>>> Unable to load the default editor. Please choose an editor.\n')
Messages.send(
'>>> Unable to load the default editor. Please choose an editor.\n')
if editor == '':
Messages.send('>>> No editor selected.\n')

View File

@ -79,10 +79,10 @@ class DrawingArea(Gtk.DrawingArea):
self.set_can_focus(True)
self.connect('focus-out-event', self._handle_focus_lost_event)
##########################################################################
# Handlers
##########################################################################
def _handle_drag_data_received(self, widget, drag_context, x, y, selection_data, info, time):
"""
Handle a drag and drop by adding a block at the given coordinate.
@ -96,7 +96,7 @@ class DrawingArea(Gtk.DrawingArea):
self._set_zoom_factor(zoom_factor)
def zoom_out(self):
change = 1/1.2
change = 1 / 1.2
zoom_factor = max(self.zoom_factor * change, 0.1)
self._set_zoom_factor(zoom_factor)

View File

@ -80,7 +80,8 @@ class ExecFlowGraphThread(threading.Thread):
Execute this C++ flow graph after generating and compiling it.
"""
generator = self.page.get_generator()
run_command = generator.file_path + '/build/' + self.flow_graph.get_option('id')
run_command = generator.file_path + \
'/build/' + self.flow_graph.get_option('id')
dirname = generator.file_path
builddir = os.path.join(dirname, 'build')

View File

@ -41,7 +41,8 @@ class FileDialogHelper(Gtk.FileChooserDialog, object):
Gtk.FileChooserDialog.__init__(self, title=self.title, action=self.action,
transient_for=parent)
self.add_buttons('gtk-cancel', Gtk.ResponseType.CANCEL, ok_stock, Gtk.ResponseType.OK)
self.add_buttons('gtk-cancel', Gtk.ResponseType.CANCEL,
ok_stock, Gtk.ResponseType.OK)
self.set_select_multiple(False)
self.set_local_only(True)
@ -49,12 +50,14 @@ class FileDialogHelper(Gtk.FileChooserDialog, object):
self.current_file_path = current_file_path or path.join(
Constants.DEFAULT_FILE_PATH, Constants.NEW_FLOGRAPH_TITLE + Constants.FILE_EXTENSION)
self.set_current_folder(path.dirname(current_file_path)) # current directory
self.set_current_folder(path.dirname(
current_file_path)) # current directory
self.setup_filters()
def setup_filters(self, filters=None):
set_default = True
filters = filters or ([(self.filter_label, self.filter_ext)] if self.filter_label else [])
filters = filters or (
[(self.filter_label, self.filter_ext)] if self.filter_label else [])
filters.append(('All Files', ''))
for label, ext in filters:
if not label:
@ -81,7 +84,8 @@ class SaveFileDialog(FileDialogHelper):
def __init__(self, parent, current_file_path):
super(SaveFileDialog, self).__init__(parent, current_file_path)
self.set_current_name(path.splitext(path.basename(self.current_file_path))[0] + self.filter_ext)
self.set_current_name(path.splitext(path.basename(
self.current_file_path))[0] + self.filter_ext)
self.set_create_folders(True)
self.set_do_overwrite_confirmation(True)
@ -94,7 +98,8 @@ class OpenFileDialog(FileDialogHelper):
Dialogs.MessageDialogWrapper(
self.parent,
Gtk.MessageType.WARNING, Gtk.ButtonsType.CLOSE, 'Cannot Open!',
'File <b>{filename}</b> Does not Exist!'.format(filename=Utils.encode(filename)),
'File <b>{filename}</b> Does not Exist!'.format(
filename=Utils.encode(filename)),
).run_and_destroy()
def get_filename(self):
@ -145,7 +150,8 @@ class SaveConsole(SaveFileDialog):
class SaveScreenShot(SaveFileDialog):
title = 'Save a Flow Graph Screen Shot...'
filters = [('PDF Files', '.pdf'), ('PNG Files', '.png'), ('SVG Files', '.svg')]
filters = [('PDF Files', '.pdf'), ('PNG Files', '.png'),
('SVG Files', '.svg')]
filter_ext = '.pdf' # the default
def __init__(self, parent, current_file_path=''):
@ -154,7 +160,8 @@ class SaveScreenShot(SaveFileDialog):
self.config = Gtk.Application.get_default().config
self._button = button = Gtk.CheckButton(label='Background transparent')
self._button.set_active(self.config.screen_shot_background_transparent())
self._button.set_active(
self.config.screen_shot_background_transparent())
self.set_extra_widget(button)
def setup_filters(self, filters=None):
@ -164,7 +171,8 @@ class SaveScreenShot(SaveFileDialog):
Dialogs.MessageDialogWrapper(
self.parent,
Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, 'Can not Save!',
'File Extension of <b>{filename}</b> not supported!'.format(filename=Utils.encode(filename)),
'File Extension of <b>{filename}</b> not supported!'.format(
filename=Utils.encode(filename)),
).run_and_destroy()
def run(self):

View File

@ -43,7 +43,8 @@ class MainWindow(Gtk.ApplicationWindow):
MainWindow constructor
Setup the menu, toolbar, flow graph editor notebook, block selection window...
"""
Gtk.ApplicationWindow.__init__(self, title="GNU Radio Companion", application=app)
Gtk.ApplicationWindow.__init__(
self, title="GNU Radio Companion", application=app)
log.debug("__init__()")
self._platform = platform
@ -63,7 +64,8 @@ class MainWindow(Gtk.ApplicationWindow):
icon = icon_theme.lookup_icon("gnuradio-grc", 48, 0)
if not icon:
# Set default window icon
self.set_icon_from_file(os.path.dirname(os.path.abspath(__file__)) + "/icon.png")
self.set_icon_from_file(os.path.dirname(
os.path.abspath(__file__)) + "/icon.png")
else:
# Use gnuradio icon
self.set_icon(icon.load_icon())
@ -85,7 +87,7 @@ class MainWindow(Gtk.ApplicationWindow):
vbox.pack_start(self.tool_bar, False, False, 0)
# Main parent container for the different panels
self.main = Gtk.HPaned() #(orientation=Gtk.Orientation.HORIZONTAL)
self.main = Gtk.HPaned() # (orientation=Gtk.Orientation.HORIZONTAL)
vbox.pack_start(self.main, True, True, 0)
# Create the notebook
@ -99,15 +101,19 @@ class MainWindow(Gtk.ApplicationWindow):
# Create the block tree and variable panels
self.btwin = BlockTreeWindow(platform)
self.btwin.connect('create_new_block', self._add_block_to_current_flow_graph)
self.btwin.connect('create_new_block',
self._add_block_to_current_flow_graph)
self.vars = VariableEditor()
self.vars.connect('create_new_block', self._add_block_to_current_flow_graph)
self.vars.connect('remove_block', self._remove_block_from_current_flow_graph)
self.vars.connect('create_new_block',
self._add_block_to_current_flow_graph)
self.vars.connect(
'remove_block', self._remove_block_from_current_flow_graph)
# Figure out which place to put the variable editor
self.left = Gtk.VPaned() #orientation=Gtk.Orientation.VERTICAL)
self.right = Gtk.VPaned() #orientation=Gtk.Orientation.VERTICAL)
self.left_subpanel = Gtk.HPaned() #orientation=Gtk.Orientation.HORIZONTAL)
self.left = Gtk.VPaned() # orientation=Gtk.Orientation.VERTICAL)
self.right = Gtk.VPaned() # orientation=Gtk.Orientation.VERTICAL)
# orientation=Gtk.Orientation.HORIZONTAL)
self.left_subpanel = Gtk.HPaned()
self.variable_panel_sidebar = self.config.variable_editor_sidebar()
if self.variable_panel_sidebar:
@ -133,9 +139,11 @@ class MainWindow(Gtk.ApplicationWindow):
self.main.set_position(self.config.blocks_window_position())
self.left.set_position(self.config.console_window_position())
if self.variable_panel_sidebar:
self.right.set_position(self.config.variable_editor_position(sidebar=True))
self.right.set_position(
self.config.variable_editor_position(sidebar=True))
else:
self.left_subpanel.set_position(self.config.variable_editor_position())
self.left_subpanel.set_position(
self.config.variable_editor_position())
self.show_all()
log.debug("Main window ready")
@ -238,16 +246,18 @@ class MainWindow(Gtk.ApplicationWindow):
file_path: optional file to load into the flow graph
show: true if the page should be shown after loading
"""
#if the file is already open, show the open page and return
if file_path and file_path in self._get_files(): #already open
page = self.notebook.get_nth_page(self._get_files().index(file_path))
# if the file is already open, show the open page and return
if file_path and file_path in self._get_files(): # already open
page = self.notebook.get_nth_page(
self._get_files().index(file_path))
self._set_page(page)
return
try: #try to load from file
if file_path: Messages.send_start_load(file_path)
try: # try to load from file
if file_path:
Messages.send_start_load(file_path)
flow_graph = self._platform.make_flow_graph()
flow_graph.grc_file_path = file_path
#print flow_graph
# print flow_graph
page = Page(
self,
flow_graph=flow_graph,
@ -260,18 +270,20 @@ class MainWindow(Gtk.ApplicationWindow):
str(Messages.flowgraph_error)
)
)
if file_path: Messages.send_end_load()
except Exception as e: #return on failure
if file_path:
Messages.send_end_load()
except Exception as e: # return on failure
Messages.send_fail_load(e)
if isinstance(e, KeyError) and str(e) == "'options'":
# This error is unrecoverable, so crash gracefully
exit(-1)
return
#add this page to the notebook
# add this page to the notebook
self.notebook.append_page(page, page.tab)
self.notebook.set_tab_reorderable(page, True)
#only show if blank or manual
if not file_path or show: self._set_page(page)
# only show if blank or manual
if not file_path or show:
self._set_page(page)
def close_pages(self):
"""
@ -280,25 +292,29 @@ class MainWindow(Gtk.ApplicationWindow):
Returns:
true if all closed
"""
open_files = [file for file in self._get_files() if file] #filter blank files
open_files = [file for file in self._get_files()
if file] # filter blank files
open_file = self.current_page.file_path
#close each page
# close each page
for page in sorted(self.get_pages(), key=lambda p: p.saved):
self.page_to_be_closed = page
closed = self.close_page(False)
if not closed:
break
if self.notebook.get_n_pages(): return False
#save state before closing
if self.notebook.get_n_pages():
return False
# save state before closing
self.config.set_open_files(open_files)
self.config.file_open(open_file)
self.config.main_window_size(self.get_size())
self.config.console_window_position(self.left.get_position())
self.config.blocks_window_position(self.main.get_position())
if self.variable_panel_sidebar:
self.config.variable_editor_position(self.right.get_position(), sidebar=True)
self.config.variable_editor_position(
self.right.get_position(), sidebar=True)
else:
self.config.variable_editor_position(self.left_subpanel.get_position())
self.config.variable_editor_position(
self.left_subpanel.get_position())
self.config.save()
return True
@ -311,29 +327,31 @@ class MainWindow(Gtk.ApplicationWindow):
Args:
ensure: boolean
"""
if not self.page_to_be_closed: self.page_to_be_closed = self.current_page
#show the page if it has an executing flow graph or is unsaved
if not self.page_to_be_closed:
self.page_to_be_closed = self.current_page
# show the page if it has an executing flow graph or is unsaved
if self.page_to_be_closed.process or not self.page_to_be_closed.saved:
self._set_page(self.page_to_be_closed)
#unsaved? ask the user
# unsaved? ask the user
if not self.page_to_be_closed.saved:
response = self._save_changes() # return value is either OK, CLOSE, or CANCEL
response = self._save_changes() # return value is either OK, CLOSE, or CANCEL
if response == Gtk.ResponseType.OK:
Actions.FLOW_GRAPH_SAVE() #try to save
if not self.page_to_be_closed.saved: #still unsaved?
self.page_to_be_closed = None #set the page to be closed back to None
Actions.FLOW_GRAPH_SAVE() # try to save
if not self.page_to_be_closed.saved: # still unsaved?
self.page_to_be_closed = None # set the page to be closed back to None
return False
elif response == Gtk.ResponseType.CANCEL:
self.page_to_be_closed = None
return False
#stop the flow graph if executing
# stop the flow graph if executing
if self.page_to_be_closed.process:
Actions.FLOW_GRAPH_KILL()
#remove the page
self.notebook.remove_page(self.notebook.page_num(self.page_to_be_closed))
# remove the page
self.notebook.remove_page(
self.notebook.page_num(self.page_to_be_closed))
if ensure and self.notebook.get_n_pages() == 0:
self.new_page() #no pages, make a new one
self.page_to_be_closed = None #set the page to be closed back to None
self.new_page() # no pages, make a new one
self.page_to_be_closed = None # set the page to be closed back to None
return True
############################################################
@ -405,7 +423,8 @@ class MainWindow(Gtk.ApplicationWindow):
page: the page widget
"""
self.current_page = page
self.notebook.set_current_page(self.notebook.page_num(self.current_page))
self.notebook.set_current_page(
self.notebook.page_num(self.current_page))
def _save_changes(self):
"""

View File

@ -118,9 +118,12 @@ class Page(Gtk.HBox):
self.viewport.add(self.drawing_area)
self.scrolled_window = Gtk.ScrolledWindow()
self.scrolled_window.set_size_request(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
self.scrolled_window.set_policy(Gtk.PolicyType.ALWAYS, Gtk.PolicyType.ALWAYS)
self.scrolled_window.connect('key-press-event', self._handle_scroll_window_key_press)
self.scrolled_window.set_size_request(
MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
self.scrolled_window.set_policy(
Gtk.PolicyType.ALWAYS, Gtk.PolicyType.ALWAYS)
self.scrolled_window.connect(
'key-press-event', self._handle_scroll_window_key_press)
self.scrolled_window.add(self.viewport)
self.pack_start(self.scrolled_window, True, True, 0)

View File

@ -42,6 +42,7 @@ def have_dark_theme():
return False
return is_dark_theme(theme)
def add_style_provider():
"""
Load GTK styles
@ -55,6 +56,8 @@ def add_style_provider():
style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
add_style_provider()
@ -93,7 +96,8 @@ class InputParam(Gtk.HBox):
"""
Set the markup, color, tooltip, show/hide.
"""
self.label.set_markup(self.param.format_label_markup(self._have_pending_changes))
self.label.set_markup(
self.param.format_label_markup(self._have_pending_changes))
self.set_color('dtype_' + self.param.dtype)
self.set_tooltip_text(self.param.format_tooltip_text())
@ -117,14 +121,14 @@ class InputParam(Gtk.HBox):
Handle a gui change by setting the new param value,
calling the callback (if applicable), and updating.
"""
#set the new value
# set the new value
self.param.set_value(self.get_text())
#call the callback
# call the callback
if self._changed_callback:
self._changed_callback(self, None)
else:
self.param.validate()
#gui update
# gui update
self._have_pending_changes = False
self._update_gui()
@ -205,7 +209,8 @@ class PythonEditorParam(InputParam):
self.pack_start(button, True, True, True)
def open_editor(self, widget=None):
self.param.parent_flowgraph.install_external_editor(self.param, parent=self._transient_for)
self.param.parent_flowgraph.install_external_editor(
self.param, parent=self._transient_for)
def get_text(self):
pass # we never update the value from here
@ -297,13 +302,15 @@ class FileParam(EntryParam):
"""
# get the paths
file_path = self.param.is_valid() and self.param.get_evaluated() or ''
(dirname, basename) = os.path.isfile(file_path) and os.path.split(file_path) or (file_path, '')
(dirname, basename) = os.path.isfile(
file_path) and os.path.split(file_path) or (file_path, '')
# check for qss theme default directory
if self.param.key == 'qt_qss_theme':
dirname = os.path.dirname(dirname) # trim filename
if not os.path.exists(dirname):
config = self.param.parent_platform.config
dirname = os.path.join(config.install_prefix, '/share/gnuradio/themes')
config = self.param.parent_platform.config
dirname = os.path.join(
config.install_prefix, '/share/gnuradio/themes')
if not os.path.exists(dirname):
dirname = os.getcwd() # fix bad paths
@ -313,17 +320,20 @@ class FileParam(EntryParam):
title='Open a Data File...', action=Gtk.FileChooserAction.OPEN,
transient_for=self._transient_for,
)
file_dialog.add_buttons('gtk-cancel', Gtk.ResponseType.CANCEL, 'gtk-open', Gtk.ResponseType.OK)
file_dialog.add_buttons(
'gtk-cancel', Gtk.ResponseType.CANCEL, 'gtk-open', Gtk.ResponseType.OK)
elif self.param.dtype == 'file_save':
file_dialog = Gtk.FileChooserDialog(
title='Save a Data File...', action=Gtk.FileChooserAction.SAVE,
transient_for=self._transient_for,
)
file_dialog.add_buttons('gtk-cancel', Gtk.ResponseType.CANCEL, 'gtk-save', Gtk.ResponseType.OK)
file_dialog.add_buttons(
'gtk-cancel', Gtk.ResponseType.CANCEL, 'gtk-save', Gtk.ResponseType.OK)
file_dialog.set_do_overwrite_confirmation(True)
file_dialog.set_current_name(basename) # show the current filename
else:
raise ValueError("Can't open file chooser dialog for type " + repr(self.param.dtype))
raise ValueError(
"Can't open file chooser dialog for type " + repr(self.param.dtype))
file_dialog.set_current_folder(dirname) # current directory
file_dialog.set_select_multiple(False)
file_dialog.set_local_only(True)
@ -334,6 +344,7 @@ class FileParam(EntryParam):
self._apply_change()
file_dialog.destroy() # destroy the dialog
class DirectoryParam(FileParam):
"""Provide an entry box for a directory and a button to browse for it."""
@ -344,23 +355,26 @@ class DirectoryParam(FileParam):
"""
dirname = self.param.get_evaluated() if self.param.is_valid() else ''
if not os.path.isdir(dirname): # Check if directory exists, if not fall back to workdir
# Check if directory exists, if not fall back to workdir
if not os.path.isdir(dirname):
dirname = os.getcwd()
if self.param.dtype == "dir_select": # Setup directory selection dialog, and fail for unexpected dtype
if self.param.dtype == "dir_select": # Setup directory selection dialog, and fail for unexpected dtype
dir_dialog = Gtk.FileChooserDialog(
title='Select a Directory...', action=Gtk.FileChooserAction.SELECT_FOLDER,
transient_for=self._transient_for
)
else:
raise ValueError("Can't open directory chooser dialog for type " + repr(self.param.dtype))
raise ValueError(
"Can't open directory chooser dialog for type " + repr(self.param.dtype))
# Set dialog properties
dir_dialog.add_buttons('gtk-cancel', Gtk.ResponseType.CANCEL, 'gtk-open', Gtk.ResponseType.OK)
dir_dialog.add_buttons(
'gtk-cancel', Gtk.ResponseType.CANCEL, 'gtk-open', Gtk.ResponseType.OK)
dir_dialog.set_current_folder(dirname)
dir_dialog.set_local_only(True)
dir_dialog.set_select_multiple(False)
# Show dialog and update parameter on success
if Gtk.ResponseType.OK == dir_dialog.run():
path = dir_dialog.get_filename()

View File

@ -24,7 +24,8 @@ class ParserErrorsDialog(Gtk.Dialog):
Args:
block: a block instance
"""
GObject.GObject.__init__(self, title='Parser Errors', buttons=(Gtk.STOCK_CLOSE, Gtk.ResponseType.ACCEPT))
GObject.GObject.__init__(self, title='Parser Errors', buttons=(
Gtk.STOCK_CLOSE, Gtk.ResponseType.ACCEPT))
self._error_logs = None
self.tree_store = Gtk.TreeStore(str)
@ -48,11 +49,12 @@ class ParserErrorsDialog(Gtk.Dialog):
tree_view.expand_row(row.path, False)
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scrolled_window.set_policy(
Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scrolled_window.add(tree_view)
self.vbox.pack_start(scrolled_window, True)
self.set_size_request(2*MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT)
self.set_size_request(2 * MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT)
self.show_all()
def update_tree_store(self, error_logs):
@ -68,7 +70,8 @@ class ParserErrorsDialog(Gtk.Dialog):
code = None
for error in errors:
# http://lxml.de/api/lxml.etree._LogEntry-class.html
em = self.tree_store.append(parent, ["Line {e.line}: {e.message}".format(e=error)])
em = self.tree_store.append(
parent, ["Line {e.line}: {e.message}".format(e=error)])
if code:
self.tree_store.append(em, ["\n".join(
"{} {}{}".format(line, code[line - 1].replace("\t", " ").strip("\n"),

View File

@ -64,7 +64,8 @@ class PropsDialog(Gtk.Dialog):
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.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
self._docs_box.set_policy(
Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
self._docs_vbox = Gtk.VBox(homogeneous=False, spacing=0)
self._docs_box.add(self._docs_vbox)
self._docs_link = Gtk.Label(use_markup=True)
@ -81,7 +82,8 @@ class PropsDialog(Gtk.Dialog):
# todo: set font size in non-deprecated way
# code_view.override_font(Pango.FontDescription('monospace %d' % Constants.FONT_SIZE))
code_box = Gtk.ScrolledWindow()
code_box.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
code_box.set_policy(Gtk.PolicyType.AUTOMATIC,
Gtk.PolicyType.AUTOMATIC)
code_box.add(self._code_text_display)
notebook.append_page(code_box, Gtk.Label(label="Generated Code"))
else:
@ -90,7 +92,8 @@ class PropsDialog(Gtk.Dialog):
# Error Messages for the block
self._error_messages_text_display = SimpleTextDisplay()
self._error_box = Gtk.ScrolledWindow()
self._error_box.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
self._error_box.set_policy(
Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
self._error_box.add(self._error_messages_text_display)
vpaned.pack2(self._error_box)
vpaned.set_position(int(0.65 * Constants.MIN_DIALOG_HEIGHT))
@ -117,7 +120,8 @@ class PropsDialog(Gtk.Dialog):
label = Gtk.Label()
vbox = Gtk.VBox()
scroll_box = Gtk.ScrolledWindow()
scroll_box.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scroll_box.set_policy(Gtk.PolicyType.AUTOMATIC,
Gtk.PolicyType.AUTOMATIC)
scroll_box.add(vbox)
self.notebook.append_page(scroll_box, label)
self._params_boxes.append((category, label, vbox))
@ -232,7 +236,8 @@ class PropsDialog(Gtk.Dialog):
buf.insert(pos, '\n')
# if given the current parameters an exact match can be made
block_constructor = self._block.templates.render('make').rsplit('.', 2)[-1]
block_constructor = self._block.templates.render(
'make').rsplit('.', 2)[-1]
block_class = block_constructor.partition('(')[0].strip()
if block_class in docstrings:
docstrings = {block_class: docstrings[block_class]}

View File

@ -9,6 +9,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
from . import Actions
from .Constants import STATE_CACHE_SIZE
class StateCache(object):
"""
The state cache is an interface to a list to record data/states and to revert to previous states.
@ -23,7 +24,7 @@ class StateCache(object):
Args:
initial_state: the initial state (nested data)
"""
self.states = [None] * STATE_CACHE_SIZE #fill states
self.states = [None] * STATE_CACHE_SIZE # fill states
self.current_state_index = 0
self.num_prev_states = 0
self.num_next_states = 0
@ -38,10 +39,12 @@ class StateCache(object):
Args:
state: the new state
"""
self.current_state_index = (self.current_state_index + 1)%STATE_CACHE_SIZE
self.current_state_index = (
self.current_state_index + 1) % STATE_CACHE_SIZE
self.states[self.current_state_index] = state
self.num_prev_states = self.num_prev_states + 1
if self.num_prev_states == STATE_CACHE_SIZE: self.num_prev_states = STATE_CACHE_SIZE - 1
if self.num_prev_states == STATE_CACHE_SIZE:
self.num_prev_states = STATE_CACHE_SIZE - 1
self.num_next_states = 0
self.update_actions()
@ -63,7 +66,8 @@ class StateCache(object):
the previous state or None
"""
if self.num_prev_states > 0:
self.current_state_index = (self.current_state_index + STATE_CACHE_SIZE -1)%STATE_CACHE_SIZE
self.current_state_index = (
self.current_state_index + STATE_CACHE_SIZE - 1) % STATE_CACHE_SIZE
self.num_next_states = self.num_next_states + 1
self.num_prev_states = self.num_prev_states - 1
return self.get_current_state()
@ -77,7 +81,8 @@ class StateCache(object):
the next state or None
"""
if self.num_next_states > 0:
self.current_state_index = (self.current_state_index + 1)%STATE_CACHE_SIZE
self.current_state_index = (
self.current_state_index + 1) % STATE_CACHE_SIZE
self.num_next_states = self.num_next_states - 1
self.num_prev_states = self.num_prev_states + 1
return self.get_current_state()

View File

@ -32,7 +32,7 @@ def get_rotated_coordinate(coor, rotation):
# handles negative angles
rotation = (rotation + 360) % 360
if rotation not in Constants.POSSIBLE_ROTATIONS:
raise ValueError('unusable rotation angle "%s"'%str(rotation))
raise ValueError('unusable rotation angle "%s"' % str(rotation))
# determine the number of degrees to rotate
cos_r, sin_r = {
0: (1, 0), 90: (0, 1), 180: (-1, 0), 270: (0, -1),
@ -76,7 +76,7 @@ def num_to_str(num):
"""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'):
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())
@ -87,7 +87,8 @@ def num_to_str(num):
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 ''
output += eng_notation(num.imag, '+g' if output else 'g') + \
'j' if num.imag else ''
return output
else:
return str(num)
@ -144,6 +145,7 @@ def scale_scalar(coor, reverse=False):
factor = Constants.DPI_SCALING if not reverse else 1 / Constants.DPI_SCALING
return int(coor * factor)
def get_modifier_key(angle_brackets=False):
"""
Get the modifier key based on platform.
@ -167,9 +169,11 @@ def get_modifier_key(angle_brackets=False):
_nproc = None
def get_cmake_nproc():
""" Get number of cmake processes for C++ flowgraphs """
global _nproc # Cached result
global _nproc # Cached result
if _nproc:
return _nproc
try:
@ -180,5 +184,5 @@ def get_cmake_nproc():
if not _nproc:
_nproc = 1
_nproc = max(_nproc//2 - 1, 1)
_nproc = max(_nproc // 2 - 1, 1)
return _nproc

View File

@ -22,29 +22,35 @@ class VariableEditorContextMenu(Gtk.Menu):
Gtk.Menu.__init__(self)
self.imports = Gtk.MenuItem(label="Add _Import")
self.imports.connect('activate', var_edit.handle_action, var_edit.ADD_IMPORT)
self.imports.connect(
'activate', var_edit.handle_action, var_edit.ADD_IMPORT)
self.add(self.imports)
self.variables = Gtk.MenuItem(label="Add _Variable")
self.variables.connect('activate', var_edit.handle_action, var_edit.ADD_VARIABLE)
self.variables.connect(
'activate', var_edit.handle_action, var_edit.ADD_VARIABLE)
self.add(self.variables)
self.add(Gtk.SeparatorMenuItem())
self.enable = Gtk.MenuItem(label="_Enable")
self.enable.connect('activate', var_edit.handle_action, var_edit.ENABLE_BLOCK)
self.enable.connect(
'activate', var_edit.handle_action, var_edit.ENABLE_BLOCK)
self.disable = Gtk.MenuItem(label="_Disable")
self.disable.connect('activate', var_edit.handle_action, var_edit.DISABLE_BLOCK)
self.disable.connect(
'activate', var_edit.handle_action, var_edit.DISABLE_BLOCK)
self.add(self.enable)
self.add(self.disable)
self.add(Gtk.SeparatorMenuItem())
self.delete = Gtk.MenuItem(label="_Delete")
self.delete.connect('activate', var_edit.handle_action, var_edit.DELETE_BLOCK)
self.delete.connect(
'activate', var_edit.handle_action, var_edit.DELETE_BLOCK)
self.add(self.delete)
self.add(Gtk.SeparatorMenuItem())
self.properties = Gtk.MenuItem(label="_Properties...")
self.properties.connect('activate', var_edit.handle_action, var_edit.OPEN_PROPERTIES)
self.properties.connect(
'activate', var_edit.handle_action, var_edit.OPEN_PROPERTIES)
self.add(self.properties)
self.show_all()
@ -87,13 +93,16 @@ class VariableEditor(Gtk.VBox):
self.treeview = Gtk.TreeView(model=self.treestore)
self.treeview.set_enable_search(False)
self.treeview.set_search_column(-1)
#self.treeview.set_enable_search(True)
#self.treeview.set_search_column(ID_INDEX)
# self.treeview.set_enable_search(True)
# self.treeview.set_search_column(ID_INDEX)
self.treeview.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
self.treeview.set_headers_visible(True)
self.treeview.connect('button-press-event', self._handle_mouse_button_press)
self.treeview.connect('button-release-event', self._handle_mouse_button_release)
self.treeview.connect('motion-notify-event', self._handle_motion_notify)
self.treeview.connect('button-press-event',
self._handle_mouse_button_press)
self.treeview.connect('button-release-event',
self._handle_mouse_button_release)
self.treeview.connect('motion-notify-event',
self._handle_motion_notify)
self.treeview.connect('key-press-event', self._handle_key_button_press)
# Block Name or Category
@ -133,9 +142,11 @@ class VariableEditor(Gtk.VBox):
# 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.set_policy(
Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scrolled_window.add(self.treeview)
scrolled_window.set_size_request(Constants.DEFAULT_BLOCKS_WINDOW_WIDTH, -1)
scrolled_window.set_size_request(
Constants.DEFAULT_BLOCKS_WINDOW_WIDTH, -1)
self.pack_start(scrolled_window, True, True, 0)
# Context menus
@ -215,9 +226,11 @@ class VariableEditor(Gtk.VBox):
imports = self.treestore.append(None, [None, 'Imports'])
variables = self.treestore.append(None, [None, 'Variables'])
for block in self._imports:
self.treestore.append(imports, [block, block.params['id'].get_value()])
self.treestore.append(
imports, [block, block.params['id'].get_value()])
for block in sorted(self._variables, key=lambda v: v.name):
self.treestore.append(variables, [block, block.params['id'].get_value()])
self.treestore.append(
variables, [block, block.params['id'].get_value()])
def _handle_name_edited_cb(self, cell, path, new_text):
block = self.treestore[path][BLOCK_INDEX]
@ -243,7 +256,7 @@ class VariableEditor(Gtk.VBox):
self.emit('create_new_block', 'variable')
elif key == self.OPEN_PROPERTIES:
# TODO: This probably isn't working because the action doesn't expect a parameter
#Actions.BLOCK_PARAM_MODIFY()
# Actions.BLOCK_PARAM_MODIFY()
pass
elif key == self.DELETE_BLOCK:
self.emit('remove_block', self._block.name)
@ -251,12 +264,15 @@ class VariableEditor(Gtk.VBox):
if self._confirm_delete:
# Create a context menu to confirm the delete operation
confirmation_menu = Gtk.Menu()
block_id = self._block.params['id'].get_value().replace("_", "__")
block_id = self._block.params['id'].get_value().replace(
"_", "__")
confirm = Gtk.MenuItem(label="Delete {}".format(block_id))
confirm.connect('activate', self.handle_action, self.DELETE_BLOCK)
confirm.connect('activate', self.handle_action,
self.DELETE_BLOCK)
confirmation_menu.add(confirm)
confirmation_menu.show_all()
confirmation_menu.popup(None, None, None, None, event.button, event.time)
confirmation_menu.popup(
None, None, None, None, event.button, event.time)
else:
self.handle_action(None, self.DELETE_BLOCK, None)
elif key == self.ENABLE_BLOCK:
@ -287,7 +303,8 @@ class VariableEditor(Gtk.VBox):
if self._block and event.type == Gdk.EventType._2BUTTON_PRESS:
# Open the advanced dialog if it is a gui variable
if self._block.key not in ("variable", "import"):
self.handle_action(None, self.OPEN_PROPERTIES, event=event)
self.handle_action(
None, self.OPEN_PROPERTIES, event=event)
return True
if event.type == Gdk.EventType.BUTTON_PRESS:
# User is adding/removing blocks
@ -295,19 +312,24 @@ class VariableEditor(Gtk.VBox):
if path[2] > col.cell_get_position(self.action_cell)[0]:
if row[1] == "Imports":
# Add a new import block.
self.handle_action(None, self.ADD_IMPORT, event=event)
self.handle_action(
None, self.ADD_IMPORT, event=event)
elif row[1] == "Variables":
# Add a new variable block
self.handle_action(None, self.ADD_VARIABLE, event=event)
self.handle_action(
None, self.ADD_VARIABLE, event=event)
else:
self.handle_action(None, self.DELETE_CONFIRM, event=event)
self.handle_action(
None, self.DELETE_CONFIRM, event=event)
return True
elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS:
if self._block:
self._context_menu.update_sensitive(True, enabled=self._block.enabled)
self._context_menu.update_sensitive(
True, enabled=self._block.enabled)
else:
self._context_menu.update_sensitive(False)
self._context_menu.popup(None, None, None, None, event.button, event.time)
self._context_menu.popup(
None, None, None, None, event.button, event.time)
# Null handler. Stops the treeview from handling double click events.
if event.type == Gdk.EventType._2BUTTON_PRESS:

View File

@ -1 +0,0 @@

View File

@ -64,7 +64,8 @@ class Block(CoreBlock, Drawable):
"""
coor = Utils.scale(coor, reverse=True)
if Actions.TOGGLE_SNAP_TO_GRID.get_active():
offset_x, offset_y = (0, self.height / 2) if self.is_horizontal() else (self.height / 2, 0)
offset_x, offset_y = (
0, self.height / 2) if self.is_horizontal() else (self.height / 2, 0)
coor = (
Utils.align_to_grid(coor[0] + offset_x) - offset_x,
Utils.align_to_grid(coor[1] + offset_y) - offset_y
@ -137,8 +138,10 @@ class Block(CoreBlock, Drawable):
for ports, has_busses in zip((self.active_sources, self.active_sinks), bussified):
if not ports:
continue
port_separation = Constants.PORT_SEPARATION if not has_busses else ports[0].height + Constants.PORT_SPACING
offset = (self.height - (len(ports) - 1) * port_separation - ports[0].height) / 2
port_separation = Constants.PORT_SEPARATION if not has_busses else ports[
0].height + Constants.PORT_SPACING
offset = (self.height - (len(ports) - 1) *
port_separation - ports[0].height) / 2
for port in ports:
port.create_shapes()
port.coordinate = {
@ -148,7 +151,8 @@ class Block(CoreBlock, Drawable):
270: (offset, +self.width),
}[port.connector_direction]
offset += Constants.PORT_SEPARATION if not has_busses else port.height + Constants.PORT_SPACING
offset += Constants.PORT_SEPARATION if not has_busses else port.height + \
Constants.PORT_SPACING
def create_labels(self, cr=None):
"""Create the labels for the signal block."""
@ -178,7 +182,8 @@ class Block(CoreBlock, Drawable):
markups = [param.format_block_surface_markup()
for param in self.params.values() if (param.hide not in ('all', 'part') or (param.dtype == 'id' and force_show_id))]
else:
markups = ['<span font_desc="{font}"><b>key: </b>{key}</span>'.format(font=Constants.PARAM_FONT, key=self.key)]
markups = ['<span font_desc="{font}"><b>key: </b>{key}</span>'.format(
font=Constants.PARAM_FONT, key=self.key)]
params_layout.set_spacing(Constants.LABEL_SEPARATION * Pango.SCALE)
params_layout.set_markup('\n'.join(markups))
@ -197,12 +202,13 @@ class Block(CoreBlock, Drawable):
self.create_port_labels()
def get_min_height_for_ports(ports):
min_height = 2 * Constants.PORT_BORDER_SEPARATION + len(ports) * Constants.PORT_SEPARATION
min_height = 2 * Constants.PORT_BORDER_SEPARATION + \
len(ports) * Constants.PORT_SEPARATION
# If any of the ports are bus ports - make the min height larger
if any([p.dtype == 'bus' for p in ports]):
min_height = 2 * Constants.PORT_BORDER_SEPARATION + sum(
port.height + Constants.PORT_SPACING for port in ports if port.dtype == 'bus'
) - Constants.PORT_SPACING
) - Constants.PORT_SPACING
else:
if ports:
@ -213,11 +219,13 @@ class Block(CoreBlock, Drawable):
get_min_height_for_ports(self.active_sinks),
get_min_height_for_ports(self.active_sources))
self.width, self.height = width, height = Utils.align_to_grid((width, height))
self.width, self.height = width, height = Utils.align_to_grid(
(width, height))
self._surface_layouts_offsets = [
(0, (height - label_height) / 2.0),
(0, (height - label_height) / 2.0 + Constants.LABEL_SEPARATION + title_height / Pango.SCALE)
(0, (height - label_height) / 2.0 +
Constants.LABEL_SEPARATION + title_height / Pango.SCALE)
]
title_layout.set_width(width * Pango.SCALE)
@ -243,7 +251,8 @@ class Block(CoreBlock, Drawable):
complexity = utils.flow_graph_complexity.calculate(self.parent)
markups.append(
'<span foreground="#444" size="medium" font_desc="{font}">'
'<b>Complexity: {num}bal</b></span>'.format(num=Utils.num_to_str(complexity), font=Constants.BLOCK_FONT)
'<b>Complexity: {num}bal</b></span>'.format(
num=Utils.num_to_str(complexity), font=Constants.BLOCK_FONT)
)
comment = self.comment # Returns None if there are no comments
if comment:
@ -303,7 +312,8 @@ class Block(CoreBlock, Drawable):
for port in self.active_ports():
port_selected = port.what_is_selected(
coor=[a - b for a, b in zip(coor, self.coordinate)],
coor_m=[a - b for a, b in zip(coor, self.coordinate)] if coor_m is not None else None
coor_m=[
a - b for a, b in zip(coor, self.coordinate)] if coor_m is not None else None
)
if port_selected:
return port_selected
@ -359,7 +369,8 @@ class Block(CoreBlock, Drawable):
true for change
"""
type_templates = ' '.join(p.dtype for p in self.params.values()) + ' '
type_templates += ' '.join(p.get_raw('dtype') for p in (self.sinks + self.sources))
type_templates += ' '.join(p.get_raw('dtype')
for p in (self.sinks + self.sources))
type_param = None
for key, param in self.params.items():
if not param.is_enum():
@ -396,7 +407,8 @@ class Block(CoreBlock, Drawable):
"""
changed = False
# Concat the nports string from the private nports settings of all ports
nports_str = ' '.join(str(port.get_raw('multiplicity')) for port in self.ports())
nports_str = ' '.join(str(port.get_raw('multiplicity'))
for port in self.ports())
# Modify all params whose keys appear in the nports string
for key, param in self.params.items():
if param.is_enum() or param.key not in nports_str:

View File

@ -25,6 +25,7 @@ def get_color(color_code):
# fg colors
#################################################################################
HIGHLIGHT_COLOR = get_color('#00FFFF')
BORDER_COLOR = get_color('#616161')
BORDER_COLOR_DISABLED = get_color('#888888')
@ -62,8 +63,10 @@ DEFAULT_DOMAIN_COLOR = get_color('#777777')
# port colors
#################################################################################
PORT_TYPE_TO_COLOR = {key: get_color(color) for name, key, sizeof, color in Constants.CORE_TYPES}
PORT_TYPE_TO_COLOR.update((key, get_color(color)) for key, (_, color) in Constants.ALIAS_TYPES.items())
PORT_TYPE_TO_COLOR = {key: get_color(
color) for name, key, sizeof, color in Constants.CORE_TYPES}
PORT_TYPE_TO_COLOR.update((key, get_color(color))
for key, (_, color) in Constants.ALIAS_TYPES.items())
#################################################################################
@ -109,4 +112,3 @@ LIGHT_THEME_STYLES = b"""
#enum_custom { background-color: #EEEEEE; }
"""

View File

@ -73,11 +73,13 @@ class Connection(CoreConnection, Drawable):
# first two components relative to source connector, rest relative to sink connector
self._rel_points = [
rotate((15, 0), source.rotation), # line from 0,0 to here, bezier curve start
# line from 0,0 to here, bezier curve start
rotate((15, 0), source.rotation),
rotate((50, 0), source.rotation), # bezier curve control point 1
rotate((-50, 0), sink.rotation), # bezier curve control point 2
rotate((-15, 0), sink.rotation), # bezier curve end
rotate((-CONNECTOR_ARROW_HEIGHT, 0), sink.rotation), # line to arrow head
rotate((-CONNECTOR_ARROW_HEIGHT, 0),
sink.rotation), # line to arrow head
]
self._current_coordinates = None # triggers _make_path()
@ -110,7 +112,8 @@ class Connection(CoreConnection, Drawable):
# make rel_point all relative to source connector
p0 = 0, 0 # x_start - x_pos, y_start - y_pos
p1, p2, (dx_e1, dy_e1), (dx_e2, dy_e2), (dx_e3, dy_e3) = self._rel_points
p1, p2, (dx_e1, dy_e1), (dx_e2, dy_e2), (dx_e3,
dy_e3) = self._rel_points
p3 = x_e + dx_e1, y_e + dy_e1
p4 = x_e + dx_e2, y_e + dy_e2
p5 = x_e + dx_e3, y_e + dy_e3
@ -137,7 +140,8 @@ class Connection(CoreConnection, Drawable):
self.create_shapes() # triggers _make_path() call below
self._current_port_rotations = port_rotations
new_coordinates = (source.parent_block.coordinate, sink.parent_block.coordinate)
new_coordinates = (source.parent_block.coordinate,
sink.parent_block.coordinate)
if self._current_coordinates != new_coordinates:
self._make_path(cr)
self._current_coordinates = new_coordinates
@ -175,7 +179,7 @@ class Connection(CoreConnection, Drawable):
cr.set_source_rgba(*color2)
cr.rotate(self._arrow_rotation)
cr.rel_move_to(CONNECTOR_ARROW_HEIGHT, 0)
cr.rel_line_to(-CONNECTOR_ARROW_HEIGHT, -CONNECTOR_ARROW_BASE/2)
cr.rel_line_to(-CONNECTOR_ARROW_HEIGHT, -CONNECTOR_ARROW_BASE / 2)
cr.rel_line_to(0, CONNECTOR_ARROW_BASE)
cr.close_path()
cr.fill()
@ -238,4 +242,5 @@ class DummyCoreConnection(object):
def has_real_sink(self):
return self.sink_port is not self._dummy_port
DummyConnection = Connection.make_cls_with_base(DummyCoreConnection)

View File

@ -125,7 +125,8 @@ class FlowGraph(CoreFlowgraph, Drawable):
editor.open_editor()
except Exception as e:
# Problem launching the editor. Need to select a new editor.
Messages.send('>>> Error opening an external editor. Please select a different editor.\n')
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.
self.parent_platform.config.editor = ''
@ -153,10 +154,13 @@ class FlowGraph(CoreFlowgraph, Drawable):
# calculate the position coordinate
h_adj = scroll_pane.get_hadjustment()
v_adj = scroll_pane.get_vadjustment()
if coor is None: coor = (
int(random.uniform(.25, .75)*h_adj.get_page_size() + h_adj.get_value()),
int(random.uniform(.25, .75)*v_adj.get_page_size() + v_adj.get_value()),
)
if coor is None:
coor = (
int(random.uniform(.25, .75) *
h_adj.get_page_size() + h_adj.get_value()),
int(random.uniform(.25, .75) *
v_adj.get_page_size() + v_adj.get_value()),
)
# get the new block
block = self.new_block(key)
block.coordinate = coor
@ -218,17 +222,17 @@ class FlowGraph(CoreFlowgraph, Drawable):
Returns:
the clipboard
"""
#get selected blocks
# get selected blocks
blocks = list(self.selected_blocks())
if not blocks:
return None
#calc x and y min
# calc x and y min
x_min, y_min = blocks[0].coordinate
for block in blocks:
x, y = block.coordinate
x_min = min(x, x_min)
y_min = min(y, y_min)
#get connections between selected blocks
# get connections between selected blocks
connections = list(filter(
lambda c: c.source_block in blocks and c.sink_block in blocks,
self.connections,
@ -281,7 +285,8 @@ class FlowGraph(CoreFlowgraph, Drawable):
block.move((x_off, y_off))
while any(Utils.align_to_grid(block.coordinate) == Utils.align_to_grid(other.coordinate)
for other in self.blocks if other is not block):
block.move((Constants.CANVAS_GRID_SIZE, Constants.CANVAS_GRID_SIZE))
block.move((Constants.CANVAS_GRID_SIZE,
Constants.CANVAS_GRID_SIZE))
# shift all following blocks
x_off += Constants.CANVAS_GRID_SIZE
y_off += Constants.CANVAS_GRID_SIZE
@ -353,15 +358,16 @@ class FlowGraph(CoreFlowgraph, Drawable):
if not blocks:
return
min_x, min_y = self.selected_block.coordinate
min_x, min_y = self.selected_block.coordinate
for selected_block in blocks:
x, y = selected_block.coordinate
min_x, min_y = min(min_x, x), min(min_y, y)
# Sanitize delta_coordinate so that blocks don't move to negative coordinate
delta_coordinate = max(delta_coordinate[0],-min_x), max(delta_coordinate[1], -min_y)
delta_coordinate = max(
delta_coordinate[0], -min_x), max(delta_coordinate[1], -min_y)
# Move selected blocks
# Move selected blocks
for selected_block in blocks:
selected_block.move(delta_coordinate)
self.element_moved = True
@ -388,15 +394,15 @@ class FlowGraph(CoreFlowgraph, Drawable):
x += selected_block.width
y += selected_block.height
max_x, max_y = max(max_x, x), max(max_y, y)
ctr_x, ctr_y = (max_x + min_x)/2, (max_y + min_y)/2
ctr_x, ctr_y = (max_x + min_x) / 2, (max_y + min_y) / 2
# align the blocks as requested
transform = {
Actions.BLOCK_VALIGN_TOP: lambda x, y, w, h: (x, min_y),
Actions.BLOCK_VALIGN_MIDDLE: lambda x, y, w, h: (x, ctr_y - h/2),
Actions.BLOCK_VALIGN_MIDDLE: lambda x, y, w, h: (x, ctr_y - h / 2),
Actions.BLOCK_VALIGN_BOTTOM: lambda x, y, w, h: (x, max_y - h),
Actions.BLOCK_HALIGN_LEFT: lambda x, y, w, h: (min_x, y),
Actions.BLOCK_HALIGN_CENTER: lambda x, y, w, h: (ctr_x-w/2, y),
Actions.BLOCK_HALIGN_CENTER: lambda x, y, w, h: (ctr_x - w / 2, y),
Actions.BLOCK_HALIGN_RIGHT: lambda x, y, w, h: (max_x - w, y),
}.get(calling_action, lambda *args: args)
@ -419,21 +425,22 @@ class FlowGraph(CoreFlowgraph, Drawable):
"""
if not any(self.selected_blocks()):
return False
#initialize min and max coordinates
# initialize min and max coordinates
min_x, min_y = max_x, max_y = self.selected_block.coordinate
# rotate each selected block, and find min/max coordinate
for selected_block in self.selected_blocks():
selected_block.rotate(rotation)
#update the min/max coordinate
# update the min/max coordinate
x, y = selected_block.coordinate
min_x, min_y = min(min_x, x), min(min_y, y)
max_x, max_y = max(max_x, x), max(max_y, y)
#calculate center point of selected blocks
ctr_x, ctr_y = (max_x + min_x)/2, (max_y + min_y)/2
#rotate the blocks around the center point
# calculate center point of selected blocks
ctr_x, ctr_y = (max_x + min_x) / 2, (max_y + min_y) / 2
# rotate the blocks around the center point
for selected_block in self.selected_blocks():
x, y = selected_block.coordinate
x, y = Utils.get_rotated_coordinate((x - ctr_x, y - ctr_y), rotation)
x, y = Utils.get_rotated_coordinate(
(x - ctr_x, y - ctr_y), rotation)
selected_block.coordinate = (x + ctr_x, y + ctr_y)
return True
@ -496,7 +503,7 @@ class FlowGraph(CoreFlowgraph, Drawable):
element.create_labels(cr)
def create_shapes(self):
#TODO - this is a workaround for bus ports not having a proper coordinate
# TODO - this is a workaround for bus ports not having a proper coordinate
# until the shape is drawn. The workaround is to draw blocks before connections
for element in filter(lambda x: x.is_block, self._elements_to_draw):
@ -582,7 +589,8 @@ class FlowGraph(CoreFlowgraph, Drawable):
else: # called from a mouse release
if not self.element_moved and (not self.selected_elements or self.drawing_area.ctrl_mask) and not self._new_connection:
selected_elements = self.what_is_selected(self.coordinate, self.press_coor)
selected_elements = self.what_is_selected(
self.coordinate, self.press_coor)
# this selection and the last were ports, try to connect them
if self.make_connection():
@ -635,7 +643,8 @@ class FlowGraph(CoreFlowgraph, Drawable):
if selected_port and selected_port.is_source:
selected.remove(selected_port.parent_block)
self._new_connection = DummyConnection(selected_port, coordinate=coor)
self._new_connection = DummyConnection(
selected_port, coordinate=coor)
self.drawing_area.queue_draw()
# update selected ports
if selected_port is not self._new_selected_port:
@ -806,19 +815,21 @@ class FlowGraph(CoreFlowgraph, Drawable):
dX, dY = x - X, y - Y
if Actions.TOGGLE_SNAP_TO_GRID.get_active() or self.drawing_area.mod1_mask:
dX, dY = int(round(dX / Constants.CANVAS_GRID_SIZE)), int(round(dY / Constants.CANVAS_GRID_SIZE))
dX, dY = int(round(dX / Constants.CANVAS_GRID_SIZE)
), int(round(dY / Constants.CANVAS_GRID_SIZE))
dX, dY = dX * Constants.CANVAS_GRID_SIZE, dY * Constants.CANVAS_GRID_SIZE
else:
dX, dY = int(round(dX)), int(round(dY))
if dX != 0 or dY != 0:
self.move_selected((dX, dY))
self.coordinate = (X+dX, Y+dY)
self.coordinate = (X + dX, Y + dY)
redraw = True
return redraw
def get_extents(self):
show_comments = Actions.TOGGLE_SHOW_BLOCK_COMMENTS.get_active()
def sub_extents():
for element in self._elements_to_draw:
yield element.get_extents()
@ -828,5 +839,6 @@ class FlowGraph(CoreFlowgraph, Drawable):
extent = 10000000, 10000000, 0, 0
cmps = (min, min, max, max)
for sub_extent in sub_extents():
extent = [cmp(xy, e_xy) for cmp, xy, e_xy in zip(cmps, extent, sub_extent)]
extent = [cmp(xy, e_xy)
for cmp, xy, e_xy in zip(cmps, extent, sub_extent)]
return tuple(extent)

View File

@ -86,20 +86,20 @@ class Param(CoreParam):
tooltip_lines.extend(' * ' + msg for msg in errors)
return '\n'.join(tooltip_lines)
##################################################
# Truncate helper method
##################################################
def truncate(self, string, style=0):
max_len = max(27 - len(self.name), 3)
if len(string) > max_len:
if style < 0: # Front truncate
string = '...' + string[3-max_len:]
string = '...' + string[3 - max_len:]
elif style == 0: # Center truncate
string = string[:max_len//2 - 3] + '...' + string[-max_len//2:]
string = string[:max_len // 2 - 3] + \
'...' + string[-max_len // 2:]
elif style > 0: # Rear truncate
string = string[:max_len-3] + '...'
string = string[:max_len - 3] + '...'
return string
def pretty_print(self):
@ -183,5 +183,5 @@ class Param(CoreParam):
display_value = expr_string
return '<span {foreground} font_desc="{font}"><b>{label}:</b> {value}</span>'.format(
foreground='foreground="red"' if not self.is_valid() else '', font=Constants.PARAM_FONT,
foreground='foreground="red"' if not self.is_valid() else '', font=Constants.PARAM_FONT,
label=Utils.encode(self.name), value=display_value)

View File

@ -68,9 +68,11 @@ class Port(CorePort, Drawable):
color = colors.PORT_TYPE_TO_COLOR.get('message')
else:
self._font_color[-1] = 1.0
color = colors.PORT_TYPE_TO_COLOR.get(self.dtype) or colors.PORT_TYPE_TO_COLOR.get('')
color = colors.PORT_TYPE_TO_COLOR.get(
self.dtype) or colors.PORT_TYPE_TO_COLOR.get('')
if self.vlen > 1:
dark = (0, 0, 30 / 255.0, 50 / 255.0, 70 / 255.0)[min(4, self.vlen)]
dark = (0, 0, 30 / 255.0, 50 / 255.0,
70 / 255.0)[min(4, self.vlen)]
color = tuple(max(c - dark, 0) for c in color)
self._bg_color = color
self._border_color = tuple(max(c - 0.3, 0) for c in color)
@ -84,8 +86,8 @@ class Port(CorePort, Drawable):
self.bounds_from_area(self._area)
self._connector_coordinate = {
0: (self.width, self.height / 2),
90: (self.height / 2, 0),
0: (self.width, self.height / 2),
90: (self.height / 2, 0),
180: (0, self.height / 2),
270: (self.height / 2, self.width)
}[self.connector_direction]
@ -112,7 +114,8 @@ class Port(CorePort, Drawable):
label_width, label_height = self.label_layout.get_size()
self.width = 2 * Constants.PORT_LABEL_PADDING + label_width / Pango.SCALE
self.height = (2 * Constants.PORT_LABEL_PADDING + label_height*(3 if self.dtype == 'bus' else 1)) / Pango.SCALE
self.height = (2 * Constants.PORT_LABEL_PADDING + label_height *
(3 if self.dtype == 'bus' else 1)) / Pango.SCALE
self._label_layout_offsets = [0, Constants.PORT_LABEL_PADDING]
self.height += self.height % 2 # uneven height

12
main.py
View File

@ -4,12 +4,14 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
import argparse, logging, sys
from gi.repository import Gtk
import argparse
import logging
import sys
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('PangoCairo', '1.0')
from gi.repository import Gtk
VERSION_AND_DISCLAIMER_TEMPLATE = """\
@ -34,7 +36,8 @@ def main():
parser = argparse.ArgumentParser(
description=VERSION_AND_DISCLAIMER_TEMPLATE % gr.version())
parser.add_argument('flow_graphs', nargs='*')
parser.add_argument('--log', choices=['debug', 'info', 'warning', 'error', 'critical'], default='warning')
parser.add_argument(
'--log', choices=['debug', 'info', 'warning', 'error', 'critical'], default='warning')
args = parser.parse_args()
# Enable logging
@ -73,7 +76,8 @@ def main():
log.debug("Loading platform")
platform = Platform(
version=gr.version(),
version_parts=(gr.major_version(), gr.api_version(), gr.minor_version()),
version_parts=(gr.major_version(), gr.api_version(),
gr.minor_version()),
prefs=gr.prefs(),
install_prefix=gr.prefix()
)

View File

@ -47,6 +47,7 @@ def die(error, message):
print("The original error message follows.", file=sys.stderr)
sys.exit(type(error).__name__ + '\n\n' + msg)
def check_gtk():
try:
import gi

View File

@ -37,9 +37,11 @@ def test_list():
def test_parse_error():
with pytest.raises(TemplateError):
MakoTemplates(_bind_to=Block(num='123'), test='abc${num NOT CLOSING').render('test')
MakoTemplates(_bind_to=Block(num='123'),
test='abc${num NOT CLOSING').render('test')
def test_parse_error2():
with pytest.raises(TemplateError):
MakoTemplates(_bind_to=Block(num='123'), test='abc${ WRONG_VAR }').render('test')
MakoTemplates(_bind_to=Block(num='123'),
test='abc${ WRONG_VAR }').render('test')

View File

@ -39,7 +39,8 @@ def test_conditional():
def test_simple_format_string():
convert = functools.partial(parser.Converter(names={'abc'}).convert_simple, spec=parser.FormatString)
convert = functools.partial(parser.Converter(
names={'abc'}).convert_simple, spec=parser.FormatString)
assert '{abc}' == convert('$abc')
assert '{abc:eval}' == convert('$abc()')
assert '{abc}' == convert('$(abc)')
@ -50,7 +51,8 @@ def test_simple_format_string():
def test_hard_format_string():
names = {'abc': {'ff'}, 'param1': {}, 'param2': {}}
convert = functools.partial(parser.Converter(names).convert_hard, spec=parser.FormatString)
convert = functools.partial(parser.Converter(
names).convert_hard, spec=parser.FormatString)
assert 'make_a_cool_block_{abc.ff}({param1}, {param2})' == \
convert('make_a_cool_block_${abc.ff}($param1, $param2)')
@ -66,7 +68,8 @@ def test_opts():
def test_nested():
assert 'abc(abc) abc + abc abc[abc]' == c2p('$abc($abc) $(abc + $abc) ${abc[$abc]}')
assert 'abc(abc) abc + abc abc[abc]' == c2p(
'$abc($abc) $(abc + $abc) ${abc[$abc]}')
assert '(abc_abc_)' == c2p('(abc_$(abc)_)')

View File

@ -13,11 +13,13 @@ import tempfile
from grc.compiler import main
def test_compiler(capsys):
args = Namespace(
output=tempfile.gettempdir(),
user_lib_dir=False,
grc_files=[path.join(path.dirname(__file__), 'resources', 'test_compiler.grc')],
grc_files=[path.join(path.dirname(__file__),
'resources', 'test_compiler.grc')],
run=True
)

View File

@ -13,11 +13,13 @@ import tempfile
from grc.compiler import main
def test_cpp(capsys):
args = Namespace(
output=tempfile.gettempdir(),
user_lib_dir=False,
grc_files=[path.join(path.dirname(__file__), 'resources', 'test_cpp.grc')],
grc_files=[path.join(path.dirname(__file__),
'resources', 'test_cpp.grc')],
run=True
)

View File

@ -14,12 +14,14 @@ except FileExistsError:
# Gather blocks
BLOCK_PATHS = []
ROOT = path.join(path.dirname(__file__), '../..')
BLOCK_PATHS = [path.join(path.dirname(__file__), '../../grc/blocks'), '../../build/gr-uhd/grc']
BLOCK_PATHS = [path.join(path.dirname(
__file__), '../../grc/blocks'), '../../build/gr-uhd/grc']
for file_dir in os.scandir(ROOT):
# If it is a module
if path.isdir(file_dir) and file_dir.name.startswith("gr-"):
BLOCK_PATHS.append(path.join(file_dir, "grc"))
def gather_examples():
global ROOT
example_paths = []
@ -39,11 +41,13 @@ def gather_examples():
continue
return example_paths
def print_proper(element):
if element.is_block:
return element.name
return f"{element.parent.name} - {element}"
@pytest.mark.examples
@pytest.mark.parametrize("example", gather_examples())
def test_all_examples(example):
@ -62,7 +66,9 @@ def test_all_examples(example):
flow_graph.rewrite()
flow_graph.validate()
assert flow_graph.is_valid(), (example.name, [f"{print_proper(elem)}: {msg}" for elem, msg in flow_graph.iter_error_messages()])
assert flow_graph.is_valid(), (example.name, [
f"{print_proper(elem)}: {msg}" for elem, msg in flow_graph.iter_error_messages()])
generator = platform.Generator(flow_graph, path.join(path.dirname(__file__), 'resources/tests'))
generator = platform.Generator(flow_graph, path.join(
path.dirname(__file__), 'resources/tests'))
generator.write()

View File

@ -15,7 +15,8 @@ from grc.core.platform import Platform
def test_generator():
# c&p form compiler code.
grc_file = path.join(path.dirname(__file__), 'resources', 'test_compiler.grc')
grc_file = path.join(path.dirname(__file__),
'resources', 'test_compiler.grc')
out_dir = tempfile.gettempdir()
block_paths = [
path.join(path.dirname(__file__), '../../grc/blocks'),
@ -35,5 +36,6 @@ def test_generator():
assert flow_graph.is_valid()
generator = platform.Generator(flow_graph, path.join(path.dirname(__file__), 'resources'))
generator = platform.Generator(
flow_graph, path.join(path.dirname(__file__), 'resources'))
generator.write()

View File

@ -11,7 +11,8 @@ from grc.converter import flow_graph
def test_flow_graph_converter():
filename = path.join(path.dirname(__file__), 'resources', 'test_compiler.grc')
filename = path.join(path.dirname(__file__),
'resources', 'test_compiler.grc')
data = flow_graph.from_xml(filename)
@ -19,7 +20,8 @@ def test_flow_graph_converter():
def test_flow_graph_converter_with_fp():
filename = path.join(path.dirname(__file__), 'resources', 'test_compiler.grc')
filename = path.join(path.dirname(__file__),
'resources', 'test_compiler.grc')
with open(filename, 'rb') as fp:
data = flow_graph.from_xml(fp)

View File

@ -58,7 +58,8 @@ def test_min():
def test_extra_keys():
checker = Validator(BLOCK_SCHEME)
assert checker.run({'id': 'test', 'abcdefg': 'nonsense', 'file_format': 1})
assert checker.messages == [('block', 'warn', "Ignoring extra key 'abcdefg'")]
assert checker.messages == [
('block', 'warn', "Ignoring extra key 'abcdefg'")]
def test_checker():