结构框图

sequenceDiagram
    participant User
    participant GUI
    participant Process
    
    User->>GUI: 点击"浏览"按钮
    GUI->>User: 显示文件选择对话框
    User->>GUI: 选择valid.launch
    GUI->>GUI: 进入FileSelected状态
    
    User->>GUI: 点击"执行"按钮
    GUI->>Process: 启动roslaunch
    Process-->>GUI: 进程启动成功
    GUI->>GUI: 进入Running状态
    
    loop 日志输出
        Process-->>GUI: 实时传输标准输出
    end
    
    Process-->>GUI: 进程退出(返回码0)
    GUI->>GUI: 进入Completed状态
    
    User->>GUI: 点击"×"删除按钮
    GUI->>Process: 终止进程(如果运行中)
    GUI->>GUI: 进入Removed状态

代码讲解

以下是基于Mermaid状态图的核心逻辑代码实现,结合状态图的关键部分进行详细说明:

状态图关键部分与代码对应关系

Idle 状态

  • 描述:初始状态,未选择有效launch文件。
  • 关键代码

    src/LaunchItem.cpp 8 lines
    // LaunchItem构造函数
    LaunchItem::LaunchItem(QWidget *parent) : 
        QWidget(parent), ui(new Ui::LaunchItem), process(new QProcess(this)) {
      
        ui->setupUi(this);
        ui->lineEdit->clear(); // 清空路径输入框
        ui->textEdit->clear();  // 清空输出窗口
    }
    

FileSelected 状态

  • 描述:用户通过浏览按钮选择了一个有效的launch文件。
  • 关键代码

    src/LaunchItem.cpp 11 lines
    void LaunchItem::onBrowseClicked() {
        QString path = QFileDialog::getOpenFileName(
            this, "选择Launch文件", 
            QDir::homePath(), 
            "ROS Launch Files (*.launch)"
        );
        if (!path.isEmpty()) {
            ui->lineEdit->setText(path); // 更新路径输入框
            // 此时进入FileSelected状态
        }
    }
    

Executing 状态

  • 描述:用户点击“执行”按钮,尝试启动roslaunch进程。
  • 关键代码

    src/LaunchItem.cpp 13 lines
    void LaunchItem::onExecuteClicked() {
        QString launchPath = ui->lineEdit->text();
        if (launchPath.isEmpty() || !QFile::exists(launchPath)) {
            ui->textEdit->append("错误:无效的launch文件路径");
            return; // 进入Error状态
        }
    
        QString cmd = QString("source /opt/ros/noetic/setup.bash && roslaunch %1")
                      .arg(launchPath);
      
        process->start("bash", QStringList() << "-c" << cmd);
        // 进入Executing状态
    }
    

Running 状态

  • 描述:roslaunch进程成功启动,正在运行并输出日志。
  • 关键代码

    src/LaunchItem.cpp 13 lines
    void LaunchItem::readProcessOutput() {
        QString output = process->readAllStandardOutput();
        QString error = process->readAllStandardError();
      
        if (!output.isEmpty()) {
            ui->textEdit->append(output); // 实时输出标准日志
        }
        if (!error.isEmpty()) {
            ui->textEdit->setTextColor(Qt::red);
            ui->textEdit->append(error);  // 实时输出错误日志
            ui->textEdit->setTextColor(Qt::black);
        }
    }
    

Completed 状态

  • 描述:roslaunch进程正常退出(返回码0)。
  • 关键代码

    src/LaunchItem.cpp 9 lines
    connect(process, QOverload<int>::of(&QProcess::finished), [this](int exitCode) {
        if (exitCode == 0) {
            ui->textEdit->append("进程正常退出");
            // 进入Completed状态
        } else {
            ui->textEdit->append("进程异常退出,返回码: " + QString::number(exitCode));
            // 进入Error状态
        }
    });
    

Error 状态

  • 描述:出现错误(无效路径或进程异常退出)。
  • 关键代码

    src/LaunchItem.cpp 9 lines
    void LaunchItem::onExecuteClicked() {
        QString launchPath = ui->lineEdit->text();
        if (launchPath.isEmpty() || !QFile::exists(launchPath)) {
            ui->textEdit->append("错误:无效的launch文件路径");
            // 进入Error状态
            return;
        }
        // ...
    }
    

Removed 状态

  • 描述:用户点击“×”按钮,删除当前LaunchItem。
  • 关键代码

    src/MainWindow.cpp 8 lines
    void LaunchItem::removeLaunchItem(QWidget *item) {
        if (process->state() == QProcess::Running) {
            process->terminate(); // 终止进程
            process->waitForFinished(1000);
        }
        item->deleteLater(); // 删除UI组件
        // 进入Removed状态
    }
    

状态流转的核心逻辑

从 Idle 到 FileSelected

  • 触发条件:用户通过浏览按钮选择了一个有效的launch文件。
  • 代码实现

    src/LaunchItem.cpp 7 lines
    void LaunchItem::onBrowseClicked() {
        QString path = QFileDialog::getOpenFileName(...);
        if (!path.isEmpty()) {
            ui->lineEdit->setText(path); // 更新路径输入框
            // 进入FileSelected状态
        }
    }
    

从 FileSelected 到 Executing

  • 触发条件:用户点击“执行”按钮。
  • 代码实现

    src/LaunchItem.cpp 9 lines
    void LaunchItem::onExecuteClicked() {
        QString launchPath = ui->lineEdit->text();
        if (launchPath.isEmpty() || !QFile::exists(launchPath)) {
            ui->textEdit->append("错误:无效的launch文件路径");
            return; // 进入Error状态
        }
        process->start("bash", QStringList() << "-c" << cmd);
        // 进入Executing状态
    }
    

从 Executing 到 Running

  • 触发条件:roslaunch进程成功启动。
  • 代码实现

    src/LaunchItem.cpp 4 lines
    connect(process, &QProcess::started, [this] {
        ui->textEdit->append("进程已启动");
        // 进入Running状态
    });
    

从 Running 到 Completed/Error

  • 触发条件:进程退出(正常或异常)。
  • 代码实现

    src/LaunchItem.cpp 9 lines
    connect(process, QOverload<int>::of(&QProcess::finished), [this](int exitCode) {
        if (exitCode == 0) {
            ui->textEdit->append("进程正常退出");
            // 进入Completed状态
        } else {
            ui->textEdit->append("进程异常退出,返回码: " + QString::number(exitCode));
            // 进入Error状态
        }
    });
    

从任何状态到 Removed

  • 触发条件:用户点击“×”按钮。
  • 代码实现

    src/LaunchItem.cpp 4 lines
    connect(ui->btnRemove, &QPushButton::clicked, [this] {
        emit removeRequested(); // 通知主窗口删除当前item
        // 进入Removed状态
    });
    

核心逻辑总结

状态触发条件关键代码
Idle初始化ui->lineEdit->clear(); ui->textEdit->clear();
FileSelected用户选择有效launch文件ui->lineEdit->setText(path);
Executing用户点击“执行”按钮process->start("bash", QStringList() << "-c" << cmd);
Running进程成功启动connect(process, &QProcess::started, [this] { ... });
Completed进程正常退出(返回码0)connect(process, QOverload<int>::of(&QProcess::finished), [this](int code) { ... });
Error无效路径或进程异常退出ui->textEdit->append("错误:无效的launch文件路径");
Removed用户点击“×”按钮item->deleteLater();

通过以上代码和状态流转的对应关系,可以清晰地理解整个系统的核心逻辑。如果需要进一步扩展功能(如批量执行、状态持久化等),可以基于此框架进行开发。