diff options
author | Sebastian Koslowski <koslowski@kit.edu> | 2016-07-15 23:25:54 +0200 |
---|---|---|
committer | Sebastian Koslowski <koslowski@kit.edu> | 2016-07-29 15:45:07 +0200 |
commit | 93ce3961a572da6ec3dbef1f24a22f4153acaa61 (patch) | |
tree | 274a78b5ce37f5305818c6ebab7ba495a2da4e12 | |
parent | 36f186bc46f528d95d9186955e91736d1fdb299e (diff) |
grc: refactor: Port, Param, Options init clean-up
-rw-r--r-- | grc/core/Block.py | 300 | ||||
-rw-r--r-- | grc/core/Constants.py | 11 | ||||
-rw-r--r-- | grc/core/Element.py | 9 | ||||
-rw-r--r-- | grc/core/Element.pyi | 16 | ||||
-rw-r--r-- | grc/core/Param.py | 272 | ||||
-rw-r--r-- | grc/core/Platform.py | 73 | ||||
-rw-r--r-- | grc/core/Port.py | 13 | ||||
-rw-r--r-- | grc/core/generator/FlowGraphProxy.py | 2 | ||||
-rw-r--r-- | grc/core/utils/__init__.py | 1 | ||||
-rw-r--r-- | grc/core/utils/_num_to_str.py | 30 | ||||
-rw-r--r-- | grc/gui/Block.py | 4 | ||||
-rw-r--r-- | grc/gui/Connection.py | 8 | ||||
-rw-r--r-- | grc/gui/MainWindow.py | 10 | ||||
-rw-r--r-- | grc/gui/Param.py | 79 | ||||
-rw-r--r-- | grc/gui/ParamWidgets.py | 16 | ||||
-rw-r--r-- | grc/gui/Platform.py | 8 | ||||
-rw-r--r-- | grc/gui/Port.py | 9 | ||||
-rw-r--r-- | grc/gui/Utils.py | 45 |
18 files changed, 411 insertions, 495 deletions
diff --git a/grc/core/Block.py b/grc/core/Block.py index 8ffb99b5bd..d37909b4b8 100644 --- a/grc/core/Block.py +++ b/grc/core/Block.py @@ -36,7 +36,7 @@ from . Constants import ( BLOCK_FLAG_THROTTLE, BLOCK_FLAG_DISABLE_BYPASS, BLOCK_FLAG_DEPRECATED, ) -from . Element import Element +from . Element import Element, lazy_property def _get_elem(iterable, key): @@ -53,21 +53,12 @@ class Block(Element): STATE_LABELS = ['disabled', 'enabled', 'bypassed'] - def __init__(self, flow_graph, n): - """ - Make a new block from nested data. - - Args: - flow_graph: the parent element - n: the nested odict + def __init__(self, parent, key, name, **n): + """Make a new block from nested data.""" + super(Block, self).__init__(parent) - Returns: - block a new block - """ - Element.__init__(self, parent=flow_graph) - - self.name = n['name'] - self.key = n['key'] + self.key = key + self.name = name self.category = [cat.strip() for cat in n.get('category', '').split('/') if cat.strip()] self.flags = n.get('flags', '') self._doc = n.get('doc', '').strip('\n').replace('\\\n', '') @@ -98,7 +89,6 @@ class Block(Element): has_sources=len(sources_n) ) - self.port_counters = [itertools.count(), itertools.count()] self.sources = self._init_ports(sources_n, direction='source') self.sinks = self._init_ports(sinks_n, direction='sink') self.active_sources = [] # on rewrite @@ -106,18 +96,15 @@ class Block(Element): self.states = {'_enabled': True} - self._epy_source_hash = -1 # for epy blocks - self._epy_reload_error = None - self._init_bus_ports(n) - def _add_param(self, key, name, value='', type='raw', **kwargs): - n = {'key': key, 'name': name, 'value': value, 'type': type} - n.update(kwargs) - self.params[key] = self.parent_platform.Param(block=self, n=n) - def _init_params(self, params_n, has_sources, has_sinks): - self._add_param(key='id', name='ID', type='id') + param_factory = self.parent_platform.get_new_param + + def add_param(key, **kwargs): + self.params[key] = param_factory(self, key=key, **kwargs) + + add_param(key='id', name='ID', type='id') # Virtual source/sink and pad source/sink blocks are # indistinguishable from normal GR blocks. Make explicit @@ -133,35 +120,40 @@ class Block(Element): self.flags += BLOCK_FLAG_DISABLE_BYPASS if not (is_virtual_or_pad or is_variable or self.key == 'options'): - self._add_param(key='alias', name='Block Alias', type='string', - hide='part', tab=ADVANCED_PARAM_TAB) + add_param(key='alias', name='Block Alias', type='string', + hide='part', tab=ADVANCED_PARAM_TAB) if not is_virtual_or_pad and (has_sources or has_sinks): - self._add_param(key='affinity', name='Core Affinity', type='int_vector', - hide='part', tab=ADVANCED_PARAM_TAB) + add_param(key='affinity', name='Core Affinity', type='int_vector', + hide='part', tab=ADVANCED_PARAM_TAB) if not is_virtual_or_pad and has_sources: - self._add_param(key='minoutbuf', name='Min Output Buffer', type='int', - hide='part', value='0', tab=ADVANCED_PARAM_TAB) - self._add_param(key='maxoutbuf', name='Max Output Buffer', type='int', - hide='part', value='0', tab=ADVANCED_PARAM_TAB) + add_param(key='minoutbuf', name='Min Output Buffer', type='int', + hide='part', value='0', tab=ADVANCED_PARAM_TAB) + add_param(key='maxoutbuf', name='Max Output Buffer', type='int', + hide='part', value='0', tab=ADVANCED_PARAM_TAB) + base_params_n = {n['key']: n for n in params_n} for param_n in params_n: - param = self.parent_platform.Param(block=self, n=param_n) - key = param.key + key = param_n['key'] if key in self.params: raise Exception('Key "{}" already exists in params'.format(key)) - self.params[key] = param - self._add_param(key='comment', name='Comment', type='_multiline', hide='part', - value='', tab=ADVANCED_PARAM_TAB) + extended_param_n = base_params_n.get(param_n.pop('base_key', None), {}) + extended_param_n.update(param_n) + self.params[key] = param_factory(self, **extended_param_n) + + add_param(key='comment', name='Comment', type='_multiline', hide='part', + value='', tab=ADVANCED_PARAM_TAB) def _init_ports(self, ports_n, direction): - port_cls = self.parent_platform.Port + port_factory = self.parent_platform.get_new_port ports = [] port_keys = set() - for port_n in ports_n: - port = port_cls(block=self, n=port_n, dir=direction) + stream_port_keys = itertools.count() + for i, port_n in enumerate(ports_n): + port_n.setdefault('key', str(next(stream_port_keys))) + port = port_factory(parent=self, direction=direction, **port_n) key = port.key if key in port_keys: raise Exception('Key "{}" already exists in {}'.format(key, direction)) @@ -169,61 +161,14 @@ class Block(Element): ports.append(port) return ports - def _run_checks(self): - """Evaluate the checks""" - for check in self._checks: - check_res = self.resolve_dependencies(check) - try: - if not self.parent.evaluate(check_res): - self.add_error_message('Check "{}" failed.'.format(check)) - except: - self.add_error_message('Check "{}" did not evaluate.'.format(check)) - - def _validate_generate_mode_compat(self): - """check if this is a GUI block and matches the selected generate option""" - current_generate_option = self.parent.get_option('generate_options') - - def check_generate_mode(label, flag, valid_options): - block_requires_mode = ( - flag in self.flags or self.name.upper().startswith(label) - ) - if block_requires_mode and current_generate_option not in valid_options: - self.add_error_message("Can't generate this block in mode: {} ".format( - repr(current_generate_option))) - - check_generate_mode('WX GUI', BLOCK_FLAG_NEED_WX_GUI, ('wx_gui',)) - check_generate_mode('QT GUI', BLOCK_FLAG_NEED_QT_GUI, ('qt_gui', 'hb_qt_gui')) - - def _validate_var_value(self): - """or variables check the value (only if var_value is used)""" - if self.is_variable and self._var_value != '$value': - value = self._var_value - try: - value = self.get_var_value() - self.parent.evaluate(value) - except Exception as err: - self.add_error_message('Value "{}" cannot be evaluated:\n{}'.format(value, err)) - - def validate(self): - """ - Validate this block. - Call the base class validate. - Evaluate the checks: each check must evaluate to True. - """ - Element.validate(self) - self._run_checks() - self._validate_generate_mode_compat() - self._validate_var_value() - if self._epy_reload_error: - self.params['_source_code'].add_error_message(str(self._epy_reload_error)) - + ############################################## + # validation and rewrite + ############################################## def rewrite(self): """ Add and remove ports to adjust for the nports. """ Element.rewrite(self) - # Check and run any custom rewrite function for this block - getattr(self, 'rewrite_' + self.key, lambda: None)() # Adjust nports, disconnect hidden ports for ports in (self.sources, self.sinks): @@ -258,6 +203,55 @@ class Block(Element): self.active_sources = [p for p in self.get_sources_gui() if not p.get_hide()] self.active_sinks = [p for p in self.get_sinks_gui() if not p.get_hide()] + def validate(self): + """ + Validate this block. + Call the base class validate. + Evaluate the checks: each check must evaluate to True. + """ + Element.validate(self) + self._run_checks() + self._validate_generate_mode_compat() + self._validate_var_value() + + def _run_checks(self): + """Evaluate the checks""" + for check in self._checks: + check_res = self.resolve_dependencies(check) + try: + if not self.parent.evaluate(check_res): + self.add_error_message('Check "{}" failed.'.format(check)) + except: + self.add_error_message('Check "{}" did not evaluate.'.format(check)) + + def _validate_generate_mode_compat(self): + """check if this is a GUI block and matches the selected generate option""" + current_generate_option = self.parent.get_option('generate_options') + + def check_generate_mode(label, flag, valid_options): + block_requires_mode = ( + flag in self.flags or self.name.upper().startswith(label) + ) + if block_requires_mode and current_generate_option not in valid_options: + self.add_error_message("Can't generate this block in mode: {} ".format( + repr(current_generate_option))) + + check_generate_mode('WX GUI', BLOCK_FLAG_NEED_WX_GUI, ('wx_gui',)) + check_generate_mode('QT GUI', BLOCK_FLAG_NEED_QT_GUI, ('qt_gui', 'hb_qt_gui')) + + def _validate_var_value(self): + """or variables check the value (only if var_value is used)""" + if self.is_variable and self._var_value != '$value': + value = self._var_value + try: + value = self.get_var_value() + self.parent.evaluate(value) + except Exception as err: + self.add_error_message('Value "{}" cannot be evaluated:\n{}'.format(value, err)) + + ############################################## + # Getters + ############################################## def get_imports(self, raw=False): """ Resolve all import statements. @@ -316,14 +310,7 @@ class Block(Element): # Also kept get_enabled and set_enabled to keep compatibility @property def state(self): - """ - Gets the block's current state. - - Returns: - ENABLED - 0 - BYPASSED - 1 - DISABLED - 2 - """ + """Gets the block's current state.""" try: return self.STATE_LABELS[int(self.states['_enabled'])] except ValueError: @@ -331,14 +318,7 @@ class Block(Element): @state.setter def state(self, value): - """ - Sets the state for the block. - - Args: - ENABLED - 0 - BYPASSED - 1 - DISABLED - 2 - """ + """Sets the state for the block.""" try: encoded = self.STATE_LABELS.index(value) except ValueError: @@ -425,11 +405,11 @@ class Block(Element): def get_comment(self): return self.params['comment'].get_value() - @property + @lazy_property def is_throtteling(self): return BLOCK_FLAG_THROTTLE in self.flags - @property + @lazy_property def is_deprecated(self): return BLOCK_FLAG_DEPRECATED in self.flags @@ -459,12 +439,11 @@ class Block(Element): return self.filter_bus_port(self.sources) def get_connections(self): - return sum([port.get_connections() for port in self.get_ports()], []) + return sum((port.get_connections() for port in self.get_ports()), []) ############################################## # Resolve ############################################## - def resolve_dependencies(self, tmpl): """ Resolve a paramater dependency with cheetah templates. @@ -515,7 +494,7 @@ class Block(Element): # Try to increment the enum by direction try: - keys = type_param.get_option_keys() + keys = list(type_param.options.keys()) old_index = keys.index(type_param.get_value()) new_index = (old_index + direction + len(keys)) % len(keys) type_param.set_value(keys[new_index]) @@ -693,7 +672,7 @@ class Block(Element): for i in range(len(struct)): n['key'] = str(len(ports)) n = dict(n) - port = self.parent.parent.Port(block=self, n=n, dir=direc) + port = self.parent_platform.get_new_port(self, direction=direc, **n) ports.append(port) elif any('bus' == p.get_type() for p in ports): for elt in get_p_gui(): @@ -716,15 +695,14 @@ class Block(Element): class EPyBlock(Block): - def __init__(self, flow_graph, n): - super(EPyBlock, self).__init__(flow_graph, n) + def __init__(self, flow_graph, **n): + super(EPyBlock, self).__init__(flow_graph, **n) self._epy_source_hash = -1 # for epy blocks self._epy_reload_error = None + def rewrite(self): + Element.rewrite(self) - def rewrite_epy_block(self): - flowgraph = self.parent_flowgraph - platform = self.parent_platform param_blk = self.params['_io_cache'] param_src = self.params['_source_code'] @@ -758,53 +736,65 @@ class EPyBlock(Block): self._make = '{0}.{1}({2})'.format(self.get_id(), blk_io.cls, ', '.join( '{0}=${{ {0} }}'.format(key) for key, _ in blk_io.params)) self._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') + self._update_ports('out', self.sources, blk_io.sources, 'source') + + super(EPyBlock, self).rewrite() + def _update_params(self, params_in_src): + param_factory = self.parent_platform.get_new_param params = {} for param in list(self.params): if hasattr(param, '__epy_param__'): params[param.key] = param del self.params[param.key] - for key, value in blk_io.params: + for key, value in params_in_src: try: param = params[key] - param.set_default(value) + if param.default == param.value: + param.set_value(value) + param.default = str(value) except KeyError: # need to make a new param - name = key.replace('_', ' ').title() - n = dict(name=name, key=key, type='raw', value=value) - param = platform.Param(block=self, n=n) + param = param_factory( + parent=self, key=key, type='raw', value=value, + name=key.replace('_', ' ').title(), + ) setattr(param, '__epy_param__', True) self.params[key] = param - def update_ports(label, ports, port_specs, direction): - ports_to_remove = list(ports) - iter_ports = iter(ports) - ports_new = [] - port_current = next(iter_ports, None) - for key, port_type in port_specs: - reuse_port = ( - port_current is not None and - port_current.get_type() == port_type and - (key.isdigit() or port_current.key == key) - ) - if reuse_port: - ports_to_remove.remove(port_current) - port, port_current = port_current, next(iter_ports, None) - else: - n = dict(name=label + str(key), type=port_type, key=key) - if port_type == 'message': - n['name'] = key - n['optional'] = '1' - port = platform.Port(block=self, n=n, dir=direction) - ports_new.append(port) - # replace old port list with new one - del ports[:] - ports.extend(ports_new) - # remove excess port connections - for port in ports_to_remove: - for connection in port.get_connections(): - flowgraph.remove_element(connection) - - update_ports('in', self.sinks, blk_io.sinks, 'sink') - update_ports('out', self.sources, blk_io.sources, 'source') - self.rewrite() + def _update_ports(self, label, ports, port_specs, direction): + port_factory = self.parent_platform.get_new_port + ports_to_remove = list(ports) + iter_ports = iter(ports) + ports_new = [] + port_current = next(iter_ports, None) + for key, port_type in port_specs: + reuse_port = ( + port_current is not None and + port_current.get_type() == port_type and + (key.isdigit() or port_current.key == key) + ) + if reuse_port: + ports_to_remove.remove(port_current) + port, port_current = port_current, next(iter_ports, None) + else: + n = dict(name=label + str(key), type=port_type, key=key) + if port_type == 'message': + n['name'] = key + n['optional'] = '1' + port = port_factory(self, direction=direction, **n) + ports_new.append(port) + # replace old port list with new one + del ports[:] + ports.extend(ports_new) + # remove excess port connections + for port in ports_to_remove: + for connection in port.get_connections(): + self.parent_flowgraph.remove_element(connection) + + def validate(self): + super(EPyBlock, self).validate() + if self._epy_reload_error: + self.params['_source_code'].add_error_message(str(self._epy_reload_error)) diff --git a/grc/core/Constants.py b/grc/core/Constants.py index 40fe69dd02..bc387a4837 100644 --- a/grc/core/Constants.py +++ b/grc/core/Constants.py @@ -59,6 +59,17 @@ TOP_BLOCK_FILE_MODE = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH HIER_BLOCK_FILE_MODE = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH +PARAM_TYPE_NAMES = ( + 'raw', 'enum', + 'complex', 'real', 'float', 'int', + 'complex_vector', 'real_vector', 'float_vector', 'int_vector', + 'hex', 'string', 'bool', + 'file_open', 'file_save', '_multiline', '_multiline_python_external', + 'id', 'stream_id', + 'grid_pos', 'notebook', 'gui_hint', + 'import', +) + # Define types, native python + numpy VECTOR_TYPES = (tuple, list, set, numpy.ndarray) COMPLEX_TYPES = [complex, numpy.complex, numpy.complex64, numpy.complex128] diff --git a/grc/core/Element.py b/grc/core/Element.py index cd0514beb6..415b086402 100644 --- a/grc/core/Element.py +++ b/grc/core/Element.py @@ -29,16 +29,15 @@ class lazy_property(object): if instance is None: return self value = self.func(instance) - weak_value = weakref.proxy(value) if not weakref.ProxyType else value - setattr(instance, self.func.__name__, weak_value) - return weak_value + setattr(instance, self.func.__name__, value) + return value -def property_nop_write(func): +def nop_write(prop): """Make this a property with a nop setter""" def nop(self, value): pass - return property(fget=func, fset=nop) + return prop.setter(nop) class Element(object): diff --git a/grc/core/Element.pyi b/grc/core/Element.pyi index c81180a33e..46c6d3480d 100644 --- a/grc/core/Element.pyi +++ b/grc/core/Element.pyi @@ -1,4 +1,4 @@ -# Copyright 2008, 2009, 2015, 2016 Free Software Foundation, Inc. +# Copyright 2016 Free Software Foundation, Inc. # This file is part of GNU Radio # # GNU Radio Companion is free software; you can redistribute it and/or @@ -27,17 +27,11 @@ class Element(object): ... @property - def parent(self): + def parent(self): -> 'Element' ... - def get_parent_by_type(self, cls): - parent = self.parent - if parent is None: - return None - elif isinstance(parent, cls): - return parent - else: - return parent.get_parent_by_type(cls) + def get_parent_by_type(self, cls): -> 'Element' + ... @lazy_property def parent_platform(self): -> Platform.Platform @@ -50,5 +44,3 @@ class Element(object): @lazy_property def parent_block(self): -> Block.Block ... - - diff --git a/grc/core/Param.py b/grc/core/Param.py index 841f85ec86..a2effad2f9 100644 --- a/grc/core/Param.py +++ b/grc/core/Param.py @@ -20,13 +20,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from __future__ import absolute_import import ast -import weakref import re import collections from six.moves import builtins, filter, map, range, zip -from . import Constants, utils +from . import Constants from .Element import Element # Blacklist certain ids, its not complete, but should help @@ -41,52 +40,6 @@ _check_id_matcher = re.compile('^[a-z|A-Z]\w*$') _show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook)$') -class Option(Element): - - def __init__(self, param, n): - Element.__init__(self, param) - self._name = n.get('name') - self.key = n.get('key') - self._opts = dict() - opts = n.get('opt', []) - # Test against opts when non enum - if not self.parent.is_enum() and opts: - raise Exception('Options for non-enum types cannot have sub-options') - # Extract opts - for opt in opts: - # Separate the key:value - try: - key, value = opt.split(':') - except: - raise Exception('Error separating "{}" into key:value'.format(opt)) - # Test against repeated keys - if key in self._opts: - raise Exception('Key "{}" already exists in option'.format(key)) - # Store the option - self._opts[key] = value - - def __str__(self): - return 'Option {}({})'.format(self.get_name(), self.key) - - def get_name(self): - return self._name - - def get_key(self): - return self.key - - ############################################## - # Access Opts - ############################################## - def get_opt_keys(self): - return list(self._opts.keys()) - - def get_opt(self, key): - return self._opts[key] - - def get_opts(self): - return list(self._opts.values()) - - class TemplateArg(object): """ A cheetah template argument created from a param. @@ -96,10 +49,12 @@ class TemplateArg(object): """ def __init__(self, param): - self._param = weakref.proxy(param) + self._param = param def __getitem__(self, item): - return str(self._param.get_opt(item)) if self._param.is_enum() else NotImplemented + param = self._param + opts = param.options_opts[param.get_value()] + return str(opts[item]) if param.is_enum() else NotImplemented def __str__(self): return str(self._param.to_code()) @@ -112,147 +67,63 @@ class Param(Element): is_param = True - def __init__(self, block, n): - """ - Make a new param from nested data. + def __init__(self, parent, key, name, type='raw', value='', **n): + """Make a new param from nested data""" + super(Param, self).__init__(parent) + self.key = key + self._name = name + self.value = self.default = value + self._type = type - Args: - block: the parent element - n: the nested odict - """ - # If the base key is a valid param key, copy its data and overlay this params data - base_key = n.get('base_key') - if base_key and base_key in block.params: - n_expanded = block.params[base_key]._n.copy() - n_expanded.update(n) - n = n_expanded - # Save odict in case this param will be base for another - self._n = n - # Parse the data - self._name = n['name'] - self.key = n['key'] - value = n.get('value', '') - self._type = n.get('type', 'raw') self._hide = n.get('hide', '') self.tab_label = n.get('tab', Constants.DEFAULT_PARAM_TAB) - # Build the param - Element.__init__(self, parent=block) - # Create the Option objects from the n data - self._options = list() self._evaluated = None - for o_n in n.get('option', []): - option = Option(param=self, n=o_n) - key = option.key - # Test against repeated keys - if key in self.get_option_keys(): - raise Exception('Key "{}" already exists in options'.format(key)) - # Store the option - self.get_options().append(option) - # Test the enum options - if self.is_enum(): - # Test against options with identical keys - if len(set(self.get_option_keys())) != len(self.get_options()): - raise Exception('Options keys "{}" are not unique.'.format(self.get_option_keys())) - # Test against inconsistent keys in options - opt_keys = self.get_options()[0].get_opt_keys() - for option in self.get_options(): - if set(opt_keys) != set(option.get_opt_keys()): - raise Exception('Opt keys "{}" are not identical across all options.'.format(opt_keys)) - # If a value is specified, it must be in the options keys - if value or value in self.get_option_keys(): - self._value = value - else: - self._value = self.get_option_keys()[0] - if self.get_value() not in self.get_option_keys(): - raise Exception('The value "{}" is not in the possible values of "{}".'.format(self.get_value(), self.get_option_keys())) - else: - self._value = value or '' - self._default = value + + self.options = [] + self.options_names = [] + self.options_opts = {} + self._init_options(options_n=n.get('option', [])) + self._init = False self._hostage_cells = list() self.template_arg = TemplateArg(self) - def get_types(self): - return ( - 'raw', 'enum', - 'complex', 'real', 'float', 'int', - 'complex_vector', 'real_vector', 'float_vector', 'int_vector', - 'hex', 'string', 'bool', - 'file_open', 'file_save', '_multiline', '_multiline_python_external', - 'id', 'stream_id', - 'grid_pos', 'notebook', 'gui_hint', - 'import', - ) - - def __repr__(self): - """ - Get the repr (nice string format) for this param. - - Returns: - the string representation - """ - ################################################## - # Truncate helper method - ################################################## - def _truncate(string, style=0): - max_len = max(27 - len(self.get_name()), 3) - if len(string) > max_len: - if style < 0: # Front truncate - string = '...' + string[3-max_len:] - elif style == 0: # Center truncate - string = string[:max_len/2 - 3] + '...' + string[-max_len/2:] - elif style > 0: # Rear truncate - string = string[:max_len-3] + '...' - return string - - ################################################## - # Simple conditions - ################################################## - if not self.is_valid(): - return _truncate(self.get_value()) - if self.get_value() in self.get_option_keys(): - return self.get_option(self.get_value()).get_name() - - ################################################## - # Split up formatting by type - ################################################## - # Default center truncate - truncate = 0 - e = self.get_evaluated() - t = self.get_type() - if isinstance(e, bool): - return str(e) - elif isinstance(e, Constants.COMPLEX_TYPES): - dt_str = utils.num_to_str(e) - elif isinstance(e, Constants.VECTOR_TYPES): - # Vector types - if len(e) > 8: - # Large vectors use code - dt_str = self.get_value() - truncate = 1 - else: - # Small vectors use eval - dt_str = ', '.join(map(utils.num_to_str, e)) - elif t in ('file_open', 'file_save'): - dt_str = self.get_value() - truncate = -1 - else: - # Other types - dt_str = str(e) - - # Done - return _truncate(dt_str, truncate) - - def __repr2__(self): - """ - Get the repr (nice string format) for this param. + def _init_options(self, options_n): + """Create the Option objects from the n data""" + option_keys = set() + for option_n in options_n: + key, name = option_n['key'], option_n['name'] + # Test against repeated keys + if key in option_keys: + raise KeyError('Key "{}" already exists in options'.format(key)) + option_keys.add(key) + # Store the option + self.options.append(key) + self.options_names.append(name) - Returns: - the string representation - """ if self.is_enum(): - return self.get_option(self.get_value()).get_name() - return self.get_value() + self._init_enum(options_n) + + def _init_enum(self, options_n): + opt_ref = None + for option_n in options_n: + key, opts_raw = option_n['key'], option_n.get('opt', []) + try: + self.options_opts[key] = opts = dict(opt.split(':') for opt in opts_raw) + except TypeError: + raise ValueError('Error separating opts into key:value') + + if opt_ref is None: + opt_ref = set(opts.keys()) + elif opt_ref != set(opts): + raise ValueError('Opt keys ({}) are not identical across all options.' + ''.format(', '.join(opt_ref))) + if not self.value: + self.value = self.default = self.options[0] + elif self.value not in self.options: + self.value = self.default = self.options[0] # TODO: warn + # raise ValueError('The value {!r} is not in the possible values of {}.' + # ''.format(self.get_value(), ', '.join(self.options))) def __str__(self): return 'Param - {}({})'.format(self.get_name(), self.key) @@ -295,7 +166,7 @@ class Param(Element): The value must be evaluated and type must a possible type. """ Element.validate(self) - if self.get_type() not in self.get_types(): + if self.get_type() not in Constants.PARAM_TYPE_NAMES: self.add_error_message('Type "{}" is not a possible type.'.format(self.get_type())) self._evaluated = None @@ -606,20 +477,20 @@ class Param(Element): return self._type == 'enum' def get_value(self): - value = self._value - if self.is_enum() and value not in self.get_option_keys(): - value = self.get_option_keys()[0] + value = self.value + if self.is_enum() and value not in self.options: + value = self.options[0] self.set_value(value) return value def set_value(self, value): # Must be a string - self._value = str(value) + self.value = str(value) def set_default(self, value): - if self._default == self._value: + if self.default == self.value: self.set_value(value) - self._default = str(value) + self.default = str(value) def get_type(self): return self.parent.resolve_dependencies(self._type) @@ -633,29 +504,8 @@ class Param(Element): ############################################## # Access Options ############################################## - def get_option_keys(self): - return [elem.key for elem in self._options] - - def get_option(self, key): - for option in self._options: - if option.key == key: - return option - return ValueError('Key "{}" not found in {}.'.format(key, self.get_option_keys())) - - def get_options(self): - return self._options - - ############################################## - # Access Opts - ############################################## - def get_opt_keys(self): - return self.get_option(self.get_value()).get_opt_keys() - - def get_opt(self, key): - return self.get_option(self.get_value()).get_opt(key) - - def get_opts(self): - return self.get_option(self.get_value()).get_opts() + def opt_value(self, key): + return self.options_opts[self.get_value()][key] ############################################## # Import/Export Methods diff --git a/grc/core/Platform.py b/grc/core/Platform.py index e32bd9198a..696281380e 100644 --- a/grc/core/Platform.py +++ b/grc/core/Platform.py @@ -41,17 +41,6 @@ from .utils import extract_docs class Platform(Element): - Config = Config - Generator = Generator - FlowGraph = FlowGraph - Connection = Connection - block_classes = { - None: Block, # default - 'epy_block': EPyBlock, - } - Port = Port - Param = Param - is_platform = True def __init__(self, *args, **kwargs): @@ -166,6 +155,7 @@ class Platform(Element): # print >> sys.stderr, 'Warning: Block validation failed:\n\t%s\n\tIgnoring: %s' % (e, xml_file) pass except Exception as e: + raise print('Warning: XML parsing failed:\n\t%r\n\tIgnoring: %s' % (e, xml_file), file=sys.stderr) # Add blocks to block tree @@ -200,17 +190,18 @@ class Platform(Element): ParseXML.validate_dtd(xml_file, self._block_dtd) n = ParseXML.from_file(xml_file).get('block', {}) n['block_wrapper_path'] = xml_file # inject block wrapper path - # Get block instance and add it to the list of blocks - block = self.block_classes[None](self._flow_graph, n) - key = block.key + key = n.pop('key') + if key in self.blocks: - print('Warning: Block with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file), file=sys.stderr) - else: # Store the block - self.blocks[key] = block - self._blocks_n[key] = n + print('Warning: Block with key "{}" already exists.\n' + '\tIgnoring: {}'.format(key, xml_file), file=sys.stderr) + return + # Store the block + self.blocks[key] = block = self.get_new_block(self._flow_graph, key, **n) + self._blocks_n[key] = n self._docstring_extractor.query( - block.key, + key, block.get_imports(raw=True), block.get_make(raw=True) ) @@ -305,12 +296,46 @@ class Platform(Element): ParseXML.validate_dtd(flow_graph_file, Constants.FLOW_GRAPH_DTD) return ParseXML.from_file(flow_graph_file) - def get_new_flow_graph(self): - return self.FlowGraph(platform=self) - def get_blocks(self): return list(self.blocks.values()) - def get_new_block(self, flow_graph, key): + def get_generate_options(self): + gen_opts = self.blocks['options'].get_param('generate_options') + generate_mode_default = gen_opts.get_value() + return [(key, name, key == generate_mode_default) + for key, name in zip(gen_opts.options, gen_opts.options_names)] + + ############################################## + # Factories + ############################################## + Config = Config + Generator = Generator + FlowGraph = FlowGraph + Connection = Connection + block_classes = { + None: Block, # default + 'epy_block': EPyBlock, + } + port_classes = { + None: Port, # default + } + param_classes = { + None: Param, # default + } + + def get_new_flow_graph(self): + return self.FlowGraph(platform=self) + + def get_new_block(self, parent, key, **kwargs): cls = self.block_classes.get(key, self.block_classes[None]) - return cls(flow_graph, n=self._blocks_n[key]) + if not kwargs: + kwargs = self._blocks_n[key] + return cls(parent, key=key, **kwargs) + + def get_new_param(self, parent, **kwargs): + cls = self.param_classes[None] + return cls(parent, **kwargs) + + def get_new_port(self, parent, **kwargs): + cls = self.port_classes[None] + return cls(parent, **kwargs)
\ No newline at end of file diff --git a/grc/core/Port.py b/grc/core/Port.py index ef6a92c7b1..9030bef375 100644 --- a/grc/core/Port.py +++ b/grc/core/Port.py @@ -105,7 +105,7 @@ class Port(Element): is_port = True - def __init__(self, block, n, dir): + def __init__(self, block, direction, **n): """ Make a new port from nested data. @@ -117,6 +117,7 @@ class Port(Element): self._n = n if n['type'] == 'message': n['domain'] = Constants.GR_MESSAGE_DOMAIN + if 'domain' not in n: n['domain'] = Constants.DEFAULT_DOMAIN elif n['domain'] == Constants.GR_MESSAGE_DOMAIN: @@ -125,8 +126,6 @@ class Port(Element): if n['type'] == 'msg': n['key'] = 'msg' - n.setdefault('key', str(next(block.port_counters[dir == 'source']))) - # Build the port Element.__init__(self, parent=block) # Grab the data @@ -135,13 +134,12 @@ class Port(Element): self._type = n.get('type', '') self.domain = n.get('domain') self._hide = n.get('hide', '') - self._dir = dir + self._dir = direction self._hide_evaluated = False # Updated on rewrite() self._nports = n.get('nports', '') self._vlen = n.get('vlen', '') self._optional = bool(n.get('optional')) - self.di_optional = bool(n.get('optional')) self._clones = [] # References to cloned ports (for nports > 1) def __str__(self): @@ -150,15 +148,12 @@ class Port(Element): if self.is_sink: return 'Sink - {}({})'.format(self.get_name(), self.key) - def get_types(self): - return list(Constants.TYPE_TO_SIZEOF.keys()) - def is_type_empty(self): return not self._n['type'] def validate(self): Element.validate(self) - if self.get_type() not in self.get_types(): + if self.get_type() not in Constants.TYPE_TO_SIZEOF.keys(): self.add_error_message('Type "{}" is not a possible type.'.format(self.get_type())) platform = self.parent.parent.parent if self.domain not in platform.domains: diff --git a/grc/core/generator/FlowGraphProxy.py b/grc/core/generator/FlowGraphProxy.py index d5d31c0dce..678be4433e 100644 --- a/grc/core/generator/FlowGraphProxy.py +++ b/grc/core/generator/FlowGraphProxy.py @@ -70,7 +70,7 @@ class FlowGraphProxy(object): 'label': str(pad.get_param('label').get_evaluated()), 'type': str(pad.get_param('type').get_evaluated()), 'vlen': str(pad.get_param('vlen').get_value()), - 'size': pad.get_param('type').get_opt('size'), + 'size': pad.get_param('type').opt_value('size'), 'optional': bool(pad.get_param('optional').get_evaluated()), } num_ports = pad.get_param('num_streams').get_evaluated() diff --git a/grc/core/utils/__init__.py b/grc/core/utils/__init__.py index 4b1717b930..d095179a10 100644 --- a/grc/core/utils/__init__.py +++ b/grc/core/utils/__init__.py @@ -22,4 +22,3 @@ from . import epy_block_io from . import extract_docs from ._complexity import calculate_flowgraph_complexity -from ._num_to_str import num_to_str diff --git a/grc/core/utils/_num_to_str.py b/grc/core/utils/_num_to_str.py deleted file mode 100644 index 92b2167bb5..0000000000 --- a/grc/core/utils/_num_to_str.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -"""${FILE_NAME}""" -from grc.core import Constants - -__author__ = "Sebastian Koslowski" -__email__ = "sebastian.koslowski@gmail.com" -__copyright__ = "Copyright 2016, Sebastian Koslowski" - - -def num_to_str(num): - """ Display logic for numbers """ - def eng_notation(value, fmt='g'): - """Convert a number to a string in engineering notation. E.g., 5e-9 -> 5n""" - template = '{:' + fmt + '}{}' - magnitude = abs(value) - for exp, symbol in zip(range(9, -15-1, -3), 'GMk munpf'): - factor = 10 ** exp - if magnitude >= factor: - return template.format(value / factor, symbol.strip()) - return template.format(value, '') - - if isinstance(num, Constants.COMPLEX_TYPES): - num = complex(num) # Cast to python complex - if num == 0: - return '0' - output = eng_notation(num.real) if num.real else '' - output += eng_notation(num.imag, '+g' if output else 'g') + 'j' if num.imag else '' - return output - else: - return str(num) diff --git a/grc/gui/Block.py b/grc/gui/Block.py index b55e471e37..c8611933f0 100644 --- a/grc/gui/Block.py +++ b/grc/gui/Block.py @@ -36,12 +36,12 @@ from ..core.Block import Block as CoreBlock class Block(CoreBlock, Element): """The graphical signal block.""" - def __init__(self, flow_graph, n): + def __init__(self, parent, **n): """ Block constructor. Add graphics related params to the block. """ - super(self.__class__, self).__init__(flow_graph, n) + super(self.__class__, self).__init__(parent, **n) self.states.update(_coordinate=(0, 0), _rotation=0) self.width = self.height = 0 diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py index 6122ec13b4..87dd97a520 100644 --- a/grc/gui/Connection.py +++ b/grc/gui/Connection.py @@ -23,7 +23,7 @@ from . import Colors, Utils from .Constants import CONNECTOR_ARROW_BASE, CONNECTOR_ARROW_HEIGHT from .Element import Element -from ..core.Element import property_nop_write +from ..core.Element import nop_write from ..core.Connection import Connection as _Connection @@ -46,11 +46,13 @@ class Connection(Element, _Connection): self._sink_rot = self._source_rot = None self._sink_coor = self._source_coor = None - @property_nop_write + @nop_write + @property def coordinate(self): return self.source_port.get_connector_coordinate() - @property_nop_write + @nop_write + @property def rotation(self): """ Get the 0 degree rotation. diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py index bd07a667d4..97f9033974 100644 --- a/grc/gui/MainWindow.py +++ b/grc/gui/MainWindow.py @@ -41,7 +41,6 @@ from ..core import Messages ############################################################ # Main window ############################################################ - class MainWindow(Gtk.Window): """The topmost window with menus, the tool bar, and other major windows.""" @@ -56,14 +55,6 @@ class MainWindow(Gtk.Window): Setup the menu, toolbar, flow graph editor notebook, block selection window... """ self._platform = platform - - gen_opts = platform.blocks['options'].get_param('generate_options') - generate_mode_default = gen_opts.get_value() - generate_modes = [ - (o.key, o.get_name(), o.key == generate_mode_default) - for o in gen_opts.get_options()] - - # Load preferences Preferences.load(platform) # Setup window @@ -72,6 +63,7 @@ class MainWindow(Gtk.Window): self.add(vbox) # Create the menu bar and toolbar + generate_modes = platform.get_generate_options() self.add_accel_group(Actions.get_accel_group()) self.menu_bar = Bars.MenuBar(generate_modes, action_handler_callback) vbox.pack_start(self.menu_bar, False, False, 0) diff --git a/grc/gui/Param.py b/grc/gui/Param.py index 9d5b55f339..f688d2aad4 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -19,6 +19,7 @@ from __future__ import absolute_import from . import Utils, Constants from . import ParamWidgets +from .Element import Element from ..core.Param import Param as _Param @@ -26,6 +27,8 @@ from ..core.Param import Param as _Param class Param(_Param): """The graphical parameter.""" + make_cls_with_base = classmethod(Element.make_cls_with_base.__func__) + def get_input(self, *args, **kwargs): """ Get the graphical gtk class to represent this parameter. @@ -36,19 +39,20 @@ class Param(_Param): Returns: gtk input class """ - if self.get_type() in ('file_open', 'file_save'): + type_ = self.get_type() + if type_ in ('file_open', 'file_save'): input_widget_cls = ParamWidgets.FileParam elif self.is_enum(): input_widget_cls = ParamWidgets.EnumParam - elif self.get_options(): + elif self.options: input_widget_cls = ParamWidgets.EnumEntryParam - elif self.get_type() == '_multiline': + elif type_ == '_multiline': input_widget_cls = ParamWidgets.MultiLineEntryParam - elif self.get_type() == '_multiline_python_external': + elif type_ == '_multiline_python_external': input_widget_cls = ParamWidgets.PythonEditorParam else: @@ -66,8 +70,8 @@ class Param(_Param): return '<span underline="{line}" foreground="{color}" font_desc="Sans 9">{label}</span>'.format( line='low' if has_callback else 'none', color='blue' if have_pending_changes else - 'black' if self.is_valid() else - 'red', + 'black' if self.is_valid() else + 'red', label=Utils.encode(self.get_name()) ) @@ -86,6 +90,66 @@ class Param(_Param): tooltip_lines.extend(' * ' + msg for msg in errors) return '\n'.join(tooltip_lines) + def pretty_print(self): + """ + Get the repr (nice string format) for this param. + + Returns: + the string representation + """ + ################################################## + # Truncate helper method + ################################################## + def _truncate(string, style=0): + max_len = max(27 - len(self.get_name()), 3) + if len(string) > max_len: + if style < 0: # Front truncate + string = '...' + string[3-max_len:] + elif style == 0: # Center truncate + string = string[:max_len/2 - 3] + '...' + string[-max_len/2:] + elif style > 0: # Rear truncate + string = string[:max_len-3] + '...' + return string + + ################################################## + # Simple conditions + ################################################## + value = self.get_value() + if not self.is_valid(): + return _truncate(value) + if value in self.options: + return self.options_names[self.options.index(value)] + + ################################################## + # Split up formatting by type + ################################################## + # Default center truncate + truncate = 0 + e = self.get_evaluated() + t = self.get_type() + if isinstance(e, bool): + return str(e) + elif isinstance(e, Constants.COMPLEX_TYPES): + dt_str = Utils.num_to_str(e) + elif isinstance(e, Constants.VECTOR_TYPES): + # Vector types + if len(e) > 8: + # Large vectors use code + dt_str = self.get_value() + truncate = 1 + else: + # Small vectors use eval + dt_str = ', '.join(map(Utils.num_to_str, e)) + elif t in ('file_open', 'file_save'): + dt_str = self.get_value() + truncate = -1 + else: + # Other types + dt_str = str(e) + + # Done + return _truncate(dt_str, truncate) + def format_block_surface_markup(self): """ Get the markup for this param. @@ -95,5 +159,6 @@ class Param(_Param): """ return '<span foreground="{color}" font_desc="{font}"><b>{label}:</b> {value}</span>'.format( color='black' if self.is_valid() else 'red', font=Constants.PARAM_FONT, - label=Utils.encode(self.get_name()), value=Utils.encode(repr(self).replace('\n', ' ')) + label=Utils.encode(self.get_name()), + value=Utils.encode(self.pretty_print().replace('\n', ' ')) ) diff --git a/grc/gui/ParamWidgets.py b/grc/gui/ParamWidgets.py index 0c5728feed..4e6ba27ac9 100644 --- a/grc/gui/ParamWidgets.py +++ b/grc/gui/ParamWidgets.py @@ -188,11 +188,11 @@ class EnumParam(InputParam): def __init__(self, *args, **kwargs): InputParam.__init__(self, *args, **kwargs) self._input = Gtk.ComboBoxText() - for option in self.param.get_options(): - self._input.append_text(option.get_name()) + for option_name in self.param.options_names: + self._input.append_text(option_name) value = self.param.get_value() - active_index = self.param.get_option_keys().index(value) + active_index = self.param.options.index(value) self._input.set_active(active_index) self._input.connect('changed', self._editing_callback) @@ -200,7 +200,7 @@ class EnumParam(InputParam): self.pack_start(self._input, False, False, 0) def get_text(self): - return self.param.get_option_keys()[self._input.get_active()] + return self.param.options[self._input.get_active()] def set_tooltip_text(self, text): self._input.set_tooltip_text(text) @@ -212,12 +212,12 @@ class EnumEntryParam(InputParam): def __init__(self, *args, **kwargs): InputParam.__init__(self, *args, **kwargs) self._input = Gtk.ComboBoxText.new_with_entry() - for option in self.param.get_options(): - self._input.append_text(option.get_name()) + for option_name in self.param.options_names: + self._input.append_text(option_name) value = self.param.get_value() try: - active_index = self.param.get_option_keys().index(value) + active_index = self.param.options.index(value) self._input.set_active(active_index) except ValueError: self._input.set_active(-1) @@ -237,7 +237,7 @@ class EnumEntryParam(InputParam): if self.has_custom_value: return self._input.get_child().get_text() else: - return self.param.get_option_keys()[self._input.get_active()] + return self.param.options[self._input.get_active()] def set_tooltip_text(self, text): if self.has_custom_value: # custom entry diff --git a/grc/gui/Platform.py b/grc/gui/Platform.py index b8dd6aa074..6a2a13f644 100644 --- a/grc/gui/Platform.py +++ b/grc/gui/Platform.py @@ -61,12 +61,14 @@ class Platform(CorePlatform): print(e, file=sys.stderr) ############################################## - # Constructors + # Factories ############################################## Config = Config FlowGraph = FlowGraph Connection = Connection block_classes = {key: Block.make_cls_with_base(cls) for key, cls in CorePlatform.block_classes.items()} - Port = Port - Param = Param + port_classes = {key: Port.make_cls_with_base(cls) + for key, cls in CorePlatform.port_classes.items()} + param_classes = {key: Param.make_cls_with_base(cls) + for key, cls in CorePlatform.param_classes.items()} diff --git a/grc/gui/Port.py b/grc/gui/Port.py index 4b3f6edb81..db3ab9da23 100644 --- a/grc/gui/Port.py +++ b/grc/gui/Port.py @@ -25,19 +25,19 @@ from gi.repository import Gtk, PangoCairo, Pango from . import Actions, Colors, Utils, Constants from .Element import Element -from ..core.Element import property_nop_write +from ..core.Element import nop_write from ..core.Port import Port as _Port class Port(_Port, Element): """The graphical port.""" - def __init__(self, block, n, dir): + def __init__(self, parent, direction, **n): """ Port constructor. Create list of connector coordinates. """ - super(Port, self).__init__(block, n, dir) + super(self.__class__, self).__init__(parent, direction, **n) Element.__init__(self) self._connector_coordinate = (0, 0) self._hovering = True @@ -155,7 +155,8 @@ class Port(_Port, Element): elif self.is_sink: return (self.rotation + 180) % 360 - @property_nop_write + @nop_write + @property def rotation(self): return self.parent_block.rotation diff --git a/grc/gui/Utils.py b/grc/gui/Utils.py index 304c8b8411..d474c66f19 100644 --- a/grc/gui/Utils.py +++ b/grc/gui/Utils.py @@ -21,7 +21,7 @@ from __future__ import absolute_import from gi.repository import GLib -from .Constants import POSSIBLE_ROTATIONS, CANVAS_GRID_SIZE +from .Constants import POSSIBLE_ROTATIONS, CANVAS_GRID_SIZE, COMPLEX_TYPES def get_rotated_coordinate(coor, rotation): @@ -66,16 +66,6 @@ def get_angle_from_coordinates(p1, p2): return 270 if y2 > y1 else 90 -def encode(value): - """Make sure that we pass only valid utf-8 strings into markup_escape_text. - - Older versions of glib seg fault if the last byte starts a multi-byte - character. - """ - valid_utf8 = value.decode('utf-8', errors='replace').encode('utf-8') - return GLib.markup_escape_text(valid_utf8) - - def align_to_grid(coor, mode=round): def align(value): return int(mode(value / (1.0 * CANVAS_GRID_SIZE)) * CANVAS_GRID_SIZE) @@ -84,3 +74,36 @@ def align_to_grid(coor, mode=round): except TypeError: x = coor return align(coor) + + +def num_to_str(num): + """ Display logic for numbers """ + def eng_notation(value, fmt='g'): + """Convert a number to a string in engineering notation. E.g., 5e-9 -> 5n""" + template = '{:' + fmt + '}{}' + magnitude = abs(value) + for exp, symbol in zip(range(9, -15-1, -3), 'GMk munpf'): + factor = 10 ** exp + if magnitude >= factor: + return template.format(value / factor, symbol.strip()) + return template.format(value, '') + + if isinstance(num, COMPLEX_TYPES): + num = complex(num) # Cast to python complex + if num == 0: + return '0' + output = eng_notation(num.real) if num.real else '' + output += eng_notation(num.imag, '+g' if output else 'g') + 'j' if num.imag else '' + return output + else: + return str(num) + + +def encode(value): + """Make sure that we pass only valid utf-8 strings into markup_escape_text. + + Older versions of glib seg fault if the last byte starts a multi-byte + character. + """ + valid_utf8 = value.decode('utf-8', errors='replace').encode('utf-8') + return GLib.markup_escape_text(valid_utf8) |