#include "nmWxSensitive.h" #include "nmDataAnalyzeManager.h" #include "nmDataSensitive.h" #include "nmDataReservoir.h" #include "nmDataWellBase.h" #include "nmDataPerforation.h" #include "iUnitHelper.h" #include "iUnitGroup.h" #include "iSysParaHelper.h" #include "iParameter.h" #include "mModuleDefines.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { QString formatSamplingValue(double value, int digits) { // 仅对显示值取有效数字,较大或较小的数自动使用科学计数法。 return value == 0.0 ? QString("0") : QString::number(value, 'g', digits); } void setSamplingEditValue(QLineEdit* edit, double value) { // 显示文本与原始数值分开保存,打开或保存窗口不会丢失采样精度。 const QString text = formatSamplingValue(value, 6); edit->setProperty("samplingValue", value); edit->setProperty("samplingText", text); edit->setText(text); } double samplingEditValue(const QLineEdit* edit, bool* ok) { // 未修改的格式化文本读取原始值;用户的新输入按实际文本解析。 const QVariant value = edit->property("samplingValue"); if (value.isValid() && edit->text() == edit->property("samplingText").toString()) return value.toDouble(ok); return edit->text().trimmed().toDouble(ok); } QString samplingBaseUnit(const QString& name) { // 与现有 PEBI 数据层的业务基准单位保持一致。 if (name == "Pi") return "MPa"; if (name == "k") return "mD"; if (name == "h") return "m"; if (name == "C") return "m^3/MPa"; return QString(); } bool convertSamplingValue(const QString& name, double value, QString from, QString to, double& converted) { // 优先复用项目单位组;旧配置的 psi 按绝对压力单位迁移。 if (from == "psi") from = "psia"; if (to == "psi") to = "psia"; if (from == "dimensionless") from.clear(); if (to == "dimensionless") to.clear(); if (from == to || (from.isEmpty() && to.isEmpty())) { converted = value; return qIsFinite(converted); } iUnitGroup* group = iUnitHelper::getUnitGroupByUnit(from); int digit = 0; if (group && group->indexOf(to) >= 0 && group->convert(from, value, to, converted, digit)) return qIsFinite(converted); if (name == "h") converted = nmDataAttribute::convertLengthUnit(value, from, to); else if (name == "Pi") converted = nmDataAttribute::convertPressureUnit(value, from, to); else if (name == "k") converted = nmDataAttribute::convertPermeabilityUnit(value, from, to); else return false; return qIsFinite(converted); } bool isSupportedSamplingVariable(const QString& name) { return name == "Skin" || name == "C" || name == "Pi" || name == "k" || name == "h" || name == QString(QChar(0x03c6)); } bool samplingPhysicalRange(const QString& name, double& minimum, double& maximum) { // 与自动拟合共用 NM 系统参数表,数值均为业务基准单位。 QString parameterName; if (name == "Skin") parameterName = "Result_W_Skin"; else if (name == "C") parameterName = "Result_W_C"; else if (name == "Pi") parameterName = "Result_Pi"; else if (name == "k") parameterName = "Result_K"; else if (name == "h") parameterName = "Result_h"; else if (name == QString(QChar(0x03c6))) parameterName = "Result_phi"; else return false; iSysParaHelper* helper = _paraHelper; iParameter* parameter = helper ? helper->getPara(parameterName, s_Nm_Serie) : nullptr; if (!parameter || !qIsFinite(parameter->m_dMin) || !qIsFinite(parameter->m_dMax) || parameter->m_dMin >= parameter->m_dMax) return false; minimum = name == "Skin" ? 0.0 : parameter->m_dMin; maximum = parameter->m_dMax; return minimum < maximum; } bool nearlyEqualSamplingValue(double left, double right) { // 沿用自动拟合的八个机器精度容差,只处理边界的浮点舍入差。 return qAbs(left - right) <= DBL_EPSILON * 8.0 * qMax(qAbs(left), qAbs(right)); } bool validSamplingValue(const QString& name, double& value) { // 所有采样值都受系统物理范围约束,边界末位误差归一到精确边界。 if (!qIsFinite(value)) return false; double minimum = 0.0, maximum = 0.0; if (!samplingPhysicalRange(name, minimum, maximum)) return false; if (value < minimum && !nearlyEqualSamplingValue(value, minimum)) return false; if (value > maximum && !nearlyEqualSamplingValue(value, maximum)) return false; value = qBound(minimum, value, maximum); return true; } } // ========= 右对齐委托:保证显示和编辑时都是右对齐 ========= class RightAlignDelegate : public QStyledItemDelegate { public: explicit RightAlignDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {} void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override { QStyledItemDelegate::initStyleOption(option, index); option->displayAlignment = Qt::AlignRight | Qt::AlignVCenter; } QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override { QWidget *editor = QStyledItemDelegate::createEditor(parent, option, index); if (QLineEdit *line = qobject_cast(editor)) { line->setAlignment(Qt::AlignRight | Qt::AlignVCenter); } return editor; } void setEditorData(QWidget *editor, const QModelIndex &index) const override { QLineEdit* line = qobject_cast(editor); // 编辑时沿用三位有效数字显示,避免重新暴露浮点尾数。 if (line) line->setText(index.data(Qt::UserRole).isValid() ? formatSamplingValue(index.data(Qt::UserRole).toDouble(), 3) : index.data(Qt::DisplayRole).toString()); } void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override { QLineEdit* line = qobject_cast(editor); if (!line) return; bool ok = false; double value = line->text().trimmed().toDouble(&ok); // 编辑后文本未改变时保留完整采样值,不把显示舍入当成用户修改。 const QVariant original = index.data(Qt::UserRole); if (original.isValid() && line->text() == formatSamplingValue(original.toDouble(), 3)) value = original.toDouble(&ok); // 非法输入保留原文,由 Generate 或预览统一定位并提示。 model->setData(index, ok && qIsFinite(value) ? QVariant(value) : QVariant(), Qt::UserRole); model->setData(index, ok && qIsFinite(value) ? formatSamplingValue(value, 3) : line->text(), Qt::DisplayRole); } }; nmWxSensitive::nmWxSensitive(QWidget *parent, nmDataAnalyzeManager* manager) : iDlgBase(parent), m_selectedValueIndex(0), m_loading(true), m_pDataManager(manager ? manager : nmDataAnalyzeManager::getCurrentInstance()), m_previewTable(nullptr), m_previewPageEdit(nullptr), m_previewPageLabel(nullptr), m_previewPage(0) { // 显式绑定所属分析窗口,后续切换窗口不会改变配置保存目标。 if (m_pDataManager) m_sensitiveData = m_pDataManager->getSensitiveDataCopy(); refreshModelValues(); setupUI(); createConnections(); populateTreeWidget(); createDetailPages(); loadDataToUI(); m_loading = false; onTreeItemChanged(nullptr, 0); } nmWxSensitive::~nmWxSensitive() { } void nmWxSensitive::refreshModelValues() { // 仅将本期六个参数绑定到主井和储层,旧采样范围按显示单位迁移一次。 nmDataWellBase* well = m_pDataManager ? m_pDataManager->findWellByCode(m_pDataManager->getPrimaryWellCode()) : nullptr; nmDataReservoir* reservoir = m_pDataManager ? m_pDataManager->getReservoirData() : nullptr; const QStringList names = QStringList() << "Skin" << "C" << "Pi" << "k" << "h" << QString(QChar(0x03c6)); for (int n = 0; n < names.size(); ++n) { nmDataAttribute* attribute = nullptr; const QString name = names[n]; if (well && name == "Skin" && well->getPerforationCount() > 0) attribute = &well->getPerforation(0)->getSkin(); else if (well && name == "C") attribute = &well->getWellboreStorage(); else if (reservoir && name == "Pi") attribute = &reservoir->getInitialPressure(); else if (reservoir && name == "k") attribute = &reservoir->getPermeability(); else if (reservoir && name == "h") attribute = &reservoir->getThickness(); else if (reservoir && name == QString(QChar(0x03c6))) attribute = &reservoir->getPorosity(); int index = -1; for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) if (m_sensitiveData.getVariable(i).getVarName() == name) { index = i; break; } const bool fresh = index < 0; if (fresh) { nmDataSensitive::VariableSampling variable; variable.setVarName(name); variable.setVarGroup(n < 2 ? "Tested Well" : "Reservoir"); variable.setNumber(5); m_sensitiveData.getVariables().append(variable); index = m_sensitiveData.getVariableCount() - 1; } nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(index); double physicalMin = 0.0, physicalMax = 0.0; if (!attribute || !samplingPhysicalRange(name, physicalMin, physicalMax)) { variable.setEnabled(false); variable.getModelValue().setValue(QVariant()); continue; } const QString baseUnit = samplingBaseUnit(name); QString displayUnit = attribute->getUnit(); if (displayUnit == "dimensionless") displayUnit.clear(); double converted = 0.0; if (!convertSamplingValue(name, attribute->getValue().toDouble(), baseUnit, displayUnit, converted)) displayUnit = baseUnit; double minimum = variable.getMinValue().getValue().toDouble(); double maximum = variable.getMaxValue().getValue().toDouble(); QVector values = variable.getValues(); double previousModel = variable.getModelValue().getValue().toDouble(); bool migrated = !fresh && (m_sensitiveData.usesBaseUnits() || variable.getMode() == nmDataSensitive::VariableSampling::MODE_MANUAL || !values.isEmpty()); if (migrated && !m_sensitiveData.usesBaseUnits()) { const QString oldUnit = variable.getModelValue().getUnit(); convertSamplingValue(name, previousModel, oldUnit, baseUnit, previousModel); migrated = convertSamplingValue(name, minimum, oldUnit, baseUnit, minimum) && convertSamplingValue(name, maximum, oldUnit, baseUnit, maximum); for (int i = 0; migrated && i < values.size(); ++i) migrated = convertSamplingValue(name, values[i], oldUnit, baseUnit, values[i]); } // 识别 Skin、Pi 的自动默认范围,按当前模型更新,保留用户自定义区间。 const bool previousDefault = variable.getMode() == nmDataSensitive::VariableSampling::MODE_AUTOMATIC && ((nearlyEqualSamplingValue(minimum, qMin(previousModel / 2.0, previousModel * 2.0)) && nearlyEqualSamplingValue(maximum, qMax(previousModel / 2.0, previousModel * 2.0))) || (name == "Skin" && previousModel != 0.0 && nearlyEqualSamplingValue(minimum, qMax(physicalMin, qMin(previousModel * 0.9, previousModel * 1.1))) && nearlyEqualSamplingValue(maximum, qMin(physicalMax, qMax(previousModel * 0.9, previousModel * 1.1)))) || (name == "Skin" && previousModel == 0.0 && (minimum == -1.0 || minimum == 0.0) && maximum == 1.0) || (name == "Pi" && variable.getModelValue().getMinValue().isValid() && variable.getModelValue().getMaxValue().isValid() && nearlyEqualSamplingValue(minimum, variable.getModelValue().getMinValue().toDouble()) && nearlyEqualSamplingValue(maximum, variable.getModelValue().getMaxValue().toDouble()))); if (!migrated || ((name == "Skin" || name == "Pi") && previousDefault)) { const double model = attribute->getValue().toDouble(); minimum = qMin(model / 2.0, model * 2.0); maximum = qMax(model / 2.0, model * 2.0); if (name == "Skin") { minimum = model == 0.0 ? -1.0 : qMin(model * 0.9, model * 1.1); maximum = model == 0.0 ? 1.0 : qMax(model * 0.9, model * 1.1); } if (name == "Pi") { minimum = physicalMin; maximum = physicalMax; } } if (!migrated) { values.clear(); variable.setMode(nmDataSensitive::VariableSampling::MODE_AUTOMATIC); variable.setLog(false); variable.setNumber(5); } // 初始范围和已保存范围都截断到硬限制,手动采样值留给提交校验。 minimum = qBound(physicalMin, minimum, physicalMax); maximum = qBound(physicalMin, maximum, physicalMax); variable.setModelValue(*attribute); variable.getModelValue().setUnit(displayUnit); variable.getModelValue().setValueRange(physicalMin, physicalMax); variable.setMinValue(variable.getModelValue()); variable.setMaxValue(variable.getModelValue()); variable.getMinValue().setValue(minimum); variable.getMaxValue().setValue(maximum); variable.setValues(values); // Saphir 在重新打开对话框时同步无效 Log 范围的实际状态。 if (variable.getMode() == nmDataSensitive::VariableSampling::MODE_AUTOMATIC && (minimum <= 0.0 || maximum <= 0.0)) variable.setLog(false); } for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) if (!isSupportedSamplingVariable(m_sensitiveData.getVariable(i).getVarName())) m_sensitiveData.getVariable(i).setEnabled(false); m_sensitiveData.setUsesBaseUnits(true); } void nmWxSensitive::loadDataToUI() { // 加载过程中阻止联动刷新,先恢复设置,再一次生成各参数表格。 calculationTypeCombo->setCurrentIndex(int(m_sensitiveData.getCalculationType())); for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) { nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(i); const QString name = variable.getVarName(); const int idx = findVariableIndex(name); if (idx < 0) continue; const bool available = isSupportedSamplingVariable(name) && variable.getModelValue().getValue().isValid(); for (int group = 0; group < variablesTree->topLevelItemCount(); ++group) { QTreeWidgetItem* parent = variablesTree->topLevelItem(group); for (int child = 0; child < parent->childCount(); ++child) { QTreeWidgetItem* item = parent->child(child); if (item->text(0) != name) continue; item->setDisabled(!available); item->setCheckState(0, available && variable.getEnabled() ? Qt::Checked : Qt::Unchecked); } } detailStack->widget(idx + 1)->setEnabled(available); m_modeCombos[idx]->setCurrentIndex(int(variable.getMode())); m_logChecks[idx]->setChecked(variable.getLog()); m_numSpins[idx]->setValue(qMax(1, variable.getNumber())); const QString unit = variable.getModelValue().getUnit(); m_unitLabels[idx]->setText(unit); double model = variable.getModelValue().getValue().toDouble(); double minimum = variable.getMinValue().getValue().toDouble(); double maximum = variable.getMaxValue().getValue().toDouble(); convertSamplingValue(name, model, samplingBaseUnit(name), unit, model); convertSamplingValue(name, minimum, samplingBaseUnit(name), unit, minimum); convertSamplingValue(name, maximum, samplingBaseUnit(name), unit, maximum); setSamplingEditValue(m_modelSpins[idx], model); setSamplingEditValue(m_minSpins[idx], minimum); setSamplingEditValue(m_maxSpins[idx], maximum); double displayMin = variable.getModelValue().getMinValue().toDouble(); double displayMax = variable.getModelValue().getMaxValue().toDouble(); convertSamplingValue(name, displayMin, samplingBaseUnit(name), unit, displayMin); convertSamplingValue(name, displayMax, samplingBaseUnit(name), unit, displayMax); const QString rangeHint = tr("Allowed range: [%1, %2] %3") .arg(formatSamplingValue(displayMin, 6)).arg(formatSamplingValue(displayMax, 6)).arg(unit); m_minSpins[idx]->setToolTip(rangeHint); m_maxSpins[idx]->setToolTip(rangeHint); if (variable.getMode() == nmDataSensitive::VariableSampling::MODE_MANUAL) { QTableWidget* table = m_valueTables[idx]; table->setRowCount(variable.getNumber()); const QVector& values = variable.getValues(); for (int row = 0; row < values.size() && row < table->rowCount(); ++row) { double display = values[row]; convertSamplingValue(name, display, samplingBaseUnit(name), unit, display); QTableWidgetItem* item = new QTableWidgetItem(formatSamplingValue(display, 3)); item->setData(Qt::UserRole, display); table->setItem(row, 1, item); } } updateValuesForPage(idx); } } int nmWxSensitive::findVariableIndex(const QString& varName) { // 根据创建顺序查找变量索引 QStringList allVars; allVars << "Zw" << "Hw" << "Lw" << "Skin" << "C" // Tested Well << "Pi" << "k" << "h" << QString(QChar(0x03c6)) << "ntg" << "kz/kr" // Reservoir << "Total compressibility"; // Pvt return allVars.indexOf(varName); } void nmWxSensitive::updateModelCount() { // 勾选、数量和方法修改后立即更新总次数,不等待 Generate。 if (m_loading) return; m_sensitiveData.setCalculationType( static_cast(calculationTypeCombo->currentIndex())); for (int group = 0; group < variablesTree->topLevelItemCount(); ++group) { QTreeWidgetItem* parent = variablesTree->topLevelItem(group); for (int child = 0; child < parent->childCount(); ++child) { QTreeWidgetItem* item = parent->child(child); for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) { nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(i); if (variable.getVarName() != item->text(0)) continue; variable.setEnabled(!item->isDisabled() && item->checkState(0) == Qt::Checked); const int idx = findVariableIndex(variable.getVarName()); if (idx >= 0) variable.setNumber(m_numSpins[idx]->value()); } } } bool overflow = false; const quint64 count = m_sensitiveData.calculateModelCount(&overflow); m_sensitiveData.setTotalModelCount(count); modelCountLabel->setText(overflow ? tr("Too many models") : QString::number(count)); previewButton->setEnabled(!overflow && count > 0); generateButton->setEnabled(m_pDataManager && !overflow && count > 0); } bool nmWxSensitive::saveDataFromUI() { // 校验选中参数后再提交副本,失败不改写数据中心已有配置。 updateModelCount(); if (m_sensitiveData.getTotalModelCount() == 0) return false; nmDataSensitive candidate(m_sensitiveData); for (int i = 0; i < candidate.getVariableCount(); ++i) { nmDataSensitive::VariableSampling& variable = candidate.getVariable(i); const QString name = variable.getVarName(); const int idx = findVariableIndex(name); if (idx < 0 || !isSupportedSamplingVariable(name)) continue; variable.setMode(static_cast(m_modeCombos[idx]->currentIndex())); variable.setLog(m_logChecks[idx]->isChecked()); const QString unit = m_unitLabels[idx]->text(); const QString baseUnit = samplingBaseUnit(name); bool valid = true; if (variable.getMode() == nmDataSensitive::VariableSampling::MODE_AUTOMATIC) { bool minOk = false, maxOk = false; double minimum = samplingEditValue(m_minSpins[idx], &minOk); double maximum = samplingEditValue(m_maxSpins[idx], &maxOk); valid = minOk && maxOk && convertSamplingValue(name, minimum, unit, baseUnit, minimum) && convertSamplingValue(name, maximum, unit, baseUnit, maximum) && minimum <= maximum && validSamplingValue(name, minimum) && validSamplingValue(name, maximum); if (valid) { variable.getMinValue().setValue(minimum); variable.getMaxValue().setValue(maximum); // Number=1 使用模型值,也必须在同一物理范围内。 QVector values = variable.samplingValues(); for (int row = 0; valid && row < values.size(); ++row) valid = validSamplingValue(name, values[row]); if (valid) variable.setValues(values); } } else { QVector values; QTableWidget* table = m_valueTables[idx]; for (int row = 0; row < table->rowCount(); ++row) { QTableWidgetItem* item = table->item(row, 1); bool ok = false; double value = 0.0; if (item) value = item->data(Qt::UserRole).isValid() ? item->data(Qt::UserRole).toDouble(&ok) : item->text().trimmed().toDouble(&ok); if (!ok || !convertSamplingValue(name, value, unit, baseUnit, value) || !validSamplingValue(name, value)) { valid = false; break; } values.append(value); } valid = valid && values.size() == variable.getNumber(); if (valid) variable.setValues(values); } if (!valid && variable.getEnabled()) { double lower = 0.0, upper = 0.0; const bool hasRange = samplingPhysicalRange(name, lower, upper) && convertSamplingValue(name, lower, baseUnit, unit, lower) && convertSamplingValue(name, upper, baseUnit, unit, upper); QMessageBox::warning(this, tr("Sensitivity"), hasRange ? tr("%1: invalid sampling values. Allowed range: [%2, %3] %4. Check the range and every manual row.") .arg(name).arg(formatSamplingValue(lower, 6)).arg(formatSamplingValue(upper, 6)).arg(unit) : tr("%1: the physical range is unavailable.").arg(name)); detailStack->setCurrentIndex(idx + 1); return false; } if (!variable.getEnabled()) continue; // 本期处理常量参数;属性场开启时常量会被插值覆盖,因此不能静默运行。 if (m_pDataManager && (name == "k" || name == "h" || name == QString(QChar(0x03c6)))) { const QString property = name == QString(QChar(0x03c6)) ? "phi" : name; const QVector dataSets = m_pDataManager->getPropertyInterpolationDataSets(); for (int field = 0; field < dataSets.size(); ++field) { if (dataSets[field].useForCalculation && dataSets[field].property == property && !dataSets[field].points.isEmpty()) { QMessageBox::warning(this, tr("Sensitivity"), tr("%1 uses a non-uniform field. Disable that field before sampling a constant value.").arg(name)); return false; } } } } m_sensitiveData = candidate; return true; } void nmWxSensitive::setupUI() { setWindowTitle(tr("Sensitivity")); setMinimumSize(900, 550); QVBoxLayout *mainLayout = new QVBoxLayout(this); // ===== 顶部:Calculation type ===== QHBoxLayout *calcTypeLayout = new QHBoxLayout(); QLabel *calcTypeLabel = new QLabel(tr("Calculation type:"), this); calculationTypeCombo = new QComboBox(this); calculationTypeCombo->addItem(tr("Deterministic")); calculationTypeCombo->addItem(tr("Deterministic multivariate")); //calculationTypeCombo->addItem(tr("Monte-Carlo")); //calculationTypeCombo->addItem(tr("Monte-Carlo + Improve")); calcTypeLayout->addWidget(calcTypeLabel); calcTypeLayout->addWidget(calculationTypeCombo); calcTypeLayout->addStretch(); mainLayout->addLayout(calcTypeLayout); // ===== 中间:左 Variables 面板 + 右 detailStack ===== QSplitter *centerSplitter = new QSplitter(Qt::Horizontal, this); centerSplitter->setChildrenCollapsible(false); // 不允许把某一侧完全折叠 centerSplitter->setHandleWidth(4); // 中间拖动条宽度 centerSplitter->setStyleSheet( "QSplitter::handle:horizontal {" " background-color: #C0C0C0;" "}" "QSplitter::handle:horizontal:hover {" " background-color: #A0A0A0;" "}" ); // --- 左侧:Variables 面板(QFrame + 标题栏 + Tree)--- QFrame *variablesPanel = new QFrame(this); variablesPanel->setFrameShape(QFrame::StyledPanel); variablesPanel->setFrameShadow(QFrame::Raised); variablesPanel->setMinimumWidth(230); QVBoxLayout *variablesPanelLayout = new QVBoxLayout(variablesPanel); variablesPanelLayout->setContentsMargins(0, 0, 0, 0); variablesPanelLayout->setSpacing(0); // 标题栏 QWidget *varHeader = new QWidget(variablesPanel); varHeader->setFixedHeight(24); varHeader->setAutoFillBackground(true); { QPalette pal = varHeader->palette(); pal.setColor(QPalette::Window, palette().color(QPalette::Midlight)); varHeader->setPalette(pal); } QHBoxLayout *varHeaderLayout = new QHBoxLayout(varHeader); varHeaderLayout->setContentsMargins(6, 0, 6, 0); varHeaderLayout->setSpacing(0); QLabel *varTitle = new QLabel(tr("Variables"), varHeader); varTitle->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); QFont f = varTitle->font(); f.setBold(true); varTitle->setFont(f); varHeaderLayout->addWidget(varTitle); varHeaderLayout->addStretch(); // Tree 区域 variablesTree = new QTreeWidget(variablesPanel); variablesTree->setHeaderHidden(true); variablesTree->setFrameShape(QFrame::NoFrame); // 外层已经有 frame variablesTree->setMinimumHeight(350); variablesPanelLayout->addWidget(varHeader); variablesPanelLayout->addWidget(variablesTree, 1); // 把左侧 panel 加进 splitter centerSplitter->addWidget(variablesPanel); // --- 右侧:detailStack(后面每个 page 自己做标题栏)--- detailStack = new QStackedWidget(this); detailStack->setContentsMargins(0, 0, 0, 0); // index 0:默认提示页 QWidget *blankPage = new QWidget(this); QVBoxLayout *blankLayout = new QVBoxLayout(blankPage); blankLayout->setContentsMargins(0, 0, 0, 0); QLabel *blankLabel = new QLabel(tr("Select a variable on the left."), blankPage); blankLayout->addStretch(); blankLayout->addWidget(blankLabel, 0, Qt::AlignHCenter); blankLayout->addStretch(); detailStack->addWidget(blankPage); // 把右侧 stack 加进 splitter centerSplitter->addWidget(detailStack); // 左边宽度基本固定,右边自适应 centerSplitter->setStretchFactor(0, 0); centerSplitter->setStretchFactor(1, 1); mainLayout->addWidget(centerSplitter, 1); // ===== 底部:Total number of models + 按钮 ===== QHBoxLayout *bottomLayout = new QHBoxLayout(); QLabel *totalLabel = new QLabel(tr("Total number of models:"), this); modelCountLabel = new QLabel(tr("0"), this); bottomLayout->addWidget(totalLabel); bottomLayout->addWidget(modelCountLabel); previewButton = new QPushButton(this); previewButton->setIcon(QIcon(QCoreApplication::applicationDirPath().section('/', 0, -2) + "/Res/Icon/Eyes.png")); previewButton->setFixedSize(28, 24); previewButton->setToolTip(tr("Sensitivity runs")); bottomLayout->addWidget(previewButton); bottomLayout->addStretch(); generateButton = new QPushButton(tr("Generate"), this); cancelButton = new QPushButton(tr("Cancel"), this); bottomLayout->addWidget(generateButton); bottomLayout->addWidget(cancelButton); mainLayout->addLayout(bottomLayout); } void nmWxSensitive::createConnections() { connect(generateButton, SIGNAL(clicked()), this, SLOT(onGenerateClicked())); connect(cancelButton, SIGNAL(clicked()), this, SLOT(onCancelClicked())); connect(previewButton, SIGNAL(clicked()), this, SLOT(onPreviewClicked())); connect(calculationTypeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onCalculationTypeChanged(int))); connect(variablesTree, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(onTreeItemClicked(QTreeWidgetItem*,int))); connect(variablesTree, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(onTreeItemChanged(QTreeWidgetItem*,int))); } void nmWxSensitive::populateTreeWidget() { variablesTree->clear(); // ===== Tested Well ===== QTreeWidgetItem *testedWellItem = new QTreeWidgetItem(variablesTree); testedWellItem->setText(0, tr("Tested Well")); testedWellItem->setCheckState(0, Qt::Unchecked); testedWellItem->setFlags(testedWellItem->flags() | Qt::ItemIsUserCheckable); QStringList testedWellChildren; testedWellChildren << "Zw" << "Hw" << "Lw" << "Skin" << "C"; int i; for (i = 0; i < testedWellChildren.size(); ++i) { QTreeWidgetItem *childItem = new QTreeWidgetItem(testedWellItem); childItem->setText(0, testedWellChildren.at(i)); childItem->setCheckState(0, Qt::Unchecked); childItem->setFlags(childItem->flags() | Qt::ItemIsUserCheckable); childItem->setDisabled(!isSupportedSamplingVariable(testedWellChildren.at(i))); } // ===== Reservoir ===== QTreeWidgetItem *reservoirItem = new QTreeWidgetItem(variablesTree); reservoirItem->setText(0, tr("Reservoir")); reservoirItem->setCheckState(0, Qt::Unchecked); reservoirItem->setFlags(reservoirItem->flags() | Qt::ItemIsUserCheckable); QStringList reservoirChildren; reservoirChildren << "Pi" << "k" << "h" << QString(QChar(0x03c6)) << "ntg" << "kz/kr"; for (i = 0; i < reservoirChildren.size(); ++i) { QTreeWidgetItem *childItem = new QTreeWidgetItem(reservoirItem); childItem->setText(0, reservoirChildren.at(i)); childItem->setCheckState(0, Qt::Unchecked); childItem->setFlags(childItem->flags() | Qt::ItemIsUserCheckable); childItem->setDisabled(!isSupportedSamplingVariable(reservoirChildren.at(i))); } // ===== Pvt ===== QTreeWidgetItem *pvtItem = new QTreeWidgetItem(variablesTree); pvtItem->setText(0, tr("Pvt")); pvtItem->setCheckState(0, Qt::Unchecked); pvtItem->setFlags(pvtItem->flags() | Qt::ItemIsUserCheckable); QTreeWidgetItem *compressibilityItem = new QTreeWidgetItem(pvtItem); compressibilityItem->setText(0, tr("Total compressibility")); compressibilityItem->setCheckState(0, Qt::Unchecked); compressibilityItem->setFlags(compressibilityItem->flags() | Qt::ItemIsUserCheckable); compressibilityItem->setDisabled(true); variablesTree->expandAll(); } void nmWxSensitive::createDetailPages() { int i, j; // 依次为每个"叶子变量"创建一个页面,并把 index 存到 item->data 里 for (i = 0; i < variablesTree->topLevelItemCount(); ++i) { QTreeWidgetItem *topItem = variablesTree->topLevelItem(i); for (j = 0; j < topItem->childCount(); ++j) { QTreeWidgetItem *childItem = topItem->child(j); if (childItem == nullptr) { continue; } QString varName = childItem->text(0); QWidget *page = createVariablePage(varName); int pageIndex = detailStack->addWidget(page); // index 从 1 开始 childItem->setData(0, Qt::UserRole, pageIndex); } } } QIcon nmWxSensitive::createTriangleIcon() { QPixmap trianglePixmap(16, 16); trianglePixmap.fill(Qt::transparent); QPainter painter(&trianglePixmap); painter.setRenderHint(QPainter::Antialiasing, true); // 绘制小三角形(指向右的箭头) QPolygon triangle; triangle << QPoint(4, 6) << QPoint(4, 10) << QPoint(10, 8); painter.setBrush(QBrush(QColor(0, 0, 0))); // 黑色三角形 painter.setPen(QPen(QColor(0, 0, 0), 1)); painter.drawPolygon(triangle); return QIcon(trianglePixmap); } QWidget *nmWxSensitive::createVariablePage(const QString &varName) { QWidget *page = new QWidget(this); QHBoxLayout *pageLayout = new QHBoxLayout(page); pageLayout->setContentsMargins(0, 0, 0, 0); pageLayout->setSpacing(0); // ===== 整个 Sampling 面板:QFrame + 标题栏 + 内容 ===== QFrame *samplingPanel = new QFrame(page); samplingPanel->setFrameShape(QFrame::StyledPanel); samplingPanel->setFrameShadow(QFrame::Raised); QVBoxLayout *panelLayout = new QVBoxLayout(samplingPanel); panelLayout->setContentsMargins(0, 0, 0, 0); panelLayout->setSpacing(0); // ---- 标题栏:Sampling Xxx ---- QWidget *header = new QWidget(samplingPanel); header->setFixedHeight(24); header->setAutoFillBackground(true); { QPalette pal = header->palette(); pal.setColor(QPalette::Window, palette().color(QPalette::Midlight)); header->setPalette(pal); } QHBoxLayout *headerLayout = new QHBoxLayout(header); headerLayout->setContentsMargins(6, 0, 6, 0); headerLayout->setSpacing(0); QLabel *title = new QLabel(tr("Sampling %1").arg(varName), header); QFont tf = title->font(); tf.setBold(true); title->setFont(tf); headerLayout->addWidget(title); headerLayout->addStretch(); panelLayout->addWidget(header); // ---- 内容区:左参数 + 右表格 ---- QWidget *content = new QWidget(samplingPanel); QHBoxLayout *contentLayout = new QHBoxLayout(content); contentLayout->setContentsMargins(6, 0, 6, 6); contentLayout->setSpacing(12); // 左:参数区(GridLayout) QGridLayout *paramLayout = new QGridLayout(); paramLayout->setVerticalSpacing(6); int row = 0; // Mode + Log? QLabel *modeLabel = new QLabel(tr("Mode"), content); QComboBox *modeCombo = new QComboBox(content); modeCombo->addItem(tr("Automatic")); // 0 modeCombo->addItem(tr("Manual")); // 1 QCheckBox *logCheck = new QCheckBox(tr("Log?"), content); QWidget *modeRowWidget = new QWidget(content); QHBoxLayout *modeRowLayout = new QHBoxLayout(modeRowWidget); modeRowLayout->setContentsMargins(0, 0, 0, 0); modeRowLayout->addWidget(modeCombo); modeRowLayout->addWidget(logCheck); modeRowLayout->addStretch(); paramLayout->addWidget(modeLabel, row, 0); paramLayout->addWidget(modeRowWidget, row, 1); ++row; // Number QLabel *numLabel = new QLabel(tr("Number"), content); QSpinBox *numSpin = new QSpinBox(content); numSpin->setRange(1, 1000); //numSpin->setValue(1); numSpin->setButtonSymbols(QAbstractSpinBox::NoButtons); paramLayout->addWidget(numLabel, row, 0); paramLayout->addWidget(numSpin, row, 1); ++row; // 空行(Number 和 Model value 之间) int gapRow = row; paramLayout->setRowMinimumHeight(gapRow, 12); ++row; // Model value + 单位 QLabel *modelLabel = new QLabel(tr("Model value:"), content); QLineEdit *modelSpin = new QLineEdit(content); modelSpin->setObjectName("sensitivityModel_" + varName); // 基准值来自真实模型,只作参照;输入框支持科学计数法和完整精度。 modelSpin->setReadOnly(true); QLabel *unitLabel = new QLabel(tr(""), content); // ⭐ 单位也从数据加载 QWidget *modelRowWidget = new QWidget(content); QHBoxLayout *modelRowLayout = new QHBoxLayout(modelRowWidget); modelRowLayout->setContentsMargins(0, 0, 0, 0); modelRowLayout->addWidget(modelSpin); modelRowLayout->addWidget(unitLabel); modelRowLayout->addStretch(); paramLayout->addWidget(modelLabel, row, 0); paramLayout->addWidget(modelRowWidget, row, 1); ++row; // Minimum QLabel *minLabel = new QLabel(tr("Minimum:"), content); QLineEdit *minSpin = new QLineEdit(content); minSpin->setObjectName("sensitivityMinimum_" + varName); paramLayout->addWidget(minLabel, row, 0); paramLayout->addWidget(minSpin, row, 1); ++row; // Maximum QLabel *maxLabel = new QLabel(tr("Maximum:"), content); QLineEdit *maxSpin = new QLineEdit(content); maxSpin->setObjectName("sensitivityMaximum_" + varName); paramLayout->addWidget(maxLabel, row, 0); paramLayout->addWidget(maxSpin, row, 1); ++row; // 统一编辑框宽度 int fullWidth = modelSpin->sizeHint().width(); int editWidth = qMax(150, fullWidth); modeCombo->setFixedWidth(editWidth); numSpin->setFixedWidth(editWidth); modelSpin->setFixedWidth(editWidth); minSpin->setFixedWidth(editWidth); maxSpin->setFixedWidth(editWidth); modeCombo->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); numSpin->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); modelSpin->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); minSpin->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); maxSpin->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QWidget *paramWidget = new QWidget(content); QVBoxLayout *leftVBox = new QVBoxLayout(paramWidget); leftVBox->setContentsMargins(0, 6, 0, 0); leftVBox->addLayout(paramLayout); leftVBox->addStretch(); // 右:Values 表格(双列:箭头 + 值) QTableWidget *valuesTable = new QTableWidget(content); valuesTable->setObjectName("sensitivityValues_" + varName); valuesTable->setColumnCount(2); QStringList headers; headers << "" << tr("Values"); valuesTable->setHorizontalHeaderLabels(headers); valuesTable->verticalHeader()->setVisible(false); valuesTable->setColumnWidth(0, 15); valuesTable->setColumnWidth(1, 130); QHeaderView *hh = valuesTable->horizontalHeader(); hh->setResizeMode(QHeaderView::Fixed); hh->setHighlightSections(false); valuesTable->setStyleSheet( "QHeaderView::section {" " border-top: 0px;" " border-left: 0px;" " border-right: 1px solid lightgray;" " border-bottom: 1px solid lightgray;" "}" "QTableCornerButton::section {" " border-top: 0px;" " border-left: 0px;" " border-right: 1px solid lightgray;" " border-bottom: 1px solid lightgray;" "}" ); valuesTable->horizontalHeader()->setStretchLastSection(false); valuesTable->setEditTriggers(QAbstractItemView::DoubleClicked); valuesTable->setSelectionBehavior(QAbstractItemView::SelectRows); valuesTable->setSelectionMode(QAbstractItemView::NoSelection); valuesTable->setFocusPolicy(Qt::NoFocus); valuesTable->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); valuesTable->setShowGrid(true); valuesTable->setItemDelegateForColumn(1, new RightAlignDelegate(valuesTable)); int tableWidth = valuesTable->frameWidth() * 2 + valuesTable->verticalHeader()->sizeHint().width() + valuesTable->columnWidth(0) + valuesTable->columnWidth(1); valuesTable->setFixedWidth(tableWidth); QWidget *tableContainer = new QWidget(content); QVBoxLayout *tableLayout = new QVBoxLayout(tableContainer); tableLayout->setContentsMargins(0, 0, 0, 0); tableLayout->setSpacing(0); tableLayout->addWidget(valuesTable); tableContainer->setFixedWidth(tableWidth); contentLayout->addWidget(paramWidget, 1); contentLayout->addStretch(); contentLayout->addWidget(tableContainer, 0, Qt::AlignRight); panelLayout->addWidget(content); pageLayout->addWidget(samplingPanel); page->setEnabled(isSupportedSamplingVariable(varName)); // ===== 保存控件指针 ===== m_modeCombos.append(modeCombo); m_logChecks.append(logCheck); m_numSpins.append(numSpin); m_modelSpins.append(modelSpin); m_minSpins.append(minSpin); m_maxSpins.append(maxSpin); m_valueTables.append(valuesTable); m_unitLabels.append(unitLabel); connect(numSpin, SIGNAL(valueChanged(int)), this, SLOT(onVariableParamChanged())); connect(minSpin, SIGNAL(textChanged(QString)), this, SLOT(onVariableParamChanged())); connect(maxSpin, SIGNAL(textChanged(QString)), this, SLOT(onVariableParamChanged())); connect(minSpin, SIGNAL(editingFinished()), this, SLOT(onSamplingEditingFinished())); connect(maxSpin, SIGNAL(editingFinished()), this, SLOT(onSamplingEditingFinished())); connect(modeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onVariableParamChanged())); connect(logCheck, SIGNAL(toggled(bool)), this, SLOT(onVariableParamChanged())); connect(valuesTable, SIGNAL(cellClicked(int,int)), this, SLOT(onTableCellClicked(int,int))); return page; } void nmWxSensitive::updateValuesForPage(int idx) { if (idx < 0 || idx >= m_numSpins.size()) return; // 自动模式由范围生成只读表格;手动模式保留每行值,不因选中行而重算。 QTableWidget* table = m_valueTables[idx]; const bool automatic = m_modeCombos[idx]->currentIndex() == 0; m_minSpins[idx]->setEnabled(automatic); m_maxSpins[idx]->setEnabled(automatic); m_logChecks[idx]->setEnabled(automatic); const int number = m_numSpins[idx]->value(); table->setRowCount(number); nmDataSensitive::VariableSampling variable; for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) { if (findVariableIndex(m_sensitiveData.getVariable(i).getVarName()) == idx) { variable = m_sensitiveData.getVariable(i); break; } } QVector samples; const QString name = variable.getVarName(); const QString unit = m_unitLabels[idx]->text(); if (automatic) { bool minOk = false, maxOk = false; double minimum = samplingEditValue(m_minSpins[idx], &minOk); double maximum = samplingEditValue(m_maxSpins[idx], &maxOk); if (minOk && maxOk && minimum <= maximum && convertSamplingValue(name, minimum, unit, samplingBaseUnit(name), minimum) && convertSamplingValue(name, maximum, unit, samplingBaseUnit(name), maximum) && validSamplingValue(name, minimum) && validSamplingValue(name, maximum)) { variable.setMode(nmDataSensitive::VariableSampling::MODE_AUTOMATIC); variable.setNumber(number); variable.setLog(m_logChecks[idx]->isChecked()); variable.getMinValue().setValue(minimum); variable.getMaxValue().setValue(maximum); samples = variable.samplingValues(); } } for (int row = 0; row < number; ++row) { QTableWidgetItem* arrow = table->item(row, 0); if (!arrow) { arrow = new QTableWidgetItem(); table->setItem(row, 0, arrow); } arrow->setFlags(Qt::ItemIsEnabled); arrow->setIcon(row == m_selectedValueIndex ? createTriangleIcon() : QIcon()); QTableWidgetItem* item = table->item(row, 1); if (!item) { item = new QTableWidgetItem(); table->setItem(row, 1, item); } item->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); item->setFlags(automatic ? Qt::ItemIsEnabled : Qt::ItemIsEnabled | Qt::ItemIsEditable); if (automatic) { double display = row < samples.size() ? samples[row] : 0.0; const bool valid = row < samples.size() && convertSamplingValue(name, display, samplingBaseUnit(name), unit, display); item->setData(Qt::UserRole, valid ? QVariant(display) : QVariant()); item->setText(valid ? formatSamplingValue(display, 3) : QString()); } } } void nmWxSensitive::onSamplingEditingFinished() { // 输入结束后截断越界范围,再按六位有效数字显示,计算仍使用原始精度。 QLineEdit* edit = qobject_cast(sender()); if (m_loading || !edit) return; bool ok = false; double value = samplingEditValue(edit, &ok); if (!ok || !qIsFinite(value)) return; for (int idx = 0; idx < m_minSpins.size(); ++idx) { if (edit != m_minSpins[idx] && edit != m_maxSpins[idx]) continue; for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) { const QString name = m_sensitiveData.getVariable(i).getVarName(); if (findVariableIndex(name) != idx) continue; double lower = 0.0, upper = 0.0; const QString unit = m_unitLabels[idx]->text(); const QString baseUnit = samplingBaseUnit(name); if (!samplingPhysicalRange(name, lower, upper) || !convertSamplingValue(name, value, unit, baseUnit, value)) return; value = qBound(lower, value, upper); if (!convertSamplingValue(name, value, baseUnit, unit, value)) return; setSamplingEditValue(edit, value); updateValuesForPage(idx); return; } } } void nmWxSensitive::onVariableParamChanged() { // 数据加载时不响应控件逐项赋值,编辑时只刷新所属参数页。 if (m_loading) return; for (int i = 0; i < m_numSpins.size(); ++i) { if (sender() == m_numSpins[i] || sender() == m_minSpins[i] || sender() == m_maxSpins[i] || sender() == m_modeCombos[i] || sender() == m_logChecks[i]) { m_selectedValueIndex = qMin(m_selectedValueIndex, m_numSpins[i]->value() - 1); updateValuesForPage(i); break; } } updateModelCount(); } void nmWxSensitive::onTableCellClicked(int row, int column) { Q_UNUSED(column); // 选中标记不改写表格内容,避免自动重算或丢失手动输入。 m_selectedValueIndex = row; QTableWidget* table = qobject_cast(sender()); if (!table) return; for (int i = 0; i < table->rowCount(); ++i) if (table->item(i, 0)) table->item(i, 0)->setIcon(i == row ? createTriangleIcon() : QIcon()); } void nmWxSensitive::onTreeItemChanged(QTreeWidgetItem* item, int column) { Q_UNUSED(column); if (m_loading) return; // 分组选择与子项选择双向同步,禁用的参数不参与全选和总次数。 m_loading = true; variablesTree->blockSignals(true); if (item && item->childCount() > 0) { const Qt::CheckState state = item->checkState(0); for (int i = 0; i < item->childCount(); ++i) if (!item->child(i)->isDisabled()) item->child(i)->setCheckState(0, state); } for (int group = 0; group < variablesTree->topLevelItemCount(); ++group) { QTreeWidgetItem* parent = variablesTree->topLevelItem(group); int available = 0, checked = 0; for (int i = 0; i < parent->childCount(); ++i) { if (parent->child(i)->isDisabled()) continue; ++available; if (parent->child(i)->checkState(0) == Qt::Checked) ++checked; } parent->setCheckState(0, checked == 0 ? Qt::Unchecked : checked == available ? Qt::Checked : Qt::PartiallyChecked); parent->setDisabled(available == 0); } variablesTree->blockSignals(false); m_loading = false; updateModelCount(); } void nmWxSensitive::onGenerateClicked() { // 本阶段只保存经过校验的采样配置,实际批量求解将在下一阶段接入。 if (!m_pDataManager || !saveDataFromUI()) return; m_pDataManager->updateSensitiveData(m_sensitiveData); accept(); } void nmWxSensitive::onCancelClicked() { reject(); } void nmWxSensitive::onCalculationTypeChanged(int index) { Q_UNUSED(index); // 两种确定性方法共用参数设置,仅切换总次数及组合规则。 updateModelCount(); } void nmWxSensitive::onTreeItemClicked(QTreeWidgetItem* item, int column) { Q_UNUSED(column); if (item->childCount() > 0) { detailStack->setCurrentIndex(0); return; } const int pageIndex = item->data(0, Qt::UserRole).toInt(); if (pageIndex <= 0) return; detailStack->setCurrentIndex(pageIndex); // 页面切换只更新箭头,保留用户正在编辑的采样表。 m_selectedValueIndex = 0; QTableWidget* table = m_valueTables[pageIndex - 1]; for (int row = 0; row < table->rowCount(); ++row) if (table->item(row, 0)) table->item(row, 0)->setIcon(row == 0 ? createTriangleIcon() : QIcon()); } void nmWxSensitive::onPreviewClicked() { if (!saveDataFromUI()) return; // 按页解码编号,组合数再大也只创建当前页的五十行。 QDialog dialog(this); dialog.setWindowTitle(tr("Sensitivity runs")); dialog.resize(850, 500); QVBoxLayout* layout = new QVBoxLayout(&dialog); m_previewTable = new QTableWidget(&dialog); m_previewTable->setEditTriggers(QAbstractItemView::NoEditTriggers); layout->addWidget(m_previewTable); QHBoxLayout* navigation = new QHBoxLayout(); QPushButton* previous = new QPushButton(&dialog); previous->setIcon(style()->standardIcon(QStyle::SP_ArrowLeft)); previous->setToolTip(tr("Previous page")); QPushButton* next = new QPushButton(&dialog); next->setIcon(style()->standardIcon(QStyle::SP_ArrowRight)); next->setToolTip(tr("Next page")); m_previewPageEdit = new QLineEdit(&dialog); m_previewPageEdit->setFixedWidth(180); m_previewPageLabel = new QLabel(&dialog); navigation->addWidget(previous); navigation->addWidget(m_previewPageEdit); navigation->addWidget(m_previewPageLabel); navigation->addWidget(next); navigation->addStretch(); layout->addLayout(navigation); QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Close, Qt::Horizontal, &dialog); layout->addWidget(buttons); connect(buttons, SIGNAL(rejected()), &dialog, SLOT(reject())); connect(previous, SIGNAL(clicked()), this, SLOT(onPreviewPrevious())); connect(next, SIGNAL(clicked()), this, SLOT(onPreviewNext())); connect(m_previewPageEdit, SIGNAL(returnPressed()), this, SLOT(onPreviewJump())); m_previewPage = 0; refreshPreviewPage(); dialog.exec(); m_previewTable = nullptr; m_previewPageEdit = nullptr; m_previewPageLabel = nullptr; } void nmWxSensitive::refreshPreviewPage() { // 参数列与计算数据共用 getRunValues,单变量运行的其他参数显示基准值。 if (!m_previewTable) return; QStringList headers; headers << tr("Run"); QList selected; for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) { nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(i); if (!variable.getEnabled()) continue; selected.append(i); const QString unit = variable.getModelValue().getUnit(); headers << variable.getVarName() + (unit.isEmpty() ? QString() : " (" + unit + ")"); } m_previewTable->clear(); m_previewTable->setColumnCount(headers.size()); m_previewTable->setHorizontalHeaderLabels(headers); const quint64 total = m_sensitiveData.getTotalModelCount(); const quint64 first = m_previewPage * 50; const int rows = int(qMin(quint64(50), total - first)); m_previewTable->setRowCount(rows); for (int row = 0; row < rows; ++row) { QVector values; if (!m_sensitiveData.getRunValues(first + row, values)) break; m_previewTable->setItem(row, 0, new QTableWidgetItem(QString::number(first + row + 1))); for (int column = 0; column < selected.size(); ++column) { const int index = selected[column]; nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(index); double display = values[index]; convertSamplingValue(variable.getVarName(), display, samplingBaseUnit(variable.getVarName()), variable.getModelValue().getUnit(), display); QTableWidgetItem* item = new QTableWidgetItem(formatSamplingValue(display, 3)); item->setToolTip(formatSamplingValue(display, 6)); item->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); m_previewTable->setItem(row, column + 1, item); } } m_previewTable->resizeColumnsToContents(); m_previewPageEdit->setText(QString::number(m_previewPage + 1)); m_previewPageLabel->setText(tr("/ %1 pages; %2 runs").arg((total - 1) / 50 + 1).arg(total)); } void nmWxSensitive::onPreviewPrevious() { if (m_previewPage > 0) --m_previewPage; refreshPreviewPage(); } void nmWxSensitive::onPreviewNext() { if ((m_previewPage + 1) * 50 < m_sensitiveData.getTotalModelCount()) ++m_previewPage; refreshPreviewPage(); } void nmWxSensitive::onPreviewJump() { // 允许直接跳转到大批量组合中的指定页,页码使用六十四位整数。 bool ok = false; const quint64 page = m_previewPageEdit->text().trimmed().toULongLong(&ok); const quint64 pages = (m_sensitiveData.getTotalModelCount() - 1) / 50 + 1; if (ok && page > 0 && page <= pages) m_previewPage = page - 1; refreshPreviewPage(); }