diff --git a/HPPA/HPPA.vcxproj b/HPPA/HPPA.vcxproj index 349d2e9..9f13fa9 100644 --- a/HPPA/HPPA.vcxproj +++ b/HPPA/HPPA.vcxproj @@ -164,6 +164,7 @@ Create + @@ -262,6 +263,7 @@ + diff --git a/HPPA/HPPA.vcxproj.filters b/HPPA/HPPA.vcxproj.filters index 79bc464..4ccbd4e 100644 --- a/HPPA/HPPA.vcxproj.filters +++ b/HPPA/HPPA.vcxproj.filters @@ -247,6 +247,9 @@ Source Files + + Source Files + @@ -405,6 +408,9 @@ Header Files + + Header Files + diff --git a/HPPA/TaskTreeModel.cpp b/HPPA/TaskTreeModel.cpp new file mode 100644 index 0000000..87d76f1 --- /dev/null +++ b/HPPA/TaskTreeModel.cpp @@ -0,0 +1,584 @@ +#include "TaskTreeModel.h" +#include +#include +#include + +// ==================== TreeNode 实现 ==================== + +TreeNode::TreeNode(TreeNode* parent) + : m_parent(parent) +{ +} + +TreeNode::~TreeNode() +{ + qDeleteAll(m_children); +} + +void TreeNode::appendChild(TreeNode* child) +{ + m_children.append(child); +} + +TreeNode* TreeNode::child(int row) +{ + if (row < 0 || row >= m_children.size()) + return nullptr; + return m_children.at(row); +} + +int TreeNode::childCount() const +{ + return m_children.size(); +} + +int TreeNode::row() const +{ + if (m_parent) { + return m_parent->m_children.indexOf(const_cast(this)); + } + return 0; +} + +TreeNode* TreeNode::parentNode() +{ + return m_parent; +} + +// ==================== TaskTreeModel 实现 ==================== + +TaskTreeModel::TaskTreeModel(QObject* parent) + : QAbstractItemModel(parent) + , m_rootNode(new TreeNode()) +{ + m_rootNode->nodeType = TreeNodeType::Root; +} + +TaskTreeModel::~TaskTreeModel() +{ + delete m_rootNode; +} + +QModelIndex TaskTreeModel::index(int row, int column, const QModelIndex& parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + TreeNode* parentNode; + if (!parent.isValid()) + parentNode = m_rootNode; + else + parentNode = static_cast(parent.internalPointer()); + + TreeNode* childNode = parentNode->child(row); + if (childNode) + return createIndex(row, column, childNode); + + return QModelIndex(); +} + +QModelIndex TaskTreeModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) + return QModelIndex(); + + TreeNode* childNode = static_cast(index.internalPointer()); + TreeNode* parentNode = childNode->parentNode(); + + if (parentNode == m_rootNode) + return QModelIndex(); + + return createIndex(parentNode->row(), 0, parentNode); +} + +int TaskTreeModel::rowCount(const QModelIndex& parent) const +{ + TreeNode* parentNode; + if (!parent.isValid()) + parentNode = m_rootNode; + else + parentNode = static_cast(parent.internalPointer()); + + return parentNode->childCount(); +} + +int TaskTreeModel::columnCount(const QModelIndex& parent) const +{ + Q_UNUSED(parent) + return ColumnCount; +} + +QVariant TaskTreeModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + TreeNode* node = static_cast(index.internalPointer()); + + if (role == Qt::DisplayRole) { + if (node->nodeType == TreeNodeType::Task && node->taskData) { + const TimedTask& task = *node->taskData; + switch (index.column()) { + case ColName: + return QString::fromLocal8Bit("定时任务 %1").arg(task.id); + case ColScheduledTime: + return task.scheduledTime.toString("yyyy-MM-dd HH:mm:ss"); + case ColStartTime: + return task.startTime.isValid() ? + task.startTime.toString("HH:mm:ss") : "-"; + case ColEndTime: + return task.endTime.isValid() ? + task.endTime.toString("HH:mm:ss") : "-"; + case ColDuration: + return formatDuration(task.durationMinutes); + case ColStatus: + return statusToString(task.status); + case ColProgress: { + int finished = 0; + for (const auto& sub : task.subTasks) { + if (sub.status == TaskStatus::Finished) finished++; + } + return QString("%1/%2").arg(finished).arg(task.subTasks.size()); + } + case ColPath: + return task.savePath; + } + } + else if (node->nodeType == TreeNodeType::SubTask && node->subTaskData) { + const SubTask& subTask = *node->subTaskData; + switch (index.column()) { + case ColName: + return subTaskTypeToString(subTask.type); + case ColScheduledTime: + return "-"; + case ColStartTime: + return subTask.startTime.isValid() ? + subTask.startTime.toString("HH:mm:ss") : "-"; + case ColEndTime: + return subTask.endTime.isValid() ? + subTask.endTime.toString("HH:mm:ss") : "-"; + case ColDuration: + return formatDuration(subTask.durationMinutes); + case ColStatus: + return statusToString(subTask.status); + case ColProgress: + return "-"; + case ColPath: + return "-"; + } + } + } + else if (role == Qt::BackgroundRole) { + TaskStatus status = TaskStatus::Waiting; + if (node->nodeType == TreeNodeType::Task && node->taskData) { + status = node->taskData->status; + } + else if (node->nodeType == TreeNodeType::SubTask && node->subTaskData) { + status = node->subTaskData->status; + } + return QBrush(statusColor(status)); + } + else if (role == Qt::ForegroundRole) { + TaskStatus status = TaskStatus::Waiting; + if (node->nodeType == TreeNodeType::Task && node->taskData) { + status = node->taskData->status; + } + else if (node->nodeType == TreeNodeType::SubTask && node->subTaskData) { + status = node->subTaskData->status; + } + if (status == TaskStatus::Running) { + return QBrush(QColor(0, 100, 0)); // 深绿色文字 + } + } + else if (role == Qt::FontRole) { + QFont font; + if (node->nodeType == TreeNodeType::Task) { + font.setBold(true); + if (node->taskData && node->taskData->status == TaskStatus::Running) { + font.setItalic(true); + } + } + else if (node->nodeType == TreeNodeType::SubTask) { + if (node->subTaskData && node->subTaskData->status == TaskStatus::Running) { + font.setBold(true); + } + } + return font; + } + else if (role == Qt::DecorationRole && index.column() == ColName) { + if (node->nodeType == TreeNodeType::Task) { + // 可以返回任务图标 + // return QIcon(":/icons/task.png"); + } + else if (node->nodeType == TreeNodeType::SubTask && node->subTaskData) { + // 根据子任务类型返回不同图标 + switch (node->subTaskData->type) { + case SubTaskType::HyperSpectual400_1000nm: + case SubTaskType::HyperSpectual1000_1700nm: + // return QIcon(":/icons/hyperspectral.png"); + break; + case SubTaskType::SingleLensReflex: + // return QIcon(":/icons/camera.png"); + break; + case SubTaskType::DepthCamera: + // return QIcon(":/icons/depth.png"); + break; + } + } + } + else if (role == Qt::TextAlignmentRole) { + if (index.column() == ColName || index.column() == ColPath) { + return static_cast(Qt::AlignLeft | Qt::AlignVCenter); + } + return Qt::AlignCenter; + } + else if (role == Qt::ToolTipRole) { + if (node->nodeType == TreeNodeType::Task && node->taskData) { + return QString::fromLocal8Bit("任务ID: %1\n保存路径: %2\n预热时间: %3 分钟") + .arg(node->taskData->id) + .arg(node->taskData->savePath) + .arg(node->taskData->HalogenLampPreheatingTime_Minute); + } + else if (node->nodeType == TreeNodeType::SubTask && node->subTaskData) { + QString tip = QString::fromLocal8Bit("类型: %1\n路径文件: %2") + .arg(subTaskTypeToString(node->subTaskData->type)) + .arg(node->subTaskData->pathLineFilePath); + + if (node->subTaskData->type == SubTaskType::HyperSpectual400_1000nm || + node->subTaskData->type == SubTaskType::HyperSpectual1000_1700nm) { + tip += QString::fromLocal8Bit("\n帧率: %1\n曝光时间: %2") + .arg(node->subTaskData->frameRate) + .arg(node->subTaskData->exposureTime); + } + return tip; + } + } + + return QVariant(); +} + +QVariant TaskTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal || role != Qt::DisplayRole) + return QVariant(); + + switch (section) { + case ColName: return QString::fromLocal8Bit("名称"); + case ColScheduledTime: return QString::fromLocal8Bit("计划时间"); + case ColStartTime: return QString::fromLocal8Bit("开始时间"); + case ColEndTime: return QString::fromLocal8Bit("结束时间"); + case ColDuration: return QString::fromLocal8Bit("耗时"); + case ColStatus: return QString::fromLocal8Bit("状态"); + case ColProgress: return QString::fromLocal8Bit("进度"); + case ColPath: return QString::fromLocal8Bit("路径"); + } + return QVariant(); +} + +Qt::ItemFlags TaskTreeModel::flags(const QModelIndex& index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +void TaskTreeModel::setTasks(const QVector& tasks) +{ + beginResetModel(); + + m_tasks = tasks; + clearTree(); + setupModelData(); + + endResetModel(); +} + +void TaskTreeModel::setupModelData() +{ + for (int i = 0; i < m_tasks.size(); ++i) { + TimedTask& task = m_tasks[i]; + + // 创建任务节点 + TreeNode* taskNode = new TreeNode(m_rootNode); + taskNode->nodeType = TreeNodeType::Task; + taskNode->taskId = task.id; + taskNode->taskData = &task; + m_rootNode->appendChild(taskNode); + + // 为每个子任务创建节点 + for (int j = 0; j < task.subTasks.size(); ++j) { + SubTask& subTask = task.subTasks[j]; + + TreeNode* subTaskNode = new TreeNode(taskNode); + subTaskNode->nodeType = TreeNodeType::SubTask; + subTaskNode->taskId = task.id; + subTaskNode->subTaskIndex = j; + subTaskNode->subTaskData = &subTask; + taskNode->appendChild(subTaskNode); + } + } +} + +void TaskTreeModel::clearTree() +{ + // 删除所有子节点 + while (m_rootNode->childCount() > 0) { + delete m_rootNode->child(0); + } +} + +void TaskTreeModel::updateTask(const TimedTask& task) +{ + int dataIndex = findTaskDataIndex(task.id); + if (dataIndex < 0) return; + + // 更新数据 + m_tasks[dataIndex] = task; + + // 重新设置指针(因为数据被复制了) + TreeNode* taskNode = findTaskNode(task.id); + if (taskNode) { + taskNode->taskData = &m_tasks[dataIndex]; + + // 更新子任务指针 + for (int i = 0; i < taskNode->childCount() && i < m_tasks[dataIndex].subTasks.size(); ++i) { + TreeNode* subNode = taskNode->child(i); + if (subNode) { + subNode->subTaskData = &m_tasks[dataIndex].subTasks[i]; + } + } + } + + // 通知视图更新任务行 + QModelIndex taskIndex = getTaskIndex(task.id); + if (taskIndex.isValid()) { + QModelIndex topLeft = index(taskIndex.row(), 0, taskIndex.parent()); + QModelIndex bottomRight = index(taskIndex.row(), ColumnCount - 1, taskIndex.parent()); + emit dataChanged(topLeft, bottomRight); + + // 更新所有子任务行 + int subCount = rowCount(taskIndex); + if (subCount > 0) { + QModelIndex subTopLeft = index(0, 0, taskIndex); + QModelIndex subBottomRight = index(subCount - 1, ColumnCount - 1, taskIndex); + emit dataChanged(subTopLeft, subBottomRight); + } + } + + emit taskUpdated(task.id); +} + +void TaskTreeModel::updateSubTask(int taskId, int subTaskIndex, const SubTask& subTask) +{ + int dataIndex = findTaskDataIndex(taskId); + if (dataIndex < 0 || subTaskIndex >= m_tasks[dataIndex].subTasks.size()) + return; + + m_tasks[dataIndex].subTasks[subTaskIndex] = subTask; + + // 重新设置指针 + TreeNode* subNode = findSubTaskNode(taskId, subTaskIndex); + if (subNode) { + subNode->subTaskData = &m_tasks[dataIndex].subTasks[subTaskIndex]; + } + + QModelIndex subTaskModelIndex = getSubTaskIndex(taskId, subTaskIndex); + if (subTaskModelIndex.isValid()) { + QModelIndex topLeft = index(subTaskModelIndex.row(), 0, subTaskModelIndex.parent()); + QModelIndex bottomRight = index(subTaskModelIndex.row(), ColumnCount - 1, subTaskModelIndex.parent()); + emit dataChanged(topLeft, bottomRight); + } + + // 同时更新父任务的进度显示 + QModelIndex taskIndex = getTaskIndex(taskId); + if (taskIndex.isValid()) { + QModelIndex progressIndex = index(taskIndex.row(), ColProgress, taskIndex.parent()); + emit dataChanged(progressIndex, progressIndex); + } + + emit subTaskUpdated(taskId, subTaskIndex); +} + +void TaskTreeModel::updateTaskStatus(int taskId, TaskStatus status) +{ + int dataIndex = findTaskDataIndex(taskId); + if (dataIndex < 0) return; + + m_tasks[dataIndex].status = status; + + if (status == TaskStatus::Running) { + m_tasks[dataIndex].startTime = QDateTime::currentDateTime(); + } + else if (status == TaskStatus::Finished) { + m_tasks[dataIndex].endTime = QDateTime::currentDateTime(); + if (m_tasks[dataIndex].startTime.isValid()) { + m_tasks[dataIndex].durationMinutes = + m_tasks[dataIndex].startTime.secsTo(m_tasks[dataIndex].endTime) / 60.0; + } + } + + QModelIndex taskIndex = getTaskIndex(taskId); + if (taskIndex.isValid()) { + QModelIndex topLeft = index(taskIndex.row(), 0, taskIndex.parent()); + QModelIndex bottomRight = index(taskIndex.row(), ColumnCount - 1, taskIndex.parent()); + emit dataChanged(topLeft, bottomRight); + } +} + +void TaskTreeModel::updateSubTaskStatus(int taskId, int subTaskIndex, TaskStatus status) +{ + int dataIndex = findTaskDataIndex(taskId); + if (dataIndex < 0 || subTaskIndex >= m_tasks[dataIndex].subTasks.size()) + return; + + SubTask& subTask = m_tasks[dataIndex].subTasks[subTaskIndex]; + subTask.status = status; + + if (status == TaskStatus::Running) { + subTask.startTime = QDateTime::currentDateTime(); + } + else if (status == TaskStatus::Finished) { + subTask.endTime = QDateTime::currentDateTime(); + if (subTask.startTime.isValid()) { + subTask.durationMinutes = subTask.startTime.secsTo(subTask.endTime) / 60.0; + } + } + + QModelIndex subTaskModelIndex = getSubTaskIndex(taskId, subTaskIndex); + if (subTaskModelIndex.isValid()) { + QModelIndex topLeft = index(subTaskModelIndex.row(), 0, subTaskModelIndex.parent()); + QModelIndex bottomRight = index(subTaskModelIndex.row(), ColumnCount - 1, subTaskModelIndex.parent()); + emit dataChanged(topLeft, bottomRight); + } + + // 更新父任务进度 + QModelIndex taskIndex = getTaskIndex(taskId); + if (taskIndex.isValid()) { + QModelIndex progressIndex = index(taskIndex.row(), ColProgress, taskIndex.parent()); + emit dataChanged(progressIndex, progressIndex); + } +} + +TimedTask* TaskTreeModel::getTask(int taskId) +{ + int idx = findTaskDataIndex(taskId); + if (idx >= 0) { + return &m_tasks[idx]; + } + return nullptr; +} + +SubTask* TaskTreeModel::getSubTask(int taskId, int subTaskIndex) +{ + TimedTask* task = getTask(taskId); + if (task && subTaskIndex >= 0 && subTaskIndex < task->subTasks.size()) { + return &task->subTasks[subTaskIndex]; + } + return nullptr; +} + +QModelIndex TaskTreeModel::getTaskIndex(int taskId) const +{ + for (int i = 0; i < m_rootNode->childCount(); ++i) { + TreeNode* node = m_rootNode->child(i); + if (node && node->taskId == taskId) { + return createIndex(i, 0, node); + } + } + return QModelIndex(); +} + +QModelIndex TaskTreeModel::getSubTaskIndex(int taskId, int subTaskIndex) const +{ + TreeNode* taskNode = findTaskNode(taskId); + if (taskNode && subTaskIndex < taskNode->childCount()) { + TreeNode* subNode = taskNode->child(subTaskIndex); + if (subNode) { + return createIndex(subTaskIndex, 0, subNode); + } + } + return QModelIndex(); +} + +TreeNode* TaskTreeModel::findTaskNode(int taskId) const +{ + for (int i = 0; i < m_rootNode->childCount(); ++i) { + TreeNode* node = m_rootNode->child(i); + if (node && node->taskId == taskId) { + return node; + } + } + return nullptr; +} + +TreeNode* TaskTreeModel::findSubTaskNode(int taskId, int subTaskIndex) const +{ + TreeNode* taskNode = findTaskNode(taskId); + if (taskNode && subTaskIndex < taskNode->childCount()) { + return taskNode->child(subTaskIndex); + } + return nullptr; +} + +int TaskTreeModel::findTaskDataIndex(int taskId) const +{ + for (int i = 0; i < m_tasks.size(); ++i) { + if (m_tasks[i].id == taskId) { + return i; + } + } + return -1; +} + +QString TaskTreeModel::statusToString(TaskStatus status) const +{ + switch (status) { + case TaskStatus::Waiting: return QString::fromLocal8Bit("等待中"); + case TaskStatus::Running: return QString::fromLocal8Bit("运行中"); + case TaskStatus::Finished: return QString::fromLocal8Bit("已完成"); + case TaskStatus::Skiped: return QString::fromLocal8Bit("已跳过"); + } + return "未知"; +} + +QString TaskTreeModel::subTaskTypeToString(SubTaskType type) const +{ + switch (type) { + case SubTaskType::HyperSpectual400_1000nm: return QString::fromLocal8Bit("高光谱 400-1000nm"); + case SubTaskType::HyperSpectual1000_1700nm: return QString::fromLocal8Bit("高光谱 1000-1700nm"); + case SubTaskType::SingleLensReflex: return QString::fromLocal8Bit("单反相机"); + case SubTaskType::DepthCamera: return QString::fromLocal8Bit("深度相机"); + } + return "未知类型"; +} + +QString TaskTreeModel::formatDuration(double minutes) const +{ + if (minutes <= 0) return "-"; + + int hours = static_cast(minutes) / 60; + int mins = static_cast(minutes) % 60; + int secs = static_cast((minutes - static_cast(minutes)) * 60); + + if (hours > 0) { + return QString::fromLocal8Bit(("%1时%2分%3秒")).arg(hours).arg(mins).arg(secs); + } + else if (mins > 0) { + return QString::fromLocal8Bit(("%1分%2秒")).arg(mins).arg(secs); + } + else { + return QString::fromLocal8Bit(("%1秒")).arg(secs); + } +} + +QColor TaskTreeModel::statusColor(TaskStatus status) const +{ + switch (status) { + case TaskStatus::Waiting: return QColor(255, 255, 230); // 浅黄 + case TaskStatus::Running: return QColor(200, 255, 200); // 浅绿 + case TaskStatus::Finished: return QColor(220, 220, 255); // 浅蓝 + case TaskStatus::Skiped: return QColor(220, 220, 220); // 浅灰 + } + return QColor(255, 255, 255); +} diff --git a/HPPA/TaskTreeModel.h b/HPPA/TaskTreeModel.h new file mode 100644 index 0000000..e60ed38 --- /dev/null +++ b/HPPA/TaskTreeModel.h @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#include +#include "TimedDataCollectionDataStructures.h" + +// 树节点类型 +enum class TreeNodeType { + Root, + Task, + SubTask +}; + +// 树节点数据 +class TreeNode +{ +public: + explicit TreeNode(TreeNode* parent = nullptr); + ~TreeNode(); + + void appendChild(TreeNode* child); + TreeNode* child(int row); + int childCount() const; + int row() const; + TreeNode* parentNode(); + + TreeNodeType nodeType = TreeNodeType::Root; + int taskId = -1; // 任务ID + int subTaskIndex = -1; // 子任务索引(仅子任务节点有效) + + // 直接存储数据指针,方便更新 + TimedTask* taskData = nullptr; + SubTask* subTaskData = nullptr; + +private: + QVector m_children; + TreeNode* m_parent; +}; + +// 树形模型 +class TaskTreeModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum Column { + ColName = 0, // 任务名称/子任务类型 + ColScheduledTime, // 计划时间 + ColStartTime, // 开始时间 + ColEndTime, // 结束时间 + ColDuration, // 耗时 + ColStatus, // 状态 + ColProgress, // 进度(仅任务有效) + ColPath, // 路径 + ColumnCount + }; + + explicit TaskTreeModel(QObject* parent = nullptr); + ~TaskTreeModel(); + + // QAbstractItemModel 接口实现 + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& index) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + + // 数据操作 + void setTasks(const QVector& tasks); + void updateTask(const TimedTask& task); + void updateSubTask(int taskId, int subTaskIndex, const SubTask& subTask); + void updateTaskStatus(int taskId, TaskStatus status); + void updateSubTaskStatus(int taskId, int subTaskIndex, TaskStatus status); + + // 获取数据 + QVector& tasks() { return m_tasks; } + const QVector& tasks() const { return m_tasks; } + TimedTask* getTask(int taskId); + SubTask* getSubTask(int taskId, int subTaskIndex); + + // 获取模型索引 + QModelIndex getTaskIndex(int taskId) const; + QModelIndex getSubTaskIndex(int taskId, int subTaskIndex) const; + +signals: + void taskUpdated(int taskId); + void subTaskUpdated(int taskId, int subTaskIndex); + +private: + void setupModelData(); + void clearTree(); + TreeNode* findTaskNode(int taskId) const; + TreeNode* findSubTaskNode(int taskId, int subTaskIndex) const; + int findTaskDataIndex(int taskId) const; + + QString statusToString(TaskStatus status) const; + QString subTaskTypeToString(SubTaskType type) const; + QString formatDuration(double minutes) const; + QColor statusColor(TaskStatus status) const; + + TreeNode* m_rootNode; + QVector m_tasks; +}; diff --git a/HPPA/TimedDataCollection.cpp b/HPPA/TimedDataCollection.cpp index f812e97..f21f85a 100644 --- a/HPPA/TimedDataCollection.cpp +++ b/HPPA/TimedDataCollection.cpp @@ -1,169 +1,462 @@ #include "TimedDataCollection.h" -#include -#include +#include +#include +#include +#include +#include TimedDataCollection::TimedDataCollection(QWidget* parent) - : QDialog(parent) - , m_scheduler(nullptr) + : QDialog(parent) { - ui.setupUi(this); + ui.setupUi(this); - ui.treeWidget->setDragEnabled(true); // 启用拖拽 - ui.treeWidget->setAcceptDrops(true); // 接受拖放 - ui.treeWidget->setDropIndicatorShown(true); // 显示插入位置指示线 - ui.treeWidget->setDragDropMode(QAbstractItemView::InternalMove); // 内部移动 + setupUI(); - // 初始化调度器 - m_scheduler = new TaskScheduler(this); + // 初始化调度器 + m_scheduler = new TaskScheduler(this); - // 连接调度器信号 - connect(m_scheduler, &TaskScheduler::taskStarted, this, &TimedDataCollection::taskStarted); - connect(m_scheduler, &TaskScheduler::taskFinished, this, &TimedDataCollection::taskFinished); - connect(m_scheduler, &TaskScheduler::subTaskStarted, this, &TimedDataCollection::subTaskStarted); - connect(m_scheduler, &TaskScheduler::subTaskFinished, this, &TimedDataCollection::subTaskFinished); - connect(m_scheduler, &TaskScheduler::errorOccurred, this, &TimedDataCollection::errorOccurred); + setupConnections(); - // 采集相关信号透传 - connect(m_scheduler, &TaskScheduler::hyperCamParm, this, &TimedDataCollection::hyperCamParm); - connect(m_scheduler, &TaskScheduler::camParm, this, &TimedDataCollection::camParm); - connect(m_scheduler, &TaskScheduler::motorParm, this, &TimedDataCollection::motorParm); - connect(m_scheduler, &TaskScheduler::startRecordSignal, this, &TimedDataCollection::startRecordSignal); + // 读取任务文件 + readTimedTaskFromFile("D:/0tmp/3Dtest/task.json"); +} - connect(m_scheduler, &TaskScheduler::switchHalogenLampSignal, this, &TimedDataCollection::switchHalogenLampSignal); - connect(m_scheduler, &TaskScheduler::switchD65LampSignal, this, &TimedDataCollection::switchD65LampSignal); - connect(m_scheduler, &TaskScheduler::switchSlrSignal, this, &TimedDataCollection::switchSlrSignal); +void TimedDataCollection::setupUI() +{ + // 设置窗口大小 + //resize(1200, 600); + //setWindowTitle("定时数据采集系统"); - connect(this, &TimedDataCollection::sequenceCompleteSignal, m_scheduler, &TaskScheduler::sequenceCompleteSignal); - connect(this, &TimedDataCollection::Back2OriginSignal, m_scheduler, &TaskScheduler::Back2OriginSignal); + // 创建任务树形模型 + m_taskModel = new TaskTreeModel(this); - //writeRead(); - readTimedTaskFromFile("D:/0tmp/3Dtest/task.json"); + // 创建树形视图 + ui.taskTreeView->setModel(m_taskModel); - // 加载任务到调度器 - m_scheduler->loadTasks(m_loadedTasks); + // 配置树形视图 + ui.taskTreeView->setSelectionBehavior(QAbstractItemView::SelectRows); + ui.taskTreeView->setSelectionMode(QAbstractItemView::SingleSelection); + ui.taskTreeView->setAlternatingRowColors(true); + ui.taskTreeView->setAnimated(true); + ui.taskTreeView->setExpandsOnDoubleClick(true); + ui.taskTreeView->setEditTriggers(QAbstractItemView::NoEditTriggers); + ui.taskTreeView->setUniformRowHeights(true); + ui.taskTreeView->setIndentation(25); - connect(ui.run_btn, &QPushButton::clicked, this, &TimedDataCollection::startScheduler); + // 配置表头 + QHeaderView* header = ui.taskTreeView->header(); + header->setStretchLastSection(true); + header->setSectionResizeMode(QHeaderView::Interactive); + header->setDefaultAlignment(Qt::AlignCenter); - startScheduler(); + // 设置列宽 + ui.taskTreeView->setColumnWidth(TaskTreeModel::ColName, 200); + ui.taskTreeView->setColumnWidth(TaskTreeModel::ColScheduledTime, 150); + ui.taskTreeView->setColumnWidth(TaskTreeModel::ColStartTime, 100); + ui.taskTreeView->setColumnWidth(TaskTreeModel::ColEndTime, 100); + ui.taskTreeView->setColumnWidth(TaskTreeModel::ColDuration, 100); + ui.taskTreeView->setColumnWidth(TaskTreeModel::ColStatus, 100); + ui.taskTreeView->setColumnWidth(TaskTreeModel::ColProgress, 80); + + // 创建状态标签 + ui.statusLabel->setStyleSheet( + "QLabel {" + " background-color: #f0f0f0;" + " border: 1px solid #ccc;" + " border-radius: 4px;" + " padding: 8px;" + " font-size: 12px;" + "}" + ); + + // 连接按钮信号 + connect(ui.start_btn, &QPushButton::clicked, this, &TimedDataCollection::startScheduler); + connect(ui.stop_btn, &QPushButton::clicked, this, &TimedDataCollection::stopScheduler); + connect(ui.refresh_btn, &QPushButton::clicked, this, [this]() { + readTimedTaskFromFile("D:/0tmp/3Dtest/task.json"); + }); + connect(ui.expandAll_btn, &QPushButton::clicked, ui.taskTreeView, &QTreeView::expandAll); + connect(ui.collapseAll_btn, &QPushButton::clicked, ui.taskTreeView, &QTreeView::collapseAll); + + // 树视图交互信号 + connect(ui.taskTreeView, &QTreeView::clicked, + this, &TimedDataCollection::onTreeItemClicked); + connect(ui.taskTreeView, &QTreeView::doubleClicked, + this, &TimedDataCollection::onTreeItemDoubleClicked); + connect(ui.taskTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, + this, &TimedDataCollection::onTreeSelectionChanged); +} + +void TimedDataCollection::setupConnections() +{ + // 调度器信号连接 + connect(m_scheduler, &TaskScheduler::taskStarted, + this, &TimedDataCollection::onTaskStarted); + connect(m_scheduler, &TaskScheduler::taskFinished, + this, &TimedDataCollection::onTaskFinished); + connect(m_scheduler, &TaskScheduler::subTaskStarted, + this, &TimedDataCollection::onSubTaskStarted); + connect(m_scheduler, &TaskScheduler::subTaskFinished, + this, &TimedDataCollection::onSubTaskFinished); + connect(m_scheduler, &TaskScheduler::taskDataChanged, + this, &TimedDataCollection::onTaskDataChanged); + connect(m_scheduler, &TaskScheduler::errorOccurred, + this, &TimedDataCollection::onErrorOccurred); + + // 采集相关信号透传 + connect(m_scheduler, &TaskScheduler::hyperCamParm, + this, &TimedDataCollection::hyperCamParm); + connect(m_scheduler, &TaskScheduler::camParm, + this, &TimedDataCollection::camParm); + connect(m_scheduler, &TaskScheduler::motorParm, + this, &TimedDataCollection::motorParm); + connect(m_scheduler, &TaskScheduler::startRecordSignal, + this, &TimedDataCollection::startRecordSignal); + + connect(m_scheduler, &TaskScheduler::switchHalogenLampSignal, + this, &TimedDataCollection::switchHalogenLampSignal); + connect(m_scheduler, &TaskScheduler::switchD65LampSignal, + this, &TimedDataCollection::switchD65LampSignal); + connect(m_scheduler, &TaskScheduler::switchSlrSignal, + this, &TimedDataCollection::switchSlrSignal); + + connect(this, &TimedDataCollection::sequenceCompleteSignal, + m_scheduler, &TaskScheduler::sequenceCompleteSignal); + connect(this, &TimedDataCollection::Back2OriginSignal, + m_scheduler, &TaskScheduler::Back2OriginSignal); } TimedDataCollection::~TimedDataCollection() { - if (m_scheduler) { - m_scheduler->stop(); - } + if (m_scheduler) { + m_scheduler->stop(); + } } -void TimedDataCollection::subTaskCompleted(int status) +// ==================== 任务状态更新槽实现 ==================== + +void TimedDataCollection::onTaskStarted(int taskId) { - emit sequenceCompleteSignal(status); + m_currentTaskId = taskId; + m_currentSubTaskIndex = -1; + + m_taskModel->updateTaskStatus(taskId, TaskStatus::Running); + + // 展开当前运行的任务 + expandRunningTask(); + + updateStatusBar(); + emit taskStarted(taskId); } -void TimedDataCollection::onBack2Origin() +void TimedDataCollection::onTaskFinished(int taskId, bool success) { - emit Back2OriginSignal(); + TaskStatus status = success ? TaskStatus::Finished : TaskStatus::Skiped; + m_taskModel->updateTaskStatus(taskId, status); + + if (m_currentTaskId == taskId) { + m_currentTaskId = -1; + m_currentSubTaskIndex = -1; + } + + updateStatusBar(); + emit taskFinished(taskId, success); } -void TimedDataCollection::startScheduler() +void TimedDataCollection::onSubTaskStarted(int taskId, int subTaskIndex) { - if (m_scheduler) { - m_scheduler->start(); - } + m_currentSubTaskIndex = subTaskIndex; + + m_taskModel->updateSubTaskStatus(taskId, subTaskIndex, TaskStatus::Running); + + // 滚动到当前子任务 + QModelIndex subTaskIndex_model = m_taskModel->getSubTaskIndex(taskId, subTaskIndex); + if (subTaskIndex_model.isValid()) { + ui.taskTreeView->scrollTo(subTaskIndex_model); + ui.taskTreeView->setCurrentIndex(subTaskIndex_model); + } + + updateStatusBar(); + emit subTaskStarted(taskId, subTaskIndex); } -void TimedDataCollection::stopScheduler() +void TimedDataCollection::onSubTaskFinished(int taskId, int subTaskIndex) { - if (m_scheduler) { - m_scheduler->stop(); - } + m_taskModel->updateSubTaskStatus(taskId, subTaskIndex, TaskStatus::Finished); + + updateStatusBar(); + emit subTaskFinished(taskId, subTaskIndex); +} + +void TimedDataCollection::onTaskDataChanged(const TimedTask& task) +{ + // 核心:从 TaskExecutor 同步回来的完整任务数据 + m_taskModel->updateTask(task); + + updateStatusBar(); +} + +void TimedDataCollection::onErrorOccurred(const QString& error) +{ + ui.statusLabel->setText(QString::fromLocal8Bit("错误: %1").arg(error)); + ui.statusLabel->setStyleSheet( + "QLabel {" + " background-color: #ffebee;" + " border: 1px solid #f44336;" + " border-radius: 4px;" + " padding: 8px;" + " color: #c62828;" + "}" + ); + + QMessageBox::warning(this, "错误", error); + emit errorOccurred(error); +} + +void TimedDataCollection::onTreeItemClicked(const QModelIndex& index) +{ + if (!index.isValid()) return; + + TreeNode* node = static_cast(index.internalPointer()); + if (!node) return; + + QString info; + if (node->nodeType == TreeNodeType::Task && node->taskData) { + info = QString::fromLocal8Bit("选中任务 %1 - 状态: %2") + .arg(node->taskData->id) + .arg(node->taskData->status == TaskStatus::Running ? "运行中" : + node->taskData->status == TaskStatus::Finished ? "已完成" : "等待中"); + } + else if (node->nodeType == TreeNodeType::SubTask && node->subTaskData) { + info = QString::fromLocal8Bit("选中子任务 - 类型: %1") + .arg(m_taskModel->data(m_taskModel->index(index.row(), + TaskTreeModel::ColName, index.parent())).toString()); + } + + // 可以更新状态栏或其他UI +} + +void TimedDataCollection::onTreeItemDoubleClicked(const QModelIndex& index) +{ + if (!index.isValid()) return; + + TreeNode* node = static_cast(index.internalPointer()); + if (!node) return; + + if (node->nodeType == TreeNodeType::Task && node->taskData) { + const TimedTask& task = *node->taskData; + QString details = QString::fromLocal8Bit( + "【任务详情】\n\n" + "任务ID: %1\n" + "计划时间: %2\n" + "开始时间: %3\n" + "结束时间: %4\n" + "耗时: %5 分钟\n" + "状态: %6\n" + "子任务数: %7\n" + "保存路径: %8\n" + "卤素灯预热时间: %9 分钟" + ).arg(task.id) + .arg(task.scheduledTime.toString("yyyy-MM-dd HH:mm:ss")) + .arg(task.startTime.isValid() ? task.startTime.toString("yyyy-MM-dd HH:mm:ss") : "-") + .arg(task.endTime.isValid() ? task.endTime.toString("yyyy-MM-dd HH:mm:ss") : "-") + .arg(task.durationMinutes, 0, 'f', 2) + .arg(static_cast(task.status)) + .arg(task.subTasks.size()) + .arg(task.savePath) + .arg(task.HalogenLampPreheatingTime_Minute); + + QMessageBox::information(this, QString::fromLocal8Bit("任务详情"), details); + } + else if (node->nodeType == TreeNodeType::SubTask && node->subTaskData) { + const SubTask& subTask = *node->subTaskData; + QString typeStr; + switch (subTask.type) { + case SubTaskType::HyperSpectual400_1000nm: typeStr = QString::fromLocal8Bit("高光谱 400-1000nm"); break; + case SubTaskType::HyperSpectual1000_1700nm: typeStr = QString::fromLocal8Bit("高光谱 1000-1700nm"); break; + case SubTaskType::SingleLensReflex: typeStr = QString::fromLocal8Bit("单反相机"); break; + case SubTaskType::DepthCamera: typeStr = QString::fromLocal8Bit("深度相机"); break; + } + + QString details = QString::fromLocal8Bit( + "【子任务详情】\n\n" + "类型: %1\n" + "开始时间: %2\n" + "结束时间: %3\n" + "耗时: %4 分钟\n" + "路径文件: %5\n" + "状态: %6" + ).arg(typeStr) + .arg(subTask.startTime.isValid() ? subTask.startTime.toString("yyyy-MM-dd HH:mm:ss") : "-") + .arg(subTask.endTime.isValid() ? subTask.endTime.toString("yyyy-MM-dd HH:mm:ss") : "-") + .arg(subTask.durationMinutes, 0, 'f', 2) + .arg(subTask.pathLineFilePath) + .arg(static_cast(subTask.status)); + + if (subTask.type == SubTaskType::HyperSpectual400_1000nm || + subTask.type == SubTaskType::HyperSpectual1000_1700nm) { + details += QString::fromLocal8Bit("\n帧率: %1\n曝光时间: %2") + .arg(subTask.frameRate) + .arg(subTask.exposureTime); + } + + QMessageBox::information(this, QString::fromLocal8Bit("子任务详情"), details); + } +} + +void TimedDataCollection::onTreeSelectionChanged() +{ + // 可以根据选择更新其他UI组件 +} + +void TimedDataCollection::updateStatusBar() +{ + QString statusText; + QString styleSheet; + + if (m_currentTaskId > 0) { + TimedTask* task = m_taskModel->getTask(m_currentTaskId); + if (task) { + int finished = 0; + for (const auto& sub : task->subTasks) { + if (sub.status == TaskStatus::Finished) finished++; + } + + statusText = QString::fromLocal8Bit("▶ 正在执行任务 %1 | 子任务进度: %2/%3") + .arg(m_currentTaskId) + .arg(finished) + .arg(task->subTasks.size()); + + if (m_currentSubTaskIndex >= 0 && m_currentSubTaskIndex < task->subTasks.size()) { + QString subTypeStr; + switch (task->subTasks[m_currentSubTaskIndex].type) { + case SubTaskType::HyperSpectual400_1000nm: subTypeStr = QString::fromLocal8Bit("高光谱400-1000nm"); break; + case SubTaskType::HyperSpectual1000_1700nm: subTypeStr = QString::fromLocal8Bit("高光谱1000-1700nm"); break; + case SubTaskType::SingleLensReflex: subTypeStr = QString::fromLocal8Bit("单反相机"); break; + case SubTaskType::DepthCamera: subTypeStr = QString::fromLocal8Bit("深度相机"); break; + } + statusText += QString::fromLocal8Bit(" | 当前: %1").arg(subTypeStr); + } + + styleSheet = + "QLabel {" + " background-color: #e8f5e9;" + " border: 1px solid #4CAF50;" + " border-radius: 4px;" + " padding: 8px;" + " color: #2e7d32;" + " font-weight: bold;" + "}"; + } + } + else { + // 统计任务状态 + int waiting = 0, running = 0, finished = 0; + for (const auto& task : m_taskModel->tasks()) { + switch (task.status) { + case TaskStatus::Waiting: waiting++; break; + case TaskStatus::Running: running++; break; + case TaskStatus::Finished: finished++; break; + default: break; + } + } + + statusText = QString::fromLocal8Bit("任务统计 | 等待: %1 | 运行中: %2 | 已完成: %3 | 总计: %4") + .arg(waiting).arg(running).arg(finished) + .arg(m_taskModel->tasks().size()); + + styleSheet = + "QLabel {" + " background-color: #f5f5f5;" + " border: 1px solid #ccc;" + " border-radius: 4px;" + " padding: 8px;" + "}"; + } + + ui.statusLabel->setText(statusText); + ui.statusLabel->setStyleSheet(styleSheet); +} + +void TimedDataCollection::expandRunningTask() +{ + if (m_currentTaskId > 0) { + QModelIndex taskIndex = m_taskModel->getTaskIndex(m_currentTaskId); + if (taskIndex.isValid()) { + ui.taskTreeView->expand(taskIndex); + ui.taskTreeView->scrollTo(taskIndex); + ui.taskTreeView->setCurrentIndex(taskIndex); + } + } } void TimedDataCollection::readTimedTaskFromFile(const QString& filePath) { - // 从文件读取 - if (TimedDataCollectionDataStructuresReaderWriter::loadTasksFromFile(filePath, m_loadedTasks)) { - qDebug() << "Tasks loaded successfully, count:" << m_loadedTasks.size(); - for (const auto& t : m_loadedTasks) { - qDebug() << " Task ID:" << t.id << "SubTasks:" << t.subTasks.size(); - } - } - else { - qDebug() << "Failed to load tasks from:" << filePath; - } + QVector loadedTasks; + if (TimedDataCollectionDataStructuresReaderWriter::loadTasksFromFile(filePath, loadedTasks)) { + qDebug() << "Tasks loaded successfully, count:" << loadedTasks.size(); - int a = 1; + m_taskModel->setTasks(loadedTasks); + ui.taskTreeView->expandAll(); // 默认展开所有任务 + + // 更新调度器 + if (m_scheduler) { + m_scheduler->loadTasks(loadedTasks); + } + + updateStatusBar(); + } + else { + qDebug() << "Failed to load tasks from:" << filePath; + QMessageBox::warning(this, "加载失败", + QString("无法从文件加载任务:\n%1").arg(filePath)); + } } -void TimedDataCollection::writeRead() +void TimedDataCollection::startScheduler() { - // 创建2个定时任务测试 - QVector tasks; + if (m_scheduler) { + m_scheduler->loadTasks(m_taskModel->tasks()); + m_scheduler->start(); - for (int i = 0; i < 2; ++i) { - TimedTask task; - task.id = i + 1; - task.scheduledTime = QDateTime::currentDateTime().addDays(i + 1); - task.savePath = QString("D:/0tmp/data"); - task.status = TaskStatus::Waiting; - - // 创建4种子任务 - QVector types = { - SubTaskType::HyperSpectual400_1000nm, - SubTaskType::HyperSpectual1000_1700nm, - SubTaskType::SingleLensReflex, - SubTaskType::DepthCamera - }; - - for (int j = 0; j < types.size(); ++j) { - SubTask subTask; - subTask.type = types[j]; - subTask.startTime = task.scheduledTime.addSecs(j * 3600); - subTask.endTime = subTask.startTime.addSecs(1800); - subTask.durationMinutes = 1800; - subTask.estimatedDurationMinutes = 1800; - subTask.pathLineFilePath = QString("D:/0tmp/3Dtest/pathLine/%1.RecordLine3").arg(j); - subTask.status = TaskStatus::Waiting; - - // 根据类型设置特有属性 - if (subTask.type == SubTaskType::HyperSpectual400_1000nm || - subTask.type == SubTaskType::HyperSpectual1000_1700nm) { - subTask.frameRate = 30.0; - subTask.exposureTime = 1; - } - if (subTask.type == SubTaskType::HyperSpectual1000_1700nm) { - subTask.defaultRenderBand = 1200; - } - if (subTask.type == SubTaskType::SingleLensReflex || - subTask.type == SubTaskType::DepthCamera) { - subTask.captureIntervalSeconds = 5; - } - - task.subTasks.append(subTask); - } - - tasks.append(task); - } - - // 保存到文件 - QString filePath = "D:/0tmp/3Dtest/task.json"; - if (TimedDataCollectionDataStructuresReaderWriter::saveTasksToFile(filePath, tasks)) { - qDebug() << "Tasks saved to:" << filePath; - } - else { - qDebug() << "Failed to save tasks to:" << filePath; - } - - // 从文件读取 - QVector loadedTasks; - if (TimedDataCollectionDataStructuresReaderWriter::loadTasksFromFile(filePath, loadedTasks)) { - qDebug() << "Tasks loaded successfully, count:" << loadedTasks.size(); - for (const auto& t : loadedTasks) { - qDebug() << " Task ID:" << t.id << "SubTasks:" << t.subTasks.size(); - } - } - else { - qDebug() << "Failed to load tasks from:" << filePath; - } - - int a = 1; + ui.statusLabel->setText(QString::fromLocal8Bit("调度器已启动,等待任务执行...")); + ui.statusLabel->setStyleSheet( + "QLabel {" + " background-color: #fff3e0;" + " border: 1px solid #ff9800;" + " border-radius: 4px;" + " padding: 8px;" + " color: #e65100;" + "}"); + } +} + +void TimedDataCollection::stopScheduler() +{ + if (m_scheduler) { + m_scheduler->stop(); + + m_currentTaskId = -1; + m_currentSubTaskIndex = -1; + + ui.statusLabel->setText(QString::fromLocal8Bit("调度器已停止")); + ui.statusLabel->setStyleSheet( + "QLabel {" + " background-color: #fce4ec;" + " border: 1px solid #e91e63;" + " border-radius: 4px;" + " padding: 8px;" + " color: #880e4f;" + "}"); + } +} + +void TimedDataCollection::subTaskCompleted(int status) +{ + emit sequenceCompleteSignal(status); +} + +void TimedDataCollection::onBack2Origin() +{ + emit Back2OriginSignal(); } diff --git a/HPPA/TimedDataCollection.h b/HPPA/TimedDataCollection.h index afc88f9..6faa2d7 100644 --- a/HPPA/TimedDataCollection.h +++ b/HPPA/TimedDataCollection.h @@ -1,56 +1,73 @@ #pragma once #include -#include -#include -#include - +#include +#include +#include #include "ui_TimedDataCollection_ui.h" #include "TimedDataCollectionDataStructures.h" +#include "TaskTreeModel.h" class TimedDataCollection : public QDialog { - Q_OBJECT + Q_OBJECT public: - TimedDataCollection(QWidget* parent = nullptr); - ~TimedDataCollection(); + TimedDataCollection(QWidget* parent = nullptr); + ~TimedDataCollection(); - void readTimedTaskFromFile(const QString& filePath); + void readTimedTaskFromFile(const QString& filePath); public Q_SLOTS: - void startScheduler(); - void stopScheduler(); - void subTaskCompleted(int status); - void onBack2Origin(); + void startScheduler(); + void stopScheduler(); + void subTaskCompleted(int status); + void onBack2Origin(); + +private Q_SLOTS: + // 任务状态更新槽 + void onTaskStarted(int taskId); + void onTaskFinished(int taskId, bool success); + void onSubTaskStarted(int taskId, int subTaskIndex); + void onSubTaskFinished(int taskId, int subTaskIndex); + void onTaskDataChanged(const TimedTask& task); + void onErrorOccurred(const QString& error); + + // 树视图交互 + void onTreeItemClicked(const QModelIndex& index); + void onTreeItemDoubleClicked(const QModelIndex& index); + void onTreeSelectionChanged(); Q_SIGNALS: - void taskStarted(int taskId); - void taskFinished(int taskId, bool success); + void taskStarted(int taskId); + void taskFinished(int taskId, bool success); + void subTaskStarted(int taskId, int subTaskIndex); + void subTaskFinished(int taskId, int subTaskIndex); + void errorOccurred(const QString& error); - void subTaskStarted(int taskId, int subTaskIndex); - void subTaskFinished(int taskId, int subTaskIndex); - void errorOccurred(const QString& error); + void hyperCamParm(int camType, double f, double e, QString filePath, QString fileName); + void camParm(int camType, int captureIntervalSeconds, QString folder); + void motorParm(QString pathLineFilePath); + void startRecordSignal(int camType); - // 采集相关信号 (透传 TaskScheduler) - void hyperCamParm(int camType, double f, double e, QString filePath, QString fileName); - void camParm(int camType, int captureIntervalSeconds, QString folder); - void motorParm(QString pathLineFilePath); - void startRecordSignal(int camType); + void switchHalogenLampSignal(int state); + void switchD65LampSignal(int state); + void switchSlrSignal(int state); - void switchHalogenLampSignal(int state); - void switchD65LampSignal(int state); - void switchSlrSignal(int state); - - // 马达反馈的信号 - void sequenceCompleteSignal(int status); - void Back2OriginSignal(); + void sequenceCompleteSignal(int status); + void Back2OriginSignal(); private: - Ui::TimedDataCollection_ui ui; + void setupUI(); + void setupConnections(); + void updateStatusBar(); + void expandRunningTask(); - void writeRead(); + Ui::TimedDataCollection_ui ui; - QVector m_loadedTasks; - TaskScheduler* m_scheduler; + TaskTreeModel* m_taskModel; // 任务树形模型 + TaskScheduler* m_scheduler; + + int m_currentTaskId = -1; + int m_currentSubTaskIndex = -1; }; diff --git a/HPPA/TimedDataCollectionDataStructures.cpp b/HPPA/TimedDataCollectionDataStructures.cpp index b903899..2d9d30f 100644 --- a/HPPA/TimedDataCollectionDataStructures.cpp +++ b/HPPA/TimedDataCollectionDataStructures.cpp @@ -220,7 +220,7 @@ void TaskExecutor::execute(const TimedTask& task) m_task = task; m_task.startTime = QDateTime::currentDateTime(); - + m_currentSubTaskIndex = 0; m_isRunning = true; @@ -331,6 +331,8 @@ void TaskExecutor::onSequenceComplete(int status) break; } } + + emit taskUpdated(m_task); } void TaskExecutor::onBack2Origin() @@ -355,12 +357,15 @@ void TaskExecutor::onBack2Origin() // 所有子任务完成 m_task.endTime = QDateTime::currentDateTime(); m_task.durationMinutes = (double)m_task.startTime.secsTo(m_task.endTime) / 60; + m_task.status = TaskStatus::Finished; qDebug() << "TaskExecutor: task time consuming(Minutes): " << m_task.durationMinutes; m_isRunning = false; qDebug() << "TaskExecutor: All subtasks completed"; emit finished(true); } + + emit taskUpdated(m_task); } void TaskExecutor::onError(const QString& error) @@ -383,6 +388,8 @@ void TaskExecutor::executeNextSubTask() subTask.status = TaskStatus::Running; subTask.startTime = QDateTime::currentDateTime(); + emit taskUpdated(m_task); + QString tmp = "TaskExecutor: Starting subtask" + QString::number(m_currentSubTaskIndex) + "type:" + static_cast(subTask.type); printMsgAndTime(tmp); //printMsgAndTime("excute " + QString::number(m_currentSubTaskIndex) + " subTask: "); @@ -589,6 +596,18 @@ void TaskScheduler::executeTask(TimedTask& task) connect(this, &TaskScheduler::sequenceCompleteSignal, m_currentExecutor, &TaskExecutor::onSequenceComplete); connect(this, &TaskScheduler::Back2OriginSignal, m_currentExecutor, &TaskExecutor::onBack2Origin); + connect(m_currentExecutor, &TaskExecutor::taskUpdated, + this, [this](const TimedTask& updatedTask) { + for (int i = 0; i < m_tasks.size(); ++i) { + if (m_tasks[i].id == updatedTask.id) { + m_tasks[i] = updatedTask; + break; + } + } + emit taskDataChanged(updatedTask); + }); + + // 开始执行 m_currentExecutor->execute(task); } diff --git a/HPPA/TimedDataCollectionDataStructures.h b/HPPA/TimedDataCollectionDataStructures.h index 35f62e7..7571707 100644 --- a/HPPA/TimedDataCollectionDataStructures.h +++ b/HPPA/TimedDataCollectionDataStructures.h @@ -137,6 +137,7 @@ public: signals: void finished(bool success); // 任务完成 + void taskUpdated(const TimedTask& task); // 确保有这个信号 void subTaskStarted(int subTaskIndex, SubTaskType type); // 子任务开始 void subTaskFinished(int subTaskIndex, SubTaskType type, bool success); // 子任务完成 void errorOccurred(const QString& error); // 错误发生 @@ -194,6 +195,7 @@ public: signals: void taskStarted(int taskId); // 任务开始 void taskFinished(int taskId, bool success); // 任务完成 + void taskDataChanged(const TimedTask& task); void schedulerStateChanged(bool running); // 调度器状态变化 diff --git a/HPPA/TimedDataCollection_ui.ui b/HPPA/TimedDataCollection_ui.ui index de0255e..0c33fef 100644 --- a/HPPA/TimedDataCollection_ui.ui +++ b/HPPA/TimedDataCollection_ui.ui @@ -6,7 +6,7 @@ 0 0 - 1160 + 1008 576 @@ -18,229 +18,11 @@ - - - false - - - true - - - - 任务 - - - - - 计划时间 - - - - - 开始时间 - - - - - 结束时间 - - - - - 耗时 - - - - - 状态 - - - - - 1 - - - - - - - - - - - - - - - - - - - a - - - - - - - - - - - - - - - - - - - - b - - - - - - 2 - - - - - - - - - - - - - - - - - - - a - - - - - b - - - - - - 3 - - - - - - - - - - - - - - - - - - - a - - - - - b - - - - - - 4 - - - - - - - - - - - - - - - - - - - a - - - - - b - - - - - - 5 - - - - - - - - - - - - - - - - - - - a - - - - - b - - - - - - - - - 1 - - - - + - - - - 添加总计划任务 - - - @@ -248,27 +30,69 @@ - + 添加子任务 - - + + + + 添加总计划任务 + + - + 删除子任务 - - + + - 运行 + 停止调度 + + + + + + + + + + 刷新 + + + + + + + 开始调度 + + + + + + + 展开 + + + + + + + 折叠 + + + + + + + TextLabel