Files
2026-08-10 19:55:29 +08:00

473 lines
21 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "RadioWidget.h"
#include <QDebug>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <algorithm>
#include <string>
#include <vector>
#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<std::size_t>(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<std::size_t>(layout.content_height));
}
}
QWidget* Radio_Widget::create_spectrogram_power_plot() {
class Spectrum_Plot : public renderive::Latency_Eager_Plot {
public:
std::shared_ptr<renderive::Frequency_Axis> x_axis{};
std::shared_ptr<renderive::Axis> y_axis{};
std::shared_ptr<renderive::Selection_Rectangle_Overlay> msr{};
std::shared_ptr<renderive::Spectrum> 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<renderive::Frequency_Axis> x_axis{};
std::shared_ptr<renderive::Time_Axis> y_axis{};
std::shared_ptr<renderive::Selection_Rectangle_Overlay> msr{};
std::shared_ptr<renderive::Waterfall> 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<renderive::Frequency_Axis> x_axis{};
std::shared_ptr<renderive::Axis> y_axis{};
std::shared_ptr<renderive::Selection_Rectangle_Overlay> msr{};
std::shared_ptr<renderive::Spectrum> 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<renderive::Time_Axis> x_axis{};
std::shared_ptr<renderive::Axis> y_axis{};
std::shared_ptr<renderive::Frequency_Trace> audio{};
std::shared_ptr<renderive::Selection_Rectangle_Overlay> 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("频率表\n1289.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<double> 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<QKeyEvent*>(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);
}