XNSim/XNRunner/mainwindow.cpp

463 lines
14 KiB
C++
Raw Permalink Normal View History

2025-04-28 12:25:20 +08:00
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QProcess>
#include "MonitorThread.h"
#include <QRegularExpression>
#include <QTextCharFormat>
#include <QTextCursor>
#include <QScrollBar>
#include <QTimer>
#include <QMessageBox>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow), userScrolled(false)
{
ui->setupUi(this);
// 关闭菜单栏和状态栏
menuBar()->hide();
statusBar()->hide();
setWindowTitle("XNRunner");
//设置窗口图标
setWindowIcon(QIcon(":/icon/XNRunner.png"));
// 设置窗口字体
QFont appFont("Arial", 14);
QApplication::setFont(appFont);
// Create widgets
QLabel *label = new QLabel("Environment Configuration", this);
//label->setFixedSize(200, 30);
QComboBox *comboBox = new QComboBox(this);
QPushButton *runPauseButton = new QPushButton("Run", this);
runPauseButton->setFixedSize(100, 30);
runPauseButton->setObjectName("runPauseButton");
// 连接按钮点击信号到槽函数
connect(runPauseButton, &QPushButton::clicked, this, &MainWindow::OnRunPauseButtonClicked);
QPushButton *endButton = new QPushButton("Abort", this);
endButton->setFixedSize(100, 30);
endButton->setObjectName("endButton");
// 连接按钮点击信号到槽函数
connect(endButton, &QPushButton::clicked, this, &MainWindow::OnEndButtonClicked);
QTreeWidget *modelTree = new QTreeWidget(this);
modelTree->setObjectName("modelTree");
modelTree->setHeaderLabel("Models List");
modelTree->setMaximumWidth(200);
QTreeWidget *serviceTree = new QTreeWidget(this);
serviceTree->setObjectName("serviceTree");
serviceTree->setHeaderLabel("Services List");
serviceTree->setMaximumWidth(200);
QTextEdit *textEdit = new QTextEdit(this);
textEdit->setObjectName("textEdit");
textEdit->setReadOnly(true);
// 设置文本编辑器的编码格式
//QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
// 连接滚动条信号以检测用户滚动
connect(textEdit->verticalScrollBar(), &QScrollBar::valueChanged, this,
&MainWindow::onTextEditScrolled);
QProgressBar *progressBar = new QProgressBar(this);
progressBar->setObjectName("progressBar");
// 布局设置
QVBoxLayout *mainLayout = new QVBoxLayout;
QHBoxLayout *topLayout = new QHBoxLayout;
QHBoxLayout *middleLayout = new QHBoxLayout;
// 上部分布局
topLayout->addWidget(label);
topLayout->addWidget(comboBox);
topLayout->addWidget(runPauseButton);
topLayout->addWidget(endButton);
// 中间部分布局
QVBoxLayout *treeLayout = new QVBoxLayout;
treeLayout->addWidget(modelTree);
treeLayout->addWidget(serviceTree);
middleLayout->addLayout(treeLayout);
middleLayout->addWidget(textEdit);
// 添加布局到主布局
mainLayout->addLayout(topLayout);
mainLayout->addLayout(middleLayout);
mainLayout->addWidget(progressBar);
// 设置中心窗口的布局
QWidget *centralWidget = new QWidget(this);
centralWidget->setLayout(mainLayout);
setCentralWidget(centralWidget);
populateComboBoxWithFiles(comboBox);
currentRunStatus = 0;
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::populateComboBoxWithFiles(QComboBox *comboBox)
{
comboBox->setObjectName("comboBox");
// 获取当前目录
QDir dir(QDir::currentPath() + "/Scenario");
// 获取所有 .sce 和 .xml 文件
QStringList filters;
filters << "*.sce" << "*.xml";
dir.setNameFilters(filters);
dir.setFilter(QDir::Files);
QFileInfoList fileList = dir.entryInfoList();
// 将文件名添加到 comboBox
QFileInfo latestFile;
for (const QFileInfo &fileInfo : fileList) {
comboBox->addItem(fileInfo.fileName());
// 找到最新的文件
if (latestFile.lastModified() < fileInfo.lastModified()) {
latestFile = fileInfo;
}
}
// 默认选中最新的文件
QTimer::singleShot(1000, [this, comboBox, latestFile]() {
// 先设置一个不同的索引
if (comboBox->count() > 0) {
comboBox->setCurrentIndex(-1); // 设置为无选中项
}
connect(comboBox, &QComboBox::currentIndexChanged, this,
&MainWindow::OnComboBoxCurrentIndexChanged);
if (!latestFile.fileName().isEmpty()) {
comboBox->setCurrentText(latestFile.fileName());
}
});
}
void MainWindow::OnRunPauseButtonClicked()
{
if (currentRunStatus == 0) {
QComboBox *comboBox = findChild<QComboBox *>("comboBox");
if (comboBox) {
QTextEdit *textEdit = findChild<QTextEdit *>("textEdit");
if (textEdit) {
textEdit->clear();
}
QProcess *process = new QProcess(this);
process->setObjectName("engineProcess");
QString selectedFile = comboBox->currentText();
QDir dir(QDir::currentPath() + "/Scenario");
QStringList args;
args << dir.filePath(selectedFile);
process->start("./XNEngine", args);
// Connect standard output
connect(process, &QProcess::readyReadStandardOutput, this, &MainWindow::OnEngineOutput);
// Connect standard error
connect(process, &QProcess::readyReadStandardError, this,
&MainWindow::OnEngineErrorOutput);
MonitorThread *monitorThread = new MonitorThread(this);
monitorThread->setObjectName("monitorThread");
connect(monitorThread, &MonitorThread::runStatusChanged, this,
&MainWindow::OnRunStatusChanged);
connect(this, &MainWindow::Pause, monitorThread, &MonitorThread::OnPause);
connect(this, &MainWindow::Continue, monitorThread, &MonitorThread::OnContinue);
connect(this, &MainWindow::Abort, monitorThread, &MonitorThread::OnAbort);
connect(monitorThread, &MonitorThread::abortProcess, this, &MainWindow::OnEndProcess);
monitorThread->start();
}
} else if (currentRunStatus == 1) {
emit Pause();
} else if (currentRunStatus == 2) {
emit Continue();
}
OnRunStatusChanged(currentRunStatus);
}
void MainWindow::OnEngineOutput()
{
QProcess *process = findChild<QProcess *>("engineProcess");
if (process) {
QString output = QString::fromUtf8(process->readAllStandardOutput());
QTextEdit *textEdit = findChild<QTextEdit *>("textEdit");
if (textEdit) {
applyAnsiColors(textEdit, output);
}
}
}
void MainWindow::applyAnsiColors(QTextEdit *textEdit, const QString &text)
{
QRegularExpression regex("\033\\[([0-9;]+)m");
QRegularExpressionMatchIterator i = regex.globalMatch(text);
QTextCursor cursor(textEdit->textCursor());
cursor.movePosition(QTextCursor::End);
int lastPos = 0;
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
QStringList codes = match.captured(1).split(';');
QString color = "white"; // Default color
for (const QString &code : codes) {
if (code == "31")
color = "red";
else if (code == "32")
color = "green";
else if (code == "33")
color = "yellow";
else if (code == "34")
color = "blue";
else if (code == "35")
color = "magenta";
else if (code == "36")
color = "cyan";
else if (code == "37")
color = "white";
else if (code == "0")
color = "white"; // Reset to default
}
// Insert text before the ANSI code
cursor.insertText(text.mid(lastPos, match.capturedStart() - lastPos));
// Set the color for the text after the ANSI code
QTextCharFormat format;
format.setForeground(QColor(color));
cursor.setCharFormat(format);
lastPos = match.capturedEnd();
}
// Insert remaining text
cursor.insertText(text.mid(lastPos));
// 删除多余的行
limitTextEditLines(textEdit, 500);
// 如果用户没有手动滚动,则滚动到末尾
if (!userScrolled) {
cursor.movePosition(QTextCursor::End);
textEdit->setTextCursor(cursor);
textEdit->ensureCursorVisible();
}
}
void MainWindow::limitTextEditLines(QTextEdit *textEdit, int maxLines)
{
QTextDocument *doc = textEdit->document();
while (doc->blockCount() > maxLines) {
QTextCursor cursor(doc);
cursor.select(QTextCursor::BlockUnderCursor);
cursor.removeSelectedText();
cursor.deleteChar(); // Remove the newline character
}
}
void MainWindow::OnEngineErrorOutput()
{
QProcess *process = findChild<QProcess *>("engineProcess");
if (process) {
// 直接使用 fromUtf8 处理错误输出
QString errorOutput = QString::fromUtf8(process->readAllStandardError());
QTextEdit *textEdit = findChild<QTextEdit *>("textEdit");
if (textEdit) {
applyAnsiColors(textEdit, errorOutput);
}
}
}
void MainWindow::OnRunStatusChanged(int status)
{
QPushButton *runPauseButton = findChild<QPushButton *>("runPauseButton");
if (runPauseButton) {
if (status == 0) {
runPauseButton->setText("Run");
runPauseButton->setEnabled(true);
currentRunStatus = 0;
} else if (status == 1) {
runPauseButton->setText("Pause");
runPauseButton->setEnabled(true);
currentRunStatus = 1;
} else if (status == 2) {
runPauseButton->setText("Continue");
runPauseButton->setEnabled(true);
currentRunStatus = 2;
} else if (status == 3) {
runPauseButton->setText("Run");
runPauseButton->setEnabled(true);
currentRunStatus = 0;
} else {
runPauseButton->setEnabled(false);
currentRunStatus = 0;
}
}
}
void MainWindow::OnEndButtonClicked()
{
if (currentRunStatus != 0) {
emit Abort();
OnEndProcess();
}
}
void MainWindow::OnEndProcess()
{
QProcess *process = this->findChild<QProcess *>("engineProcess");
QTextEdit *textEdit = this->findChild<QTextEdit *>("textEdit");
if (process) {
QTimer::singleShot(30000, [this, process, textEdit]() {
if (textEdit) {
textEdit->append("XNEngine has been forcibly terminated!");
}
process->kill();
});
process->waitForFinished();
if (textEdit) {
textEdit->append("XNEngine exited normally!");
}
delete process;
}
MonitorThread *monitorThread = this->findChild<MonitorThread *>("monitorThread");
if (monitorThread) {
monitorThread->wait();
delete monitorThread;
}
OnRunStatusChanged(3);
}
void MainWindow::OnComboBoxCurrentIndexChanged(int index)
{
// 处理 comboBox 的当前索引改变事件
QComboBox *comboBox = findChild<QComboBox *>("comboBox");
if (comboBox) {
comboBox->setEnabled(false); // Disable the combo box
QTreeWidget *modelTree = findChild<QTreeWidget *>("modelTree");
if (modelTree) {
modelTree->clear();
}
QTreeWidget *serviceTree = findChild<QTreeWidget *>("serviceTree");
if (serviceTree) {
serviceTree->clear();
}
QString selectedFile = comboBox->currentText();
// 更新文本编辑器或进度条
QPushButton *runPauseButton = findChild<QPushButton *>("runPauseButton");
if (runPauseButton) {
runPauseButton->setEnabled(false); // Disable the run/pause button
}
QDir dir(QDir::currentPath() + "/Scenario");
QFile file(dir.filePath(selectedFile));
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox::warning(this, "Warning", "Failed to open the file!");
return;
}
QString xmlContent = file.readAll();
file.close();
// Process the XML content as needed
// For example, you could parse it using QDomDocument or any other XML parser
QDomDocument xmlDoc;
if (!xmlDoc.setContent(xmlContent)) {
// Handle error parsing the XML
comboBox->setEnabled(true); // Enable the combo box
if (runPauseButton) {
runPauseButton->setEnabled(true); // Enable the run/pause button
}
QMessageBox::warning(this, "Warning", "Failed to parse the XML file!");
return;
}
// TODO: 解析xml文件获取模型和服务的清单,并启动校验
QDomElement root = xmlDoc.documentElement();
if (modelTree) {
for (int i = 0; i < root.elementsByTagName("ModelGroup").count(); i++) {
QDomElement models = root.elementsByTagName("ModelGroup").at(i).toElement();
QString modelGroupName = models.attribute("Name");
modelTree->addTopLevelItem(new QTreeWidgetItem(QStringList() << modelGroupName));
for (QDomElement model = models.firstChildElement("Model"); !model.isNull();
model = model.nextSiblingElement("Model")) {
QString modelName = model.attribute("Name");
//添加到modelTree的子节点
modelTree->topLevelItem(i)->addChild(
new QTreeWidgetItem(QStringList() << modelName));
}
//默认展开modelTree的第一个子节点
modelTree->topLevelItem(i)->setExpanded(true);
}
}
if (serviceTree) {
QDomElement services = root.firstChildElement("ServicesList");
for (QDomElement service = services.firstChildElement("Service"); !service.isNull();
service = service.nextSiblingElement("Service")) {
QString serviceName = service.attribute("Name");
serviceTree->addTopLevelItem(new QTreeWidgetItem(QStringList() << serviceName));
}
}
QTextEdit *textEdit = findChild<QTextEdit *>("textEdit");
if (textEdit) {
textEdit->clear();
}
QProcess *process = new QProcess(this);
process->setObjectName("engineProcess");
QStringList args;
args << dir.filePath(selectedFile) << "-test";
connect(process, &QProcess::readyReadStandardOutput, this, &MainWindow::OnEngineOutput);
connect(process, &QProcess::readyReadStandardError, this, &MainWindow::OnEngineErrorOutput);
QTimer::singleShot(1000, [=]() {
process->start("./XNEngine", args);
process->waitForFinished();
comboBox->setEnabled(true); // Enable the combo box
if (runPauseButton) {
runPauseButton->setEnabled(true); // Enable the run/pause button
}
delete process;
});
}
}
void MainWindow::onTextEditScrolled(int value)
{
QScrollBar *scrollBar = qobject_cast<QScrollBar *>(sender());
if (scrollBar) {
int max = scrollBar->maximum();
int threshold = 5; // 允许的误差值
// 如果滚动条接近底部,设置 userScrolled 为 false
userScrolled = (value < max - threshold);
// 如果用户滚动到接近末尾,重置 userScrolled
if (value >= max - threshold) {
userScrolled = false;
}
}
}