- 改用clicked信号代替toggled,避免程序改动误触发 - 初始默认不勾选(setChecked(false)) - 勾选框样式优化:勾上蓝底,不勾灰色 - 勾选立即update()刷新图例+曲线
313 lines
9.8 KiB
C++
313 lines
9.8 KiB
C++
#include "mainwindow.h"
|
|
#include "plotwidget.h"
|
|
#include "udpreceiver.h"
|
|
#include "datamanager.h"
|
|
|
|
#include <QApplication>
|
|
#include <QStatusBar>
|
|
#include <QSplitter>
|
|
#include <QScrollArea>
|
|
#include <QDebug>
|
|
#include <QDir>
|
|
|
|
MainWindow::MainWindow(QWidget *parent)
|
|
: QMainWindow(parent)
|
|
, m_udpReceiver(new UdpReceiver(this))
|
|
, m_dataManager(new DataManager(this))
|
|
, m_isReceiving(false)
|
|
, m_packetCount(0)
|
|
{
|
|
setWindowTitle("AiAnalysis - AI数据实时分析");
|
|
setupUi();
|
|
|
|
// UDP数据接收
|
|
connect(m_udpReceiver, &UdpReceiver::dataReceived, this, &MainWindow::onDataReceived);
|
|
connect(m_udpReceiver, &UdpReceiver::errorOccurred, this, [this](const QString &err) {
|
|
QMessageBox::warning(this, "错误", err);
|
|
});
|
|
}
|
|
|
|
MainWindow::~MainWindow()
|
|
{
|
|
stopReceiving();
|
|
}
|
|
|
|
void MainWindow::setupUi()
|
|
{
|
|
QWidget *centralWidget = new QWidget(this);
|
|
setCentralWidget(centralWidget);
|
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);
|
|
mainLayout->setContentsMargins(8, 8, 8, 8);
|
|
mainLayout->setSpacing(6);
|
|
|
|
// ========== 顶部控制栏 ==========
|
|
QHBoxLayout *controlLayout = new QHBoxLayout();
|
|
|
|
QLabel *portLabel = new QLabel("UDP端口:");
|
|
portLabel->setStyleSheet("color: #ccc; font-size: 13px;");
|
|
|
|
m_portEdit = new QLineEdit();
|
|
m_portEdit->setPlaceholderText("输入端口号");
|
|
m_portEdit->setMaximumWidth(120);
|
|
m_portEdit->setStyleSheet(
|
|
"QLineEdit { background: #2a2a3e; color: #fff; border: 1px solid #555; "
|
|
"border-radius: 4px; padding: 4px 8px; font-size: 13px; }"
|
|
);
|
|
|
|
m_startStopBtn = new QPushButton("开始监听");
|
|
m_startStopBtn->setStyleSheet(
|
|
"QPushButton { background: #2ecc71; color: #fff; border: none; "
|
|
"border-radius: 4px; padding: 6px 20px; font-size: 13px; font-weight: bold; }"
|
|
"QPushButton:hover { background: #27ae60; }"
|
|
);
|
|
connect(m_startStopBtn, &QPushButton::clicked, this, &MainWindow::onStartStop);
|
|
|
|
m_openFileBtn = new QPushButton("打开文件");
|
|
m_openFileBtn->setStyleSheet(
|
|
"QPushButton { background: #3498db; color: #fff; border: none; "
|
|
"border-radius: 4px; padding: 6px 16px; font-size: 13px; }"
|
|
"QPushButton:hover { background: #2980b9; }"
|
|
);
|
|
connect(m_openFileBtn, &QPushButton::clicked, this, &MainWindow::onOpenFile);
|
|
|
|
m_clearBtn = new QPushButton("清除");
|
|
m_clearBtn->setStyleSheet(
|
|
"QPushButton { background: #e74c3c; color: #fff; border: none; "
|
|
"border-radius: 4px; padding: 6px 16px; font-size: 13px; }"
|
|
"QPushButton:hover { background: #c0392b; }"
|
|
);
|
|
connect(m_clearBtn, &QPushButton::clicked, this, &MainWindow::onClear);
|
|
|
|
controlLayout->addWidget(portLabel);
|
|
controlLayout->addWidget(m_portEdit);
|
|
controlLayout->addWidget(m_startStopBtn);
|
|
controlLayout->addSpacing(20);
|
|
controlLayout->addWidget(m_openFileBtn);
|
|
controlLayout->addWidget(m_clearBtn);
|
|
controlLayout->addStretch();
|
|
|
|
mainLayout->addLayout(controlLayout);
|
|
|
|
// ========== 中间区域: 绘图 + 通道选择 ==========
|
|
QHBoxLayout *centerLayout = new QHBoxLayout();
|
|
|
|
// 绘图区域
|
|
m_plotWidget = new PlotWidget(this);
|
|
m_plotWidget->setTitle("AI 数据实时波形");
|
|
centerLayout->addWidget(m_plotWidget, 1);
|
|
|
|
// 右侧通道选择面板
|
|
QGroupBox *channelGroup = new QGroupBox("通道选择");
|
|
channelGroup->setStyleSheet(
|
|
"QGroupBox { color: #ccc; border: 1px solid #444; border-radius: 4px; "
|
|
"margin-top: 12px; padding-top: 16px; font-size: 12px; }"
|
|
"QGroupBox::title { subcontrol-origin: margin; left: 10px; }"
|
|
);
|
|
|
|
QVBoxLayout *channelLayout = new QVBoxLayout(channelGroup);
|
|
channelLayout->setSpacing(2);
|
|
|
|
for (int i = 0; i < 16; ++i) {
|
|
QCheckBox *check = new QCheckBox(QString("CH%1").arg(i+1));
|
|
check->setChecked(false);
|
|
check->setStyleSheet(
|
|
"QCheckBox { color: #aaa; font-size: 11px; spacing: 4px; }"
|
|
"QCheckBox::indicator { width: 14px; height: 14px; }"
|
|
"QCheckBox::indicator:checked { background-color: #3498db; border: 1px solid #2980b9; }"
|
|
"QCheckBox::indicator:unchecked { background-color: #333; border: 1px solid #555; }"
|
|
);
|
|
connect(check, &QCheckBox::clicked, this, [this, i](bool checked) {
|
|
m_plotWidget->setChannelVisible(i, checked);
|
|
m_plotWidget->update();
|
|
});
|
|
channelLayout->addWidget(check);
|
|
m_channelChecks.append(check);
|
|
}
|
|
|
|
channelLayout->addStretch();
|
|
|
|
QScrollArea *scrollArea = new QScrollArea();
|
|
scrollArea->setWidget(channelGroup);
|
|
scrollArea->setWidgetResizable(true);
|
|
scrollArea->setMaximumWidth(120);
|
|
scrollArea->setStyleSheet("QScrollArea { border: none; background: transparent; }");
|
|
|
|
centerLayout->addWidget(scrollArea);
|
|
mainLayout->addLayout(centerLayout, 1);
|
|
|
|
// ========== 底部状态栏 ==========
|
|
QHBoxLayout *statusLayout = new QHBoxLayout();
|
|
|
|
m_statusLabel = new QLabel("就绪 - 等待开始监听");
|
|
m_statusLabel->setStyleSheet("color: #888; font-size: 12px;");
|
|
|
|
m_packetLabel = new QLabel("收到: 0 包");
|
|
m_packetLabel->setStyleSheet("color: #888; font-size: 12px;");
|
|
|
|
QLabel *hintLabel = new QLabel("提示: 滚轮缩放 | 右键拖拽平移 | 双击恢复自动范围");
|
|
hintLabel->setStyleSheet("color: #555; font-size: 11px;");
|
|
|
|
statusLayout->addWidget(m_statusLabel);
|
|
statusLayout->addStretch();
|
|
statusLayout->addWidget(m_packetLabel);
|
|
statusLayout->addSpacing(20);
|
|
statusLayout->addWidget(hintLabel);
|
|
|
|
mainLayout->addLayout(statusLayout);
|
|
|
|
// 全局样式
|
|
setStyleSheet("QMainWindow { background-color: #1a1a2e; }");
|
|
}
|
|
|
|
void MainWindow::onStartStop()
|
|
{
|
|
if (m_isReceiving) {
|
|
stopReceiving();
|
|
} else {
|
|
startReceiving();
|
|
}
|
|
}
|
|
|
|
void MainWindow::startReceiving()
|
|
{
|
|
bool ok;
|
|
quint16 port = m_portEdit->text().toUShort(&ok);
|
|
if (!ok || port == 0) {
|
|
QMessageBox::warning(this, "错误", "请输入有效的端口号 (1-65535)");
|
|
return;
|
|
}
|
|
|
|
if (!m_udpReceiver->start(port)) {
|
|
return;
|
|
}
|
|
|
|
// 清除旧数据
|
|
m_plotWidget->clear();
|
|
m_packetCount = 0;
|
|
m_packetLabel->setText("收到: 0 包");
|
|
|
|
// 创建数据保存目录
|
|
m_dataDir = QApplication::applicationDirPath() + "/data_" + QString::number(port);
|
|
m_dataManager->startRecording(m_dataDir);
|
|
|
|
m_isReceiving = true;
|
|
m_portEdit->setEnabled(false);
|
|
m_startStopBtn->setText("停止监听");
|
|
m_startStopBtn->setStyleSheet(
|
|
"QPushButton { background: #e74c3c; color: #fff; border: none; "
|
|
"border-radius: 4px; padding: 6px 20px; font-size: 13px; font-weight: bold; }"
|
|
"QPushButton:hover { background: #c0392b; }"
|
|
);
|
|
|
|
m_statusLabel->setText(QString("监听中 - 端口 %1 | 保存至 %2").arg(port).arg(m_dataDir));
|
|
m_statusLabel->setStyleSheet("color: #2ecc71; font-size: 12px;");
|
|
}
|
|
|
|
void MainWindow::stopReceiving()
|
|
{
|
|
m_udpReceiver->stop();
|
|
m_dataManager->stopRecording();
|
|
|
|
m_isReceiving = false;
|
|
m_portEdit->setEnabled(true);
|
|
m_startStopBtn->setText("开始监听");
|
|
m_startStopBtn->setStyleSheet(
|
|
"QPushButton { background: #2ecc71; color: #fff; border: none; "
|
|
"border-radius: 4px; padding: 6px 20px; font-size: 13px; font-weight: bold; }"
|
|
"QPushButton:hover { background: #27ae60; }"
|
|
);
|
|
|
|
m_statusLabel->setText(QString("已停止 - 共收到 %1 包").arg(m_packetCount));
|
|
m_statusLabel->setStyleSheet("color: #e74c3c; font-size: 12px;");
|
|
}
|
|
|
|
void MainWindow::onOpenFile()
|
|
{
|
|
QString filePath = QFileDialog::getOpenFileName(
|
|
this, "打开数据文件", m_dataDir, "CSV文件 (*.csv);;所有文件 (*)");
|
|
|
|
if (filePath.isEmpty()) return;
|
|
|
|
stopReceiving();
|
|
loadAndDisplayFile(filePath);
|
|
}
|
|
|
|
void MainWindow::loadAndDisplayFile(const QString &filePath)
|
|
{
|
|
QString error;
|
|
auto channels = DataManager::loadFile(filePath, error);
|
|
|
|
if (!error.isEmpty() && channels[0].isEmpty()) {
|
|
QMessageBox::warning(this, "错误", error);
|
|
return;
|
|
}
|
|
|
|
m_plotWidget->setAllData(channels);
|
|
m_plotWidget->setTitle(QString("回放: %1").arg(QFileInfo(filePath).fileName()));
|
|
|
|
m_statusLabel->setText(QString("回放模式 - %1 | %2 个数据点")
|
|
.arg(QFileInfo(filePath).fileName())
|
|
.arg(m_plotWidget->totalPoints()));
|
|
m_statusLabel->setStyleSheet("color: #3498db; font-size: 12px;");
|
|
|
|
m_packetLabel->setText(QString("已加载: %1 点").arg(m_plotWidget->totalPoints()));
|
|
}
|
|
|
|
void MainWindow::onClear()
|
|
{
|
|
m_plotWidget->clear();
|
|
m_packetCount = 0;
|
|
m_packetLabel->setText("收到: 0 包");
|
|
m_statusLabel->setText("已清除");
|
|
m_statusLabel->setStyleSheet("color: #888; font-size: 12px;");
|
|
}
|
|
|
|
void MainWindow::onDataReceived(const QByteArray &data)
|
|
{
|
|
QString raw = QString::fromUtf8(data).trimmed();
|
|
if (raw.isEmpty()) return;
|
|
|
|
// 可能一个UDP包包含多行
|
|
QStringList lines = raw.split('\n', QString::SkipEmptyParts);
|
|
for (const QString &line : lines) {
|
|
processDataLine(line.trimmed());
|
|
}
|
|
}
|
|
|
|
void MainWindow::processDataLine(const QString &line)
|
|
{
|
|
QStringList parts = line.split(',');
|
|
if (parts.size() < 16) return;
|
|
|
|
QVector<int> values(16);
|
|
bool allOk = true;
|
|
for (int i = 0; i < 16; ++i) {
|
|
bool ok;
|
|
values[i] = parts[i].toInt(&ok);
|
|
if (!ok) {
|
|
allOk = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!allOk) return;
|
|
|
|
// 转换为double给绘图
|
|
QVector<double> dvalues(16);
|
|
for (int i = 0; i < 16; ++i) {
|
|
dvalues[i] = static_cast<double>(values[i]);
|
|
}
|
|
|
|
m_plotWidget->addDataPoint(dvalues);
|
|
m_dataManager->appendDataPoint(values);
|
|
|
|
m_packetCount++;
|
|
m_packetLabel->setText(QString("收到: %1 包").arg(m_packetCount));
|
|
}
|
|
|
|
void MainWindow::onToggleChannel(int channel, bool visible)
|
|
{
|
|
m_plotWidget->setChannelVisible(channel, visible);
|
|
}
|