diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index ba54cc4..84c0b3f 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -33,7 +33,10 @@ #include #include "MeasureClient.h" #include "MeasureAnalysisDataTableView.h" -#include "GvfToCsv/GvfToCsv.h" +#include "csv.h" +#include + +using namespace io; using namespace ads; MainWindow* MainWindow::_s_main_win = nullptr; @@ -383,6 +386,8 @@ void MainWindow::closeProject(const QString& project_name) } } + + void MainWindow::showEvent(QShowEvent *event) { QMainWindow::showEvent(event); @@ -476,6 +481,15 @@ void MainWindow::on_action_start_measure_triggered() QString item_name = QStringLiteral(u"测量粒子数据"); QStandardItem * particleData = node[item_name]; ProjectList::Instance()->SetNodeStatus(particleData,status,true); + //创建通道道址计数文件夹 + const QString& all_channel_particle_data_filename = models->GetAllChannelParticleDataFilename(); + const QString& all_ch_count_dir = models->GetProjectDir(); + + if (!all_channel_particle_data_filename.isEmpty()) { + const QString& every_ch_count_dir = QDir(models->GetProjectDir()).filePath(QStringLiteral(u"通道道址计数")); + QDir every_ch_count_output_dir(every_ch_count_dir); + every_ch_count_output_dir.mkpath(every_ch_count_dir); + } QString projectName; QString deviceCfg; @@ -586,7 +600,6 @@ void MainWindow::onRunningInfo(const QString &run_info) void MainWindow::onGvfData(const QByteArray &data) { - // LOG_INFO(QStringLiteral(u"GVFDATA: %1").arg(QString::fromUtf8(data.toHex().toUpper()))); QList particles = _gvfToCsv->parseParticleFrames(data); if (particles.isEmpty()) { LOG_INFO(QStringLiteral(u"本次GVF数据未解析到有效粒子,跳过写入CSV")); @@ -625,9 +638,19 @@ void MainWindow::onGvfData(const QByteArray &data) out << csvBuffer; out.flush(); // 确保数据立即写入磁盘,避免程序崩溃丢失数据 outFile.close(); + //处理粒子数据 + changeUpdata(particles); + //处理道址计数 + changeChannelParticleCount(particles); +} +void MainWindow::changeUpdata(QList &data) +{ + QString dir = ProjectList::Instance()->GetCurrentProjectModel()->GetProjectDir(); + QString csvPath = QStringLiteral(u"%1/%2").arg(dir).arg("粒子数据.csv"); auto dockList = _dock_manager->dockWidgetsMap().values(); + int i = 0; for(auto dock : dockList) { MeasureAnalysisView* view = dynamic_cast(dock->widget()); @@ -636,26 +659,108 @@ void MainWindow::onGvfData(const QByteArray &data) && view->GetViewType() == MeasureAnalysisView::DataTable) { MeasureAnalysisDataTableView* table = dynamic_cast(view); - table->RefreshTableData(csvPath); + QString boardId = QString::number(data.at(i).boardId); + QString channelId = QString::number(data.at(i).channelId); + QString address = QString::number(data.at(i).address); + QString timestampCount = QString::number(data.at(i).timestampCount); + QStringList dataList; + dataList << boardId << channelId << address << timestampCount; - ProjectList* project_list_model = ProjectList::Instance(); - auto project_model = project_list_model->GetCurrentProjectModel(); - const QString& all_channel_particle_data_filename = project_model->GetAllChannelParticleDataFilename(); - if (!all_channel_particle_data_filename.isEmpty()) { - const QString& all_ch_count_dir = project_model->GetProjectDir(); - const QString& every_ch_count_dir = QDir(project_model->GetProjectDir()).filePath(QStringLiteral(u"通道道址计数")); - auto count_task = new DataProcessWorkPool::EveryChannelParticleCountDataTask; - count_task->SetAllChannelParticleDataFilename(all_channel_particle_data_filename); - count_task->SetAllChannelCountResultDir(all_ch_count_dir); - count_task->SetEveryChannelCountResultDir(every_ch_count_dir); - count_task->SetFinishedNotifier(project_list_model, "onChannelAddressCountProcessFinished", project_model->GetProjectName()); - count_task->StartTask(); - } - // initAction(); + // dataList << QString("%1,%2,%3,%4").arg(boardId).arg(channelId).arg(address).arg(timestampCount); + table->AppendRow(dataList,false); + i++; } } } +void MainWindow::changeChannelParticleCount(QList &data) +{ + bool ret_ok = true; + + // 通道号 -> 地址 -> 计数 + for(auto info : data) + { + // 板卡和通道号计算,通道号 = 板卡号 * 4 + 通道号 + int channel_num = (info.boardId) * 4 + (info.channelId + 1); + // 统计每个通道的粒子计数 + if (!channel_address_counts.contains(channel_num)) { + channel_address_counts[channel_num] = QMap(); + } + channel_address_counts[channel_num][info.address]++; + } + + MeasureAnalysisProjectModel* models = ProjectList::Instance()->GetCurrentProjectModel(); + const QString& every_ch_count_dir = QDir(models->GetProjectDir()).filePath(QStringLiteral(u"通道道址计数")); + QDir every_ch_count_output_dir(every_ch_count_dir); + + // 写入每个通道的粒子计数数据(优化:使用一次打开文件,批量写入) + QMap> channel_file_streams; + // 预创建所有通道的文件流 + for (auto channel_it = channel_address_counts.begin(); channel_it != channel_address_counts.end(); ++channel_it) { + uint channel_num = channel_it.key(); + QString count_data_filename = every_ch_count_output_dir.filePath(QStringLiteral(u"通道%1粒子计数.csv").arg(channel_num)); + particle_count_filename_list.insert(channel_num, count_data_filename); + // 创建文件流 + std::shared_ptr out(new std::ofstream(QStrToSysPath(count_data_filename))); + channel_file_streams[channel_num] = out; + *out << QString(QStringLiteral(u"道址")).toStdString() << "," << QString(QStringLiteral(u"计数")).toStdString() << std::endl; + } + // 批量写入数据 + for (auto channel_it = channel_address_counts.begin(); channel_it != channel_address_counts.end(); ++channel_it) { + uint channel_num = channel_it.key(); + const QMap& address_counts = channel_it.value(); + auto out_stream = channel_file_streams[channel_num]; + for (auto address_it = address_counts.begin(); address_it != address_counts.end(); ++address_it) { + uint address = address_it.key(); + unsigned long long count = address_it.value(); + *out_stream << address << "," << count << std::endl; + } + } + channel_file_streams.clear(); + const QString& project_name = models->GetProjectName(); + MeasureAnalysisProjectModel* project_model = ProjectList::Instance()->GetProjectModel(project_name); + if (project_model == nullptr) { + ret_ok = false; + } else { + // 更新项目模型中的通道粒子计数数据文件名 + for (auto it = particle_count_filename_list.begin(); it != particle_count_filename_list.end(); ++it) { + project_model->SetChannelAddressCountDataFilename(it.key(), it.value()); + } + + QMap > project_node_items = ProjectList::Instance()->getProjectNodeItems(); + QMap node_map = project_node_items[models->GetProjectName()]; + + const QString& adrr_count_item_name = QStringLiteral(u"道址计数"); + const QMap& filename_list = models->GetChannelAddressCountDataFilenameList(); + bool status_ok = false; + QString status = QStringLiteral(u"无效"); + if (!filename_list.isEmpty()) { + status_ok = true; + status = QStringLiteral(u"有效"); + } + + if (node_map.contains(adrr_count_item_name)) { + auto adrr_count_item = node_map[adrr_count_item_name]; + ProjectList::Instance()->SetNodeStatus(adrr_count_item, status, status_ok); + for (auto it = filename_list.begin(); it != filename_list.end(); ++it) { + uint ch_num = it.key(); + QString item_name = QStringLiteral(u"通道%1道址计数").arg(ch_num); + if(node_map.contains(item_name)) + { + return; + } + const QVariant& analys_type = QVariant::fromValue(AnalysisType::AddressCountData); + QStandardItem* node_item = ProjectList::Instance()->AddChildNode(adrr_count_item, item_name, status, analys_type, true, status_ok); + node_item->setData(project_name, Qt::UserRole + 2); + node_item->setData(ch_num, Qt::UserRole + 3); + node_map[item_name] = node_item; + } + } + } + + +} + void MainWindow::on_action_stop_measure_triggered() { diff --git a/src/MainWindow.h b/src/MainWindow.h index 3fce619..5e2d31e 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -3,6 +3,7 @@ #include #include +#include "GvfToCsv/GvfToCsv.h" QT_BEGIN_NAMESPACE namespace Ui { @@ -18,7 +19,6 @@ class MeasureAnalysisTreeView; class BackgroundTaskListView; class QPushButton; class MeasureClient; -class GvfToCsv; QT_END_NAMESPACE class MainWindow : public QMainWindow @@ -52,7 +52,10 @@ private: void initStatusBar(); void applyStyleSheet(); void closeProject(const QString &project_name); - + //处理粒子数据 + void changeUpdata(QList &data); + //处理道址计数 + void changeChannelParticleCount(QList &data); signals: void newProject(const QString &project_name); @@ -80,6 +83,7 @@ private slots: void on_action_stop_measure_triggered(); + private: QMutex _mutex_info_output; QPlainTextEdit* _plain_edit_info_output; @@ -100,5 +104,11 @@ private: QStringList deviceList; GvfToCsv *_gvfToCsv = nullptr; + + QMap> channel_address_counts; // 通道号 -> 地址 -> 计数 + QMap particle_count_filename_list; + + + }; #endif // MAINWINDOW_H diff --git a/src/MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.cpp b/src/MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.cpp index d98cb97..dd8a336 100644 --- a/src/MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.cpp +++ b/src/MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.cpp @@ -5,6 +5,17 @@ #include #include "GlobalDefine.h" +//2026-06-10 +static QString escapeCsvField(const QString& field) +{ + if (field.contains(',') || field.contains('"') || field.contains('\n') || field.contains('\r')) { + QString escaped = field; + escaped.replace('"', "\"\""); + return "\"" + escaped + "\""; + } + return field; +} + MeasureAnalysisDataTableView::MeasureAnalysisDataTableView(QWidget* parent) : MeasureAnalysisView { parent } , _preload_policy(PreloadPolicy::Conservative) @@ -36,16 +47,28 @@ void MeasureAnalysisDataTableView::SetAnalyzeDataFilename(const QMap(data_files_set.first().toString()); if (!csv_ddata_source->isValid()) { return; } - VirtualTableModel* table_model = new VirtualTableModel; - table_model->setDataSource(csv_ddata_source); - table_model->setPreloadPolicy(_preload_policy); - table_model->setBlockSize(_block_size); - _tableView->setVirtualModel(table_model); + //2026-06-10 + if (_tableModel) { + _tableModel->deleteLater(); + } + + _tableModel = new VirtualTableModel; + _tableModel->setDataSource(csv_ddata_source); + _tableModel->setPreloadPolicy(_preload_policy); + _tableModel->setBlockSize(_block_size); + + // VirtualTableModel* table_model = new VirtualTableModel; + // table_model->setDataSource(csv_ddata_source); + // table_model->setPreloadPolicy(_preload_policy); + // table_model->setBlockSize(_block_size); + + _tableView->setVirtualModel(_tableModel); _tableView->setBufferSize(_buffer_size); } @@ -54,10 +77,82 @@ void MeasureAnalysisDataTableView::RefreshTableData(const QString &csvFilePath) auto csv_source = std::make_shared(csvFilePath); if(!csv_source->isValid()) return; - VirtualTableModel* newModel = new VirtualTableModel; - newModel->setDataSource(csv_source); - newModel->setPreloadPolicy(_preload_policy); - newModel->setBlockSize(_block_size); - _tableView->setVirtualModel(newModel); + //2026-06-10 + if (_tableModel) { + _tableModel->deleteLater(); + } + + _tableModel = new VirtualTableModel; + _tableModel->setDataSource(csv_source); + _tableModel->setPreloadPolicy(_preload_policy); + _tableModel->setBlockSize(_block_size); + + // VirtualTableModel* newModel = new VirtualTableModel; + // newModel->setDataSource(csv_source); + // newModel->setPreloadPolicy(_preload_policy); + // newModel->setBlockSize(_block_size); + _tableView->setVirtualModel(_tableModel); _tableView->setBufferSize(_buffer_size); } + +void MeasureAnalysisDataTableView::AppendRow(const QVariantList &rowData, bool writeToFile) +{ + // 1. 前置校验 + if (!_tableModel || !_tableModel->dataSource()) { + LOG_WARN(QStringLiteral(u"追加行失败:表格模型或数据源未初始化")); + return; + } + + auto dataSource = std::dynamic_pointer_cast(_tableModel->dataSource()); + if (!dataSource || !dataSource->isValid()) { + LOG_WARN(QStringLiteral(u"追加行失败:CSV数据源无效")); + return; + } + + // 2. 列数匹配 + const int expectedColumns = _tableModel->columnCount(); + if (rowData.size() != expectedColumns) { + LOG_WARN(QStringLiteral(u"追加行失败:列数不匹配,期望%1列,实际%2列") + .arg(expectedColumns).arg(rowData.size())); + return; + } + //需要写入文件的 + if (writeToFile) { + QFile file(dataSource->filePath()); + QTextStream out(&file); + out.setCodec("UTF-8"); // 与读取时的 QString::fromUtf8 保持一致 + + QStringList escapedFields; + for (const QVariant& field : rowData) { + escapedFields.append(escapeCsvField(field.toString())); + } + + out << escapedFields.join(',') << "\n"; + file.close(); + LOG_DEBUG(QStringLiteral(u"已成功将新行写入CSV文件:%1").arg(dataSource->filePath())); + RefreshTableData(dataSource->filePath()); + return; + } + + QFile file(dataSource->filePath()); + if (!file.open(QIODevice::Append | QIODevice::Text)) { + LOG_ERROR(QStringLiteral(u"追加行失败:无法打开文件 %1,错误:%2") + .arg(file.fileName()).arg(file.errorString())); + return; + } + + // 4. 重新加载整个表格,确保模型与文件同步 + RefreshTableData(dataSource->filePath()); + + // 5. 自动滚动到底部 + _tableView->scrollToBottom(); +} + +void MeasureAnalysisDataTableView::AppendRow(const QStringList &rowData, bool writeToFile) +{ + QVariantList varList; + for (const QString& field : rowData) { + varList.append(QVariant(field)); + } + AppendRow(varList, writeToFile); +} diff --git a/src/MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.h b/src/MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.h index 39e3af0..729f630 100644 --- a/src/MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.h +++ b/src/MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.h @@ -21,12 +21,19 @@ public: virtual void SetAnalyzeDataFilename(const QMap& data_files_set); void RefreshTableData(const QString& csvFilePath); + + // 2026-06-10 表尾插入数据函数(支持两种参数类型,默认同时写入CSV文件) + void AppendRow(const QVariantList& rowData, bool writeToFile = true); + void AppendRow(const QStringList& rowData, bool writeToFile = true); private: // 私有成员变量 VirtualTableView *_tableView; PreloadPolicy _preload_policy; // 预加载策略 uint _block_size; // 块大小输入框 uint _buffer_size; // 缓冲区大小输入框 + //2026-06-10 + VirtualTableModel* _tableModel = nullptr; // 持有模型指针,避免内存泄漏 + }; #endif // MEASUREANALYSISDATATABLEVIEW_H diff --git a/src/VirtualTable/VirtualTableModel.cpp b/src/VirtualTable/VirtualTableModel.cpp index 49d2e3f..45f8336 100644 --- a/src/VirtualTable/VirtualTableModel.cpp +++ b/src/VirtualTable/VirtualTableModel.cpp @@ -111,6 +111,11 @@ QVariant VirtualTableModel::headerData(int section, Qt::Orientation orientation, return QVariant(); } +std::shared_ptr VirtualTableModel::dataSource() const +{ + return m_dataSource; +} + void VirtualTableModel::setDataSource(std::shared_ptr source) { beginResetModel(); diff --git a/src/VirtualTable/VirtualTableModel.h b/src/VirtualTable/VirtualTableModel.h index 57fd507..c392a48 100644 --- a/src/VirtualTable/VirtualTableModel.h +++ b/src/VirtualTable/VirtualTableModel.h @@ -64,6 +64,10 @@ public: QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + //2026-06-10 + std::shared_ptr dataSource() const; + + // 公共接口方法 /** * @brief 设置数据源