diff options
author | Andrej Rode <mail@andrejro.de> | 2018-06-23 17:44:58 +0200 |
---|---|---|
committer | Andrej Rode <mail@andrejro.de> | 2018-06-23 17:44:58 +0200 |
commit | 5e07e65fe9350a51cd45f8f21cfa6bf144e42e1c (patch) | |
tree | b88dde44a0ba1f2ce1b382bbca02e80050f77a17 /grc | |
parent | 9f7e39ee5141791f93dbc1c842b1d1a49e33b068 (diff) | |
parent | e82e337d0c74fa7ea5e8b8429de29bec43818552 (diff) |
Merge branch 'python3_fix' into python3_merge
Diffstat (limited to 'grc')
58 files changed, 1636 insertions, 1837 deletions
diff --git a/grc/blocks/CMakeLists.txt b/grc/blocks/CMakeLists.txt index 2dbc7c6d0d..f5ec6dd214 100644 --- a/grc/blocks/CMakeLists.txt +++ b/grc/blocks/CMakeLists.txt @@ -21,40 +21,39 @@ include(GrPython) file(GLOB yml_files "*.yml") -file(GLOB xml_files "*.xml") -macro(REPLACE_IN_FILE _xml_block match replace) - set(xml_block_src "${CMAKE_CURRENT_SOURCE_DIR}/${_xml_block}") - set(xml_block "${CMAKE_CURRENT_BINARY_DIR}/${_xml_block}") +macro(REPLACE_IN_FILE _yml_block match replace) + set(yml_block_src "${CMAKE_CURRENT_SOURCE_DIR}/${_yml_block}") + set(yml_block "${CMAKE_CURRENT_BINARY_DIR}/${_yml_block}") - list(REMOVE_ITEM xml_files "${xml_block_src}") - file(READ "${xml_block_src}" xml_block_src_text) + list(REMOVE_ITEM yml_files "${yml_block_src}") + file(READ "${yml_block_src}" yml_block_src_text) string(REPLACE "${match}" "${replace}" - xml_block_text "${xml_block_src_text}") - file(WRITE "${xml_block}" "${xml_block_text}") + yml_block_text "${yml_block_src_text}") + file(WRITE "${yml_block}" "${yml_block_text}") - list(APPEND generated_xml_files "${xml_block}") + list(APPEND generated_yml_files "${yml_block}") endmacro() -macro(GEN_BLOCK_XML _generator _xml_block) +macro(GEN_BLOCK_YML _generator _yml_block) set(generator ${CMAKE_CURRENT_SOURCE_DIR}/${_generator}) - set(xml_block ${CMAKE_CURRENT_BINARY_DIR}/${_xml_block}) - list(APPEND generated_xml_files ${xml_block}) + set(yml_block ${CMAKE_CURRENT_BINARY_DIR}/${_yml_block}) + list(APPEND generated_yml_files ${yml_block}) add_custom_command( - DEPENDS ${generator} OUTPUT ${xml_block} - COMMAND ${PYTHON_EXECUTABLE} ${generator} ${xml_block} + DEPENDS ${generator} OUTPUT ${yml_block} + COMMAND ${PYTHON_EXECUTABLE} ${generator} ${yml_block} ) endmacro() -GEN_BLOCK_XML(variable_struct.xml.py variable_struct.xml) +GEN_BLOCK_YML(variable_struct.block.yml.py variable_struct.block.yml) if(DESIRED_QT_VERSION EQUAL 4) - REPLACE_IN_FILE(options.xml PyQt5 PyQt4) + REPLACE_IN_FILE(options.yml PyQt5 PyQt4) endif() -add_custom_target(grc_generated_xml ALL DEPENDS ${generated_xml_files}) +add_custom_target(grc_generated_yml ALL DEPENDS ${generated_yml_files}) install( - FILES ${yml_files} ${xml_files} ${generated_xml_files} + FILES ${yml_files} ${generated_yml_files} DESTINATION ${GRC_BLOCKS_DIR} ) diff --git a/grc/blocks/block_tree.xml b/grc/blocks/block_tree.xml deleted file mode 100644 index 2a063f1f60..0000000000 --- a/grc/blocks/block_tree.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0"?> -<cat> - <name>[Core]</name> - <cat> - <name>Misc</name> - <block>pad_source</block> - <block>pad_sink</block> - <block>virtual_source</block> - <block>virtual_sink</block> - - <block>epy_module</block> - - <block>note</block> - <block>import</block> - </cat> - <cat> - <name>Variables</name> - <block>variable</block> - <block>variable_struct</block> - <block>variable_config</block> - <block>variable_function_probe</block> - <block>parameter</block> - </cat> -</cat> diff --git a/grc/blocks/grc.tree.yml b/grc/blocks/grc.tree.yml new file mode 100644 index 0000000000..c84a6dc478 --- /dev/null +++ b/grc/blocks/grc.tree.yml @@ -0,0 +1,15 @@ +'[Core]': +- Misc: + - pad_source + - pad_sink + - virtual_source + - virtual_sink + - epy_module + - note + - import +- Variables: + - variable + - variable_struct + - variable_config + - variable_function_probe + - parameter diff --git a/grc/blocks/import.block.yml b/grc/blocks/import.block.yml new file mode 100644 index 0000000000..2d36b7396d --- /dev/null +++ b/grc/blocks/import.block.yml @@ -0,0 +1,20 @@ +id: import_ +label: Import + +parameters: +- id: imports + label: Import + dtype: import + +templates: + imports: ${imports} + +documentation: |- + Import additional python modules into the namespace. + + Examples: + from gnuradio.filter import firdes + import math,cmath + from math import pi + +file_format: 1 diff --git a/grc/blocks/import.xml b/grc/blocks/import.xml deleted file mode 100644 index 58e99a2d01..0000000000 --- a/grc/blocks/import.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Import python modules into the namespace -################################################### - --> -<block> - <name>Import</name> - <key>import</key> - <import>$imports</import> - <make></make> - <param> - <name>Import</name> - <key>imports</key> - <value></value> - <type>import</type> - </param> - <doc> -Import additional python modules into the namespace. - -Examples: -from gnuradio.filter import firdes -import math,cmath -from math import pi - </doc> -</block> diff --git a/grc/blocks/note.block.yml b/grc/blocks/note.block.yml new file mode 100644 index 0000000000..3f21a75ceb --- /dev/null +++ b/grc/blocks/note.block.yml @@ -0,0 +1,9 @@ +id: note +label: Note + +parameters: +- id: note + label: Note + dtype: string + +file_format: 1 diff --git a/grc/blocks/note.xml b/grc/blocks/note.xml deleted file mode 100644 index db6687c033..0000000000 --- a/grc/blocks/note.xml +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Note Block (dummy) -################################################### - --> -<block> - <name>Note</name> - <key>note</key> - <make></make> - <param> - <name>Note</name> - <key>note</key> - <value></value> - <type>string</type> - </param> -</block> diff --git a/grc/blocks/options.block.yml b/grc/blocks/options.block.yml new file mode 100644 index 0000000000..ab18f8ae5f --- /dev/null +++ b/grc/blocks/options.block.yml @@ -0,0 +1,146 @@ +id: options +label: Options + +parameters: +- id: title + label: Title + dtype: string + hide: ${ ('none' if title else 'part') } +- id: author + label: Author + dtype: string + hide: ${ ('none' if author else 'part') } +- id: description + label: Description + dtype: string + hide: ${ ('none' if description else 'part') } +- id: window_size + label: Canvas Size + dtype: int_vector + hide: part +- id: generate_options + label: Generate Options + dtype: enum + default: qt_gui + options: [qt_gui, bokeh_gui, no_gui, hb, hb_qt_gui] + option_labels: [QT GUI, Bokeh GUI, No GUI, Hier Block, Hier Block (QT GUI)] +- id: category + label: Category + dtype: string + default: '[GRC Hier Blocks]' + hide: ${ ('none' if generate_options.startswith('hb') else 'all') } +- id: run_options + label: Run Options + dtype: enum + default: prompt + options: [run, prompt] + option_labels: [Run to Completion, Prompt for Exit] + hide: ${ ('none' if generate_options == 'no_gui' else 'all') } +- id: placement + label: Widget Placement + dtype: int_vector + default: (0,0) + hide: ${ ('part' if generate_options == 'bokeh_gui' else 'all') } +- id: sizing_mode + label: Sizing Mode + dtype: enum + default: fixed + options: [fixed, stretch_both, scale_width, scale_height, scale_both] + option_labels: [Fixed, Stretch Both, Scale Width, Scale Height, Scale Both] + hide: ${ ('part' if generate_options == 'bokeh_gui' else 'all') } +- id: run + label: Run + dtype: bool + default: 'True' + options: ['True', 'False'] + option_labels: [Autostart, 'Off'] + hide: ${ ('all' if generate_options not in ('qt_gui', 'bokeh_gui') else ('part' + if run else 'none')) } +- id: max_nouts + label: Max Number of Output + dtype: int + default: '0' + hide: ${ ('all' if generate_options.startswith('hb') else ('none' if max_nouts + else 'part')) } +- id: realtime_scheduling + label: Realtime Scheduling + dtype: enum + options: ['', '1'] + option_labels: ['Off', 'On'] + hide: ${ ('all' if generate_options.startswith('hb') else ('none' if realtime_scheduling + else 'part')) } +- id: qt_qss_theme + label: QSS Theme + dtype: file_open + hide: ${ ('all' if generate_options != 'qt_gui' else ('none' if qt_qss_theme else + 'part')) } +- id: thread_safe_setters + label: Thread-safe setters + category: Advanced + dtype: enum + options: ['', '1'] + option_labels: ['Off', 'On'] + hide: part +- id: run_command + label: Run Command + category: Advanced + dtype: string + default: '{python} -u {filename}' + hide: ${ ('all' if generate_options.startswith('hb') else 'part') } +- id: hier_block_src_path + label: Hier Block Source Path + category: Advanced + dtype: string + default: '.:' + hide: part + +asserts: +- ${ not window_size or len(window_size) == 2 } +- ${ not window_size or 300 <= window_size[0] <= 4096 } +- ${ not window_size or 300 <= window_size[1] <= 4096 } +- ${ len(placement) == 4 or len(placement) == 2 } +- ${ all(i >= 0 for i in placement) } + +templates: + imports: |- + from gnuradio import gr + from gnuradio.filter import firdes + % if generate_options == 'qt_gui': + from PyQt5 import Qt + import sys + % endif + % if generate_options == 'bokeh_gui': + import time + import signal + import functools + from bokeh.client import push_session + from bokeh.plotting import curdoc + % endif + % if not generate_options.startswith('hb'): + from argparse import ArgumentParser + from gnuradio.eng_arg import eng_float, intx + from gnuradio import eng_notation + % endif + callbacks: + - 'if ${run}: self.start() + + else: self.stop(); self.wait()' + +documentation: |- + The options block sets special parameters for the flow graph. Only one option block is allowed per flow graph. + + Title, author, and description parameters are for identification purposes. + + The window size controls the dimensions of the flow graph editor. The window size (width, height) must be between (300, 300) and (4096, 4096). + + The generate options controls the type of code generated. Non-graphical flow graphs should avoid using graphical sinks or graphical variable controls. + + In a graphical application, run can be controlled by a variable to start and stop the flowgraph at runtime. + + The id of this block determines the name of the generated file and the name of the class. For example, an id of my_block will generate the file my_block.py and class my_block(gr.... + + The category parameter determines the placement of the block in the block selection window. The category only applies when creating hier blocks. To put hier blocks into the root category, enter / for the category. + + The Max Number of Output is the maximum number of output items allowed for any block in the flowgraph; to disable this set the max_nouts equal to 0.Use this to adjust the maximum latency a flowgraph can exhibit. + +file_format: 1 diff --git a/grc/blocks/options.xml b/grc/blocks/options.xml deleted file mode 100644 index 5fa4fc1b17..0000000000 --- a/grc/blocks/options.xml +++ /dev/null @@ -1,250 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Options Block: -## options for window size, -## and flow graph building. -################################################### - --> -<block> - <name>Options</name> - <key>options</key> - <import>from gnuradio import gr</import> - <import>from gnuradio.filter import firdes</import> - <import>#if $generate_options() == 'qt_gui' -from PyQt5 import Qt -import sys -#end if -#if $generate_options() == 'bokeh_gui' -import time -import signal -import functools -from bokeh.client import push_session -from bokeh.plotting import curdoc -#end if -#if not $generate_options().startswith('hb') -from argparse import ArgumentParser -from gnuradio.eng_arg import eng_float, intx -from gnuradio import eng_notation -#end if</import> - <make></make> - <callback>if $run: self.start() -else: self.stop(); self.wait()</callback> - <param> - <name>Title</name> - <key>title</key> - <value></value> - <type>string</type> - <hide>#if $title() then 'none' else 'part'#</hide> - </param> - <param> - <name>Author</name> - <key>author</key> - <value></value> - <type>string</type> - <hide>#if $author() then 'none' else 'part'#</hide> - </param> - <param> - <name>Description</name> - <key>description</key> - <value></value> - <type>string</type> - <hide>#if $description() then 'none' else 'part'#</hide> - </param> - <param> - <name>Canvas Size</name> - <key>window_size</key> - <value></value> - <type>int_vector</type> - <hide>part</hide> - </param> - <param> - <name>Generate Options</name> - <key>generate_options</key> - <value>qt_gui</value> - <type>enum</type> - <option> - <name>QT GUI</name> - <key>qt_gui</key> - </option> - <option> - <name>Bokeh GUI</name> - <key>bokeh_gui</key> - </option> - <option> - <name>No GUI</name> - <key>no_gui</key> - </option> - <option> - <name>Hier Block</name> - <key>hb</key> - </option> - <option> - <name>Hier Block (QT GUI)</name> - <key>hb_qt_gui</key> - </option> - </param> - <param> - <name>Category</name> - <key>category</key> - <value>[GRC Hier Blocks]</value> - <type>string</type> - <hide>#if $generate_options().startswith('hb') then 'none' else 'all'#</hide> - </param> - <param> - <name>Run Options</name> - <key>run_options</key> - <value>prompt</value> - <type>enum</type> - <hide>#if $generate_options() == 'no_gui' then 'none' else 'all'#</hide> - <option> - <name>Run to Completion</name> - <key>run</key> - </option> - <option> - <name>Prompt for Exit</name> - <key>prompt</key> - </option> - </param> - <param> - <name>Widget Placement</name> - <key>placement</key> - <value>(0,0)</value> - <type>int_vector</type> - <hide>#if $generate_options() == 'bokeh_gui' then 'part' else 'all'#</hide> - </param> - <param> - <name>Sizing Mode</name> - <key>sizing_mode</key> - <value>fixed</value> - <type>enum</type> - <hide>#if $generate_options() == 'bokeh_gui' then 'part' else 'all'#</hide> - <option> - <name>Fixed</name> - <key>fixed</key> - </option> - <option> - <name>Stretch Both</name> - <key>stretch_both</key> - </option> - <option> - <name>Scale Width</name> - <key>scale_width</key> - </option> - <option> - <name>Scale Height</name> - <key>scale_height</key> - </option> - <option> - <name>Scale Both</name> - <key>scale_both</key> - </option> - </param> - <param> - <name>Run</name> - <key>run</key> - <value>True</value> - <type>bool</type> - <hide>#if $generate_options() not in ('qt_gui', 'bokeh_gui') then 'all' else ('part' if $run() else 'none')#</hide> - <option> - <name>Autostart</name> - <key>True</key> - </option> - <option> - <name>Off</name> - <key>False</key> - </option> - </param> - <param> - <name>Max Number of Output</name> - <key>max_nouts</key> - <value>0</value> - <type>int</type> - <hide>#if $generate_options().startswith('hb') then 'all' else ('none' if $max_nouts() else 'part')#</hide> - </param> - <param> - <name>Realtime Scheduling</name> - <key>realtime_scheduling</key> - <value></value> - <type>enum</type> - <hide>#if $generate_options().startswith('hb') then 'all' else ('none' if $realtime_scheduling() else 'part')#</hide> - <option> - <name>Off</name> - <key></key> - </option> - <option> - <name>On</name> - <key>1</key> - </option> - </param> - <param> - <name>QSS Theme</name> - <key>qt_qss_theme</key> - <value></value> - <type>file_open</type> - <hide>#if $generate_options() != 'qt_gui' then 'all' else ('none' if $qt_qss_theme() else 'part')#</hide> - </param> - <param> - <name>Thread-safe setters</name> - <key>thread_safe_setters</key> - <value></value> - <type>enum</type> - <hide>part</hide> - <option> - <name>Off</name> - <key></key> - </option> - <option> - <name>On</name> - <key>1</key> - </option> - <tab>Advanced</tab> - </param> - <param> - <name>Run Command</name> - <key>run_command</key> - <value>{python} -u {filename}</value> - <type>string</type> - <hide>#if $generate_options().startswith('hb') then 'all' else 'part'</hide> - <tab>Advanced</tab> - </param> - <param> - <name>Hier Block Source Path</name> - <key>hier_block_src_path</key> - <value>.:</value> - <type>string</type> - <hide>part</hide> - <tab>Advanced</tab> - </param> - <check>not $window_size or len($window_size) == 2</check> - <check>not $window_size or 300 <= $(window_size)[0] <= 4096</check> - <check>not $window_size or 300 <= $(window_size)[1] <= 4096</check> - <check>len($placement) == 4 or len($placement) == 2</check> - <check>all(i >= 0 for i in $(placement))</check> - <doc> -The options block sets special parameters for the flow graph. \ -Only one option block is allowed per flow graph. - -Title, author, and description parameters are for identification purposes. - -The window size controls the dimensions of the flow graph editor. \ -The window size (width, height) must be between (300, 300) and (4096, 4096). - -The generate options controls the type of code generated. \ -Non-graphical flow graphs should avoid using graphical sinks or graphical variable controls. - -In a graphical application, \ -run can be controlled by a variable to start and stop the flowgraph at runtime. - -The id of this block determines the name of the generated file and the name of the class. \ -For example, an id of my_block will generate the file my_block.py and class my_block(gr.... - -The category parameter determines the placement of the block in the block selection window. \ -The category only applies when creating hier blocks. \ -To put hier blocks into the root category, enter / for the category. - -The Max Number of Output is the maximum number of output items allowed for any block \ -in the flowgraph; to disable this set the max_nouts equal to 0.\ -Use this to adjust the maximum latency a flowgraph can exhibit. - </doc> -</block> diff --git a/grc/blocks/pad_sink.block.yml b/grc/blocks/pad_sink.block.yml new file mode 100644 index 0000000000..d304a998b4 --- /dev/null +++ b/grc/blocks/pad_sink.block.yml @@ -0,0 +1,51 @@ +id: pad_sink +label: Pad Sink + +parameters: +- id: label + label: Label + dtype: string + default: out +- id: type + label: Input Type + dtype: enum + options: [complex, float, int, short, byte, bit, message, ''] + option_labels: [Complex, Float, Int, Short, Byte, Bits, Message, Wildcard] + option_attributes: + size: [gr.sizeof_gr_complex, gr.sizeof_float, gr.sizeof_int, gr.sizeof_short, + gr.sizeof_char, gr.sizeof_char, '0', '0'] + hide: part +- id: vlen + label: Vec Length + dtype: int + default: '1' + hide: ${ 'part' if vlen == 1 else 'none' } +- id: num_streams + label: Num Streams + dtype: int + default: '1' + hide: part +- id: optional + label: Optional + dtype: bool + default: 'False' + options: ['True', 'False'] + option_labels: [Optional, Required] + hide: part + +inputs: +- domain: stream + dtype: ${ type } + vlen: ${ vlen } + multiplicity: ${ num_streams } + +asserts: +- ${ vlen > 0 } +- ${ num_streams > 0 } + +documentation: |- + The inputs of this block will become the outputs to this flow graph when it is instantiated as a hierarchical block. + + Pad sink will be ordered alphabetically by their ids. The first pad sink will have an index of 0. + +file_format: 1 diff --git a/grc/blocks/pad_sink.xml b/grc/blocks/pad_sink.xml deleted file mode 100644 index 8ea8871d2e..0000000000 --- a/grc/blocks/pad_sink.xml +++ /dev/null @@ -1,103 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Pad Sink: IO Pads -################################################### - --> -<block> - <name>Pad Sink</name> - <key>pad_sink</key> - <make></make> - <param> - <name>Label</name> - <key>label</key> - <value>out</value> - <type>string</type> - </param> - <param> - <name>Input Type</name> - <key>type</key> - <type>enum</type> - <option> - <name>Complex</name> - <key>complex</key> - <opt>size:gr.sizeof_gr_complex</opt> - </option> - <option> - <name>Float</name> - <key>float</key> - <opt>size:gr.sizeof_float</opt> - </option> - <option> - <name>Int</name> - <key>int</key> - <opt>size:gr.sizeof_int</opt> - </option> - <option> - <name>Short</name> - <key>short</key> - <opt>size:gr.sizeof_short</opt> - </option> - <option> - <name>Byte</name> - <key>byte</key> - <opt>size:gr.sizeof_char</opt> - </option> - <option> - <name>Bits</name> - <key>bit</key> - <opt>size:gr.sizeof_char</opt> - </option> - <option> - <name>Message</name> - <key>message</key> - <opt>size:0</opt> - </option> - <option> - <name>Wildcard</name> - <key></key> - <opt>size:0</opt> - </option> - </param> - <param> - <name>Vec Length</name> - <key>vlen</key> - <value>1</value> - <type>int</type> - </param> - - <param> - <name>Num Streams</name> - <key>num_streams</key> - <value>1</value> - <type>int</type> - </param> - <param> - <name>Optional</name> - <key>optional</key> - <value>False</value> - <type>bool</type> - <hide>part</hide> - <option> - <name>Optional</name> - <key>True</key> - </option> - <option> - <name>Required</name> - <key>False</key> - </option> - </param> - <check>$vlen > 0</check> - <check>$num_streams > 0</check> - <sink> - <name>in</name> - <type>$type</type> - <vlen>$vlen</vlen> - <nports>$num_streams</nports> - </sink> - <doc> -The inputs of this block will become the outputs to this flow graph when it is instantiated as a hierarchical block. - -Pad sink will be ordered alphabetically by their ids. The first pad sink will have an index of 0. - </doc> -</block> diff --git a/grc/blocks/pad_source.block.yml b/grc/blocks/pad_source.block.yml new file mode 100644 index 0000000000..92f7a8b822 --- /dev/null +++ b/grc/blocks/pad_source.block.yml @@ -0,0 +1,51 @@ +id: pad_source +label: Pad Source + +parameters: +- id: label + label: Label + dtype: string + default: in +- id: type + label: Output Type + dtype: enum + options: [complex, float, int, short, byte, bit, message, ''] + option_labels: [Complex, Float, Int, Short, Byte, Bits, Message, Wildcard] + option_attributes: + size: [gr.sizeof_gr_complex, gr.sizeof_float, gr.sizeof_int, gr.sizeof_short, + gr.sizeof_char, gr.sizeof_char, '0', '0'] + hide: part +- id: vlen + label: Vec Length + dtype: int + default: '1' + hide: ${ 'part' if vlen == 1 else 'none' } +- id: num_streams + label: Num Streams + dtype: int + default: '1' + hide: part +- id: optional + label: Optional + dtype: bool + default: 'False' + options: ['True', 'False'] + option_labels: [Optional, Required] + hide: part + +outputs: +- domain: stream + dtype: ${ type } + vlen: ${ vlen } + multiplicity: ${ num_streams } + +asserts: +- ${ vlen > 0 } +- ${ num_streams > 0 } + +documentation: |- + The outputs of this block will become the inputs to this flow graph when it is instantiated as a hierarchical block. + + Pad sources will be ordered alphabetically by their ids. The first pad source will have an index of 0. + +file_format: 1 diff --git a/grc/blocks/pad_source.xml b/grc/blocks/pad_source.xml deleted file mode 100644 index 3d8ccbed6a..0000000000 --- a/grc/blocks/pad_source.xml +++ /dev/null @@ -1,104 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Pad Source: IO Pads -################################################### - --> -<block> - <name>Pad Source</name> - <key>pad_source</key> - <make></make> - <param> - <name>Label</name> - <key>label</key> - <value>in</value> - <type>string</type> - </param> - <param> - <name>Output Type</name> - <key>type</key> - <type>enum</type> - <option> - <name>Complex</name> - <key>complex</key> - <opt>size:gr.sizeof_gr_complex</opt> - </option> - <option> - <name>Float</name> - <key>float</key> - <opt>size:gr.sizeof_float</opt> - </option> - <option> - <name>Int</name> - <key>int</key> - <opt>size:gr.sizeof_int</opt> - </option> - <option> - <name>Short</name> - <key>short</key> - <opt>size:gr.sizeof_short</opt> - </option> - <option> - <name>Byte</name> - <key>byte</key> - <opt>size:gr.sizeof_char</opt> - </option> - <option> - <name>Bits</name> - <key>bit</key> - <opt>size:gr.sizeof_char</opt> - </option> - <option> - <name>Message</name> - <key>message</key> - <opt>size:0</opt> - </option> - <option> - <name>Wildcard</name> - <key></key> - <opt>size:0</opt> - </option> - </param> - <param> - <name>Vec Length</name> - <key>vlen</key> - <value>1</value> - <type>int</type> - </param> - - <param> - <name>Num Streams</name> - <key>num_streams</key> - <value>1</value> - <type>int</type> - </param> - - <param> - <name>Optional</name> - <key>optional</key> - <value>False</value> - <type>bool</type> - <hide>part</hide> - <option> - <name>Optional</name> - <key>True</key> - </option> - <option> - <name>Required</name> - <key>False</key> - </option> - </param> - <check>$vlen > 0</check> - <check>$num_streams > 0</check> - <source> - <name>out</name> - <type>$type</type> - <vlen>$vlen</vlen> - <nports>$num_streams</nports> - </source> - <doc> -The outputs of this block will become the inputs to this flow graph when it is instantiated as a hierarchical block. - -Pad sources will be ordered alphabetically by their ids. The first pad source will have an index of 0. - </doc> -</block> diff --git a/grc/blocks/parameter.block.yml b/grc/blocks/parameter.block.yml new file mode 100644 index 0000000000..ac97c7d319 --- /dev/null +++ b/grc/blocks/parameter.block.yml @@ -0,0 +1,55 @@ +id: parameter +label: Parameter + +parameters: +- id: label + label: Label + dtype: string + hide: ${ ('none' if label else 'part') } +- id: value + label: Value + dtype: ${ type.type } + default: '0' +- id: type + label: Type + dtype: enum + options: ['', complex, eng_float, intx, long, str] + option_labels: [None, Complex, Float, Int, Long, String] + option_attributes: + type: [raw, complex, real, int, int, string] + hide: ${ ('none' if type else 'part') } +- id: short_id + label: Short ID + dtype: string + hide: ${ 'all' if not type else ('none' if short_id else 'part') } +- id: hide + label: Show + dtype: enum + options: [none, part] + option_labels: [Always, Only in Properties] + hide: part + +asserts: +- ${ len(short_id) in (0, 1) } +- ${ short_id == '' or short_id.isalpha() } + +templates: + var_make: self.${id} = ${id} + make: ${value} + +documentation: |- + This block represents a parameter to the flow graph. A parameter can be used to pass command line arguments into a top block. Or, parameters can pass arguments into an instantiated hierarchical block. + + The paramater value cannot depend on any variables. + + Leave the label blank to use the parameter id as the label. + + When type is not None, this parameter also becomes a command line option of the form: + + -[short_id] --[id] [value] + + The Short ID field may be left blank. + + To disable showing the parameter on the hierarchical block in GRC, use Only in Properties option in the Show field. + +file_format: 1 diff --git a/grc/blocks/parameter.xml b/grc/blocks/parameter.xml deleted file mode 100644 index f01527acb0..0000000000 --- a/grc/blocks/parameter.xml +++ /dev/null @@ -1,118 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Parameter block: a grc variable with key, value -################################################### - --> -<block> - <name>Parameter</name> - <key>parameter</key> - <var_make>self.$(id) = $(id)</var_make> - <make>$value</make> - <param> - <name>Label</name> - <key>label</key> - <value></value> - <type>string</type> - <hide>#if $label() then 'none' else 'part'#</hide> - </param> - <param> - <name>Value</name> - <key>value</key> - <value>0</value> - <type>$type.type</type> - </param> - <param> - <name>Type</name> - <key>type</key> - <value></value> - <type>enum</type> - <hide>#if $type() then 'none' else 'part'#</hide> - <option> - <name>None</name> - <key></key> - <opt>type:raw</opt> - </option> - <option> - <name>Complex</name> - <key>complex</key> - <opt>type:complex</opt> - </option> - <option> - <name>Float</name> - <key>eng_float</key> - <opt>type:real</opt> - </option> - <option> - <name>Int</name> - <key>intx</key> - <opt>type:int</opt> - </option> - <option> - <name>Long</name> - <key>long</key> - <opt>type:int</opt> - </option> - <option> - <name>String</name> - <key>str</key> - <opt>type:string</opt> - </option> - <!-- Do not forget to add option value type handler import into - grc/python/flow_graph.tmpl for each new type. --> - <!-- not supported yet in tmpl - <option> - <name>Boolean</name> - <key>bool</key> - <opt>type:bool</opt> - </option> - --> - </param> - <param> - <name>Short ID</name> - <key>short_id</key> - <value></value> - <type>string</type> - <hide>#if not $type() -all#slurp -#elif $short_id() -none#slurp -#else -part#slurp -#end if</hide> - </param> - <param> - <name>Show</name> - <key>hide</key> - <value></value> - <type>enum</type> - <hide>part</hide> - <option> - <name>Always</name> - <key>none</key> <!--## Do not hide the parameter value--> - </option> - <option> - <name>Only in Properties</name> - <key>part</key> <!--## Partially hide the parameter value--> - </option> - </param> - <check>len($short_id) in (0, 1)</check> - <check>$short_id == '' or $(short_id).isalpha()</check> - <doc> -This block represents a parameter to the flow graph. \ -A parameter can be used to pass command line arguments into a top block. \ -Or, parameters can pass arguments into an instantiated hierarchical block. - -The paramater value cannot depend on any variables. - -Leave the label blank to use the parameter id as the label. - -When type is not None, this parameter also becomes a command line option of the form: - --[short_id] --[id] [value] - -The Short ID field may be left blank. - -To disable showing the parameter on the hierarchical block in GRC, use Only in Properties option in the Show field. - </doc> -</block> diff --git a/grc/blocks/variable.block.yml b/grc/blocks/variable.block.yml new file mode 100644 index 0000000000..fa62dabe87 --- /dev/null +++ b/grc/blocks/variable.block.yml @@ -0,0 +1,19 @@ +id: variable +label: Variable + +parameters: +- id: value + label: Value + dtype: raw + default: '0' +value: ${ value } + +templates: + var_make: self.${id} = ${id} = ${value} + callbacks: + - self.set_${id}(${value}) + +documentation: |- + This block maps a value to a unique variable. This variable block has no graphical representation. + +file_format: 1 diff --git a/grc/blocks/variable.xml b/grc/blocks/variable.xml deleted file mode 100644 index afee0f5d4a..0000000000 --- a/grc/blocks/variable.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Variable block: a grc variable with key, value -################################################### - --> -<block> - <name>Variable</name> - <key>variable</key> - <var_make>self.$(id) = $(id) = $value</var_make> - <make></make> - <callback>self.set_$(id)($value)</callback> - <param> - <name>Value</name> - <key>value</key> - <value>0</value> - <type>raw</type> - </param> - <doc> -This block maps a value to a unique variable. \ -This variable block has no graphical representation. - </doc> -</block> diff --git a/grc/blocks/variable_config.block.yml b/grc/blocks/variable_config.block.yml new file mode 100644 index 0000000000..bb64ea2a8f --- /dev/null +++ b/grc/blocks/variable_config.block.yml @@ -0,0 +1,58 @@ +id: variable_config +label: Variable Config + +parameters: +- id: value + label: Default Value + dtype: ${ type } + default: '0' +- id: type + label: Type + dtype: enum + default: real + options: [real, int, bool, string] + option_labels: [Float, Int, Bool, String] + option_attributes: + get: [getfloat, getint, getboolean, get] +- id: config_file + label: Config File + dtype: file_open + default: default +- id: section + label: Section + dtype: string + default: main +- id: option + label: Option + dtype: string + default: key +- id: writeback + label: WriteBack + dtype: raw + default: None +value: ${ value } + +templates: + imports: import ConfigParser + var_make: 'self._${id}_config = ConfigParser.ConfigParser() + + self._${id}_config.read(${config_file}) + + try: ${id} = self._${id}_config.${type.get}(${section}, ${option}) + + except: ${id} = ${value} + + self.${id} = ${id}' + callbacks: + - self.set_${id}(${value}) + - "self._${id}_config = ConfigParser.ConfigParser()\nself._${id}_config.read(${config_file})\n\ + if not self._${id}_config.has_section(${section}):\n\tself._${id}_config.add_section(${section})\n\ + self._${id}_config.set(${section}, ${option}, str(${writeback}))\nself._${id}_config.write(open(${config_file},\ + \ 'w'))" + +documentation: |- + This block represents a variable that can be read from a config file. + + To save the value back into the config file: enter the name of another variable into the writeback param. When the other variable is changed at runtime, the config file will be re-written. + +file_format: 1 diff --git a/grc/blocks/variable_config.xml b/grc/blocks/variable_config.xml deleted file mode 100644 index 11bff9edc2..0000000000 --- a/grc/blocks/variable_config.xml +++ /dev/null @@ -1,88 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Variable Config block: -## a variable that reads and writes to a config file -################################################### - --> -<block> - <name>Variable Config</name> - <key>variable_config</key> - <import>import ConfigParser</import> - <var_make>self._$(id)_config = ConfigParser.ConfigParser() -self._$(id)_config.read($config_file) -try: $(id) = self._$(id)_config.$(type.get)($section, $option) -except: $(id) = $value -self.$(id) = $(id)</var_make> - <make></make> - <callback>self.set_$(id)($value)</callback> - <callback>self._$(id)_config = ConfigParser.ConfigParser() -self._$(id)_config.read($config_file) -if not self._$(id)_config.has_section($section): - self._$(id)_config.add_section($section) -self._$(id)_config.set($section, $option, str($writeback)) -self._$(id)_config.write(open($config_file, 'w'))</callback> - <param> - <name>Default Value</name> - <key>value</key> - <value>0</value> - <type>$type</type> - </param> - <param> - <name>Type</name> - <key>type</key> - <value>real</value> - <type>enum</type> - <option> - <name>Float</name> - <key>real</key> - <opt>get:getfloat</opt> - </option> - <option> - <name>Int</name> - <key>int</key> - <opt>get:getint</opt> - </option> - <option> - <name>Bool</name> - <key>bool</key> - <opt>get:getboolean</opt> - </option> - <option> - <name>String</name> - <key>string</key> - <opt>get:get</opt> - </option> - </param> - <param> - <name>Config File</name> - <key>config_file</key> - <value>default</value> - <type>file_open</type> - </param> - <param> - <name>Section</name> - <key>section</key> - <value>main</value> - <type>string</type> - </param> - <param> - <name>Option</name> - <key>option</key> - <value>key</value> - <type>string</type> - </param> - <param> - <name>WriteBack</name> - <key>writeback</key> - <value>None</value> - <type>raw</type> - </param> - <doc> -This block represents a variable that can be read from a config file. - -To save the value back into the config file: \ -enter the name of another variable into the writeback param. \ -When the other variable is changed at runtime, the config file will be re-written. - </doc> -</block> diff --git a/grc/blocks/variable_function_probe.block.yml b/grc/blocks/variable_function_probe.block.yml new file mode 100644 index 0000000000..702ab5d60e --- /dev/null +++ b/grc/blocks/variable_function_probe.block.yml @@ -0,0 +1,54 @@ +id: variable_function_probe +label: Function Probe + +parameters: +- id: block_id + label: Block ID + dtype: string + default: my_block_0 +- id: function_name + label: Function Name + dtype: string + default: get_number +- id: function_args + label: Function Args + dtype: string + hide: ${ ('none' if function_args else 'part') } +- id: poll_rate + label: Poll Rate (Hz) + dtype: real + default: '10' +- id: value + label: Initial Value + dtype: raw + default: '0' + hide: part +value: ${ value } + +templates: + imports: |- + import time + import threading + var_make: self.${id} = ${id} = ${value} + make: "\ndef _${id}_probe():\n while True:\n <% obj = 'self' + ('.'\ + \ + block_id if block_id else '') %>\n val = ${obj}.${function_name}(${function_args})\n\ + \ try:\n self.set_${id}(val)\n except AttributeError:\n\ + \ pass\n time.sleep(1.0 / (${poll_rate}))\n_${id}_thread\ + \ = threading.Thread(target=_${id}_probe)\n_${id}_thread.daemon = True\n_${id}_thread.start()\n\ + \ " + callbacks: + - self.set_${id}(${value}) + +documentation: |- + Periodically probe a function and set its value to this variable. + + Set the values for block ID, function name, and function args appropriately: Block ID should be the ID of another block in this flow graph. An empty Block ID references the flow graph itself. Function name should be the name of a class method on that block. Function args are the parameters passed into that function. For a function with no arguments, leave function args blank. When passing a string for the function arguments, quote the string literal: '"arg"'. + + The values will used literally, and generated into the following form: + self.block_id.function_name(function_args) + or, if the Block ID is empty, + self.function_name(function_args) + + To poll a stream for a level, use this with the probe signal block. + +file_format: 1 diff --git a/grc/blocks/variable_function_probe.xml b/grc/blocks/variable_function_probe.xml deleted file mode 100644 index 47c11b29fe..0000000000 --- a/grc/blocks/variable_function_probe.xml +++ /dev/null @@ -1,78 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Variable function probe -################################################### - --> -<block> - <name>Function Probe</name> - <key>variable_function_probe</key> - <import>import time</import> - <import>import threading</import> - <var_make>self.$(id) = $(id) = $value</var_make> - <make> -def _$(id)_probe(): - while True: - #set $obj = 'self' + ('.' + $block_id() if $block_id() else '') - val = $(obj).$(function_name())($(function_args())) - try: - self.set_$(id)(val) - except AttributeError: - pass - time.sleep(1.0 / ($poll_rate)) -_$(id)_thread = threading.Thread(target=_$(id)_probe) -_$(id)_thread.daemon = True -_$(id)_thread.start() - </make> - <callback>self.set_$(id)($value)</callback> - <param> - <name>Block ID</name> - <key>block_id</key> - <value>my_block_0</value> - <type>string</type> - </param> - <param> - <name>Function Name</name> - <key>function_name</key> - <value>get_number</value> - <type>string</type> - </param> - <param> - <name>Function Args</name> - <key>function_args</key> - <value></value> - <type>string</type> - <hide>#if $function_args() then 'none' else 'part'#</hide> - </param> - <param> - <name>Poll Rate (Hz)</name> - <key>poll_rate</key> - <value>10</value> - <type>real</type> - </param> - <param> - <name>Initial Value</name> - <key>value</key> - <value>0</value> - <type>raw</type> - <hide>part</hide> - </param> - <doc> -Periodically probe a function and set its value to this variable. - -Set the values for block ID, function name, and function args appropriately: \ -Block ID should be the ID of another block in this flow graph. \ -An empty Block ID references the flow graph itself. \ -Function name should be the name of a class method on that block. \ -Function args are the parameters passed into that function. \ -For a function with no arguments, leave function args blank. \ -When passing a string for the function arguments, quote the string literal: '"arg"'. - -The values will used literally, and generated into the following form: - self.block_id.function_name(function_args) -or, if the Block ID is empty, - self.function_name(function_args) - -To poll a stream for a level, use this with the probe signal block. - </doc> -</block> diff --git a/grc/blocks/variable_struct.block.yml.py b/grc/blocks/variable_struct.block.yml.py new file mode 100644 index 0000000000..19b29982e7 --- /dev/null +++ b/grc/blocks/variable_struct.block.yml.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python + +MAX_NUM_FIELDS = 20 + +HEADER = """\ +id: variable_struct +label: Struct Variable + +parameters: +""" + +TEMPLATES = """\ + +templates: + imports: "def struct(data): return type('Struct', (object,), data)()" + var_make: |- + self.${{id}} = ${{id}} = struct({{ + % for i in range({0}): + <% + field = getVar('field' + str(i)) + %> + % if len(str(field)) > 2: + ${{field}}: getVar('value' + str(i)), + % endif + % endfor + }}) + var_value: |- + struct({{ + % for i in range({0}): + <% + field = getVar('field' + str(i)) + %> + % if len(str(field)) > 2: + ${{field}}: getVar('field' + str(i)), + % endif + % endfor + }}) +""" + +FIELD0 = """\ +- id: field0 + label: Field 0 + category: Fields + dtype: string + default: ${field0} + hide: part +""" + +FIELDS = """\ +- id: field{0} + label: Field {0} + category: Fields + dtype: string + hide: part +""" + +VALUES = """\ +- id: value{0} + label: ${{field{0}}} + dtype: raw + default: '0' + hide: ${{ 'none' if field{0} else 'all' }} +""" + +ASSERTS = """\ +- ${{ (str(field{0}) or "a")[0].isalpha() }} +- ${{ (str(field{0}) or "a").isalnum() }} +""" + +FOOTER = """\ + +documentation: |- + This is a simple struct/record like variable. + + Attribute/field names can be specified in the tab 'Fields'. + For each non-empty field a parameter with type raw is shown. + Value access via the dot operator, e.g. "variable_struct_0.field0" + +file_format: 1 +""" + + +def make_yml(num_fields): + return ''.join(( + HEADER.format(num_fields), + FIELD0, ''.join(FIELDS.format(i) for i in range(1, num_fields)), + ''.join(VALUES.format(i) for i in range(num_fields)), + 'value: ${value}\n\nasserts:\n', + ''.join(ASSERTS.format(i) for i in range(num_fields)), + ''.join(TEMPLATES.format(num_fields)), + FOOTER + )) + + +if __name__ == '__main__': + import sys + try: + filename = sys.argv[1] + except IndexError: + filename = __file__[:-3] + + data = make_yml(MAX_NUM_FIELDS) + + with open(filename, 'wb') as fp: + fp.write(data.encode()) diff --git a/grc/blocks/variable_struct.xml.py b/grc/blocks/variable_struct.xml.py deleted file mode 100644 index c0d3dac355..0000000000 --- a/grc/blocks/variable_struct.xml.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env python - -MAX_NUM_FIELDS = 20 - -HEADER = """\ -<block> - <name>Struct Variable</name> - <key>variable_struct</key> - <import>def struct(data): return type('Struct', (object,), data)()</import> - <var_make>self.$id = $id = struct({{#slurp -#for $i in range({0}): -#set $field = $getVar('field' + str(i)) -#if len(str($field)) > 2 -$field: $getVar('value' + str(i)), #slurp -#end if -#end for -}})</var_make> - <var_value>struct({{#slurp -#for $i in range({0}): -#set $field = $getVar('field' + str(i)) -#if len(str($field)) > 2 -$field: $getVar('value' + str(i)), #slurp -#end if -#end for -}})</var_value> - <make></make> -""" - -FIELD0 = """\ - <param> - <name>Field 0</name> - <key>field0</key> - <value>field0</value> - <type>string</type> - <hide>part</hide> - <tab>Fields</tab> - </param> -""" - -FIELDS = """\ - <param> - <name>Field {0}</name> - <key>field{0}</key> - <value></value> - <type>string</type> - <hide>part</hide> - <tab>Fields</tab> - </param> -""" - -VALUES = """\ - <param> - <name>$field{0}()</name> - <key>value{0}</key> - <value>0</value> - <type>raw</type> - <hide>#if $field{0}() then 'none' else 'all'#</hide> - </param> -""" - -CHECKS = """\ - <check>($str($field{0}) or "a")[0].isalpha()</check> - <check>($str($field{0}) or "a").isalnum()</check> -""" - -FOOTER = """\ - <doc>This is a simple struct/record like variable. - -Attribute/field names can be specified in the tab 'Fields'. -For each non-empty field a parameter with type raw is shown. -Value access via the dot operator, e.g. "variable_struct_0.field0" - </doc> -</block> -""" - - -def make_xml(num_fields): - return ''.join(( - HEADER.format(num_fields), - FIELD0, ''.join(FIELDS.format(i) for i in range(1, num_fields)), - ''.join(VALUES.format(i) for i in range(num_fields)), - ''.join(CHECKS.format(i) for i in range(num_fields)), - FOOTER - )) - - -if __name__ == '__main__': - import sys - try: - filename = sys.argv[1] - except IndexError: - filename = __file__[:-3] - - data = make_xml(MAX_NUM_FIELDS) - - with open(filename, 'wb') as fp: - fp.write(data.encode()) diff --git a/grc/blocks/virtual_sink.xml b/grc/blocks/virtual_sink.xml deleted file mode 100644 index 35fb27e67c..0000000000 --- a/grc/blocks/virtual_sink.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Virtual Sink -################################################### - --> -<block> - <name>Virtual Sink</name> - <key>virtual_sink</key> - <make></make> - <param> - <name>Stream ID</name> - <key>stream_id</key> - <value></value> - <type>stream_id</type> - </param> - <sink> - <name>in</name> - <type></type> - </sink> -</block> diff --git a/grc/blocks/virtual_source.xml b/grc/blocks/virtual_source.xml deleted file mode 100644 index e0c7754492..0000000000 --- a/grc/blocks/virtual_source.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Virtual Source -################################################### - --> -<block> - <name>Virtual Source</name> - <key>virtual_source</key> - <make></make> - <param> - <name>Stream ID</name> - <key>stream_id</key> - <value></value> - <type>stream_id</type> - </param> - <source> - <name>out</name> - <type></type> - </source> -</block> diff --git a/grc/core/Config.py b/grc/core/Config.py index eb53e1751d..4accb74c63 100644 --- a/grc/core/Config.py +++ b/grc/core/Config.py @@ -31,8 +31,6 @@ class Config(object): hier_block_lib_dir = os.environ.get('GRC_HIER_PATH', Constants.DEFAULT_HIER_BLOCK_LIB_DIR) - yml_block_cache = os.path.expanduser('~/.cache/grc_gnuradio') # FIXME: remove this as soon as converter is stable - def __init__(self, version, version_parts=None, name=None, prefs=None): self._gr_prefs = prefs if prefs else DummyPrefs() self.version = version @@ -40,9 +38,6 @@ class Config(object): if name: self.name = name - if not os.path.exists(self.yml_block_cache): - os.mkdir(self.yml_block_cache) - @property def block_paths(self): path_list_sep = {'/': ':', '\\': ';'}[os.path.sep] @@ -50,7 +45,6 @@ class Config(object): paths_sources = ( self.hier_block_lib_dir, os.environ.get('GRC_BLOCKS_PATH', ''), - self.yml_block_cache, self._gr_prefs.get_string('grc', 'local_blocks_path', ''), self._gr_prefs.get_string('grc', 'global_blocks_path', ''), ) diff --git a/grc/core/Constants.py b/grc/core/Constants.py index fc5383378c..8ed8899c70 100644 --- a/grc/core/Constants.py +++ b/grc/core/Constants.py @@ -20,6 +20,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from __future__ import absolute_import import os +import numbers import stat import numpy @@ -31,6 +32,8 @@ BLOCK_DTD = os.path.join(DATA_DIR, 'block.dtd') DEFAULT_FLOW_GRAPH = os.path.join(DATA_DIR, 'default_flow_graph.grc') DEFAULT_HIER_BLOCK_LIB_DIR = os.path.expanduser('~/.grc_gnuradio') +CACHE_FILE = os.path.expanduser('~/.cache/grc_gnuradio/cache.json') + BLOCK_DESCRIPTION_FILE_FORMAT_VERSION = 1 # File format versions: # 0: undefined / legacy @@ -52,7 +55,7 @@ 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 = ( +PARAM_TYPE_NAMES = { 'raw', 'enum', 'complex', 'real', 'float', 'int', 'complex_vector', 'real_vector', 'float_vector', 'int_vector', @@ -61,18 +64,17 @@ PARAM_TYPE_NAMES = ( 'id', 'stream_id', 'gui_hint', 'import', -) +} + +PARAM_TYPE_MAP = { + 'complex': numbers.Complex, + 'float': numbers.Real, + 'real': numbers.Real, + 'int': numbers.Integral, +} # Define types, native python + numpy VECTOR_TYPES = (tuple, list, set, numpy.ndarray) -COMPLEX_TYPES = [complex, numpy.complex, numpy.complex64, numpy.complex128] -REAL_TYPES = [float, numpy.float, numpy.float32, numpy.float64] -INT_TYPES = [int, numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.uint64, - numpy.uint, numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64] -# Cast to tuple for isinstance, concat subtypes -COMPLEX_TYPES = tuple(COMPLEX_TYPES + REAL_TYPES + INT_TYPES) -REAL_TYPES = tuple(REAL_TYPES + INT_TYPES) -INT_TYPES = tuple(INT_TYPES) # Updating colors. Using the standard color palette from: # http://www.google.com/design/spec/style/color.html#color-color-palette diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py index 3f21ec6a9c..8c59ec0bea 100644 --- a/grc/core/FlowGraph.py +++ b/grc/core/FlowGraph.py @@ -172,7 +172,7 @@ class FlowGraph(Element): return elements def children(self): - return itertools.chain(self.blocks, self.connections) + return itertools.chain(self.iter_enabled_blocks(), self.connections) def rewrite(self): """ diff --git a/grc/core/Param.py b/grc/core/Param.py deleted file mode 100644 index 56855908ea..0000000000 --- a/grc/core/Param.py +++ /dev/null @@ -1,413 +0,0 @@ -""" -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 -""" - -from __future__ import absolute_import - -import ast -import numbers -import re -import collections - -import six -from six.moves import builtins, filter, map, range, zip - -from . import Constants, blocks -from .base import Element -from .utils.descriptors import Evaluated, EvaluatedEnum, setup_names - -# Blacklist certain ids, its not complete, but should help -ID_BLACKLIST = ['self', 'options', 'gr', 'math', 'firdes'] + dir(builtins) -try: - from gnuradio import gr - ID_BLACKLIST.extend(attr for attr in dir(gr.top_block()) if not attr.startswith('_')) -except (ImportError, AttributeError): - pass - - -class TemplateArg(str): - """ - A cheetah template argument created from a param. - The str of this class evaluates to the param's to code method. - The use of this class as a dictionary (enum only) will reveal the enum opts. - The __call__ or () method can return the param evaluated to a raw python data type. - """ - - def __new__(cls, param): - value = param.to_code() - instance = str.__new__(cls, value) - setattr(instance, '_param', param) - return instance - - def __getitem__(self, item): - return str(self._param.get_opt(item)) if self._param.is_enum() else NotImplemented - - def __getattr__(self, item): - if not self._param.is_enum(): - raise AttributeError() - try: - return str(self._param.get_opt(item)) - except KeyError: - raise AttributeError() - - def __str__(self): - return str(self._param.to_code()) - - def __call__(self): - return self._param.get_evaluated() - - -@setup_names -class Param(Element): - - is_param = True - - name = Evaluated(str, default='no name') - dtype = EvaluatedEnum(Constants.PARAM_TYPE_NAMES, default='raw') - hide = EvaluatedEnum('none all part') - - # region init - def __init__(self, parent, id, label='', dtype='raw', default='', - options=None, option_labels=None, option_attributes=None, - category='', hide='none', **_): - """Make a new param from nested data""" - super(Param, self).__init__(parent) - self.key = id - self.name = label.strip() or id.title() - self.category = category or Constants.DEFAULT_PARAM_TAB - - self.dtype = dtype - self.value = self.default = str(default) - - self.options = self._init_options(options or [], option_labels or [], - option_attributes or {}) - self.hide = hide or 'none' - # end of args ######################################################## - - self._evaluated = None - self._stringify_flag = False - self._lisitify_flag = False - self._init = False - - @property - def template_arg(self): - return TemplateArg(self) - - def _init_options(self, values, labels, attributes): - """parse option and option attributes""" - options = collections.OrderedDict() - options.attributes = collections.defaultdict(dict) - - padding = [''] * max(len(values), len(labels)) - attributes = {key: value + padding for key, value in six.iteritems(attributes)} - - for i, option in enumerate(values): - # Test against repeated keys - if option in options: - raise KeyError('Value "{}" already exists in options'.format(option)) - # get label - try: - label = str(labels[i]) - except IndexError: - label = str(option) - # Store the option - options[option] = label - options.attributes[option] = {attrib: values[i] for attrib, values in six.iteritems(attributes)} - - default = next(iter(options)) if options else '' - if not self.value: - self.value = self.default = default - - if self.is_enum() and self.value not in options: - self.value = self.default = default # TODO: warn - # raise ValueError('The value {!r} is not in the possible values of {}.' - # ''.format(self.get_value(), ', '.join(self.options))) - return options - # endregion - - def __str__(self): - return 'Param - {}({})'.format(self.name, self.key) - - def __repr__(self): - return '{!r}.param[{}]'.format(self.parent, self.key) - - def is_enum(self): - return self.get_raw('dtype') == 'enum' - - def get_value(self): - value = self.value - if self.is_enum() and value not in self.options: - value = self.default - self.set_value(value) - return value - - def set_value(self, value): - # Must be a string - self.value = str(value) - - def set_default(self, value): - if self.default == self.value: - self.set_value(value) - self.default = str(value) - - def rewrite(self): - Element.rewrite(self) - del self.name - del self.dtype - del self.hide - - self._evaluated = None - try: - self._evaluated = self.evaluate() - except Exception as e: - self.add_error_message(str(e)) - - def validate(self): - """ - Validate the param. - The value must be evaluated and type must a possible type. - """ - Element.validate(self) - if self.dtype not in Constants.PARAM_TYPE_NAMES: - self.add_error_message('Type "{}" is not a possible type.'.format(self.dtype)) - - 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 - dtype = self.dtype - expr = self.get_value() - - ######################### - # Enum Type - ######################### - if self.is_enum(): - return expr - - ######################### - # Numeric Types - ######################### - elif dtype in ('raw', 'complex', 'real', 'float', 'int', 'hex', 'bool'): - # Raise exception if python cannot evaluate this value - try: - value = self.parent_flowgraph.evaluate(expr) - except Exception as value: - raise Exception('Value "{}" cannot be evaluated:\n{}'.format(expr, value)) - # Raise an exception if the data is invalid - if dtype == 'raw': - return value - elif dtype == 'complex': - if not isinstance(value, Constants.COMPLEX_TYPES): - raise Exception('Expression "{}" is invalid for type complex.'.format(str(value))) - return value - elif dtype in ('real', 'float'): - if not isinstance(value, Constants.REAL_TYPES): - raise Exception('Expression "{}" is invalid for type float.'.format(str(value))) - return value - elif dtype == 'int': - if not isinstance(value, Constants.INT_TYPES): - raise Exception('Expression "{}" is invalid for type integer.'.format(str(value))) - return value - elif dtype == 'hex': - return hex(value) - elif dtype == 'bool': - if not isinstance(value, bool): - raise Exception('Expression "{}" is invalid for type bool.'.format(str(value))) - return value - else: - raise TypeError('Type "{}" not handled'.format(dtype)) - ######################### - # Numeric Vector Types - ######################### - elif dtype in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'): - default = [] - - if not expr: - return default # Turn a blank string into an empty list, so it will eval - - try: - value = self.parent.parent.evaluate(expr) - except Exception as value: - raise Exception('Value "{}" cannot be evaluated:\n{}'.format(expr, value)) - - if not isinstance(value, Constants.VECTOR_TYPES): - self._lisitify_flag = True - value = [value] - - # Raise an exception if the data is invalid - if dtype == 'complex_vector' and not all(isinstance(item, numbers.Complex) for item in value): - raise Exception('Expression "{}" is invalid for type complex vector.'.format(value)) - elif dtype in ('real_vector', 'float_vector') and not all(isinstance(item, numbers.Real) for item in value): - raise Exception('Expression "{}" is invalid for type float vector.'.format(value)) - elif dtype == 'int_vector' and not all(isinstance(item, Constants.INT_TYPES) for item in value): - raise Exception('Expression "{}" is invalid for type integer vector.'.format(str(value))) - return value - ######################### - # String Types - ######################### - elif dtype in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'): - # Do not check if file/directory exists, that is a runtime issue - try: - value = self.parent.parent.evaluate(expr) - if not isinstance(value, str): - raise Exception() - except: - self._stringify_flag = True - value = str(expr) - if dtype == '_multiline_python_external': - ast.parse(value) # Raises SyntaxError - return value - ######################### - # Unique ID Type - ######################### - elif dtype == 'id': - self.validate_block_id() - return expr - - ######################### - # Stream ID Type - ######################### - elif dtype == 'stream_id': - self.validate_stream_id() - return expr - - ######################### - # GUI Position/Hint - ######################### - elif dtype == 'gui_hint': - 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 = '?' - - # 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 - ######################### - elif dtype == 'import': - # New namespace - n = dict() - try: - exec(expr, n) - except ImportError: - raise Exception('Import "{}" failed.'.format(expr)) - except Exception: - raise Exception('Bad import syntax: "{}".'.format(expr)) - return [k for k in list(n.keys()) if str(k) != '__builtins__'] - - ######################### - else: - raise TypeError('Type "{}" not handled'.format(dtype)) - - def validate_block_id(self): - value = self.value - # Can python use this as a variable? - if not re.match(r'^[a-z|A-Z]\w*$', value): - raise Exception('ID "{}" must begin with a letter and may contain letters, numbers, ' - 'and underscores.'.format(value)) - if value in ID_BLACKLIST: - raise Exception('ID "{}" is blacklisted.'.format(value)) - block_names = [block.name for block in self.parent_flowgraph.iter_enabled_blocks()] - # Id should only appear once, or zero times if block is disabled - if self.key == 'id' and block_names.count(value) > 1: - raise Exception('ID "{}" is not unique.'.format(value)) - elif value not in block_names: - raise Exception('ID "{}" does not exist.'.format(value)) - return value - - def validate_stream_id(self): - value = self.value - stream_ids = [ - block.params['stream_id'].value - for block in self.parent_flowgraph.iter_enabled_blocks() - if isinstance(block, blocks.VirtualSink) - ] - # Check that the virtual sink's stream id is unique - if isinstance(self.parent_block, blocks.VirtualSink) and stream_ids.count(value) >= 2: - # Id should only appear once, or zero times if block is disabled - raise Exception('Stream ID "{}" is not unique.'.format(value)) - # Check that the virtual source's steam id is found - elif isinstance(self.parent_block, blocks.VirtualSource) and value not in stream_ids: - raise Exception('Stream ID "{}" is not found.'.format(value)) - - 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 - """ - self._init = True - v = self.get_value() - t = self.dtype - # String types - if t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'): - if not self._init: - self.evaluate() - return repr(v) if self._stringify_flag else 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_opt(self, item): - return self.options.attributes[self.get_value()][item] diff --git a/grc/core/blocks/__init__.py b/grc/core/blocks/__init__.py index e4a085d477..4ca0d5d2bc 100644 --- a/grc/core/blocks/__init__.py +++ b/grc/core/blocks/__init__.py @@ -29,6 +29,7 @@ build_ins = {} def register_build_in(cls): + cls.loaded_from = '(build-in)' build_ins[cls.key] = cls return cls diff --git a/grc/core/blocks/_build.py b/grc/core/blocks/_build.py index 9221433387..6db06040cf 100644 --- a/grc/core/blocks/_build.py +++ b/grc/core/blocks/_build.py @@ -17,8 +17,13 @@ from __future__ import absolute_import +import collections +import itertools import re +from ..Constants import ADVANCED_PARAM_TAB +from ..utils import to_list + from .block import Block from ._flags import Flags from ._templates import MakoTemplates @@ -29,23 +34,24 @@ def build(id, label='', category='', flags='', documentation='', parameters=None, inputs=None, outputs=None, templates=None, **kwargs): block_id = id - cls = type(block_id, (Block,), {}) + cls = type(str(block_id), (Block,), {}) cls.key = block_id cls.label = label or block_id.title() cls.category = [cat.strip() for cat in category.split('/') if cat.strip()] - cls.flags = Flags(flags) + cls.flags = Flags(to_list(flags)) if re.match(r'options$|variable|virtual', block_id): - cls.flags += Flags.NOT_DSP + Flags.DISABLE_BYPASS + cls.flags.set(Flags.NOT_DSP, Flags.DISABLE_BYPASS) cls.documentation = {'': documentation.strip('\n\t ').replace('\\\n', '')} - cls.asserts = [_single_mako_expr(a, block_id) for a in (asserts or [])] + cls.asserts = [_single_mako_expr(a, block_id) for a in to_list(asserts)] - cls.parameters_data = parameters or [] - cls.inputs_data = inputs or [] - cls.outputs_data = outputs or [] + cls.inputs_data = _build_ports(inputs, 'sink') if inputs else [] + cls.outputs_data = _build_ports(outputs, 'source') if outputs else [] + cls.parameters_data = _build_params(parameters or [], + bool(cls.inputs_data), bool(cls.outputs_data), cls.flags) cls.extra_data = kwargs templates = templates or {} @@ -62,8 +68,68 @@ def build(id, label='', category='', flags='', documentation='', return cls +def _build_ports(ports_raw, direction): + ports = [] + port_ids = set() + stream_port_ids = itertools.count() + + for i, port_params in enumerate(ports_raw): + port = port_params.copy() + port['direction'] = direction + + port_id = port.setdefault('id', str(next(stream_port_ids))) + if port_id in port_ids: + raise Exception('Port id "{}" already exists in {}s'.format(port_id, direction)) + port_ids.add(port_id) + + ports.append(port) + return ports + + +def _build_params(params_raw, have_inputs, have_outputs, flags): + params = [] + + def add_param(**data): + params.append(data) + + add_param(id='id', name='ID', dtype='id', hide='part') + + if not flags.not_dsp: + add_param(id='alias', name='Block Alias', dtype='string', + hide='part', category=ADVANCED_PARAM_TAB) + + if have_outputs or have_inputs: + add_param(id='affinity', name='Core Affinity', dtype='int_vector', + hide='part', category=ADVANCED_PARAM_TAB) + + if have_outputs: + add_param(id='minoutbuf', name='Min Output Buffer', dtype='int', + hide='part', value='0', category=ADVANCED_PARAM_TAB) + add_param(id='maxoutbuf', name='Max Output Buffer', dtype='int', + hide='part', value='0', category=ADVANCED_PARAM_TAB) + + base_params_n = {} + for param_data in params_raw: + param_id = param_data['id'] + if param_id in params: + raise Exception('Param id "{}" is not unique'.format(param_id)) + + base_key = param_data.get('base_key', None) + param_data_ext = base_params_n.get(base_key, {}).copy() + param_data_ext.update(param_data) + + add_param(**param_data_ext) + base_params_n[param_id] = param_data_ext + + add_param(id='comment', name='Comment', dtype='_multiline', hide='part', + value='', category=ADVANCED_PARAM_TAB) + return params + + def _single_mako_expr(value, block_id): - match = re.match(r'\s*\$\{\s*(.*?)\s*\}\s*', str(value)) - if value and not match: + if not value: + return None + value = value.strip() + if not (value.startswith('${') and value.endswith('}')): raise ValueError('{} is not a mako substitution in {}'.format(value, block_id)) - return match.group(1) if match else None + return value[2:-1].strip() diff --git a/grc/core/blocks/_flags.py b/grc/core/blocks/_flags.py index ffea2ad569..bbedd6a2d7 100644 --- a/grc/core/blocks/_flags.py +++ b/grc/core/blocks/_flags.py @@ -17,10 +17,8 @@ from __future__ import absolute_import -import six - -class Flags(six.text_type): +class Flags(object): THROTTLE = 'throttle' DISABLE_BYPASS = 'disable_bypass' @@ -28,12 +26,14 @@ class Flags(six.text_type): DEPRECATED = 'deprecated' NOT_DSP = 'not_dsp' + def __init__(self, flags): + self.data = set(flags) + def __getattr__(self, item): return item in self - def __add__(self, other): - if not isinstance(other, six.string_types): - return NotImplemented - return self.__class__(str(self) + other) + def __contains__(self, item): + return item in self.data - __iadd__ = __add__ + def set(self, *flags): + self.data.update(flags) diff --git a/grc/core/blocks/block.py b/grc/core/blocks/block.py index adc046936d..0cb3f61237 100644 --- a/grc/core/blocks/block.py +++ b/grc/core/blocks/block.py @@ -19,7 +19,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from __future__ import absolute_import -import ast import collections import itertools @@ -29,7 +28,6 @@ from six.moves import range from ._templates import MakoTemplates from ._flags import Flags -from ..Constants import ADVANCED_PARAM_TAB from ..base import Element from ..utils.descriptors import lazy_property @@ -63,82 +61,28 @@ class Block(Element): outputs_data = [] extra_data = {} + loaded_from = '(unknown)' - # region Init def __init__(self, parent): """Make a new block from nested data.""" super(Block, self).__init__(parent) - self.params = self._init_params() - self.sinks = self._init_ports(self.inputs_data, direction='sink') - self.sources = self._init_ports(self.outputs_data, direction='source') - - self.active_sources = [] # on rewrite - self.active_sinks = [] # on rewrite - - self.states = {'state': True} - - def _init_params(self): - is_dsp_block = not self.flags.not_dsp - has_inputs = bool(self.inputs_data) - has_outputs = bool(self.outputs_data) - - params = collections.OrderedDict() param_factory = self.parent_platform.make_param - - def add_param(id, **kwargs): - params[id] = param_factory(self, id=id, **kwargs) - - add_param(id='id', name='ID', dtype='id', - hide='none' if (self.key == 'options' or self.is_variable) else 'part') - - if is_dsp_block: - add_param(id='alias', name='Block Alias', dtype='string', - hide='part', category=ADVANCED_PARAM_TAB) - - if has_outputs or has_inputs: - add_param(id='affinity', name='Core Affinity', dtype='int_vector', - hide='part', category=ADVANCED_PARAM_TAB) - - if has_outputs: - add_param(id='minoutbuf', name='Min Output Buffer', dtype='int', - hide='part', value='0', category=ADVANCED_PARAM_TAB) - add_param(id='maxoutbuf', name='Max Output Buffer', dtype='int', - hide='part', value='0', category=ADVANCED_PARAM_TAB) - - base_params_n = {} - for param_data in self.parameters_data: - param_id = param_data['id'] - if param_id in params: - raise Exception('Param id "{}" is not unique'.format(param_id)) - - base_key = param_data.get('base_key', None) - param_data_ext = base_params_n.get(base_key, {}).copy() - param_data_ext.update(param_data) - - add_param(**param_data_ext) - base_params_n[param_id] = param_data_ext - - add_param(id='comment', name='Comment', dtype='_multiline', hide='part', - value='', category=ADVANCED_PARAM_TAB) - return params - - def _init_ports(self, ports_n, direction): - ports = [] port_factory = self.parent_platform.make_port - port_ids = set() - stream_port_ids = itertools.count() + self.params = collections.OrderedDict( + (data['id'], param_factory(parent=self, **data)) + for data in self.parameters_data + ) + if self.key == 'options' or self.is_variable: + self.params['id'].hide = 'part' - for i, port_data in enumerate(ports_n): - 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) + self.sinks = [port_factory(parent=self, **params) for params in self.inputs_data] + self.sources = [port_factory(parent=self, **params) for params in self.outputs_data] - port = port_factory(parent=self, direction=direction, **port_data) - ports.append(port) - return ports - # endregion + self.active_sources = [] # on rewrite + self.active_sinks = [] # on rewrite + + self.states = {'state': True} # region Rewrite_and_Validation def rewrite(self): diff --git a/grc/core/cache.py b/grc/core/cache.py new file mode 100644 index 0000000000..f438d58bd9 --- /dev/null +++ b/grc/core/cache.py @@ -0,0 +1,99 @@ +# Copyright 2017 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 + +from __future__ import absolute_import, print_function + +from io import open +import json +import logging +import os + +import six + +from .io import yaml + +logger = logging.getLogger(__name__) + + +class Cache(object): + + def __init__(self, filename): + self.cache_file = filename + self.cache = {} + self.need_cache_write = True + self._accessed_items = set() + try: + os.makedirs(os.path.dirname(filename)) + except OSError: + pass + try: + self._converter_mtime = os.path.getmtime(filename) + except OSError: + self._converter_mtime = -1 + + def load(self): + try: + logger.debug("Loading block cache from: {}".format(self.cache_file)) + with open(self.cache_file, encoding='utf-8') as cache_file: + self.cache = json.load(cache_file) + self.need_cache_write = False + except (IOError, ValueError): + self.need_cache_write = True + + def get_or_load(self, filename): + self._accessed_items.add(filename) + if os.path.getmtime(filename) <= self._converter_mtime: + try: + return self.cache[filename] + except KeyError: + pass + + with open(filename, encoding='utf-8') as fp: + data = yaml.safe_load(fp) + self.cache[filename] = data + self.need_cache_write = True + return data + + def save(self): + if not self.need_cache_write: + return + + logger.info('Saving %d entries to json cache', len(self.cache)) + with open(self.cache_file, 'w', encoding='utf8') as cache_file: + json.dump(self.cache, cache_file) + + def prune(self): + for filename in (set(self.cache) - self._accessed_items): + del self.cache[filename] + + def __enter__(self): + self.load() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.save() + + +def byteify(data): + if isinstance(data, dict): + return {byteify(key): byteify(value) for key, value in six.iteritems(data)} + elif isinstance(data, list): + return [byteify(element) for element in data] + elif isinstance(data, six.text_type) and six.PY2: + return data.encode('utf-8') + else: + return data diff --git a/grc/core/default_flow_graph.grc b/grc/core/default_flow_graph.grc index 9df289f327..d57ec75aea 100644 --- a/grc/core/default_flow_graph.grc +++ b/grc/core/default_flow_graph.grc @@ -5,6 +5,7 @@ options: parameters: + id: 'top_block' title: 'top_block' states: coordinate: 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/params/__init__.py b/grc/core/params/__init__.py new file mode 100644 index 0000000000..93663bdada --- /dev/null +++ b/grc/core/params/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2017 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 + +from .param import Param diff --git a/grc/core/params/dtypes.py b/grc/core/params/dtypes.py new file mode 100644 index 0000000000..f52868c080 --- /dev/null +++ b/grc/core/params/dtypes.py @@ -0,0 +1,103 @@ +# Copyright 2008-2017 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 + +from __future__ import absolute_import + +import re + +from six.moves import builtins + +from .. import blocks +from .. import Constants + + +# Blacklist certain ids, its not complete, but should help +ID_BLACKLIST = ['self', 'options', 'gr', 'math', 'firdes'] + dir(builtins) +try: + from gnuradio import gr + ID_BLACKLIST.extend(attr for attr in dir(gr.top_block()) if not attr.startswith('_')) +except (ImportError, AttributeError): + pass + + +validators = {} + + +def validates(*dtypes): + def decorator(func): + for dtype in dtypes: + assert dtype in Constants.PARAM_TYPE_NAMES + validators[dtype] = func + return func + return decorator + + +class ValidateError(Exception): + """Raised by validate functions""" + + +@validates('id') +def validate_block_id(param): + value = param.value + # Can python use this as a variable? + if not re.match(r'^[a-z|A-Z]\w*$', value): + raise ValidateError('ID "{}" must begin with a letter and may contain letters, numbers, ' + 'and underscores.'.format(value)) + if value in ID_BLACKLIST: + raise ValidateError('ID "{}" is blacklisted.'.format(value)) + block_names = [block.name for block in param.parent_flowgraph.iter_enabled_blocks()] + # Id should only appear once, or zero times if block is disabled + if param.key == 'id' and block_names.count(value) > 1: + raise ValidateError('ID "{}" is not unique.'.format(value)) + elif value not in block_names: + raise ValidateError('ID "{}" does not exist.'.format(value)) + return value + + +@validates('stream_id') +def validate_stream_id(param): + value = param.value + stream_ids = [ + block.params['stream_id'].value + for block in param.parent_flowgraph.iter_enabled_blocks() + if isinstance(block, blocks.VirtualSink) + ] + # Check that the virtual sink's stream id is unique + if isinstance(param.parent_block, blocks.VirtualSink) and stream_ids.count(value) >= 2: + # Id should only appear once, or zero times if block is disabled + raise ValidateError('Stream ID "{}" is not unique.'.format(value)) + # Check that the virtual source's steam id is found + elif isinstance(param.parent_block, blocks.VirtualSource) and value not in stream_ids: + raise ValidateError('Stream ID "{}" is not found.'.format(value)) + + +@validates('complex', 'real', 'float', 'int') +def validate_scalar(param): + valid_types = Constants.PARAM_TYPE_MAP[param.dtype] + if not isinstance(param.get_evaluated(), valid_types): + raise ValidateError('Expression {!r} is invalid for type {!r}.'.format( + param.get_evaluated(), param.dtype)) + + +@validates('complex_vector', 'real_vector', 'float_vector', 'int_vector') +def validate_vector(param): + # todo: check vector types + + valid_types = Constants.PARAM_TYPE_MAP[param.dtype.split('_', 1)[0]] + if not all(isinstance(item, valid_types) for item in param.get_evaluated()): + raise ValidateError('Expression {!r} is invalid for type {!r}.'.format( + param.get_evaluated(), param.dtype)) diff --git a/grc/core/params/param.py b/grc/core/params/param.py new file mode 100644 index 0000000000..30a48bb434 --- /dev/null +++ b/grc/core/params/param.py @@ -0,0 +1,407 @@ +# Copyright 2008-2017 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 + +from __future__ import absolute_import + +import ast +import collections +import textwrap + +import six +from six.moves import range + +from .. import Constants +from ..base import Element +from ..utils.descriptors import Evaluated, EvaluatedEnum, setup_names + +from . import dtypes +from .template_arg import TemplateArg + + +@setup_names +class Param(Element): + + is_param = True + + name = Evaluated(str, default='no name') + dtype = EvaluatedEnum(Constants.PARAM_TYPE_NAMES, default='raw') + hide = EvaluatedEnum('none all part') + + # region init + def __init__(self, parent, id, label='', dtype='raw', default='', + options=None, option_labels=None, option_attributes=None, + category='', hide='none', **_): + """Make a new param from nested data""" + super(Param, self).__init__(parent) + self.key = id + self.name = label.strip() or id.title() + self.category = category or Constants.DEFAULT_PARAM_TAB + + self.dtype = dtype + self.value = self.default = str(default) + + self.options = self._init_options(options or [], option_labels or [], + option_attributes or {}) + self.hide = hide or 'none' + # end of args ######################################################## + + self._evaluated = None + self._stringify_flag = False + self._lisitify_flag = False + self.hostage_cells = set() + self._init = False + + def _init_options(self, values, labels, attributes): + """parse option and option attributes""" + options = collections.OrderedDict() + options.attributes = collections.defaultdict(dict) + + padding = [''] * max(len(values), len(labels)) + attributes = {key: value + padding for key, value in six.iteritems(attributes)} + + for i, option in enumerate(values): + # Test against repeated keys + if option in options: + raise KeyError('Value "{}" already exists in options'.format(option)) + # get label + try: + label = str(labels[i]) + except IndexError: + label = str(option) + # Store the option + options[option] = label + options.attributes[option] = {attrib: values[i] for attrib, values in six.iteritems(attributes)} + + default = next(iter(options)) if options else '' + if not self.value: + self.value = self.default = default + + if self.is_enum() and self.value not in options: + self.value = self.default = default # TODO: warn + # raise ValueError('The value {!r} is not in the possible values of {}.' + # ''.format(self.get_value(), ', '.join(self.options))) + return options + # endregion + + @property + def template_arg(self): + return TemplateArg(self) + + def __str__(self): + return 'Param - {}({})'.format(self.name, self.key) + + def __repr__(self): + return '{!r}.param[{}]'.format(self.parent, self.key) + + def is_enum(self): + return self.get_raw('dtype') == 'enum' + + def get_value(self): + value = self.value + if self.is_enum() and value not in self.options: + value = self.default + self.set_value(value) + return value + + def set_value(self, value): + # Must be a string + self.value = str(value) + + def set_default(self, value): + if self.default == self.value: + self.set_value(value) + self.default = str(value) + + def rewrite(self): + Element.rewrite(self) + del self.name + del self.dtype + del self.hide + + self._evaluated = None + try: + self._evaluated = self.evaluate() + except Exception as e: + self.add_error_message(str(e)) + + rewriter = getattr(dtypes, 'rewrite_' + self.dtype, None) + if rewriter: + rewriter(self) + + def validate(self): + """ + Validate the param. + The value must be evaluated and type must a possible type. + """ + Element.validate(self) + if self.dtype not in Constants.PARAM_TYPE_NAMES: + self.add_error_message('Type "{}" is not a possible type.'.format(self.dtype)) + + validator = dtypes.validators.get(self.dtype, None) + if self._init and validator: + try: + validator(self) + except dtypes.ValidateError as e: + self.add_error_message(e.message) + + 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 + dtype = self.dtype + expr = self.get_value() + + ######################### + # ID and Enum types (not evaled) + ######################### + if dtype in ('id', 'stream_id') or self.is_enum(): + return expr + + ######################### + # Numeric Types + ######################### + elif dtype in ('raw', 'complex', 'real', 'float', 'int', 'hex', 'bool'): + if expr: + try: + value = self.parent_flowgraph.evaluate(expr) + except Exception as e: + raise Exception('Value "{}" cannot be evaluated:\n{}'.format(expr, e)) + else: + value = 0 + if dtype == 'hex': + value = hex(value) + elif dtype == 'bool': + value = bool(value) + return value + + ######################### + # Numeric Vector Types + ######################### + elif dtype in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'): + if not expr: + return [] # Turn a blank string into an empty list, so it will eval + try: + value = self.parent.parent.evaluate(expr) + except Exception as value: + raise Exception('Value "{}" cannot be evaluated:\n{}'.format(expr, value)) + if not isinstance(value, Constants.VECTOR_TYPES): + self._lisitify_flag = True + value = [value] + return value + ######################### + # String Types + ######################### + elif dtype in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'): + # Do not check if file/directory exists, that is a runtime issue + try: + value = self.parent_flowgraph.evaluate(expr) + if not isinstance(value, str): + raise Exception() + except: + self._stringify_flag = True + value = str(expr) + if dtype == '_multiline_python_external': + ast.parse(value) # Raises SyntaxError + return value + ######################### + # GUI Position/Hint + ######################### + elif dtype == 'gui_hint': + return self.parse_gui_hint(expr) if self.parent_block.state == 'enabled' else '' + ######################### + # Import Type + ######################### + elif dtype == 'import': + # New namespace + n = dict() + try: + exec(expr, n) + except ImportError: + raise Exception('Import "{}" failed.'.format(expr)) + except Exception: + raise Exception('Bad import syntax: "{}".'.format(expr)) + return [k for k in list(n.keys()) if str(k) != '__builtins__'] + + ######################### + else: + raise TypeError('Type "{}" not handled'.format(dtype)) + + 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 + """ + self._init = True + value = self.get_value() + # String types + if self.dtype in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'): + if not self._init: + self.evaluate() + return repr(value) if self._stringify_flag else value + + # Vector types + elif self.dtype in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'): + if not self._init: + self.evaluate() + return '[' + value + ']' if self._lisitify_flag else value + else: + return value + + 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: + dtype: 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/params/template_arg.py b/grc/core/params/template_arg.py new file mode 100644 index 0000000000..5c8c610b4f --- /dev/null +++ b/grc/core/params/template_arg.py @@ -0,0 +1,50 @@ +# Copyright 2008-2017 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 + +from __future__ import absolute_import + + +class TemplateArg(str): + """ + A cheetah template argument created from a param. + The str of this class evaluates to the param's to code method. + The use of this class as a dictionary (enum only) will reveal the enum opts. + The __call__ or () method can return the param evaluated to a raw python data type. + """ + + def __new__(cls, param): + value = param.to_code() + instance = str.__new__(cls, value) + setattr(instance, '_param', param) + return instance + + def __getitem__(self, item): + return str(self._param.get_opt(item)) if self._param.is_enum() else NotImplemented + + def __getattr__(self, item): + if not self._param.is_enum(): + raise AttributeError() + try: + return str(self._param.get_opt(item)) + except KeyError: + raise AttributeError() + + def __str__(self): + return str(self._param.to_code()) + + def __call__(self): + return self._param.get_evaluated() diff --git a/grc/core/platform.py b/grc/core/platform.py index 538bacade2..6d02cb6441 100644 --- a/grc/core/platform.py +++ b/grc/core/platform.py @@ -19,7 +19,6 @@ from __future__ import absolute_import, print_function from codecs import open from collections import namedtuple -import glob import os import logging from itertools import chain @@ -29,16 +28,16 @@ from six.moves import range from . import ( Messages, Constants, - blocks, ports, errors, utils, schema_checker + blocks, params, ports, errors, utils, schema_checker ) from .Config import Config +from .cache import Cache from .base import Element from .io import yaml from .generator import Generator from .FlowGraph import FlowGraph from .Connection import Connection -from .Param import Param logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) @@ -141,44 +140,41 @@ class Platform(Element): self.connection_templates.clear() self._block_categories.clear() - # FIXME: remove this as soon as converter is stable - from ..converter import Converter - converter = Converter(self.config.block_paths, self.config.yml_block_cache) - converter.run() - logging.info('XML converter done.') - - for file_path in self._iter_files_in_block_path(path): - try: - data = converter.cache[file_path] - except KeyError: - with open(file_path, encoding='utf-8') as fp: - data = yaml.safe_load(fp) - - if file_path.endswith('.block.yml'): - loader = self.load_block_description - scheme = schema_checker.BLOCK_SCHEME - elif file_path.endswith('.domain.yml'): - loader = self.load_domain_description - scheme = schema_checker.DOMAIN_SCHEME - elif file_path.endswith('.tree.yml'): - loader = self.load_category_tree_description - scheme = None - else: - continue - - try: - checker = schema_checker.Validator(scheme) - passed = checker.run(data) - for msg in checker.messages: - logger.warning('{:<40s} {}'.format(os.path.basename(file_path), msg)) - if not passed: - logger.info('YAML schema check failed for: ' + file_path) - - loader(data, file_path) - except Exception as error: - logger.exception('Error while loading %s', file_path) - logger.exception(error) - raise + # # FIXME: remove this as soon as converter is stable + # from ..converter import Converter + # converter = Converter(self.config.block_paths, self.config.yml_block_cache) + # converter.run() + # logging.info('XML converter done.') + + with Cache(Constants.CACHE_FILE) as cache: + for file_path in self._iter_files_in_block_path(path): + data = cache.get_or_load(file_path) + + if file_path.endswith('.block.yml'): + loader = self.load_block_description + scheme = schema_checker.BLOCK_SCHEME + elif file_path.endswith('.domain.yml'): + loader = self.load_domain_description + scheme = schema_checker.DOMAIN_SCHEME + elif file_path.endswith('.tree.yml'): + loader = self.load_category_tree_description + scheme = None + else: + continue + + try: + checker = schema_checker.Validator(scheme) + passed = checker.run(data) + for msg in checker.messages: + logger.warning('{:<40s} {}'.format(os.path.basename(file_path), msg)) + if not passed: + logger.info('YAML schema check failed for: ' + file_path) + + loader(data, file_path) + except Exception as error: + logger.exception('Error while loading %s', file_path) + logger.exception(error) + raise for key, block in six.iteritems(self.blocks): category = self._block_categories.get(key, block.category) @@ -201,10 +197,9 @@ class Platform(Element): if os.path.isfile(entry): yield entry elif os.path.isdir(entry): - pattern = os.path.join(entry, '**.' + ext) - yield_from = glob.iglob(pattern) - for file_path in yield_from: - yield file_path + for dirpath, dirnames, filenames in os.walk(entry): + for filename in sorted(filter(lambda f: f.endswith('.' + ext), filenames)): + yield os.path.join(dirpath, filename) else: logger.debug('Ignoring invalid path entry %r', entry) @@ -232,16 +227,18 @@ class Platform(Element): log.error('Unknown format version %d in %s', file_format, file_path) return - block_id = data.pop('id').rstrip('_') + block_id = data['id'] = data['id'].rstrip('_') if block_id in self.block_classes_build_in: log.warning('Not overwriting build-in block %s with %s', block_id, file_path) return if block_id in self.blocks: - log.warning('Block with id "%s" overwritten by %s', block_id, file_path) + log.warning('Block with id "%s" loaded from\n %s\noverwritten by\n %s', + block_id, self.blocks[block_id].loaded_from, file_path) try: - block_cls = self.blocks[block_id] = self.new_block_class(block_id, **data) + block_cls = self.blocks[block_id] = self.new_block_class(**data) + block_cls.loaded_from = file_path except errors.BlockLoadError as error: log.error('Unable to load block %s', block_id) log.exception(error) @@ -288,19 +285,12 @@ class Platform(Element): path = [] def load_category(name, elements): - if not isinstance(name, str): - log.debug('invalid name %r', name) - return - if isinstance(elements, list): - pass - elif isinstance(elements, str): - elements = [elements] - else: - log.debug('Ignoring elements of %s', name) + if not isinstance(name, six.string_types): + log.debug('Invalid name %r', name) return path.append(name) - for element in elements: - if isinstance(element, str): + for element in utils.to_list(elements): + if isinstance(element, six.string_types): block_id = element self._block_categories[block_id] = list(path) elif isinstance(element, dict): @@ -404,7 +394,7 @@ class Platform(Element): 'clone': ports.PortClone, # clone of ports with multiplicity > 1 } param_classes = { - None: Param, # default + None: params.Param, # default } def make_flow_graph(self, from_filename=None): @@ -415,8 +405,8 @@ class Platform(Element): fg.import_data(data) return fg - def new_block_class(self, block_id, **data): - return blocks.build(block_id, **data) + def new_block_class(self, **data): + return blocks.build(**data) def make_block(self, parent, block_id, **kwargs): cls = self.block_classes[block_id] diff --git a/grc/core/schema_checker/block.py b/grc/core/schema_checker/block.py index ea079b4276..d511e36887 100644 --- a/grc/core/schema_checker/block.py +++ b/grc/core/schema_checker/block.py @@ -37,7 +37,7 @@ TEMPLATES_SCHEME = expand( BLOCK_SCHEME = expand( id=Spec(types=str_, required=True, item_scheme=None), label=str_, - category=(list, str_), + category=str_, flags=(list, str_), parameters=Spec(types=list, required=False, item_scheme=PARAM_SCHEME), diff --git a/grc/core/utils/__init__.py b/grc/core/utils/__init__.py index 660eb594a5..f2ac986fb4 100644 --- a/grc/core/utils/__init__.py +++ b/grc/core/utils/__init__.py @@ -17,5 +17,17 @@ from __future__ import absolute_import +import six + from . import epy_block_io, expr_utils, extract_docs, flow_graph_complexity from .hide_bokeh_gui_options_if_not_installed import hide_bokeh_gui_options_if_not_installed + + +def to_list(value): + if not value: + return [] + elif isinstance(value, six.string_types): + return [value] + else: + return list(value) + diff --git a/grc/core/utils/descriptors/evaluated.py b/grc/core/utils/descriptors/evaluated.py index 313cee5b96..0e1b68761c 100644 --- a/grc/core/utils/descriptors/evaluated.py +++ b/grc/core/utils/descriptors/evaluated.py @@ -15,6 +15,10 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +from __future__ import absolute_import + +import six + class Evaluated(object): def __init__(self, expected_type, default, name=None): @@ -62,7 +66,7 @@ class Evaluated(object): def __set__(self, instance, value): attribs = instance.__dict__ value = value or self.default - if isinstance(value, str) and value.startswith('${') and value.endswith('}'): + if isinstance(value, six.text_type) and value.startswith('${') and value.endswith('}'): attribs[self.name_raw] = value[2:-1].strip() else: attribs[self.name] = type(self.default)(value) @@ -75,9 +79,10 @@ class Evaluated(object): class EvaluatedEnum(Evaluated): def __init__(self, allowed_values, default=None, name=None): - self.allowed_values = allowed_values if isinstance(allowed_values, (list, tuple)) else \ - allowed_values.split() - default = default if default is not None else self.allowed_values[0] + if isinstance(allowed_values, six.string_types): + allowed_values = set(allowed_values.split()) + self.allowed_values = allowed_values + default = default if default is not None else next(iter(self.allowed_values)) super(EvaluatedEnum, self).__init__(str, default, name) def default_eval_func(self, instance): diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index d214f28049..14b0422764 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -302,6 +302,7 @@ ELEMENT_DELETE = actions.register("win.delete", label='_Delete', tooltip='Delete the selected blocks', icon_name='edit-delete', + keypresses=["Delete"], ) BLOCK_MOVE = actions.register("win.block_move") BLOCK_ROTATE_CCW = actions.register("win.block_rotate_ccw", diff --git a/grc/gui/Application.py b/grc/gui/Application.py index ea7ad5cd80..70cf9b78b2 100644 --- a/grc/gui/Application.py +++ b/grc/gui/Application.py @@ -663,7 +663,7 @@ class Application(Gtk.Application): flow_graph_update(new_flow_graph) page.saved = False elif action == Actions.FLOW_GRAPH_SCREEN_CAPTURE: - file_path, background_transparent = FileDialogs.SaveScreenShot(main, page.file_path).run() + file_path, background_transparent = SaveScreenShotDialog(main, page.get_file_path()).run() if file_path is not None: try: Utils.make_screenshot(flow_graph, file_path, background_transparent) diff --git a/grc/gui/ParamWidgets.py b/grc/gui/ParamWidgets.py index 18d1da736b..747c3ffec5 100644 --- a/grc/gui/ParamWidgets.py +++ b/grc/gui/ParamWidgets.py @@ -57,12 +57,13 @@ class InputParam(Gtk.HBox): """The base class for an input parameter inside the input parameters dialog.""" expand = False - def __init__(self, param, changed_callback=None, editing_callback=None): + def __init__(self, param, changed_callback=None, editing_callback=None, transient_for=None): Gtk.HBox.__init__(self) self.param = param self._changed_callback = changed_callback self._editing_callback = editing_callback + self._transient_for = transient_for self.label = Gtk.Label() self.label.set_size_request(Utils.scale_scalar(150), -1) @@ -199,7 +200,7 @@ class PythonEditorParam(InputParam): self.pack_start(button, True, True, True) def open_editor(self, widget=None): - self.param.parent_flowgraph.install_external_editor(self.param) + self.param.parent_flowgraph.install_external_editor(self.param, parent=self._transient_for) def get_text(self): pass # we never update the value from here @@ -289,7 +290,7 @@ class FileParam(EntryParam): If the button was clicked, open a file dialog in open/save format. Replace the text in the entry with the new filename from the file dialog. """ - #get the paths + # get the paths file_path = self.param.is_valid() and self.param.get_evaluated() or '' (dirname, basename) = os.path.isfile(file_path) and os.path.split(file_path) or (file_path, '') # check for qss theme default directory @@ -301,22 +302,28 @@ class FileParam(EntryParam): if not os.path.exists(dirname): dirname = os.getcwd() # fix bad paths - #build the dialog + # build the dialog if self.param.dtype == 'file_open': - file_dialog = Gtk.FileChooserDialog('Open a Data File...', None, - Gtk.FileChooserAction.OPEN, ('gtk-cancel',Gtk.ResponseType.CANCEL,'gtk-open',Gtk.ResponseType.OK)) + file_dialog = Gtk.FileChooserDialog( + 'Open a Data File...', None, Gtk.FileChooserAction.OPEN, + ('gtk-cancel', Gtk.ResponseType.CANCEL, 'gtk-open', Gtk.ResponseType.OK), + transient_for=self._transient_for, + ) elif self.param.dtype == 'file_save': - file_dialog = Gtk.FileChooserDialog('Save a Data File...', None, - Gtk.FileChooserAction.SAVE, ('gtk-cancel',Gtk.ResponseType.CANCEL, 'gtk-save',Gtk.ResponseType.OK)) + file_dialog = Gtk.FileChooserDialog( + 'Save a Data File...', None, Gtk.FileChooserAction.SAVE, + ('gtk-cancel', Gtk.ResponseType.CANCEL, 'gtk-save', Gtk.ResponseType.OK), + transient_for=self._transient_for, + ) file_dialog.set_do_overwrite_confirmation(True) - file_dialog.set_current_name(basename) #show the current filename + file_dialog.set_current_name(basename) # show the current filename else: raise ValueError("Can't open file chooser dialog for type " + repr(self.param.dtype)) - file_dialog.set_current_folder(dirname) #current directory + file_dialog.set_current_folder(dirname) # current directory file_dialog.set_select_multiple(False) file_dialog.set_local_only(True) - if Gtk.ResponseType.OK == file_dialog.run(): #run the dialog - file_path = file_dialog.get_filename() #get the file path + if Gtk.ResponseType.OK == file_dialog.run(): # run the dialog + file_path = file_dialog.get_filename() # get the file path self._input.set_text(file_path) self._editing_callback() self._apply_change() diff --git a/grc/gui/Platform.py b/grc/gui/Platform.py index 2a38bc619e..8eb79f3459 100644 --- a/grc/gui/Platform.py +++ b/grc/gui/Platform.py @@ -63,8 +63,8 @@ class Platform(CorePlatform): FlowGraph = canvas.FlowGraph Connection = canvas.Connection - def new_block_class(self, block_id, **data): - cls = CorePlatform.new_block_class(self, block_id, **data) + def new_block_class(self, **data): + cls = CorePlatform.new_block_class(self, **data) return canvas.Block.make_cls_with_base(cls) block_classes_build_in = {key: canvas.Block.make_cls_with_base(cls) diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py index 9ce9bf2701..ac4506a3d8 100644 --- a/grc/gui/PropsDialog.py +++ b/grc/gui/PropsDialog.py @@ -185,12 +185,13 @@ class PropsDialog(Gtk.Dialog): continue box_all_valid = box_all_valid and param.is_valid() - input_widget = param.get_input(self._handle_changed, self._activate_apply) + input_widget = param.get_input(self._handle_changed, self._activate_apply, + transient_for=self.get_transient_for()) input_widget.show_all() vbox.pack_start(input_widget, input_widget.expand, True, 1) - label.set_markup('<span foreground="{color}">{name}</span>'.format( - color='black' if box_all_valid else 'red', name=Utils.encode(category) + label.set_markup('<span {color}>{name}</span>'.format( + color='foreground="red"' if not box_all_valid else '', name=Utils.encode(category) )) vbox.show() # show params box with new params diff --git a/grc/gui/Utils.py b/grc/gui/Utils.py index f47c2e6b97..1b32e91439 100644 --- a/grc/gui/Utils.py +++ b/grc/gui/Utils.py @@ -19,6 +19,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from __future__ import absolute_import +import numbers + from gi.repository import GLib import cairo import six @@ -91,7 +93,7 @@ def num_to_str(num): return template.format(value / factor, symbol.strip()) return template.format(value, '') - if isinstance(num, Constants.COMPLEX_TYPES): + if isinstance(num, numbers.Complex): num = complex(num) # Cast to python complex if num == 0: return '0' diff --git a/grc/gui/canvas/flowgraph.py b/grc/gui/canvas/flowgraph.py index 79af5d5931..3e0fd83dad 100644 --- a/grc/gui/canvas/flowgraph.py +++ b/grc/gui/canvas/flowgraph.py @@ -88,7 +88,7 @@ class FlowGraph(CoreFlowgraph, Drawable): break return block_id - def install_external_editor(self, param): + def install_external_editor(self, param, parent=None): target = (param.parent_block.name, param.key) if target in self._external_updaters: @@ -96,7 +96,7 @@ class FlowGraph(CoreFlowgraph, Drawable): else: config = self.parent_platform.config editor = (find_executable(config.editor) or - Dialogs.choose_editor(None, config)) # todo: pass in parent + Dialogs.choose_editor(parent, config)) # todo: pass in parent if not editor: return updater = functools.partial( diff --git a/grc/gui/canvas/param.py b/grc/gui/canvas/param.py index e2c335d9cf..5777423c68 100644 --- a/grc/gui/canvas/param.py +++ b/grc/gui/canvas/param.py @@ -17,11 +17,11 @@ from __future__ import absolute_import -from .drawable import Drawable +import numbers +from .drawable import Drawable from .. import ParamWidgets, Utils, Constants - -from ...core.Param import Param as CoreParam +from ...core.params import Param as CoreParam class Param(CoreParam): @@ -128,7 +128,7 @@ class Param(CoreParam): t = self.dtype if isinstance(e, bool): return str(e) - elif isinstance(e, Constants.COMPLEX_TYPES): + elif isinstance(e, numbers.Complex): dt_str = Utils.num_to_str(e) elif isinstance(e, Constants.VECTOR_TYPES): # Vector types diff --git a/grc/tests/resources/file1.block.yml b/grc/tests/resources/file1.block.yml new file mode 100644 index 0000000000..f486c89ea8 --- /dev/null +++ b/grc/tests/resources/file1.block.yml @@ -0,0 +1,38 @@ +id: block_key +label: testname + +parameters: +- id: vlen + label: Vec Length + category: test + dtype: int + default: '1' + hide: ${ 'part' if vlen == 1 else 'none' } +- id: out_type + label: Vec Length + dtype: string + default: complex + hide: part +- id: a + label: Alpha + dtype: ${ (out_type) } + default: '0' + +inputs: +- domain: stream + dtype: complex + vlen: ${ 2 * vlen } +- domain: message + id: in2 + +outputs: +- domain: stream + dtype: ${ out_type } + vlen: ${ vlen } +asserts: +- ${ vlen > 0 } + +templates: + make: blocks.complex_to_mag_squared(${vlen}) + +file_format: 1 diff --git a/grc/tests/resources/file1.xml b/grc/tests/resources/file1.xml deleted file mode 100644 index f03288b85d..0000000000 --- a/grc/tests/resources/file1.xml +++ /dev/null @@ -1,58 +0,0 @@ -<?xml version="1.0"?> -<!-- -Copyright 2014 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 ---> -<block> - <name>testname</name> - <key>block_key</key> - <make>blocks.complex_to_mag_squared($(vlen))</make> - <param> - <name>Vec Length</name> - <key>vlen</key> - <value>1</value> - <type>int</type> - <tab>test</tab> - </param> - <param> - <name>Vec Length</name> - <key>out_type</key> - <value>complex</value> - <type>string</type> - </param> - <param> - <name>Alpha</name> - <key>a</key> - <value>0</value> - <type>($out_type)</type> - </param> - <check>$vlen > 0</check> - <sink> - <name>in</name> - <type>complex</type> - <vlen>2 * $vlen</vlen> - </sink> - <sink> - <name>in2</name> - <type>message</type> - </sink> - <source> - <name>out</name> - <type>$out_type</type> - <vlen>$vlen</vlen> - </source> -</block> diff --git a/grc/tests/resources/file2.block.yml b/grc/tests/resources/file2.block.yml new file mode 100644 index 0000000000..459527260c --- /dev/null +++ b/grc/tests/resources/file2.block.yml @@ -0,0 +1,31 @@ +id: blocks_and_const_xx +label: And Const + +parameters: +- id: type + label: IO Type + dtype: enum + options: [int, short, byte] + option_attributes: + fcn: [ii, ss, bb] + hide: part +- id: const + label: Constant + dtype: int + default: '0' + +inputs: +- domain: stream + dtype: ${ type } + +outputs: +- domain: stream + dtype: ${ type } + +templates: + imports: from gnuradio import blocks + make: blocks.and_const_${type.fcn}(${const}) + callbacks: + - set_k(${const}) + +file_format: 1 diff --git a/grc/tests/resources/file2.xml b/grc/tests/resources/file2.xml deleted file mode 100644 index 1300c7f5a1..0000000000 --- a/grc/tests/resources/file2.xml +++ /dev/null @@ -1,80 +0,0 @@ -<?xml version="1.0"?> -<!-- -Copyright 2014 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 ---> -<!-- -################################################### -## And Const Block: -## all types, 1 output, 1 input & const -################################################### - --> -<block> - <name>And Const</name> - <key>blocks_and_const_xx</key> - <category>test</category> - <throttle>1</throttle> - <import>from gnuradio import blocks</import> - <make>blocks.and_const_$(type.fcn)($const)</make> - <callback>set_k($const)</callback> - <param> - <name>IO Type</name> - <key>type</key> - <type>enum</type> - <option> - <name>Int</name> - <key>int</key> - <opt>fcn:ii</opt> - </option> - <option> - <name>Short</name> - <key>short</key> - <opt>fcn:ss</opt> - </option> - <option> - <name>Byte</name> - <key>byte</key> - <opt>fcn:bb</opt> - </option> - </param> - <param> - <name>Constant</name> - <key>const</key> - <value>0</value> - <type>${type}</type> - <hide>#if $log then 'none' else 'part'#</hide> - </param> - <sink> - <name>in</name> - <type>$type</type> - </sink> - <source> - <name>out</name> - <type>$(type.fcn)</type> - </source> - <doc> -This block creates a variable check box. \ -Leave the label blank to use the variable id as the label. - -A check box selects between two values of similar type. \ -Te values do not necessarily need to be of boolean type. - -The GUI hint can be used to position the widget within the application. \ -The hint is of the form [tab_id@tab_index]: [row, col, row_span, col_span]. \ -Both the tab specification and the grid position are optional. - </doc> -</block> diff --git a/grc/tests/resources/file3.block.yml b/grc/tests/resources/file3.block.yml new file mode 100644 index 0000000000..e53515d134 --- /dev/null +++ b/grc/tests/resources/file3.block.yml @@ -0,0 +1,66 @@ +id: variable_qtgui_check_box +label: QT GUI Check Box + +parameters: +- id: label + label: Label + dtype: string + hide: ${ ('none' if label else 'part') } +- id: type + label: Type + dtype: enum + default: int + options: [real, int, string, bool, raw] + option_labels: [Float, Integer, String, Boolean, Any] + option_attributes: + conv: [float, int, str, bool, eval] + hide: part +- id: value + label: Default Value + dtype: ${ type } + default: 'True' +- id: 'true' + label: 'True' + dtype: ${ type } + default: 'True' +- id: 'false' + label: 'False' + dtype: ${ type } + default: 'False' +- id: gui_hint + label: GUI Hint + dtype: gui_hint + hide: part +value: ${ value } + +asserts: +- ${value in (true, false)} + +templates: + imports: from PyQt4 import Qt + var_make: self.${id} = ${id} = ${value} + callbacks: + - self.set_${id}(${value}) + - self._${id}_callback(${id}) + make: |- + <% + win = '_%s_check_box'%id + if not label: + label = id + %> + ${win} = Qt.QCheckBox(${label}) + self._${id}_choices = {True: ${true}, False: ${false}} + self._${id}_choices_inv = dict((v,k) for k,v in self._${id}_choices.iteritems()) + self._${id}_callback = lambda i: Qt.QMetaObject.invokeMethod(${win}, "setChecked", Qt.Q_ARG("bool", self._${id}_choices_inv[i])) + self._${id}_callback(self.${id}) + ${win}.stateChanged.connect(lambda i: self.set_${id}(self._${id}_choices[bool(i)])) + ${gui_hint()(win)} + +documentation: |- + This block creates a variable check box. Leave the label blank to use the variable id as the label. + + A check box selects between two values of similar type. Te values do not necessarily need to be of boolean type. + + The GUI hint can be used to position the widget within the application. The hint is of the form [tab_id@tab_index]: [row, col, row_span, col_span]. Both the tab specification and the grid position are optional. + +file_format: 1 diff --git a/grc/tests/resources/file3.xml b/grc/tests/resources/file3.xml deleted file mode 100644 index 71753badb1..0000000000 --- a/grc/tests/resources/file3.xml +++ /dev/null @@ -1,100 +0,0 @@ -<?xml version="1.0"?> -<!-- -Copyright 2014 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 ---> -<!-- -################################################### -##Variable Check Box: -## a gui check box form -################################################### - --> -<block> - <name>QT GUI Check Box</name> - <key>variable_qtgui_check_box</key> - <import>from PyQt4 import Qt</import> - <var_make>self.$(id) = $(id) = $value</var_make> - <make>#set $win = '_%s_check_box'%$id -#if not $label() - #set $label = '"%s"'%$id -#end if -$win = Qt.QCheckBox($label) -self._$(id)_choices = {True: $true, False: $false} -self._$(id)_choices_inv = dict((v,k) for k,v in self._$(id)_choices.iteritems()) -self._$(id)_callback = lambda i: Qt.QMetaObject.invokeMethod($(win), "setChecked", Qt.Q_ARG("bool", self._$(id)_choices_inv[i])) -self._$(id)_callback(self.$id) -$(win).stateChanged.connect(lambda i: self.set_$(id)(self._$(id)_choices[bool(i)])) -$(gui_hint()($win))</make> - <callback>self.set_$(id)($value)</callback> - <callback>self._$(id)_callback($id)</callback> - <param> - <name>Label</name> - <key>label</key> - <value></value> - <type>string</type> - <hide>#if $label() then 'none' else 'part'#</hide> - </param> - <param> - <name>Type</name> - <key>type</key> - <value>int</value> - <type>enum</type> - <hide>part</hide> - <option><name>Float</name><key>real</key><opt>conv:float</opt></option> - <option><name>Integer</name><key>int</key><opt>conv:int</opt></option> - <option><name>String</name><key>string</key><opt>conv:str</opt></option> - <option><name>Boolean</name><key>bool</key><opt>conv:bool</opt></option> - <option><name>Any</name><key>raw</key><opt>conv:eval</opt></option> - </param> - <param> - <name>Default Value</name> - <key>value</key> - <value>True</value> - <type>$type</type> - </param> - <param> - <name>True</name> - <key>true</key> - <value>True</value> - <type>$type</type> - </param> - <param> - <name>False</name> - <key>false</key> - <value>False</value> - <type>$type</type> - </param> - <param> - <name>GUI Hint</name> - <key>gui_hint</key> - <value></value> - <type>gui_hint</type> - <hide>part</hide> - </param> - <check>$value in ($true, $false)</check> - <doc> -This block creates a variable check box. \ -Leave the label blank to use the variable id as the label. - -A check box selects between two values of similar type. \ -Te values do not necessarily need to be of boolean type. - -The GUI hint can be used to position the widget within the application. \ -The hint is of the form [tab_id@tab_index]: [row, col, row_span, col_span]. \ -Both the tab specification and the grid position are optional. - </doc> -</block> |