#include "nmWxReservoirPropertiesEditor.h" #include "nmReservoirPropertiesDataSource.h" #include "nmReservoirPropertiesSession.h" #include "nmReservoirParameterPage.h" #include "iUnitGroup.h" #include "iUnitWxBinder.h" #include "mModuleDefines.h" #include "ZxBaseUtil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include nmWxReservoirPropertiesEditor::nmWxReservoirPropertiesEditor( nmIReservoirPropertiesDataSource* pDataSource, QWidget* pParent) : iDlgBase(pParent), m_pDataSource(pDataSource), m_pSession(new nmReservoirPropertiesSession(pDataSource)), m_pCategoryButtonGroup(nullptr), m_pStackedWidget(nullptr), m_pButtonBox(nullptr), m_pFooterHelpButton(nullptr), m_pStatusLabel(nullptr), m_nCurrentCategory(NM_RESERVOIR_PAGE_RESERVOIR), m_bInitialized(false) { for (int nIndex = 0; nIndex < NM_RESERVOIR_PAGE_COUNT; ++nIndex) { m_pPages[nIndex] = nullptr; } // 单位绑定器必须先切换到数值试井参数系列,再创建任何参数字段。 setParaSrcTag(s_Nm_Serie); createUi(); setWindowTitle(tr("Reservoir properties settings")); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); // 首次打开使用建议尺寸,后续最小宽高均由页面内容和布局自动决定。 resize(880, 520); } nmWxReservoirPropertiesEditor::~nmWxReservoirPropertiesEditor() { delete m_pSession; m_pSession = nullptr; m_pDataSource = nullptr; } void nmWxReservoirPropertiesEditor::createUi() { // 第一步:创建原型中的顶部ribbon和无边框页面堆栈。 QVBoxLayout* pMainLayout = new QVBoxLayout(this); pMainLayout->setContentsMargins(12, 10, 12, 0); pMainLayout->setSpacing(0); pMainLayout->addWidget(createCategoryBar()); m_pStackedWidget = new QStackedWidget(this); pMainLayout->addWidget(m_pStackedWidget, 1); // 第二步:底部左侧放帮助与状态,右侧只放确定和取消。 QWidget* pFooterWidget = new QWidget(this); QHBoxLayout* pFooterLayout = new QHBoxLayout(pFooterWidget); pFooterLayout->setContentsMargins(4, 5, 4, 5); pFooterLayout->setSpacing(8); m_pFooterHelpButton = new QToolButton(pFooterWidget); m_pFooterHelpButton->setText(tr("Help")); m_pFooterHelpButton->setIcon(zxLoadIcon("Help")); m_pFooterHelpButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); m_pFooterHelpButton->setAutoRaise(true); pFooterLayout->addWidget(m_pFooterHelpButton); m_pStatusLabel = new QLabel(pFooterWidget); m_pStatusLabel->setWordWrap(true); pFooterLayout->addWidget(m_pStatusLabel, 1); m_pButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, pFooterWidget); QPushButton* pOkButton = m_pButtonBox->button(QDialogButtonBox::Ok); QPushButton* pCancelButton = m_pButtonBox->button(QDialogButtonBox::Cancel); if (pOkButton != nullptr) { pOkButton->setIcon(zxLoadIcon("OK")); pOkButton->setText(tr("OK")); } if (pCancelButton != nullptr) { pCancelButton->setIcon(zxLoadIcon("Cancel")); pCancelButton->setText(tr("Cancel")); } pFooterLayout->addWidget(m_pButtonBox); pMainLayout->addWidget(pFooterWidget); connect(m_pButtonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(m_pButtonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(m_pFooterHelpButton, SIGNAL(clicked()), this, SLOT(slotHelp())); } QWidget* nmWxReservoirPropertiesEditor::createCategoryBar() { // 使用无标题分组框承载顶部导航,获得与框架工具栏相近的浅色外边框。 QGroupBox* pCategoryBar = new QGroupBox(this); // 为边框和内部命令区保留少量间距,窗口拉伸时右侧空白自然扩展。 QHBoxLayout* pBarLayout = new QHBoxLayout(pCategoryBar); pBarLayout->setContentsMargins(3, 3, 3, 3); pBarLayout->setSpacing(0); m_pCategoryButtonGroup = new QButtonGroup(this); // 第一步:基础组只包含储层参数。 QHBoxLayout* pBasicLayout = nullptr; QWidget* pBasicGroup = createRibbonGroup( tr("Basic"), pCategoryBar, pBasicLayout); pBasicLayout->insertWidget(0, createCategoryButton( NM_RESERVOIR_PAGE_RESERVOIR, tr("Reservoir parameters"), "ResData", pBasicGroup)); pBarLayout->addWidget(pBasicGroup); pBarLayout->addWidget(createRibbonSeparator(pCategoryBar)); // 第二步:井筒参数独立成组,避免与储层内部区域对象混在一起。 QHBoxLayout* pWellLayout = nullptr; QWidget* pWellGroup = createRibbonGroup( tr("Wells"), pCategoryBar, pWellLayout); pWellLayout->insertWidget(0, createCategoryButton( NM_RESERVOIR_PAGE_WELL, tr("Well parameters"), "NmWell", pWellGroup)); pBarLayout->addWidget(pWellGroup); pBarLayout->addWidget(createRibbonSeparator(pCategoryBar)); // 第三步:四类储层平面对象按用户工作顺序放入同一个几何对象组。 QHBoxLayout* pGeometryLayout = nullptr; QWidget* pGeometryGroup = createRibbonGroup( tr("Geometry objects"), pCategoryBar, pGeometryLayout); pGeometryLayout->insertWidget(0, createCategoryButton( NM_RESERVOIR_PAGE_FAULT, tr("Faults"), "NmFault", pGeometryGroup)); pGeometryLayout->insertWidget(1, createCategoryButton( NM_RESERVOIR_PAGE_FRACTURE, tr("Fractures"), "NmFault", pGeometryGroup)); pGeometryLayout->insertWidget(2, createCategoryButton( NM_RESERVOIR_PAGE_COMPOSITE_REGION, tr("Composite regions"), "NmRegion", pGeometryGroup)); pGeometryLayout->insertWidget(3, createCategoryButton( NM_RESERVOIR_PAGE_REGION_MARK, tr("Region marks"), "RegionSelect", pGeometryGroup)); pBarLayout->addWidget(pGeometryGroup); pBarLayout->addWidget(createRibbonSeparator(pCategoryBar)); // 第四步:帮助是即时命令,不加入互斥页面按钮组。 QHBoxLayout* pHelpLayout = nullptr; QWidget* pHelpGroup = createRibbonGroup( tr("Help"), pCategoryBar, pHelpLayout); pHelpLayout->insertWidget(0, createHelpButton(pHelpGroup)); pBarLayout->addWidget(pHelpGroup); pBarLayout->addStretch(1); connect(m_pCategoryButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotCategoryButtonClicked(int))); return pCategoryBar; } QWidget* nmWxReservoirPropertiesEditor::createRibbonGroup( const QString& sCaption, QWidget* pParent, QHBoxLayout*& pCommandLayout) { QWidget* pGroupWidget = new QWidget(pParent); // 分组左右保留稳定留白,使按钮不会紧贴相邻分组的竖向分隔线。 QVBoxLayout* pGroupLayout = new QVBoxLayout(pGroupWidget); pGroupLayout->setContentsMargins(11, 2, 11, 1); // 按钮区与下方分组标题之间留出间距,避免两行文字贴得过近。 pGroupLayout->setSpacing(4); QWidget* pCommandWidget = new QWidget(pGroupWidget); pCommandLayout = new QHBoxLayout(pCommandWidget); pCommandLayout->setContentsMargins(0, 0, 0, 0); pCommandLayout->setSpacing(6); pCommandLayout->addStretch(); pGroupLayout->addWidget(pCommandWidget, 1); // 分组标题使用主题灰色和下划线,保持框架参考工具栏的层级表现。 QLabel* pCaptionLabel = new QLabel(sCaption, pGroupWidget); pCaptionLabel->setAlignment(Qt::AlignCenter); pCaptionLabel->setFixedHeight(16); QFont oCaptionFont = pCaptionLabel->font(); oCaptionFont.setUnderline(true); pCaptionLabel->setFont(oCaptionFont); QPalette oCaptionPalette = pCaptionLabel->palette(); oCaptionPalette.setColor( QPalette::WindowText, pGroupWidget->palette().color(QPalette::Mid)); pCaptionLabel->setPalette(oCaptionPalette); pGroupLayout->addWidget(pCaptionLabel); return pGroupWidget; } QFrame* nmWxReservoirPropertiesEditor::createRibbonSeparator( QWidget* pParent) { // 使用单像素平直竖线,避免Sunken双线在端点形成视觉折角。 QFrame* pSeparator = new QFrame(pParent); pSeparator->setFrameShape(QFrame::VLine); pSeparator->setFrameShadow(QFrame::Plain); pSeparator->setLineWidth(1); QPalette oSeparatorPalette = pSeparator->palette(); oSeparatorPalette.setColor( QPalette::WindowText, pParent->palette().color(QPalette::Mid)); pSeparator->setPalette(oSeparatorPalette); pSeparator->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); return pSeparator; } QToolButton* nmWxReservoirPropertiesEditor::createCategoryButton( nmReservoirPropertiesCategory eCategory, const QString& sText, const QString& sIconName, QWidget* pParent) { QToolButton* pButton = new QToolButton(pParent); pButton->setText(sText); pButton->setIcon(zxLoadIcon(sIconName)); pButton->setIconSize(QSize(25, 25)); pButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); // 分类按钮仅执行页面切换命令,不保留持续选中状态。 pButton->setCheckable(false); pButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); m_pCategoryButtonGroup->addButton( pButton, static_cast(eCategory)); return pButton; } QToolButton* nmWxReservoirPropertiesEditor::createHelpButton( QWidget* pParent) { QToolButton* pButton = new QToolButton(pParent); pButton->setText(tr("Help")); pButton->setIcon(zxLoadIcon("Help")); pButton->setIconSize(QSize(25, 25)); pButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); pButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); connect(pButton, SIGNAL(clicked()), this, SLOT(slotHelp())); return pButton; } bool nmWxReservoirPropertiesEditor::initialize(QString& sError) { // 第一步:建立工作副本,失败时不创建不完整的字段控件。 if (m_pSession == nullptr || !m_pSession->initialize(sError)) { m_bInitialized = false; return false; } // 第二步:创建页面并逐页验证XML参数定义和单位绑定。 createPages(); for (int nIndex = 0; nIndex < NM_RESERVOIR_PAGE_COUNT; ++nIndex) { if (!m_pPages[nIndex]->reloadObjects(sError)) { m_bInitialized = false; return false; } } // 第三步:没有对应对象的分类直接禁用,避免用户进入无操作价值的空页面。 updateCategoryButtonStates(); m_pStackedWidget->setCurrentIndex(NM_RESERVOIR_PAGE_RESERVOIR); m_bInitialized = true; validateAllPages(); sError.clear(); return true; } bool nmWxReservoirPropertiesEditor::bindUnitField( QLineEdit* pLineEdit, QComboBox* pUnitComboBox, const QString& sBaseParameterId) { // 参数XML已统一使用无歧义的mD作为渗透率基准单位,所有字段直接复用框架绑定器。 return bindUnitPairWxs(pLineEdit, pUnitComboBox, sBaseParameterId, false); } void nmWxReservoirPropertiesEditor::setUnitBaseValue( QLineEdit* pLineEdit, double dBaseValue) { if (m_pUnitBinder != nullptr) { m_pUnitBinder->setBaseValue(pLineEdit, dBaseValue); } } double nmWxReservoirPropertiesEditor::unitBaseValue( QLineEdit* pLineEdit) const { return m_pUnitBinder != nullptr ? m_pUnitBinder->getBaseValue(pLineEdit) : 0.0; } bool nmWxReservoirPropertiesEditor::isUnitInputValid( QLineEdit* pLineEdit) { return m_pUnitBinder != nullptr && m_pUnitBinder->isInputOK(pLineEdit, true, nullptr); } QString nmWxReservoirPropertiesEditor::currentUnit( QLineEdit* pLineEdit) const { return m_pUnitBinder != nullptr ? m_pUnitBinder->getCurrentUnit(pLineEdit) : QString(); } iUnitGroup* nmWxReservoirPropertiesEditor::unitGroup( QLineEdit* pLineEdit) const { return m_pUnitBinder != nullptr ? m_pUnitBinder->getUnitGroup(pLineEdit) : nullptr; } void nmWxReservoirPropertiesEditor::createPages() { if (m_pStackedWidget->count() > 0) { return; } for (int nIndex = 0; nIndex < NM_RESERVOIR_PAGE_COUNT; ++nIndex) { nmReservoirPropertiesCategory eCategory = static_cast(nIndex); m_pPages[nIndex] = new nmReservoirParameterPage( eCategory, m_pSession, this, m_pStackedWidget); connect(m_pPages[nIndex], SIGNAL(sigValidityChanged(bool)), this, SLOT(slotPageValidityChanged(bool))); m_pStackedWidget->addWidget(m_pPages[nIndex]); } } void nmWxReservoirPropertiesEditor::slotCategoryButtonClicked( int nCategory) { if (!m_bInitialized || nCategory < 0 || nCategory >= NM_RESERVOIR_PAGE_COUNT) { return; } // 分类按钮状态由对象列表决定;此处再次防御程序主动触发的非法切换。 QAbstractButton* pCategoryButton = m_pCategoryButtonGroup != nullptr ? m_pCategoryButtonGroup->button(nCategory) : nullptr; if (pCategoryButton == nullptr || !pCategoryButton->isEnabled()) { return; } // 离开当前分类前先校验并保存,非法时保持当前页。 if (m_pPages[m_nCurrentCategory] != nullptr && !m_pPages[m_nCurrentCategory]->commitPendingEdits()) { m_pStatusLabel->setText( tr("Please correct the invalid parameter values.")); return; } m_nCurrentCategory = nCategory; m_pStackedWidget->setCurrentIndex(nCategory); m_pStatusLabel->clear(); } void nmWxReservoirPropertiesEditor::updateCategoryButtonStates() { if (m_pCategoryButtonGroup == nullptr) { return; } // 逐类读取页面已经加载的对象列表,页面不存在或对象为空时禁用命令。 for (int nIndex = 0; nIndex < NM_RESERVOIR_PAGE_COUNT; ++nIndex) { QAbstractButton* pCategoryButton = m_pCategoryButtonGroup->button(nIndex); bool bEnabled = m_pPages[nIndex] != nullptr && m_pPages[nIndex]->hasEditableObjects(); if (pCategoryButton != nullptr) { pCategoryButton->setEnabled(bEnabled); } } } void nmWxReservoirPropertiesEditor::slotPageValidityChanged(bool bValid) { Q_UNUSED(bValid); validateAllPages(); } void nmWxReservoirPropertiesEditor::slotHelp() { // 顶部和底部帮助命令统一打开系统帮助文档。 ZxBaseUtil::startHelp(); } bool nmWxReservoirPropertiesEditor::validateAllPages() { bool bAllValid = m_bInitialized; if (m_bInitialized) { for (int nIndex = 0; nIndex < NM_RESERVOIR_PAGE_COUNT; ++nIndex) { if (m_pPages[nIndex] != nullptr && !m_pPages[nIndex]->isInputValid()) { bAllValid = false; break; } } } QPushButton* pOkButton = m_pButtonBox != nullptr ? m_pButtonBox->button(QDialogButtonBox::Ok) : nullptr; if (pOkButton != nullptr) { pOkButton->setEnabled(bAllValid); } return bAllValid; } void nmWxReservoirPropertiesEditor::accept() { if (!m_bInitialized) { return; } // 第一步:依次提交各页面当前对象尚未离开焦点的输入。 for (int nIndex = 0; nIndex < NM_RESERVOIR_PAGE_COUNT; ++nIndex) { if (m_pPages[nIndex] != nullptr && !m_pPages[nIndex]->commitPendingEdits()) { m_nCurrentCategory = nIndex; m_pStackedWidget->setCurrentIndex(nIndex); m_pStatusLabel->setText( tr("Please correct the invalid parameter values.")); validateAllPages(); return; } } // 第二步:把完整工作副本一次性交给数据源。 QString sError; if (!m_pSession->commit(sError)) { m_pStatusLabel->setText(sError); QMessageBox::warning(this, tr("Reservoir properties"), sError); return; } // 第三步:仅在提交成功后关闭对话框。 iDlgBase::accept(); } void nmWxReservoirPropertiesEditor::reject() { // 工作副本从未提前写入真实数据,取消时直接关闭即可。 iDlgBase::reject(); }