463 lines
14 KiB
C++
Executable File
463 lines
14 KiB
C++
Executable File
#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;
|
||
}
|
||
}
|
||
}
|