XNSim/XNMonitor/DataMonitorWidget/DataMonitorWidget.cpp
2025-04-28 12:25:20 +08:00

1347 lines
45 KiB
C++
Executable File
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.

/**
* @file DataMonitorWidget.cpp
* @author jinchao
* @brief 数据监控窗口类实现
* @version 1.0
* @date 2025-03-10
*
* @copyright Copyright (c) 2025 COMAC
*
*/
#include "DataMonitorWidget.h"
#include <QTreeWidget>
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<int> 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> modelDefinition = QSharedPointer<ModelDefinition>::create();
// 设置模型名称
modelDefinition->modelName = fileName.split("/").last().split(".").first();
// 读取文件
QTextStream in(&file);
// 命名空间栈
QStack<QSharedPointer<NamespaceDefinition>> namespaceStack;
// 当前结构体
QSharedPointer<StructDefinition> 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<NamespaceDefinition> newNS =
QSharedPointer<NamespaceDefinition>::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<StructDefinition>::create();
}
// 设置在结构体中
inStruct = true;
// 花括号计数加1
braceCount++;
// 如果当前结构体为空,则创建新的结构体
if (!currentStruct) {
// 创建新的结构体
currentStruct = QSharedPointer<StructDefinition>::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<MemberVariable> var = QSharedPointer<MemberVariable>::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<StructDefinition>::create();
}
} else {
// 如果命名空间栈不为空
if (!namespaceStack.isEmpty()) {
// 获取命名空间
QSharedPointer<NamespaceDefinition> 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> modelDefinition)
{
// 如果当前标签索引不为2则返回
if (currentTabIndex != 2)
return;
// 获取模型接口树
QTreeWidget *tree = findChild<QTreeWidget *>("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<NamespaceDefinition> &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<QTableWidget *>("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<double>();
// 发送开始监控信号
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<QPushButton *>(objectName);
// 如果绘图按钮存在
if (otherPlotButton) {
// 设置绘图按钮为未选中状态
otherPlotButton->setChecked(false);
}
}
}
// 获取当前数据
if (!varDataMap.contains(varName)) {
// 添加变量数据映射
varDataMap[varName] = QVector<double>();
}
// 如果按钮被选中,则显示图表
if (plotButton->isChecked()) {
// 获取变量数据
const QVector<double> &data = varDataMap[varName];
// 如果变量数据不为空
if (data.size() > 0) {
// 创建x轴数据
QVector<double> xData;
// 遍历变量数据
for (int i = 0; i < data.size(); ++i) {
xData.push_back(i);
}
// 获取图表对象
XNCustomPlot *customPlot = findChild<XNCustomPlot *>("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<XNCustomPlot *>("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<QPushButton *>(table->cellWidget(row, 4));
if (plotButton && plotButton->isChecked()) {
XNCustomPlot *customPlot = findChild<XNCustomPlot *>("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<int> 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<QTableWidget *>("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<QPushButton *>(objectName);
if (plotButton && plotButton->isChecked()) {
XNCustomPlot *customPlot = findChild<XNCustomPlot *>("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<double> &data = varDataMap[varName];
QVector<double> 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<double>::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<QTreeWidget *>("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);
}