/** * @file DataMonitorWidget.cpp * @author jinchao * @brief 数据监控窗口类实现 * @version 1.0 * @date 2025-03-10 * * @copyright Copyright (c) 2025 COMAC * */ #include "DataMonitorWidget.h" #include DataMonitorWidget::DataMonitorWidget(QWidget *parent) : QWidget(parent) { setupTabDataMonitoring(); // 设置数据监控标签页 InitialDataMonitorThread(); // 初始化数据监控线程 } DataMonitorWidget::~DataMonitorWidget() { // 释放数据监控线程 if (dataMonitorThread != nullptr) { dataMonitorThread->onThreadQuit(); dataMonitorThread->quit(); dataMonitorThread->wait(); delete dataMonitorThread; dataMonitorThread = nullptr; } } void DataMonitorWidget::onSetCurrentTabIndex(int index) { this->currentTabIndex = index; if (currentTabIndex == 2) { //如果当前标签索引为2,则显示数据监控窗口 this->show(); emit controlDataMonitorThread(true); } else { //如果当前标签索引不为2,则隐藏数据监控窗口 this->hide(); emit controlDataMonitorThread(false); } } void DataMonitorWidget::InitialDataMonitorThread() { // 初始化数据监控线程 dataMonitorThread = new DataMonitorThread(this); // 连接数据监控线程的更新监控数据信号到更新监控数据槽函数 connect(dataMonitorThread, &DataMonitorThread::updateMonitoringData, this, &DataMonitorWidget::updateMonitoringData); // 连接控制数据监控线程信号到数据监控线程控制函数 connect(this, &DataMonitorWidget::controlDataMonitorThread, dataMonitorThread, &DataMonitorThread::onThreadController); // 连接监控开始信号到数据监控线程开始监控函数 connect(this, &DataMonitorWidget::monitoringStarted, dataMonitorThread, &DataMonitorThread::onStartMonitoring); // 连接监控停止信号到数据监控线程停止监控函数 connect(this, &DataMonitorWidget::monitoringStopped, dataMonitorThread, &DataMonitorThread::onStopMonitoring); // 连接监控暂停信号到数据监控线程暂停监控函数 connect(this, &DataMonitorWidget::monitoringPaused, dataMonitorThread, &DataMonitorThread::onPauseMonitoring); // 连接监控恢复信号到数据监控线程恢复监控函数 connect(this, &DataMonitorWidget::monitoringResumed, dataMonitorThread, &DataMonitorThread::onResumeMonitoring); // 连接注入一次数据信号到数据监控线程注入一次数据函数 connect(this, &DataMonitorWidget::injectOnceData, dataMonitorThread, &DataMonitorThread::onInjectOnceData); // 连接注入持续数据信号到数据监控线程注入持续数据函数 connect(this, &DataMonitorWidget::injectContinuousData, dataMonitorThread, &DataMonitorThread::onInjectContinuousData); // 连接停止注入持续数据信号到数据监控线程停止注入持续数据函数 connect(this, &DataMonitorWidget::stopInjectContinuousData, dataMonitorThread, &DataMonitorThread::onStopInjectContinuousData); // 连接保存模型定义信号到数据监控线程保存模型定义函数 connect(this, &DataMonitorWidget::saveModelDefinition, dataMonitorThread, &DataMonitorThread::onSaveModelDefinition); // 连接数据监控线程的发送调试消息信号到发送调试消息槽函数 connect(dataMonitorThread, &DataMonitorThread::sendDebugMessage, this, &DataMonitorWidget::onSendDebugMessage); // 启动数据监控线程 dataMonitorThread->start(); // 发送控制数据监控线程信号 emit controlDataMonitorThread(true); } // 设置数据监控标签页 void DataMonitorWidget::setupTabDataMonitoring() { QHBoxLayout *mainLayout = new QHBoxLayout(this); // 创建水平布局 // 使用QSplitter进行左右栏的动态分割 QSplitter *horizontalSplitter = new QSplitter(Qt::Horizontal); // 创建一个水平方向的QSplitter // 左栏布局 setupTabDataMonitoringLeftPanel(horizontalSplitter); // 右栏布局 setupTabDataMonitoringRightPanel(horizontalSplitter); // 设置QSplitter的初始分割比例为1:4,即左栏占五分之一,右栏占五分之四 QList sizes; sizes << 1 << 4; horizontalSplitter->setSizes(sizes); // 将QSplitter添加到主布局中 mainLayout->addWidget(horizontalSplitter); } // 设置数据监控左栏 void DataMonitorWidget::setupTabDataMonitoringLeftPanel(QSplitter *splitter) { // 左栏布局 QVBoxLayout *leftLayout = new QVBoxLayout(); leftLayout->setObjectName("ModelInterfaceLeftLayout"); // 添加搜索框 QLineEdit *searchLineEdit = new QLineEdit(); searchLineEdit->setObjectName("ModelInterfaceSearchBox"); searchLineEdit->setPlaceholderText("Search (use * as wildcard)"); searchLineEdit->setClearButtonEnabled(true); // 设置搜索框字体 QFont searchFont = searchLineEdit->font(); searchFont.setPointSize(14); searchLineEdit->setFont(searchFont); leftLayout->addWidget(searchLineEdit); QTreeWidget *tree = new QTreeWidget(); tree->setColumnCount(2); // 设置为两列 tree->setObjectName("ModelInterfaceTree"); tree->setHeaderLabels(QStringList() << "模型接口列表" << "操作"); // 设置两列的表头 tree->setIndentation(10); // 设置缩进大小 // 设置树控件的字体 QFont treeFont = tree->font(); treeFont.setPointSize(16); // 设置字体大小为16 tree->setFont(treeFont); // 同时设置表头字体 QFont headerFont = treeFont; headerFont.setBold(true); // 表头字体加粗 tree->header()->setFont(headerFont); // 设置列宽模式和固定宽度 tree->header()->setSectionResizeMode(0, QHeaderView::Stretch); // 第一列自动伸展 tree->header()->setSectionResizeMode(1, QHeaderView::Fixed); // 第二列固定宽度 tree->setColumnWidth(1, 100); // 设置第二列宽度为80像素 tree->header()->setStretchLastSection(false); // 禁止最后一列自动伸展 // 设置树控件的样式表 tree->setStyleSheet("QTreeWidget {" "show-decoration-selected: 1;" "}" "QTreeWidget::branch:has-children:!has-siblings:closed," "QTreeWidget::branch:closed:has-children:has-siblings" "{" "border-image: none;" "image: url(:/qss_icons/dark/rc/chevron-right.png);" "}" "QTreeWidget::branch:open:has-children:!has-siblings," "QTreeWidget::branch:open:has-children:has-siblings" "{" "border-image: none;" "image: url(:/qss_icons/dark/rc/chevron-down.png);" "}"); leftLayout->addWidget(tree); QPushButton *openModelInterfaceFileButton = new QPushButton("打开模型接口文件"); leftLayout->addWidget(openModelInterfaceFileButton); // 将按钮添加到左栏布局中 connect(openModelInterfaceFileButton, &QPushButton::clicked, this, &DataMonitorWidget::openModelInterfaceFile); // 连接搜索框的文本改变信号到搜索函数 connect(searchLineEdit, &QLineEdit::textChanged, this, &DataMonitorWidget::searchModelInterface); // 创建一个QWidget作为左栏的容器,并设置其布局为leftLayout QWidget *leftPanel = new QWidget(); leftPanel->setLayout(leftLayout); // 创建一个QScrollArea来包含左栏的布局 QScrollArea *leftScrollArea = new QScrollArea(this); leftScrollArea->setWidget(leftPanel); leftScrollArea->setWidgetResizable(true); leftScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); leftScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); // 将QScrollArea添加到QSplitter中 splitter->addWidget(leftScrollArea); } // 打开模型接口文件 void DataMonitorWidget::openModelInterfaceFile() { // 打开文件对话框 QString fileName = QFileDialog::getOpenFileName(this, "选择模型接口文件", "", "模型接口文件(*.idl)"); // 如果文件名不为空 if (fileName.isEmpty()) return; // 创建文件 QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox::warning(this, "错误", "无法打开文件:" + file.errorString()); return; } // 创建模型定义 QSharedPointer modelDefinition = QSharedPointer::create(); // 设置模型名称 modelDefinition->modelName = fileName.split("/").last().split(".").first(); // 读取文件 QTextStream in(&file); // 命名空间栈 QStack> namespaceStack; // 当前结构体 QSharedPointer currentStruct; // 当前行 QString line; // 是否在结构体中 bool inStruct = false; // 花括号计数 int braceCount = 0; // 逐行读取IDL文件 while (!in.atEnd()) { // 读取一行 line = in.readLine().trimmed(); // 跳过空行和注释 if (line.isEmpty() || line.startsWith("//")) continue; // 处理命名空间开始 if (line.startsWith("module")) { // 花括号计数加1 braceCount++; // 创建新的命名空间 QSharedPointer newNS = QSharedPointer::create(); // 获取命名空间名称 QString nsNames = line.mid(7).trimmed(); newNS->namespaceName = nsNames.split(" ").first(); // 直接使用完整名称 // 将新的命名空间压入命名空间栈 namespaceStack.push(newNS); // 继续处理下一行 continue; } // 处理结构体开始 if (line.startsWith("struct")) { // 如果已经在结构体中,并且当前结构体不为空,则将当前结构体添加到命名空间栈中 if (inStruct && currentStruct && !currentStruct->structName.isEmpty()) { // 将当前结构体添加到命名空间栈中 namespaceStack.top()->structDefinitions.append(currentStruct); // 创建新的结构体 currentStruct = QSharedPointer::create(); } // 设置在结构体中 inStruct = true; // 花括号计数加1 braceCount++; // 如果当前结构体为空,则创建新的结构体 if (!currentStruct) { // 创建新的结构体 currentStruct = QSharedPointer::create(); } // 设置结构体名称 currentStruct->structName = line.split(" ").at(1); // 继续处理下一行 continue; } // 处理成员变量 if (inStruct && currentStruct) { // 如果当前行不是花括号,则处理成员变量 if (!line.startsWith("{") && !line.startsWith("}")) { // 描述 QString description; // 注释位置 int commentPos = line.indexOf("//"); // 如果注释位置不为-1,则获取注释 if (commentPos != -1) { // 获取注释 description = line.mid(commentPos + 2).trimmed(); // 获取成员变量声明 line = line.left(commentPos).trimmed(); } // 分割成员变量声明 QStringList parts = line.split(";").first().split(" ", Qt::SkipEmptyParts); // 如果该行以@开头,移除第一个参数 if (parts[0].startsWith("@")) parts.pop_front(); // 如果成员变量声明大小大于等于2,则处理成员变量 if (parts.size() >= 2) { // 创建成员变量 QSharedPointer var = QSharedPointer::create(); // 设置数据类型 var->dataType = parts[0]; // 设置变量声明 QString varDecl = parts[1]; // 如果变量声明包含[,则设置为数组 if (varDecl.contains("[")) { // 设置为数组 var->isArray = true; // 设置变量名称 var->variableName = varDecl.split("[").first(); // 正则表达式 QRegularExpression re("\\[(\\d+)\\]"); // 匹配 QRegularExpressionMatchIterator i = re.globalMatch(varDecl); // 遍历匹配 while (i.hasNext()) { // 获取匹配 QRegularExpressionMatch match = i.next(); // 添加到数组大小 var->arraySizes.append(match.captured(1).toInt()); } } else { // 设置为非数组 var->isArray = false; // 设置变量名称 var->variableName = varDecl; } // 设置描述 var->description = description; // 添加到结构体成员变量列表 currentStruct->memberVariables.append(var); } } } // 处理花括号闭合 if (line.contains("}")) { // 花括号计数减1 braceCount--; // 如果当前在结构体中 if (inStruct) { // 设置不在结构体中 inStruct = false; // 如果当前结构体不为空,则将当前结构体添加到命名空间栈中 if (currentStruct && !currentStruct->structName.isEmpty()) { // 将当前结构体添加到命名空间栈中 namespaceStack.top()->structDefinitions.append(currentStruct); // 创建新的结构体 currentStruct = QSharedPointer::create(); } } else { // 如果命名空间栈不为空 if (!namespaceStack.isEmpty()) { // 获取命名空间 QSharedPointer completedNS = namespaceStack.pop(); // 只有当栈为空时,才添加到namespaceDefinitions if (namespaceStack.isEmpty()) { // 添加到namespaceDefinitions modelDefinition->namespaceDefinitions.append(completedNS); } else { // 添加到子命名空间 namespaceStack.top()->childNamespaces.append(completedNS); } } } } } // 关闭文件 file.close(); // 更新界面 updateInterfaceInfoPage(modelDefinition); // 保存模型定义 emit saveModelDefinition(modelDefinition); } // 更新模型接口信息页面 void DataMonitorWidget::updateInterfaceInfoPage(QSharedPointer modelDefinition) { // 如果当前标签索引不为2,则返回 if (currentTabIndex != 2) return; // 获取模型接口树 QTreeWidget *tree = findChild("ModelInterfaceTree"); if (!tree) return; // 创建模型项 QTreeWidgetItem *modelItem = new QTreeWidgetItem(tree); // 设置模型名称 modelItem->setText(0, modelDefinition->modelName); // 为模型项添加按钮 QPushButton *modelButton = new QPushButton("关闭"); // 设置按钮对象名称 modelButton->setObjectName(QString("btn_model_%1").arg(modelDefinition->modelName)); // 将按钮添加到树中 tree->setItemWidget(modelItem, 1, modelButton); // 连接按钮的点击信号到槽函数 connect(modelButton, &QPushButton::clicked, this, [=]() { // 显示确认对话框 QMessageBox::StandardButton reply; reply = QMessageBox::question( this, "确认关闭", QString("确定要关闭模型 %1 的接口描述吗?").arg(modelDefinition->modelName), QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::Yes) { // 从树中删除该模型项及其所有子项 delete tree->takeTopLevelItem(tree->indexOfTopLevelItem(modelItem)); } }); // 遍历namespaces,为每个命名空间创建一个根节点 for (const auto &ns : modelDefinition->namespaceDefinitions) { addNameSpaceToTree(modelDefinition->modelName, QStringList(), ns, modelItem, tree); } modelItem->setExpanded(true); } // 添加命名空间到树 void DataMonitorWidget::addNameSpaceToTree(const QString &modelName, const QStringList &topicNameList, const QSharedPointer &ns, QTreeWidgetItem *parentItem, QTreeWidget *tree) { // 创建命名空间项 QTreeWidgetItem *namespaceItem = new QTreeWidgetItem(parentItem); // 新的主题名称列表 QStringList newTopicNameList = topicNameList; // 添加命名空间名称 newTopicNameList.append(ns->namespaceName); // 设置命名空间名称 namespaceItem->setText(0, ns->namespaceName); // 设置模块类型 namespaceItem->setText(1, "Module"); // 遍历子命名空间 for (const auto &childNS : ns->childNamespaces) { // 添加到树中 addNameSpaceToTree(modelName, newTopicNameList, childNS, namespaceItem, tree); } // 遍历命名空间中的结构体定义 for (const auto &structDef : ns->structDefinitions) { // 创建结构体项 QTreeWidgetItem *structItem = new QTreeWidgetItem(namespaceItem); // 设置结构体名称 structItem->setText(0, structDef->structName); // 设置模块类型 structItem->setText(1, "Struct"); // 新的主题名称列表 QString topicName = newTopicNameList.join("::") + "::" + structDef->structName; // 遍历结构体的成员变量 for (const auto &memberVar : structDef->memberVariables) { // 创建成员变量项 QTreeWidgetItem *memberItem = new QTreeWidgetItem(structItem); // 设置成员变量名称 memberItem->setText(0, memberVar->variableName); // 如果成员变量是数组 if (memberVar->isArray) { // 为成员变量项添加按钮 QPushButton *memberButton = new QPushButton("监控全部"); // 按钮对象名称 QString btnName = "btn_member_" + newTopicNameList.join("_"); // 设置按钮对象名称 memberButton->setObjectName(btnName); // 将按钮添加到树中 tree->setItemWidget(memberItem, 1, memberButton); // 连接监控全部按钮的点击信号 connect(memberButton, &QPushButton::clicked, this, [=]() { for (int i = 0; i < memberVar->arraySizes[0]; ++i) { // 如果成员变量是二维数组 if (memberVar->arraySizes.size() == 2) { // 遍历二维数组 for (int j = 0; j < memberVar->arraySizes[1]; ++j) { // 添加监控数据 addMonitoringData(modelName, topicName, QString("%1[%2][%3]") .arg(memberVar->variableName) .arg(i) .arg(j), memberVar->dataType); } } else { // 添加监控数据 addMonitoringData(modelName, topicName, QString("%1[%2]").arg(memberVar->variableName).arg(i), memberVar->dataType); } } }); // 遍历一维数组 for (int i = 0; i < memberVar->arraySizes[0]; ++i) { // 创建成员变量项 QTreeWidgetItem *memberItem1 = new QTreeWidgetItem(memberItem); // 设置成员变量名称 memberItem1->setText( 0, QStringLiteral("%1[%2]").arg(memberVar->variableName).arg(i)); // 如果成员变量是二维数组 if (memberVar->arraySizes.size() == 2) { // 为数组元素项添加按钮 QPushButton *arrayButton = new QPushButton("监控全部"); // 按钮对象名称 QString btnName = "btn_array_" + newTopicNameList.join("_"); // 设置按钮对象名称 arrayButton->setObjectName(btnName); // 将按钮添加到树中 tree->setItemWidget(memberItem1, 1, arrayButton); // 连接二维数组监控全部按钮的点击信号 connect(arrayButton, &QPushButton::clicked, this, [=]() { for (int j = 0; j < memberVar->arraySizes[1]; ++j) { addMonitoringData(modelName, topicName, QString("%1[%2][%3]") .arg(memberVar->variableName) .arg(i) .arg(j), memberVar->dataType); } }); // 遍历二维数组 for (int j = 0; j < memberVar->arraySizes[1]; ++j) { // 创建二维数组元素项 QTreeWidgetItem *arrayElementItem = new QTreeWidgetItem(memberItem1); // 设置二维数组元素名称 arrayElementItem->setText(0, QStringLiteral("%1[%2][%3]") .arg(memberVar->variableName) .arg(i) .arg(j)); // 为二维数组元素项添加按钮 QPushButton *arrayElement2Button = new QPushButton("监控"); QString btnName = "btn_array2_" + newTopicNameList.join("_"); arrayElement2Button->setObjectName(btnName); tree->setItemWidget(arrayElementItem, 1, arrayElement2Button); // 连接二维数组元素监控按钮的点击信号 connect(arrayElement2Button, &QPushButton::clicked, this, [=]() { addMonitoringData(modelName, topicName, QString("%1[%2][%3]") .arg(memberVar->variableName) .arg(i) .arg(j), memberVar->dataType); }); } } else { // 为数组元素项添加按钮 QPushButton *arrayButton = new QPushButton("监控"); QString btnName = "btn_array_" + newTopicNameList.join("_"); arrayButton->setObjectName(btnName); tree->setItemWidget(memberItem1, 1, arrayButton); // 连接一维数组元素监控按钮的点击信号 connect(arrayButton, &QPushButton::clicked, this, [=]() { addMonitoringData(modelName, topicName, QString("%1[%2]").arg(memberVar->variableName).arg(i), memberVar->dataType); }); } } } else { // 为成员变量项添加按钮 QPushButton *memberButton = new QPushButton("监控"); QString btnName = "btn_member_" + newTopicNameList.join("_"); memberButton->setObjectName(btnName); tree->setItemWidget(memberItem, 1, memberButton); // 连接普通变量监控按钮的点击信号 connect(memberButton, &QPushButton::clicked, this, [=]() { addMonitoringData(modelName, topicName, memberVar->variableName, memberVar->dataType); }); } } } } // 添加监控数据 void DataMonitorWidget::addMonitoringData(const QString &modelName, const QString &topicName, const QString &varName, const QString &dataType) { QTableWidget *table = findChild("dataMonitoringTableWidget"); if (!table) return; // 查找是否已经监控该变量 for (int i = 0; i < table->rowCount(); ++i) { QTableWidgetItem *item = table->item(i, 0); if (item && item->text() == varName) { QMessageBox::information(this, "提示", "该变量已经监控了"); return; } } // 如果没有找到该变量,则添加新行 int row = table->rowCount(); table->setRowCount(row + 1); // 初始化新行的所有列 for (int col = 0; col < table->columnCount(); ++col) { QTableWidgetItem *item = new QTableWidgetItem(); if (col != 7) { item->setFlags(item->flags() & ~Qt::ItemIsEditable); } table->setItem(row, col, item); } // 设置接口名称 table->item(row, 0)->setText(varName); // 设置主题名称 table->item(row, 1)->setText(topicName); // 设置模型名称 table->item(row, 2)->setText(modelName); // 设置数据类型 table->item(row, 3)->setText(dataType); // 设置初始值 table->item(row, 4)->setText("--"); // 设置监控状态(灰色) table->item(row, 5)->setText("未开始"); table->item(row, 5)->setForeground(QColor(128, 128, 128)); // 设置更改值单元格(可编辑) QTableWidgetItem *changeValueItem = new QTableWidgetItem(); table->setItem(row, 7, changeValueItem); // 创建监控控制按钮组 QWidget *buttonWidget = new QWidget(); QHBoxLayout *buttonLayout = new QHBoxLayout(buttonWidget); buttonLayout->setContentsMargins(2, 2, 2, 2); buttonLayout->setSpacing(2); // 创建开始按钮 QPushButton *startStopButton = new QPushButton("开始"); // 创建暂停按钮 QPushButton *pauseResumeButton = new QPushButton("暂停"); // 创建绘图按钮 QPushButton *plotButton = new QPushButton("绘图"); // 创建删除按钮 QPushButton *deleteButton = new QPushButton("删除"); // 设置暂停按钮不可用 pauseResumeButton->setEnabled(false); // 设置绘图按钮不可用 plotButton->setEnabled(false); // 设置绘图按钮可选中 plotButton->setCheckable(true); // 设置绘图按钮对象名称 plotButton->setObjectName(QString("btn_plot_%1").arg(varName)); // 将按钮添加到布局中 buttonLayout->addWidget(startStopButton); buttonLayout->addWidget(pauseResumeButton); buttonLayout->addWidget(plotButton); buttonLayout->addWidget(deleteButton); // 将按钮组添加到表格中 table->setCellWidget(row, 6, buttonWidget); // 创建注入按钮组 QWidget *injectButtonWidget = new QWidget(); QHBoxLayout *injectLayout = new QHBoxLayout(injectButtonWidget); injectLayout->setContentsMargins(2, 2, 2, 2); injectLayout->setSpacing(2); // 创建注入一次按钮 QPushButton *injectOnceButton = new QPushButton("注入一次"); QPushButton *injectContinuousButton = new QPushButton("持续注入"); injectOnceButton->setFixedWidth(100); injectContinuousButton->setFixedWidth(100); injectOnceButton->setEnabled(false); injectContinuousButton->setEnabled(false); // 将按钮添加到布局中 injectLayout->addWidget(injectOnceButton); injectLayout->addWidget(injectContinuousButton); // 将按钮组添加到表格中 table->setCellWidget(row, 8, injectButtonWidget); // 连接开始/停止按钮的点击信号 connect(startStopButton, &QPushButton::clicked, this, [=]() { if (startStopButton->text() == "开始") { // 设置开始按钮文本为停止 startStopButton->setText("停止"); // 设置暂停按钮可用 pauseResumeButton->setEnabled(true); // 设置绘图按钮可用 plotButton->setEnabled(true); // 设置注入一次按钮可用 injectOnceButton->setEnabled(true); // 设置持续注入按钮可用 injectContinuousButton->setEnabled(true); // 设置删除按钮不可用 deleteButton->setEnabled(false); // 设置状态为"监控中"(绿色) QTableWidgetItem *statusItem = table->item(row, 5); statusItem->setText("监控中"); statusItem->setForeground(QColor(0, 255, 0)); // 绿色 // 设置注入一次按钮可用 injectOnceButton->setEnabled(true); // 设置持续注入按钮可用 injectContinuousButton->setEnabled(true); // 添加变量数据映射 varDataMap[varName] = QVector(); // 发送开始监控信号 emit monitoringStarted(modelName, topicName, varName); } else { // 设置开始按钮文本为开始 startStopButton->setText("开始"); // 设置暂停按钮文本为暂停 pauseResumeButton->setText("暂停"); // 设置暂停按钮不可用 pauseResumeButton->setEnabled(false); // 设置绘图按钮不可用 plotButton->setEnabled(false); // 设置注入一次按钮不可用 injectOnceButton->setEnabled(false); // 设置持续注入按钮不可用 injectContinuousButton->setEnabled(false); // 设置删除按钮可用 deleteButton->setEnabled(true); // 设置状态为"未开始"(灰色) QTableWidgetItem *statusItem = table->item(row, 5); statusItem->setText("未开始"); statusItem->setForeground(QColor(128, 128, 128)); // 灰色 // 设置注入一次按钮不可用 injectOnceButton->setEnabled(false); // 设置持续注入按钮不可用 injectContinuousButton->setEnabled(false); // 移除变量数据映射 varDataMap.remove(varName); // 发送停止监控信号 emit monitoringStopped(modelName, topicName, varName); } }); // 连接暂停/继续按钮的点击信号 connect(pauseResumeButton, &QPushButton::clicked, this, [=]() { if (pauseResumeButton->text() == "暂停") { // 设置暂停按钮文本为继续 pauseResumeButton->setText("继续"); // 设置状态为"已暂停"(橙色) QTableWidgetItem *statusItem = table->item(row, 5); statusItem->setText("已暂停"); statusItem->setForeground(QColor(255, 165, 0)); // 橙色 // 设置注入一次按钮可用 injectOnceButton->setEnabled(true); // 设置持续注入按钮可用 injectContinuousButton->setEnabled(true); // 发送暂停监控信号 emit monitoringPaused(modelName, topicName, varName); } else { // 设置暂停按钮文本为暂停 pauseResumeButton->setText("暂停"); // 设置状态为"监控中"(绿色) QTableWidgetItem *statusItem = table->item(row, 5); statusItem->setText("监控中"); statusItem->setForeground(QColor(0, 255, 0)); // 绿色 // 设置注入一次按钮不可用 injectOnceButton->setEnabled(false); // 设置持续注入按钮不可用 injectContinuousButton->setEnabled(false); // 发送继续监控信号 emit monitoringResumed(modelName, topicName, varName); } }); // 连接绘图按钮的点击信号 connect(plotButton, &QPushButton::clicked, this, [=]() { // 遍历表格中所有行,将其他行的plotButton设置为未选中状态 for (int i = 0; i < table->rowCount(); ++i) { if (i != row) { // 跳过当前行 // 获取变量名称 QString varName = table->item(i, 0)->text(); // 获取绘图按钮对象名称 QString objectName = QString("btn_plot_%1").arg(varName); // 获取绘图按钮 QPushButton *otherPlotButton = findChild(objectName); // 如果绘图按钮存在 if (otherPlotButton) { // 设置绘图按钮为未选中状态 otherPlotButton->setChecked(false); } } } // 获取当前数据 if (!varDataMap.contains(varName)) { // 添加变量数据映射 varDataMap[varName] = QVector(); } // 如果按钮被选中,则显示图表 if (plotButton->isChecked()) { // 获取变量数据 const QVector &data = varDataMap[varName]; // 如果变量数据不为空 if (data.size() > 0) { // 创建x轴数据 QVector xData; // 遍历变量数据 for (int i = 0; i < data.size(); ++i) { xData.push_back(i); } // 获取图表对象 XNCustomPlot *customPlot = findChild("dataMonitoringCustomPlot"); if (customPlot) { if (customPlot->graphCount() == 0) { // 添加新的图表并设置绿色 customPlot->addGraph(); QPen greenPen(Qt::green); greenPen.setWidth(2); customPlot->graph(0)->setPen(greenPen); } // 设置图表数据 customPlot->graph(0)->setData(xData, data); // 自动调整坐标轴范围 customPlot->xAxis->setRange(0, data.size()); customPlot->yAxis->rescale(); // 重绘图表 customPlot->replot(); } } } else { // 如果取消选中,则清除图表 XNCustomPlot *customPlot = findChild("dataMonitoringCustomPlot"); if (customPlot) { customPlot->clearGraphs(); customPlot->replot(); } } }); // 连接删除按钮的点击信号 connect(deleteButton, &QPushButton::clicked, this, [=]() { QMessageBox::StandardButton reply; reply = QMessageBox::question(this, "确认删除", "确定要删除该监控项吗?", QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::Yes) { // 如果该行正在绘图,则清空绘图 QPushButton *plotButton = qobject_cast(table->cellWidget(row, 4)); if (plotButton && plotButton->isChecked()) { XNCustomPlot *customPlot = findChild("dataMonitoringCustomPlot"); if (customPlot) { customPlot->clearGraphs(); customPlot->replot(); } } // 清空该行的所有内容 for (int col = 0; col < table->columnCount(); ++col) { if (table->item(row, col)) { table->item(row, col)->setText(""); } } // 移除按钮组件 table->removeCellWidget(row, 6); // 移除注入按钮 table->removeCellWidget(row, 8); // 移除该行 table->removeRow(row); } }); connect(injectOnceButton, &QPushButton::clicked, this, [=]() { // 读取更改值单元格的内容 QTableWidgetItem *changeValueItem = table->item(row, 7); if (!changeValueItem || changeValueItem->text().isEmpty()) { QMessageBox::warning(this, "警告", "请先输入注入值"); return; } emit injectOnceData(modelName, topicName, varName, changeValueItem->text().toDouble()); }); connect(injectContinuousButton, &QPushButton::clicked, this, [=]() { if (injectContinuousButton->text() == "持续注入") { // 读取更改值单元格的内容 QTableWidgetItem *changeValueItem = table->item(row, 7); if (!changeValueItem || changeValueItem->text().isEmpty()) { QMessageBox::warning(this, "警告", "请先输入注入值"); return; } injectContinuousButton->setText("停止注入"); emit injectContinuousData(modelName, topicName, varName, changeValueItem->text().toDouble()); } else { injectContinuousButton->setText("持续注入"); emit stopInjectContinuousData(modelName, topicName, varName); } }); } // 设置数据监控右面板 void DataMonitorWidget::setupTabDataMonitoringRightPanel(QSplitter *splitter) { // 右栏布局 QVBoxLayout *rightLayout = new QVBoxLayout(); // 创建一个新的QSplitter用于垂直分割右栏的上下两部分 QSplitter *verticalSplitter = new QSplitter(Qt::Vertical); // 垂直方向的QSplitter // 表格布局 QTableWidget *dataMonitoringTableWidget = new QTableWidget(); setupDataMonitoringTableWidget(dataMonitoringTableWidget); // 图表绘制区域 QWidget *dataMonitoringChartWidget = new QWidget(); // 图表区域,可以使用XNCustomPlot等库进行绘制 setupDataMonitoringChartWidget(dataMonitoringChartWidget); // 配置图表区域 // 将表格和图表添加到垂直分割器中 verticalSplitter->addWidget(dataMonitoringTableWidget); verticalSplitter->addWidget(dataMonitoringChartWidget); QList sizes; sizes << 3 << 2; verticalSplitter->setSizes(sizes); // 将垂直分割器添加到右栏布局中 rightLayout->addWidget(verticalSplitter); // 创建一个QWidget作为右栏的容器,并设置其布局为rightLayout QWidget *rightPanel = new QWidget(); rightPanel->setLayout(rightLayout); // 将右栏容器添加到QSplitter中 splitter->addWidget(rightPanel); } // 设置数据监控表格 void DataMonitorWidget::setupDataMonitoringTableWidget(QTableWidget *dataMonitoringTableWidget) { dataMonitoringTableWidget->setRowCount(0); // 设置初始行数为1(空行) dataMonitoringTableWidget->setColumnCount(9); // 设置表格的列数 QFont headerFont("Arial", 16, QFont::Bold); dataMonitoringTableWidget->horizontalHeader()->setFont(headerFont); // 设置表格的列标题 dataMonitoringTableWidget->setHorizontalHeaderLabels({"接口名称", "主题名称", "模型名称", "数据类型", "值", "监控状态", "监控控制", "注入值", "数据注入"}); // 设置表格的选择模式为单选 dataMonitoringTableWidget->setSelectionMode(QAbstractItemView::SingleSelection); // 设置表格仅第8列可编辑 for (int col = 0; col < dataMonitoringTableWidget->columnCount(); col++) { for (int row = 0; row < dataMonitoringTableWidget->rowCount(); row++) { QTableWidgetItem *item = dataMonitoringTableWidget->item(row, col); if (item) { if (col == 7) { // 第8列(索引7)可编辑 item->setFlags(item->flags() | Qt::ItemIsEditable); } else { // 其他列不可编辑 item->setFlags(item->flags() & ~Qt::ItemIsEditable); } } } } // 设置表格的objectName,以便后续可以通过findChild找到并更新它 dataMonitoringTableWidget->setObjectName("dataMonitoringTableWidget"); // 设置列宽 dataMonitoringTableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); // 设置各列的固定宽度 dataMonitoringTableWidget->setColumnWidth(0, 300); // 接口名称列 dataMonitoringTableWidget->setColumnWidth(1, 100); // 主题名称列 dataMonitoringTableWidget->setColumnWidth(2, 100); // 模型名称列 dataMonitoringTableWidget->setColumnWidth(3, 100); // 数据类型列 dataMonitoringTableWidget->setColumnWidth(4, 100); // 值列 dataMonitoringTableWidget->setColumnWidth(5, 100); // 监控状态列 dataMonitoringTableWidget->setColumnWidth(6, 400); // 监控控制列 - 设置更宽以容纳按钮 dataMonitoringTableWidget->setColumnWidth(7, 100); // 更改值列 dataMonitoringTableWidget->setColumnWidth(8, 200); // 数据注入列 dataMonitoringTableWidget->horizontalHeader()->setStretchLastSection( true); // 最后一列(数据注入)自适应 // // 初始化第一行的所有列 // for (int col = 0; col < dataMonitoringTableWidget->columnCount(); ++col) { // QTableWidgetItem *item = new QTableWidgetItem(); // if (col != 0 && col != 5) { // item->setFlags(item->flags() & ~Qt::ItemIsEditable); // } // dataMonitoringTableWidget->setItem(0, col, item); // } } // 更新监控数据 void DataMonitorWidget::updateMonitoringData(const QString &varName, const QString &value) { if (currentTabIndex != 2) return; QTableWidget *table = findChild("dataMonitoringTableWidget"); if (!table) return; int row = -1; for (int i = 0; i < table->rowCount(); ++i) { if (table->item(i, 0) && table->item(i, 0)->text() == varName) { row = i; break; } } if (row != -1) { // 更新表格值 table->item(row, 4)->setText(value); // 更新数据 bool ok; double dValue = value.toDouble(&ok); if (ok) { varDataMap[varName].append(dValue); if (varDataMap[varName].size() > 100) { varDataMap[varName].removeFirst(); } // 如果该变量正在绘图,则更新图表 QString objectName = QString("btn_plot_%1").arg(varName); QPushButton *plotButton = findChild(objectName); if (plotButton && plotButton->isChecked()) { XNCustomPlot *customPlot = findChild("dataMonitoringCustomPlot"); if (customPlot && customPlot->graph(0)) { // 确保使用绿色 QPen greenPen(Qt::green); greenPen.setWidth(2); customPlot->graph(0)->setPen(greenPen); // 保存当前的Y轴范围 QCPRange yRange = customPlot->yAxis->range(); // 准备数据 const QVector &data = varDataMap[varName]; QVector xData(data.size()); for (int i = 0; i < data.size(); ++i) { xData[i] = i; } // 设置新数据 customPlot->graph(0)->setData(xData, data); // X轴始终自动调整 customPlot->xAxis->setRange(0, data.size()); // 检查新数据是否超出当前Y轴范围 bool needYRescale = false; if (customPlot->isYAxisUserScaled()) { double newValue = data.last(); if (newValue < yRange.lower || newValue > yRange.upper) { // 如果超出范围,扩大范围而不是完全重置 double margin = (yRange.upper - yRange.lower) * 0.1; // 10% 边距 if (newValue < yRange.lower) { yRange.lower = newValue - margin; } if (newValue > yRange.upper) { yRange.upper = newValue + margin; } customPlot->setInternalChange(true); // 使用新添加的方法 customPlot->yAxis->setRange(yRange); customPlot->setInternalChange(false); } } else { customPlot->setInternalChange(true); // 使用新添加的方法 customPlot->yAxis->rescale(); customPlot->setInternalChange(false); } // 重绘图表 customPlot->replot(); } } } } } // 设置数据监控图表 void DataMonitorWidget::setupDataMonitoringChartWidget(QWidget *dataMonitoringChartWidget) { // 设置图表对象名称 dataMonitoringChartWidget->setObjectName("dataMonitoringChartWidget"); // 创建图表 XNCustomPlot *customPlot = new XNCustomPlot(dataMonitoringChartWidget); // 设置图表对象名称 customPlot->setObjectName("dataMonitoringCustomPlot"); // 设置图表 setupPlotWidget(customPlot); // 设置图表布局 dataMonitoringChartWidget->setLayout(new QVBoxLayout()); // 将图表添加到布局中 dataMonitoringChartWidget->layout()->addWidget(customPlot); } void DataMonitorWidget::setupPlotWidget(XNCustomPlot *customPlot) { // 设置图表背景 customPlot->setBackground(QBrush(QColor("#19232D"))); // 设置x轴标签颜色 customPlot->xAxis->setLabelColor(Qt::white); // 设置y轴标签颜色 customPlot->yAxis->setLabelColor(Qt::white); // 设置x轴标签 customPlot->xAxis->setLabel("时间(s)"); // 设置y轴标签 customPlot->yAxis->setLabel("值"); // 设置x轴标签旋转 customPlot->xAxis->setTickLabelRotation(60); // 设置x轴标签颜色 customPlot->xAxis->setTickLabelColor(Qt::white); // 设置y轴标签颜色 customPlot->yAxis->setTickLabelColor(Qt::white); // 设置x轴网格 customPlot->xAxis->grid()->setVisible(true); // 设置y轴网格 customPlot->yAxis->grid()->setVisible(true); // 设置不使用抗锯齿 customPlot->setNotAntialiasedElements(QCP::aeAll); // 设置快速绘制 customPlot->setPlottingHints(QCP::phFastPolylines); // 拖动时关闭抗锯齿 customPlot->setNoAntialiasingOnDrag(true); // 优化图表性能 customPlot->setBufferDevicePixelRatio(1.0); // 设置缓冲区像素比 customPlot->setOpenGl(true); // 启用OpenGL加速(如果支持) // 确保图表可以接收鼠标事件 customPlot->setMouseTracking(true); customPlot->setAttribute(Qt::WA_Hover); customPlot->setFocusPolicy(Qt::StrongFocus); // 启用所有交互选项 customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectItems | QCP::iSelectAxes | QCP::iSelectLegend); // 创建新的顶层层级 customPlot->addLayer("topmost", customPlot->layer("main"), QCustomPlot::limAbove); QCPLayer *topmostLayer = customPlot->layer("topmost"); topmostLayer->setMode(QCPLayer::lmBuffered); // 将所有现有图层移到新层级下方 for (int i = 0; i < customPlot->layerCount(); ++i) { QCPLayer *layer = customPlot->layer(i); if (layer != topmostLayer) { layer->setMode(QCPLayer::lmBuffered); customPlot->moveLayer(layer, topmostLayer, QCustomPlot::limBelow); } } // 创建坐标标签 m_coordLabel = new QCPItemText(customPlot); // 设置坐标标签位置对齐 m_coordLabel->setPositionAlignment(Qt::AlignTop | Qt::AlignLeft); // 设置坐标标签文本对齐 m_coordLabel->setTextAlignment(Qt::AlignLeft); // 设置坐标标签可见 m_coordLabel->setVisible(false); // 设置坐标标签内边距 m_coordLabel->setPadding(QMargins(8, 8, 8, 8)); // 设置坐标标签画刷 m_coordLabel->setBrush(QBrush(QColor(40, 40, 40, 255))); // 设置坐标标签笔刷 m_coordLabel->setPen(QPen(QColor(80, 80, 80, 255))); // 设置坐标标签颜色 m_coordLabel->setColor(Qt::white); // 设置坐标标签字体 QFont labelFont = QFont("Arial", 16); labelFont.setBold(true); m_coordLabel->setFont(labelFont); // 设置坐标标签层级 m_coordLabel->setLayer("topmost"); // 创建点追踪器 m_tracer = new QCPItemTracer(customPlot); m_tracer->setStyle(QCPItemTracer::tsCircle); m_tracer->setPen(QPen(Qt::red)); m_tracer->setBrush(Qt::red); m_tracer->setSize(8); // 设置追踪器可见 m_tracer->setVisible(false); // 设置追踪器层级 m_tracer->setLayer("topmost"); // 连接鼠标事件 connect(customPlot, &XNCustomPlot::mousePressed, this, [=](QMouseEvent *event) { if (event->button() == Qt::LeftButton && customPlot->graph(0)) { double x = customPlot->xAxis->pixelToCoord(event->pos().x()); double y = customPlot->yAxis->pixelToCoord(event->pos().y()); if (!customPlot->graph(0)->data()->isEmpty()) { double minDistance = std::numeric_limits::max(); QCPGraphDataContainer::const_iterator closestPoint; bool foundPoint = false; for (auto it = customPlot->graph(0)->data()->begin(); it != customPlot->graph(0)->data()->end(); ++it) { double distance = qSqrt(qPow(it->key - x, 2) + qPow(it->value - y, 2)); if (distance < minDistance) { minDistance = distance; closestPoint = it; foundPoint = true; } } if (foundPoint) { m_tracer->setGraph(customPlot->graph(0)); m_tracer->setGraphKey(closestPoint->key); m_tracer->setVisible(true); m_coordLabel->position->setCoords(closestPoint->key, closestPoint->value); m_coordLabel->setText(QString("时间: %1\n数值: %2") .arg(closestPoint->key) .arg(closestPoint->value)); m_coordLabel->setVisible(true); customPlot->replot(); } } } }); // 确保在初始化时重新绘制一次 customPlot->replot(); } // 搜索模型接口 void DataMonitorWidget::searchModelInterface(const QString &text) { QTreeWidget *tree = findChild("ModelInterfaceTree"); if (!tree) return; // 如果搜索文本为空,显示所有项 if (text.isEmpty()) { showAllTreeItems(tree->invisibleRootItem()); return; } // 隐藏所有项,然后只显示匹配的项 hideAllTreeItems(tree->invisibleRootItem()); // 搜索匹配项并显示 for (int i = 0; i < tree->topLevelItemCount(); ++i) { QTreeWidgetItem *topItem = tree->topLevelItem(i); searchAndShowMatchingItems(topItem, text); } } // 递归显示所有树项目 void DataMonitorWidget::showAllTreeItems(QTreeWidgetItem *item) { if (!item) return; item->setHidden(false); for (int i = 0; i < item->childCount(); ++i) { showAllTreeItems(item->child(i)); } } // 递归隐藏所有树项目 void DataMonitorWidget::hideAllTreeItems(QTreeWidgetItem *item) { if (!item) return; for (int i = 0; i < item->childCount(); ++i) { hideAllTreeItems(item->child(i)); item->child(i)->setHidden(true); } } // 递归搜索并显示匹配的项目 bool DataMonitorWidget::searchAndShowMatchingItems(QTreeWidgetItem *item, const QString &text) { if (!item) return false; bool foundInChildren = false; // 搜索子项 for (int i = 0; i < item->childCount(); ++i) { bool foundInThisChild = searchAndShowMatchingItems(item->child(i), text); foundInChildren = foundInChildren || foundInThisChild; } // 检查当前项是否匹配 bool matchesThis = false; // 支持通配符 * 但不能以 * 开头 if (text.contains('*') && !text.startsWith('*')) { // 将用户输入的通配符表达式转换为正则表达式 QString pattern = QRegularExpression::escape(text); pattern.replace("\\*", ".*"); // 将 * 转换为正则表达式中的 .* QRegularExpression regex(pattern, QRegularExpression::CaseInsensitiveOption); matchesThis = regex.match(item->text(0)).hasMatch(); } else { // 标准的包含搜索 matchesThis = item->text(0).contains(text, Qt::CaseInsensitive); } // 如果当前项匹配或其任何子项匹配,则显示当前项 if (matchesThis || foundInChildren) { item->setHidden(false); // 如果匹配,展开父项以显示匹配的子项 if (matchesThis) { QTreeWidgetItem *parent = item->parent(); while (parent) { parent->setExpanded(true); parent->setHidden(false); parent = parent->parent(); } } return true; } return false; } // 发送调试信息 void DataMonitorWidget::onSendDebugMessage(int type, const QString &message) { emit sendDebugMessage(type, message); }