diff options
Diffstat (limited to 'grc')
-rw-r--r-- | grc/converter/block.py | 6 | ||||
-rw-r--r-- | grc/core/Param.py | 166 | ||||
-rw-r--r-- | grc/core/blocks/_build.py | 4 | ||||
-rw-r--r-- | grc/core/blocks/block.py | 19 | ||||
-rw-r--r-- | grc/core/generator/flow_graph.py.mako | 24 | ||||
-rw-r--r-- | grc/core/generator/hier_block.py | 4 | ||||
-rw-r--r-- | grc/core/generator/top_block.py | 12 | ||||
-rw-r--r-- | grc/core/schema_checker/block.py | 2 | ||||
-rw-r--r-- | grc/core/utils/hide_bokeh_gui_options_if_not_installed.py | 7 | ||||
-rw-r--r-- | grc/gui/MainWindow.py | 2 | ||||
-rw-r--r-- | grc/gui/Notebook.py | 2 |
11 files changed, 176 insertions, 72 deletions
diff --git a/grc/converter/block.py b/grc/converter/block.py index 04e5c905a0..0e362d97c0 100644 --- a/grc/converter/block.py +++ b/grc/converter/block.py @@ -95,14 +95,14 @@ def convert_block_xml(node): data['outputs'] = [convert_port_xml(port_node, converter.to_python_dec) for port_node in node.iterfind('source')] or no_value - - data['checks'] = [converter.to_python_dec(check_node.text) - for check_node in node.iterfind('checks')] or no_value data['value'] = ( converter.to_python_dec(node.findtext('var_value')) or ('${ value }' if block_id.startswith('variable') else no_value) ) + data['asserts'] = [converter.to_python_dec(check_node.text) + for check_node in node.iterfind('check')] or no_value + data['templates'] = convert_templates(node, converter.to_mako, block_id) or no_value docs = node.findtext('doc') diff --git a/grc/core/Param.py b/grc/core/Param.py index 56855908ea..13439f43b4 100644 --- a/grc/core/Param.py +++ b/grc/core/Param.py @@ -23,9 +23,10 @@ import ast import numbers import re import collections +import textwrap import six -from six.moves import builtins, filter, map, range, zip +from six.moves import builtins, range from . import Constants, blocks from .base import Element @@ -102,6 +103,7 @@ class Param(Element): self._evaluated = None self._stringify_flag = False self._lisitify_flag = False + self.hostage_cells = set() self._init = False @property @@ -299,38 +301,11 @@ class Param(Element): # GUI Position/Hint ######################### elif dtype == 'gui_hint': - if ':' in expr: - tab, pos = expr.split(':') - elif '@' in expr: - tab, pos = expr, '' + if self.parent_block.state == 'disabled': + return '' else: - tab, pos = '', expr + return self.parse_gui_hint(expr) - 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) ######################### # Import Type ######################### @@ -411,3 +386,132 @@ class Param(Element): def get_opt(self, item): return self.options.attributes[self.get_value()][item] + + ############################################## + # GUI Hint + ############################################## + def parse_gui_hint(self, expr): + """ + Parse/validate gui hint value. + + Args: + expr: gui_hint string from a block's 'gui_hint' param + + Returns: + string of python code for positioning GUI elements in pyQT + """ + self.hostage_cells.clear() + + # Parsing + if ':' in expr: + tab, pos = expr.split(':') + elif ',' in expr: + tab, pos = '', expr + else: + tab, pos = expr, '' + + if '@' in tab: + tab, index = tab.split('@') + else: + index = '0' + index = int(index) + + # Validation + def parse_pos(): + e = self.parent_flowgraph.evaluate(pos) + + if not isinstance(e, (list, tuple)) or len(e) not in (2, 4) or not all(isinstance(ei, int) for ei in e): + raise Exception('Invalid GUI Hint entered: {e!r} (Must be a list of {{2,4}} non-negative integers).'.format(e=e)) + + if len(e) == 2: + row, col = e + row_span = col_span = 1 + else: + row, col, row_span, col_span = e + + if (row < 0) or (col < 0): + raise Exception('Invalid GUI Hint entered: {e!r} (non-negative integers only).'.format(e=e)) + + if (row_span < 1) or (col_span < 1): + raise Exception('Invalid GUI Hint entered: {e!r} (positive row/column span required).'.format(e=e)) + + return row, col, row_span, col_span + + def validate_tab(): + tabs = (block for block in self.parent_flowgraph.iter_enabled_blocks() + if block.key == 'qtgui_tab_widget' and block.name == tab) + tab_block = next(iter(tabs), None) + if not tab_block: + raise Exception('Invalid tab name entered: {tab} (Tab name not found).'.format(tab=tab)) + + tab_index_size = int(tab_block.params['num_tabs'].value) + if index >= tab_index_size: + raise Exception('Invalid tab index entered: {tab}@{index} (Index out of range).'.format( + tab=tab, index=index)) + + # Collision Detection + def collision_detection(row, col, row_span, col_span): + my_parent = '{tab}@{index}'.format(tab=tab, index=index) if tab else 'main' + # Calculate hostage cells + for r in range(row, row + row_span): + for c in range(col, col + col_span): + self.hostage_cells.add((my_parent, (r, c))) + + for other in self.get_all_params('gui_hint'): + if other is self: + continue + collision = next(iter(self.hostage_cells & other.hostage_cells), None) + if collision: + raise Exception('Block {block!r} is also using parent {parent!r}, cell {cell!r}.'.format( + block=other.parent_block.name, parent=collision[0], cell=collision[1] + )) + + # Code Generation + if tab: + validate_tab() + layout = '{tab}_grid_layout_{index}'.format(tab=tab, index=index) + else: + layout = 'top_grid_layout' + + widget = '%s' # to be fill-out in the mail template + + if pos: + row, col, row_span, col_span = parse_pos() + collision_detection(row, col, row_span, col_span) + + widget_str = textwrap.dedent(""" + self.{layout}.addWidget({widget}, {row}, {col}, {row_span}, {col_span}) + for r in range({row}, {row_end}): + self.{layout}.setRowStretch(r, 1) + for c in range({col}, {col_end}): + self.{layout}.setColumnStretch(c, 1) + """.strip('\n')).format( + layout=layout, widget=widget, + row=row, row_span=row_span, row_end=row+row_span, + col=col, col_span=col_span, col_end=col+col_span, + ) + + else: + widget_str = 'self.{layout}.addWidget({widget})'.format(layout=layout, widget=widget) + + return widget_str + + def get_all_params(self, dtype, key=None): + """ + Get all the params from the flowgraph that have the given type and + optionally a given key + + Args: + type: the specified type + key: the key to match against + + Returns: + a list of params + """ + params = [] + for block in self.parent_flowgraph.iter_enabled_blocks(): + params.extend( + param for param in block.params.values() + if param.dtype == dtype and (key is None or key == param.name) + ) + return params diff --git a/grc/core/blocks/_build.py b/grc/core/blocks/_build.py index 9a50086cea..9221433387 100644 --- a/grc/core/blocks/_build.py +++ b/grc/core/blocks/_build.py @@ -25,7 +25,7 @@ from ._templates import MakoTemplates def build(id, label='', category='', flags='', documentation='', - checks=None, value=None, + value=None, asserts=None, parameters=None, inputs=None, outputs=None, templates=None, **kwargs): block_id = id @@ -41,7 +41,7 @@ def build(id, label='', category='', flags='', documentation='', cls.documentation = {'': documentation.strip('\n\t ').replace('\\\n', '')} - cls.checks = [_single_mako_expr(check, block_id) for check in (checks or [])] + cls.asserts = [_single_mako_expr(a, block_id) for a in (asserts or [])] cls.parameters_data = parameters or [] cls.inputs_data = inputs or [] diff --git a/grc/core/blocks/block.py b/grc/core/blocks/block.py index e0858957ab..adc046936d 100644 --- a/grc/core/blocks/block.py +++ b/grc/core/blocks/block.py @@ -55,7 +55,7 @@ class Block(Element): documentation = {'': ''} value = None - checks = [] + asserts = [] templates = MakoTemplates() parameters_data = [] @@ -127,11 +127,10 @@ class Block(Element): port_factory = self.parent_platform.make_port port_ids = set() - def make_stream_port_id(_pool=itertools.count()): - return {'sink': 'in', 'source': 'out'}[direction] + str(next(_pool)) + stream_port_ids = itertools.count() for i, port_data in enumerate(ports_n): - port_id = port_data.setdefault('id', make_stream_port_id()) + port_id = port_data.setdefault('id', str(next(stream_port_ids))) if port_id in port_ids: raise Exception('Port id "{}" already exists in {}s'.format(port_id, direction)) port_ids.add(port_id) @@ -191,18 +190,18 @@ class Block(Element): Evaluate the checks: each check must evaluate to True. """ Element.validate(self) - self._run_checks() + self._run_asserts() self._validate_generate_mode_compat() self._validate_var_value() - def _run_checks(self): + def _run_asserts(self): """Evaluate the checks""" - for check in self.checks: + for expr in self.asserts: try: - if not self.evaluate(check): - self.add_error_message('Check "{}" failed.'.format(check)) + if not self.evaluate(expr): + self.add_error_message('Assertion "{}" failed.'.format(expr)) except: - self.add_error_message('Check "{}" did not evaluate.'.format(check)) + self.add_error_message('Assertion "{}" did not evaluate.'.format(expr)) def _validate_generate_mode_compat(self): """check if this is a GUI block and matches the selected generate option""" diff --git a/grc/core/generator/flow_graph.py.mako b/grc/core/generator/flow_graph.py.mako index b054f7aebf..877c9eee9d 100644 --- a/grc/core/generator/flow_graph.py.mako +++ b/grc/core/generator/flow_graph.py.mako @@ -90,9 +90,9 @@ class ${class_name}(gr.top_block, Qt.QWidget): self.restoreGeometry(self.settings.value("geometry")) except: pass -#elif $generate_options == 'bokeh_gui' +% elif generate_options == 'bokeh_gui': -class $(class_name)(gr.top_block): +class ${class_name}(gr.top_block): def __init__(self, doc): gr.top_block.__init__(self, "${title}") self.doc = doc @@ -117,8 +117,8 @@ class ${class_name}(gr.hier_block2): <%def name="make_io_sig(io_sigs)"> <% size_strs = ['%s*%s'%(io_sig['size'], io_sig['vlen']) for io_sig in io_sigs] %> % if len(io_sigs) == 0: -gr.io_signature(0, 0, 0)\ - #elif len(${io_sigs}) == 1 +gr.io_signature(0, 0, 0) + % elif len(io_sigs) == 1: gr.io_signature(1, 1, ${size_strs[0]}) % else: gr.io_signaturev(${len(io_sigs)}, ${len(io_sigs)}, [${', '.join(ize_strs)}]) @@ -200,7 +200,7 @@ gr.io_signaturev(${len(io_sigs)}, ${len(io_sigs)}, [${', '.join(ize_strs)}]) ########################################################## ## Create a layout entry if not manually done for BokehGUI ########################################################## -%if generate_options == 'bokeh_gui' +% if generate_options == 'bokeh_gui': if self.widget_lst: input_t = bokehgui.BokehLayout.widgetbox(self.widget_lst) widgetbox = bokehgui.BokehLayout.WidgetLayout(input_t) @@ -357,7 +357,7 @@ def main(top_block_cls=${class_name}, options=None): sys.stderr.write("Monitor '{0}' does not have an enable ('en') parameter.".format("tb.${m.name}")) % endfor qapp.exec_() - #elif $generate_options == 'bokeh_gui' + % elif generate_options == 'bokeh_gui': serverProc, port = bokehgui.utils.create_server() def killProc(signum, frame, tb): tb.stop() @@ -366,16 +366,16 @@ def main(top_block_cls=${class_name}, options=None): serverProc.kill() time.sleep(1) try: - ${'#'} Define the document instance + # Define the document instance doc = curdoc() - #if ${flow_graph.get_option('author')} - doc.title = "$title - ${flow_graph.get_option('author')}" - #else + % if flow_graph.get_option('author'): + doc.title = "${title} - ${flow_graph.get_option('author')}" + % else: doc.title = "${title}" - #end if + % endif session = push_session(doc, session_id="${flow_graph.get_option('id')}", url = "http://localhost:" + port + "/bokehgui") - ${'#'} Create Top Block instance + # Create Top Block instance tb = top_block_cls(doc) try: tb.start() diff --git a/grc/core/generator/hier_block.py b/grc/core/generator/hier_block.py index 237fd71377..31cd198c01 100644 --- a/grc/core/generator/hier_block.py +++ b/grc/core/generator/hier_block.py @@ -149,8 +149,8 @@ class QtHierBlockGenerator(HierBlockGenerator): block_n['param'].append(gui_hint_param) block_n['make'] += ( - "\n#set $win = 'self.%s' % $id" - "\n${gui_hint()($win)}" + "\n<% win = 'self.' + id %>" + "\n${ gui_hint % win }" ) return {'block': block_n} diff --git a/grc/core/generator/top_block.py b/grc/core/generator/top_block.py index d6a7e35575..799ebb1076 100644 --- a/grc/core/generator/top_block.py +++ b/grc/core/generator/top_block.py @@ -4,7 +4,6 @@ import os import tempfile import textwrap import time -import re from mako.template import Template @@ -33,14 +32,13 @@ class TopBlockGenerator(object): self._generate_options = self._flow_graph.get_option('generate_options') self._mode = TOP_BLOCK_FILE_MODE - dirname = os.path.dirname(file_path) # Handle the case where the directory is read-only # In this case, use the system's temp directory - if not os.access(dirname, os.W_OK): - dirname = tempfile.gettempdir() + if not os.access(file_path, os.W_OK): + file_path = tempfile.gettempdir() filename = self._flow_graph.get_option('id') + '.py' - self.file_path = os.path.join(dirname, filename) - self._dirname = dirname + self.file_path = os.path.join(file_path, filename) + self._dirname = file_path def _warnings(self): throttling_blocks = [b for b in self._flow_graph.get_enabled_blocks() @@ -228,7 +226,7 @@ class TopBlockGenerator(object): key = port.key if not key.isdigit(): - key = re.findall(r'\d+', key)[0] + key.repr(key) return '({block}, {key})'.format(block=block, key=key) diff --git a/grc/core/schema_checker/block.py b/grc/core/schema_checker/block.py index db8830fddf..ea079b4276 100644 --- a/grc/core/schema_checker/block.py +++ b/grc/core/schema_checker/block.py @@ -44,7 +44,7 @@ BLOCK_SCHEME = expand( inputs=Spec(types=list, required=False, item_scheme=PORT_SCHEME), outputs=Spec(types=list, required=False, item_scheme=PORT_SCHEME), - checks=(list, str_), + asserts=(list, str_), value=str_, templates=Spec(types=dict, required=False, item_scheme=TEMPLATES_SCHEME), diff --git a/grc/core/utils/hide_bokeh_gui_options_if_not_installed.py b/grc/core/utils/hide_bokeh_gui_options_if_not_installed.py index f209e515a8..ab4a42b2e7 100644 --- a/grc/core/utils/hide_bokeh_gui_options_if_not_installed.py +++ b/grc/core/utils/hide_bokeh_gui_options_if_not_installed.py @@ -20,5 +20,8 @@ def hide_bokeh_gui_options_if_not_installed(options_blk): try: import bokehgui except ImportError: - generate_options = options_blk.params['generate_options'] - del generate_options.options['bokeh_gui'] + for param in options_blk.parameters_data: + if param['id'] == 'generate_options': + ind = param['options'].index('bokeh_gui') + del param['options'][ind] + del param['option_labels'][ind] diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py index ec927a4777..e737610a79 100644 --- a/grc/gui/MainWindow.py +++ b/grc/gui/MainWindow.py @@ -71,7 +71,7 @@ class MainWindow(Gtk.ApplicationWindow): vbox = Gtk.VBox() self.add(vbox) - icon_theme = gtk.icon_theme_get_default() + icon_theme = Gtk.IconTheme.get_default() icon = icon_theme.lookup_icon("gnuradio-grc", 48, 0) if not icon: # Set window icon diff --git a/grc/gui/Notebook.py b/grc/gui/Notebook.py index 21db913c0e..9f63190b31 100644 --- a/grc/gui/Notebook.py +++ b/grc/gui/Notebook.py @@ -151,7 +151,7 @@ class Page(Gtk.HBox): generator """ platform = self.flow_graph.parent_platform - return platform.Generator(self.flow_graph, self.file_path) + return platform.Generator(self.flow_graph, os.path.dirname(self.file_path)) def _handle_button(self, button): """ |