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