diff options
author | Christophe Seguinot <christophe.seguinot@univ-lille.fr> | 2020-02-20 13:43:44 +0100 |
---|---|---|
committer | mormj <34754695+mormj@users.noreply.github.com> | 2020-05-19 15:41:14 -0400 |
commit | 09b5b0a6cb6b2cd619180a662027824fe0a827df (patch) | |
tree | 1cf34dadb7d536a4c63b5130f60a467811cdb7d7 /gr-qtgui | |
parent | bfe366bf354c87c6275d5dd921aac97d9591ba92 (diff) |
qtgui: Add eye sink widget
Diffstat (limited to 'gr-qtgui')
22 files changed, 6100 insertions, 6 deletions
diff --git a/gr-qtgui/docs/qtgui.dox b/gr-qtgui/docs/qtgui.dox index a430c5a60a..5790d88a97 100644 --- a/gr-qtgui/docs/qtgui.dox +++ b/gr-qtgui/docs/qtgui.dox @@ -29,6 +29,8 @@ purposes. These include: \li Time Domain (gr::qtgui::time_sink_c and gr::qtgui::time_sink_f): x-axis is time, y-axis is amplitude. +\li Eye pattern (gr::qtgui::eye_sink_c and gr::qtgui::eye_sink_f): +x-axis is time, y-axis is amplitude. \li Frequency Domain or PSD (gr::qtgui::freq_sink_c and gr::qtgui::freq_sink_f): x-axis is frequency, y-axis is magnitude in dB. @@ -44,7 +46,7 @@ the data stream. \li Combined Sink (gr::qtgui::sink_c and gr::qtgui::sink_f): combines time, frequency, waterfall, and constellation plots into one widget. -The time domain, frequency domain, and waterfall have both a complex +The time domain, eye pattern, frequency domain, and waterfall have both a complex and a floating point block. The constellation plot only makes sense with complex inputs. The time raster plots accept bits and floats. @@ -88,11 +90,11 @@ FFT averaging. \subsection qtgui_menu_trigger Triggering Menu for Time Plots -The time plots have triggering capabilities. Triggering can happen -when the signal of a specific channel crosses (positive or negative -slope) a certain level threshold. Or triggering can be done off a -specific stream tag such that whenever a tag of a given key is found, -the scope will trigger. +The time plots and eye pattern plots have triggering capabilities. +Triggering can happen when the signal of a specific channel crosses +(positive or negative slope) a certain level threshold. Or triggering +can be done off a specific stream tag such that whenever a tag of a +given key is found, the scope will trigger. In the signal level mode, the trigger can be either 'auto' or 'normal' where the latter will only trigger when the event is seen. The 'auto' diff --git a/gr-qtgui/examples/qtgui_eye_sink_example.grc b/gr-qtgui/examples/qtgui_eye_sink_example.grc new file mode 100644 index 0000000000..8e201ee41e --- /dev/null +++ b/gr-qtgui/examples/qtgui_eye_sink_example.grc @@ -0,0 +1,719 @@ +options: + parameters: + author: '' + catch_exceptions: 'True' + category: Custom + cmake_opt: '' + comment: '' + copyright: '' + description: '' + gen_cmake: 'On' + gen_linking: dynamic + generate_options: qt_gui + hier_block_src_path: '.:' + id: pyqt_eye_sink + max_nouts: '0' + output_language: python + placement: (0,0) + qt_qss_theme: '' + realtime_scheduling: '' + run: 'True' + run_command: '{python} -u {filename}' + run_options: prompt + sizing_mode: fixed + thread_safe_setters: '' + title: Eye Sink Example + window_size: 2000,2000 + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 11] + rotation: 0 + state: enabled + +blocks: +- name: alpha + id: variable_qtgui_range + parameters: + comment: Roll off factor (RRC filter) + gui_hint: 0,1,1,1 + label: Roll off + min_len: '200' + orient: Qt.Horizontal + rangeType: float + start: '0.0001' + step: '0.01' + stop: '1' + value: '0.35' + widget: counter_slider + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [280, 324.0] + rotation: 0 + state: enabled +- name: bpsk + id: variable_constellation_rect + parameters: + comment: '' + const_points: '[1, -1]' + imag_sect: '0' + precision: '8' + real_sect: '2' + rot_sym: '4' + soft_dec_lut: None + sym_map: '[0, 1]' + w_imag_sect: '1' + w_real_sect: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [16, 476.0] + rotation: 0 + state: enabled +- name: nfilts + id: variable + parameters: + comment: '' + value: '32' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [512, 12.0] + rotation: 0 + state: enabled +- name: noise_volt + id: variable_qtgui_range + parameters: + comment: '' + gui_hint: 0,0,1,1 + label: Noise Voltage + min_len: '200' + orient: Qt.Vertical + rangeType: float + start: '0' + step: '0.01' + stop: '1' + value: '0.0001' + widget: counter_slider + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [16, 324.0] + rotation: 0 + state: enabled +- name: qam_16 + id: variable_constellation_rect + parameters: + comment: '' + const_points: digital.qam_16()[0] + imag_sect: '2' + precision: '8' + real_sect: '2' + rot_sym: '4' + soft_dec_lut: None + sym_map: digital.qam_16()[1] + w_imag_sect: '1' + w_real_sect: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [384, 476.0] + rotation: 0 + state: enabled +- name: qam_64 + id: variable_constellation_rect + parameters: + comment: '' + const_points: '[(-7+7j),(-5+7j),(-3+7j),(-1+7j),(+1+7j),(+3+7j),(+5+7j),(+7+7j),(-7+5j),(-5+5j),(-3+5j),(-1+5j),(+1+5j),(+3+5j),(+5+5j),(+7+5j),(-7+3j),(-5+3j),(-3+3j),(-1+3j),(+1+3j),(+3+3j),(+5+3j),(+7+3j),(-7+1j),(-5+1j),(-3+1j),(-1+1j),(+1+1j),(+3+1j),(+5+1j),(+7+1j),(-7-1j),(-5-1j),(-3-1j),(-1-1j),(+1-1j),(+3-1j),(+5-1j),(+7-1j),(-7-3j),(-5-3j),(-3-3j),(-1-3j),(+1-3j),(+3-3j),(+5-3j),(+7-3j),(-7-5j),(-5-5j),(-3-5j),(-1-5j),(+1-5j),(+3-5j),(+5-5j),(+7-5j),(-7-7j),(-5-7j),(-3-7j),(-1-7j),(+1-7j),(+3-7j),(+5-7j),(+7-7j)] + + ' + imag_sect: '2' + precision: '8' + real_sect: '2' + rot_sym: '4' + soft_dec_lut: None + sym_map: '[26, 27,25, 24, 8, 9, 11, 10,30,31,29,28,12, 13,15,14,22,23,21,20,4,5,7,6,18,19,17,16,0,1,3,2,50,51,49,48,32,33,35,34,54,55,53,52,36,37,39,38,62,63,61,60,44,45,47,46,58,59,57,56,40,41,43,42]' + w_imag_sect: '1' + w_real_sect: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [560, 476.0] + rotation: 0 + state: enabled +- name: qpsk + id: variable_constellation_rect + parameters: + comment: '' + const_points: '[0.707+0.707j, -0.707+0.707j, -0.707-0.707j, 0.707-0.707j]' + imag_sect: '2' + precision: '8' + real_sect: '2' + rot_sym: '4' + soft_dec_lut: None + sym_map: '[0, 1, 2, 3]' + w_imag_sect: '1' + w_real_sect: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [192, 476.0] + rotation: 0 + state: enabled +- name: rrc_taps + id: variable + parameters: + comment: '' + value: firdes.root_raised_cosine(nfilts, nfilts, 1.0/float(sps), alpha, 45*nfilts) + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [584, 12.0] + rotation: 0 + state: enabled +- name: samp_rate + id: variable + parameters: + comment: '' + value: symbol_rate*sps + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [280, 12.0] + rotation: 0 + state: enabled +- name: sps + id: variable + parameters: + comment: '' + value: '8' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [368, 12.0] + rotation: 0 + state: enabled +- name: symbol_rate + id: variable + parameters: + comment: '' + value: '100000' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [176, 12.0] + rotation: 0 + state: enabled +- name: taps + id: variable + parameters: + comment: '' + value: '[1.0, 0.25-0.25j, 0.50 + 0.10j, -0.3 + 0.2j]' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [576, 9] + rotation: 0 + state: disabled +- name: taps + id: variable + parameters: + comment: '' + value: '[1.0 + 0.0j, ]' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [440, 12.0] + rotation: 0 + state: enabled +- name: time_offset + id: variable_qtgui_range + parameters: + comment: '' + gui_hint: 0,2,1,1 + label: Timing Offset + min_len: '200' + orient: Qt.Horizontal + rangeType: float + start: '0.999' + step: '0.0001' + stop: '1.001' + value: '1.00' + widget: counter_slider + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [144, 324.0] + rotation: 0 + state: enabled +- name: analog_random_source_x_0 + id: analog_random_source_x + parameters: + affinity: '' + alias: '' + comment: '' + max: '256' + maxoutbuf: '0' + min: '0' + minoutbuf: '0' + num_samps: '10000' + repeat: 'True' + type: byte + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [16, 116.0] + rotation: 0 + state: enabled +- name: blocks_throttle_0 + id: blocks_throttle + parameters: + affinity: '' + alias: '' + comment: '' + ignoretag: 'True' + maxoutbuf: '0' + minoutbuf: '0' + samples_per_second: samp_rate + type: complex + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [400, 140.0] + rotation: 0 + state: enabled +- name: channels_channel_model_0 + id: channels_channel_model + parameters: + affinity: '' + alias: '' + block_tags: 'False' + comment: 'At channel output, digital signal is + + Root Raised Cosine (RRC) filtered. + + (It exhibits inter symbol interference (ISI))' + epsilon: time_offset + freq_offset: '0' + maxoutbuf: '0' + minoutbuf: '0' + noise_voltage: noise_volt + seed: '0' + taps: taps + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [520, 212.0] + rotation: 0 + state: enabled +- name: digital_constellation_modulator_0 + id: digital_constellation_modulator + parameters: + affinity: '' + alias: '' + comment: "Constellation can be set to : \n\"bpsk\" \"qpsk\" \"qam_16\" or \"qam_64\"" + constellation: qpsk + differential: 'False' + excess_bw: alpha + log: 'False' + maxoutbuf: '0' + minoutbuf: '0' + samples_per_symbol: sps + verbose: 'False' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [168, 116.0] + rotation: 0 + state: enabled +- name: digital_pfb_clock_sync_xxx_0 + id: digital_pfb_clock_sync_xxx + parameters: + affinity: '' + alias: '' + comment: 'Timing synchronization also + + perform RRC filtering' + filter_size: nfilts + init_phase: nfilts/2 + loop_bw: 6.28/100.0 + max_dev: '1.5' + maxoutbuf: '0' + minoutbuf: '0' + osps: sps + sps: sps*1.001 + taps: rrc_taps + type: ccf + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [768, 52.0] + rotation: 0 + state: enabled +- name: digital_pfb_clock_sync_xxx_0_0 + id: digital_pfb_clock_sync_xxx + parameters: + affinity: '' + alias: '' + comment: '' + filter_size: nfilts + init_phase: nfilts/2 + loop_bw: 6.28/100.0 + max_dev: '1.5' + maxoutbuf: '0' + minoutbuf: '0' + osps: '1' + sps: sps*1.001 + taps: rrc_taps + type: ccf + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [768, 284.0] + rotation: 0 + state: enabled +- name: qtgui_const_sink_x_0 + id: qtgui_const_sink_x + parameters: + affinity: '' + alias: '' + alpha1: '0.5' + alpha10: '1.0' + alpha2: '1.0' + alpha3: '1.0' + alpha4: '1.0' + alpha5: '1.0' + alpha6: '1.0' + alpha7: '1.0' + alpha8: '1.0' + alpha9: '1.0' + autoscale: 'False' + axislabels: 'True' + color1: '"blue"' + color10: '"red"' + color2: '"red"' + color3: '"green"' + color4: '"black"' + color5: '"cyan"' + color6: '"magenta"' + color7: '"yellow"' + color8: '"dark red"' + color9: '"dark green"' + comment: '' + grid: 'False' + gui_hint: 1,2,1,1 + label1: '' + label10: '' + label2: '' + label3: '' + label4: '' + label5: '' + label6: '' + label7: '' + label8: '' + label9: '' + legend: 'True' + marker1: '0' + marker10: '0' + marker2: '0' + marker3: '0' + marker4: '0' + marker5: '0' + marker6: '0' + marker7: '0' + marker8: '0' + marker9: '0' + name: RRC @ receiver input + nconnections: '1' + size: '2048' + style1: '0' + style10: '0' + style2: '0' + style3: '0' + style4: '0' + style5: '0' + style6: '0' + style7: '0' + style8: '0' + style9: '0' + tr_chan: '0' + tr_level: '0.0' + tr_mode: qtgui.TRIG_MODE_FREE + tr_slope: qtgui.TRIG_SLOPE_POS + tr_tag: '""' + type: complex + update_time: '0.10' + width1: '1' + width10: '1' + width2: '1' + width3: '1' + width4: '1' + width5: '1' + width6: '1' + width7: '1' + width8: '1' + width9: '1' + xmax: '2' + xmin: '-2' + ymax: '2' + ymin: '-2' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [768, 428.0] + rotation: 0 + state: enabled +- name: qtgui_const_sink_x_0_0 + id: qtgui_const_sink_x + parameters: + affinity: '' + alias: '' + alpha1: '0.5' + alpha10: '1.0' + alpha2: '1.0' + alpha3: '1.0' + alpha4: '1.0' + alpha5: '1.0' + alpha6: '1.0' + alpha7: '1.0' + alpha8: '1.0' + alpha9: '1.0' + autoscale: 'False' + axislabels: 'True' + color1: '"blue"' + color10: '"red"' + color2: '"red"' + color3: '"green"' + color4: '"black"' + color5: '"cyan"' + color6: '"magenta"' + color7: '"yellow"' + color8: '"dark red"' + color9: '"dark green"' + comment: '' + grid: 'False' + gui_hint: 2,2,1,1 + label1: '' + label10: '' + label2: '' + label3: '' + label4: '' + label5: '' + label6: '' + label7: '' + label8: '' + label9: '' + legend: 'True' + marker1: '0' + marker10: '0' + marker2: '0' + marker3: '0' + marker4: '0' + marker5: '0' + marker6: '0' + marker7: '0' + marker8: '0' + marker9: '0' + name: RC, synced + nconnections: '1' + size: '2048' + style1: '0' + style10: '0' + style2: '0' + style3: '0' + style4: '0' + style5: '0' + style6: '0' + style7: '0' + style8: '0' + style9: '0' + tr_chan: '0' + tr_level: '0.0' + tr_mode: qtgui.TRIG_MODE_FREE + tr_slope: qtgui.TRIG_SLOPE_POS + tr_tag: '""' + type: complex + update_time: '0.10' + width1: '1' + width10: '1' + width2: '1' + width3: '1' + width4: '1' + width5: '1' + width6: '1' + width7: '1' + width8: '1' + width9: '1' + xmax: '2' + xmin: '-2' + ymax: '2' + ymin: '-2' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1008, 268.0] + rotation: 0 + state: enabled +- name: qtgui_eye_sink_x_0 + id: qtgui_eye_sink_x + parameters: + affinity: '' + alias: '' + alpha1: '1.0' + alpha10: '1.0' + alpha2: '1.0' + alpha3: '1.0' + alpha4: '1.0' + alpha5: '1.0' + alpha6: '1.0' + alpha7: '1.0' + alpha8: '1.0' + alpha9: '1.0' + autoscale: 'False' + axislabels: 'True' + color1: blue + color10: dark blue + color2: blue + color3: blue + color4: blue + color5: cyan + color6: magenta + color7: yellow + color8: dark red + color9: dark green + comment: '' + ctrlpanel: 'False' + entags: 'True' + grid: 'False' + gui_hint: 1,0,2,2 + label1: Re{receiver input} + label10: Signal 10 + label2: Im{receiver input} + label3: Re{after sync.} + label4: Im{after sync.} + label5: Signal 5 + label6: Signal 6 + label7: Signal 7 + label8: Signal 8 + label9: Signal 9 + legend: 'True' + marker1: '-1' + marker10: '-1' + marker2: '-1' + marker3: '-1' + marker4: '-1' + marker5: '-1' + marker6: '-1' + marker7: '-1' + marker8: '-1' + marker9: '-1' + name: '""' + nconnections: '2' + samp_per_symbol: sps + size: '2048' + srate: samp_rate + style1: '1' + style10: '1' + style2: '0' + style3: '1' + style4: '1' + style5: '1' + style6: '1' + style7: '1' + style8: '1' + style9: '1' + tr_chan: '0' + tr_delay: '0' + tr_level: '0.0' + tr_mode: qtgui.TRIG_MODE_FREE + tr_slope: qtgui.TRIG_SLOPE_POS + tr_tag: '""' + type: complex + update_time: '0.10' + width1: '1' + width10: '1' + width2: '1' + width3: '1' + width4: '1' + width5: '1' + width6: '1' + width7: '1' + width8: '1' + width9: '1' + ylabel: Amplitude + ymax: '1.5' + ymin: '-1.5' + yunit: '""' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1008, 172.0] + rotation: 0 + state: true +- name: virtual_sink_0 + id: virtual_sink + parameters: + alias: channel_input + comment: '' + stream_id: channel_input + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [560, 140.0] + rotation: 0 + state: true +- name: virtual_source_0 + id: virtual_source + parameters: + alias: '' + comment: '' + stream_id: channel_input + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [328, 252.0] + rotation: 0 + state: true + +connections: +- [analog_random_source_x_0, '0', digital_constellation_modulator_0, '0'] +- [blocks_throttle_0, '0', virtual_sink_0, '0'] +- [channels_channel_model_0, '0', digital_pfb_clock_sync_xxx_0, '0'] +- [channels_channel_model_0, '0', digital_pfb_clock_sync_xxx_0_0, '0'] +- [channels_channel_model_0, '0', qtgui_const_sink_x_0, '0'] +- [channels_channel_model_0, '0', qtgui_eye_sink_x_0, '0'] +- [digital_constellation_modulator_0, '0', blocks_throttle_0, '0'] +- [digital_pfb_clock_sync_xxx_0, '0', qtgui_eye_sink_x_0, '1'] +- [digital_pfb_clock_sync_xxx_0_0, '0', qtgui_const_sink_x_0_0, '0'] +- [virtual_source_0, '0', channels_channel_model_0, '0'] + +metadata: + file_format: 1 diff --git a/gr-qtgui/grc/qtgui.tree.yml b/gr-qtgui/grc/qtgui.tree.yml index 51bbc807dc..d860963737 100644 --- a/gr-qtgui/grc/qtgui.tree.yml +++ b/gr-qtgui/grc/qtgui.tree.yml @@ -1,6 +1,7 @@ '[Core]': - Instrumentation: - QT: + - qtgui_eye_sink_x - qtgui_freq_sink_x - qtgui_time_sink_x - qtgui_const_sink_x diff --git a/gr-qtgui/grc/qtgui_eye_sink_x.block.yml b/gr-qtgui/grc/qtgui_eye_sink_x.block.yml new file mode 100644 index 0000000000..58a5e076b2 --- /dev/null +++ b/gr-qtgui/grc/qtgui_eye_sink_x.block.yml @@ -0,0 +1,1022 @@ +id: qtgui_eye_sink_x +label: QT GUI Eye Sink + +parameters: +- id: type + label: Type + dtype: enum + default: complex + options: [complex, float, msg_complex, msg_float] + option_labels: [Complex, Float, Complex Message, Float Message] + option_attributes: + fcn: [eye_sink_c, eye_sink_f, eye_sink_c, eye_sink_f] + t: [complex, float, message, message] + hide: part +- id: name + label: Name + dtype: string + default: '""' + hide: ${ ('none' if len(name) > 0 else 'part') } +- id: ylabel + label: Y Axis Label + dtype: string + default: Amplitude + hide: part +- id: yunit + label: Y Axis Unit + dtype: string + default: '""' + hide: part +- id: size + label: Number of Points + dtype: int + default: '1024' + hide: ${ ('all' if type.startswith('msg') else 'none') } +- id: samp_per_symbol + label: Samples per Symbol + dtype: int + default: 'sps' +- id: srate + label: Sample Rate + dtype: float + default: samp_rate +- id: grid + label: Grid + dtype: enum + default: 'False' + options: ['True', 'False'] + option_labels: ['Yes', 'No'] + hide: part +- id: autoscale + label: Autoscale + dtype: enum + default: 'False' + options: ['True', 'False'] + option_labels: ['Yes', 'No'] +- id: ymin + label: Y min + dtype: float + default: '-1' + hide: part +- id: ymax + label: Y max + dtype: float + default: '1' + hide: part +- id: nconnections + label: Number of Inputs + dtype: int + default: '1' + hide: ${ ('all' if type.startswith('msg') else 'part') } +- id: update_time + label: Update Period + dtype: float + default: '0.10' + hide: part +- id: entags + label: Disp. Tags + dtype: enum + default: 'True' + options: ['True', 'False'] + option_labels: ['Yes', 'No'] + hide: ${ ('all' if type.startswith('msg') else 'part') } +- id: gui_hint + label: GUI Hint + dtype: gui_hint + hide: part +- id: tr_mode + label: Trigger Mode + category: Trigger + dtype: enum + default: qtgui.TRIG_MODE_FREE + options: [qtgui.TRIG_MODE_FREE, qtgui.TRIG_MODE_AUTO, qtgui.TRIG_MODE_NORM, qtgui.TRIG_MODE_TAG] + option_labels: [Free, Auto, Normal, Tag] + hide: part +- id: tr_slope + label: Trigger Slope + category: Trigger + dtype: enum + default: qtgui.TRIG_MODE_POS + options: [qtgui.TRIG_SLOPE_POS, qtgui.TRIG_SLOPE_NEG] + option_labels: [Positive, Negative] + hide: part +- id: tr_level + label: Trigger Level + category: Trigger + dtype: float + default: '0.0' + hide: part +- id: tr_delay + label: Trigger Delay + category: Trigger + dtype: float + default: '0' + hide: part +- id: tr_chan + label: Trigger Channel + category: Trigger + dtype: int + default: '0' + hide: part +- id: tr_tag + label: Trigger Tag Key + category: Trigger + dtype: string + default: '""' + hide: part +- id: ctrlpanel + label: Control Panel + category: Config + dtype: enum + default: 'False' + options: ['True', 'False'] + option_labels: ['Yes', 'No'] + hide: part +- id: legend + label: Legend + category: Config + dtype: enum + default: 'True' + options: ['True', 'False'] + option_labels: ['Yes', 'No'] + hide: part +- id: axislabels + label: Axis Labels + category: Config + dtype: enum + default: 'True' + options: ['True', 'False'] + option_labels: ['Yes', 'No'] + hide: part +- id: label1 + label: Line 1 Label + dtype: string + default: 'Signal 1' + hide: ${ ('part' if ( + int(nconnections) >= 1 + or (type == "complex" and int(nconnections) >= 1) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: width1 + label: Line 1 Width + default: 1 + hide: ${ ('part' if ( + int(nconnections) >= 1 + or (type == "complex" and int(nconnections) >= 1) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: color1 + label: Line 1 Color + dtype: enum + options: ['blue', 'red', 'green', 'black', 'cyan', 'magenta', 'yellow', 'dark red', 'dark green', 'dark blue'] + option_labels: ['Blue', 'Red', 'Green', 'Black', 'Cyan', 'Magenta', 'Yellow', 'Dark Red', 'Dark Green', 'Dark Blue'] + default: 'blue' + hide: ${ ('part' if ( + int(nconnections) >= 1 + or (type == "complex" and int(nconnections) >= 1) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: style1 + label: Line 1 Style + dtype: enum + options: ['1','2','3','4','5','0'] + option_labels: ['Solid','Dash','Dots','Dash-Dot','Dash-Dot-Dot'] + default: 1 + hide: ${ ('part' if ( + int(nconnections) >= 1 + or (type == "complex" and int(nconnections) >= 1) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: marker1 + label: Line 1 Marker + dtype: enum + options: ['-1','0','1','2','3','4','5','6','7','8','9'] + option_labels: ['None','Circle','Rectangle','Diamond','Triangle','Down Triangle','Left Triangle','Right Triangle','Cross','X-Cross'] + default: -1 + hide: ${ ('part' if ( + int(nconnections) >= 1 + or (type == "complex" and int(nconnections) >= 1) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: alpha1 + label: Line 1 Alpha + dtype: real + default: 1.0 + hide: ${ ('part' if ( + int(nconnections) >= 1 + or (type == "complex" and int(nconnections) >= 1) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config + +- id: label2 + label: Line 2 Label + dtype: string + default: 'Signal 2' + base_key: label1 + hide: ${ ('part' if ( + int(nconnections) >= 2 + or (type == "complex" and int(nconnections) >= 1) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: width2 + label: Line 2 Width + default: 1 + base_key: width1 + hide: ${ ('part' if ( + int(nconnections) >= 2 + or (type == "complex" and int(nconnections) >= 1) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: color2 + label: Line 2 Color + dtype: enum + options: ['blue', 'red', 'green', 'black', 'cyan', 'magenta', 'yellow', 'dark red', 'dark green', 'dark blue'] + option_labels: ['Blue', 'Red', 'Green', 'Black', 'Cyan', 'Magenta', 'Yellow', 'Dark Red', 'Dark Green', 'Dark Blue'] + default: 'blue' + base_key: color1 + hide: ${ ('part' if ( + int(nconnections) >= 2 + or (type == "complex" and int(nconnections) >= 1) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: style2 + label: Line 2 Style + dtype: enum + options: ['1','2','3','4','5','0'] + option_labels: ['Solid','Dash','Dots','Dash-Dot','Dash-Dot-Dot'] + default: 1 + base_key: style1 + hide: ${ ('part' if ( + int(nconnections) >= 2 + or (type == "complex" and int(nconnections) >= 1) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: marker2 + label: Line 2 Marker + dtype: enum + options: ['-1','0','1','2','3','4','5','6','7','8','9'] + option_labels: ['None','Circle','Rectangle','Diamond','Triangle','Down Triangle','Left Triangle','Right Triangle','Cross','X-Cross'] + default: -1 + base_key: marker1 + hide: ${ ('part' if ( + int(nconnections) >= 2 + or (type == "complex" and int(nconnections) >= 1) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: alpha2 + label: Line 2 Alpha + dtype: real + default: 1.0 + base_key: alpha1 + hide: ${ ('part' if ( + int(nconnections) >= 2 + or (type == "complex" and int(nconnections) >= 1) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config + +- id: label3 + label: Line 3 Label + dtype: string + default: 'Signal 3' + base_key: label1 + hide: ${ ('part' if ( + int(nconnections) >= 3 + or (type == "complex" and int(nconnections) >= 2) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: width3 + label: Line 3 Width + default: 1 + base_key: width1 + hide: ${ ('part' if ( + int(nconnections) >= 3 + or (type == "complex" and int(nconnections) >= 2) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: color3 + label: Line 3 Color + dtype: enum + options: ['blue', 'red', 'green', 'black', 'cyan', 'magenta', 'yellow', 'dark red', 'dark green', 'dark blue'] + option_labels: ['Blue', 'Red', 'Green', 'Black', 'Cyan', 'Magenta', 'Yellow', 'Dark Red', 'Dark Green', 'Dark Blue'] + default: 'blue' + base_key: color1 + hide: ${ ('part' if ( + int(nconnections) >= 3 + or (type == "complex" and int(nconnections) >= 2) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: style3 + label: Line 3 Style + dtype: enum + options: ['1','2','3','4','5','0'] + option_labels: ['Solid','Dash','Dots','Dash-Dot','Dash-Dot-Dot'] + default: 1 + base_key: style1 + hide: ${ ('part' if ( + int(nconnections) >= 3 + or (type == "complex" and int(nconnections) >= 2) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: marker3 + label: Line 3 Marker + dtype: enum + options: ['-1','0','1','2','3','4','5','6','7','8','9'] + option_labels: ['None','Circle','Rectangle','Diamond','Triangle','Down Triangle','Left Triangle','Right Triangle','Cross','X-Cross'] + default: -1 + base_key: marker1 + hide: ${ ('part' if ( + int(nconnections) >= 3 + or (type == "complex" and int(nconnections) >= 2) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: alpha3 + label: Line 3 Alpha + dtype: real + default: 1.0 + base_key: alpha1 + hide: ${ ('part' if ( + int(nconnections) >= 3 + or (type == "complex" and int(nconnections) >= 2) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config + +- id: label4 + label: Line 4 Label + dtype: string + default: 'Signal 4' + base_key: label1 + hide: ${ ('part' if ( + int(nconnections) >= 4 + or (type == "complex" and int(nconnections) >= 2) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: width4 + label: Line 4 Width + default: 1 + base_key: width1 + hide: ${ ('part' if ( + int(nconnections) >= 4 + or (type == "complex" and int(nconnections) >= 2) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: color4 + label: Line 4 Color + dtype: enum + options: ['blue', 'red', 'green', 'black', 'cyan', 'magenta', 'yellow', 'dark red', 'dark green', 'dark blue'] + option_labels: ['Blue', 'Red', 'Green', 'Black', 'Cyan', 'Magenta', 'Yellow', 'Dark Red', 'Dark Green', 'Dark Blue'] + default: 'blue' + base_key: color1 + hide: ${ ('part' if ( + int(nconnections) >= 4 + or (type == "complex" and int(nconnections) >= 2) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: style4 + label: Line 4 Style + dtype: enum + options: ['1','2','3','4','5','0'] + option_labels: ['Solid','Dash','Dots','Dash-Dot','Dash-Dot-Dot'] + default: 1 + base_key: style1 + hide: ${ ('part' if ( + int(nconnections) >= 4 + or (type == "complex" and int(nconnections) >= 2) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: marker4 + label: Line 4 Marker + dtype: enum + options: ['-1','0','1','2','3','4','5','6','7','8','9'] + option_labels: ['None','Circle','Rectangle','Diamond','Triangle','Down Triangle','Left Triangle','Right Triangle','Cross','X-Cross'] + default: -1 + base_key: marker1 + hide: ${ ('part' if ( + int(nconnections) >= 4 + or (type == "complex" and int(nconnections) >= 2) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: alpha4 + label: Line 4 Alpha + dtype: real + default: 1.0 + base_key: alpha1 + hide: ${ ('part' if ( + int(nconnections) >= 4 + or (type == "complex" and int(nconnections) >= 2) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config + +- id: label5 + label: Line 5 Label + dtype: string + default: 'Signal 5' + base_key: label1 + hide: ${ ('part' if ( + int(nconnections) >= 5 + or (type == "complex" and int(nconnections) >= 3) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: width5 + label: Line 5 Width + default: 1 + base_key: width1 + hide: ${ ('part' if ( + int(nconnections) >= 5 + or (type == "complex" and int(nconnections) >= 3) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: color5 + label: Line 5 Color + dtype: enum + options: ['blue', 'red', 'green', 'black', 'cyan', 'magenta', 'yellow', 'dark red', 'dark green', 'dark blue'] + option_labels: ['Blue', 'Red', 'Green', 'Black', 'Cyan', 'Magenta', 'Yellow', 'Dark Red', 'Dark Green', 'Dark Blue'] + default: 'blue' + base_key: color1 + hide: ${ ('part' if ( + int(nconnections) >= 5 + or (type == "complex" and int(nconnections) >= 3) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: style5 + label: Line 5 Style + dtype: enum + options: ['1','2','3','4','5','0'] + option_labels: ['Solid','Dash','Dots','Dash-Dot','Dash-Dot-Dot'] + default: 1 + base_key: style1 + hide: ${ ('part' if ( + int(nconnections) >= 5 + or (type == "complex" and int(nconnections) >= 3) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: marker5 + label: Line 5 Marker + dtype: enum + options: ['-1','0','1','2','3','4','5','6','7','8','9'] + option_labels: ['None','Circle','Rectangle','Diamond','Triangle','Down Triangle','Left Triangle','Right Triangle','Cross','X-Cross'] + default: -1 + base_key: marker1 + hide: ${ ('part' if ( + int(nconnections) >= 5 + or (type == "complex" and int(nconnections) >= 3) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: alpha5 + label: Line 5 Alpha + dtype: real + default: 1.0 + base_key: alpha1 + hide: ${ ('part' if ( + int(nconnections) >= 5 + or (type == "complex" and int(nconnections) >= 3) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config + +- id: label6 + label: Line 6 Label + dtype: string + default: 'Signal 6' + base_key: label1 + hide: ${ ('part' if ( + int(nconnections) >= 6 + or (type == "complex" and int(nconnections) >= 3) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: width6 + label: Line 6 Width + default: 1 + base_key: width1 + hide: ${ ('part' if ( + int(nconnections) >= 6 + or (type == "complex" and int(nconnections) >= 3) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: color6 + label: Line 6 Color + dtype: enum + options: ['blue', 'red', 'green', 'black', 'cyan', 'magenta', 'yellow', 'dark red', 'dark green', 'dark blue'] + option_labels: ['Blue', 'Red', 'Green', 'Black', 'Cyan', 'Magenta', 'Yellow', 'Dark Red', 'Dark Green', 'Dark Blue'] + default: 'blue' + base_key: color1 + hide: ${ ('part' if ( + int(nconnections) >= 6 + or (type == "complex" and int(nconnections) >= 3) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: style6 + label: Line 6 Style + dtype: enum + options: ['1','2','3','4','5','0'] + option_labels: ['Solid','Dash','Dots','Dash-Dot','Dash-Dot-Dot'] + default: 1 + base_key: style1 + hide: ${ ('part' if ( + int(nconnections) >= 6 + or (type == "complex" and int(nconnections) >= 3) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: marker6 + label: Line 6 Marker + dtype: enum + options: ['-1','0','1','2','3','4','5','6','7','8','9'] + option_labels: ['None','Circle','Rectangle','Diamond','Triangle','Down Triangle','Left Triangle','Right Triangle','Cross','X-Cross'] + default: -1 + base_key: marker1 + hide: ${ ('part' if ( + int(nconnections) >= 6 + or (type == "complex" and int(nconnections) >= 3) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: alpha6 + label: Line 6 Alpha + dtype: real + default: 1.0 + base_key: alpha1 + hide: ${ ('part' if ( + int(nconnections) >= 6 + or (type == "complex" and int(nconnections) >= 3) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config + +- id: label7 + label: Line 7 Label + dtype: string + default: 'Signal 7' + base_key: label1 + hide: ${ ('part' if ( + int(nconnections) >= 7 + or (type == "complex" and int(nconnections) >= 4) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: width7 + label: Line 7 Width + default: 1 + base_key: width1 + hide: ${ ('part' if ( + int(nconnections) >= 7 + or (type == "complex" and int(nconnections) >= 4) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: color7 + label: Line 7 Color + dtype: enum + options: ['blue', 'red', 'green', 'black', 'cyan', 'magenta', 'yellow', 'dark red', 'dark green', 'dark blue'] + option_labels: ['Blue', 'Red', 'Green', 'Black', 'Cyan', 'Magenta', 'Yellow', 'Dark Red', 'Dark Green', 'Dark Blue'] + default: 'blue' + base_key: color1 + hide: ${ ('part' if ( + int(nconnections) >= 7 + or (type == "complex" and int(nconnections) >= 4) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: style7 + label: Line 7 Style + dtype: enum + options: ['1','2','3','4','5','0'] + option_labels: ['Solid','Dash','Dots','Dash-Dot','Dash-Dot-Dot'] + default: 1 + base_key: style1 + hide: ${ ('part' if ( + int(nconnections) >= 7 + or (type == "complex" and int(nconnections) >= 4) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: marker7 + label: Line 7 Marker + dtype: enum + options: ['-1','0','1','2','3','4','5','6','7','8','9'] + option_labels: ['None','Circle','Rectangle','Diamond','Triangle','Down Triangle','Left Triangle','Right Triangle','Cross','X-Cross'] + default: -1 + base_key: marker1 + hide: ${ ('part' if ( + int(nconnections) >= 7 + or (type == "complex" and int(nconnections) >= 4) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: alpha7 + label: Line 7 Alpha + dtype: real + default: 1.0 + base_key: alpha1 + hide: ${ ('part' if ( + int(nconnections) >= 7 + or (type == "complex" and int(nconnections) >= 4) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config + +- id: label8 + label: Line 8 Label + dtype: string + default: 'Signal 8' + base_key: label1 + hide: ${ ('part' if ( + int(nconnections) >= 8 + or (type == "complex" and int(nconnections) >= 4) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: width8 + label: Line 8 Width + default: 1 + base_key: width1 + hide: ${ ('part' if ( + int(nconnections) >= 8 + or (type == "complex" and int(nconnections) >= 4) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: color8 + label: Line 8 Color + dtype: enum + options: ['blue', 'red', 'green', 'black', 'cyan', 'magenta', 'yellow', 'dark red', 'dark green', 'dark blue'] + option_labels: ['Blue', 'Red', 'Green', 'Black', 'Cyan', 'Magenta', 'Yellow', 'Dark Red', 'Dark Green', 'Dark Blue'] + default: 'blue' + base_key: color1 + hide: ${ ('part' if ( + int(nconnections) >= 8 + or (type == "complex" and int(nconnections) >= 4) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: style8 + label: Line 8 Style + dtype: enum + options: ['1','2','3','4','5','0'] + option_labels: ['Solid','Dash','Dots','Dash-Dot','Dash-Dot-Dot'] + default: 1 + base_key: style1 + hide: ${ ('part' if ( + int(nconnections) >= 8 + or (type == "complex" and int(nconnections) >= 4) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: marker8 + label: Line 8 Marker + dtype: enum + options: ['-1','0','1','2','3','4','5','6','7','8','9'] + option_labels: ['None','Circle','Rectangle','Diamond','Triangle','Down Triangle','Left Triangle','Right Triangle','Cross','X-Cross'] + default: -1 + base_key: marker1 + hide: ${ ('part' if ( + int(nconnections) >= 8 + or (type == "complex" and int(nconnections) >= 4) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: alpha8 + label: Line 8 Alpha + dtype: real + default: 1.0 + base_key: alpha1 + hide: ${ ('part' if ( + int(nconnections) >= 8 + or (type == "complex" and int(nconnections) >= 4) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config + +- id: label9 + label: Line 9 Label + dtype: string + default: 'Signal 9' + base_key: label1 + hide: ${ ('part' if ( + int(nconnections) >= 9 + or (type == "complex" and int(nconnections) >= 5) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: width9 + label: Line 9 Width + default: 1 + base_key: width1 + hide: ${ ('part' if ( + int(nconnections) >= 9 + or (type == "complex" and int(nconnections) >= 5) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: color9 + label: Line 9 Color + dtype: enum + options: ['blue', 'red', 'green', 'black', 'cyan', 'magenta', 'yellow', 'dark red', 'dark green', 'dark blue'] + option_labels: ['Blue', 'Red', 'Green', 'Black', 'Cyan', 'Magenta', 'Yellow', 'Dark Red', 'Dark Green', 'Dark Blue'] + default: 'blue' + base_key: color1 + hide: ${ ('part' if ( + int(nconnections) >= 9 + or (type == "complex" and int(nconnections) >= 5) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: style9 + label: Line 9 Style + dtype: enum + options: ['1','2','3','4','5','0'] + option_labels: ['Solid','Dash','Dots','Dash-Dot','Dash-Dot-Dot'] + default: 1 + base_key: style1 + hide: ${ ('part' if ( + int(nconnections) >= 9 + or (type == "complex" and int(nconnections) >= 5) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: marker9 + label: Line 9 Marker + dtype: enum + options: ['-1','0','1','2','3','4','5','6','7','8','9'] + option_labels: ['None','Circle','Rectangle','Diamond','Triangle','Down Triangle','Left Triangle','Right Triangle','Cross','X-Cross'] + default: -1 + base_key: marker1 + hide: ${ ('part' if ( + int(nconnections) >= 9 + or (type == "complex" and int(nconnections) >= 5) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: alpha9 + label: Line 9 Alpha + dtype: real + default: 1.0 + base_key: alpha1 + hide: ${ ('part' if ( + int(nconnections) >= 9 + or (type == "complex" and int(nconnections) >= 5) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config + +- id: label10 + label: Line 10 Label + dtype: string + default: 'Signal 10' + base_key: label1 + hide: ${ ('part' if ( + int(nconnections) >= 10 + or (type == "complex" and int(nconnections) >= 5) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: width10 + label: Line 10 Width + default: 1 + base_key: width1 + hide: ${ ('part' if ( + int(nconnections) >= 10 + or (type == "complex" and int(nconnections) >= 5) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: color10 + label: Line 10 Color + dtype: enum + options: ['blue', 'red', 'green', 'black', 'cyan', 'magenta', 'yellow', 'dark red', 'dark green', 'dark blue'] + option_labels: ['Blue', 'Red', 'Green', 'Black', 'Cyan', 'Magenta', 'Yellow', 'Dark Red', 'Dark Green', 'Dark Blue'] + default: 'blue' + base_key: color1 + hide: ${ ('part' if ( + int(nconnections) >= 10 + or (type == "complex" and int(nconnections) >= 5) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: style10 + label: Line 10 Style + dtype: enum + options: ['1','2','3','4','5','0'] + option_labels: ['Solid','Dash','Dots','Dash-Dot','Dash-Dot-Dot'] + default: 1 + base_key: style1 + hide: ${ ('part' if ( + int(nconnections) >= 10 + or (type == "complex" and int(nconnections) >= 5) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: marker10 + label: Line 10 Marker + dtype: enum + options: ['-1','0','1','2','3','4','5','6','7','8','9'] + option_labels: ['None','Circle','Rectangle','Diamond','Triangle','Down Triangle','Left Triangle','Right Triangle','Cross','X-Cross'] + default: -1 + base_key: marker1 + hide: ${ ('part' if ( + int(nconnections) >= 10 + or (type == "complex" and int(nconnections) >= 5) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config +- id: alpha10 + label: Line 10 Alpha + dtype: real + default: 1.0 + base_key: alpha1 + hide: ${ ('part' if ( + int(nconnections) >= 10 + or (type == "complex" and int(nconnections) >= 5) + or (type == "msg_complex")) and (not type == "msg_float") + else 'all') + } + category: Config + +asserts: +- ${nconnections <= (5 if type == 'complex' else 10)} + +inputs: +- domain: stream + dtype: ${ type.t } + multiplicity: ${ (0 if type.startswith('msg') else nconnections) } + optional: true + +templates: + imports: |- + from PyQt5 import Qt + from gnuradio import qtgui + from gnuradio.filter import firdes + import sip + callbacks: + - set_time_domain_axis(${min}, ${max}) + - set_update_time(${update_time}) + - set_y_axis(${ymin}, ${ymax}) + - set_samp_rate(${srate}) + - set_samp_per_symbol(${samp_per_symbol}) + - self.${id}.set_trigger_mode(${tr_mode}, ${tr_slope}, ${tr_level}, ${tr_delay}, + ${tr_chan}, ${tr_tag}) + make: |- + <% + win = 'self._%s_win'%id + %>\ + qtgui.${type.fcn}( + ${size}, #size + ${srate}, #samp_rate + ${name}, #name + ${0 if type.startswith('msg') else nconnections} #number of inputs + ) + self.${id}.set_update_time(${update_time}) + self.${id}.set_samp_per_symbol(${samp_per_symbol}) + self.${id}.set_y_axis(${ymin}, ${ymax}) + + self.${id}.set_y_label(${ylabel}, ${yunit}) + + self.${id}.enable_tags(${entags}) + self.${id}.set_trigger_mode(${tr_mode}, ${tr_slope}, ${tr_level}, ${tr_delay}, ${tr_chan}, ${tr_tag}) + self.${id}.enable_autoscale(${autoscale}) + self.${id}.enable_grid(${grid}) + self.${id}.enable_axis_labels(${axislabels}) + self.${id}.enable_control_panel(${ctrlpanel}) + + % if legend == "False": + self.${id}.disable_legend() + % endif + + labels = [${label1}, ${label2}, ${label3}, ${label4}, ${label5}, + ${label6}, ${label7}, ${label8}, ${label9}, ${label10}] + widths = [${width1}, ${width2}, ${width3}, ${width4}, ${width5}, + ${width6}, ${width7}, ${width8}, ${width9}, ${width10}] + colors = ['${color1}', '${color2}', '${color3}', '${color4}', '${color5}', + '${color6}', '${color7}', '${color8}', '${color9}', '${color10}'] + alphas = [${alpha1}, ${alpha2}, ${alpha3}, ${alpha4}, ${alpha5}, + ${alpha6}, ${alpha7}, ${alpha8}, ${alpha9}, ${alpha10}] + styles = [${style1}, ${style2}, ${style3}, ${style4}, ${style5}, + ${style6}, ${style7}, ${style8}, ${style9}, ${style10}] + markers = [${marker1}, ${marker2}, ${marker3}, ${marker4}, ${marker5}, + ${marker6}, ${marker7}, ${marker8}, ${marker9}, ${marker10}] + + + % if type.endswith('complex'): + for i in range(${2 if type.startswith('msg') else 2*int(nconnections)}): + if len(labels[i]) == 0: + if (i % 2 == 0): + self.${id}.set_line_label(i, "Eye [Re{{Data {0}}}]".format(round(i/2))) + else: + self.${id}.set_line_label(i, "Eye [Im{{Data {0}}}]".format(round((i-1)/2))) + else: + self.${id}.set_line_label(i, labels[i]) + self.${id}.set_line_width(i, widths[i]) + self.${id}.set_line_color(i, colors[i]) + self.${id}.set_line_style(i, styles[i]) + self.${id}.set_line_marker(i, markers[i]) + self.${id}.set_line_alpha(i, alphas[i]) + % else: + for i in range(${1 if type.startswith('msg') else int(nconnections)}): + if len(labels[i]) == 0: + self.${id}.set_line_label(i, "Eye[Data {0}]".format(i)) + else: + self.${id}.set_line_label(i, labels[i]) + self.${id}.set_line_width(i, widths[i]) + self.${id}.set_line_color(i, colors[i]) + self.${id}.set_line_style(i, styles[i]) + self.${id}.set_line_marker(i, markers[i]) + self.${id}.set_line_alpha(i, alphas[i]) + % endif + + ${win} = sip.wrapinstance(self.${id}.pyqwidget(), Qt.QWidget) + ${gui_hint() % win} + +documentation: |- + 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/gr-qtgui/include/gnuradio/qtgui/CMakeLists.txt b/gr-qtgui/include/gnuradio/qtgui/CMakeLists.txt index 25a05a3178..596b561e54 100644 --- a/gr-qtgui/include/gnuradio/qtgui/CMakeLists.txt +++ b/gr-qtgui/include/gnuradio/qtgui/CMakeLists.txt @@ -16,8 +16,19 @@ install(FILES ConstellationDisplayPlot.h const_sink_c.h displayform.h + eyedisplaysform.h DisplayPlot.h + timecontrolpanel.h + eyecontrolpanel.h + eyedisplayform.h + EyeDisplayPlot.h + eye_sink_c.h + eye_sink_f.h form_menus.h + eye_sink_c.h + eye_sink_f.h + eyecontrolpanel.h + EyeDisplayPlot.h freqdisplayform.h freqcontrolpanel.h freq_sink_c.h diff --git a/gr-qtgui/include/gnuradio/qtgui/EyeDisplayPlot.h b/gr-qtgui/include/gnuradio/qtgui/EyeDisplayPlot.h new file mode 100644 index 0000000000..ef11fc3f4f --- /dev/null +++ b/gr-qtgui/include/gnuradio/qtgui/EyeDisplayPlot.h @@ -0,0 +1,110 @@ +/* -*- c++ -*- */ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef EYE_DISPLAY_PLOT_H +#define EYE_DISPLAY_PLOT_H + +#include <gnuradio/qtgui/DisplayPlot.h> +#include <gnuradio/tags.h> +#include <stdint.h> +#include <cstdio> +#include <vector> + +/*! + * \brief QWidget for displaying eye pattern plots. + * \ingroup qtgui_blk + */ +class EyeDisplayPlot : public DisplayPlot +{ + Q_OBJECT + + Q_PROPERTY(QColor tag_text_color READ getTagTextColor WRITE setTagTextColor) + Q_PROPERTY(QColor tag_background_color READ getTagBackgroundColor WRITE + setTagBackgroundColor) + Q_PROPERTY(Qt::BrushStyle tag_background_style READ getTagBackgroundStyle WRITE + setTagBackgroundStyle) + +public: + EyeDisplayPlot(unsigned int nplots, unsigned int curve_index, QWidget* parent); + virtual ~EyeDisplayPlot(); + + void plotNewData(const std::vector<double*> dataPoints, + const int64_t numDataPoints, + int d_sps, + const double timeInterval, + const std::vector<std::vector<gr::tag_t>>& tags = + std::vector<std::vector<gr::tag_t>>()); + + void replot(); + + void stemPlot(bool en); + + double sampleRate() const; + + const QColor getTagTextColor(); + const QColor getTagBackgroundColor(); + const Qt::BrushStyle getTagBackgroundStyle(); + void setLineColor(unsigned int which, QColor color); + void setLineWidth(unsigned int which, int width); + void setLineMarker(unsigned int which, QwtSymbol::Style marker); + void setLineStyle(unsigned int which, Qt::PenStyle style); + void setMarkerAlpha(unsigned int which, int alpha); + +public slots: + void setSampleRate(double sr, double units, const std::string& strunits); + + void setAutoScale(bool state); + void setAutoScaleShot(); + + void legendEntryChecked(QwtPlotItem* plotItem, bool on); + void legendEntryChecked(const QVariant& plotItem, bool on, int index); + + void enableTagMarker(unsigned int which, bool en); + + void setYLabel(const std::string& label, const std::string& unit = ""); + + void attachTriggerLines(bool en); + void setTriggerLines(double x, double y); + + void setTagTextColor(QColor c); + void setTagBackgroundColor(QColor c); + void setTagBackgroundStyle(Qt::BrushStyle b); + + void setLineLabel(unsigned int which, QString label); + +private: + void _resetXAxisPoints(); + void _autoScale(double bottom, double top); + + std::vector<double*> d_ydata; + + double* d_xdata; + + double d_sample_rate; + + unsigned int d_curve_index; + unsigned int nplots; + int d_sps; + unsigned int d_numPointsPerPeriod; + unsigned int d_numPeriods; + + bool d_autoscale_shot; + + std::vector<std::vector<QwtPlotMarker*>> d_tag_markers; + std::vector<bool> d_tag_markers_en; + + QList<QColor> colors; + QColor d_tag_text_color; + QColor d_tag_background_color; + Qt::BrushStyle d_tag_background_style; + + QwtPlotMarker* d_trigger_lines[2]; +}; + +#endif /* EYE_DISPLAY_PLOT_H */ diff --git a/gr-qtgui/include/gnuradio/qtgui/eye_sink_c.h b/gr-qtgui/include/gnuradio/qtgui/eye_sink_c.h new file mode 100644 index 0000000000..e8cd22f67b --- /dev/null +++ b/gr-qtgui/include/gnuradio/qtgui/eye_sink_c.h @@ -0,0 +1,186 @@ +/* -*- c++ -*- */ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef INCLUDED_QTGUI_EYE_SINK_C_H +#define INCLUDED_QTGUI_EYE_SINK_C_H + +#ifdef ENABLE_PYTHON +#include <Python.h> +#endif + +#include <gnuradio/qtgui/api.h> +#include <gnuradio/qtgui/trigger_mode.h> +#include <gnuradio/sync_block.h> +#include <qapplication.h> + +namespace gr { +namespace qtgui { + +/*! + * \brief A graphical sink to display signals eye patterns. + * \ingroup qtgui + * + * \details + * This is a QT-based graphical sink which takes a set of a complex + * streams and plots them as an eye pattern. For each signal, both + * the signal's I and Q eye patterns are plotted. Eye patterns are + * 2 symbol's time long. Symbol rate must be an integer multiple of + * the sample rate to obtain the eye pattern. The \a set_title and + * \a set_color functions can be used to change the label and color + * for a given input number. + * + * Trigger occurs at the beginning of each stream used to plot the + * eye pattern; whilst a real eye diagram would be triggered with + * a (recovered) symbol clock. For these reasons, triggering of + * noisy and/or unsynchronized signals may lead to incorrect eye + * pattern. + * + * The sink supports plotting streaming complex data or + * messages. The message port is named "in". The two modes cannot + * be used simultaneously, and \p nconnections should be set to 0 + * when using the message mode. GRC handles this issue by + * providing the "Complex Message" type that removes the streaming + * port(s). + * + * This sink can plot messages that contain either uniform vectors + * of complex 32 values (pmt::is_c32vector) or PDUs where the data + * is a uniform vector of complex 32 values. + */ +class QTGUI_API eye_sink_c : virtual public gr::sync_block +{ +public: + // gr::qtgui::eye_sink_c::sptr + typedef std::shared_ptr<eye_sink_c> sptr; + + /*! + * \brief Build complex eye sink + * + * \param size number of points to plot at once + * \param samp_rate sample rate (used to set x-axis labels) + * \param name title for the plot + * \param nconnections number of signals connected to sink + * \param parent a QWidget parent object, if any + */ + static sptr make(int size, + double samp_rate, + const std::string& name, + unsigned int nconnections = 1, + QWidget* parent = NULL); + + virtual void exec_() = 0; + virtual QWidget* qwidget() = 0; + +#ifdef ENABLE_PYTHON + virtual PyObject* pyqwidget() = 0; +#else + virtual void* pyqwidget() = 0; +#endif + + virtual void set_y_axis(double min, double max) = 0; + virtual void set_y_label(const std::string& label, const std::string& unit = "") = 0; + virtual void set_update_time(double t) = 0; + virtual void set_samp_per_symbol(unsigned int sps) = 0; + virtual void set_title(const std::string& title) = 0; + virtual void set_line_label(unsigned int which, const std::string& label) = 0; + virtual void set_line_color(unsigned int which, const std::string& color) = 0; + virtual void set_line_width(unsigned int which, int width) = 0; + virtual void set_line_style(unsigned int which, int style) = 0; + virtual void set_line_marker(unsigned int which, int marker) = 0; + virtual void set_nsamps(const int newsize) = 0; + virtual void set_samp_rate(const double samp_rate) = 0; + virtual void set_line_alpha(unsigned int which, double alpha) = 0; + + /*! + * Set up a trigger for the sink to know when to start + * plotting. Useful to isolate events and avoid noise. + * + * The trigger modes are Free, Auto, Normal, and Tag (see + * gr::qtgui::trigger_mode). The first three are like a normal + * oscope trigger function. Free means free running with no + * trigger, auto will trigger if the trigger event is seen, but + * will still plot otherwise, and normal will hold until the + * trigger event is observed. The Tag trigger mode allows us to + * trigger off a specific stream tag. The tag trigger is based + * only on the name of the tag, so when a tag of the given name + * is seen, the trigger is activated. + * + * In auto and normal mode, we look for the slope of the of the + * signal. Given a gr::qtgui::trigger_slope as either Positive + * or Negative, if the value between two samples moves in the + * given direction (x[1] > x[0] for Positive or x[1] < x[0] for + * Negative), then the trigger is activated. + * + * With the complex eye sink, each input has two eye patterns + * drawn for the real and imaginary parts of the signal. When + * selecting the \p channel value, channel 0 is the real signal + * and channel 1 is the imaginary signal. For more than 1 input + * stream, channel 2i is the real part of the ith input and + * channel (2i+1) is the imaginary part of the ith input + * channel. + * + * The \p delay value is specified in time based off the sample + * rate. If the sample rate of the block is set to 1, the delay + * is then also the sample number offset. This is the offset + * from the left-hand y-axis of the plot. It delays the signal + * to show the trigger event at the given delay along with some + * portion of the signal before the event. The delay must be + * within 0 - t_max where t_max is the maximum amount of time + * displayed on the eye pattern equal to 2 symbol time. + * + * \param mode The trigger_mode: free, auto, normal, or tag. + * \param slope The trigger_slope: positive or negative. Only + * used for auto and normal modes. + * \param level The magnitude of the trigger even for auto or normal modes. + * \param delay The delay (in units of time) for where the trigger happens. + * \param channel Which input channel to use for the trigger events. + * \param tag_key The name (as a string) of the tag to trigger off + * of if using the tag mode. + */ + virtual void set_trigger_mode(gr::qtgui::trigger_mode mode, + gr::qtgui::trigger_slope slope, + float level, + float delay, + int channel, + const std::string& tag_key = "") = 0; + + virtual std::string title() = 0; + virtual std::string line_label(unsigned int which) = 0; + virtual std::string line_color(unsigned int which) = 0; + virtual int line_width(unsigned int which) = 0; + virtual int line_style(unsigned int which) = 0; + virtual int line_marker(unsigned int which) = 0; + virtual double line_alpha(unsigned int which) = 0; + + virtual void set_size(int width, int height) = 0; + + virtual void enable_menu(bool en = true) = 0; + virtual void enable_grid(bool en = true) = 0; + virtual void enable_autoscale(bool en = true) = 0; + virtual void + enable_stem_plot(bool en = true) = 0; // Used by parent class, do not remove + virtual void + enable_semilogx(bool en = true) = 0; // Used by parent class, do not remove + virtual void + enable_semilogy(bool en = true) = 0; // Used by parent class, do not remove + virtual void enable_control_panel(bool en = true) = 0; + virtual void enable_tags(unsigned int which, bool en) = 0; + virtual void enable_tags(bool en) = 0; + virtual void enable_axis_labels(bool en = true) = 0; + virtual void disable_legend() = 0; + + virtual int nsamps() const = 0; + virtual void reset() = 0; + + QApplication* d_qApplication; +}; + +} // namespace qtgui +} // namespace gr + +#endif /* INCLUDED_QTGUI_EYE_SINK_C_H */ diff --git a/gr-qtgui/include/gnuradio/qtgui/eye_sink_f.h b/gr-qtgui/include/gnuradio/qtgui/eye_sink_f.h new file mode 100644 index 0000000000..a1082ca87a --- /dev/null +++ b/gr-qtgui/include/gnuradio/qtgui/eye_sink_f.h @@ -0,0 +1,177 @@ +/* -*- c++ -*- */ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef INCLUDED_QTGUI_EYE_SINK_F_H +#define INCLUDED_QTGUI_EYE_SINK_F_H + +#ifdef ENABLE_PYTHON +#include <Python.h> +#endif + +#include <gnuradio/qtgui/api.h> +#include <gnuradio/qtgui/trigger_mode.h> +#include <gnuradio/sync_block.h> +#include <qapplication.h> +namespace gr { +namespace qtgui { + +/*! + * \brief A graphical sink to display signals eye patterns. + * \ingroup qtgui + * + * \details + * This is a QT-based graphical sink which takes set of a float streams + * and plots them as eye patterns. For each signal, both + * the signal's I and Q eye patterns are plotted. Eye patterns are + * 2 symbol's time long. Symbol rate must be an integer multiple of + * the sample rate to obtain the eye pattern. The \a set_title and + * \a set_color functions can be used to change the label and color + * for a given input number. + * + * Trigger occurs at the beginning of each stream used to plot the + * eye pattern; whilst a real eye diagram would be triggered with + * a (recovered) symbol clock. For these reasons, triggering of + * noisy and/or unsynchronized signals may lead to incorrect eye + * pattern. + * + * The sink supports plotting streaming float data or + * messages. The message port is named "in". The two modes cannot + * be used simultaneously, and \p nconnections should be set to 0 + * when using the message mode. GRC handles this issue by + * providing the "Float Message" type that removes the streaming + * port(s). + * + * This sink can plot messages that contain either uniform vectors + * of float 32 values (pmt::is_f32vector) or PDUs where the data + * is a uniform vector of float 32 values. + */ + +class QTGUI_API eye_sink_f : virtual public gr::sync_block +{ +public: + // gr::qtgui::eye_sink_f::sptr + typedef std::shared_ptr<eye_sink_f> sptr; + + /*! + * \brief Build floating point eye sink + * + * \param size number of points to plot at once + * \param samp_rate sample rate (used to set x-axis labels) + * \param name title for the plot + * \param nconnections number of signals connected to sink + * \param parent a QWidget parent object, if any + */ + static sptr make(int size, + double samp_rate, + const std::string& name, + unsigned int nconnections = 1, + QWidget* parent = NULL); + + virtual void exec_() = 0; + virtual QWidget* qwidget() = 0; + +#ifdef ENABLE_PYTHON + virtual PyObject* pyqwidget() = 0; +#else + virtual void* pyqwidget() = 0; +#endif + + virtual void set_y_axis(double min, double max) = 0; + virtual void set_y_label(const std::string& label, const std::string& unit = "") = 0; + virtual void set_update_time(double t) = 0; + virtual void set_samp_per_symbol(unsigned int sps) = 0; + virtual void set_title(const std::string& title) = 0; + virtual void set_line_label(unsigned int which, const std::string& line) = 0; + virtual void set_line_color(unsigned int which, const std::string& color) = 0; + virtual void set_line_width(unsigned int which, int width) = 0; + virtual void set_line_style(unsigned int which, int style) = 0; + virtual void set_line_marker(unsigned int which, int marker) = 0; + virtual void set_nsamps(const int newsize) = 0; + virtual void set_samp_rate(const double samp_rate) = 0; + virtual void set_line_alpha(unsigned int which, double alpha) = 0; + + /*! + * Set up a trigger for the sink to know when to start + * plotting. Useful to isolate events and avoid noise. + * + * The trigger modes are Free, Auto, Normal, and Tag (see + * gr::qtgui::trigger_mode). The first three are like a normal + * oscope trigger function. Free means free running with no + * trigger, auto will trigger if the trigger event is seen, but + * will still plot otherwise, and normal will hold until the + * trigger event is observed. The Tag trigger mode allows us to + * trigger off a specific stream tag. The tag trigger is based + * only on the name of the tag, so when a tag of the given name + * is seen, the trigger is activated. + * + * In auto and normal mode, we look for the slope of the of the + * signal. Given a gr::qtgui::trigger_slope as either Positive + * or Negative, if the value between two samples moves in the + * given direction (x[1] > x[0] for Positive or x[1] < x[0] for + * Negative), then the trigger is activated. + * + * The \p delay value is specified in time based off the sample + * rate. If the sample rate of the block is set to 1, the delay + * is then also the sample number offset. This is the offset + * from the left-hand y-axis of the plot. It delays the signal + * to show the trigger event at the given delay along with some + * portion of the signal before the event. The delay must be + * within 0 - t_max where t_max is the maximum amount of time + * displayed on the eye pattern equal to 2 symbol time. + * + * \param mode The trigger_mode: free, auto, normal, or tag. + * \param slope The trigger_slope: positive or negative. Only + * used for auto and normal modes. + * \param level The magnitude of the trigger even for auto or normal modes. + * \param delay The delay (in units of time) for where the trigger happens. + * \param channel Which input channel to use for the trigger events. + * \param tag_key The name (as a string) of the tag to trigger off + * of if using the tag mode. + */ + virtual void set_trigger_mode(gr::qtgui::trigger_mode mode, + gr::qtgui::trigger_slope slope, + float level, + float delay, + int channel, + const std::string& tag_key = "") = 0; + + virtual std::string title() = 0; + virtual std::string line_label(unsigned int which) = 0; + virtual std::string line_color(unsigned int which) = 0; + virtual int line_width(unsigned int which) = 0; + virtual int line_style(unsigned int which) = 0; + virtual int line_marker(unsigned int which) = 0; + virtual double line_alpha(unsigned int which) = 0; + + virtual void set_size(int width, int height) = 0; + + virtual void enable_menu(bool en = true) = 0; + virtual void enable_grid(bool en = true) = 0; + virtual void enable_autoscale(bool en = true) = 0; + virtual void + enable_stem_plot(bool en = true) = 0; // Used by parent class, do not remove + virtual void + enable_semilogx(bool en = true) = 0; // Used by parent class, do not remove + virtual void + enable_semilogy(bool en = true) = 0; // Used by parent class, do not remove + virtual void enable_control_panel(bool en = true) = 0; + virtual void enable_tags(unsigned int which, bool en) = 0; + virtual void enable_tags(bool en) = 0; + virtual void enable_axis_labels(bool en = true) = 0; + virtual void disable_legend() = 0; + + virtual int nsamps() const = 0; + virtual void reset() = 0; + + QApplication* d_qApplication; +}; +} // namespace qtgui +} // namespace gr + +#endif /* INCLUDED_QTGUI_EYE_SINK_F_H */ diff --git a/gr-qtgui/include/gnuradio/qtgui/eyecontrolpanel.h b/gr-qtgui/include/gnuradio/qtgui/eyecontrolpanel.h new file mode 100644 index 0000000000..810efdc3de --- /dev/null +++ b/gr-qtgui/include/gnuradio/qtgui/eyecontrolpanel.h @@ -0,0 +1,75 @@ +/* -*- c++ -*- */ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef EYE_CONTROL_PANEL_H +#define EYE_CONTROL_PANEL_H + +#include <gnuradio/qtgui/eyedisplayform.h> +#include <gnuradio/qtgui/eyedisplaysform.h> +#include <QtGui/QtGui> +#include <QCheckBox> +#include <QComboBox> +#include <QHBoxLayout> +#include <QLabel> +#include <QPushButton> +#include <QSlider> +#include <vector> + +class EyeControlPanel : public QVBoxLayout +{ + Q_OBJECT + +public: + EyeControlPanel(EyeDisplayForm* form); + ~EyeControlPanel(); + +public slots: + void toggleAutoScale(bool en); + void toggleGrid(bool en); + void toggleTriggerMode(gr::qtgui::trigger_mode mode); + void toggleTriggerSlope(gr::qtgui::trigger_slope slope); + void toggleStopButton(); + +signals: + void signalToggleStopButton(); + +private: + EyeDisplayForm* d_parent; + QGroupBox* d_axes_box; + QGroupBox* d_trigger_box; + QGroupBox* d_extras_box; + + QVBoxLayout* d_axes_layout; + QHBoxLayout* d_yoff_layout; + QHBoxLayout* d_yrange_layout; + QVBoxLayout* d_trigger_layout; + QHBoxLayout* d_trigger_level_layout; + QHBoxLayout* d_trigger_delay_layout; + QVBoxLayout* d_extras_layout; + + QLabel* d_yoff_label; + QLabel* d_yrange_label; + QLabel* d_trigger_level_label; + QLabel* d_trigger_delay_label; + + QCheckBox* d_autoscale_check; + QCheckBox* d_grid_check; + QCheckBox* d_axislabels_check; + + QPushButton *d_yoff_plus, *d_yoff_minus; + QPushButton *d_yrange_plus, *d_yrange_minus; + QComboBox* d_trigger_mode_combo; + QComboBox* d_trigger_slope_combo; + QPushButton *d_trigger_level_plus, *d_trigger_level_minus; + QPushButton *d_trigger_delay_plus, *d_trigger_delay_minus; + QPushButton* d_autoscale_button; + QPushButton* d_stop_button; +}; + +#endif /* EYE_CONTROL_PANEL_H */ diff --git a/gr-qtgui/include/gnuradio/qtgui/eyedisplayform.h b/gr-qtgui/include/gnuradio/qtgui/eyedisplayform.h new file mode 100644 index 0000000000..d4987d264e --- /dev/null +++ b/gr-qtgui/include/gnuradio/qtgui/eyedisplayform.h @@ -0,0 +1,128 @@ +/* -*- c++ -*- */ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef EYE_DISPLAY_FORM_H +#define EYE_DISPLAY_FORM_H + +#include <gnuradio/qtgui/EyeDisplayPlot.h> +#include <gnuradio/qtgui/eyedisplaysform.h> +#include <gnuradio/qtgui/spectrumUpdateEvents.h> +#include <QtGui/QtGui> +#include <vector> + +class EyeControlPanel; + +/*! + * \brief EyeDisplaysForm child for managing eye pattern plots. + * \ingroup qtgui_blk + */ +class EyeDisplayForm : public EyeDisplaysForm +{ + Q_OBJECT + +public: + EyeDisplayForm(int nplots = 1, bool cmplx = false, QWidget* parent = 0); + ~EyeDisplayForm(); + + EyeDisplayPlot* getPlot(); + EyeDisplayPlot* getSinglePlot(unsigned int); + + int getNPoints() const; + int getSamplesPerSymbol() const; + + gr::qtgui::trigger_mode getTriggerMode() const; + gr::qtgui::trigger_slope getTriggerSlope() const; + float getTriggerLevel() const; + float getTriggerDelay() const; + int getTriggerChannel() const; + std::string getTriggerTagKey() const; + void setAxisLabels(bool en); + +public slots: + void customEvent(QEvent* e); + + void setSampleRate(const double samprate); + void setSampleRate(const QString& samprate); + void setYaxis(double min, double max); + void setYLabel(const std::string& label, const std::string& unit = ""); + void setNPoints(const int); + void autoScale(bool en); + void autoScaleShot(); + void tagMenuSlot(bool en); + void setTagMenu(unsigned int which, bool en); + + void updateTrigger(gr::qtgui::trigger_mode mode); + void setTriggerMode(gr::qtgui::trigger_mode mode); + void setTriggerSlope(gr::qtgui::trigger_slope slope); + void setTriggerLevel(QString s); + void setTriggerLevel(float level); + void setTriggerDelay(QString s); + void setTriggerDelay(float delay); + void setTriggerChannel(int chan); + void setTriggerTagKey(QString s); + void setTriggerTagKey(const std::string& s); + + void setupControlPanel(bool en); + void setupControlPanel(); + void teardownControlPanel(); + +private slots: + void newData(const QEvent*); + void notifyYAxisPlus(); + void notifyYAxisMinus(); + void notifyYRangePlus(); + void notifyYRangeMinus(); + void notifyTriggerMode(const QString& mode); + void notifyTriggerSlope(const QString& slope); + void notifyTriggerLevelPlus(); + void notifyTriggerLevelMinus(); + void notifyTriggerDelayPlus(); + void notifyTriggerDelayMinus(); + +signals: + void signalTriggerMode(gr::qtgui::trigger_mode mode); + void signalTriggerSlope(gr::qtgui::trigger_slope slope); + void signalTriggerLevel(float level); + void signalTriggerDelay(float delay); + void signalReplot(); + void signalNPoints(const int npts); + +private: + QIntValidator* d_int_validator; + + double d_start_frequency; + double d_stop_frequency; + double d_current_units; + int d_npoints; + unsigned int d_rows; + unsigned int d_cols; + NPointsMenu* d_nptsmenu; + + QAction* d_controlpanelmenu; + std::vector<QAction*> d_tagsmenu; + + QMenu* d_triggermenu; + TriggerModeMenu* d_tr_mode_menu; + TriggerSlopeMenu* d_tr_slope_menu; + PopupMenu* d_tr_level_act; + PopupMenu* d_tr_delay_act; + TriggerChannelMenu* d_tr_channel_menu; + PopupMenu* d_tr_tag_key_act; + + gr::qtgui::trigger_mode d_trig_mode; + gr::qtgui::trigger_slope d_trig_slope; + float d_trig_level; + float d_trig_delay; + int d_trig_channel; + std::string d_trig_tag_key; + + EyeControlPanel* d_controlpanel; +}; + +#endif /* EYE_DISPLAY_FORM_H */ diff --git a/gr-qtgui/include/gnuradio/qtgui/eyedisplaysform.h b/gr-qtgui/include/gnuradio/qtgui/eyedisplaysform.h new file mode 100644 index 0000000000..0e9fcf2fb8 --- /dev/null +++ b/gr-qtgui/include/gnuradio/qtgui/eyedisplaysform.h @@ -0,0 +1,129 @@ +/* -*- c++ -*- */ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef EYE_DISPLAYS_FORM_H +#define EYE_DISPLAYS_FORM_H + +#include <gnuradio/qtgui/api.h> +#include <gnuradio/qtgui/spectrumUpdateEvents.h> +#include <QtGui/QtGui> +#include <vector> + +#include <qwt_plot_grid.h> +#include <qwt_plot_layout.h> + +#include <gnuradio/qtgui/DisplayPlot.h> +#include <gnuradio/qtgui/form_menus.h> + +/*! + * \brief Base class for setting up and managing QTGUI plot forms. + * \ingroup qtgui_blk + */ +class QTGUI_API EyeDisplaysForm : public QWidget +{ + Q_OBJECT + +public: + EyeDisplaysForm(int nplots = 1, QWidget* parent = 0); + ~EyeDisplaysForm(); + + void Reset(); + bool isClosed() const; + + void enableMenu(bool en = true); + +public slots: + void resizeEvent(QResizeEvent* e); + void mousePressEvent(QMouseEvent* e); + virtual void customEvent(QEvent* e) = 0; + + void closeEvent(QCloseEvent* e); + + void setUpdateTime(double t); + void setSamplesPerSymbol(int64_t sps); + void setTitle(const QString& title); + void setLineLabel(unsigned int which, const QString& label); + void setLineColor(unsigned int which, const QString& color); + void setLineWidth(unsigned int which, unsigned int width); + void setLineStyle(unsigned int which, Qt::PenStyle style); + void setLineMarker(unsigned int which, QwtSymbol::Style style); + void setMarkerAlpha(unsigned int which, unsigned int alpha); + + QString title(); + QString lineLabel(unsigned int which); + QString lineColor(unsigned int which); + int lineWidth(unsigned int which); + Qt::PenStyle lineStyle(unsigned int which); + QwtSymbol::Style lineMarker(unsigned int which); + int markerAlpha(unsigned int which); + + virtual void setSampleRate(const QString& rate); + + void setStop(bool on); + void setStop(); + + void setGrid(bool on); + void setAxisLabels(bool en); + + void saveFigure(); + + void disableLegend(); + +private slots: + virtual void newData(const QEvent*) = 0; + virtual void autoScale(bool) = 0; + void updateGuiTimer(); + + virtual void onPlotPointSelected(const QPointF p); + +signals: + void plotPointSelected(const QPointF p, int type); + void toggleGrid(bool en); + +protected: + bool d_isclosed; + + unsigned int d_nplots; + int d_sps; + + QGridLayout* d_layout; + DisplayPlot* d_display_plot; + std::vector<DisplayPlot*> d_displays_plot; + bool d_system_specified_flag; + + std::vector<QwtPlotGrid*> d_grids; + + bool d_menu_on; + QMenu* d_menu; + + QAction* d_stop_act; + bool d_stop_state; + QAction* d_grid_act; + bool d_grid_state; + QAction* d_axislabelsmenu; + bool d_axislabels; + + QAction* d_autoscale_act; + bool d_autoscale_state; + + QList<QMenu*> d_lines_menu; + QList<LineTitleAction*> d_line_title_act; + QList<LineColorMenu*> d_line_color_menu; + QList<LineWidthMenu*> d_line_width_menu; + QList<LineStyleMenu*> d_line_style_menu; + QList<LineMarkerMenu*> d_line_marker_menu; + QList<MarkerAlphaMenu*> d_marker_alpha_menu; + + PopupMenu* d_samp_rate_act; + QAction* d_save_act; + + double d_update_time; +}; + +#endif /* EYE_DISPLAYS_FORM_H */ diff --git a/gr-qtgui/lib/CMakeLists.txt b/gr-qtgui/lib/CMakeLists.txt index 7ab6fd1ab6..f3adae9c7c 100644 --- a/gr-qtgui/lib/CMakeLists.txt +++ b/gr-qtgui/lib/CMakeLists.txt @@ -13,6 +13,7 @@ add_library(gnuradio-qtgui DisplayPlot.cc FrequencyDisplayPlot.cc + EyeDisplayPlot.cc TimeDomainDisplayPlot.cc TimeRasterDisplayPlot.cc timeRasterGlobalData.cc @@ -23,6 +24,9 @@ add_library(gnuradio-qtgui VectorDisplayPlot.cc spectrumdisplayform.cc displayform.cc + eyedisplaysform.cc + eyedisplayform.cc + eyecontrolpanel.cc timedisplayform.cc timecontrolpanel.cc timerasterdisplayform.cc @@ -38,6 +42,8 @@ add_library(gnuradio-qtgui plot_raster.cc sink_c_impl.cc sink_f_impl.cc + eye_sink_c_impl.cc + eye_sink_f_impl.cc time_sink_c_impl.cc time_sink_f_impl.cc time_raster_sink_b_impl.cc @@ -89,7 +95,10 @@ set(qtgui_mod_includedir ${CMAKE_CURRENT_SOURCE_DIR}/../include/gnuradio/qtgui) QT5_WRAP_CPP(qtgui_moc_sources ${qtgui_mod_includedir}/spectrumdisplayform.h ${qtgui_mod_includedir}/displayform.h + ${qtgui_mod_includedir}/eyedisplaysform.h + ${qtgui_mod_includedir}/eyedisplayform.h ${qtgui_mod_includedir}/timedisplayform.h + ${qtgui_mod_includedir}/eyecontrolpanel.h ${qtgui_mod_includedir}/timecontrolpanel.h ${qtgui_mod_includedir}/timerasterdisplayform.h ${qtgui_mod_includedir}/freqdisplayform.h @@ -101,6 +110,7 @@ QT5_WRAP_CPP(qtgui_moc_sources ${qtgui_mod_includedir}/vectordisplayform.h ${qtgui_mod_includedir}/form_menus.h ${qtgui_mod_includedir}/DisplayPlot.h + ${qtgui_mod_includedir}/EyeDisplayPlot.h ${qtgui_mod_includedir}/FrequencyDisplayPlot.h ${qtgui_mod_includedir}/TimeDomainDisplayPlot.h ${qtgui_mod_includedir}/TimeRasterDisplayPlot.h diff --git a/gr-qtgui/lib/EyeDisplayPlot.cc b/gr-qtgui/lib/EyeDisplayPlot.cc new file mode 100644 index 0000000000..bcc4ecf9f3 --- /dev/null +++ b/gr-qtgui/lib/EyeDisplayPlot.cc @@ -0,0 +1,678 @@ +/* -*- c++ -*- */ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef EYE_DISPLAY_PLOT_C +#define EYE_DISPLAY_PLOT_C + +#include <gnuradio/qtgui/EyeDisplayPlot.h> + +#include <qwt_legend.h> +#include <qwt_scale_draw.h> +#include <volk/volk.h> +#include <QColor> +#include <cmath> +#include <iostream> + +class TimePrecisionClass +{ +public: + TimePrecisionClass(const int timePrecision) { d_timePrecision = timePrecision; } + + virtual ~TimePrecisionClass() {} + + virtual unsigned int getTimePrecision() const { return d_timePrecision; } + + virtual void setTimePrecision(const unsigned int newPrecision) + { + d_timePrecision = newPrecision; + } + +protected: + unsigned int d_timePrecision; +}; + +class EyeDisplayZoomer : public QwtPlotZoomer, public TimePrecisionClass +{ +public: +#if QWT_VERSION < 0x060100 + EyeDisplayZoomer(QwtPlotCanvas* canvas, const unsigned int timePrecision) +#else /* QWT_VERSION < 0x060100 */ + EyeDisplayZoomer(QWidget* canvas, const unsigned int timePrecision) +#endif /* QWT_VERSION < 0x060100 */ + : QwtPlotZoomer(canvas), TimePrecisionClass(timePrecision), d_yUnitType("V") + { + setTrackerMode(QwtPicker::AlwaysOn); + } + + virtual ~EyeDisplayZoomer() {} + + virtual void updateTrackerText() { updateDisplay(); } + + void setUnitType(const std::string& type) { d_unitType = type; } + + std::string unitType() { return d_unitType; } + + void setYUnitType(const std::string& type) { d_yUnitType = type; } + +protected: + using QwtPlotZoomer::trackerText; + virtual QwtText trackerText(const QPoint& p) const + { + QwtText t; + QwtDoublePoint dp = QwtPlotZoomer::invTransform(p); + if ((fabs(dp.y()) > 0.0001) && (fabs(dp.y()) < 10000)) { + t.setText(QString("%1 %2, %3 %4") + .arg(dp.x(), 0, 'f', getTimePrecision()) + .arg(d_unitType.c_str()) + .arg(dp.y(), 0, 'f', 4) + .arg(d_yUnitType.c_str())); + } else { + t.setText(QString("%1 %2, %3 %4") + .arg(dp.x(), 0, 'f', getTimePrecision()) + .arg(d_unitType.c_str()) + .arg(dp.y(), 0, 'e', 4) + .arg(d_yUnitType.c_str())); + } + + return t; + } + +private: + std::string d_unitType; + std::string d_yUnitType; +}; + +/*********************************************************************** + * Main Eye Pattern plotter widget + **********************************************************************/ + +EyeDisplayPlot::EyeDisplayPlot(unsigned int nplots, + unsigned int curve_index, + QWidget* parent) + : DisplayPlot(1, parent) +{ + + d_numPoints = 1024; + d_nplots = nplots; + + d_sps = 4; + + d_numPointsPerPeriod = 2 * d_sps + 1; + d_numPeriods = std::floor((d_numPoints - 1) / 2 / d_sps); + + d_xdata = new double[d_numPointsPerPeriod]; + d_curve_index = curve_index; + memset(d_xdata, 0x0, d_numPointsPerPeriod * sizeof(double)); + + d_tag_text_color = Qt::black; + d_tag_background_color = Qt::white; + d_tag_background_style = Qt::NoBrush; + + d_zoomer = new EyeDisplayZoomer(canvas(), 0); + +#if QWT_VERSION < 0x060000 + d_zoomer->setSelectionFlags(QwtPicker::RectSelection | QwtPicker::DragSelection); +#endif + + d_zoomer->setMousePattern( + QwtEventPattern::MouseSelect2, Qt::RightButton, Qt::ControlModifier); + d_zoomer->setMousePattern(QwtEventPattern::MouseSelect3, Qt::RightButton); + + const QColor c(Qt::darkRed); + d_zoomer->setRubberBandPen(c); + d_zoomer->setTrackerPen(c); + + d_autoscale_shot = false; + QwtText title; + title.setText(QString("Eye [Data %1]").arg(d_curve_index)); + setTitle(title); + + setAxisScaleEngine(QwtPlot::xBottom, new QwtLinearScaleEngine); + setXaxis(-1.0 * d_numPointsPerPeriod, d_numPointsPerPeriod - 1); + QwtText xAxisTitle(QString("Time (sec)")); + xAxisTitle.setRenderFlags(Qt::AlignRight | Qt::AlignVCenter); + setAxisTitle(QwtPlot::xBottom, xAxisTitle); + + setAxisScaleEngine(QwtPlot::yLeft, new QwtLinearScaleEngine); + setYaxis(-2.0, 2.0); + QwtText yAxisTitle(QString("Amplitude")); + setAxisTitle(QwtPlot::yLeft, yAxisTitle); + + colors << QColor(Qt::blue) << QColor(Qt::red) << QColor(Qt::green) + << QColor(Qt::black) << QColor(Qt::cyan) << QColor(Qt::magenta) + << QColor(Qt::yellow) << QColor(Qt::gray) << QColor(Qt::darkRed) + << QColor(Qt::darkGreen) << QColor(Qt::darkBlue) + << QColor(Qt::darkGray) + // cycle through all colors again to increase eye_sink_f input limit + // from 12 to 24, otherwise you get a segfault + << QColor(Qt::blue) << QColor(Qt::red) << QColor(Qt::green) + << QColor(Qt::black) << QColor(Qt::cyan) << QColor(Qt::magenta) + << QColor(Qt::yellow) << QColor(Qt::gray) << QColor(Qt::darkRed) + << QColor(Qt::darkGreen) << QColor(Qt::darkBlue) << QColor(Qt::darkGray); + + // Setup dataPoints and plot vectors + // Automatically deleted when parent is deleted + for (unsigned int i = 0; i < d_numPeriods; ++i) { + d_ydata.push_back(new double[d_numPointsPerPeriod]); + memset(d_ydata[i], 0x0, d_numPointsPerPeriod * sizeof(double)); + d_plot_curve.push_back( + new QwtPlotCurve(QString("Eye [Data %1]").arg(d_curve_index))); + d_plot_curve[i]->attach(this); + d_plot_curve[i]->setPen(QPen(colors[d_curve_index])); + d_plot_curve[i]->setRenderHint(QwtPlotItem::RenderAntialiased); + + QwtSymbol* symbol = new QwtSymbol(QwtSymbol::NoSymbol, + QBrush(colors[d_curve_index]), + QPen(colors[d_curve_index]), + QSize(7, 7)); + +#if QWT_VERSION < 0x060000 + d_plot_curve[i]->setRawData(d_xdata, d_ydata[i], d_numPointsPerPeriod); + d_plot_curve[i]->setSymbol(*symbol); +#else + d_plot_curve[i]->setRawSamples(d_xdata, d_ydata[i], d_numPointsPerPeriod); + d_plot_curve[i]->setSymbol(symbol); +#endif + } + + d_sample_rate = 1; + _resetXAxisPoints(); + + // d_tag_markers and d_tag_markers are 2 std::vector of size 1 for Eye Plot + d_tag_markers.resize(1); + d_tag_markers_en = std::vector<bool>(1, true); + + d_trigger_lines[0] = new QwtPlotMarker(); + d_trigger_lines[0]->setLineStyle(QwtPlotMarker::HLine); + d_trigger_lines[0]->setLinePen(QPen(Qt::red, 0.6, Qt::DashLine)); + d_trigger_lines[0]->setRenderHint(QwtPlotItem::RenderAntialiased); + d_trigger_lines[0]->setXValue(0.0); + d_trigger_lines[0]->setYValue(0.0); + + d_trigger_lines[1] = new QwtPlotMarker(); + d_trigger_lines[1]->setLineStyle(QwtPlotMarker::VLine); + d_trigger_lines[1]->setLinePen(QPen(Qt::red, 0.6, Qt::DashLine)); + d_trigger_lines[1]->setRenderHint(QwtPlotItem::RenderAntialiased); + d_trigger_lines[1]->setXValue(0.0); + d_trigger_lines[1]->setYValue(0.0); +} + +EyeDisplayPlot::~EyeDisplayPlot() +{ + + // Delete d_ydata set used by this EyeDisplayPlot + delete[] d_ydata[d_curve_index]; + + // Delete d_xdata once (it is used by some EyeDisplayPlot) + if (d_curve_index == 0) + delete[] d_xdata; + + // d_zoomer and _panner deleted when parent deleted +} + +void EyeDisplayPlot::replot() { QwtPlot::replot(); } + +void EyeDisplayPlot::plotNewData(const std::vector<double*> dataPoints, + const int64_t numDataPoints, + int sps, + const double timeInterval, + const std::vector<std::vector<gr::tag_t>>& tags) +{ + + if (!d_stop) { + if ((numDataPoints > 0)) { + if ((numDataPoints != d_numPoints) || (d_sps != sps)) { + // Detach all curves + for (unsigned int i = 0; i < d_numPeriods; ++i) { + d_plot_curve[i]->detach(); + } + d_numPoints = numDataPoints; + d_sps = sps; + d_numPointsPerPeriod = 2 * d_sps + 1; + d_numPeriods = std::floor((d_numPoints - 1) / 2 / d_sps); + + // clear d_plot_curve, d_xdata, d_ydata + d_plot_curve.clear(); + d_ydata.clear(); + delete[] d_xdata; + d_xdata = new double[(1 + d_numPointsPerPeriod)]; + _resetXAxisPoints(); + + // New data structure and data + for (unsigned int i = 0; i < d_numPeriods; ++i) { + int64_t time_index = i * (d_numPointsPerPeriod - 1); + d_ydata.push_back(new double[d_numPointsPerPeriod]); + memcpy(d_ydata[i], + &(dataPoints[d_curve_index][time_index]), + d_numPointsPerPeriod * sizeof(double)); + d_plot_curve.push_back( + new QwtPlotCurve(QString("Eye [Data %1]").arg(d_curve_index))); + d_plot_curve[i]->attach(this); + d_plot_curve[i]->setPen(QPen(colors[d_curve_index])); + d_plot_curve[i]->setRenderHint(QwtPlotItem::RenderAntialiased); + + QwtSymbol* symbol = new QwtSymbol(QwtSymbol::NoSymbol, + QBrush(colors[d_curve_index]), + QPen(colors[d_curve_index]), + QSize(7, 7)); + +#if QWT_VERSION < 0x060000 + d_plot_curve[i]->setRawData( + d_xdata, d_ydata[i], d_numPointsPerPeriod); + d_plot_curve[i]->setSymbol(*symbol); +#else + d_plot_curve[i]->setRawSamples( + d_xdata, d_ydata[i], d_numPointsPerPeriod); + d_plot_curve[i]->setSymbol(symbol); +#endif + } + } else { + // New data + for (unsigned int i = 0; i < d_numPeriods; ++i) { + int64_t time_index = i * (d_numPointsPerPeriod - 1); + memcpy(d_ydata[i], + &(dataPoints[d_curve_index][time_index]), + d_numPointsPerPeriod * sizeof(double)); + } + } + + // Detach and delete any tags that were plotted last time + for (size_t i = 0; i < d_tag_markers[0].size(); i++) { + d_tag_markers[0][i]->detach(); + delete d_tag_markers[0][i]; + } + d_tag_markers[0].clear(); + + // Plot and attach any new tags found. + // First test if this was a complex input where real/imag get + // split here into two stream. + if (!tags.empty()) { + unsigned int mult = d_nplots / tags.size(); + unsigned int tags_index = d_curve_index / mult; + std::vector<std::vector<gr::tag_t>>::const_iterator tag = tags.begin(); + // Move iterator to curve[d_curve_index] tags set i.e. tags[tags_index] + for (unsigned int i = 0; i < tags_index; i++) { + tag++; + } + + std::vector<gr::tag_t>::const_iterator t; + for (t = tag->begin(); t != tag->end(); t++) { + uint64_t offset = (*t).offset; + + // Ignore tag if its offset is outside our plottable vector. + if (offset >= (uint64_t)((d_numPointsPerPeriod - 1) * d_numPeriods)) { + continue; + } + + // Adjust the offset in range of the eye pattern length (0 to + // d_numPointsPerPeriod - 1) + unsigned int period = offset / (d_numPointsPerPeriod - 1); + unsigned int eye_offset = offset % (d_numPointsPerPeriod - 1); + double sample_offset = double(eye_offset) / d_sample_rate; + + std::stringstream s; + s << (*t).key << ": " << (*t).value; + + double yval = d_ydata[period][eye_offset]; + + // Find if we already have a marker at this location + std::vector<QwtPlotMarker*>::iterator mitr; + for (mitr = d_tag_markers[0].begin(); mitr != d_tag_markers[0].end(); + mitr++) { + if ((*mitr)->xValue() == sample_offset) { + break; + } + } + // If no matching marker, create a new one + if (mitr == d_tag_markers[0].end()) { + bool show = d_plot_curve[0]->isVisible(); + + QwtPlotMarker* m = new QwtPlotMarker(); + m->setXValue(sample_offset); + m->setYValue(yval); + + QBrush brush(getTagBackgroundColor(), getTagBackgroundStyle()); + + QPen pen; + pen.setColor(Qt::black); + pen.setWidth(1); + + QwtSymbol* sym = + new QwtSymbol(QwtSymbol::NoSymbol, brush, pen, QSize(12, 12)); + + if (yval >= 0) { + sym->setStyle(QwtSymbol::DTriangle); + m->setLabelAlignment(Qt::AlignTop); + } else { + sym->setStyle(QwtSymbol::UTriangle); + m->setLabelAlignment(Qt::AlignBottom); + } + +#if QWT_VERSION < 0x060000 + m->setSymbol(*sym); +#else + m->setSymbol(sym); +#endif + QwtText tag_label(s.str().c_str()); + tag_label.setColor(getTagTextColor()); + m->setLabel(tag_label); + + m->attach(this); + + if (!(show && d_tag_markers_en[0])) { + m->hide(); + } + + d_tag_markers[0].push_back(m); + } else { + // Prepend the new tag to the existing marker + // And set it at the max value + if (fabs(yval) < fabs((*mitr)->yValue())) + (*mitr)->setYValue(yval); + QString orig = (*mitr)->label().text(); + s << std::endl; + orig.prepend(s.str().c_str()); + + QwtText newtext(orig); + newtext.setColor(getTagTextColor()); + + QBrush brush(getTagBackgroundColor(), getTagBackgroundStyle()); + newtext.setBackgroundBrush(brush); + + (*mitr)->setLabel(newtext); + } + } + } + + if (d_autoscale_state) { + double bottom = 1e20, top = -1e20; + for (unsigned int i = 0; i < d_numPeriods; ++i) { + for (int64_t point = 0; point < d_numPointsPerPeriod; point++) { + if (d_ydata[i][point] < bottom) { + bottom = d_ydata[i][point]; + } + if (d_ydata[i][point] > top) { + top = d_ydata[i][point]; + } + } + } + _autoScale(bottom, top); + if (d_autoscale_shot) { + d_autoscale_state = false; + d_autoscale_shot = false; + } + } + replot(); + } + } +} + +void EyeDisplayPlot::legendEntryChecked(QwtPlotItem* plotItem, bool on) +{ + // When line is turned on/off, immediately show/hide tag markers + for (size_t i = 0; i < d_tag_markers[0].size(); i++) { + if (!(!on && d_tag_markers_en[0])) + d_tag_markers[0][i]->hide(); + else + d_tag_markers[0][i]->show(); + } + DisplayPlot::legendEntryChecked(plotItem, on); +} + +void EyeDisplayPlot::legendEntryChecked(const QVariant& plotItem, bool on, int index) +{ +#if QWT_VERSION < 0x060100 + std::runtime_error("EyeDisplayPlot::legendEntryChecked with QVariant not " + "enabled in this version of QWT."); +#else + QwtPlotItem* p = infoToItem(plotItem); + legendEntryChecked(p, on); +#endif /* QWT_VERSION < 0x060100 */ +} + +void EyeDisplayPlot::_resetXAxisPoints() +{ + double delt = 1.0 / d_sample_rate; + for (long loc = 0; loc < d_numPointsPerPeriod; loc++) { + d_xdata[loc] = delt * loc; + } + + setAxisScale(QwtPlot::xBottom, 0, 2.0 * delt * d_sps); + // Set up zoomer base for maximum unzoom x-axis + // and reset to maximum unzoom level + QwtDoubleRect zbase = d_zoomer->zoomBase(); + zbase.setLeft(0.0); + zbase.setRight(2.0 * delt * d_sps); + d_zoomer->zoom(zbase); + d_zoomer->setZoomBase(zbase); + d_zoomer->zoom(0); +} + +void EyeDisplayPlot::_autoScale(double bottom, double top) +{ + // Auto scale the y-axis with a margin of 20% (10 dB for log scale) + double _bot = bottom - fabs(bottom) * 0.20; + double _top = top + fabs(top) * 0.20; + setYaxis(_bot, _top); +} + +void EyeDisplayPlot::setAutoScale(bool state) { d_autoscale_state = state; } + +void EyeDisplayPlot::setAutoScaleShot() +{ + d_autoscale_state = true; + d_autoscale_shot = true; +} + +void EyeDisplayPlot::setSampleRate(double sr, double units, const std::string& strunits) +{ + double newsr = sr / units; + if ((newsr != d_sample_rate) || + (((EyeDisplayZoomer*)d_zoomer)->unitType() != strunits)) { + d_sample_rate = sr / units; + _resetXAxisPoints(); + + // While we could change the displayed sigfigs based on the unit being + // displayed, I think it looks better by just setting it to 4 regardless. + // double display_units = ceil(log10(units)/2.0); + double display_units = 4; + QwtText axisTitle(QString("Time (%1)").arg(strunits.c_str())); + axisTitle.setRenderFlags(Qt::AlignRight | Qt::AlignVCenter); + setAxisTitle(QwtPlot::xBottom, axisTitle); + ((EyeDisplayZoomer*)d_zoomer)->setTimePrecision(display_units); + ((EyeDisplayZoomer*)d_zoomer)->setUnitType(strunits); + } +} + +double EyeDisplayPlot::sampleRate() const { return d_sample_rate; } + +void EyeDisplayPlot::stemPlot(bool en) +{ + for (unsigned int i = 0; i < d_nplots; ++i) { + d_plot_curve[i]->setStyle(QwtPlotCurve::Lines); + setLineMarker(i, QwtSymbol::NoSymbol); + } +} + +void EyeDisplayPlot::enableTagMarker(unsigned int which, bool en) +{ + // which always equal 0 + if ((size_t)which < d_tag_markers_en.size()) + d_tag_markers_en[0] = en; + else + throw std::runtime_error( + "TimeDomainDisplayPlot: enabled tag marker does not exist."); +} + +const QColor EyeDisplayPlot::getTagTextColor() { return d_tag_text_color; } + +const QColor EyeDisplayPlot::getTagBackgroundColor() { return d_tag_background_color; } + +const Qt::BrushStyle EyeDisplayPlot::getTagBackgroundStyle() +{ + return d_tag_background_style; +} + +void EyeDisplayPlot::setTagTextColor(QColor c) { d_tag_text_color = c; } + +void EyeDisplayPlot::setTagBackgroundColor(QColor c) { d_tag_background_color = c; } + +void EyeDisplayPlot::setTagBackgroundStyle(Qt::BrushStyle b) +{ + d_tag_background_style = b; +} + +void EyeDisplayPlot::setYLabel(const std::string& label, const std::string& unit) +{ + std::string l = label; + if (unit.length() > 0) + l += " (" + unit + ")"; + QwtText yAxisTitle(QString(l.c_str())); + setAxisTitle(QwtPlot::yLeft, yAxisTitle); + + ((EyeDisplayZoomer*)d_zoomer)->setYUnitType(unit); +} + +void EyeDisplayPlot::attachTriggerLines(bool en) +{ + if (en) { + d_trigger_lines[0]->attach(this); + d_trigger_lines[1]->attach(this); + } else { + d_trigger_lines[0]->detach(); + d_trigger_lines[1]->detach(); + } +} + +void EyeDisplayPlot::setTriggerLines(double x, double y) +{ + d_trigger_lines[0]->setXValue(x); + d_trigger_lines[0]->setYValue(y); + d_trigger_lines[1]->setXValue(x); + d_trigger_lines[1]->setYValue(y); +} + +// DisplayPlot base class methods overriding +void EyeDisplayPlot::setLineLabel(unsigned int which, QString label) +{ + + if (which >= d_plot_curve.size()) + throw std::runtime_error("EyeDisplayPlot::setLineLabel: index out of bounds"); + d_plot_curve[which]->setTitle(label); +} + +// Overriding of method DisplayPlot::setLineColor +void EyeDisplayPlot::setLineColor(unsigned int which, QColor color) +{ + for (unsigned int i = 0; i < d_plot_curve.size(); ++i) { + // Set the color of the pen + QPen pen(d_plot_curve[i]->pen()); + pen.setColor(color); + d_plot_curve[i]->setPen(pen); + // And set the color of the markers +#if QWT_VERSION < 0x060000 + d_plot_curve[i]->setPen(pen); + QwtSymbol sym = (QwtSymbol)d_plot_curve[i]->symbol(); + setLineMarker(i, sym.style()); +#else + QwtSymbol* sym = (QwtSymbol*)d_plot_curve[i]->symbol(); + if (sym) { + sym->setColor(color); + sym->setPen(pen); + d_plot_curve[i]->setSymbol(sym); + } +#endif + } +} + +// Overriding of method DisplayPlot::setLineWidth +void EyeDisplayPlot::setLineWidth(unsigned int which, int width) +{ + for (unsigned int i = 0; i < d_plot_curve.size(); ++i) { + // Set the new line width + QPen pen(d_plot_curve[i]->pen()); + pen.setWidth(width); + d_plot_curve[i]->setPen(pen); + + // Scale the marker size proportionally +#if QWT_VERSION < 0x060000 + QwtSymbol sym = (QwtSymbol)d_plot_curve[i]->symbol(); + sym.setSize(7 + 10 * log10(1.0 * width), 7 + 10 * log10(1.0 * width)); + d_plot_curve[i]->setSymbol(sym); +#else + QwtSymbol* sym = (QwtSymbol*)d_plot_curve[i]->symbol(); + if (sym) { + sym->setSize(7 + 10 * log10(1.0 * i), 7 + 10 * log10(1.0 * i)); + d_plot_curve[i]->setSymbol(sym); + } +#endif + } +} + +// Overriding of method DisplayPlot::setLineMarker +void EyeDisplayPlot::setLineMarker(unsigned int which, QwtSymbol::Style marker) +{ + for (unsigned int i = 0; i < d_plot_curve.size(); ++i) { +#if QWT_VERSION < 0x060000 + QwtSymbol sym = (QwtSymbol)d_plot_curve[i]->symbol(); + QPen pen(d_plot_curve[i]->pen()); + QBrush brush(pen.color()); + sym.setStyle(marker); + sym.setPen(pen); + sym.setBrush(brush); + d_plot_curve[i]->setSymbol(sym); +#else + QwtSymbol* sym = (QwtSymbol*)d_plot_curve[i]->symbol(); + if (sym) { + sym->setStyle(marker); + d_plot_curve[i]->setSymbol(sym); + } +#endif + } +} + +// Overriding of method DisplayPlot::setLineStyle +void EyeDisplayPlot::setLineStyle(unsigned int which, Qt::PenStyle style) +{ + for (unsigned int i = 0; i < d_plot_curve.size(); ++i) { + QPen pen(d_plot_curve[i]->pen()); + pen.setStyle(style); + d_plot_curve[i]->setPen(pen); + } +} + +// Overriding of method DisplayPlot::setMarkerAlpha +void EyeDisplayPlot::setMarkerAlpha(unsigned int which, int alpha) +{ + for (unsigned int i = 0; i < d_plot_curve.size(); ++i) { + // Get the pen color + QPen pen(d_plot_curve[i]->pen()); + QColor color = pen.color(); + + // Set new alpha and update pen + color.setAlpha(alpha); + pen.setColor(color); + d_plot_curve[i]->setPen(pen); + + // And set the new color for the markers +#if QWT_VERSION < 0x060000 + QwtSymbol sym = (QwtSymbol)d_plot_curve[i]->symbol(); + setLineMarker(i, sym.style()); +#else + QwtSymbol* sym = (QwtSymbol*)d_plot_curve[i]->symbol(); + if (sym) { + sym->setColor(color); + sym->setPen(pen); + d_plot_curve[i]->setSymbol(sym); + } +#endif + } +} + +#endif /* EYE_DISPLAY_PLOT_C */ diff --git a/gr-qtgui/lib/eye_sink_c_impl.cc b/gr-qtgui/lib/eye_sink_c_impl.cc new file mode 100644 index 0000000000..ae0ae292c7 --- /dev/null +++ b/gr-qtgui/lib/eye_sink_c_impl.cc @@ -0,0 +1,701 @@ +/* -*- c++ -*- */ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "eye_sink_c_impl.h" + +#include <gnuradio/block_detail.h> +#include <gnuradio/fft/fft.h> +#include <gnuradio/io_signature.h> +#include <gnuradio/prefs.h> + +#include <qwt_symbol.h> +#include <volk/volk.h> + +#include <string.h> +#include <algorithm> + +namespace gr { +namespace qtgui { + +eye_sink_c::sptr eye_sink_c::make(int size, + double samp_rate, + const std::string& name, + unsigned int nconnections, + QWidget* parent) +{ + return gnuradio::get_initial_sptr( + new eye_sink_c_impl(size, samp_rate, name, nconnections, parent)); +} + +eye_sink_c_impl::eye_sink_c_impl(int size, + double samp_rate, + const std::string& name, + unsigned int nconnections, + QWidget* parent) + : sync_block("eye_sink_c", + io_signature::make(0, nconnections, sizeof(gr_complex)), + io_signature::make(0, 0, 0)), + d_size(size), + d_buffer_size(2 * size), + d_samp_rate(samp_rate), + d_name(name), + d_nconnections(2 * nconnections), + d_tag_key(pmt::mp("tags")), + d_parent(parent) +{ + if (nconnections > 12) + throw std::runtime_error("eye_sink_c only supports up to 12 inputs"); + + // Required now for Qt; argc must be greater than 0 and argv + // must have at least one valid character. Must be valid through + // life of the qApplication: + // http://harmattan-dev.nokia.com/docs/library/html/qt4/qapplication.html + d_argc = 1; + d_argv = new char; + d_argv[0] = '\0'; + + d_main_gui = NULL; + + // setup PDU handling input port + message_port_register_in(pmt::mp("in")); + set_msg_handler(pmt::mp("in"), boost::bind(&eye_sink_c_impl::handle_pdus, this, _1)); + + // +2 for the PDU message buffers + for (unsigned int n = 0; n < d_nconnections + 2; n++) { + d_buffers.push_back( + (double*)volk_malloc(d_buffer_size * sizeof(double), volk_get_alignment())); + memset(d_buffers[n], 0, d_buffer_size * sizeof(double)); + } + + // We don't use cbuffers with the PDU message handling capabilities. + for (unsigned int n = 0; n < d_nconnections / 2; n++) { + d_cbuffers.push_back((gr_complex*)volk_malloc(d_buffer_size * sizeof(gr_complex), + volk_get_alignment())); + std::fill_n(d_cbuffers[n], d_buffer_size, 0); + } + + // Set alignment properties for VOLK + const int alignment_multiple = volk_get_alignment() / sizeof(gr_complex); + set_alignment(std::max(1, alignment_multiple)); + + d_tags = std::vector<std::vector<gr::tag_t>>(d_nconnections / 2); + + initialize(); + + d_main_gui->setNPoints(d_size); // setup GUI box with size + set_trigger_mode(gr::qtgui::TRIG_MODE_FREE, gr::qtgui::TRIG_SLOPE_POS, 0, 0, 0); + + set_history(2); // so we can look ahead for the trigger slope + declare_sample_delay(1); // delay the tags for a history of 2 +} + +eye_sink_c_impl::~eye_sink_c_impl() +{ + if (!d_main_gui->isClosed()) + d_main_gui->close(); + + // d_main_gui is a qwidget destroyed with its parent + for (unsigned int n = 0; n < d_nconnections + 2; n++) { + volk_free(d_buffers[n]); + } + for (unsigned int n = 0; n < d_nconnections / 2; n++) { + volk_free(d_cbuffers[n]); + } + + delete d_argv; +} + +bool eye_sink_c_impl::check_topology(int ninputs, int noutputs) +{ + return (unsigned int)(2 * ninputs) == d_nconnections; +} + +void eye_sink_c_impl::initialize() +{ + if (qApp != NULL) { + d_qApplication = qApp; + } else { +#if QT_VERSION >= 0x040500 && QT_VERSION < 0x050000 + std::string style = prefs::singleton()->get_string("qtgui", "style", "raster"); + QApplication::setGraphicsSystem(QString(style.c_str())); +#endif + d_qApplication = new QApplication(d_argc, &d_argv); + } + + // If a style sheet is set in the prefs file, enable it here. + check_set_qss(d_qApplication); + + unsigned int numplots = (d_nconnections > 0) ? d_nconnections : 2; + d_main_gui = new EyeDisplayForm(numplots, true, d_parent); + d_main_gui->setNPoints(d_size); + d_main_gui->setSampleRate(d_samp_rate); + d_main_gui->disableLegend(); + + // initialize update time to 10 times a second + set_update_time(0.1); + // initialize number of samples per symbol to 4 + set_samp_per_symbol(4); +} + +void eye_sink_c_impl::exec_() { d_qApplication->exec(); } + +QWidget* eye_sink_c_impl::qwidget() { return d_main_gui; } + +#ifdef ENABLE_PYTHON +PyObject* eye_sink_c_impl::pyqwidget() +{ + PyObject* w = PyLong_FromVoidPtr((void*)d_main_gui); + PyObject* retarg = Py_BuildValue("N", w); + return retarg; +} +#else +void* eye_sink_c_impl::pyqwidget() { return NULL; } +#endif + +void eye_sink_c_impl::set_y_axis(double min, double max) +{ + d_main_gui->setYaxis(min, max); +} + +void eye_sink_c_impl::set_y_label(const std::string& label, const std::string& unit) +{ + d_main_gui->setYLabel(label, unit); +} + +void eye_sink_c_impl::set_update_time(double t) +{ + // convert update time to ticks + gr::high_res_timer_type tps = gr::high_res_timer_tps(); + d_update_time = t * tps; + d_main_gui->setUpdateTime(t); + d_last_time = 0; +} + +void eye_sink_c_impl::set_samp_per_symbol(unsigned int sps) +{ + d_main_gui->setSamplesPerSymbol(sps); +} + +void eye_sink_c_impl::set_title(const std::string& title) +{ + // set_title no longer used but called by swig +} + + +void eye_sink_c_impl::set_line_label(unsigned int which, const std::string& label) +{ + d_main_gui->setLineLabel(which, label.c_str()); +} + +void eye_sink_c_impl::set_line_color(unsigned int which, const std::string& color) +{ + d_main_gui->setLineColor(which, color.c_str()); +} + +void eye_sink_c_impl::set_line_width(unsigned int which, int width) +{ + d_main_gui->setLineWidth(which, width); +} + +void eye_sink_c_impl::set_line_style(unsigned int which, int style) +{ + d_main_gui->setLineStyle(which, (Qt::PenStyle)style); +} + +void eye_sink_c_impl::set_line_marker(unsigned int which, int marker) +{ + d_main_gui->setLineMarker(which, (QwtSymbol::Style)marker); +} + +void eye_sink_c_impl::set_line_alpha(unsigned int which, double alpha) +{ + d_main_gui->setMarkerAlpha(which, (int)(255.0 * alpha)); +} + +void eye_sink_c_impl::set_trigger_mode(gr::qtgui::trigger_mode mode, + gr::qtgui::trigger_slope slope, + float level, + float delay, + int channel, + const std::string& tag_key) +{ + gr::thread::scoped_lock lock(d_setlock); + + d_trigger_mode = mode; + d_trigger_slope = slope; + d_trigger_level = level; + d_trigger_delay = static_cast<int>(delay * d_samp_rate); + d_trigger_channel = channel; + d_trigger_tag_key = pmt::intern(tag_key); + d_triggered = false; + d_trigger_count = 0; + int d_sps = d_main_gui->getSamplesPerSymbol(); + + if ((d_trigger_delay < 0) || (d_trigger_delay > 2 * d_sps)) { + GR_LOG_WARN( + d_logger, + boost::format("Trigger delay (%1%) outside of display range (0:%2%).") % + (d_trigger_delay / d_samp_rate) % ((2 * d_sps) / d_samp_rate)); + d_trigger_delay = std::max(0, std::min(2 * d_sps, d_trigger_delay)); + delay = d_trigger_delay / d_samp_rate; + } + + d_main_gui->setTriggerMode(d_trigger_mode); + d_main_gui->setTriggerSlope(d_trigger_slope); + d_main_gui->setTriggerLevel(d_trigger_level); + d_main_gui->setTriggerDelay(delay); + d_main_gui->setTriggerChannel(d_trigger_channel); + d_main_gui->setTriggerTagKey(tag_key); + + _reset(); +} + +void eye_sink_c_impl::set_size(int width, int height) +{ + d_main_gui->resize(QSize(width, height)); +} + +std::string eye_sink_c_impl::title() { return d_main_gui->title().toStdString(); } + +std::string eye_sink_c_impl::line_label(unsigned int which) +{ + return d_main_gui->lineLabel(which).toStdString(); +} + +std::string eye_sink_c_impl::line_color(unsigned int which) +{ + return d_main_gui->lineColor(which).toStdString(); +} + +int eye_sink_c_impl::line_width(unsigned int which) +{ + return d_main_gui->lineWidth(which); +} + +int eye_sink_c_impl::line_style(unsigned int which) +{ + return d_main_gui->lineStyle(which); +} + +int eye_sink_c_impl::line_marker(unsigned int which) +{ + return d_main_gui->lineMarker(which); +} + +double eye_sink_c_impl::line_alpha(unsigned int which) +{ + return (double)(d_main_gui->markerAlpha(which)) / 255.0; +} + +void eye_sink_c_impl::set_nsamps(const int newsize) +{ + if (newsize != d_size) { + gr::thread::scoped_lock lock(d_setlock); + + // Set new size and reset buffer index + // (throws away any currently held data, but who cares?) + d_size = newsize; + d_buffer_size = 2 * d_size; + int d_sps = d_main_gui->getSamplesPerSymbol(); + + // Resize buffers and replace data + for (unsigned int n = 0; n < d_nconnections + 2; n++) { + volk_free(d_buffers[n]); + d_buffers[n] = (double*)volk_malloc(d_buffer_size * sizeof(double), + volk_get_alignment()); + memset(d_buffers[n], 0, d_buffer_size * sizeof(double)); + } + + for (unsigned int n = 0; n < d_nconnections / 2; n++) { + volk_free(d_cbuffers[n]); + d_cbuffers[n] = (gr_complex*)volk_malloc(d_buffer_size * sizeof(gr_complex), + volk_get_alignment()); + std::fill_n(d_cbuffers[n], d_buffer_size, 0); + } + + // If delay was set beyond the new boundary, pull it back. + if (d_trigger_delay > 2 * d_sps) { + GR_LOG_WARN(d_logger, + boost::format("Trigger delay (%1%) outside of display range " + "(0:%2%). Moving to 50%% point.") % + (2 * d_sps / d_samp_rate) % ((d_sps) / d_samp_rate)); + d_trigger_delay = d_sps; + d_main_gui->setTriggerDelay(d_trigger_delay / d_samp_rate); + } + + d_main_gui->setNPoints(d_size); + _reset(); + } +} + +void eye_sink_c_impl::set_samp_rate(const double samp_rate) +{ + gr::thread::scoped_lock lock(d_setlock); + d_samp_rate = samp_rate; + d_main_gui->setSampleRate(d_samp_rate); +} + +int eye_sink_c_impl::nsamps() const { return d_size; } + +void eye_sink_c_impl::enable_menu(bool en) { d_main_gui->enableMenu(en); } + +void eye_sink_c_impl::enable_grid(bool en) { d_main_gui->setGrid(en); } + +void eye_sink_c_impl::enable_autoscale(bool en) { d_main_gui->autoScale(en); } + +void eye_sink_c_impl::enable_stem_plot(bool en) +{ + /* Used by parent class, do not remove*/ +} + +void eye_sink_c_impl::enable_semilogx(bool en) +{ + /* Used by parent class, do not remove*/ +} + +void eye_sink_c_impl::enable_semilogy(bool en) +{ + /* Used by parent class, do not remove*/ +} + +void eye_sink_c_impl::enable_control_panel(bool en) +{ + if (en) + d_main_gui->setupControlPanel(); + else + d_main_gui->teardownControlPanel(); +} + +void eye_sink_c_impl::enable_tags(unsigned int which, bool en) +{ + d_main_gui->setTagMenu(which, en); +} + +void eye_sink_c_impl::enable_tags(bool en) +{ + for (unsigned int n = 0; n < d_nconnections; ++n) { + d_main_gui->setTagMenu(n, en); + } +} + +void eye_sink_c_impl::enable_axis_labels(bool en) { d_main_gui->setAxisLabels(en); } + +void eye_sink_c_impl::disable_legend() { d_main_gui->disableLegend(); } + +void eye_sink_c_impl::reset() +{ + gr::thread::scoped_lock lock(d_setlock); + _reset(); +} + +void eye_sink_c_impl::_reset() +{ + unsigned int n; + if (d_trigger_delay) { + for (n = 0; n < d_nconnections / 2; n++) { + // Move the tail of the buffers to the front. This section + // represents data that might have to be plotted again if a + // trigger occurs and we have a trigger delay set. The tail + // section is between (d_end-d_trigger_delay) and d_end. + memmove(d_cbuffers[n], + &d_cbuffers[n][d_end - d_trigger_delay], + d_trigger_delay * sizeof(gr_complex)); + + // Also move the offsets of any tags that occur in the tail + // section so they would be plotted again, too. + std::vector<gr::tag_t> tmp_tags; + for (size_t t = 0; t < d_tags[n].size(); t++) { + if (d_tags[n][t].offset > (uint64_t)(d_size - d_trigger_delay)) { + d_tags[n][t].offset = + d_tags[n][t].offset - (d_size - d_trigger_delay); + tmp_tags.push_back(d_tags[n][t]); + } + } + d_tags[n] = tmp_tags; + } + } + // Otherwise, just clear the local list of tags. + else { + for (n = 0; n < d_nconnections / 2; n++) { + d_tags[n].clear(); + } + } + + // Reset the start and end indices. + d_start = 0; + d_end = d_size; + + // Reset the trigger. If in free running mode, ignore the + // trigger delay and always set trigger to true. + if (d_trigger_mode == gr::qtgui::TRIG_MODE_FREE) { + d_index = 0; + d_triggered = true; + } else { + d_index = d_trigger_delay; + d_triggered = false; + } +} + +void eye_sink_c_impl::_npoints_resize() +{ + int newsize = d_main_gui->getNPoints(); + set_nsamps(newsize); +} + +void eye_sink_c_impl::_adjust_tags(int adj) +{ + for (size_t n = 0; n < d_tags.size(); n++) { + for (size_t t = 0; t < d_tags[n].size(); t++) { + d_tags[n][t].offset += adj; + } + } +} + +void eye_sink_c_impl::_gui_update_trigger() +{ + d_trigger_mode = d_main_gui->getTriggerMode(); + d_trigger_slope = d_main_gui->getTriggerSlope(); + d_trigger_level = d_main_gui->getTriggerLevel(); + d_trigger_channel = d_main_gui->getTriggerChannel(); + d_trigger_count = 0; + int d_sps = d_main_gui->getSamplesPerSymbol(); + + float delayf = d_main_gui->getTriggerDelay(); + int delay = static_cast<int>(delayf * d_samp_rate); + + if (delay != d_trigger_delay) { + // We restrict the delay to be within the window of time being + // plotted. + if ((delay < 0) || (delay > 2 * d_sps)) { + GR_LOG_WARN( + d_logger, + boost::format("Trigger delay (%1%) outside of display range (0:%2%).") % + (delay / d_samp_rate) % ((2 * d_sps) / d_samp_rate)); + delay = std::max(0, std::min(2 * d_sps, delay)); + delayf = delay / d_samp_rate; + } + + d_trigger_delay = delay; + d_main_gui->setTriggerDelay(delayf); + _reset(); + } + + std::string tagkey = d_main_gui->getTriggerTagKey(); + d_trigger_tag_key = pmt::intern(tagkey); +} + +void eye_sink_c_impl::_test_trigger_tags(int nitems) +{ + int trigger_index; + + uint64_t nr = nitems_read(d_trigger_channel / 2); + std::vector<gr::tag_t> tags; + get_tags_in_range( + tags, d_trigger_channel / 2, nr, nr + nitems + 1, d_trigger_tag_key); + if (!tags.empty()) { + trigger_index = tags[0].offset - nr; + int start = d_index + trigger_index - d_trigger_delay - 1; + if (start >= 0) { + d_triggered = true; + d_start = start; + d_end = d_start + d_size; + d_trigger_count = 0; + _adjust_tags(-d_start); + } + } +} + +void eye_sink_c_impl::_test_trigger_norm(int nitems, gr_vector_const_void_star inputs) +{ + int trigger_index; + const gr_complex* in = (const gr_complex*)inputs[d_trigger_channel / 2]; + for (trigger_index = 0; trigger_index < nitems - 1; trigger_index++) { + d_trigger_count++; + + // Test if trigger has occurred based on the input stream, + // channel number, and slope direction + if (_test_trigger_slope(&in[trigger_index])) { + d_triggered = true; + d_start = d_index + trigger_index - d_trigger_delay; + d_end = d_start + d_size; + d_trigger_count = 0; + _adjust_tags(-d_start); + break; + } + } + + // If using auto trigger mode, trigger periodically even + // without a trigger event. + if ((d_trigger_mode == gr::qtgui::TRIG_MODE_AUTO) && (d_trigger_count > d_size)) { + d_triggered = true; + d_trigger_count = 0; + } +} + +bool eye_sink_c_impl::_test_trigger_slope(const gr_complex* in) const +{ + float x0, x1; + if (d_trigger_channel % 2 == 0) { + x0 = in[0].real(); + x1 = in[1].real(); + } else { + x0 = in[0].imag(); + x1 = in[1].imag(); + } + + if (d_trigger_slope == gr::qtgui::TRIG_SLOPE_POS) + return ((x0 <= d_trigger_level) && (x1 > d_trigger_level)); + else + return ((x0 >= d_trigger_level) && (x1 < d_trigger_level)); +} + +int eye_sink_c_impl::work(int noutput_items, + gr_vector_const_void_star& input_items, + gr_vector_void_star& output_items) +{ + unsigned int n = 0; + const gr_complex* in; + + _npoints_resize(); + _gui_update_trigger(); + + gr::thread::scoped_lock lock(d_setlock); + + int nfill = d_end - d_index; // how much room left in buffers + int nitems = std::min(noutput_items, nfill); // num items we can put in buffers + + // If auto, normal, or tag trigger, look for the trigger + if ((d_trigger_mode != gr::qtgui::TRIG_MODE_FREE) && !d_triggered) { + // trigger off a tag key (first one found) + if (d_trigger_mode == gr::qtgui::TRIG_MODE_TAG) { + _test_trigger_tags(nitems); + } + // Normal or Auto trigger + else { + _test_trigger_norm(nitems, input_items); + } + } + + // Copy data into the buffers. + for (n = 0; n < d_nconnections / 2; n++) { + in = (const gr_complex*)input_items[n]; + memcpy(&d_cbuffers[n][d_index], &in[1], nitems * sizeof(gr_complex)); + + uint64_t nr = nitems_read(n); + std::vector<gr::tag_t> tags; + get_tags_in_range(tags, n, nr, nr + nitems); + for (size_t t = 0; t < tags.size(); t++) { + tags[t].offset = tags[t].offset - nr + (d_index - d_start - 1); + } + d_tags[n].insert(d_tags[n].end(), tags.begin(), tags.end()); + } + d_index += nitems; + + // If we've have a trigger and a full d_size of items in the buffers, plot. + if ((d_triggered) && (d_index == d_end)) { + // Copy data to be plotted to start of buffers. + for (n = 0; n < d_nconnections / 2; n++) { + volk_32fc_deinterleave_64f_x2(d_buffers[2 * n + 0], + d_buffers[2 * n + 1], + &d_cbuffers[n][d_start], + d_size); + } + + // Plot if we are able to update + if (gr::high_res_timer_now() - d_last_time > d_update_time) { + d_last_time = gr::high_res_timer_now(); + d_qApplication->postEvent(d_main_gui, + new TimeUpdateEvent(d_buffers, d_size, d_tags)); + } + + // We've plotting, so reset the state + _reset(); + } + + // If we've filled up the buffers but haven't triggered, reset. + if (d_index == d_end) { + _reset(); + } + + return nitems; +} + +void eye_sink_c_impl::handle_pdus(pmt::pmt_t msg) +{ + size_t len; + pmt::pmt_t dict, samples; + std::vector<std::vector<gr::tag_t>> t(1); + + // Test to make sure this is either a PDU or a uniform vector of + // samples. Get the samples PMT and the dictionary if it's a PDU. + // If not, we throw an error and exit. + if (pmt::is_pair(msg)) { + dict = pmt::car(msg); + samples = pmt::cdr(msg); + } else if (pmt::is_uniform_vector(msg)) { + samples = msg; + } else { + throw std::runtime_error("eye_sink_c: message must be either " + "a PDU or a uniform vector of samples."); + } + + // add tag info if it is present in metadata + if (pmt::is_dict(dict)) { + if (pmt::dict_has_key(dict, d_tag_key)) { + d_tags.clear(); + pmt::pmt_t tags = pmt::dict_ref(dict, d_tag_key, pmt::PMT_NIL); + int len = pmt::length(tags); + for (int i = 0; i < len; i++) { + // get tag info from list + pmt::pmt_t tup = pmt::vector_ref(tags, i); + int tagval = pmt::to_long(pmt::tuple_ref(tup, 0)); + pmt::pmt_t k = pmt::tuple_ref(tup, 1); + pmt::pmt_t v = pmt::tuple_ref(tup, 2); + + // add the tag + t[0].push_back(gr::tag_t()); + t[0][t[0].size() - 1].offset = tagval; + t[0][t[0].size() - 1].key = k; + t[0][t[0].size() - 1].value = v; + t[0][t[0].size() - 1].srcid = pmt::PMT_NIL; + } + } + } + + len = pmt::length(samples); + + const gr_complex* in; + if (pmt::is_c32vector(samples)) { + in = (const gr_complex*)pmt::c32vector_elements(samples, len); + } else { + throw std::runtime_error("eye_sink_c: unknown data type " + "of samples; must be complex."); + } + + // Plot if we're past the last update time + if (gr::high_res_timer_now() - d_last_time > d_update_time) { + d_last_time = gr::high_res_timer_now(); + + set_nsamps(len); + + volk_32fc_deinterleave_64f_x2(d_buffers[2 * d_nconnections + 0], + d_buffers[2 * d_nconnections + 1], + in, + len); + + d_qApplication->postEvent(d_main_gui, new TimeUpdateEvent(d_buffers, len, t)); + } +} + +} /* namespace qtgui */ +} /* namespace gr */ diff --git a/gr-qtgui/lib/eye_sink_c_impl.h b/gr-qtgui/lib/eye_sink_c_impl.h new file mode 100644 index 0000000000..1e62587355 --- /dev/null +++ b/gr-qtgui/lib/eye_sink_c_impl.h @@ -0,0 +1,140 @@ +/* -*- c++ -*- */ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef INCLUDED_QTGUI_EYE_SINK_C_IMPL_H +#define INCLUDED_QTGUI_EYE_SINK_C_IMPL_H + +#include <gnuradio/qtgui/eye_sink_c.h> + +#include <gnuradio/high_res_timer.h> +#include <gnuradio/qtgui/eyedisplayform.h> + +namespace gr { +namespace qtgui { + +class QTGUI_API eye_sink_c_impl : public eye_sink_c +{ +private: + void initialize(); + + int d_size, d_buffer_size; + double d_samp_rate; + std::string d_name; + unsigned int d_nconnections; + + const pmt::pmt_t d_tag_key; + + int d_index, d_start, d_end; + std::vector<gr_complex*> d_cbuffers; + std::vector<double*> d_buffers; + std::vector<std::vector<gr::tag_t>> d_tags; + + int d_argc; + char* d_argv; + QWidget* d_parent; + EyeDisplayForm* d_main_gui; + + gr::high_res_timer_type d_update_time; + gr::high_res_timer_type d_last_time; + + // Members used for triggering scope + gr::qtgui::trigger_mode d_trigger_mode; + gr::qtgui::trigger_slope d_trigger_slope; + float d_trigger_level; + int d_trigger_channel; + int d_trigger_delay; + pmt::pmt_t d_trigger_tag_key; + bool d_triggered; + int d_trigger_count; + + void _reset(); + void _npoints_resize(); + void _adjust_tags(int adj); + void _gui_update_trigger(); + void _test_trigger_tags(int nitems); + void _test_trigger_norm(int nitems, gr_vector_const_void_star inputs); + bool _test_trigger_slope(const gr_complex* in) const; + + // Handles message input port for displaying PDU samples. + void handle_pdus(pmt::pmt_t msg); + +public: + eye_sink_c_impl(int size, + double samp_rate, + const std::string& name, + unsigned int nconnections, + QWidget* parent = NULL); + ~eye_sink_c_impl(); + + bool check_topology(int ninputs, int noutputs); + + void exec_(); + QWidget* qwidget(); + +#ifdef ENABLE_PYTHON + PyObject* pyqwidget(); +#else + void* pyqwidget(); +#endif + + void set_y_axis(double min, double max); + void set_y_label(const std::string& label, const std::string& unit = ""); + void set_update_time(double t); + void set_samp_per_symbol(unsigned int sps); + void set_title(const std::string& title); + void set_line_label(unsigned int which, const std::string& label); + void set_line_color(unsigned int which, const std::string& color); + void set_line_width(unsigned int which, int width); + void set_line_style(unsigned int which, int style); + void set_line_marker(unsigned int which, int marker); + void set_nsamps(const int size); + void set_samp_rate(const double samp_rate); + void set_line_alpha(unsigned int which, double alpha); + void set_trigger_mode(gr::qtgui::trigger_mode mode, + gr::qtgui::trigger_slope slope, + float level, + float delay, + int channel, + const std::string& tag_key = ""); + + std::string title(); + std::string line_label(unsigned int which); + std::string line_color(unsigned int which); + int line_width(unsigned int which); + int line_style(unsigned int which); + int line_marker(unsigned int which); + double line_alpha(unsigned int which); + + void set_size(int width, int height); + + int nsamps() const; + + void enable_menu(bool en); + void enable_grid(bool en); + void enable_autoscale(bool en); + void enable_stem_plot(bool en); // Used by parent class, do not remove + void enable_semilogx(bool en); // Used by parent class, do not remove + void enable_semilogy(bool en); // Used by parent class, do not remove + void enable_control_panel(bool en); + void enable_tags(unsigned int which, bool en); + void enable_tags(bool en); + void enable_axis_labels(bool en); + void disable_legend(); + + void reset(); + + int work(int noutput_items, + gr_vector_const_void_star& input_items, + gr_vector_void_star& output_items); +}; + +} // namespace qtgui +} // namespace gr + +#endif /* INCLUDED_QTGUI_EYE_SINK_C_IMPL_H */ diff --git a/gr-qtgui/lib/eye_sink_f_impl.cc b/gr-qtgui/lib/eye_sink_f_impl.cc new file mode 100644 index 0000000000..93998344b6 --- /dev/null +++ b/gr-qtgui/lib/eye_sink_f_impl.cc @@ -0,0 +1,650 @@ +/* -*- c++ -*- */ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "eye_sink_f_impl.h" + +#include <gnuradio/block_detail.h> +#include <gnuradio/buffer.h> +#include <gnuradio/fft/fft.h> +#include <gnuradio/io_signature.h> +#include <gnuradio/prefs.h> + +#include <qwt_symbol.h> +#include <volk/volk.h> + +#include <string.h> + +namespace gr { +namespace qtgui { + +eye_sink_f::sptr eye_sink_f::make(int size, + double samp_rate, + const std::string& name, + unsigned int nconnections, + QWidget* parent) +{ + return gnuradio::get_initial_sptr( + new eye_sink_f_impl(size, samp_rate, name, nconnections, parent)); +} + +eye_sink_f_impl::eye_sink_f_impl(int size, + double samp_rate, + const std::string& name, + unsigned int nconnections, + QWidget* parent) + : sync_block("eye_sink_f", + io_signature::make(0, nconnections, sizeof(float)), + io_signature::make(0, 0, 0)), + d_size(size), + d_buffer_size(2 * size), + d_samp_rate(samp_rate), + d_name(name), + d_nconnections(nconnections), + d_parent(parent) +{ + if (nconnections > 24) + throw std::runtime_error("eye_sink_f only supports up to 24 inputs"); + + // Required now for Qt; argc must be greater than 0 and argv + // must have at least one valid character. Must be valid through + // life of the qApplication: + // http://harmattan-dev.nokia.com/docs/library/html/qt4/qapplication.html + d_argc = 1; + d_argv = new char; + d_argv[0] = '\0'; + + d_main_gui = NULL; + + // setup PDU handling input port + message_port_register_in(pmt::mp("in")); + set_msg_handler(pmt::mp("in"), boost::bind(&eye_sink_f_impl::handle_pdus, this, _1)); + + // +1 for the PDU buffer + for (unsigned int n = 0; n < d_nconnections + 1; n++) { + d_buffers.push_back( + (double*)volk_malloc(d_buffer_size * sizeof(double), volk_get_alignment())); + memset(d_buffers[n], 0, d_buffer_size * sizeof(double)); + + d_fbuffers.push_back( + (float*)volk_malloc(d_buffer_size * sizeof(float), volk_get_alignment())); + memset(d_fbuffers[n], 0, d_buffer_size * sizeof(float)); + } + + // Set alignment properties for VOLK + const int alignment_multiple = volk_get_alignment() / sizeof(float); + set_alignment(std::max(1, alignment_multiple)); + + d_tags = std::vector<std::vector<gr::tag_t>>(d_nconnections); + + initialize(); + + d_main_gui->setNPoints(d_size); // setup GUI box with size + set_trigger_mode(gr::qtgui::TRIG_MODE_FREE, gr::qtgui::TRIG_SLOPE_POS, 0, 0, 0); + + set_history(2); // so we can look ahead for the trigger slope + declare_sample_delay(1); // delay the tags for a history of 2 +} + +eye_sink_f_impl::~eye_sink_f_impl() +{ + if (!d_main_gui->isClosed()) + d_main_gui->close(); + + // d_main_gui is a qwidget destroyed with its parent + for (unsigned int n = 0; n < d_nconnections + 1; n++) { + volk_free(d_buffers[n]); + volk_free(d_fbuffers[n]); + } + + delete d_argv; +} + +bool eye_sink_f_impl::check_topology(int ninputs, int noutputs) +{ + return (unsigned int)(ninputs) == d_nconnections; +} + +void eye_sink_f_impl::initialize() +{ + if (qApp != NULL) { + d_qApplication = qApp; + } else { +#if QT_VERSION >= 0x040500 && QT_VERSION < 0x050000 + std::string style = prefs::singleton()->get_string("qtgui", "style", "raster"); + QApplication::setGraphicsSystem(QString(style.c_str())); +#endif + d_qApplication = new QApplication(d_argc, &d_argv); + } + + // If a style sheet is set in the prefs file, enable it here. + check_set_qss(d_qApplication); + + unsigned int numplots = (d_nconnections > 0) ? d_nconnections : 1; + d_main_gui = new EyeDisplayForm(numplots, false, d_parent); + d_main_gui->setNPoints(d_size); + d_main_gui->setSampleRate(d_samp_rate); + d_main_gui->disableLegend(); + + // initialize update time to 10 times a second + set_update_time(0.1); + // initialize number of samples per symbol to 4 + set_samp_per_symbol(4); +} + +void eye_sink_f_impl::exec_() { d_qApplication->exec(); } + +QWidget* eye_sink_f_impl::qwidget() { return d_main_gui; } + +#ifdef ENABLE_PYTHON +PyObject* eye_sink_f_impl::pyqwidget() +{ + PyObject* w = PyLong_FromVoidPtr((void*)d_main_gui); + PyObject* retarg = Py_BuildValue("N", w); + return retarg; +} +#else +void* eye_sink_f_impl::pyqwidget() { return NULL; } +#endif + +void eye_sink_f_impl::set_y_axis(double min, double max) +{ + d_main_gui->setYaxis(min, max); +} + +void eye_sink_f_impl::set_y_label(const std::string& label, const std::string& unit) +{ + d_main_gui->setYLabel(label, unit); +} + +void eye_sink_f_impl::set_update_time(double t) +{ + // convert update time to ticks + gr::high_res_timer_type tps = gr::high_res_timer_tps(); + d_update_time = t * tps; + d_main_gui->setUpdateTime(t); + d_last_time = 0; +} + +void eye_sink_f_impl::set_samp_per_symbol(unsigned int sps) +{ + d_main_gui->setSamplesPerSymbol(sps); +} + +void eye_sink_f_impl::set_title(const std::string& title) +{ + // set_title no longer used but called by swig +} + +void eye_sink_f_impl::set_line_label(unsigned int which, const std::string& label) +{ + d_main_gui->setLineLabel(which, label.c_str()); +} + +void eye_sink_f_impl::set_line_color(unsigned int which, const std::string& color) +{ + d_main_gui->setLineColor(which, color.c_str()); +} + +void eye_sink_f_impl::set_line_width(unsigned int which, int width) +{ + d_main_gui->setLineWidth(which, width); +} + +void eye_sink_f_impl::set_line_style(unsigned int which, int style) +{ + d_main_gui->setLineStyle(which, (Qt::PenStyle)style); +} + +void eye_sink_f_impl::set_line_marker(unsigned int which, int marker) +{ + d_main_gui->setLineMarker(which, (QwtSymbol::Style)marker); +} + +void eye_sink_f_impl::set_line_alpha(unsigned int which, double alpha) +{ + d_main_gui->setMarkerAlpha(which, (int)(255.0 * alpha)); +} + +void eye_sink_f_impl::set_trigger_mode(gr::qtgui::trigger_mode mode, + gr::qtgui::trigger_slope slope, + float level, + float delay, + int channel, + const std::string& tag_key) +{ + gr::thread::scoped_lock lock(d_setlock); + + d_trigger_mode = mode; + d_trigger_slope = slope; + d_trigger_level = level; + d_trigger_delay = static_cast<int>(delay * d_samp_rate); + d_trigger_channel = channel; + d_trigger_tag_key = pmt::intern(tag_key); + d_triggered = false; + d_trigger_count = 0; + int d_sps = d_main_gui->getSamplesPerSymbol(); + + if ((d_trigger_delay < 0) || (d_trigger_delay > 2 * d_sps)) { + GR_LOG_WARN( + d_logger, + boost::format("Trigger delay (%1%) outside of display range (0:%2%).") % + (d_trigger_delay / d_samp_rate) % ((2 * d_sps) / d_samp_rate)); + d_trigger_delay = std::max(0, std::min(2 * d_sps, d_trigger_delay)); + delay = d_trigger_delay / d_samp_rate; + } + + d_main_gui->setTriggerMode(d_trigger_mode); + d_main_gui->setTriggerSlope(d_trigger_slope); + d_main_gui->setTriggerLevel(d_trigger_level); + d_main_gui->setTriggerDelay(delay); + d_main_gui->setTriggerChannel(d_trigger_channel); + d_main_gui->setTriggerTagKey(tag_key); + + _reset(); +} + +void eye_sink_f_impl::set_size(int width, int height) +{ + d_main_gui->resize(QSize(width, height)); +} + +std::string eye_sink_f_impl::title() { return d_main_gui->title().toStdString(); } + +std::string eye_sink_f_impl::line_label(unsigned int which) +{ + return d_main_gui->lineLabel(0).toStdString(); +} + +std::string eye_sink_f_impl::line_color(unsigned int which) +{ + return d_main_gui->lineColor(0).toStdString(); +} + +int eye_sink_f_impl::line_width(unsigned int which) { return d_main_gui->lineWidth(0); } + +int eye_sink_f_impl::line_style(unsigned int which) { return d_main_gui->lineStyle(0); } + +int eye_sink_f_impl::line_marker(unsigned int which) { return d_main_gui->lineMarker(0); } + +double eye_sink_f_impl::line_alpha(unsigned int which) +{ + return (double)(d_main_gui->markerAlpha(0)) / 255.0; +} + +void eye_sink_f_impl::set_nsamps(const int newsize) +{ + if (newsize != d_size) { + gr::thread::scoped_lock lock(d_setlock); + + // Set new size and reset buffer index + // (throws away any currently held data, but who cares?) + d_size = newsize; + d_buffer_size = 2 * d_size; + int d_sps = d_main_gui->getSamplesPerSymbol(); + + // Resize buffers and replace data + for (unsigned int n = 0; n < d_nconnections + 1; n++) { + volk_free(d_buffers[n]); + d_buffers[n] = (double*)volk_malloc(d_buffer_size * sizeof(double), + volk_get_alignment()); + memset(d_buffers[n], 0, d_buffer_size * sizeof(double)); + + volk_free(d_fbuffers[n]); + d_fbuffers[n] = + (float*)volk_malloc(d_buffer_size * sizeof(float), volk_get_alignment()); + memset(d_fbuffers[n], 0, d_buffer_size * sizeof(float)); + } + + // If delay was set beyond the new boundary, pull it back. + if (d_trigger_delay > 2 * d_sps) { + GR_LOG_WARN(d_logger, + boost::format("Trigger delay (%1%) outside of display range " + "(0:%2%). Moving to 50%% point.") % + (d_trigger_delay / d_samp_rate) % + ((2 * d_sps) / d_samp_rate)); + d_trigger_delay = d_sps; + d_main_gui->setTriggerDelay(d_trigger_delay / d_samp_rate); + } + + d_main_gui->setNPoints(d_size); + _reset(); + } +} + +void eye_sink_f_impl::set_samp_rate(const double samp_rate) +{ + gr::thread::scoped_lock lock(d_setlock); + d_samp_rate = samp_rate; + d_main_gui->setSampleRate(d_samp_rate); +} + +int eye_sink_f_impl::nsamps() const { return d_size; } + +void eye_sink_f_impl::enable_stem_plot(bool en) +{ + /* Used by parent class, do not remove*/ +} + +void eye_sink_f_impl::enable_menu(bool en) { d_main_gui->enableMenu(en); } + +void eye_sink_f_impl::enable_grid(bool en) { d_main_gui->setGrid(en); } + +void eye_sink_f_impl::enable_autoscale(bool en) { d_main_gui->autoScale(en); } + +void eye_sink_f_impl::enable_semilogx(bool en) +{ + /* Used by parent class, do not remove*/ +} + +void eye_sink_f_impl::enable_semilogy(bool en) +{ + /* Used by parent class, do not remove*/ +} + +void eye_sink_f_impl::enable_control_panel(bool en) +{ + if (en) + d_main_gui->setupControlPanel(); + else + d_main_gui->teardownControlPanel(); +} + +void eye_sink_f_impl::enable_tags(unsigned int which, bool en) +{ + d_main_gui->setTagMenu(which, en); +} + +void eye_sink_f_impl::enable_tags(bool en) +{ + for (unsigned int n = 0; n < d_nconnections; ++n) { + d_main_gui->setTagMenu(n, en); + } +} + +void eye_sink_f_impl::enable_axis_labels(bool en) { d_main_gui->setAxisLabels(en); } + +void eye_sink_f_impl::disable_legend() { d_main_gui->disableLegend(); } + +void eye_sink_f_impl::reset() +{ + gr::thread::scoped_lock lock(d_setlock); + _reset(); +} + +void eye_sink_f_impl::_reset() +{ + unsigned int n; + if (d_trigger_delay) { + for (n = 0; n < d_nconnections; n++) { + // Move the tail of the buffers to the front. This section + // represents data that might have to be plotted again if a + // trigger occurs and we have a trigger delay set. The tail + // section is between (d_end-d_trigger_delay) and d_end. + memmove(d_fbuffers[n], + &d_fbuffers[n][d_end - d_trigger_delay], + d_trigger_delay * sizeof(float)); + + // Also move the offsets of any tags that occur in the tail + // section so they would be plotted again, too. + std::vector<gr::tag_t> tmp_tags; + for (size_t t = 0; t < d_tags[n].size(); t++) { + if (d_tags[n][t].offset > (uint64_t)(d_size - d_trigger_delay)) { + d_tags[n][t].offset = + d_tags[n][t].offset - (d_size - d_trigger_delay); + tmp_tags.push_back(d_tags[n][t]); + } + } + d_tags[n] = tmp_tags; + } + } + // Otherwise, just clear the local list of tags. + else { + for (n = 0; n < d_nconnections; n++) { + d_tags[n].clear(); + } + } + + // Reset the start and end indices. + d_start = 0; + d_end = d_size; + + // Reset the trigger. If in free running mode, ignore the + // trigger delay and always set trigger to true. + if (d_trigger_mode == gr::qtgui::TRIG_MODE_FREE) { + d_index = 0; + d_triggered = true; + } else { + d_index = d_trigger_delay; + d_triggered = false; + } +} + +void eye_sink_f_impl::_npoints_resize() +{ + int newsize = d_main_gui->getNPoints(); + set_nsamps(newsize); +} + +void eye_sink_f_impl::_adjust_tags(int adj) +{ + for (size_t n = 0; n < d_tags.size(); n++) { + for (size_t t = 0; t < d_tags[n].size(); t++) { + d_tags[n][t].offset += adj; + } + } +} + +void eye_sink_f_impl::_gui_update_trigger() +{ + d_trigger_mode = d_main_gui->getTriggerMode(); + d_trigger_slope = d_main_gui->getTriggerSlope(); + d_trigger_level = d_main_gui->getTriggerLevel(); + d_trigger_channel = d_main_gui->getTriggerChannel(); + d_trigger_count = 0; + int d_sps = d_main_gui->getSamplesPerSymbol(); + + float delayf = d_main_gui->getTriggerDelay(); + int delay = static_cast<int>(delayf * d_samp_rate); + + if (delay != d_trigger_delay) { + // We restrict the delay to be within the window of time being + // plotted. + if ((delay < 0) || (delay > 2 * d_sps)) { + GR_LOG_WARN( + d_logger, + boost::format("Trigger delay (%1%) outside of display range (0:%2%).") % + (delay / d_samp_rate) % ((2 * d_sps) / d_samp_rate)); + delay = std::max(0, std::min(2 * d_sps, delay)); + delayf = delay / d_samp_rate; + } + + d_trigger_delay = delay; + d_main_gui->setTriggerDelay(delayf); + _reset(); + } + + std::string tagkey = d_main_gui->getTriggerTagKey(); + d_trigger_tag_key = pmt::intern(tagkey); +} + +void eye_sink_f_impl::_test_trigger_tags(int nitems) +{ + int trigger_index; + + uint64_t nr = nitems_read(d_trigger_channel); + std::vector<gr::tag_t> tags; + get_tags_in_range(tags, d_trigger_channel, nr, nr + nitems + 1, d_trigger_tag_key); + if (!tags.empty()) { + trigger_index = tags[0].offset - nr; + int start = d_index + trigger_index - d_trigger_delay - 1; + if (start >= 0) { + d_triggered = true; + d_start = start; + d_end = d_start + d_size; + d_trigger_count = 0; + _adjust_tags(-d_start); + } + } +} + +void eye_sink_f_impl::_test_trigger_norm(int nitems, gr_vector_const_void_star inputs) +{ + int trigger_index; + const float* in = (const float*)inputs[d_trigger_channel]; + for (trigger_index = 0; trigger_index < nitems; trigger_index++) { + d_trigger_count++; + + // Test if trigger has occurred based on the input stream, + // channel number, and slope direction + if (_test_trigger_slope(&in[trigger_index])) { + d_triggered = true; + d_start = d_index + trigger_index - d_trigger_delay; + d_end = d_start + d_size; + d_trigger_count = 0; + _adjust_tags(-d_start); + break; + } + } + + // If using auto trigger mode, trigger periodically even + // without a trigger event. + if ((d_trigger_mode == gr::qtgui::TRIG_MODE_AUTO) && (d_trigger_count > d_size)) { + d_triggered = true; + d_trigger_count = 0; + } +} + +bool eye_sink_f_impl::_test_trigger_slope(const float* in) const +{ + float x0, x1; + x0 = in[0]; + x1 = in[1]; + + if (d_trigger_slope == gr::qtgui::TRIG_SLOPE_POS) + return ((x0 <= d_trigger_level) && (x1 > d_trigger_level)); + else + return ((x0 >= d_trigger_level) && (x1 < d_trigger_level)); +} + +int eye_sink_f_impl::work(int noutput_items, + gr_vector_const_void_star& input_items, + gr_vector_void_star& output_items) +{ + unsigned int n = 0, idx = 0; + const float* in; + + _npoints_resize(); + _gui_update_trigger(); + + gr::thread::scoped_lock lock(d_setlock); + + int nfill = d_end - d_index; // how much room left in buffers + int nitems = std::min(noutput_items, nfill); // num items we can put in buffers + + // If auto, normal, or tag trigger, look for the trigger + if ((d_trigger_mode != gr::qtgui::TRIG_MODE_FREE) && !d_triggered) { + // trigger off a tag key (first one found) + if (d_trigger_mode == gr::qtgui::TRIG_MODE_TAG) { + _test_trigger_tags(nitems); + } + // Normal or Auto trigger + else { + _test_trigger_norm(nitems, input_items); + } + } + + // Copy data into the buffers. + for (n = 0; n < d_nconnections; n++) { + in = (const float*)input_items[idx]; + memcpy(&d_fbuffers[n][d_index], &in[1], nitems * sizeof(float)); + + uint64_t nr = nitems_read(idx); + std::vector<gr::tag_t> tags; + get_tags_in_range(tags, idx, nr, nr + nitems); + for (size_t t = 0; t < tags.size(); t++) { + tags[t].offset = tags[t].offset - nr + (d_index - d_start - 1); + } + d_tags[idx].insert(d_tags[idx].end(), tags.begin(), tags.end()); + idx++; + } + d_index += nitems; + + // If we've have a trigger and a full d_size of items in the buffers, plot. + if ((d_triggered) && (d_index == d_end)) { + // Copy data to be plotted to start of buffers. + for (n = 0; n < d_nconnections; n++) { + volk_32f_convert_64f(d_buffers[n], &d_fbuffers[n][d_start], d_size); + } + + // Plot if we are able to update + if (gr::high_res_timer_now() - d_last_time > d_update_time) { + d_last_time = gr::high_res_timer_now(); + d_qApplication->postEvent(d_main_gui, + new TimeUpdateEvent(d_buffers, d_size, d_tags)); + } + + // We've plotting, so reset the state + _reset(); + } + + // If we've filled up the buffers but haven't triggered, reset. + if (d_index == d_end) { + _reset(); + } + + return nitems; +} + +void eye_sink_f_impl::handle_pdus(pmt::pmt_t msg) +{ + size_t len; + pmt::pmt_t dict, samples; + + // Test to make sure this is either a PDU or a uniform vector of + // samples. Get the samples PMT and the dictionary if it's a PDU. + // If not, we throw an error and exit. + if (pmt::is_pair(msg)) { + dict = pmt::car(msg); + samples = pmt::cdr(msg); + } else if (pmt::is_uniform_vector(msg)) { + samples = msg; + } else { + throw std::runtime_error("eye_sink_c: message must be either " + "a PDU or a uniform vector of samples."); + } + + len = pmt::length(samples); + + const float* in; + if (pmt::is_f32vector(samples)) { + in = (const float*)pmt::f32vector_elements(samples, len); + } else { + throw std::runtime_error("eye_sink_f: unknown data type " + "of samples; must be float."); + } + + // Plot if we're past the last update time + if (gr::high_res_timer_now() - d_last_time > d_update_time) { + d_last_time = gr::high_res_timer_now(); + + set_nsamps(len); + + volk_32f_convert_64f(d_buffers[d_nconnections], in, len); + + std::vector<std::vector<gr::tag_t>> t; + d_qApplication->postEvent(d_main_gui, new TimeUpdateEvent(d_buffers, len, t)); + } +} + +} /* namespace qtgui */ +} /* namespace gr */ diff --git a/gr-qtgui/lib/eye_sink_f_impl.h b/gr-qtgui/lib/eye_sink_f_impl.h new file mode 100644 index 0000000000..f28e569d1f --- /dev/null +++ b/gr-qtgui/lib/eye_sink_f_impl.h @@ -0,0 +1,137 @@ +/* -*- c++ -*- */ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef INCLUDED_QTGUI_EYE_SINK_F_IMPL_H +#define INCLUDED_QTGUI_EYE_SINK_F_IMPL_H + +#include <gnuradio/qtgui/eye_sink_f.h> + +#include <gnuradio/high_res_timer.h> +#include <gnuradio/qtgui/eyedisplayform.h> + +namespace gr { +namespace qtgui { + +class QTGUI_API eye_sink_f_impl : public eye_sink_f +{ +private: + void initialize(); + + int d_size, d_buffer_size; + double d_samp_rate; + std::string d_name; + unsigned int d_nconnections; + + int d_index, d_start, d_end; + std::vector<float*> d_fbuffers; + std::vector<double*> d_buffers; + std::vector<std::vector<gr::tag_t>> d_tags; + + int d_argc; + char* d_argv; + QWidget* d_parent; + EyeDisplayForm* d_main_gui; + gr::high_res_timer_type d_update_time; + gr::high_res_timer_type d_last_time; + + // Members used for triggering scope + gr::qtgui::trigger_mode d_trigger_mode; + gr::qtgui::trigger_slope d_trigger_slope; + float d_trigger_level; + int d_trigger_channel; + int d_trigger_delay; + pmt::pmt_t d_trigger_tag_key; + bool d_triggered; + int d_trigger_count; + + void _reset(); + void _npoints_resize(); + void _adjust_tags(int adj); + void _gui_update_trigger(); + void _test_trigger_tags(int nitems); + void _test_trigger_norm(int nitems, gr_vector_const_void_star inputs); + bool _test_trigger_slope(const float* in) const; + + // Handles message input port for displaying PDU samples. + void handle_pdus(pmt::pmt_t msg); + +public: + eye_sink_f_impl(int size, + double samp_rate, + const std::string& name, + unsigned int nconnections, + QWidget* parent = NULL); + ~eye_sink_f_impl(); + + bool check_topology(int ninputs, int noutputs); + + void exec_(); + QWidget* qwidget(); + +#ifdef ENABLE_PYTHON + PyObject* pyqwidget(); +#else + void* pyqwidget(); +#endif + + void set_y_axis(double min, double max); + void set_y_label(const std::string& label, const std::string& unit = ""); + void set_update_time(double t); + void set_samp_per_symbol(unsigned int sps); + void set_title(const std::string& title); + void set_line_label(unsigned int which, const std::string& label); + void set_line_color(unsigned int which, const std::string& color); + void set_line_width(unsigned int which, int width); + void set_line_style(unsigned int which, int style); + void set_line_marker(unsigned int which, int marker); + void set_nsamps(const int size); + void set_samp_rate(const double samp_rate); + void set_line_alpha(unsigned int which, double alpha); + void set_trigger_mode(gr::qtgui::trigger_mode mode, + gr::qtgui::trigger_slope slope, + float level, + float delay, + int channel, + const std::string& tag_key = ""); + + std::string title(); + std::string line_label(unsigned int which); + std::string line_color(unsigned int which); + int line_width(unsigned int which); + int line_style(unsigned int which); + int line_marker(unsigned int which); + double line_alpha(unsigned int which); + + void set_size(int width, int height); + + int nsamps() const; + + void enable_menu(bool en); + void enable_grid(bool en); + void enable_autoscale(bool en); + void enable_stem_plot(bool en); // Used by parent class, do not remove + void enable_semilogx(bool en); // Used by parent class, do not remove + void enable_semilogy(bool en); // Used by parent class, do not remove + void enable_control_panel(bool en); + void enable_tags(unsigned int which, bool en); + void enable_tags(bool en); + void enable_axis_labels(bool en); + void disable_legend(); + + void reset(); + + int work(int noutput_items, + gr_vector_const_void_star& input_items, + gr_vector_void_star& output_items); +}; + +} // namespace qtgui +} // namespace gr + +#endif /* INCLUDED_QTGUI_EYE_SINK_F_IMPL_H */ diff --git a/gr-qtgui/lib/eyecontrolpanel.cc b/gr-qtgui/lib/eyecontrolpanel.cc new file mode 100644 index 0000000000..190b425bfa --- /dev/null +++ b/gr-qtgui/lib/eyecontrolpanel.cc @@ -0,0 +1,175 @@ +/* -*- c++ -*- */ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <gnuradio/qtgui/eyecontrolpanel.h> + +EyeControlPanel::EyeControlPanel(EyeDisplayForm* form) : QVBoxLayout(), d_parent(form) +{ + // Set up the box for axes items + d_axes_box = new QGroupBox("Axes"); + d_axes_layout = new QVBoxLayout; + d_autoscale_check = new QCheckBox("Autoscale"); + d_grid_check = new QCheckBox("Grid"); + d_axislabels_check = new QCheckBox("Axis Labels"); + d_axislabels_check->setChecked(true); + + d_yoff_layout = new QHBoxLayout; + d_yoff_label = new QLabel("Y Offset:"); + d_yoff_plus = new QPushButton("+"); + d_yoff_minus = new QPushButton("-"); + d_yoff_plus->setMaximumWidth(30); + d_yoff_minus->setMaximumWidth(30); + d_yoff_layout->addWidget(d_yoff_label); + d_yoff_layout->addWidget(d_yoff_plus); + d_yoff_layout->addWidget(d_yoff_minus); + + d_yrange_layout = new QHBoxLayout; + d_yrange_label = new QLabel("Y Range:"); + d_yrange_plus = new QPushButton("+"); + d_yrange_minus = new QPushButton("-"); + d_yrange_plus->setMaximumWidth(30); + d_yrange_minus->setMaximumWidth(30); + d_yrange_layout->addWidget(d_yrange_label); + d_yrange_layout->addWidget(d_yrange_plus); + d_yrange_layout->addWidget(d_yrange_minus); + + // Set up the box for trigger items + d_trigger_box = new QGroupBox("Trigger"); + d_trigger_layout = new QVBoxLayout; + d_trigger_mode_combo = new QComboBox(); + d_trigger_mode_combo->addItem("Free"); + d_trigger_mode_combo->addItem("Auto"); + d_trigger_mode_combo->addItem("Normal"); + d_trigger_mode_combo->addItem("Tag"); + + d_trigger_slope_combo = new QComboBox(); + d_trigger_slope_combo->addItem("Positive"); + d_trigger_slope_combo->addItem("Negative"); + + d_trigger_level_layout = new QHBoxLayout; + d_trigger_level_label = new QLabel("Level:"); + d_trigger_level_plus = new QPushButton("+"); + d_trigger_level_minus = new QPushButton("-"); + d_trigger_level_plus->setMaximumWidth(30); + d_trigger_level_minus->setMaximumWidth(30); + d_trigger_level_layout->addWidget(d_trigger_level_label); + d_trigger_level_layout->addWidget(d_trigger_level_plus); + d_trigger_level_layout->addWidget(d_trigger_level_minus); + + d_trigger_delay_layout = new QHBoxLayout; + d_trigger_delay_label = new QLabel("Delay:"); + d_trigger_delay_plus = new QPushButton("+"); + d_trigger_delay_minus = new QPushButton("-"); + d_trigger_delay_plus->setMaximumWidth(30); + d_trigger_delay_minus->setMaximumWidth(30); + d_trigger_delay_layout->addWidget(d_trigger_delay_label); + d_trigger_delay_layout->addWidget(d_trigger_delay_plus); + d_trigger_delay_layout->addWidget(d_trigger_delay_minus); + + // Set up the box for other items + d_extras_box = new QGroupBox("Extras"); + d_extras_layout = new QVBoxLayout; + d_autoscale_button = new QPushButton("Autoscale"); + d_stop_button = new QPushButton("Stop"); + d_stop_button->setCheckable(true); + + // Set up the boxes into the layout + d_axes_layout->addWidget(d_autoscale_check); + d_axes_layout->addWidget(d_grid_check); + d_axes_layout->addWidget(d_axislabels_check); + d_axes_layout->addLayout(d_yoff_layout); + d_axes_layout->addLayout(d_yrange_layout); + d_axes_box->setLayout(d_axes_layout); + + d_trigger_layout->addWidget(d_trigger_mode_combo); + d_trigger_layout->addWidget(d_trigger_slope_combo); + d_trigger_layout->addLayout(d_trigger_level_layout); + d_trigger_layout->addLayout(d_trigger_delay_layout); + d_trigger_box->setLayout(d_trigger_layout); + + d_extras_layout->addWidget(d_autoscale_button); + d_extras_layout->addWidget(d_stop_button); + d_extras_box->setLayout(d_extras_layout); + + addWidget(d_axes_box); + addWidget(d_trigger_box); + addWidget(d_extras_box); + addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); + + // Connect up the control signals/slots + connect(d_autoscale_check, SIGNAL(clicked(bool)), d_parent, SLOT(autoScale(bool))); + connect(d_grid_check, SIGNAL(clicked(bool)), d_parent, SLOT(setGrid(bool))); + connect( + d_axislabels_check, SIGNAL(clicked(bool)), d_parent, SLOT(setAxisLabels(bool))); + connect(d_yoff_plus, SIGNAL(pressed(void)), d_parent, SLOT(notifyYAxisPlus(void))); + connect(d_yoff_minus, SIGNAL(pressed(void)), d_parent, SLOT(notifyYAxisMinus(void))); + + connect(d_yrange_plus, SIGNAL(pressed(void)), d_parent, SLOT(notifyYRangePlus(void))); + connect( + d_yrange_minus, SIGNAL(pressed(void)), d_parent, SLOT(notifyYRangeMinus(void))); + + connect(d_trigger_mode_combo, + SIGNAL(currentIndexChanged(const QString&)), + d_parent, + SLOT(notifyTriggerMode(const QString&))); + connect(d_trigger_slope_combo, + SIGNAL(currentIndexChanged(const QString&)), + d_parent, + SLOT(notifyTriggerSlope(const QString&))); + connect(d_trigger_level_plus, + SIGNAL(pressed(void)), + d_parent, + SLOT(notifyTriggerLevelPlus())); + connect(d_trigger_level_minus, + SIGNAL(pressed(void)), + d_parent, + SLOT(notifyTriggerLevelMinus())); + connect(d_trigger_delay_plus, + SIGNAL(pressed(void)), + d_parent, + SLOT(notifyTriggerDelayPlus())); + connect(d_trigger_delay_minus, + SIGNAL(pressed(void)), + d_parent, + SLOT(notifyTriggerDelayMinus())); + + connect( + d_autoscale_button, SIGNAL(pressed(void)), d_parent, SLOT(autoScaleShot(void))); + connect(d_stop_button, SIGNAL(pressed(void)), d_parent, SLOT(setStop(void))); + connect( + this, SIGNAL(signalToggleStopButton(void)), d_stop_button, SLOT(toggle(void))); +} + +EyeControlPanel::~EyeControlPanel() +{ + removeWidget(d_axes_box); + removeWidget(d_trigger_box); + removeWidget(d_extras_box); + delete d_axes_box; + delete d_trigger_box; + delete d_extras_box; + + // All other children of the boxes are automatically deleted. +} + +void EyeControlPanel::toggleAutoScale(bool en) { d_autoscale_check->setChecked(en); } + +void EyeControlPanel::toggleGrid(bool en) { d_grid_check->setChecked(en); } + +void EyeControlPanel::toggleTriggerMode(gr::qtgui::trigger_mode mode) +{ + d_trigger_mode_combo->setCurrentIndex(static_cast<int>(mode)); +} + +void EyeControlPanel::toggleTriggerSlope(gr::qtgui::trigger_slope slope) +{ + d_trigger_slope_combo->setCurrentIndex(static_cast<int>(slope)); +} + +void EyeControlPanel::toggleStopButton() { emit signalToggleStopButton(); } diff --git a/gr-qtgui/lib/eyedisplayform.cc b/gr-qtgui/lib/eyedisplayform.cc new file mode 100644 index 0000000000..870dfa1a2c --- /dev/null +++ b/gr-qtgui/lib/eyedisplayform.cc @@ -0,0 +1,660 @@ +/* -*- c++ -*- */ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <gnuradio/qtgui/eyecontrolpanel.h> + +#include <gnuradio/qtgui/eyedisplayform.h> + +#include <QGroupBox> +#include <QMessageBox> +#include <QSpacerItem> + +#include <cmath> +#include <iostream> + +EyeDisplayForm::EyeDisplayForm(int nplots, bool cmplx, QWidget* parent) + : EyeDisplaysForm(nplots, parent) +{ + d_current_units = 1; + + d_trig_mode = gr::qtgui::TRIG_MODE_FREE; + d_trig_slope = gr::qtgui::TRIG_SLOPE_POS; + d_trig_level = 0; + d_trig_delay = 0; + d_trig_channel = 0; + d_trig_tag_key = ""; + + d_int_validator = new QIntValidator(this); + d_int_validator->setBottom(0); + + d_layout = new QGridLayout(this); + d_controlpanel = NULL; + + unsigned int i = 0; + if ((cmplx == true) || ((cmplx == false) && (d_nplots > 3))) { + // Split eye patterns on 2 columns + d_rows = std::floor((d_nplots + 1) / 2); + d_cols = 2; + } else { + d_rows = d_nplots; + d_cols = 1; + } + + // Setup the layout of the display + for (unsigned int row = 0; row < d_rows; ++row) { + for (unsigned int col = 0; col < d_cols; ++col) { + if (i < d_nplots) { + d_displays_plot.push_back(new EyeDisplayPlot(d_nplots, i, this)); + d_layout->addWidget(d_displays_plot[i], row, col); + d_display_plot = d_displays_plot[i]; + ++i; + } + } + } + + for (unsigned int row = 0; row < d_rows; ++row) { + d_layout->setRowStretch(row, 1); + } + + for (unsigned int col = 0; col < d_cols; ++col) { + d_layout->setColumnStretch(col, 1); + } + setLayout(d_layout); + + d_nptsmenu = new NPointsMenu(this); + d_menu->addAction(d_nptsmenu); + connect(d_nptsmenu, SIGNAL(whichTrigger(int)), this, SLOT(setNPoints(const int))); + connect( + this, SIGNAL(signalNPoints(const int)), d_nptsmenu, SLOT(setDiagText(const int))); + + for (unsigned int i = 0; i < d_nplots; ++i) { + d_tagsmenu.push_back(new QAction("Show Tag Makers", this)); + d_tagsmenu[i]->setCheckable(true); + d_tagsmenu[i]->setChecked(true); + connect(d_tagsmenu[i], SIGNAL(triggered(bool)), this, SLOT(tagMenuSlot(bool))); + d_lines_menu[i]->addAction(d_tagsmenu[i]); + } + + // Set up the trigger menu + d_triggermenu = new QMenu("Trigger", this); + d_tr_mode_menu = new TriggerModeMenu(this); + d_tr_slope_menu = new TriggerSlopeMenu(this); + d_tr_level_act = new PopupMenu("Level", this); + d_tr_delay_act = new PopupMenu("Delay", this); + d_tr_channel_menu = new TriggerChannelMenu(nplots, this); + d_tr_tag_key_act = new PopupMenu("Tag Key", this); + d_triggermenu->addMenu(d_tr_mode_menu); + d_triggermenu->addMenu(d_tr_slope_menu); + d_triggermenu->addAction(d_tr_level_act); + d_triggermenu->addAction(d_tr_delay_act); + d_triggermenu->addMenu(d_tr_channel_menu); + d_triggermenu->addAction(d_tr_tag_key_act); + d_menu->addMenu(d_triggermenu); + + d_controlpanelmenu = new QAction("Control Panel", this); + d_controlpanelmenu->setCheckable(true); + d_menu->addAction(d_controlpanelmenu); + connect( + d_controlpanelmenu, SIGNAL(triggered(bool)), this, SLOT(setupControlPanel(bool))); + + setTriggerMode(gr::qtgui::TRIG_MODE_FREE); + setTriggerSlope(gr::qtgui::TRIG_SLOPE_POS); + + connect(d_tr_mode_menu, + SIGNAL(whichTrigger(gr::qtgui::trigger_mode)), + this, + SLOT(setTriggerMode(gr::qtgui::trigger_mode))); + // updates trigger state by calling set level or set tag key. + connect(d_tr_mode_menu, + SIGNAL(whichTrigger(gr::qtgui::trigger_mode)), + this, + SLOT(updateTrigger(gr::qtgui::trigger_mode))); + + connect(d_tr_slope_menu, + SIGNAL(whichTrigger(gr::qtgui::trigger_slope)), + this, + SLOT(setTriggerSlope(gr::qtgui::trigger_slope))); + + setTriggerLevel(0); + connect(d_tr_level_act, + SIGNAL(whichTrigger(QString)), + this, + SLOT(setTriggerLevel(QString))); + connect(this, SIGNAL(signalTriggerLevel(float)), this, SLOT(setTriggerLevel(float))); + + setTriggerDelay(0); + connect(d_tr_delay_act, + SIGNAL(whichTrigger(QString)), + this, + SLOT(setTriggerDelay(QString))); + connect(this, SIGNAL(signalTriggerDelay(float)), this, SLOT(setTriggerDelay(float))); + + setTriggerChannel(0); + connect( + d_tr_channel_menu, SIGNAL(whichTrigger(int)), this, SLOT(setTriggerChannel(int))); + + setTriggerTagKey(std::string("")); + connect(d_tr_tag_key_act, + SIGNAL(whichTrigger(QString)), + this, + SLOT(setTriggerTagKey(QString))); + + Reset(); + + connect(d_display_plot, + SIGNAL(plotPointSelected(const QPointF)), + this, + SLOT(onPlotPointSelected(const QPointF))); + + for (unsigned int i = 0; i < d_nplots; ++i) { + connect(this, SIGNAL(signalReplot()), getSinglePlot(i), SLOT(replot())); + } +} + +EyeDisplayForm::~EyeDisplayForm() +{ + // Qt deletes children when parent is deleted + + // Don't worry about deleting Display Plots - they are deleted when parents are + // deleted + delete d_int_validator; + + teardownControlPanel(); +} + +void EyeDisplayForm::setupControlPanel(bool en) +{ + if (en) { + setupControlPanel(); + } else { + teardownControlPanel(); + } +} + +void EyeDisplayForm::setupControlPanel() +{ + if (d_controlpanel) + delete d_controlpanel; + + // Create the control panel layout + d_controlpanel = new EyeControlPanel(this); + + // Connect action items in menu to controlpanel widgets + connect(d_autoscale_act, + SIGNAL(triggered(bool)), + d_controlpanel, + SLOT(toggleAutoScale(bool))); + connect(d_grid_act, SIGNAL(triggered(bool)), d_controlpanel, SLOT(toggleGrid(bool))); + connect(d_tr_mode_menu, + SIGNAL(whichTrigger(gr::qtgui::trigger_mode)), + d_controlpanel, + SLOT(toggleTriggerMode(gr::qtgui::trigger_mode))); + connect(this, + SIGNAL(signalTriggerMode(gr::qtgui::trigger_mode)), + d_controlpanel, + SLOT(toggleTriggerMode(gr::qtgui::trigger_mode))); + connect(d_tr_slope_menu, + SIGNAL(whichTrigger(gr::qtgui::trigger_slope)), + d_controlpanel, + SLOT(toggleTriggerSlope(gr::qtgui::trigger_slope))); + connect(this, + SIGNAL(signalTriggerSlope(gr::qtgui::trigger_slope)), + d_controlpanel, + SLOT(toggleTriggerSlope(gr::qtgui::trigger_slope))); + connect(d_stop_act, SIGNAL(triggered()), d_controlpanel, SLOT(toggleStopButton())); + + d_layout->addLayout(d_controlpanel, 0, d_cols, d_rows, 1); + + d_controlpanel->toggleAutoScale(d_autoscale_act->isChecked()); + d_controlpanel->toggleGrid(d_grid_act->isChecked()); + d_controlpanel->toggleTriggerMode(getTriggerMode()); + d_controlpanel->toggleTriggerSlope(getTriggerSlope()); + + d_controlpanelmenu->setChecked(true); +} + +void EyeDisplayForm::teardownControlPanel() +{ + if (d_controlpanel) { + d_layout->removeItem(d_controlpanel); + delete d_controlpanel; + d_controlpanel = NULL; + } + d_controlpanelmenu->setChecked(false); +} + +EyeDisplayPlot* EyeDisplayForm::getSinglePlot(unsigned int i) +{ + return ((EyeDisplayPlot*)d_displays_plot[i]); +} + +void EyeDisplayForm::newData(const QEvent* updateEvent) +{ + TimeUpdateEvent* tevent = (TimeUpdateEvent*)updateEvent; + const std::vector<double*> dataPoints = tevent->getTimeDomainPoints(); + const uint64_t numDataPoints = tevent->getNumTimeDomainDataPoints(); + const std::vector<std::vector<gr::tag_t>> tags = tevent->getTags(); + + for (unsigned int i = 0; i < d_nplots; ++i) { + getSinglePlot(i)->plotNewData( + dataPoints, numDataPoints, d_sps, d_update_time, tags); + } +} + +void EyeDisplayForm::customEvent(QEvent* e) +{ + if (e->type() == TimeUpdateEvent::Type()) { + newData(e); + } +} + +void EyeDisplayForm::setSampleRate(const QString& samprate) +{ + setSampleRate(samprate.toDouble()); +} + +void EyeDisplayForm::setSampleRate(const double samprate) +{ + if (samprate > 0) { + std::string strtime[4] = { "sec", "ms", "us", "ns" }; + double units10 = floor(log10(samprate)); + double units3 = std::max(floor(units10 / 3.0), 0.0); + double units = pow(10, (units10 - fmod(units10, 3.0))); + int iunit = static_cast<int>(units3); + + d_current_units = units; + + for (unsigned int i = 0; i < d_nplots; ++i) { + getSinglePlot(i)->setSampleRate(samprate, units, strtime[iunit]); + } + } else { + throw std::runtime_error("EyeDisplayForm: samprate must be > 0."); + } +} + +void EyeDisplayForm::setYaxis(double min, double max) +{ + for (unsigned int i = 0; i < d_nplots; ++i) { + getSinglePlot(i)->setYaxis(min, max); + } +} + +void EyeDisplayForm::setAxisLabels(bool en) +{ + + for (unsigned int i = 0; i < d_nplots; ++i) { + getSinglePlot(i)->setAxisLabels(en); + } +} + +void EyeDisplayForm::setYLabel(const std::string& label, const std::string& unit) +{ + for (unsigned int i = 0; i < d_nplots; ++i) { + getSinglePlot(i)->setYLabel(label, unit); + } +} + +int EyeDisplayForm::getNPoints() const { return d_npoints; } + +int EyeDisplayForm::getSamplesPerSymbol() const { return d_sps; } + +void EyeDisplayForm::setNPoints(const int npoints) +{ + d_npoints = npoints; + emit signalNPoints(npoints); +} + +void EyeDisplayForm::autoScale(bool en) +{ + d_autoscale_state = en; + d_autoscale_act->setChecked(en); + for (unsigned int i = 0; i < d_nplots; ++i) { + getSinglePlot(i)->setAutoScale(d_autoscale_state); + } + emit signalReplot(); +} + +void EyeDisplayForm::autoScaleShot() +{ + for (unsigned int i = 0; i < d_nplots; ++i) { + getSinglePlot(i)->setAutoScaleShot(); + } + emit signalReplot(); +} + +void EyeDisplayForm::setTagMenu(unsigned int which, bool en) +{ + getSinglePlot(which)->enableTagMarker(0, en); + d_tagsmenu[which]->setChecked(en); +} + +void EyeDisplayForm::tagMenuSlot(bool en) +{ + for (size_t i = 0; i < d_tagsmenu.size(); i++) { + getSinglePlot(i)->enableTagMarker(0, d_tagsmenu[i]->isChecked()); + } +} + +/******************************************************************** + * TRIGGER METHODS + *******************************************************************/ + +void EyeDisplayForm::setTriggerMode(gr::qtgui::trigger_mode mode) +{ + d_trig_mode = mode; + d_tr_mode_menu->getAction(mode)->setChecked(true); + + if ((d_trig_mode == gr::qtgui::TRIG_MODE_AUTO) || + (d_trig_mode == gr::qtgui::TRIG_MODE_NORM)) { + getSinglePlot(d_trig_channel)->attachTriggerLines(true); + } else { + getSinglePlot(d_trig_channel)->attachTriggerLines(false); + } + + emit signalReplot(); + emit signalTriggerMode(mode); +} + +void EyeDisplayForm::updateTrigger(gr::qtgui::trigger_mode mode) +{ + // If auto or normal mode, popup trigger level box to set + if ((d_trig_mode == gr::qtgui::TRIG_MODE_AUTO) || + (d_trig_mode == gr::qtgui::TRIG_MODE_NORM)) { + d_tr_level_act->activate(QAction::Trigger); + getSinglePlot(d_trig_channel)->attachTriggerLines(true); + } else { + getSinglePlot(d_trig_channel)->attachTriggerLines(false); + } + + // if tag mode, popup tag key box to set + if ((d_trig_tag_key.empty()) && (d_trig_mode == gr::qtgui::TRIG_MODE_TAG)) + d_tr_tag_key_act->activate(QAction::Trigger); + + emit signalReplot(); + emit signalTriggerMode(mode); +} + +gr::qtgui::trigger_mode EyeDisplayForm::getTriggerMode() const { return d_trig_mode; } + +void EyeDisplayForm::setTriggerSlope(gr::qtgui::trigger_slope slope) +{ + d_trig_slope = slope; + d_tr_slope_menu->getAction(slope)->setChecked(true); + + emit signalReplot(); + emit signalTriggerSlope(slope); +} + +gr::qtgui::trigger_slope EyeDisplayForm::getTriggerSlope() const { return d_trig_slope; } + +void EyeDisplayForm::setTriggerLevel(QString s) +{ + d_trig_level = s.toFloat(); + + if ((d_trig_mode == gr::qtgui::TRIG_MODE_AUTO) || + (d_trig_mode == gr::qtgui::TRIG_MODE_NORM)) { + getSinglePlot(d_trig_channel) + ->setTriggerLines(d_trig_delay * d_current_units, d_trig_level); + } + + emit signalReplot(); +} + +void EyeDisplayForm::setTriggerLevel(float level) +{ + d_trig_level = level; + d_tr_level_act->setText(QString().setNum(d_trig_level)); + + if ((d_trig_mode == gr::qtgui::TRIG_MODE_AUTO) || + (d_trig_mode == gr::qtgui::TRIG_MODE_NORM)) { + getSinglePlot(d_trig_channel) + ->setTriggerLines(d_trig_delay * d_current_units, d_trig_level); + } + + emit signalReplot(); +} + +float EyeDisplayForm::getTriggerLevel() const { return d_trig_level; } + +void EyeDisplayForm::setTriggerDelay(QString s) +{ + d_trig_delay = s.toFloat(); + + if ((d_trig_mode == gr::qtgui::TRIG_MODE_AUTO) || + (d_trig_mode == gr::qtgui::TRIG_MODE_NORM)) { + getSinglePlot(d_trig_channel) + ->setTriggerLines(d_trig_delay * d_current_units, d_trig_level); + } + + emit signalReplot(); +} + +void EyeDisplayForm::setTriggerDelay(float delay) +{ + d_trig_delay = delay; + d_tr_delay_act->setText(QString().setNum(d_trig_delay)); + + if ((d_trig_mode == gr::qtgui::TRIG_MODE_AUTO) || + (d_trig_mode == gr::qtgui::TRIG_MODE_NORM)) { + getSinglePlot(d_trig_channel) + ->setTriggerLines(d_trig_delay * d_current_units, d_trig_level); + } + + emit signalReplot(); +} + +float EyeDisplayForm::getTriggerDelay() const { return d_trig_delay; } + +void EyeDisplayForm::setTriggerChannel(int channel) +{ + getSinglePlot(d_trig_channel)->attachTriggerLines(false); + d_trig_channel = channel; + updateTrigger(d_trig_mode); + d_tr_channel_menu->getAction(d_trig_channel)->setChecked(true); + + emit signalReplot(); +} + +int EyeDisplayForm::getTriggerChannel() const { return d_trig_channel; } + +void EyeDisplayForm::setTriggerTagKey(QString s) +{ + d_trig_tag_key = s.toStdString(); + + emit signalReplot(); +} + +void EyeDisplayForm::setTriggerTagKey(const std::string& key) +{ + d_trig_tag_key = key; + d_tr_tag_key_act->setText(QString().fromStdString(d_trig_tag_key)); + + emit signalReplot(); +} + +std::string EyeDisplayForm::getTriggerTagKey() const { return d_trig_tag_key; } + +/******************************************************************** + * Notification messages from the control panel + *******************************************************************/ + +void EyeDisplayForm::notifyYAxisPlus() +{ + for (unsigned int i = 0; i < d_nplots; ++i) { + +#if QWT_VERSION < 0x060100 + QwtScaleDiv* ax = getSinglePlot(i)->axisScaleDiv(QwtPlot::yLeft); + double range = ax->upperBound() - ax->lowerBound(); + double step = range / 20.0; + getSinglePlot(i)->setYaxis(ax->lowerBound() + step, ax->upperBound() + step); + +#else + + QwtScaleDiv ax = getSinglePlot(i)->axisScaleDiv(QwtPlot::yLeft); + double range = ax.upperBound() - ax.lowerBound(); + double step = range / 20.0; + getSinglePlot(i)->setYaxis(ax.lowerBound() + step, ax.upperBound() + step); +#endif + } +} + +void EyeDisplayForm::notifyYAxisMinus() +{ + for (unsigned int i = 0; i < d_nplots; ++i) { + +#if QWT_VERSION < 0x060100 + QwtScaleDiv* ax = getSinglePlot(i)->axisScaleDiv(QwtPlot::yLeft); + double range = ax->upperBound() - ax->lowerBound(); + double step = range / 20.0; + getSinglePlot(i)->setYaxis(ax->lowerBound() - step, ax->upperBound() - step); + +#else + + QwtScaleDiv ax = getSinglePlot(i)->axisScaleDiv(QwtPlot::yLeft); + double range = ax.upperBound() - ax.lowerBound(); + double step = range / 20.0; + getSinglePlot(i)->setYaxis(ax.lowerBound() - step, ax.upperBound() - step); +#endif + } +} + +void EyeDisplayForm::notifyYRangePlus() +{ + for (unsigned int i = 0; i < d_nplots; ++i) { + +#if QWT_VERSION < 0x060100 + QwtScaleDiv* ax = getSinglePlot(i)->axisScaleDiv(QwtPlot::yLeft); + double range = ax->upperBound() - ax->lowerBound(); + double step = range / 20.0; + getSinglePlot(i)->setYaxis(ax->lowerBound() - step, ax->upperBound() + step); + +#else + + QwtScaleDiv ax = getSinglePlot(i)->axisScaleDiv(QwtPlot::yLeft); + double range = ax.upperBound() - ax.lowerBound(); + double step = range / 20.0; + getSinglePlot(i)->setYaxis(ax.lowerBound() - step, ax.upperBound() + step); +#endif + } +} + +void EyeDisplayForm::notifyYRangeMinus() +{ + for (unsigned int i = 0; i < d_nplots; ++i) { + +#if QWT_VERSION < 0x060100 + QwtScaleDiv* ax = getSinglePlot(i)->axisScaleDiv(QwtPlot::yLeft); + double range = ax->upperBound() - ax->lowerBound(); + double step = range / 20.0; + getSinglePlot(i)->setYaxis(ax->lowerBound() + step, ax->upperBound() - step); + +#else + + QwtScaleDiv ax = getSinglePlot(i)->axisScaleDiv(QwtPlot::yLeft); + double range = ax.upperBound() - ax.lowerBound(); + double step = range / 20.0; + getSinglePlot(i)->setYaxis(ax.lowerBound() + step, ax.upperBound() - step); +#endif + } +} + +void EyeDisplayForm::notifyTriggerMode(const QString& mode) +{ + if (mode == "Free") { + setTriggerMode(gr::qtgui::TRIG_MODE_FREE); + } else if (mode == "Auto") { + setTriggerMode(gr::qtgui::TRIG_MODE_AUTO); + } else if (mode == "Normal") { + setTriggerMode(gr::qtgui::TRIG_MODE_NORM); + } else if (mode == "Tag") { + setTriggerMode(gr::qtgui::TRIG_MODE_TAG); + updateTrigger(gr::qtgui::TRIG_MODE_TAG); + } +} + +void EyeDisplayForm::notifyTriggerSlope(const QString& slope) +{ + if (slope == "Positive") { + setTriggerSlope(gr::qtgui::TRIG_SLOPE_POS); + } else if (slope == "Negative") { + setTriggerSlope(gr::qtgui::TRIG_SLOPE_NEG); + } +} + +void EyeDisplayForm::notifyTriggerLevelPlus() +{ +#if QWT_VERSION < 0x060100 + QwtScaleDiv* ax = getSinglePlot(0)->axisScaleDiv(QwtPlot::yLeft); + double range = ax->upperBound() - ax->lowerBound(); + +#else + + QwtScaleDiv ax = getSinglePlot(0)->axisScaleDiv(QwtPlot::yLeft); + double range = ax.upperBound() - ax.lowerBound(); +#endif + + double step = range / 20.0; + emit signalTriggerLevel(getTriggerLevel() + step); +} + +void EyeDisplayForm::notifyTriggerLevelMinus() +{ +#if QWT_VERSION < 0x060100 + QwtScaleDiv* ax = getSinglePlot(0)->axisScaleDiv(QwtPlot::yLeft); + double range = ax->upperBound() - ax->lowerBound(); + +#else + + QwtScaleDiv ax = getSinglePlot(0)->axisScaleDiv(QwtPlot::yLeft); + double range = ax.upperBound() - ax.lowerBound(); +#endif + + double step = range / 20.0; + emit signalTriggerLevel(getTriggerLevel() - step); +} + +void EyeDisplayForm::notifyTriggerDelayPlus() +{ +#if QWT_VERSION < 0x060100 + QwtScaleDiv* ax = getSinglePlot(0)->axisScaleDiv(QwtPlot::xBottom); + double range = ax->upperBound() - ax->lowerBound(); + +#else + + QwtScaleDiv ax = getSinglePlot(0)->axisScaleDiv(QwtPlot::xBottom); + double range = ax.upperBound() - ax.lowerBound(); +#endif + + double step = range / (2 * d_sps); + double trig = getTriggerDelay() + step / d_current_units; + + if (trig > range / d_current_units) + trig = range / d_current_units; + emit signalTriggerDelay(trig); +} + +void EyeDisplayForm::notifyTriggerDelayMinus() +{ +#if QWT_VERSION < 0x060100 + QwtScaleDiv* ax = getSinglePlot(0)->axisScaleDiv(QwtPlot::xBottom); + double range = ax->upperBound() - ax->lowerBound(); + +#else + + QwtScaleDiv ax = getSinglePlot(0)->axisScaleDiv(QwtPlot::xBottom); + double range = ax.upperBound() - ax.lowerBound(); +#endif + + double step = range / (2 * d_sps); + double trig = getTriggerDelay() - step / d_current_units; + + if (trig < 0) + trig = 0; + emit signalTriggerDelay(trig); +} diff --git a/gr-qtgui/lib/eyedisplaysform.cc b/gr-qtgui/lib/eyedisplaysform.cc new file mode 100644 index 0000000000..04b87a06bb --- /dev/null +++ b/gr-qtgui/lib/eyedisplaysform.cc @@ -0,0 +1,370 @@ +/* -*- c++ -*- */ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <gnuradio/qtgui/eyedisplaysform.h> + +#include <QFileDialog> +#include <QPixmap> +#include <iostream> + +EyeDisplaysForm::EyeDisplaysForm(int nplots, QWidget* parent) + : QWidget(parent), d_nplots(nplots), d_system_specified_flag(false) +{ + d_isclosed = false; + d_axislabels = true; + + // Set the initial plot size + resize(QSize(800, 600)); + + // Set up a grid that can be turned on/off + QPen* gridpen = new QPen(Qt::DashLine); + gridpen->setWidthF(1.0); + gridpen->setColor(Qt::gray); + + // Each eye must have one grid attached + for (unsigned int i = 0; i < d_nplots; ++i) { + d_grids.push_back(new QwtPlotGrid()); + d_grids[i]->setPen(*gridpen); + } + + // Create a set of actions for the menu + d_stop_act = new QAction("Stop", this); + d_stop_act->setStatusTip(tr("Start/Stop")); + connect(d_stop_act, SIGNAL(triggered()), this, SLOT(setStop())); + d_stop_state = false; + + d_grid_act = new QAction("Grid", this); + d_grid_act->setCheckable(true); + d_grid_act->setStatusTip(tr("Toggle Grid on/off")); + connect(d_grid_act, SIGNAL(triggered(bool)), this, SLOT(setGrid(bool))); + d_grid_state = false; + + d_axislabelsmenu = new QAction("Axis Labels", this); + d_axislabelsmenu->setCheckable(true); + d_axislabelsmenu->setStatusTip(tr("Toggle Axis Labels on/off")); + connect(d_axislabelsmenu, SIGNAL(triggered(bool)), this, SLOT(setAxisLabels(bool))); + + // Create a pop-up menu for manipulating the figure + d_menu_on = true; + d_menu = new QMenu(this); + d_menu->addAction(d_stop_act); + d_menu->addAction(d_grid_act); + d_menu->addAction(d_axislabelsmenu); + + for (unsigned int i = 0; i < d_nplots; ++i) { + d_line_title_act.push_back(new LineTitleAction(i, this)); + d_line_color_menu.push_back(new LineColorMenu(i, this)); + d_line_width_menu.push_back(new LineWidthMenu(i, this)); + d_line_style_menu.push_back(new LineStyleMenu(i, this)); + d_line_marker_menu.push_back(new LineMarkerMenu(i, this)); + d_marker_alpha_menu.push_back(new MarkerAlphaMenu(i, this)); + + connect(d_line_title_act[i], + SIGNAL(whichTrigger(unsigned int, const QString&)), + this, + SLOT(setLineLabel(unsigned int, const QString&))); + + for (int j = 0; j < d_line_color_menu[i]->getNumActions(); j++) { + connect(d_line_color_menu[i], + SIGNAL(whichTrigger(unsigned int, const QString&)), + this, + SLOT(setLineColor(unsigned int, const QString&))); + } + + for (int j = 0; j < d_line_width_menu[i]->getNumActions(); j++) { + connect(d_line_width_menu[i], + SIGNAL(whichTrigger(unsigned int, unsigned int)), + this, + SLOT(setLineWidth(unsigned int, unsigned int))); + } + + for (int j = 0; j < d_line_style_menu[i]->getNumActions(); j++) { + connect(d_line_style_menu[i], + SIGNAL(whichTrigger(unsigned int, Qt::PenStyle)), + this, + SLOT(setLineStyle(unsigned int, Qt::PenStyle))); + } + + for (int j = 0; j < d_line_marker_menu[i]->getNumActions(); j++) { + connect(d_line_marker_menu[i], + SIGNAL(whichTrigger(unsigned int, QwtSymbol::Style)), + this, + SLOT(setLineMarker(unsigned int, QwtSymbol::Style))); + } + + for (int j = 0; j < d_marker_alpha_menu[i]->getNumActions(); j++) { + connect(d_marker_alpha_menu[i], + SIGNAL(whichTrigger(unsigned int, unsigned int)), + this, + SLOT(setMarkerAlpha(unsigned int, unsigned int))); + } + + d_lines_menu.push_back(new QMenu(tr(""), this)); + d_lines_menu[i]->addAction(d_line_title_act[i]); + d_lines_menu[i]->addMenu(d_line_color_menu[i]); + d_lines_menu[i]->addMenu(d_line_width_menu[i]); + d_lines_menu[i]->addMenu(d_line_style_menu[i]); + d_lines_menu[i]->addMenu(d_line_marker_menu[i]); + d_lines_menu[i]->addMenu(d_marker_alpha_menu[i]); + d_menu->addMenu(d_lines_menu[i]); + } + + d_samp_rate_act = new PopupMenu("Sample Rate", this); + d_samp_rate_act->setStatusTip(tr("Set Sample Rate")); + connect(d_samp_rate_act, + SIGNAL(whichTrigger(QString)), + this, + SLOT(setSampleRate(QString))); + d_menu->addAction(d_samp_rate_act); + + d_autoscale_act = new QAction("Auto Scale", this); + d_autoscale_act->setStatusTip(tr("Autoscale Plot")); + d_autoscale_act->setCheckable(true); + connect(d_autoscale_act, SIGNAL(triggered(bool)), this, SLOT(autoScale(bool))); + d_autoscale_state = false; + d_menu->addAction(d_autoscale_act); + + d_save_act = new QAction("Save", this); + d_save_act->setStatusTip(tr("Save Figure")); + connect(d_save_act, SIGNAL(triggered()), this, SLOT(saveFigure())); + d_menu->addAction(d_save_act); + + Reset(); +} + +EyeDisplaysForm::~EyeDisplaysForm() +{ + d_isclosed = true; + + // Qt deletes children when parent is deleted + // Don't worry about deleting Display Plots - they are deleted when parents are + // deleted +} + +void EyeDisplaysForm::resizeEvent(QResizeEvent* e) +{ + // not used by eye_sink +} + +void EyeDisplaysForm::mousePressEvent(QMouseEvent* e) +{ + bool ctrloff = Qt::ControlModifier != QApplication::keyboardModifiers(); + if ((e->button() == Qt::MidButton) && ctrloff && (d_menu_on)) { + if (d_stop_state == false) + d_stop_act->setText(tr("Stop")); + else + d_stop_act->setText(tr("Start")); + + // Update the line titles if changed externally + for (unsigned int i = 0; i < d_nplots; ++i) { + d_lines_menu[i]->setTitle(d_displays_plot[i]->title().text()); + } + d_menu->exec(e->globalPos()); + } +} + +void EyeDisplaysForm::updateGuiTimer() { d_display_plot->canvas()->update(); } + +void EyeDisplaysForm::onPlotPointSelected(const QPointF p) +{ + emit plotPointSelected(p, 3); +} + +void EyeDisplaysForm::Reset() {} + +bool EyeDisplaysForm::isClosed() const { return d_isclosed; } + +void EyeDisplaysForm::enableMenu(bool en) { d_menu_on = en; } + +void EyeDisplaysForm::closeEvent(QCloseEvent* e) +{ + d_isclosed = true; + qApp->processEvents(); + QWidget::closeEvent(e); +} + +void EyeDisplaysForm::setUpdateTime(double t) { d_update_time = t; } + +void EyeDisplaysForm::setSamplesPerSymbol(int64_t samples_per_symbol) +{ + d_sps = (int)samples_per_symbol; +} + +void EyeDisplaysForm::setTitle(const QString& title) +{ + /* Used by parent class, do not remove */ +} + +void EyeDisplaysForm::setLineLabel(unsigned int which, const QString& label) +{ + // Line label used as eye pattern title + d_displays_plot[which]->setTitle(label); +} + +void EyeDisplaysForm::setLineColor(unsigned int which, const QString& color) +{ + QColor c = QColor(color); + d_displays_plot[which]->setLineColor(0, c); + d_displays_plot[which]->replot(); +} + +void EyeDisplaysForm::setLineWidth(unsigned int which, unsigned int width) +{ + d_displays_plot[which]->setLineWidth(0, width); + d_displays_plot[which]->replot(); +} + +void EyeDisplaysForm::setLineStyle(unsigned int which, Qt::PenStyle style) +{ + d_displays_plot[which]->setLineStyle(0, style); + d_displays_plot[which]->replot(); +} + +void EyeDisplaysForm::setLineMarker(unsigned int which, QwtSymbol::Style marker) +{ + d_displays_plot[which]->setLineMarker(0, marker); + d_displays_plot[which]->replot(); +} + +void EyeDisplaysForm::setMarkerAlpha(unsigned int which, unsigned int alpha) +{ + d_displays_plot[which]->setMarkerAlpha(0, alpha); + d_displays_plot[which]->replot(); +} + +QString EyeDisplaysForm::title() +{ + /* Title unused by eye_sink */ + return ""; +} + +QString EyeDisplaysForm::lineLabel(unsigned int which) +{ + return d_displays_plot[which]->title().text(); +} + +QString EyeDisplaysForm::lineColor(unsigned int which) +{ + return d_displays_plot[which]->getLineColor(0).name(); +} + +int EyeDisplaysForm::lineWidth(unsigned int which) +{ + return d_displays_plot[which]->getLineWidth(0); +} + +Qt::PenStyle EyeDisplaysForm::lineStyle(unsigned int which) +{ + return d_displays_plot[which]->getLineStyle(0); +} + +QwtSymbol::Style EyeDisplaysForm::lineMarker(unsigned int which) +{ + return d_displays_plot[which]->getLineMarker(0); +} + +int EyeDisplaysForm::markerAlpha(unsigned int which) +{ + return d_displays_plot[which]->getMarkerAlpha(0); +} + +void EyeDisplaysForm::setSampleRate(const QString& rate) {} + +void EyeDisplaysForm::setStop(bool on) +{ + for (unsigned int i = 0; i < d_nplots; ++i) { + if (!on) { + // will auto-detach if already attached. + d_displays_plot[i]->setStop(false); + d_stop_state = false; + } else { + d_displays_plot[i]->setStop(true); + d_stop_state = true; + } + d_displays_plot[i]->replot(); + } +} + +void EyeDisplaysForm::setStop() +{ + if (d_stop_state == false) + setStop(true); + else + setStop(false); +} + +void EyeDisplaysForm::setGrid(bool on) +{ + if (on) { + d_grid_state = true; + } else { + d_grid_state = false; + } + + // create one grid per eye pattern + for (unsigned int i = 0; i < d_nplots; ++i) { + if (on) { + // will auto-detach if already attached. + d_grids[i]->attach(d_displays_plot[i]); + } else { + d_grids[i]->detach(); + } + d_grid_act->setChecked(on); + d_displays_plot[i]->replot(); + } +} + +void EyeDisplaysForm::setAxisLabels(bool en) +{ + d_axislabels = en; + d_axislabelsmenu->setChecked(en); + + for (unsigned int i = 0; i < d_nplots; ++i) { + d_displays_plot[i]->setAxisLabels(d_axislabels); + } +} + +void EyeDisplaysForm::saveFigure() +{ + QPixmap qpix = QPixmap::grabWidget(this); + + QString types = QString(tr("JPEG file (*.jpg);;Portable Network Graphics file " + "(*.png);;Bitmap file (*.bmp);;TIFF file (*.tiff)")); + + QString filename, filetype; + QFileDialog* filebox = new QFileDialog(0, "Save Image", "./", types); + filebox->setViewMode(QFileDialog::Detail); + if (filebox->exec()) { + filename = filebox->selectedFiles()[0]; + filetype = filebox->selectedNameFilter(); + } else { + return; + } + + if (filetype.contains(".jpg")) { + qpix.save(filename, "JPEG"); + } else if (filetype.contains(".png")) { + qpix.save(filename, "PNG"); + } else if (filetype.contains(".bmp")) { + qpix.save(filename, "BMP"); + } else if (filetype.contains(".tiff")) { + qpix.save(filename, "TIFF"); + } else { + qpix.save(filename, "JPEG"); + } + + delete filebox; +} + +void EyeDisplaysForm::disableLegend() +{ + for (unsigned int i = 0; i < d_nplots; ++i) { + d_displays_plot[i]->disableLegend(); + } +} diff --git a/gr-qtgui/python/qtgui/qa_qtgui.py b/gr-qtgui/python/qtgui/qa_qtgui.py index 457b6f8c9f..276077ee69 100644 --- a/gr-qtgui/python/qtgui/qa_qtgui.py +++ b/gr-qtgui/python/qtgui/qa_qtgui.py @@ -69,5 +69,11 @@ class test_qtgui(gr_unittest.TestCase): def test12(self): self.qtsnk = qtgui.histogram_sink_f(1024, 100, -1, 1, "Test", 1) + def test13(self): + self.qtsnk = qtgui.eye_sink_c(1024, 1, "Test", 1) + + def test13(self): + self.qtsnk = qtgui.eye_sink_c(1024, 1, "Test", 1) + if __name__ == '__main__': gr_unittest.run(test_qtgui, "test_qtgui.xml") diff --git a/gr-qtgui/swig/qtgui_swig.i b/gr-qtgui/swig/qtgui_swig.i index 9e3f4bb0f0..37136e309d 100644 --- a/gr-qtgui/swig/qtgui_swig.i +++ b/gr-qtgui/swig/qtgui_swig.i @@ -60,8 +60,11 @@ enum{ #include "gnuradio/qtgui/form_menus.h" #include "gnuradio/qtgui/DisplayPlot.h" #include "gnuradio/qtgui/displayform.h" +#include "gnuradio/qtgui/eyedisplaysform.h" #include "gnuradio/qtgui/sink_c.h" #include "gnuradio/qtgui/sink_f.h" +#include "gnuradio/qtgui/eye_sink_c.h" +#include "gnuradio/qtgui/eye_sink_f.h" #include "gnuradio/qtgui/time_sink_c.h" #include "gnuradio/qtgui/time_sink_f.h" #include "gnuradio/qtgui/time_raster_sink_b.h" @@ -80,6 +83,8 @@ enum{ %include "gnuradio/qtgui/sink_c.h" %include "gnuradio/qtgui/sink_f.h" +%include "gnuradio/qtgui/eye_sink_c.h" +%include "gnuradio/qtgui/eye_sink_f.h" %include "gnuradio/qtgui/time_sink_c.h" %include "gnuradio/qtgui/time_sink_f.h" %include "gnuradio/qtgui/time_raster_sink_b.h" @@ -97,6 +102,8 @@ enum{ GR_SWIG_BLOCK_MAGIC2(qtgui, sink_c); GR_SWIG_BLOCK_MAGIC2(qtgui, sink_f); +GR_SWIG_BLOCK_MAGIC2(qtgui, eye_sink_c); +GR_SWIG_BLOCK_MAGIC2(qtgui, eye_sink_f); GR_SWIG_BLOCK_MAGIC2(qtgui, time_sink_c); GR_SWIG_BLOCK_MAGIC2(qtgui, time_sink_f); GR_SWIG_BLOCK_MAGIC2(qtgui, time_raster_sink_b); |