From 412df3a17ec35277f5a1d2f21c6f9a287f9ef8ad Mon Sep 17 00:00:00 2001
From: Sebastian Koslowski <koslowski@kit.edu>
Date: Thu, 4 Feb 2016 17:27:21 +0100
Subject: grc-refactor: rename grc/model/ to grc/core/

---
 grc/core/Param.py | 714 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 714 insertions(+)
 create mode 100644 grc/core/Param.py

(limited to 'grc/core/Param.py')

diff --git a/grc/core/Param.py b/grc/core/Param.py
new file mode 100644
index 0000000000..f064097256
--- /dev/null
+++ b/grc/core/Param.py
@@ -0,0 +1,714 @@
+"""
+Copyright 2008-2015 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+"""
+
+import ast
+
+import re
+from gnuradio import eng_notation
+from gnuradio import gr
+
+import Constants
+from Constants import VECTOR_TYPES, COMPLEX_TYPES, REAL_TYPES, INT_TYPES
+
+from .odict import odict
+from .Element import Element
+
+# Blacklist certain ids, its not complete, but should help
+import __builtin__
+
+
+ID_BLACKLIST = ['self', 'options', 'gr', 'blks2', 'wxgui', 'wx', 'math', 'forms', 'firdes'] + \
+    filter(lambda x: not x.startswith('_'), dir(gr.top_block())) + dir(__builtin__)
+
+_check_id_matcher = re.compile('^[a-z|A-Z]\w*$')
+_show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook)$')
+
+
+def _get_keys(lst):
+    return [elem.get_key() for elem in lst]
+
+
+def _get_elem(lst, key):
+    try:
+        return lst[_get_keys(lst).index(key)]
+    except ValueError:
+        raise ValueError('Key "{}" not found in {}.'.format(key, _get_keys(lst)))
+
+
+def num_to_str(num):
+    """ Display logic for numbers """
+    if isinstance(num, COMPLEX_TYPES):
+        num = complex(num)  # Cast to python complex
+        if num == 0:
+            return '0'
+        elif num.imag == 0:
+            # Value is real
+            return '{}'.format(eng_notation.num_to_str(num.real))
+        elif num.real == 0:
+            # Value is imaginary
+            return '{}j'.format(eng_notation.num_to_str(num.imag))
+        elif num.imag < 0:
+            return '{}-{}j'.format(eng_notation.num_to_str(num.real),
+                                   eng_notation.num_to_str(abs(num.imag)))
+        else:
+            return '{}+{}j'.format(eng_notation.num_to_str(num.real),
+                                   eng_notation.num_to_str(num.imag))
+    else:
+        return str(num)
+
+
+class Option(Element):
+
+    def __init__(self, param, n):
+        Element.__init__(self, param)
+        self._name = n.find('name')
+        self._key = n.find('key')
+        self._opts = dict()
+        opts = n.findall('opt')
+        # Test against opts when non enum
+        if not self.get_parent().is_enum() and opts:
+            raise Exception('Options for non-enum types cannot have sub-options')
+        # Extract opts
+        for opt in opts:
+            # Separate the key:value
+            try:
+                key, value = opt.split(':')
+            except:
+                raise Exception('Error separating "{}" into key:value'.format(opt))
+            # Test against repeated keys
+            if key in self._opts:
+                raise Exception('Key "{}" already exists in option'.format(key))
+            # Store the option
+            self._opts[key] = value
+
+    def __str__(self):
+        return 'Option {}({})'.format(self.get_name(), self.get_key())
+
+    def get_name(self):
+        return self._name
+
+    def get_key(self):
+        return self._key
+
+    ##############################################
+    # Access Opts
+    ##############################################
+    def get_opt_keys(self):
+        return self._opts.keys()
+
+    def get_opt(self, key):
+        return self._opts[key]
+
+    def get_opts(self):
+        return self._opts.values()
+
+
+class Param(Element):
+
+    is_param = True
+
+    def __init__(self, block, n):
+        """
+        Make a new param from nested data.
+
+        Args:
+            block: the parent element
+            n: the nested odict
+        """
+        # If the base key is a valid param key, copy its data and overlay this params data
+        base_key = n.find('base_key')
+        if base_key and base_key in block.get_param_keys():
+            n_expanded = block.get_param(base_key)._n.copy()
+            n_expanded.update(n)
+            n = n_expanded
+        # Save odict in case this param will be base for another
+        self._n = n
+        # Parse the data
+        self._name = n.find('name')
+        self._key = n.find('key')
+        value = n.find('value') or ''
+        self._type = n.find('type') or 'raw'
+        self._hide = n.find('hide') or ''
+        self._tab_label = n.find('tab') or block.get_param_tab_labels()[0]
+        if self._tab_label not in block.get_param_tab_labels():
+            block.get_param_tab_labels().append(self._tab_label)
+        # Build the param
+        Element.__init__(self, block)
+        # Create the Option objects from the n data
+        self._options = list()
+        self._evaluated = None
+        for option in map(lambda o: Option(param=self, n=o), n.findall('option')):
+            key = option.get_key()
+            # Test against repeated keys
+            if key in self.get_option_keys():
+                raise Exception('Key "{}" already exists in options'.format(key))
+            # Store the option
+            self.get_options().append(option)
+        # Test the enum options
+        if self.is_enum():
+            # Test against options with identical keys
+            if len(set(self.get_option_keys())) != len(self.get_options()):
+                raise Exception('Options keys "{}" are not unique.'.format(self.get_option_keys()))
+            # Test against inconsistent keys in options
+            opt_keys = self.get_options()[0].get_opt_keys()
+            for option in self.get_options():
+                if set(opt_keys) != set(option.get_opt_keys()):
+                    raise Exception('Opt keys "{}" are not identical across all options.'.format(opt_keys))
+            # If a value is specified, it must be in the options keys
+            if value or value in self.get_option_keys():
+                self._value = value
+            else:
+                self._value = self.get_option_keys()[0]
+            if self.get_value() not in self.get_option_keys():
+                raise Exception('The value "{}" is not in the possible values of "{}".'.format(self.get_value(), self.get_option_keys()))
+        else:
+            self._value = value or ''
+        self._default = value
+        self._init = False
+        self._hostage_cells = list()
+
+    def get_types(self):
+        return (
+            'raw', 'enum',
+            'complex', 'real', 'float', 'int',
+            'complex_vector', 'real_vector', 'float_vector', 'int_vector',
+            'hex', 'string', 'bool',
+            'file_open', 'file_save', '_multiline', '_multiline_python_external',
+            'id', 'stream_id',
+            'grid_pos', 'notebook', 'gui_hint',
+            'import',
+        )
+
+    def __repr__(self):
+        """
+        Get the repr (nice string format) for this param.
+
+        Returns:
+            the string representation
+        """
+        ##################################################
+        # Truncate helper method
+        ##################################################
+        def _truncate(string, style=0):
+            max_len = max(27 - len(self.get_name()), 3)
+            if len(string) > max_len:
+                if style < 0:  # Front truncate
+                    string = '...' + string[3-max_len:]
+                elif style == 0:  # Center truncate
+                    string = string[:max_len/2 - 3] + '...' + string[-max_len/2:]
+                elif style > 0:  # Rear truncate
+                    string = string[:max_len-3] + '...'
+            return string
+
+        ##################################################
+        # Simple conditions
+        ##################################################
+        if not self.is_valid():
+            return _truncate(self.get_value())
+        if self.get_value() in self.get_option_keys():
+            return self.get_option(self.get_value()).get_name()
+
+        ##################################################
+        # Split up formatting by type
+        ##################################################
+        # Default center truncate
+        truncate = 0
+        e = self.get_evaluated()
+        t = self.get_type()
+        if isinstance(e, bool):
+            return str(e)
+        elif isinstance(e, COMPLEX_TYPES):
+            dt_str = num_to_str(e)
+        elif isinstance(e, VECTOR_TYPES):
+            # Vector types
+            if len(e) > 8:
+                # Large vectors use code
+                dt_str = self.get_value()
+                truncate = 1
+            else:
+                # Small vectors use eval
+                dt_str = ', '.join(map(num_to_str, e))
+        elif t in ('file_open', 'file_save'):
+            dt_str = self.get_value()
+            truncate = -1
+        else:
+            # Other types
+            dt_str = str(e)
+
+        # Done
+        return _truncate(dt_str, truncate)
+
+    def __repr2__(self):
+        """
+        Get the repr (nice string format) for this param.
+
+        Returns:
+            the string representation
+        """
+        if self.is_enum():
+            return self.get_option(self.get_value()).get_name()
+        return self.get_value()
+
+    def __str__(self):
+        return 'Param - {}({})'.format(self.get_name(), self.get_key())
+
+    def get_color(self):
+        """
+        Get the color that represents this param's type.
+
+        Returns:
+            a hex color code.
+        """
+        try:
+            return {
+                # Number types
+                'complex': Constants.COMPLEX_COLOR_SPEC,
+                'real': Constants.FLOAT_COLOR_SPEC,
+                'float': Constants.FLOAT_COLOR_SPEC,
+                'int': Constants.INT_COLOR_SPEC,
+                # Vector types
+                'complex_vector': Constants.COMPLEX_VECTOR_COLOR_SPEC,
+                'real_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
+                'float_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
+                'int_vector': Constants.INT_VECTOR_COLOR_SPEC,
+                # Special
+                'bool': Constants.INT_COLOR_SPEC,
+                'hex': Constants.INT_COLOR_SPEC,
+                'string': Constants.BYTE_VECTOR_COLOR_SPEC,
+                'id': Constants.ID_COLOR_SPEC,
+                'stream_id': Constants.ID_COLOR_SPEC,
+                'grid_pos': Constants.INT_VECTOR_COLOR_SPEC,
+                'notebook': Constants.INT_VECTOR_COLOR_SPEC,
+                'raw': Constants.WILDCARD_COLOR_SPEC,
+            }[self.get_type()]
+        except:
+            return '#FFFFFF'
+
+    def get_hide(self):
+        """
+        Get the hide value from the base class.
+        Hide the ID parameter for most blocks. Exceptions below.
+        If the parameter controls a port type, vlen, or nports, return part.
+        If the parameter is an empty grid position, return part.
+        These parameters are redundant to display in the flow graph view.
+
+        Returns:
+            hide the hide property string
+        """
+        hide = self.get_parent().resolve_dependencies(self._hide).strip()
+        if hide:
+            return hide
+        # Hide ID in non variable blocks
+        if self.get_key() == 'id' and not _show_id_matcher.match(self.get_parent().get_key()):
+            return 'part'
+        # Hide port controllers for type and nports
+        if self.get_key() in ' '.join(map(lambda p: ' '.join([p._type, p._nports]),
+                                          self.get_parent().get_ports())):
+            return 'part'
+        # Hide port controllers for vlen, when == 1
+        if self.get_key() in ' '.join(map(
+            lambda p: p._vlen, self.get_parent().get_ports())
+        ):
+            try:
+                if int(self.get_evaluated()) == 1:
+                    return 'part'
+            except:
+                pass
+        # Hide empty grid positions
+        if self.get_key() in ('grid_pos', 'notebook') and not self.get_value():
+            return 'part'
+        return hide
+
+    def validate(self):
+        """
+        Validate the param.
+        The value must be evaluated and type must a possible type.
+        """
+        Element.validate(self)
+        if self.get_type() not in self.get_types():
+            self.add_error_message('Type "{}" is not a possible type.'.format(self.get_type()))
+
+        self._evaluated = None
+        try:
+            self._evaluated = self.evaluate()
+        except Exception, e:
+            self.add_error_message(str(e))
+
+    def get_evaluated(self):
+        return self._evaluated
+
+    def evaluate(self):
+        """
+        Evaluate the value.
+
+        Returns:
+            evaluated type
+        """
+        self._init = True
+        self._lisitify_flag = False
+        self._stringify_flag = False
+        self._hostage_cells = list()
+        t = self.get_type()
+        v = self.get_value()
+
+        #########################
+        # Enum Type
+        #########################
+        if self.is_enum():
+            return v
+
+        #########################
+        # Numeric Types
+        #########################
+        elif t in ('raw', 'complex', 'real', 'float', 'int', 'hex', 'bool'):
+            # Raise exception if python cannot evaluate this value
+            try:
+                e = self.get_parent().get_parent().evaluate(v)
+            except Exception, e:
+                raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e))
+            # Raise an exception if the data is invalid
+            if t == 'raw':
+                return e
+            elif t == 'complex':
+                if not isinstance(e, COMPLEX_TYPES):
+                    raise Exception('Expression "{}" is invalid for type complex.'.format(str(e)))
+                return e
+            elif t == 'real' or t == 'float':
+                if not isinstance(e, REAL_TYPES):
+                    raise Exception('Expression "{}" is invalid for type float.'.format(str(e)))
+                return e
+            elif t == 'int':
+                if not isinstance(e, INT_TYPES):
+                    raise Exception('Expression "{}" is invalid for type integer.'.format(str(e)))
+                return e
+            elif t == 'hex':
+                return hex(e)
+            elif t == 'bool':
+                if not isinstance(e, bool):
+                    raise Exception('Expression "{}" is invalid for type bool.'.format(str(e)))
+                return e
+            else:
+                raise TypeError('Type "{}" not handled'.format(t))
+        #########################
+        # Numeric Vector Types
+        #########################
+        elif t in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'):
+            if not v:
+                # Turn a blank string into an empty list, so it will eval
+                v = '()'
+            # Raise exception if python cannot evaluate this value
+            try:
+                e = self.get_parent().get_parent().evaluate(v)
+            except Exception, e:
+                raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e))
+            # Raise an exception if the data is invalid
+            if t == 'complex_vector':
+                if not isinstance(e, VECTOR_TYPES):
+                    self._lisitify_flag = True
+                    e = [e]
+                if not all([isinstance(ei, COMPLEX_TYPES) for ei in e]):
+                    raise Exception('Expression "{}" is invalid for type complex vector.'.format(str(e)))
+                return e
+            elif t == 'real_vector' or t == 'float_vector':
+                if not isinstance(e, VECTOR_TYPES):
+                    self._lisitify_flag = True
+                    e = [e]
+                if not all([isinstance(ei, REAL_TYPES) for ei in e]):
+                    raise Exception('Expression "{}" is invalid for type float vector.'.format(str(e)))
+                return e
+            elif t == 'int_vector':
+                if not isinstance(e, VECTOR_TYPES):
+                    self._lisitify_flag = True
+                    e = [e]
+                if not all([isinstance(ei, INT_TYPES) for ei in e]):
+                    raise Exception('Expression "{}" is invalid for type integer vector.'.format(str(e)))
+                return e
+        #########################
+        # String Types
+        #########################
+        elif t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'):
+            # Do not check if file/directory exists, that is a runtime issue
+            try:
+                e = self.get_parent().get_parent().evaluate(v)
+                if not isinstance(e, str):
+                    raise Exception()
+            except:
+                self._stringify_flag = True
+                e = str(v)
+            if t == '_multiline_python_external':
+                ast.parse(e)  # Raises SyntaxError
+            return e
+        #########################
+        # Unique ID Type
+        #########################
+        elif t == 'id':
+            # Can python use this as a variable?
+            if not _check_id_matcher.match(v):
+                raise Exception('ID "{}" must begin with a letter and may contain letters, numbers, and underscores.'.format(v))
+            ids = [param.get_value() for param in self.get_all_params(t)]
+
+            # Id should only appear once, or zero times if block is disabled
+            if ids.count(v) > 1:
+                raise Exception('ID "{}" is not unique.'.format(v))
+            if v in ID_BLACKLIST:
+                raise Exception('ID "{}" is blacklisted.'.format(v))
+            return v
+
+        #########################
+        # Stream ID Type
+        #########################
+        elif t == 'stream_id':
+            # Get a list of all stream ids used in the virtual sinks
+            ids = [param.get_value() for param in filter(
+                lambda p: p.get_parent().is_virtual_sink(),
+                self.get_all_params(t),
+            )]
+            # Check that the virtual sink's stream id is unique
+            if self.get_parent().is_virtual_sink():
+                # Id should only appear once, or zero times if block is disabled
+                if ids.count(v) > 1:
+                    raise Exception('Stream ID "{}" is not unique.'.format(v))
+            # Check that the virtual source's steam id is found
+            if self.get_parent().is_virtual_source():
+                if v not in ids:
+                    raise Exception('Stream ID "{}" is not found.'.format(v))
+            return v
+
+        #########################
+        # GUI Position/Hint
+        #########################
+        elif t == 'gui_hint':
+            if ':' in v:
+                tab, pos = v.split(':')
+            elif '@' in v:
+                tab, pos = v, ''
+            else:
+                tab, pos = '', v
+
+            if '@' in tab:
+                tab, index = tab.split('@')
+            else:
+                index = '?'
+
+            # TODO: Problem with this code. Produces bad tabs
+            widget_str = ({
+                (True, True): 'self.%(tab)s_grid_layout_%(index)s.addWidget(%(widget)s, %(pos)s)',
+                (True, False): 'self.%(tab)s_layout_%(index)s.addWidget(%(widget)s)',
+                (False, True): 'self.top_grid_layout.addWidget(%(widget)s, %(pos)s)',
+                (False, False): 'self.top_layout.addWidget(%(widget)s)',
+            }[bool(tab), bool(pos)]) % {'tab': tab, 'index': index, 'widget': '%s', 'pos': pos}
+
+            # FIXME: Move replace(...) into the make template of the qtgui blocks
+            # Return a string here
+            class GuiHint(object):
+                def __init__(self, ws):
+                    self._ws = ws
+
+                def __call__(self, w):
+                    return (self._ws.replace('addWidget', 'addLayout') if 'layout' in w else self._ws) % w
+
+                def __str__(self):
+                    return self._ws
+            return GuiHint(widget_str)
+        #########################
+        # Grid Position Type
+        #########################
+        elif t == 'grid_pos':
+            if not v:
+                # Allow for empty grid pos
+                return ''
+            e = self.get_parent().get_parent().evaluate(v)
+            if not isinstance(e, (list, tuple)) or len(e) != 4 or not all([isinstance(ei, int) for ei in e]):
+                raise Exception('A grid position must be a list of 4 integers.')
+            row, col, row_span, col_span = e
+            # Check row, col
+            if row < 0 or col < 0:
+                raise Exception('Row and column must be non-negative.')
+            # Check row span, col span
+            if row_span <= 0 or col_span <= 0:
+                raise Exception('Row and column span must be greater than zero.')
+            # Get hostage cell parent
+            try:
+                my_parent = self.get_parent().get_param('notebook').evaluate()
+            except:
+                my_parent = ''
+            # Calculate hostage cells
+            for r in range(row_span):
+                for c in range(col_span):
+                    self._hostage_cells.append((my_parent, (row+r, col+c)))
+            # Avoid collisions
+            params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
+            for param in params:
+                for parent, cell in param._hostage_cells:
+                    if (parent, cell) in self._hostage_cells:
+                        raise Exception('Another graphical element is using parent "{}", cell "{}".'.format(str(parent), str(cell)))
+            return e
+        #########################
+        # Notebook Page Type
+        #########################
+        elif t == 'notebook':
+            if not v:
+                # Allow for empty notebook
+                return ''
+
+            # Get a list of all notebooks
+            notebook_blocks = filter(lambda b: b.get_key() == 'notebook', self.get_parent().get_parent().get_enabled_blocks())
+            # Check for notebook param syntax
+            try:
+                notebook_id, page_index = map(str.strip, v.split(','))
+            except:
+                raise Exception('Bad notebook page format.')
+            # Check that the notebook id is valid
+            try:
+                notebook_block = filter(lambda b: b.get_id() == notebook_id, notebook_blocks)[0]
+            except:
+                raise Exception('Notebook id "{}" is not an existing notebook id.'.format(notebook_id))
+
+            # Check that page index exists
+            if int(page_index) not in range(len(notebook_block.get_param('labels').evaluate())):
+                raise Exception('Page index "{}" is not a valid index number.'.format(page_index))
+            return notebook_id, page_index
+
+        #########################
+        # Import Type
+        #########################
+        elif t == 'import':
+            # New namespace
+            n = dict()
+            try:
+                exec v in n
+            except ImportError:
+                raise Exception('Import "{}" failed.'.format(v))
+            except Exception:
+                raise Exception('Bad import syntax: "{}".'.format(v))
+            return filter(lambda k: str(k) != '__builtins__', n.keys())
+
+        #########################
+        else:
+            raise TypeError('Type "{}" not handled'.format(t))
+
+    def to_code(self):
+        """
+        Convert the value to code.
+        For string and list types, check the init flag, call evaluate().
+        This ensures that evaluate() was called to set the xxxify_flags.
+
+        Returns:
+            a string representing the code
+        """
+        v = self.get_value()
+        t = self.get_type()
+        # String types
+        if t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'):
+            if not self._init:
+                self.evaluate()
+            if self._stringify_flag:
+                return '"%s"' % v.replace('"', '\"')
+            else:
+                return v
+        # Vector types
+        elif t in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'):
+            if not self._init:
+                self.evaluate()
+            if self._lisitify_flag:
+                return '(%s, )' % v
+            else:
+                return '(%s)' % v
+        else:
+            return v
+
+    def get_all_params(self, type):
+        """
+        Get all the params from the flowgraph that have the given type.
+
+        Args:
+            type: the specified type
+
+        Returns:
+            a list of params
+        """
+        return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])
+
+    def is_enum(self):
+        return self._type == 'enum'
+
+    def get_value(self):
+        value = self._value
+        if self.is_enum() and value not in self.get_option_keys():
+            value = self.get_option_keys()[0]
+            self.set_value(value)
+        return value
+
+    def set_value(self, value):
+        # Must be a string
+        self._value = str(value)
+
+    def value_is_default(self):
+        return self._default == self._value
+
+    def get_type(self):
+        return self.get_parent().resolve_dependencies(self._type)
+
+    def get_tab_label(self):
+        return self._tab_label
+
+    def get_name(self):
+        return self.get_parent().resolve_dependencies(self._name).strip()
+
+    def get_key(self):
+        return self._key
+
+    ##############################################
+    # Access Options
+    ##############################################
+    def get_option_keys(self):
+        return _get_keys(self.get_options())
+
+    def get_option(self, key):
+        return _get_elem(self.get_options(), key)
+
+    def get_options(self):
+        return self._options
+
+    ##############################################
+    # Access Opts
+    ##############################################
+    def get_opt_keys(self):
+        return self.get_option(self.get_value()).get_opt_keys()
+
+    def get_opt(self, key):
+        return self.get_option(self.get_value()).get_opt(key)
+
+    def get_opts(self):
+        return self.get_option(self.get_value()).get_opts()
+
+    ##############################################
+    # Import/Export Methods
+    ##############################################
+    def export_data(self):
+        """
+        Export this param's key/value.
+
+        Returns:
+            a nested data odict
+        """
+        n = odict()
+        n['key'] = self.get_key()
+        n['value'] = self.get_value()
+        return n
-- 
cgit v1.2.3