XNSim/XNMonitor/DataMonitorWidget/DataMonitorWidget.cpp

1347 lines
45 KiB
C++
Raw Normal View History

2025-04-28 12:25:20 +08:00
/**
* @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);
}