#include "EnergyScaleForm.h" #include "ui_EnergyScaleForm.h" #include #include #include #include #include #include #include #include #include "CustomQwtPlot.h" #include "DataCalcProcess/GaussPolyCoe.h" #include "DataCalcProcess/MathModelDefine.h" #include "DataCalcProcess/NolinearLeastSquaresCurveFit.h" #include #include #include #include #include EnergyScaleForm::EnergyScaleForm(QWidget *parent) : MeasureAnalysisView(parent) , ui(new Ui::EnergyScaleForm) , _plot(nullptr) , _rawDataCurve(nullptr) , _fitCurve(nullptr) , _rawDataSymbol(nullptr) , m_isLoadingTable(false) { ui->setupUi(this); this->_plot = new CustomQwtPlot(this); ui->layout_fittingCurve->addWidget(this->_plot); _rawDataCurve = new QwtPlotCurve("原始数据"); _fitCurve = new QwtPlotCurve("拟合曲线"); _rawDataSymbol = new QwtSymbol(QwtSymbol::Ellipse); _rawDataSymbol->setSize(8); _rawDataSymbol->setBrush(Qt::red); _rawDataSymbol->setPen(QPen(Qt::black, 1)); setupPlot(); initComboBoxUi(); initScaleDataTable(); loadAllFilesInTheFolder(); connect(ui->comboBox_channel, &QComboBox::currentTextChanged, this, &EnergyScaleForm::on_comboBox_channel_currentTextChanged); connect(ui->comboBox_fit_type, &QComboBox::currentTextChanged,this, &EnergyScaleForm::on_comboBox_fit_type_currentTextChanged); connect(ui->tablew_scale_data, &QTableWidget::cellChanged,this, &EnergyScaleForm::on_tablew_scale_data_cellChanged); } EnergyScaleForm::~EnergyScaleForm() { delete ui; } void EnergyScaleForm::InitViewWorkspace(const QString &project_name) { } void EnergyScaleForm::SetAnalyzeDataFilename(const QMap &data_files_set) { if (!data_files_set.isEmpty()) { const QString& data_name = data_files_set.firstKey(); const QString& data_filename = data_files_set.first().toString(); QFileInfo info(data_filename); QDir dir = info.dir(); if (QFileInfo(data_filename).exists()) { ui->groupBox_3->hide(); QFileInfo fileInfo(data_filename); QString baseName =QString("[%1]%2").arg(dir.dirName()).arg(fileInfo.baseName()); ui->lineEdit_name->setText(baseName); ui->lineEdit_name->setReadOnly(true); ui->groupBox_2->hide(); ui->pBtn_SaveAs->setText(QStringLiteral(u"保存到系统")); m_currentFilePath = data_filename; QString errorMsg; m_channelList = parseJsonToChannels(data_filename, errorMsg); on_comboBox_channel_currentTextChanged(QStringLiteral(u"通道1")); } } } void EnergyScaleForm::setupPlot() { _plot->setCanvasBackground(Qt::white); QwtPlotCanvas* canvas = qobject_cast(_plot->canvas()); canvas->setFrameStyle(QFrame::NoFrame); QFont font = this->font(); font.setBold(false); QwtText energy_label = QStringLiteral(u"道址"); energy_label.setFont(font); QwtText count_label = QStringLiteral(u"能量"); count_label.setFont(font); _plot->setAxisTitle(QwtPlot::xBottom, energy_label); _plot->setAxisTitle(QwtPlot::yLeft, count_label); // 设置轴自动缩放 _plot->setAxisAutoScale(QwtPlot::xBottom, true); _plot->setAxisAutoScale(QwtPlot::yLeft, true); _plot->enableAxis(QwtPlot::xBottom); _plot->enableAxis(QwtPlot::yLeft); _plot->SetAxisDragScale(QwtPlot::xBottom, true); // 启用鼠标追踪 _plot->canvas()->setMouseTracking(true); } void EnergyScaleForm::loadAllFilesInTheFolder() { const QString& energy_scale_dir_path = QDir(qApp->applicationDirPath()).filePath("configure/EnergyScale"); QDir dir(energy_scale_dir_path); QFileInfoList infoList = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); ui->listWidget->clear(); for (const QFileInfo &info : infoList) { QString displayName; if (info.isFile()) { displayName = info.baseName(); } else { displayName = info.fileName(); } QListWidgetItem *item = new QListWidgetItem(displayName); item->setData(Qt::UserRole, info.absoluteFilePath()); ui->listWidget->addItem(item); } connect(ui->listWidget, &QListWidget::itemDoubleClicked, this, &EnergyScaleForm::onItemDoubleClicked); } QMap EnergyScaleForm::parseJsonToChannels(const QString &filePath, QString &errorMsg) { QMap result; errorMsg.clear(); QFile file(filePath); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { errorMsg = QString("无法打开文件: %1").arg(file.errorString()); return result; } QByteArray data = file.readAll(); file.close(); QJsonParseError parseError; QJsonDocument doc = QJsonDocument::fromJson(data, &parseError); if (parseError.error != QJsonParseError::NoError) { errorMsg = QString("JSON 解析错误: %1").arg(parseError.errorString()); return result; } if (!doc.isObject()) { errorMsg = "JSON 根节点不是对象"; return result; } QJsonObject rootObj = doc.object(); if (rootObj.contains("Remark") && rootObj["Remark"].isString()) ui->plainTextEdit_description->setPlainText(rootObj["Remark"].toString()); for (auto it = rootObj.begin(); it != rootObj.end(); ++it) { QString channelName = it.key(); QJsonValue channelVal = it.value(); if (!channelVal.isObject()) { errorMsg += QString("通道 %1 的值不是对象,跳过\n").arg(channelName); continue; } QJsonObject channelObj = channelVal.toObject(); ChannelData chData; if (channelObj.contains("EnergyFitDegree") && channelObj["EnergyFitDegree"].isDouble()) { chData.energyFitDegree = channelObj["EnergyFitDegree"].toInt(); } else { errorMsg += QString("通道 %1 缺少 EnergyFitDegree 字段\n").arg(channelName); continue; } if (channelObj.contains("EnergyFitResultCoeffs") && channelObj["EnergyFitResultCoeffs"].isArray()) { QJsonArray coeffsArr = channelObj["EnergyFitResultCoeffs"].toArray(); for (QJsonValue v : coeffsArr) { if (v.isDouble()) chData.energyFitResultCoeffs.append(v.toDouble()); else errorMsg += QString("通道 %1 的 EnergyFitResultCoeffs 包含非数字\n").arg(channelName); } } else { errorMsg += QString("通道 %1 缺少 EnergyFitResultCoeffs 字段\n").arg(channelName); } if (channelObj.contains("FitData") && channelObj["FitData"].isArray()) { QJsonArray fitDataArr = channelObj["FitData"].toArray(); for (QJsonValue rowVal : fitDataArr) { if (!rowVal.isArray()) { errorMsg += QString("通道 %1 的 FitData 行不是数组\n").arg(channelName); continue; } QJsonArray rowArr = rowVal.toArray(); if (rowArr.size() != 7) { errorMsg += QString("通道 %1 的 FitData 行长度不为 7(实际 %2)\n") .arg(channelName).arg(rowArr.size()); continue; } FitDataRow row; row.daoSite = rowArr[0].toDouble(); row.energy = rowArr[1].toDouble(); row.fittingEnergy = rowArr[2].toDouble(); row.energyFittingDeviation = rowArr[3].toDouble(); row.resolution = rowArr[4].toDouble(); row.fitResolution = rowArr[5].toDouble(); row.resolutionFittingDeviation = rowArr[6].toDouble(); chData.fitData.append(row); } } else { errorMsg += QString("通道 %1 缺少 FitData 字段\n").arg(channelName); } if (channelObj.contains("FwhmFitResultCoeffs") && channelObj["FwhmFitResultCoeffs"].isArray()) { QJsonArray fwhmArr = channelObj["FwhmFitResultCoeffs"].toArray(); for (QJsonValue v : fwhmArr) { if (v.isDouble()) chData.fwhmFitResultCoeffs.append(v.toDouble()); else errorMsg += QString("通道 %1 的 FwhmFitResultCoeffs 包含非数字\n").arg(channelName); } } else { errorMsg += QString("通道 %1 缺少 FwhmFitResultCoeffs 字段\n").arg(channelName); } result.insert(channelName, chData); } if (result.isEmpty() && errorMsg.isEmpty()) errorMsg = "未找到任何通道数据。"; return result; } void EnergyScaleForm::initComboBoxUi() { ui->comboBox_fit_type->addItem(QStringLiteral(u"一次拟合")); ui->comboBox_fit_type->addItem(QStringLiteral(u"二次拟合")); for(int i = 1;i <= 32; ++i) { QString channelName = QStringLiteral(u"通道%1").arg(i); ui->comboBox_channel->addItem(channelName); if (!m_channelList.contains(channelName)) { ChannelData emptyData; emptyData.energyFitDegree = 1; m_channelList.insert(channelName, emptyData); } } } void EnergyScaleForm::initScaleDataTable() { QTableWidget* table = ui->tablew_scale_data; table->setColumnCount(7); // 设置表格列名 QStringList headers = { "道址", "能量", "拟合能量", "能量拟合偏差", "分辨率", "拟合分辨率", "分辨率拟合偏差" }; table->setHorizontalHeaderLabels(headers); table->setEditTriggers(QTableWidget::DoubleClicked | QTableWidget::EditKeyPressed); table->setSelectionBehavior(QTableWidget::SelectRows); // 整行选中 table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); } void EnergyScaleForm::showChannelData(const QString channelName) { if (!m_channelList.contains(channelName)) { return; } m_isLoadingTable = true; // 防止触发cellChanged ui->tablew_scale_data->setRowCount(0); const ChannelData& chData = m_channelList[channelName]; switch (chData.energyFitDegree) { case 1: { ui->comboBox_fit_type->setCurrentIndex(0); ui->label_fit_type_text->setText(QStringLiteral(u"y = a + bx")); QString coefficient = QStringLiteral(u"a = %1 , b = %2").arg(chData.energyFitResultCoeffs.at(0)).arg(chData.energyFitResultCoeffs.at(1)); ui->label_fit_result->setText(coefficient); } break; case 2: ui->comboBox_fit_type->setCurrentIndex(1); ui->label_fit_type_text->setText(QStringLiteral(u"y = a + bx + c*x^2")); QString coefficient = QStringLiteral(u"a = %1 , b = %2, c = %3").arg(chData.energyFitResultCoeffs.at(0)).arg(chData.energyFitResultCoeffs.at(1)).arg(chData.energyFitResultCoeffs.at(2)); ui->label_fit_result->setText(coefficient); break; } QString energyCoeffsStr; for (int i = 0; i < chData.energyFitResultCoeffs.size(); ++i) { energyCoeffsStr += QString("系数%1: %2 ").arg(i+1).arg(chData.energyFitResultCoeffs[i], 0, 'f', 6); } QTableWidget* table = ui->tablew_scale_data; table->setRowCount(chData.fitData.size()); for (int row = 0; row < chData.fitData.size(); ++row) { const FitDataRow& fitRow = chData.fitData[row]; table->setItem(row, 0, new QTableWidgetItem(QString::number(fitRow.daoSite, 'f', 3))); table->setItem(row, 1, new QTableWidgetItem(QString::number(fitRow.energy, 'f', 3))); table->setItem(row, 2, new QTableWidgetItem(QString::number(fitRow.fittingEnergy, 'f', 3))); table->setItem(row, 3, new QTableWidgetItem(QString::number(fitRow.energyFittingDeviation, 'f', 3))); table->setItem(row, 4, new QTableWidgetItem(QString::number(fitRow.resolution, 'f', 3))); table->setItem(row, 5, new QTableWidgetItem(QString::number(fitRow.fitResolution, 'f', 3))); table->setItem(row, 6, new QTableWidgetItem(QString::number(fitRow.resolutionFittingDeviation, 'f', 3))); for (int col = 0; col < 7; ++col) { Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if (col == 0 || col == 1 || col == 4) { flags |= Qt::ItemIsEditable; } table->item(row, col)->setFlags(flags); } } m_isLoadingTable = false; } void EnergyScaleForm::showCurve() { clearPlot(); QString channelName = ui->comboBox_channel->currentText(); if (!m_channelList.contains(channelName)) { _plot->replot(); return; } const ChannelData &chData = m_channelList[channelName]; if (chData.fitData.isEmpty()) return; QVector xRaw, yRaw; double xMin = 0; double xMax = 4096; for (const FitDataRow &row : chData.fitData) { xRaw.append(row.daoSite); yRaw.append(row.energy); } _rawDataCurve->setSamples(xRaw, yRaw); _rawDataCurve->setSymbol(_rawDataSymbol); _rawDataCurve->setStyle(QwtPlotCurve::NoCurve); _rawDataCurve->attach(_plot); if (!chData.energyFitResultCoeffs.isEmpty()) { QVector fitPoints = generateFitCurvePoints(chData); _fitCurve->setSamples(fitPoints); _fitCurve->setPen(QPen(Qt::blue, 2)); // 拟合曲线用蓝色粗线 _fitCurve->setStyle(QwtPlotCurve::Lines); _fitCurve->attach(_plot); } _plot->replot(); } void EnergyScaleForm::clearPlot() { _rawDataCurve->detach(); _fitCurve->detach(); _plot->detachItems(QwtPlotItem::Rtti_PlotCurve, false); _plot->replot(); } QVector EnergyScaleForm::generateFitCurvePoints(const ChannelData &chData) { int pointCount = 4096; QVector points; if (chData.energyFitResultCoeffs.isEmpty()) return points; for (int i = 0; i < pointCount; ++i) { double y = 0.0; switch (chData.energyFitDegree) { case 1: // 一次拟合:y = a + b*x y = LinearFunction(i,chData.energyFitResultCoeffs[0],chData.energyFitResultCoeffs[1]); break; case 2: // 二次拟合:y = a + b*x + c*x² if (chData.energyFitResultCoeffs.size() >= 3) { y = QuadraticPolynomial(i,chData.energyFitResultCoeffs[0],chData.energyFitResultCoeffs[1],chData.energyFitResultCoeffs[2]); } break; default: break; } points.append(QPointF(i, y)); } return points; } QVector EnergyScaleForm::generateFitCurvePoints(const double a, const double b, const double c) { int pointCount = 4096; QVector points; // if (chData.energyFitResultCoeffs.isEmpty()) return points; for (int i = 0; i < pointCount; ++i) { double y = 0.0; switch (ui->comboBox_fit_type->currentIndex() + 1) { case 1: y = LinearFunction(i,a,b); ;break; case 2: y = QuadraticPolynomial(i,a,b,c);break; } points.append(QPointF(i, y)); } return points; } void EnergyScaleForm::calculateFitValues(ChannelData &chData) { if (chData.fitData.isEmpty()) return; if (!chData.energyFitResultCoeffs.isEmpty()) { for (auto &row : chData.fitData) { double x = row.daoSite; double fitE = 0.0; if (chData.energyFitDegree == 1 && chData.energyFitResultCoeffs.size() >= 2) { fitE = LinearFunction(x,chData.energyFitResultCoeffs[0],chData.energyFitResultCoeffs[1]); } else if (chData.energyFitDegree == 2 && chData.energyFitResultCoeffs.size() >= 3) { fitE = QuadraticPolynomial(x,chData.energyFitResultCoeffs[0],chData.energyFitResultCoeffs[1],chData.energyFitResultCoeffs[2]); } row.fittingEnergy = fitE; row.energyFittingDeviation = row.energy - fitE; } } if (chData.fwhmFitResultCoeffs.size() >= 2) { arma::vec p(2); p(0) = chData.fwhmFitResultCoeffs[0]; // k p(1) = chData.fwhmFitResultCoeffs[1]; // c for (auto &row : chData.fitData) { double E = row.fittingEnergy; if (E <= 0) { row.fitResolution = 0.0; row.resolutionFittingDeviation = 0.0; continue; } double fitFwhm = FwhmModel(E, p); row.fitResolution = fitFwhm; row.resolutionFittingDeviation = row.resolution - fitFwhm; } } else { // 没有分辨率参数就清空 for (auto &row : chData.fitData) { row.fitResolution = 0.0; row.resolutionFittingDeviation = 0.0; } } } bool EnergyScaleForm::fitFwhmModel(ChannelData &chData) { std::vector E_list, fwhm_list; for (const auto& row : chData.fitData) { if (row.fittingEnergy > 0 && row.resolution > 0) { E_list.push_back(row.fittingEnergy); fwhm_list.push_back(row.resolution); } } if (E_list.size() < 2) { return false; } arma::vec E(E_list.data(), E_list.size()); arma::vec FWHM(fwhm_list.data(), fwhm_list.size()); arma::vec params = NolinearLeastSquaresCurveFit::Lsqcurvefit(FwhmModel,E,FWHM, { 1.0, 1.0 }); chData.fwhmFitResultCoeffs.clear(); chData.fwhmFitResultCoeffs.append(params(0)); // k chData.fwhmFitResultCoeffs.append(params(1)); // c return true; } bool EnergyScaleForm::saveChannelDataToJson(const QString &filePath) { if (filePath.isEmpty()) return false; QJsonObject rootObj; QString remark; if(systemEnble) remark = ui->plainTextEdit_description->property("systemData").toString(); else remark = ui->plainTextEdit_description->toPlainText().trimmed(); rootObj["Remark"] = remark; for (auto it = m_channelList.begin(); it != m_channelList.end(); ++it) { QString channelName = it.key(); const ChannelData& chData = it.value(); QJsonObject channelObj; channelObj["EnergyFitDegree"] = chData.energyFitDegree; // 能量拟合系数 QJsonArray energyCoeffsArr; for (double coeff : chData.energyFitResultCoeffs) { energyCoeffsArr.append(coeff); } channelObj["EnergyFitResultCoeffs"] = energyCoeffsArr; // 拟合数据 QJsonArray fitDataArr; for (const FitDataRow& row : chData.fitData) { QJsonArray rowArr; rowArr.append(row.daoSite); rowArr.append(row.energy); rowArr.append(row.fittingEnergy); rowArr.append(row.energyFittingDeviation); rowArr.append(row.resolution); rowArr.append(row.fitResolution); rowArr.append(row.resolutionFittingDeviation); fitDataArr.append(rowArr); } channelObj["FitData"] = fitDataArr; QJsonArray fwhmCoeffsArr; for (double coeff : chData.fwhmFitResultCoeffs) { fwhmCoeffsArr.append(coeff); } channelObj["FwhmFitResultCoeffs"] = fwhmCoeffsArr; rootObj[channelName] = channelObj; } QJsonDocument doc(rootObj); QFile file(filePath); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(this, "错误", QString("保存文件失败:%1").arg(file.errorString())); return false; } file.write(doc.toJson(QJsonDocument::Indented)); file.close(); return true; } bool EnergyScaleForm::isDaoSiteDuplicated(const QString &channelName, double daoSite) { if (!m_channelList.contains(channelName)) return false; const auto& fitData = m_channelList[channelName].fitData; for (const auto& row : fitData) { if (qAbs(row.daoSite - daoSite) < 1e-3) { return true; } } return false; } void EnergyScaleForm::onItemDoubleClicked(QListWidgetItem *item) { if (!item) return; ui->plainTextEdit_description->clear(); QString filePath = item->data(Qt::UserRole).toString(); if (filePath.isEmpty()) return; ui->lineEdit_name->setText(item->text()); m_currentFilePath = filePath; QString errorMsg; m_channelList = parseJsonToChannels(filePath, errorMsg); on_comboBox_channel_currentTextChanged(QStringLiteral(u"通道1")); } void EnergyScaleForm::on_comboBox_channel_currentTextChanged(const QString &text) { if (text.isEmpty()) { return; } if (!m_channelList.contains(text)) { ChannelData emptyData; emptyData.energyFitDegree = 1; m_channelList.insert(text, emptyData); } showChannelData(text); showCurve(); } void EnergyScaleForm::on_comboBox_fit_type_currentTextChanged(const QString &text) { if(text == QStringLiteral(u"一次拟合")) { ui->label_fit_type_text->setText(QStringLiteral(u"y = a + bx")); } else if(text == QStringLiteral(u"二次拟合")) { ui->label_fit_type_text->setText(QStringLiteral(u"y = a + bx + c*x^2")); } showCurve(); } void EnergyScaleForm::on_pBtn_Add_clicked() { QString channelName = ui->comboBox_channel->currentText(); QDialog dialog(this); dialog.setWindowTitle("添加能量刻度点"); dialog.setFixedSize(300, 180); // 调整高度,预留分辨率输入 dialog.setModal(true); QVBoxLayout* mainLayout = new QVBoxLayout(&dialog); mainLayout->setSpacing(15); mainLayout->setContentsMargins(20, 20, 20, 20); // 道址输入 QHBoxLayout* daoLayout = new QHBoxLayout(); QLabel* daoLabel = new QLabel("道址:"); QLineEdit* daoEdit = new QLineEdit(); daoEdit->setPlaceholderText("请输入道址值(数字)"); QDoubleValidator* daoValidator = new QDoubleValidator(0, 100000, 3, daoEdit); daoValidator->setNotation(QDoubleValidator::StandardNotation); daoEdit->setValidator(daoValidator); daoLayout->addWidget(daoLabel); daoLayout->addWidget(daoEdit); // 能量输入 QHBoxLayout* energyLayout = new QHBoxLayout(); QLabel* energyLabel = new QLabel("能量:"); QLineEdit* energyEdit = new QLineEdit(); energyEdit->setPlaceholderText("请输入能量值(数字)"); QDoubleValidator* energyValidator = new QDoubleValidator(0, 10000, 3, energyEdit); energyValidator->setNotation(QDoubleValidator::StandardNotation); energyEdit->setValidator(energyValidator); energyLayout->addWidget(energyLabel); energyLayout->addWidget(energyEdit); //分辨率输入 QHBoxLayout* resolutionLayout = new QHBoxLayout(); QLabel* resolutionLabel = new QLabel("分辨率:"); QLineEdit* resolutionEdit = new QLineEdit(); resolutionEdit->setPlaceholderText("请输入分辨率(数字)"); QDoubleValidator* resolutionValidator = new QDoubleValidator(0, 1000, 3, resolutionEdit); resolutionValidator->setNotation(QDoubleValidator::StandardNotation); resolutionEdit->setValidator(resolutionValidator); resolutionLayout->addWidget(resolutionLabel); resolutionLayout->addWidget(resolutionEdit); // 按钮行 QHBoxLayout* btnLayout = new QHBoxLayout(); QPushButton* okBtn = new QPushButton("确定"); QPushButton* cancelBtn = new QPushButton("取消"); okBtn->setDefault(true); btnLayout->addStretch(); btnLayout->addWidget(okBtn); btnLayout->addWidget(cancelBtn); mainLayout->addLayout(daoLayout); mainLayout->addLayout(energyLayout); mainLayout->addLayout(resolutionLayout); mainLayout->addLayout(btnLayout); connect(okBtn, &QPushButton::clicked, &dialog, &QDialog::accept); connect(cancelBtn, &QPushButton::clicked, &dialog, &QDialog::reject); daoEdit->setFocus(); if (dialog.exec() != QDialog::Accepted) { return; } QString daoStr = daoEdit->text().trimmed(); QString energyStr = energyEdit->text().trimmed(); QString resolutionStr = resolutionEdit->text().trimmed(); if (daoStr.isEmpty() || energyStr.isEmpty()) { QMessageBox::warning(this, "输入错误", "道址和能量不能为空"); return; } bool daoOk = false, energyOk = false, resolutionOk = false; double daoSite = daoStr.toDouble(&daoOk); double energy = energyStr.toDouble(&energyOk); double resolution = resolutionStr.isEmpty() ? 0.0 : resolutionStr.toDouble(&resolutionOk); if (!daoOk || !energyOk || (!resolutionStr.isEmpty() && !resolutionOk)) { QMessageBox::warning(this, "输入错误", "请输入有效的数字"); return; } if (isDaoSiteDuplicated(channelName, daoSite)) { QMessageBox::warning(this, "输入错误", "该道址已存在,无法重复添加"); return; } ChannelData& chData = m_channelList[channelName]; QTableWidget* table = ui->tablew_scale_data; FitDataRow newRow; newRow.daoSite = daoSite; newRow.energy = energy; newRow.resolution = resolution; newRow.fittingEnergy = 0.0; newRow.energyFittingDeviation = 0.0; newRow.fitResolution = 0.0; newRow.resolutionFittingDeviation = 0.0; chData.fitData.append(newRow); m_isLoadingTable = true; int newRowIndex = table->rowCount(); table->insertRow(newRowIndex); table->setItem(newRowIndex, 0, new QTableWidgetItem(QString::number(daoSite, 'f', 3))); table->setItem(newRowIndex, 1, new QTableWidgetItem(QString::number(energy, 'f', 3))); table->setItem(newRowIndex, 4, new QTableWidgetItem(QString::number(resolution, 'f', 3))); for (int col = 2; col < 7; ++col) { if (col != 4) { table->setItem(newRowIndex, col, new QTableWidgetItem("0.000")); } } for (int col = 0; col < 7; ++col) { Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if (col == 0 || col == 1 || col == 4) { flags |= Qt::ItemIsEditable; } table->item(newRowIndex, col)->setFlags(flags); } m_isLoadingTable = false; table->scrollToBottom(); table->selectRow(newRowIndex); showCurve(); QMessageBox::information(this, "成功", "数据点添加成功"); } void EnergyScaleForm::on_pBtn_Delete_clicked() { QTableWidget* table = ui->tablew_scale_data; int currentRow = table->currentRow(); if (currentRow < 0 || currentRow >= table->rowCount()) { QMessageBox::warning(this, "提示", "请选中要删除的行"); return; } if (QMessageBox::question(this, "确认", "是否删除选中的行?", QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } table->removeRow(currentRow); QString channelName = ui->comboBox_channel->currentText(); if (m_channelList.contains(channelName)) { ChannelData& chData = m_channelList[channelName]; if (currentRow < chData.fitData.size()) { chData.fitData.remove(currentRow); } // 删除后重新计算拟合值(如果有拟合系数) calculateFitValues(chData); // 刷新表格显示 showChannelData(channelName); } showCurve(); } void EnergyScaleForm::on_pBtn_fitting_clicked() { QString channelName = ui->comboBox_channel->currentText(); if (!m_channelList.contains(channelName)) { QMessageBox::warning(this, "拟合失败", "当前通道无数据"); return; } ChannelData& chData = m_channelList[channelName]; int rowCount = ui->tablew_scale_data->rowCount(); int fitDegree = ui->comboBox_fit_type->currentIndex() + 1; int minEnergyPoints = (fitDegree == 1) ? 2 : 3; if (rowCount < minEnergyPoints) { QMessageBox::warning(this, "拟合失败", QString("至少需要%1个数据点才能进行%2").arg(minEnergyPoints).arg(fitDegree == 1 ? "一次拟合" : "二次拟合")); return; } std::vector xRow,yRow; for(int i = 0; i < rowCount;++i) { QTableWidgetItem* daoItem = ui->tablew_scale_data->item(i,0); QTableWidgetItem* energyItem = ui->tablew_scale_data->item(i,1); if (!daoItem || !energyItem) continue; bool daoOk = false, energyOk = false; double dao = daoItem->text().toDouble(&daoOk); double energy = energyItem->text().toDouble(&energyOk); if (daoOk && energyOk) { xRow.push_back(dao); yRow.push_back(energy); } } if (xRow.size() < minEnergyPoints || yRow.size() < minEnergyPoints) { QMessageBox::warning(this, "拟合失败", "有效能量数据点不足"); return; } std::vector energyCoeff = GaussPolyCoe::PolynomialFit(xRow, yRow, fitDegree); if (energyCoeff.size() < fitDegree + 1) { QMessageBox::warning(this, "拟合失败", "能量拟合计算出错"); return; } chData.energyFitDegree = fitDegree; chData.energyFitResultCoeffs.clear(); for (double coeff : energyCoeff) { chData.energyFitResultCoeffs.append(coeff); } fitFwhmModel(chData); calculateFitValues(chData); m_isLoadingTable = true; showChannelData(channelName); m_isLoadingTable = false; showCurve(); QString energyMsg; if (fitDegree == 1) { energyMsg = QString("能量拟合成功:a = %1 , b = %2") .arg(energyCoeff[0], 0, 'f', 6) .arg(energyCoeff[1], 0, 'f', 6); } else { energyMsg = QString("能量拟合成功:a = %1 , b = %2 , c = %3") .arg(energyCoeff[0], 0, 'f', 6) .arg(energyCoeff[1], 0, 'f', 6) .arg(energyCoeff[2], 0, 'f', 6); } } void EnergyScaleForm::on_tablew_scale_data_cellChanged(int row, int column) { if (m_isLoadingTable) return; QString channelName = ui->comboBox_channel->currentText(); if (!m_channelList.contains(channelName)) return; if (row < 0 || row >= m_channelList[channelName].fitData.size()) return; ChannelData& chData = m_channelList[channelName]; FitDataRow& currentRow = chData.fitData[row]; QTableWidgetItem* item = ui->tablew_scale_data->item(row, column); if (!item) return; bool ok = false; double value = item->text().toDouble(&ok); if (!ok) { // 输入非数字时恢复原值 m_isLoadingTable = true; if (column == 0) { item->setText(QString::number(currentRow.daoSite, 'f', 3)); } else if (column == 1) { item->setText(QString::number(currentRow.energy, 'f', 3)); } else if (column == 4) { item->setText(QString::number(currentRow.resolution, 'f', 3)); } m_isLoadingTable = false; QMessageBox::warning(this, "输入错误", "请输入有效的数字"); return; } bool needRecalc = false; if (column == 0) { // 检查道址重复 if (isDaoSiteDuplicated(channelName, value) && qAbs(currentRow.daoSite - value) > 1e-3) { m_isLoadingTable = true; item->setText(QString::number(currentRow.daoSite, 'f', 3)); m_isLoadingTable = false; QMessageBox::warning(this, "提示", "道址重复,修改失败"); return; } currentRow.daoSite = value; needRecalc = true; } else if (column == 1) { currentRow.energy = value; needRecalc = true; } else if (column == 4) { currentRow.resolution = value; needRecalc = true; } if (needRecalc) { // calculateFitValues(chData); // m_isLoadingTable = true; // showChannelData(channelName); // m_isLoadingTable = false; // showCurve(); on_pBtn_fitting_clicked(); } } void EnergyScaleForm::on_pBtn_SaveAs_clicked() { QString SaveAsName = ui->pBtn_SaveAs->text(); QString targetFilePath; QString fileName; QString remark; if(SaveAsName.contains("保存到系统")) { systemEnble = true; QDialog dialog(this); dialog.setWindowTitle(QStringLiteral(u"保存到系统 - 能量刻度配置")); dialog.setFixedSize(400, 250); dialog.setModal(true); QVBoxLayout* mainLayout = new QVBoxLayout(&dialog); mainLayout->setSpacing(15); mainLayout->setContentsMargins(20, 20, 20, 20); QHBoxLayout* nameLayout = new QHBoxLayout(); QLabel* nameLabel = new QLabel(QStringLiteral(u"能量刻度名称:")); QLineEdit* nameEdit = new QLineEdit(); nameEdit->setPlaceholderText(QStringLiteral(u"请输入刻度名称:")); if (!ui->lineEdit_name->text().isEmpty()) { QString baseName = ui->lineEdit_name->text(); baseName = baseName.contains("[") ? baseName.split("]").last().trimmed() : baseName; nameEdit->setText(baseName); } nameLayout->addWidget(nameLabel); nameLayout->addWidget(nameEdit); // 备注输入框 QVBoxLayout* remarkLayout = new QVBoxLayout(); QLabel* remarkLabel = new QLabel(QStringLiteral(u"备注:")); QTextEdit* remarkEdit = new QTextEdit(); remarkEdit->setPlaceholderText(QStringLiteral(u"请输入备注信息(可选)")); remarkEdit->setPlainText(ui->plainTextEdit_description->toPlainText()); // 填充现有备注 remarkLayout->addWidget(remarkLabel); remarkLayout->addWidget(remarkEdit); // 按钮组 QHBoxLayout* btnLayout = new QHBoxLayout(); QPushButton* okBtn = new QPushButton(QStringLiteral(u"保存")); QPushButton* cancelBtn = new QPushButton(QStringLiteral(u"取消")); okBtn->setDefault(true); btnLayout->addStretch(); btnLayout->addWidget(okBtn); btnLayout->addWidget(cancelBtn); // 组装布局 mainLayout->addLayout(nameLayout); mainLayout->addLayout(remarkLayout); mainLayout->addLayout(btnLayout); connect(okBtn, &QPushButton::clicked, &dialog, &QDialog::accept); connect(cancelBtn, &QPushButton::clicked, &dialog, &QDialog::reject); if (dialog.exec() != QDialog::Accepted) { return; } fileName = nameEdit->text().trimmed(); remark = remarkEdit->toPlainText().trimmed(); if (fileName.isEmpty()) { QMessageBox::warning(this, QStringLiteral(u"输入错误"), QStringLiteral(u"能量刻度名称不能为空!")); return; } QString defaultDir = QDir(qApp->applicationDirPath()).filePath("configure/EnergyScale"); QDir dir(defaultDir); if (!dir.exists() && !dir.mkpath(".")) { QMessageBox::critical(this, QStringLiteral(u"错误"), QStringLiteral(u"无法创建目录:%1").arg(defaultDir)); return; } targetFilePath = dir.filePath(fileName + ".json"); // 强制.json后缀 if (QFile::exists(targetFilePath)) { QMessageBox::StandardButton ret = QMessageBox::question( this, QStringLiteral(u"文件已存在"), QStringLiteral(u"名称为“%1”的能量刻度文件已存在,是否覆盖?").arg(fileName), QMessageBox::Yes | QMessageBox::No, QMessageBox::No ); if (ret != QMessageBox::Yes) { return; } } ui->plainTextEdit_description->setProperty("systemData",remark); }else{ systemEnble = false; // 选择保存路径 QString defaultDir = QDir(qApp->applicationDirPath()).filePath("configure/EnergyScale"); targetFilePath = QFileDialog::getSaveFileName(this, "另存为能量刻度文件", defaultDir, "JSON文件 (*.json);;所有文件 (*.*)"); if (targetFilePath.isEmpty()) { return; } // 确保后缀是.json if (!targetFilePath.endsWith(".json", Qt::CaseInsensitive)) { targetFilePath += ".json"; } } // 保存文件 if (saveChannelDataToJson(targetFilePath)) { // m_currentFilePath = targetFilePath; // 更新当前文件路径 // ui->lineEdit_name->setText(QFileInfo(targetFilePath).baseName()); loadAllFilesInTheFolder(); } } void EnergyScaleForm::on_pBtn_Save_clicked() { systemEnble = false; if (saveChannelDataToJson(m_currentFilePath)) { QMessageBox::information(this, "成功", "数据保存成功"); } else { QMessageBox::critical(this, "失败", "数据保存失败"); } } void EnergyScaleForm::on_pBtn_Add_File_clicked() { bool isOk = false; QString fileName = QInputDialog::getText(this, QStringLiteral(u"新建能量刻度文件"), QStringLiteral(u"请输入文件名(无需后缀):"), QLineEdit::Normal, QStringLiteral(u"新刻度文件"), &isOk); if (!isOk || fileName.trimmed().isEmpty()) { return; // 用户取消或输入为空 } fileName = fileName.trimmed(); const QString energyScaleDir = QDir(qApp->applicationDirPath()).filePath("configure/EnergyScale"); QDir dir(energyScaleDir); if (!dir.exists() && !dir.mkpath(".")) { QMessageBox::critical(this, QStringLiteral(u"错误"), QStringLiteral(u"无法创建目录:%1").arg(energyScaleDir)); return; } QString filePath = dir.filePath(fileName + ".json"); if (QFile::exists(filePath)) { QMessageBox::warning(this, QStringLiteral(u"提示"), QStringLiteral(u"文件%1已存在,请更换文件名").arg(fileName)); return; } QJsonObject rootObj; for (int i = 1; i <= 32; ++i) { QString channelName = QStringLiteral(u"通道%1").arg(i); QJsonObject channelObj; channelObj["EnergyFitDegree"] = 1; // 默认一次拟合 channelObj["EnergyFitResultCoeffs"] = QJsonArray(); // 空系数 channelObj["FitData"] = QJsonArray(); // 空拟合数据 channelObj["FwhmFitResultCoeffs"] = QJsonArray(); // 空分辨率系数 rootObj[channelName] = channelObj; } QJsonDocument doc(rootObj); QFile file(filePath); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(this, QStringLiteral(u"错误"), QStringLiteral(u"无法创建文件:%1\n%2").arg(filePath).arg(file.errorString())); return; } file.write(doc.toJson(QJsonDocument::Indented)); file.close(); loadAllFilesInTheFolder(); for (int i = 0; i < ui->listWidget->count(); ++i) { QListWidgetItem* item = ui->listWidget->item(i); if (item->text() == fileName) { ui->listWidget->setCurrentItem(item); break; } } QMessageBox::information(this, QStringLiteral(u"成功"), QStringLiteral(u"文件%1创建成功").arg(fileName + ".json")); } void EnergyScaleForm::on_pBtn_Delete_File_clicked() { QListWidgetItem* selectedItem = ui->listWidget->currentItem(); if (!selectedItem) { QMessageBox::warning(this, QStringLiteral(u"提示"), QStringLiteral(u"请先选中要删除的文件/文件夹")); return; } QString filePath = selectedItem->data(Qt::UserRole).toString(); if (filePath.isEmpty()) { QMessageBox::warning(this, QStringLiteral(u"错误"), QStringLiteral(u"无法获取文件路径")); return; } QMessageBox::StandardButton ret = QMessageBox::question(this, QStringLiteral(u"确认删除"), QStringLiteral(u"是否确定删除%1?\n删除后无法恢复!").arg(selectedItem->text()), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (ret != QMessageBox::Yes) { return; } bool isDeleted = false; QFileInfo fileInfo(filePath); if (fileInfo.isFile()) { QFile file(filePath); isDeleted = file.remove(); } if (isDeleted) { loadAllFilesInTheFolder(); if (m_currentFilePath == filePath) { m_currentFilePath.clear(); ui->lineEdit_name->clear(); ui->tablew_scale_data->setRowCount(0); clearPlot(); _plot->replot(); ui->label_fit_result->clear(); ui->label_fit_type_text->clear(); } QMessageBox::information(this, QStringLiteral(u"成功"), QStringLiteral(u"删除成功")); } else { QMessageBox::critical(this, QStringLiteral(u"错误"), QStringLiteral(u"删除失败:%1").arg(filePath)); } }