#include "RadioWidget.h" #include #include #include #include #include #include #include "../component/FrequentShower.h" #include "../component/ProgressBar.h" #include "../Global.h" #include "../menu/BaseWidgetMenu.h" #include "BackEnd.h" #include "../BackEnd/MainWidget.h" #include "Core2/export.h" #include "module/radio/Tool.h" static double max = 160; static renderive::Range frequent_range = {-12000, 12000}; static bool radio_mock_perf_mode() { bool ok = false; int value = qEnvironmentVariableIntValue("RADIO_MOCK_PERF_MODE", &ok); return ok ? value != 0 : false; } static bool radio_show_performance_overlay() { bool ok = false; int value = qEnvironmentVariableIntValue("RADIO_SHOW_PERFORMANCE_OVERLAY", &ok); return ok ? value != 0 : false; } static void configure_radio_plot_perf(renderive::Latency_Eager_Plot* plot) { plot->set_max_render_fps(radio_mock_perf_mode() ? 1000 : 1000); } static renderive::Performance_Overlay_Options radio_performance_options() { renderive::Performance_Overlay_Options options; options.style.font.size = 11.0; options.style.background = renderive::Color{20, 20, 20, 255}; options.style.section_color = renderive::Color{80, 160, 255, 255}; options.style.label_color = renderive::Color{190, 196, 205, 255}; options.style.value_color = renderive::Color{238, 220, 138, 255}; options.log.enabled = true; options.log.directory = "performance"; options.log.session_name = "radio_latency"; return options; } static void apply_radio_performance_overlay(renderive::Latency_Eager_Plot* plot, const std::string& plot_name) { static bool logged = false; const bool enabled = radio_show_performance_overlay(); if (!logged) { logged = true; qInfo() << "Performance overlay enabled:" << enabled; } if (!plot || !plot->core()) return; renderive::attach_performance_overlay(*plot->core(), radio_performance_options()); renderive::set_performance_plot_name(*plot->core(), plot_name); renderive::set_performance_overlay_enabled(*plot->core(), enabled); } struct Radio_Plot_Layout { int left_axis_width{}; int bottom_axis_height{}; int content_x{}; int content_y{}; int content_width{}; int content_height{}; }; static Radio_Plot_Layout radio_plot_layout(int width, int height) { Radio_Plot_Layout layout; layout.left_axis_width = std::min(58, std::max(0, width - 1)); layout.bottom_axis_height = std::min(30, std::max(0, height - 1)); layout.content_x = layout.left_axis_width; layout.content_y = 0; layout.content_width = std::max(1, width - layout.left_axis_width); layout.content_height = std::max(1, height - layout.bottom_axis_height); return layout; } static void apply_radio_plot_layout(renderive::Abs_Axis* x_axis, renderive::Abs_Axis* y_axis, int width, int height) { const Radio_Plot_Layout layout = radio_plot_layout(width, height); if (x_axis) { x_axis->set_x(layout.content_x); x_axis->set_y(layout.content_y + layout.content_height); x_axis->set_pixel_length(static_cast(layout.content_width)); } if (y_axis) { y_axis->set_x(layout.content_x); y_axis->set_y(layout.content_y); y_axis->set_pixel_length(static_cast(layout.content_height)); } } QWidget* Radio_Widget::create_spectrogram_power_plot() { class Spectrum_Plot : public renderive::Latency_Eager_Plot { public: std::shared_ptr x_axis{}; std::shared_ptr y_axis{}; std::shared_ptr msr{}; std::shared_ptr sp{}; QMenu* menu{}; void init() override { Latency_Eager_Plot::init(); configure_radio_plot_perf(this); setObjectName("Spectrum_Plot"); auto data = create_renderable_node(root_renderable(), "Data_Renderable"); // auto frequency_axis_cache = create_renderable_node(get_root_renderable(), "Frequency_Axis_Cache"); // auto power_axis_cache = create_renderable_node(get_root_renderable(), "Power_Axis_Cache"); // frequency_axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel); // power_axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel); auto axis_cache = create_renderable_node(root_renderable(), "Power_Axis_Cache"); axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel); auto overlay = create_renderable_node(root_renderable(), "Overlay_Renderable"); x_axis = renderive::Frequency_Axis::Builder(axis_cache, renderive::Orientation::Horizontal) .set_coord_range(frequent_range) .set_tick_length(10) .build(); y_axis = renderive::Axis::Builder(axis_cache, renderive::Orientation::Vertical) .set_coord_range({max, 0}) .set_tick_length(-10) .set_sub_tick_length(-5) .set_unit_text("功率") .build(); sp = renderive::Spectrum::Builder(data, x_axis, y_axis) .set_frequency_range(frequent_range) .set_frequency_point_size(1024) .build(); msr = renderive::Selection_Rectangle_Overlay::Builder(overlay, x_axis, y_axis).build(); } protected: void resizeEvent(QResizeEvent* event) override { apply_radio_plot_layout(x_axis.get(), y_axis.get(), width(), height()); } void contextMenuEvent(QContextMenuEvent* event) override { if (!menu) { auto ws = concat_widgets( bw(cw(sp.get(), -1), "频谱图"), bw(cw(x_axis.get()), "频率x轴"), bw(cw(y_axis.get()), "功率y轴"), bw(cw(msr.get()), "多选框")); menu = create_menu(this, ws); } menu->exec(event->globalPos()); } }; auto w = new Spectrum_Plot; w->init(); apply_radio_performance_overlay(w, "spectrum"); spectrogram_line = w->sp; if (!radio_mock_perf_mode()) { progress_bar1->set_value = [this]() { double middle_frequent = this->spectrogram_line->frequency_range().center(); bool ok = false; double power = spectrogram_line->power_at(middle_frequent, ok); if (ok) this->progress_bar1->setValue(power); }; } return w; } QWidget* Radio_Widget::create_water_fall_plot() { class Water_Fall_Plot : public renderive::Latency_Eager_Plot { public: std::shared_ptr x_axis{}; std::shared_ptr y_axis{}; std::shared_ptr msr{}; std::shared_ptr wf{}; QMenu* menu{}; void init() override { Latency_Eager_Plot::init(); configure_radio_plot_perf(this); setObjectName("Water_Fall_Plot"); auto stream_cache = create_renderable_node(root_renderable(), "Waterfall_Stream_Cache"); auto data = create_renderable_node(stream_cache, "Data_Renderable"); auto time_axis = create_renderable_node(stream_cache, "Time_Axis_Renderable"); auto frequency_axis_cache = create_renderable_node(root_renderable(), "Frequency_Axis_Cache"); auto overlay = create_renderable_node(root_renderable(), "Overlay_Renderable"); stream_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel); frequency_axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel); x_axis = renderive::Frequency_Axis::Builder(frequency_axis_cache, renderive::Orientation::Horizontal) .set_tick_length(10) .set_sub_tick_length(5) .set_coord_range(frequent_range) .build(); y_axis = renderive::Time_Axis::Builder(time_axis, renderive::Orientation::Vertical) .set_tick_length(-10) .set_sub_tick_length(-5) .set_visible_time_point_count(100) .build(); wf = renderive::Waterfall::Builder(data, x_axis, y_axis) .set_frequency_range(frequent_range) .set_frequency_bin_count(1024) .set_power_range({0, max}) .build(); msr = renderive::Selection_Rectangle_Overlay::Builder(overlay, x_axis, y_axis).build(); } protected: void resizeEvent(QResizeEvent* event) override { apply_radio_plot_layout(x_axis.get(), y_axis.get(), width(), height()); } void contextMenuEvent(QContextMenuEvent* event) override { if (!menu) { auto ws = concat_widgets( bw(cw(wf.get(), -1), "瀑布图"), bw(cw(x_axis.get()), "频率轴"), bw(cw(y_axis.get()), "时间轴"), bw(cw(msr.get()), "多选框")); menu = create_menu(this, ws); } menu->exec(event->globalPos()); } }; auto w = new Water_Fall_Plot; w->init(); apply_radio_performance_overlay(w, "waterfall"); water_fall = w->wf; return w; } QWidget* Radio_Widget::create_audio_spectrogram_plot() { class Spectrum_Plot : public renderive::Latency_Eager_Plot { public: std::shared_ptr x_axis{}; std::shared_ptr y_axis{}; std::shared_ptr msr{}; std::shared_ptr sp{}; QMenu* menu{}; void init() override { Latency_Eager_Plot::init(); configure_radio_plot_perf(this); setObjectName("Spectrum_Plot"); auto data = create_renderable_node(root_renderable(), "Data_Renderable"); auto frequency_axis_cache = create_renderable_node(root_renderable(), "Frequency_Axis_Cache"); auto power_axis_cache = create_renderable_node(root_renderable(), "Power_Axis_Cache"); auto overlay = create_renderable_node(root_renderable(), "Overlay_Renderable"); frequency_axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel); power_axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel); x_axis = renderive::Frequency_Axis::Builder(frequency_axis_cache, renderive::Orientation::Horizontal) .set_coord_range({-12000, 12000}) .set_tick_length(10) .set_sub_tick_length(5) .build(); y_axis = renderive::Axis::Builder(power_axis_cache, renderive::Orientation::Vertical) .set_coord_range({12000, -12000}) .set_tick_length(-10) .set_sub_tick_length(-5) .set_unit_text("功率").build(); sp = renderive::Spectrum::Builder(data, x_axis, y_axis) .set_frequency_range({-12000, 12000}) .set_frequency_point_size(512) .build(); msr = renderive::Selection_Rectangle_Overlay::Builder(overlay, x_axis, y_axis).build(); } protected: void resizeEvent(QResizeEvent* event) override { apply_radio_plot_layout(x_axis.get(), y_axis.get(), width(), height()); } void contextMenuEvent(QContextMenuEvent* event) override { if (!menu) { auto ws = concat_widgets( bw(cw(sp.get(), -1), "频谱图"), bw(cw(x_axis.get()), "频率x轴"), bw(cw(y_axis.get()), "功率y轴"), bw(cw(msr.get()), "多选框")); menu = create_menu(this, ws); } menu->exec(event->globalPos()); } }; auto w = new Spectrum_Plot; w->init(); apply_radio_performance_overlay(w, "afterglow"); audio_spectrogram_plot = w->sp; return w; } QWidget* Radio_Widget::create_time_power_plot() { class Audio_Plot : public renderive::Latency_Eager_Plot { public: std::shared_ptr x_axis{}; std::shared_ptr y_axis{}; std::shared_ptr audio{}; std::shared_ptr msr{}; QMenu* menu{}; void init() override { Latency_Eager_Plot::init(); configure_radio_plot_perf(this); setObjectName("Audio_Plot"); auto stream_cache = create_renderable_node(root_renderable(), "Audio_Stream_Cache"); auto data = create_renderable_node(stream_cache, "Data_Renderable"); auto time_axis = create_renderable_node(stream_cache, "Time_Axis_Renderable"); auto power_axis_cache = create_renderable_node(root_renderable(), "Power_Axis_Cache"); auto overlay = create_renderable_node(root_renderable(), "Overlay_Renderable"); stream_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel); power_axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel); x_axis = renderive::Time_Axis::Builder(time_axis, renderive::Orientation::Horizontal) .set_tick_length(10) .set_sub_tick_length(5) .build(); y_axis = renderive::Axis::Builder(power_axis_cache, renderive::Orientation::Vertical) .set_coord_range({12000, -12000}) .set_tick_length(-10) .set_sub_tick_length(-5) .build(); audio = renderive::Frequency_Trace::Builder(data, x_axis, y_axis) .build(); msr = renderive::Selection_Rectangle_Overlay::Builder(overlay, x_axis, y_axis).build(); } protected: void resizeEvent(QResizeEvent* event) override { apply_radio_plot_layout(x_axis.get(), y_axis.get(), width(), height()); x_axis->set_visible_time_point_count(x_axis->pixel_sample_count()); } void contextMenuEvent(QContextMenuEvent* event) override { if (!menu) { auto ws = concat_widgets( bw(cw(audio.get(), -1), "音频图"), bw(cw(y_axis.get()), "功率y轴"), bw(cw(x_axis.get()), "时间轴"), bw(cw(msr.get()), "多选框")); menu = create_menu(this, ws); } menu->exec(event->globalPos()); } }; auto w = new Audio_Plot; w->init(); apply_radio_performance_overlay(w, "frequency_trace"); time_power = w->audio; return w; } //am, atu, vsql, nb, nr, dnf, pre, att, spl, fil1, agc_a Radio_Widget::Radio_Widget(QWidget* parent) : Base_Widget(parent) { setObjectName("电台界面"); voice_value = Back_End::instance()->YsGetAfGain(); rf_gain_value = Back_End::instance()->YsGetRfGain(); int font_size = 20; m1 = create_normal("RF Gain\n" + QString::number(rf_gain_value), font_size); m2 = create_normal("FFT SPAN\n100K", font_size); m3 = create_normal("频率表\n(12)89.6MHZ", font_size); m4 = create_normal("MODEM\nRTTY", font_size); m5 = create_normal("FIL1\n20K", font_size); m6 = create_normal("MSG1\n", font_size); select_value_widget = new Frequent_Shower(this); int n = 5; std::vector tick_list(n); double space = 100.0 / n; for (int i = 0; i < n; ++i) { tick_list[i] = i * space; } QColor orange(255, 165, 0); progress_bar1 = new Progress_Bar({ Tick("S", Qt::black), {"3", Qt::black}, {"6", Qt::black}, {"9", Qt::black}, {"20", Qt::black}, {"40", orange}, {"60", orange}, {"100", orange}, {"+160", Qt::red}, }, this); progress_bar2 = new Progress_Bar({ {"1", Qt::black}, {"2", Qt::yellow}, {"3", Qt::red}, {">5", Qt::red}, }, this); progress_bar1->tick_space_w = 2.0f; auto main = create_vbox_layout({}, this); main->addStretch(1); { // 中心Layout auto center = create_hbox_layout(); main->addLayout(center, 7); auto front = create_vbox_layout(); center->addLayout(front, 1); front->addWidget(m1, 3); front->addStretch(1); front->addWidget(m2, 3); center->addSpacing(4); auto middle = create_vbox_layout(); center->addLayout(middle, 9); middle->addStretch(1); middle->addWidget(select_value_widget, 4); auto middle_h = create_hbox_layout(); middle->addLayout(middle_h, 2); middle_h->addWidget(progress_bar1, 5); middle_h->addStretch(1); middle_h->addWidget(progress_bar2, 3); center->addStretch(4); auto back1 = create_vbox_layout(); center->addLayout(back1, 1); back1->addWidget(m3, 3); back1->addStretch(1); back1->addWidget(m4, 3); center->addSpacing(4); auto back2 = create_vbox_layout(); center->addLayout(back2, 1); back2->addWidget(m5, 3); back2->addStretch(1); back2->addWidget(m6, 3); } main->addStretch(1); auto a_h1 = create_hbox_layout(); a_h1->setSpacing(8); main->addLayout(a_h1, 12); a_h1->addWidget(create_spectrogram_power_plot(), 12); a_h1->addWidget(create_time_power_plot(), 4); main->addSpacing(8); auto a_h2 = create_hbox_layout(); a_h2->setSpacing(8); main->addLayout(a_h2, 12); a_h2->addWidget(create_water_fall_plot(), 12); a_h2->addWidget(create_audio_spectrogram_plot(), 4); init_menu(); } Radio_Widget::~Radio_Widget() { Back_End::instance()->stop_mock_thread(); } void Radio_Widget::init_menu() { int font_size = 30; auto btn1_1 = new Menu_Button("模式设置", font_size, []() {}); auto btn1_2 = new Value_Menu_Button("发射功率:%1 W", font_size, [&](Value_Menu_Button* that, bool add) { that->setValue(that->value + (double)(add ? 1 : -1)); }, -1); auto btn2_1 = new Value_Menu_Button("参考增益:%1", font_size, [&](Value_Menu_Button* that, bool add) { that->setValue(that->value + (double)(add ? 1 : -1)); }, -1); base_widget_menu->add_menu_button_on_layout1({btn1_1, btn1_2, {btn2_1},}); //base_widget_menu->add_menu_button_on_layout2( ); Base_Widget_Menu* sub1 = base_widget_menu->create_sub_widget_menu(btn1_1); auto s1_1 = new Menu_Button("AM", font_size, []() {}); auto s1_2 = new Menu_Button("FM", font_size, []() {}); auto s1_3 = new Menu_Button("SSB", font_size, []() {}); auto s1_4 = new Menu_Button("CW", font_size, []() {}); sub1->add_menu_button_on_layout1({s1_1, s1_2, s1_3, s1_4}, 1); } void Radio_Widget::focusInEvent(QFocusEvent* event) { QWidget::focusInEvent(event); select_value_widget->setFocus(); } void Radio_Widget::keyPressEvent(QKeyEvent* event) { if (event->type() == QEvent::KeyPress) { Back_End* g = Back_End::instance(); auto* key_event = dynamic_cast(event); if (g->match(key_event, "切换音量/RF Gain")) { if (m1->text().startsWith("RF Gain")) { m1->setText("Voice\n" + QString::number(voice_value)); } else { m1->setText("RF Gain\n" + QString::number(rf_gain_value)); } } if (g->match(key_event, "特别增加")) { if (m1->text().startsWith("RF Gain")) { rf_gain_value++; Back_End::instance()->YsSetRfGain(rf_gain_value); m1->setText("RF Gain\n" + QString::number(rf_gain_value)); } else { voice_value++; Back_End::instance()->YsSetAfGain(voice_value); m1->setText("Voice\n" + QString::number(voice_value)); } } if (g->match(key_event, "特别减小")) { if (m1->text().startsWith("RF Gain")) { rf_gain_value--; Back_End::instance()->YsSetRfGain(rf_gain_value); m1->setText("RF Gain\n" + QString::number(rf_gain_value)); } else { voice_value--; Back_End::instance()->YsSetAfGain(voice_value); m1->setText("Voice\n" + QString::number(voice_value)); } } } Base_Widget::keyPressEvent(event); }