#include "mainwindow.h" #include "./ui_mainwindow.h" #include #include "MonitorThread.h" #include #include #include #include #include #include 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("comboBox"); if (comboBox) { QTextEdit *textEdit = findChild("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("engineProcess"); if (process) { QString output = QString::fromUtf8(process->readAllStandardOutput()); QTextEdit *textEdit = findChild("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("engineProcess"); if (process) { // 直接使用 fromUtf8 处理错误输出 QString errorOutput = QString::fromUtf8(process->readAllStandardError()); QTextEdit *textEdit = findChild("textEdit"); if (textEdit) { applyAnsiColors(textEdit, errorOutput); } } } void MainWindow::OnRunStatusChanged(int status) { QPushButton *runPauseButton = findChild("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("engineProcess"); QTextEdit *textEdit = this->findChild("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"); if (monitorThread) { monitorThread->wait(); delete monitorThread; } OnRunStatusChanged(3); } void MainWindow::OnComboBoxCurrentIndexChanged(int index) { // 处理 comboBox 的当前索引改变事件 QComboBox *comboBox = findChild("comboBox"); if (comboBox) { comboBox->setEnabled(false); // Disable the combo box QTreeWidget *modelTree = findChild("modelTree"); if (modelTree) { modelTree->clear(); } QTreeWidget *serviceTree = findChild("serviceTree"); if (serviceTree) { serviceTree->clear(); } QString selectedFile = comboBox->currentText(); // 更新文本编辑器或进度条 QPushButton *runPauseButton = findChild("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("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(sender()); if (scrollBar) { int max = scrollBar->maximum(); int threshold = 5; // 允许的误差值 // 如果滚动条接近底部,设置 userScrolled 为 false userScrolled = (value < max - threshold); // 如果用户滚动到接近末尾,重置 userScrolled if (value >= max - threshold) { userScrolled = false; } } }