summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Koslowski <koslowski@kit.edu>2016-07-15 23:25:54 +0200
committerSebastian Koslowski <koslowski@kit.edu>2016-07-29 15:45:07 +0200
commit93ce3961a572da6ec3dbef1f24a22f4153acaa61 (patch)
tree274a78b5ce37f5305818c6ebab7ba495a2da4e12
parent36f186bc46f528d95d9186955e91736d1fdb299e (diff)
grc: refactor: Port, Param, Options init clean-up
-rw-r--r--grc/core/Block.py300
-rw-r--r--grc/core/Constants.py11
-rw-r--r--grc/core/Element.py9
-rw-r--r--grc/core/Element.pyi16
-rw-r--r--grc/core/Param.py272
-rw-r--r--grc/core/Platform.py73
-rw-r--r--grc/core/Port.py13
-rw-r--r--grc/core/generator/FlowGraphProxy.py2
-rw-r--r--grc/core/utils/__init__.py1
-rw-r--r--grc/core/utils/_num_to_str.py30
-rw-r--r--grc/gui/Block.py4
-rw-r--r--grc/gui/Connection.py8
-rw-r--r--grc/gui/MainWindow.py10
-rw-r--r--grc/gui/Param.py79
-rw-r--r--grc/gui/ParamWidgets.py16
-rw-r--r--grc/gui/Platform.py8
-rw-r--r--grc/gui/Port.py9
-rw-r--r--grc/gui/Utils.py45
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)