519 lines
18 KiB
C++
519 lines
18 KiB
C++
#include "TimedDataCollection.h"
|
|
#include <QVBoxLayout>
|
|
#include <QHBoxLayout>
|
|
#include <QMessageBox>
|
|
#include <QGroupBox>
|
|
#include <QSplitter>
|
|
|
|
TimedDataCollection::TimedDataCollection(QWidget* parent)
|
|
: QDialog(parent)
|
|
{
|
|
ui.setupUi(this);
|
|
|
|
setupUI();
|
|
|
|
// 初始化调度器
|
|
m_scheduler = new TaskScheduler(this);
|
|
|
|
setupConnections();
|
|
|
|
// 连接按钮信号
|
|
connect(ui.start_btn, &QPushButton::clicked, this, &TimedDataCollection::startScheduler);
|
|
connect(ui.stop_btn, &QPushButton::clicked, this, &TimedDataCollection::stopScheduler);
|
|
|
|
connect(ui.expandAll_btn, &QPushButton::clicked, ui.taskTreeView, &QTreeView::expandAll);
|
|
connect(ui.collapseAll_btn, &QPushButton::clicked, ui.taskTreeView, &QTreeView::collapseAll);
|
|
|
|
connect(ui.taskFileSelect_btn, &QPushButton::clicked, this, &TimedDataCollection::onSelectTaskFile);
|
|
|
|
// 树视图交互信号
|
|
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::setupUI()
|
|
{
|
|
// 设置窗口大小
|
|
//resize(1200, 600);
|
|
//setWindowTitle("定时数据采集系统");
|
|
|
|
// 创建任务树形模型
|
|
m_taskModel = new TaskTreeModel(this);
|
|
|
|
// 创建树形视图
|
|
ui.taskTreeView->setModel(m_taskModel);
|
|
|
|
// 配置树形视图
|
|
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);
|
|
|
|
// 配置表头
|
|
QHeaderView* header = ui.taskTreeView->header();
|
|
header->setStretchLastSection(true);
|
|
header->setSectionResizeMode(QHeaderView::Interactive);
|
|
header->setDefaultAlignment(Qt::AlignCenter);
|
|
|
|
// 设置列宽
|
|
ui.taskTreeView->setColumnWidth(TaskTreeModel::ColName, 200);
|
|
ui.taskTreeView->setColumnWidth(TaskTreeModel::ColScheduledTime, 150);
|
|
ui.taskTreeView->setColumnWidth(TaskTreeModel::ColCountdown, 100);
|
|
ui.taskTreeView->setColumnWidth(TaskTreeModel::ColStartTime, 100);
|
|
ui.taskTreeView->setColumnWidth(TaskTreeModel::ColEndTime, 100);
|
|
ui.taskTreeView->setColumnWidth(TaskTreeModel::ColDuration, 100);
|
|
ui.taskTreeView->setColumnWidth(TaskTreeModel::ColEstimatedDuration, 100);
|
|
ui.taskTreeView->setColumnWidth(TaskTreeModel::ColStatus, 100);
|
|
ui.taskTreeView->setColumnWidth(TaskTreeModel::ColProgress, 80);
|
|
|
|
// 创建状态标签
|
|
ui.statusLabel->setStyleSheet(
|
|
"QLabel {"
|
|
" background-color: #3856B8;"
|
|
" border: 1px solid #2f6bff;"
|
|
" border-radius: 4px;"
|
|
" padding: 8px;"
|
|
" font-size: 12px;"
|
|
"}"
|
|
);
|
|
}
|
|
|
|
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::ObtainingDepthInformationSignals, this, &TimedDataCollection::ObtainingDepthInformationSignals);
|
|
connect(m_scheduler, &TaskScheduler::LiftingPlatformSignals, this, &TimedDataCollection::LiftingPlatformSignals);
|
|
connect(m_scheduler, &TaskScheduler::AutoFocusSignals, this, &TimedDataCollection::AutoFocusSignals);
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
void TimedDataCollection::onSelectTaskFile()
|
|
{
|
|
if (m_scheduler->isRunning())
|
|
{
|
|
return;
|
|
}
|
|
|
|
QString filePath = QFileDialog::getOpenFileName(this,
|
|
tr("Select Task File"),
|
|
QString(),
|
|
tr("JSON Files (*.json);;All Files (*)"));
|
|
|
|
if (filePath.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
ui.taskFileSelect_lineEdit->setText(filePath);
|
|
readTimedTaskFromFile(filePath);
|
|
}
|
|
|
|
// ==================== 任务状态更新槽实现 ====================
|
|
|
|
void TimedDataCollection::onTaskStarted(int taskId)
|
|
{
|
|
m_currentTaskId = taskId;
|
|
m_currentSubTaskIndex = -1;
|
|
|
|
// 展开当前运行的任务
|
|
expandRunningTask();
|
|
|
|
updateStatusBar();
|
|
emit taskStarted(taskId);
|
|
}
|
|
|
|
void TimedDataCollection::onTaskFinished(int taskId, bool success)
|
|
{
|
|
if (m_currentTaskId == taskId) {
|
|
m_currentTaskId = -1;
|
|
m_currentSubTaskIndex = -1;
|
|
}
|
|
|
|
updateStatusBar();
|
|
emit taskFinished(taskId, success);
|
|
}
|
|
|
|
void TimedDataCollection::onSubTaskStarted(int taskId, int subTaskIndex)
|
|
{
|
|
m_currentSubTaskIndex = subTaskIndex;
|
|
|
|
m_taskModel->updateProgress(taskId);
|
|
|
|
// 滚动到当前子任务
|
|
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::onSubTaskFinished(int taskId, int subTaskIndex)
|
|
{
|
|
m_taskModel->updateSubTaskStatusAndProgress(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: #3856B8;"
|
|
" border: 1px solid #2f6bff;"
|
|
" border-radius: 4px;"
|
|
" padding: 8px;"
|
|
" color: #e6eeff;"
|
|
"}"
|
|
);
|
|
|
|
QMessageBox::warning(this, "错误", error);
|
|
emit errorOccurred(error);
|
|
}
|
|
|
|
void TimedDataCollection::onTreeItemClicked(const QModelIndex& index)
|
|
{
|
|
if (!index.isValid()) return;
|
|
|
|
TaskTreeNode* node = static_cast<TaskTreeNode*>(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;
|
|
|
|
TaskTreeNode* node = static_cast<TaskTreeNode*>(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<int>(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<int>(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: #3856B8;"
|
|
" border: 1px solid #2f6bff;"
|
|
" border-radius: 4px;"
|
|
" padding: 8px;"
|
|
" color: #e6eeff;"
|
|
" 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: #3856B8;"
|
|
" border: 1px solid #2f6bff;"
|
|
" 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)
|
|
{
|
|
QVector<TimedTask> loadedTasks;
|
|
if (TimedDataCollectionDataStructuresReaderWriter::loadTasksFromFile(filePath, loadedTasks)) {
|
|
qDebug() << "Tasks loaded successfully, count:" << loadedTasks.size();
|
|
|
|
// 预测耗时:计算每个子任务和整个任务的预计耗时
|
|
for (int i = 0; i < loadedTasks.size(); ++i)
|
|
{
|
|
double totalEstimatedMinutes = 0.0;
|
|
for (int j = 0; j < loadedTasks[i].subTasks.size(); ++j)
|
|
{
|
|
loadedTasks[i].subTasks[j].durationMinutes = 0.0; // 初始化实际耗时为0
|
|
QString pathLineFilePath = loadedTasks[i].subTasks[j].pathLineFilePath;
|
|
if (!pathLineFilePath.isEmpty())
|
|
{
|
|
double estimatedMinutes = PathLineManager::calculateTimeFromFile(pathLineFilePath);
|
|
loadedTasks[i].subTasks[j].estimatedDurationMinutes = estimatedMinutes;
|
|
totalEstimatedMinutes += loadedTasks[i].subTasks[j].estimatedDurationMinutes;
|
|
}
|
|
}
|
|
double slrTimeMinute = 135 / 60;
|
|
loadedTasks[i].estimatedDurationMinutes = totalEstimatedMinutes + loadedTasks[i].HalogenLampPreheatingTime_Minute + slrTimeMinute;
|
|
loadedTasks[i].durationMinutes = 0.0; // 初始化实际耗时为0
|
|
}
|
|
|
|
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::startScheduler()
|
|
{
|
|
if (m_scheduler)
|
|
{
|
|
if (m_scheduler->isRunning())
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; i < m_taskModel->tasks().size(); i++)
|
|
{
|
|
m_taskModel->setTaskAndSubtaskStatus(m_taskModel->tasks()[i].id, TaskStatus::Waiting);
|
|
}
|
|
|
|
m_scheduler->loadTasks(m_taskModel->tasks());
|
|
m_scheduler->start();
|
|
|
|
ui.statusLabel->setText(QString::fromLocal8Bit("调度器已启动,等待任务执行..."));
|
|
ui.statusLabel->setStyleSheet(
|
|
"QLabel {"
|
|
" background-color: #3856B8;"
|
|
" border: 1px solid #2f6bff;"
|
|
" border-radius: 4px;"
|
|
" padding: 8px;"
|
|
" color: #e6eeff;"
|
|
"}");
|
|
}
|
|
}
|
|
|
|
void TimedDataCollection::stopScheduler()
|
|
{
|
|
if (m_scheduler) {
|
|
|
|
for (size_t i = 0; i < m_taskModel->tasks().size(); i++)
|
|
{
|
|
m_taskModel->setTaskAndSubtaskStatus(m_taskModel->tasks()[i].id, TaskStatus::default);
|
|
}
|
|
|
|
m_scheduler->stop();
|
|
|
|
m_currentTaskId = -1;
|
|
m_currentSubTaskIndex = -1;
|
|
|
|
ui.statusLabel->setText(QString::fromLocal8Bit("调度器已停止"));
|
|
ui.statusLabel->setStyleSheet(
|
|
"QLabel {"
|
|
" background-color: #3856B8;"
|
|
" border: 1px solid #2f6bff;"
|
|
" border-radius: 4px;"
|
|
" padding: 8px;"
|
|
" color: #e6eeff;"
|
|
"}");
|
|
}
|
|
}
|
|
|
|
void TimedDataCollection::subTaskCompleted(int status)
|
|
{
|
|
emit sequenceCompleteSignal(status);
|
|
}
|
|
|
|
void TimedDataCollection::onBack2Origin()
|
|
{
|
|
emit Back2OriginSignal();
|
|
}
|