#include "mainwindow.h" #include "plotwidget.h" #include "udpreceiver.h" #include "datamanager.h" #include #include #include #include #include #include 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)); check->setChecked(true); check->setStyleSheet( "QCheckBox { color: #aaa; font-size: 11px; spacing: 4px; }" "QCheckBox::indicator { width: 12px; height: 12px; }" "QCheckBox::indicator:checked { background: #3498db; }" ); connect(check, &QCheckBox::toggled, this, [this, i](bool checked) { onToggleChannel(i, checked); }); 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 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 dvalues(16); for (int i = 0; i < 16; ++i) { dvalues[i] = static_cast(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); }