diff --git a/src/EnergyCountPeakFitView/EnergyCountPeakFitView.cpp b/src/EnergyCountPeakFitView/EnergyCountPeakFitView.cpp index 284a1b9..3cc12a6 100644 --- a/src/EnergyCountPeakFitView/EnergyCountPeakFitView.cpp +++ b/src/EnergyCountPeakFitView/EnergyCountPeakFitView.cpp @@ -24,6 +24,48 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include "MeasureAnalysisProjectModel.h" + +QJsonObject PeakFitHistoryItem::toJson() const +{ + QJsonObject obj; + obj["timestamp"] = timestamp.toString(Qt::ISODate); + obj["center"] = center; + obj["fwhm"] = fwhm; + obj["area"] = area; + obj["amplitude"] = amplitude; + obj["sigma"] = sigma; + obj["sigmoidH"] = sigmoidH; + obj["sigmoidW"] = sigmoidW; + obj["baseline"] = baseline; + obj["xMin"] = xMin; + obj["xMax"] = xMax; + return obj; +} + +PeakFitHistoryItem PeakFitHistoryItem::fromJson(const QJsonObject& obj) +{ + PeakFitHistoryItem item; + item.timestamp = QDateTime::fromString(obj["timestamp"].toString(), Qt::ISODate); + item.center = obj["center"].toDouble(); + item.fwhm = obj["fwhm"].toDouble(); + item.area = obj["area"].toDouble(); + item.amplitude = obj["amplitude"].toDouble(); + item.sigma = obj["sigma"].toDouble(); + item.sigmoidH = obj["sigmoidH"].toDouble(); + item.sigmoidW = obj["sigmoidW"].toDouble(); + item.baseline = obj["baseline"].toDouble(); + item.xMin = obj["xMin"].toDouble(); + item.xMax = obj["xMax"].toDouble(); + return item; +} EnergyCountPeakFitView::EnergyCountPeakFitView(QWidget *parent) : MeasureAnalysisView { parent } @@ -49,6 +91,17 @@ EnergyCountPeakFitView::~EnergyCountPeakFitView() void EnergyCountPeakFitView::InitViewWorkspace(const QString &project_name) { Q_UNUSED(project_name); + if (project_name.isEmpty()) return; + auto project_model = ProjectList::Instance()->GetProjectModel(project_name); + if (!project_model) return; + + QDir project_dir(project_model->GetProjectDir()); + _workspace = project_dir.filePath(this->GetViewName()); + if (!QDir(_workspace).exists()) + project_dir.mkpath(_workspace); + + _historyFilePath = QDir(_workspace).filePath("peak_fit_history.json"); + loadHistoryFromFile(); } void EnergyCountPeakFitView::SetAnalyzeDataFilename(const QMap &data_files_set) @@ -85,19 +138,21 @@ void EnergyCountPeakFitView::setupPlot() _plot->enableAxis(QwtPlot::yLeft); // 设置QWT图例 - // QwtLegend* legend = new QwtLegend(); - // legend->setDefaultItemMode(QwtLegendData::ReadOnly); - // _plot->insertLegend(legend, QwtPlot::RightLegend); + QwtLegend* legend = new QwtLegend(); + legend->setDefaultItemMode(QwtLegendData::ReadOnly); + _plot->insertLegend(legend, QwtPlot::RightLegend); _plot->SetAxisDragScale(QwtPlot::xBottom, true); - _data_selector = new CustomQwtPlotXaxisSelector(_plot->canvas()); _data_selector->setEnabled(false); // 启用鼠标追踪,并安装事件过滤器 _plot->canvas()->setMouseTracking(true); _plot->canvas()->installEventFilter(this); + + + } void EnergyCountPeakFitView::setupMenu() @@ -123,12 +178,20 @@ void EnergyCountPeakFitView::setupMenu() QAction* action_clear_rect = this->_menu->addAction(QStringLiteral(u"清除框选标记")); connect(action_clear_rect, &QAction::triggered, this, &EnergyCountPeakFitView::clearAllSelectionRects); - this->_menu->addSeparator(); - QAction* action_select_algorithm = this->_menu->addAction(QStringLiteral(u"选择拟合算法")); +// this->_menu->addSeparator(); +// QAction* action_select_algorithm = this->_menu->addAction(QStringLiteral(u"选择拟合算法")); QAction* action_clear_fit = this->_menu->addAction(QStringLiteral(u"清除拟合曲线")); connect(action_clear_fit, &QAction::triggered, this, &EnergyCountPeakFitView::clearFitCurves); + // [NEW] 保存当前拟合结果 + QAction* action_save_fit = this->_menu->addAction(QStringLiteral(u"保存当前拟合结果")); + connect(action_save_fit, &QAction::triggered, this, &EnergyCountPeakFitView::onActionSaveCurrentFit); + + // [NEW] 查看历史拟合结果 + QAction* action_show_history = this->_menu->addAction(QStringLiteral(u"峰拟合结果")); + connect(action_show_history, &QAction::triggered, this, &EnergyCountPeakFitView::onActionShowFitHistory); + QAction* action_hint = this->_menu->addAction(QStringLiteral(u"框选提示:按住 Ctrl 键并拖动左键")); action_hint->setEnabled(false); } @@ -155,8 +218,9 @@ void EnergyCountPeakFitView::loadDataFromFile(const QString &data_name, const QS y.push_back(energy_count); } // 绘制曲线 - QwtPlotCurve* curve = new QwtPlotCurve(data_name); + QwtPlotCurve* curve = new QwtPlotCurve(QStringLiteral(u"原始数据")); curve->setSamples(x, y); + curve->setPen(QPen(Qt::red, 2)); // 原始数据统一红色 _plot->AddCurve(curve); } @@ -277,7 +341,7 @@ bool EnergyCountPeakFitView::eventFilter(QObject* watched, QEvent* event) else if (event->type() == QEvent::MouseButtonRelease && me->button() == Qt::LeftButton && _isSelecting) { finishSelection(); - clearAllSelectionRects(); + fadeSelectionRectBorders(); return true; } } @@ -419,151 +483,172 @@ void EnergyCountPeakFitView::addSelectionRect(const QRectF &plotRect) PlotRectItem* rectItem = new PlotRectItem("Selection"); rectItem->setAxes(QwtPlot::xBottom, QwtPlot::yLeft); rectItem->setRect(plotRect); + // 设置填充为透明(无填充) + rectItem->setBrush(QBrush(Qt::transparent)); // 或 Qt::NoBrush + // 设置边框颜色为红色,线宽2 + rectItem->setPen(QPen(Qt::red, 2)); rectItem->attach(_plot); _selectionRectItems.append(rectItem); _plot->replot(); } +void EnergyCountPeakFitView::fadeSelectionRectBorders() +{ + for (PlotRectItem* item : _selectionRectItems) { + if (item) { + QPen fadedPen(Qt::red, 1, Qt::DashLine); // 可根据原样式调整 + item->setPen(fadedPen); + } + } + _plot->replot(); +} + QList EnergyCountPeakFitView::performPeakFitting(const QVector &x, const QVector &y, const arma::vec* userP0) { // 清除之前的拟合曲线(每次拟合全新绘制) // clearFitCurves(); + QList results; - if (x.size() != y.size() || x.size() < 3) - { - qDebug() << QStringLiteral(u"数据长度不足"); - return results; - } + if (x.size() != y.size() || x.size() < 3) + { + qDebug() << QStringLiteral(u"数据长度不足"); + return results; + } - // 转换为 Armadillo 向量 - arma::vec armaX(x.size()), armaY(y.size()); - for (int i = 0; i < x.size(); ++i) { - armaX(i) = x[i]; - armaY(i) = y[i]; - } + // 转换为 Armadillo 向量 + arma::vec armaX(x.size()), armaY(y.size()); + for (int i = 0; i < x.size(); ++i) { + armaX(i) = x[i]; + armaY(i) = y[i]; + } - const int step_w = 20; - if (armaY.n_elem < step_w) { - qDebug() << QStringLiteral(u"ROI 数据点太少,跳过寻峰。"); - return results; - } - if (arma::all(armaY == 0)) { - qDebug() << QStringLiteral(u"ROI 计数全为零,跳过寻峰。"); - return results; - } + const int step_w = 20; + if (armaY.n_elem < step_w) { + qDebug() << QStringLiteral(u"ROI 数据点太少,跳过寻峰。"); + return results; + } + if (arma::all(armaY == 0)) { + qDebug() << QStringLiteral(u"ROI 计数全为零,跳过寻峰。"); + return results; + } - // 构建两列矩阵:第一列为能量,第二列为计数 - arma::mat spec_data(armaX.n_rows, 2); - spec_data.col(0) = armaX; - spec_data.col(1) = armaY; + // 构建两列矩阵:第一列为能量,第二列为计数 + arma::mat spec_data(armaX.n_rows, 2); + spec_data.col(0) = armaX; + spec_data.col(1) = armaY; - FindPeaksBySvd peakFinder; - std::vector peaks; - try { - peaks = peakFinder.FindPeaks(spec_data, step_w); - } catch (const std::string& s) { - qDebug() << QStringLiteral(u"FindPeaks 异常:") << s.c_str(); - return results; - } catch (const std::exception& e) { - qDebug() << QStringLiteral(u"FindPeaks 异常:") << e.what(); - return results; - } catch (...) { - qDebug() << QStringLiteral(u"FindPeaks 未知异常"); - return results; - } + FindPeaksBySvd peakFinder; + std::vector peaks; + try { + peaks = peakFinder.FindPeaks(spec_data, step_w); + } catch (const std::string& s) { + qDebug() << QStringLiteral(u"FindPeaks 异常:") << s.c_str(); + return results; + } catch (const std::exception& e) { + qDebug() << QStringLiteral(u"FindPeaks 异常:") << e.what(); + return results; + } catch (...) { + qDebug() << QStringLiteral(u"FindPeaks 未知异常"); + return results; + } - if (peaks.empty()) { - qDebug() << QStringLiteral(u"未检测到峰。"); - return results; - } + if (peaks.empty()) { + qDebug() << QStringLiteral(u"未检测到峰。"); + return results; + } - const double fitRangeEnergy = 15.0; - for (const auto& pinfo : peaks) { - int idxCenter = pinfo.pos; - if (idxCenter < 0 || idxCenter >= x.size()) - continue; - double centerEnergy = armaX(idxCenter); + // 选择最重要的峰(例如幅值最大的峰) + auto bestPeak = *std::max_element(peaks.begin(), peaks.end(), + [](const FindPeaksBySvd::PeakInfo& a, const FindPeaksBySvd::PeakInfo& b) { + return a.height < b.height; + }); + int idxCenter = bestPeak.pos; + if (idxCenter < 0 || idxCenter >= x.size()) + return results; + double centerEnergy = armaX(idxCenter); - // 提取局部数据 - QVector localX, localY; - for (int i = 0; i < x.size(); ++i) { - if (x[i] >= centerEnergy - fitRangeEnergy && x[i] <= centerEnergy + fitRangeEnergy) { - localX.push_back(x[i]); - localY.push_back(y[i]); - } - } - if (localX.size() < 5) { - qDebug() << QStringLiteral(u"局部数据点不足5,跳过峰 at") << centerEnergy; - continue; - } + const double fitRangeEnergy = 15.0; + // 提取局部数据 + QVector localX, localY; + for (int i = 0; i < x.size(); ++i) { + if (x[i] >= centerEnergy - fitRangeEnergy && x[i] <= centerEnergy + fitRangeEnergy) { + localX.push_back(x[i]); + localY.push_back(y[i]); + } + } + if (localX.size() < 5) { + qDebug() << QStringLiteral(u"局部数据点不足5,跳过拟合"); + return results; + } - arma::vec xLocal(localX.size()), yLocal(localY.size()); - for (int i = 0; i < localX.size(); ++i) { - xLocal(i) = localX[i]; - yLocal(i) = localY[i]; - } + arma::vec xLocal(localX.size()), yLocal(localY.size()); + for (int i = 0; i < localX.size(); ++i) { + xLocal(i) = localX[i]; + yLocal(i) = localY[i]; + } - arma::vec p0; - if (userP0 && userP0->n_elem == 6) { - p0 = *userP0; // 使用用户提供的初值 - } else { - try { - p0 = EstimatePhotonPeakModelInitialParams(xLocal, yLocal); - } catch (...) { - qDebug() << QStringLiteral(u"初始参数估算失败,跳过峰 at") << centerEnergy; - continue; - } - } + arma::vec p0; + if (userP0 && userP0->n_elem == 6) { + p0 = *userP0; + } else { + try { + p0 = EstimatePhotonPeakModelInitialParams(xLocal, yLocal); + } catch (...) { + qDebug() << QStringLiteral(u"初始参数估算失败,跳过拟合"); + return results; + } + } - arma::vec p_fit; - try { - p_fit = NolinearLeastSquaresCurveFit::Lsqcurvefit(PhotonPeakModel, xLocal, yLocal, p0); - } catch (const std::exception& e) { - qDebug() << QStringLiteral(u"拟合失败:") << e.what(); - continue; - } + arma::vec p_fit; + try { + p_fit = NolinearLeastSquaresCurveFit::Lsqcurvefit(PhotonPeakModel, xLocal, yLocal, p0); + } catch (const std::exception& e) { + qDebug() << QStringLiteral(u"拟合失败:") << e.what(); + return results; + } - double sigma = p_fit(1); - double fwhm = sigma * 2.355; + double sigma = p_fit(1); + double fwhm = sigma * 2.355; - AdaptiveSimpsonIntegrate::FitFunction fitFunc; - fitFunc.A = p_fit(0); - fitFunc.delt= p_fit(1); - fitFunc.H = p_fit(2); - fitFunc.W = p_fit(3); - fitFunc.C = p_fit(4); - fitFunc.P = p_fit(5); - double lower = fitFunc.P - 3.0 * fitFunc.delt; - double upper = fitFunc.P + 3.0 * fitFunc.delt; - double area = AdaptiveSimpsonIntegrate::integrate(fitFunc, lower, upper); + AdaptiveSimpsonIntegrate::FitFunction fitFunc; + fitFunc.A = p_fit(0); + fitFunc.delt= p_fit(1); + fitFunc.H = p_fit(2); + fitFunc.W = p_fit(3); + fitFunc.C = p_fit(4); + fitFunc.P = p_fit(5); + double lower = fitFunc.P - 3.0 * fitFunc.delt; + double upper = fitFunc.P + 3.0 * fitFunc.delt; + double area = AdaptiveSimpsonIntegrate::integrate(fitFunc, lower, upper); - PeakFitResult result; - result.center = p_fit(5); - result.amplitude = p_fit(0); - result.sigma = p_fit(1); - result.fwhm = fwhm; - result.area = area; - result.baseline = p_fit(4); - result.sigmoidH = p_fit(2); - result.sigmoidW = p_fit(3); -// result.localX = localX; // 保存局部数据,备用 -// result.localY = localY; + PeakFitResult result; + result.center = p_fit(5); + result.amplitude = p_fit(0); + result.sigma = p_fit(1); + result.fwhm = fwhm; + result.area = area; + result.baseline = p_fit(4); + result.sigmoidH = p_fit(2); + result.sigmoidW = p_fit(3); - // 绘制拟合曲线到主图 - double xMinPlot = result.center - 3 * result.sigma; - double xMaxPlot = result.center + 3 * result.sigma; - xMinPlot = qMax(xMinPlot, localX.first()); - xMaxPlot = qMin(xMaxPlot, localX.last()); - QString curveName = QStringLiteral(u"Fit_%1keV").arg(result.center, 0, 'f', 2); - QwtPlotCurve* fitCurve = createFitCurve(result, xMinPlot, xMaxPlot, curveName); - _fitCurves.append(fitCurve); + double xMinPlot = result.center - 3 * result.sigma; + double xMaxPlot = result.center + 3 * result.sigma; + xMinPlot = qMax(xMinPlot, localX.first()); + xMaxPlot = qMin(xMaxPlot, localX.last()); + QwtPlotCurve* fitCurve = createFitCurve(result, xMinPlot, xMaxPlot, QStringLiteral(u"拟合数据")); + fitCurve->setPen(QPen(Qt::blue, 2)); + _fitCurves.append(fitCurve); - results.append(result); - } + results.append(result); + _plot->replot(); - _plot->replot(); // 刷新显示拟合曲线 - return results; + _lastFitResult = result; + _lastFitParams = p_fit; + _lastXMin = xMinPlot; + _lastXMax = xMaxPlot; + _hasLastFit = true; + return results; } QwtPlotCurve *EnergyCountPeakFitView::createFitCurve(const PeakFitResult &result, double xMin, double xMax, const QString &name) @@ -586,10 +671,9 @@ QwtPlotCurve *EnergyCountPeakFitView::createFitCurve(const PeakFitResult &result double y = PhotonPeakModel(x, p); ys.append(y); } - - QwtPlotCurve* curve = new QwtPlotCurve(name); - curve->setSamples(xs, ys); + QwtPlotCurve* curve = new QwtPlotCurve(QStringLiteral(u"拟合数据")); curve->setPen(QPen(Qt::blue, 2)); + curve->setSamples(xs, ys); curve->attach(_plot); return curve; } @@ -605,3 +689,168 @@ void EnergyCountPeakFitView::clearFitCurves() _plot->replot(); } +void EnergyCountPeakFitView::loadHistoryFromFile() +{ + if (!QFileInfo::exists(_historyFilePath)) + return; + + QFile file(_historyFilePath); + if (!file.open(QIODevice::ReadOnly)) + return; + + QByteArray data = file.readAll(); + QJsonDocument doc = QJsonDocument::fromJson(data); + if (!doc.isArray()) + return; + + _fitHistoryList.clear(); + QJsonArray arr = doc.array(); + for (const auto& val : arr) { + if (val.isObject()) + _fitHistoryList.append(PeakFitHistoryItem::fromJson(val.toObject())); + } +} + +void EnergyCountPeakFitView::saveHistoryToFile() +{ + QJsonArray arr; + for (const auto& item : _fitHistoryList) { + arr.append(item.toJson()); + } + QJsonDocument doc(arr); + QFile file(_historyFilePath); + if (file.open(QIODevice::WriteOnly)) { + file.write(doc.toJson()); + } +} + +void EnergyCountPeakFitView::saveCurrentFitToHistory() +{ + if (!_hasLastFit) { + QMessageBox::information(this, QStringLiteral(u"提示"), + QStringLiteral(u"没有可保存的拟合结果,请先框选区域进行拟合。")); + return; + } + + PeakFitHistoryItem item; + item.timestamp = QDateTime::currentDateTime(); + item.center = _lastFitResult.center; + item.fwhm = _lastFitResult.fwhm; + item.area = _lastFitResult.area; + item.amplitude = _lastFitParams(0); + item.sigma = _lastFitParams(1); + item.sigmoidH = _lastFitParams(2); + item.sigmoidW = _lastFitParams(3); + item.baseline = _lastFitParams(4); + item.xMin = _lastXMin; + item.xMax = _lastXMax; + + _fitHistoryList.append(item); + saveHistoryToFile(); + + QMessageBox::information(this, QStringLiteral(u"保存成功"), + QStringLiteral(u"已保存拟合结果(峰中心 = %1 keV)").arg(item.center)); +} + +void EnergyCountPeakFitView::displayFitFromHistory(const PeakFitHistoryItem& item) +{ +// clearFitCurves(); + + arma::vec p(6); + p(0) = item.amplitude; + p(1) = item.sigma; + p(2) = item.sigmoidH; + p(3) = item.sigmoidW; + p(4) = item.baseline; + p(5) = item.center; + + const int numPoints = 200; + QVector xs, ys; + double step = (item.xMax - item.xMin) / (numPoints - 1); + for (int i = 0; i < numPoints; ++i) { + double x = item.xMin + i * step; + xs.append(x); + ys.append(PhotonPeakModel(x, p)); + } + + QwtPlotCurve* curve = new QwtPlotCurve(QStringLiteral(u"历史拟合曲线")); + curve->setPen(QPen(Qt::blue, 2, Qt::DashLine)); + curve->setSamples(xs, ys); + curve->attach(_plot); + _fitCurves.append(curve); + + QwtPlotMarker* infoMarker = new QwtPlotMarker(); + infoMarker->setValue(item.center, 0); + infoMarker->setLabel(QStringLiteral(u"历史峰: %1 keV\nFWHM: %2") + .arg(item.center, 0, 'f', 2) + .arg(item.fwhm, 0, 'f', 2)); + infoMarker->setLabelAlignment(Qt::AlignTop | Qt::AlignRight); + infoMarker->attach(_plot); + + + _plot->replot(); +} + +void EnergyCountPeakFitView::onActionSaveCurrentFit() +{ + saveCurrentFitToHistory(); +} + +void EnergyCountPeakFitView::onActionShowFitHistory() +{ + if (_fitHistoryList.isEmpty()) { + QMessageBox::information(this, QStringLiteral(u"峰拟合结果"), + QStringLiteral(u"暂无历史拟合数据,请先进行拟合并保存。")); + return; + } + + QDialog dlg(this); + dlg.setWindowTitle(QStringLiteral(u"峰拟合结果历史")); + dlg.setMinimumWidth(450); + QVBoxLayout* layout = new QVBoxLayout(&dlg); + + QListWidget* listWidget = new QListWidget(&dlg); + for (const auto& item : _fitHistoryList) { + QString text = QStringLiteral(u"%1 峰中心: %2 keV FWHM: %3 面积: %4") + .arg(item.timestamp.toString("yyyy-MM-dd hh:mm:ss")) + .arg(item.center, 0, 'f', 2) + .arg(item.fwhm, 0, 'f', 2) + .arg(item.area, 0, 'f', 0); + listWidget->addItem(text); + } + layout->addWidget(listWidget); + + QPushButton* btnShow = new QPushButton(QStringLiteral(u"显示选中曲线"), &dlg); + QPushButton* btnDelete = new QPushButton(QStringLiteral(u"删除选中"), &dlg); + QHBoxLayout* btnLayout = new QHBoxLayout(); + btnLayout->addStretch(); + btnLayout->addWidget(btnShow); + btnLayout->addWidget(btnDelete); + layout->addLayout(btnLayout); + + connect(btnShow, &QPushButton::clicked, [&]() { + int row = listWidget->currentRow(); + if (row >= 0 && row < _fitHistoryList.size()) { + displayFitFromHistory(_fitHistoryList[row]); + dlg.accept(); + } else { + QMessageBox::warning(&dlg, QStringLiteral(u"提示"), QStringLiteral(u"请先选择一条记录。")); + } + }); + + connect(btnDelete, &QPushButton::clicked, [&]() { + int row = listWidget->currentRow(); + if (row >= 0) { + _fitHistoryList.removeAt(row); + saveHistoryToFile(); + delete listWidget->takeItem(row); + QMessageBox::information(&dlg, QStringLiteral(u"删除"), QStringLiteral(u"已删除该记录。")); + if (_fitHistoryList.isEmpty()) + dlg.close(); + } else { + QMessageBox::warning(&dlg, QStringLiteral(u"提示"), QStringLiteral(u"请先选择要删除的记录。")); + } + }); + + dlg.exec(); +} diff --git a/src/EnergyCountPeakFitView/EnergyCountPeakFitView.h b/src/EnergyCountPeakFitView/EnergyCountPeakFitView.h index c9d9c40..7f65802 100644 --- a/src/EnergyCountPeakFitView/EnergyCountPeakFitView.h +++ b/src/EnergyCountPeakFitView/EnergyCountPeakFitView.h @@ -7,6 +7,9 @@ #include #include // 新增 #include // 新增 +#include +#include +#include #include "PeakFitParamsDialog.h" #include "DataCalcProcess/FindPeaksBySvd.h" @@ -25,7 +28,22 @@ struct PeakFitResult double sigmoidH; // Sigmoid 项高度 H double sigmoidW; // Sigmoid 项宽度 W }; +struct PeakFitHistoryItem { + QDateTime timestamp; // 时间戳 + double center; // 峰中心能量 (keV) + double fwhm; // 半高宽 + double area; // 峰面积 + double amplitude; // 幅度 A + double sigma; // 标准差 delt + double sigmoidH; // H + double sigmoidW; // W + double baseline; // C + double xMin; // 拟合曲线显示范围左边界 + double xMax; // 拟合曲线显示范围右边界 + QJsonObject toJson() const; + static PeakFitHistoryItem fromJson(const QJsonObject& obj); +}; class PlotRectItem; // 前向声明 class QMenu; @@ -49,12 +67,21 @@ private: void setupMenu(); void loadDataFromFile(const QString &data_name, const QString& filename); + + void loadHistoryFromFile(); + void saveHistoryToFile(); + void saveCurrentFitToHistory(); // 保存当前拟合结果 + void displayFitFromHistory(const PeakFitHistoryItem& item); // 显示历史拟合曲线 private slots: void onActionCurveShowSetting(); void onActionPlotConfigure(); void clearAllSelectionRects(); void clearFitCurves(); // 清除所有拟合曲线 + + // [NEW] 新增菜单槽函数 + void onActionSaveCurrentFit(); + void onActionShowFitHistory(); protected: bool eventFilter(QObject* watched, QEvent* event) override; // 事件过滤器 @@ -63,6 +90,7 @@ private: void updateSelection(const QPoint& pos); void finishSelection(); void addSelectionRect(const QRectF& plotRect); + void fadeSelectionRectBorders(); QList performPeakFitting(const QVector& x, const QVector& y, const arma::vec* userP0 = nullptr); // 根据拟合参数生成曲线 @@ -83,6 +111,18 @@ private: // 存储当前显示的拟合曲线,用于清除 QList _fitCurves; + + // [NEW] 历史记录相关成员 + QList _fitHistoryList; + QString _historyFilePath; + + // [NEW] 最近一次拟合结果(用于手动保存) + PeakFitResult _lastFitResult; + arma::vec _lastFitParams; // 6个拟合参数 (A, delt, H, W, C, P) + double _lastXMin = 0.0, _lastXMax = 0.0; + bool _hasLastFit = false; + QString _workspace; + }; #endif // ENERGYCOUNTPEAKFITVIEW_H