#include "NuclideAnalysisView.h" #include "CustomQwtPlot.h" #include "csv.h" #include #include #include #include #include "MeasureAnalysisProjectModel.h" #include #include #include #include #include #include #include #include #include #include "AddNuclideDialog.h" // 更新选择框显示 static void updateSelectionMarkers(QwtPlot* plot, QwtPlotMarker*& leftMarker, QwtPlotMarker*& rightMarker, double x1, double x2) { double x_left = qMin(x1, x2); double x_right = qMax(x1, x2); // 左边界 if (!leftMarker) { leftMarker = new QwtPlotMarker(); leftMarker->setLineStyle(QwtPlotMarker::VLine); leftMarker->setLinePen(QPen(Qt::blue, 1, Qt::DashLine)); leftMarker->attach(plot); } leftMarker->setValue(x_left, 0); // 右边界 if (!rightMarker) { rightMarker = new QwtPlotMarker(); rightMarker->setLineStyle(QwtPlotMarker::VLine); rightMarker->setLinePen(QPen(Qt::blue, 1, Qt::DashLine)); rightMarker->attach(plot); } rightMarker->setValue(x_right, 0); plot->replot(); } // 清除选择框 static void clearSelectionMarkers(QwtPlotMarker*& leftMarker, QwtPlotMarker*& rightMarker) { if (leftMarker) { leftMarker->detach(); delete leftMarker; leftMarker = nullptr; } if (rightMarker) { rightMarker->detach(); delete rightMarker; rightMarker = nullptr; } } NuclideAnalysisView::NuclideAnalysisView(QWidget* parent) : MeasureAnalysisView { parent } { this->setViewType(PlotFrame); QHBoxLayout* layout = new QHBoxLayout(this); this->_plot = new CustomQwtPlot(this); layout->addWidget(this->_plot); setupPlot(); this->_menu = new QMenu(this); setupMenu(); } NuclideAnalysisView::~NuclideAnalysisView() { LOG_DEBUG(QStringLiteral(u"%1析构.").arg(this->GetViewName())); } void NuclideAnalysisView::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); } void NuclideAnalysisView::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(); if (QFileInfo(data_filename).exists()) { loadDataFromFile(data_name, data_filename); } } } bool NuclideAnalysisView::eventFilter(QObject *obj, QEvent *event) { if (obj == _plot->canvas()) { QMouseEvent* mouseEvent = static_cast(event); // 鼠标按下:记录起始位置 if (event->type() == QEvent::MouseButtonPress) { if (mouseEvent->button() == Qt::LeftButton) { m_bSelecting = true; m_selectStartX = _plot->invTransform(QwtPlot::xBottom, mouseEvent->pos().x()); m_selectStartPos = mouseEvent->pos(); updateSelectionMarkers(_plot, m_selectLeftMarker, m_selectRightMarker, m_selectStartX, m_selectStartX); return true; } } // 鼠标移动:更新选择框 if (event->type() == QEvent::MouseMove) { if (m_bSelecting) { double xValue = _plot->invTransform(QwtPlot::xBottom, mouseEvent->pos().x()); updateSelectionMarkers(_plot, m_selectLeftMarker, m_selectRightMarker, m_selectStartX, xValue); return true; } } // 鼠标释放 if (event->type() == QEvent::MouseButtonRelease) { if (mouseEvent->button() == Qt::LeftButton && m_bSelecting) { m_bSelecting = false; double xEnd = _plot->invTransform(QwtPlot::xBottom, mouseEvent->pos().x()); int dx = mouseEvent->pos().x() - m_selectStartPos.x(); int dy = mouseEvent->pos().y() - m_selectStartPos.y(); double moveDistance = sqrt(dx*dx + dy*dy); // 清除选择框 clearSelectionMarkers(m_selectLeftMarker, m_selectRightMarker); _plot->replot(); if (moveDistance > 10) { double x1 = qMin(m_selectStartX, xEnd); double x2 = qMax(m_selectStartX, xEnd); if (x2 - x1 > 1.0 && HasSpectrumData()) { PeakFitResultNuclide result = FitPeakByRange(x1, x2, m_spectrumX, m_spectrumY); if (result.success) { // 追加到累积结果 m_allFitResults.append(result); DrawPeaks(m_allFitResults); queryNuclidesForPeak(result); } } } return true; } } } return MeasureAnalysisView::eventFilter(obj, event); } void NuclideAnalysisView::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"能量(KeV)"); 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); // 给canvas安装事件过滤器,捕获鼠标点击 _plot->canvas()->installEventFilter(this); } void NuclideAnalysisView::setupMenu() { this->setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &NuclideAnalysisView::customContextMenuRequested, [this](const QPoint& pos) { this->_menu->exec(this->mapToGlobal(pos)); }); QAction* action_plot_reset = this->_menu->addAction(QStringLiteral(u"还原")); action_plot_reset->setObjectName("plot_reset"); connect(action_plot_reset, &QAction::triggered, [this]() { this->_plot->ResetPlot(); }); this->_menu->addSeparator(); QAction* action_plot_config = this->_menu->addAction(QStringLiteral(u"图表配置")); action_plot_config->setObjectName("plot_config"); connect(action_plot_config, &QAction::triggered, this, &NuclideAnalysisView::onActionPlotConfigure); this->_menu->addSeparator(); // QAction* action_plot_select = this->_menu->addAction(QStringLiteral(u"在谱线上选择")); // action_plot_select->setObjectName("plot_select"); // connect(action_plot_select, &QAction::triggered, this, &NuclideAnalysisView::onActionPlotSelect); // this->_menu->addSeparator(); QAction* action_plot_clear = this->_menu->addAction(QStringLiteral(u"清除")); action_plot_clear->setObjectName("plot_clear"); connect(action_plot_clear, &QAction::triggered, this, &NuclideAnalysisView::onActionPlotClear); this->_menu->addSeparator(); setupNuclideDialog(); } void NuclideAnalysisView::loadDataFromFile(const QString& data_name, const QString& filename) { // 加载新数据前清空旧数据 _plot->replot(); std::string address_str = QString(QStringLiteral(u"能量(KeV)")).toStdString(); std::string count_str = QString(QStringLiteral(u"计数")).toStdString(); io::CSVReader< 2, io::trim_chars<' ', '\t'>, io::double_quote_escape<',', '"'>, io::throw_on_overflow, io::empty_line_comment> reader(QStrToSysPath(filename)); reader.read_header(io::ignore_extra_column, address_str, count_str); double energy; unsigned long long energy_count; QVector x, y; while (reader.read_row(energy, energy_count)) { x.push_back(energy); y.push_back(energy_count); if(_x_max < energy) _x_max = energy; if(_y_max < energy_count) _y_max = energy_count; } _plot->SetAxisInitRange(QwtPlot::xBottom,0.0f,_x_max); _plot->SetAxisInitRange(QwtPlot::yLeft,0.0f,_y_max); // 绘制原始数据曲线 QwtPlotCurve* curve = new QwtPlotCurve(QStringLiteral(u"原始数据")); curve->setPen(QPen(Qt::gray, 2)); // 原始数据统一灰色 curve->setSamples(x, y); _plot->AddCurve(curve,false); m_spectrumX = x; m_spectrumY = y; // QVector results =FitThreePeaksFromSpectrum(x,y); // DrawPeaks(results,y); } void NuclideAnalysisView::onActionPlotConfigure() { } void NuclideAnalysisView::onActionPlotSelect() { m_bSelectHS = true; } void NuclideAnalysisView::onActionPlotClear() { if (!_plot) { return; } for (int i = 0; i < m_peakMarkers.size(); ++i) { if (m_peakMarkers[i]) { m_peakMarkers[i]->detach(); delete m_peakMarkers[i]; } } m_peakMarkers.clear(); if (_vLineMarker) { _vLineMarker->detach(); delete _vLineMarker; _vLineMarker = nullptr; } if (m_selectLeftMarker) { m_selectLeftMarker->detach(); delete m_selectLeftMarker; m_selectLeftMarker = nullptr; } if (m_selectRightMarker) { m_selectRightMarker->detach(); delete m_selectRightMarker; m_selectRightMarker = nullptr; } for (QwtPlotMarker* marker : m_selectedNuclideMarkers) { if (marker) { marker->detach(); delete marker; } } m_selectedNuclideMarkers.clear(); m_allFitResults.clear(); m_nuclideTable->setRowCount(0); m_bSelecting = false; m_bSelectHS = false; _plot->replot(); } void NuclideAnalysisView::addVerticalLine(double xValue, const QColor& color) { if(!m_bSelectHS) return; m_bSelectHS = false; if (!_vLineMarker) { _vLineMarker = new QwtPlotMarker(); _vLineMarker->setLineStyle(QwtPlotMarker::VLine); _vLineMarker->setLinePen(QPen(color, 1, Qt::SolidLine)); _vLineMarker->attach(_plot); } else { _vLineMarker->setLinePen(QPen(color, 1, Qt::SolidLine)); } _vLineMarker->setValue(xValue, 0); _plot->replot(); } // 用光子峰模型拟合并计算单个峰的半高宽 PeakFitResultNuclide NuclideAnalysisView::FitPhotonPeakCalcFwhm(const QVector& x, const QVector& y) { PeakFitResultNuclide result; result.success = false; result.center = 0.0; result.amplitude = 0.0; result.sigma = 0.0; result.fwhm = 0.0; result.area = 0.0; result.baseline = 0.0; result.sigmoidH = 0.0; result.sigmoidW = 0.0; // 参数校验 if (x.size() < 6 || y.size() < 6 || x.size() != y.size()) { return result; } int n = x.size(); arma::vec arma_x(n); arma::vec arma_y(n); for (int i = 0; i < n; ++i) { arma_x(i) = x[i]; arma_y(i) = y[i]; } arma::vec p0 = EstimatePhotonPeakModelInitialParams(arma_x, arma_y); arma::vec p_fit = NolinearLeastSquaresCurveFit::Lsqcurvefit(PhotonPeakModel, arma_x, arma_y, p0); if (p_fit.n_elem >= 6) { double A = p_fit(0); // 高斯振幅 double delt = p_fit(1); // 高斯宽度(sigma) double H = p_fit(2); // Sigmoid 高度 double W = p_fit(3); // Sigmoid 宽度 double C = p_fit(4); // 常数本底 double P = p_fit(5); // 峰位 double fwhm = delt * 2.355; double area = A * delt * 2.5066; // sqrt(2π) ≈ 2.5066 result.success = true; result.center = P; result.amplitude = A; result.sigma = delt; result.fwhm = fwhm; result.area = area; result.baseline = C; result.sigmoidH = H; result.sigmoidW = W; } return result; } // 根据x范围截取数据并拟合计FWHM PeakFitResultNuclide NuclideAnalysisView::FitPeakByRange(double x_start, double x_end, const QVector& x, const QVector& y) { PeakFitResultNuclide result; result.success = false; if (x.size() != y.size() || x_start >= x_end) { return result; } // 截取指定范围内的数据 QVector x_peak, y_peak; for (int i = 0; i < x.size(); ++i) { if (x[i] >= x_start && x[i] <= x_end) { x_peak.append(x[i]); y_peak.append(y[i]); } } // 调用拟合函数 result = FitPhotonPeakCalcFwhm(x_peak, y_peak); return result; } // 从整张谱中自动寻找三个最强的峰并计算FWHM(SVD寻峰) QVector NuclideAnalysisView::FitThreePeaksFromSpectrum( const QVector& x, const QVector& y, double height_threshold, int min_distance) { QVector results; if (x.size() < 10 || y.size() < 10 || x.size() != y.size()) { return results; } // 将 QVector 转换为 arma::mat(FindPeaksBySvd 需要矩阵格式) int n = x.size(); arma::mat spec_data(n, 2); for (int i = 0; i < n; ++i) { spec_data(i, 0) = x[i]; // 第一列:x(能量/道址) spec_data(i, 1) = y[i]; // 第二列:y(计数) } FindPeaksBySvd peakFinder; std::vector peaks; try { peaks = peakFinder.FindPeaks(spec_data, 40); // step_w = 7 } catch (const std::string& e) { return results; } if (peaks.empty()) { return results; } // std::sort(peaks.begin(), peaks.end(), // [](const FindPeaksBySvd::PeakInfo& a, const FindPeaksBySvd::PeakInfo& b) { // return a.height > b.height; // }); // if (peaks.size() > 3) { // peaks.resize(3); // } std::sort(peaks.begin(), peaks.end(), [](const FindPeaksBySvd::PeakInfo& a, const FindPeaksBySvd::PeakInfo& b) { return a.pos < b.pos; }); for (size_t i = 0; i < peaks.size(); ++i) { const FindPeaksBySvd::PeakInfo& peak = peaks[i]; double peak_pos = peak.pos; double window = peak.width; if (window < 10.0) window = 10.0; double x_start = peak_pos - window; double x_end = peak_pos + window; if (x_start < x.first()) x_start = x.first(); if (x_end > x.last()) x_end = x.last(); QVector x_peak, y_peak; for (int j = 0; j < x.size(); ++j) { if (x[j] >= x_start && x[j] <= x_end) { x_peak.append(x[j]); y_peak.append(y[j]); } } PeakFitResultNuclide result = FitPhotonPeakCalcFwhm(x_peak, y_peak); results.append(result); } return results; } // 绘制检测到的峰(矩形表示峰位和峰宽) void NuclideAnalysisView::DrawPeaks(const QVector& peaks) { if (!_plot || peaks.isEmpty()) { return; } // 先清除之前的标记 ClearPeakMarkers(); // 获取Y轴最大值,用于放置标签 double y_max = 0; if (!m_spectrumY.isEmpty()) { y_max = *std::max_element(m_spectrumY.begin(), m_spectrumY.end()); } // 为每个峰创建矩形标记 for (int i = 0; i < peaks.size(); ++i) { if (!peaks[i].success) { continue; } double center = peaks[i].center; double fwhm = peaks[i].fwhm; double amplitude = peaks[i].amplitude; double baseline = peaks[i].baseline; QColor color = Qt::red; // 半透明填充色 QColor fillColor = color; fillColor.setAlpha(5); // 透明度 0-255 // 1. 矩形(表示峰位和峰宽) // 矩形范围:x = [center - fwhm/2, center + fwhm/2] // y = [baseline, baseline + amplitude] double x_left = center - fwhm / 2.0; double x_right = center + fwhm / 2.0; double y_bottom = 0.0; double y_top = y_max; // 用 QwtPlotCurve 绘制闭合矩形 QPolygonF rectPoints; rectPoints << QPointF(x_left, y_bottom); rectPoints << QPointF(x_right, y_bottom); rectPoints << QPointF(x_right, y_top); rectPoints << QPointF(x_left, y_top); rectPoints << QPointF(x_left, y_bottom); // 闭合 QwtPlotCurve* rectCurve = new QwtPlotCurve(); rectCurve->setSamples(rectPoints); rectCurve->setPen(QPen(color, 1, Qt::SolidLine)); rectCurve->setBrush(QBrush(fillColor)); rectCurve->attach(_plot); m_peakMarkers.append(static_cast(rectCurve)); // 2. 峰位竖线(中心线,虚线) QwtPlotMarker* centerLine = new QwtPlotMarker(); centerLine->setLineStyle(QwtPlotMarker::VLine); centerLine->setLinePen(QPen(color, 1, Qt::DotLine)); centerLine->setValue(center, 0); centerLine->attach(_plot); m_peakMarkers.append(centerLine); // 3. 文本标签 QString labelText = QString("位置: %1 keV\nFWHM: %2 keV\n峰面积: %3") .arg(center, 0, 'f', 1) .arg(fwhm, 0, 'f', 2) .arg(peaks[i].area, 0, 'f', 0); QwtPlotMarker* textLabel = new QwtPlotMarker(); textLabel->setLabel(QwtText(labelText)); textLabel->setLabelAlignment(Qt::AlignLeft | Qt::AlignTop); textLabel->setLabelOrientation(Qt::Horizontal); double label_y = y_top + y_max * 0.02; if (label_y > y_max * 0.9) { label_y = y_top - y_max * 0.15; textLabel->setLabelAlignment(Qt::AlignLeft | Qt::AlignTop); } textLabel->setValue(x_left, label_y); textLabel->attach(_plot); m_peakMarkers.append(textLabel); } _plot->replot(); } // 清除所有峰标记 void NuclideAnalysisView::ClearPeakMarkers() { if (!_plot) { return; } // 分离并删除所有峰标记 for (int i = 0; i < m_peakMarkers.size(); ++i) { if (m_peakMarkers[i]) { m_peakMarkers[i]->detach(); delete m_peakMarkers[i]; } } m_peakMarkers.clear(); _plot->replot(); } bool NuclideAnalysisView::HasSpectrumData() const { return !m_spectrumX.isEmpty(); } std::vector NuclideAnalysisView::GetSvdPeaks(const QVector &x, const QVector &y, int step_w) { std::vector peaks; if (x.size() < 10 || y.size() < 10 || x.size() != y.size()) { return peaks; } int n = x.size(); arma::mat spec_data(n, 2); for (int i = 0; i < n; ++i) { spec_data(i, 0) = x[i]; spec_data(i, 1) = y[i]; } FindPeaksBySvd peakFinder; try { peaks = peakFinder.FindPeaks(spec_data, step_w); } catch (const std::string& e) { } return peaks; } void NuclideAnalysisView::setupNuclideDialog() { m_nuclideDialog = new QDialog(this); m_nuclideDialog->setWindowTitle(QStringLiteral(u"匹配核素射线")); m_nuclideDialog->resize(700, 400); QVBoxLayout* layout = new QVBoxLayout(m_nuclideDialog); layout->setContentsMargins(6, 6, 6, 6); QHBoxLayout* topBarLayout = new QHBoxLayout(); topBarLayout->addStretch(); m_addBtn = new QPushButton(QStringLiteral(u"新增核素"), m_nuclideDialog); m_addBtn->setFixedWidth(80); connect(m_addBtn, &QPushButton::clicked, this, &NuclideAnalysisView::onAddNuclide); topBarLayout->addWidget(m_addBtn); m_saveBtn = new QPushButton(QStringLiteral(u"保存"), m_nuclideDialog); m_saveBtn->setFixedWidth(80); connect(m_saveBtn, &QPushButton::clicked, this, &NuclideAnalysisView::onSaveSelectedNuclides); topBarLayout->addWidget(m_saveBtn); layout->addLayout(topBarLayout); m_nuclideTable = new QTableWidget(m_nuclideDialog); QStringList headers = { "核素", "射线类型", "能量(keV)", "操作" }; m_nuclideTable->setColumnCount(headers.size()); m_nuclideTable->setHorizontalHeaderLabels(headers); m_nuclideTable->setSelectionBehavior(QAbstractItemView::SelectRows); m_nuclideTable->setEditTriggers(QAbstractItemView::NoEditTriggers); m_nuclideTable->setAlternatingRowColors(true); m_nuclideTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); m_nuclideTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); m_nuclideTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); m_nuclideTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); m_nuclideTable->setColumnWidth(3, 120); layout->addWidget(m_nuclideTable); connect(m_nuclideTable, &QTableWidget::itemSelectionChanged, this, &NuclideAnalysisView::onNuclideTableSelectionChanged); } void NuclideAnalysisView::queryNuclidesForPeak(const PeakFitResultNuclide& peak) { for (QwtPlotMarker* marker : m_selectedNuclideMarkers) { if (marker) { marker->detach(); delete marker; } } m_selectedNuclideMarkers.clear(); if (!peak.success) return; if (!openNuclideDatabase()) { m_nuclideTable->setRowCount(1); m_nuclideTable->setItem(0, 0, new QTableWidgetItem(QStringLiteral(u"数据库未打开"))); m_nuclideDialog->show(); return; } double eMin = peak.center - peak.fwhm; double eMax = peak.center + peak.fwhm; double center = peak.center; auto& db = SqliteManager::instance(); QString sql = "SELECT ni.NUCLIDE_NAME, nri.RAY_TYPE, nri.RAY_MEV, " "nri.RAY_BRANCH_RATIO, nri.RAY_MEV_UNCERTAINTY " "FROM nuclideRayInfo nri " "INNER JOIN nuclideLib ni ON nri.NUCLIDE_ID = ni.ID;"; auto allRows = db.selectRows(sql); struct MatchItem { QString nuclideName; QString rayType; QString energyDisplay; double sortValue; }; QVector matches; for (const auto& row : allRows) { QString mevStr = row["RAY_MEV"].toString().trimmed(); if (mevStr.isEmpty()) continue; QStringList energyList = mevStr.split(','); for (const QString& eStr : energyList) { QString trimmed = eStr.trimmed(); if (trimmed.isEmpty()) continue; bool matched = false; QString displayStr; double sortVal = 0.0; if (trimmed.contains('-')) { // 范围形式:如 "11.386-12.032" QStringList rangeParts = trimmed.split('-'); if (rangeParts.size() == 2) { bool ok1 = false, ok2 = false; double low = rangeParts[0].trimmed().toDouble(&ok1); double high = rangeParts[1].trimmed().toDouble(&ok2); if (ok1 && ok2 && low <= high) { // 范围与搜索窗口有重叠就算匹配 if (high >= eMin && low <= eMax) { matched = true; displayStr = trimmed; // 取范围内最接近峰位的值用于排序 if (center < low) sortVal = low; else if (center > high) sortVal = high; else sortVal = center; } } } } else { // 单值形式 bool ok = false; double energy = trimmed.toDouble(&ok); if (ok && energy >= eMin && energy <= eMax) { matched = true; displayStr = trimmed; sortVal = energy; } } if (matched) { MatchItem item; item.nuclideName = row["NUCLIDE_NAME"].toString(); item.rayType = row["RAY_TYPE"].toString(); item.energyDisplay = displayStr; item.sortValue = sortVal; matches.append(item); } } } // 按与峰位的接近程度排序 std::sort(matches.begin(), matches.end(), [center](const MatchItem& a, const MatchItem& b) { return qAbs(a.sortValue - center) < qAbs(b.sortValue - center); }); // //单峰显示 // m_nuclideTable->setRowCount(matches.size()); // for (int i = 0; i < matches.size(); ++i) { // const auto& m = matches[i]; // m_nuclideTable->setItem(i, 0, new QTableWidgetItem(m.nuclideName)); // m_nuclideTable->setItem(i, 1, new QTableWidgetItem(m.rayType)); // m_nuclideTable->setItem(i, 2, new QTableWidgetItem(m.energyDisplay)); // createRowOperationWidgets(i); // } // if (matches.isEmpty()) { // m_nuclideTable->setRowCount(1); // m_nuclideTable->setItem(0, 0, new QTableWidgetItem(QStringLiteral(u"未找到匹配核素"))); // } //多峰显示 if (m_nuclideTable->rowCount() > 0) { QTableWidgetItem* firstItem = m_nuclideTable->item(0, 0); if (firstItem) { QString txt = firstItem->text(); if (txt == QStringLiteral(u"未找到匹配核素") || txt == QStringLiteral(u"数据库未打开")) { m_nuclideTable->removeRow(0); } } } // 新匹配结果追加到表格末尾 int startRow = m_nuclideTable->rowCount(); for (int i = 0; i < matches.size(); ++i) { int row = startRow + i; m_nuclideTable->insertRow(row); const auto& m = matches[i]; m_nuclideTable->setItem(row, 0, new QTableWidgetItem(m.nuclideName)); m_nuclideTable->setItem(row, 1, new QTableWidgetItem(m.rayType)); m_nuclideTable->setItem(row, 2, new QTableWidgetItem(m.energyDisplay)); createRowOperationWidgets(row); } // 表格为空且没匹配到时,才显示提示 if (m_nuclideTable->rowCount() == 0 && matches.isEmpty()) { m_nuclideTable->setRowCount(1); m_nuclideTable->setItem(0, 0, new QTableWidgetItem(QStringLiteral(u"未找到匹配核素"))); } m_nuclideDialog->show(); m_nuclideDialog->raise(); m_nuclideDialog->activateWindow(); } bool NuclideAnalysisView::openNuclideDatabase() { auto& db = SqliteManager::instance(); if (db.isOpen()) { return true; } QString dbPath = QCoreApplication::applicationDirPath() + "/nuclideLib.db"; if (dbPath.isEmpty()) { qWarning() << "核素数据库路径未设置"; return false; } return db.openDatabase(dbPath, "nuclide_conn"); } void NuclideAnalysisView::createRowOperationWidgets(int row) { QWidget* operationWidget = new QWidget(m_nuclideTable); QHBoxLayout* hLayout = new QHBoxLayout(operationWidget); hLayout->setContentsMargins(4, 2, 4, 2); hLayout->setSpacing(8); // 复选框 QCheckBox* checkBox = new QCheckBox(operationWidget); checkBox->setObjectName("rowCheckBox"); hLayout->addWidget(checkBox); hLayout->addStretch(); // 删除按钮 QPushButton* deleteBtn = new QPushButton(QStringLiteral(u"删除"), operationWidget); deleteBtn->setFixedWidth(50); deleteBtn->setProperty("rowIndex", row); connect(deleteBtn, &QPushButton::clicked, this, &NuclideAnalysisView::onDeleteRow); hLayout->addWidget(deleteBtn); m_nuclideTable->setCellWidget(row, 3, operationWidget); } void NuclideAnalysisView::onDeleteRow() { QPushButton* btn = qobject_cast(sender()); if (!btn) return; int row = btn->property("rowIndex").toInt(); if (row >= 0 && row < m_nuclideTable->rowCount()) { m_nuclideTable->removeRow(row); // 更新后续行的rowIndex属性 for (int i = row; i < m_nuclideTable->rowCount(); ++i) { QWidget* widget = m_nuclideTable->cellWidget(i, 3); if (widget) { QPushButton* delBtn = widget->findChild(); if (delBtn) { delBtn->setProperty("rowIndex", i); } } } } } void NuclideAnalysisView::onSaveSelectedNuclides() { QJsonArray nuclideArray; for (int i = 0; i < m_nuclideTable->rowCount(); ++i) { QWidget* widget = m_nuclideTable->cellWidget(i, 3); if (widget) { QCheckBox* checkBox = widget->findChild(); if (checkBox && checkBox->isChecked()) { QTableWidgetItem* nuclideItem = m_nuclideTable->item(i, 0); QTableWidgetItem* rayTypeItem = m_nuclideTable->item(i, 1); QTableWidgetItem* energyItem = m_nuclideTable->item(i, 2); if (nuclideItem) { QJsonObject obj; obj["nuclide"] = nuclideItem->text(); if (rayTypeItem) { obj["ray_type"] = rayTypeItem->text(); } if (energyItem) { obj["energy_keV"] = energyItem->text(); } nuclideArray.append(obj); } } } } if (nuclideArray.isEmpty()) { QMessageBox::information(m_nuclideDialog, QStringLiteral(u"提示"), QStringLiteral(u"请先勾选要保存的核素")); return; } QString savePath = _workspace + "/selected_nuclides.json"; QFile file(savePath); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { QJsonDocument doc(nuclideArray); file.write(doc.toJson(QJsonDocument::Indented)); file.close(); } else { QMessageBox::warning(m_nuclideDialog, QStringLiteral(u"保存失败"), QStringLiteral(u"无法写入文件:%1").arg(savePath)); } } void NuclideAnalysisView::onAddNuclide() { AddNuclideDialog dlg(m_nuclideDialog); if (dlg.exec() == QDialog::Accepted) { QList& selectRays = dlg.getSelectRay(); for (const QStringList& ray : selectRays) { if (ray.size() >= 3) { QString nuclideName = ray.at(1); QString energy = ray.at(2); int row = m_nuclideTable->rowCount(); m_nuclideTable->insertRow(row); m_nuclideTable->setItem(row, 0, new QTableWidgetItem(nuclideName)); m_nuclideTable->setItem(row, 1, new QTableWidgetItem(QStringLiteral(u"γ"))); m_nuclideTable->setItem(row, 2, new QTableWidgetItem(energy)); createRowOperationWidgets(row); } } } } void NuclideAnalysisView::onNuclideTableSelectionChanged() { // 清除旧竖线 for (QwtPlotMarker* marker : m_selectedNuclideMarkers) { if (marker) { marker->detach(); delete marker; } } m_selectedNuclideMarkers.clear(); // 获取选中行 QList ranges = m_nuclideTable->selectedRanges(); if (ranges.isEmpty()) { _plot->replot(); return; } // 遍历选中行,解析能量画竖线 for (const QTableWidgetSelectionRange& range : ranges) { for (int row = range.topRow(); row <= range.bottomRow(); ++row) { QTableWidgetItem* item = m_nuclideTable->item(row, 2); if (!item) continue; QString str = item->text().trimmed(); if (str.contains('-')) { // 范围能量,画两条线 QStringList parts = str.split('-'); if (parts.size() == 2) { bool ok1, ok2; double low = parts[0].trimmed().toDouble(&ok1); double high = parts[1].trimmed().toDouble(&ok2); if (ok1 && ok2) { addNuclideVLine(low); addNuclideVLine(high); } } } else { // 单能量,画一条线 bool ok; double e = str.toDouble(&ok); if (ok) addNuclideVLine(e); } } } _plot->replot(); } void NuclideAnalysisView::addNuclideVLine(double energy) { QwtPlotMarker* marker = new QwtPlotMarker(); marker->setLineStyle(QwtPlotMarker::VLine); marker->setLinePen(QPen(Qt::green, 2, Qt::SolidLine)); marker->setValue(energy, 0); marker->attach(_plot); m_selectedNuclideMarkers.append(marker); }