1347 lines
45 KiB
C++
1347 lines
45 KiB
C++
|
/**
|
|||
|
* @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);
|
|||
|
}
|