#include "nmWxWellEditor.h" #include "iUnitWxBinder.h" #include "mModuleDefines.h" #include "nmDataHorizontalFracturedWell.h" #include "nmDataPerforation.h" #include "nmDataVerticalFracturedWell.h" #include "nmDataWellBase.h" #include "nmWellEditorController.h" #include "nmWellEditorSession.h" #include "nmWellParameterPanel.h" #include "nmWellPressureFlowPage.h" #include "nmWxTimeDependentSkin.h" #include "nmWxWellboreTrajectoryDisplay.h" #include "ZxBaseUtil.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 nmWxWellEditor::nmWxWellEditor(const nmWellEditContext& oContext, QWidget* pParent) : iDlgBase(pParent), m_pSession(new nmWellEditorSession(oContext)), m_pCategoryButtonGroup(nullptr), m_pStackedWidget(nullptr), m_pPressureFlowPage(nullptr), m_pParameterPanel(nullptr), m_pTrajectoryDisplay(nullptr), m_pWellEditorController(nullptr), m_pWellCodeLineEdit(nullptr), m_pWellNameLineEdit(nullptr), m_pWellCategoryLineEdit(nullptr), m_pWellTypeComboBox(nullptr), m_pTimeDependentCheckBox(nullptr), m_pTimeDependentButton(nullptr), m_pFooterHelpButton(nullptr), m_pButtonBox(nullptr), m_pStatusLabel(nullptr), m_pMainSplitter(nullptr), m_nCurrentPage(NM_WELL_EDITOR_PAGE_PARAMETERS), m_sPanelValidationMessage(), m_bInitialized(false), m_bChangingWellType(false) { // 构造阶段只创建静态界面;工作副本和轨迹绑定由initialize()完成。 setParaSrcTag(s_Nm_Serie); createUi(); setWindowTitle(tr("Edit well")); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setMinimumSize(940, 600); resize(1180, 720); } nmWxWellEditor::~nmWxWellEditor() { // 界面和控制器仍引用工作副本,必须在会话释放井对象前销毁。 delete m_pWellEditorController; m_pWellEditorController = nullptr; delete m_pStackedWidget; m_pStackedWidget = nullptr; m_pPressureFlowPage = nullptr; m_pMainSplitter = nullptr; m_pParameterPanel = nullptr; m_pTrajectoryDisplay = nullptr; delete m_pSession; m_pSession = nullptr; } bool nmWxWellEditor::initialize(QString& sError) { if (m_pSession == nullptr || !m_pSession->initialize(sError)) { return false; } nmDataWellBase* pWell = m_pSession->currentWell(); if (pWell == nullptr) { sError = tr("The well working copy is unavailable."); return false; } // 第一步:同步井信息和时间变表皮控件。 m_pWellCodeLineEdit->setText(pWell->getWellCode()); m_pWellNameLineEdit->setText(pWell->getWellName()); QString sWellCategory; switch(pWell->getWellCategory()) { case NM_WellCategory_Oil: sWellCategory = tr("Oil well"); break; case NM_WellCategory_Gas: sWellCategory = tr("Gas well"); break; case NM_WellCategory_Water: sWellCategory = tr("Water well"); break; default: sWellCategory = tr("Unknown"); break; } m_pWellCategoryLineEdit->setText(sWellCategory); int nWellTypeIndex = m_pWellTypeComboBox->findData( static_cast(pWell->getWellType())); m_bChangingWellType = true; m_pWellTypeComboBox->setCurrentIndex(nWellTypeIndex); m_bChangingWellType = false; reloadTimeDependentSkinControls(pWell); // 第二步:创建当前井型字段并将轨迹视图绑定到同一工作副本。 if (!m_pParameterPanel->setWellData(pWell, sError)) { return false; } m_pTrajectoryDisplay->setCurrentWellboreData(pWell); createController(); m_pTrajectoryDisplay->selectDefaultViewForCurrentWell(); m_bInitialized = true; m_pPressureFlowPage->setWell(pWell); m_pStackedWidget->setCurrentIndex( NM_WELL_EDITOR_PAGE_PARAMETERS); m_nCurrentPage = NM_WELL_EDITOR_PAGE_PARAMETERS; updateAcceptState(); sError.clear(); return true; } nmDataWellBase* nmWxWellEditor::takeModifiedWell() { return m_pSession != nullptr ? m_pSession->takeModifiedWell() : nullptr; } bool nmWxWellEditor::bindUnitField( QLineEdit* pLineEdit, QComboBox* pUnitComboBox, const QString& sBaseParameterId) { return bindUnitPairWxs(pLineEdit, pUnitComboBox, sBaseParameterId, false); } void nmWxWellEditor::setUnitBaseValue(QLineEdit* pLineEdit, double dBaseValue) { if (m_pUnitBinder != nullptr) { m_pUnitBinder->setBaseValue(pLineEdit, dBaseValue); } } double nmWxWellEditor::unitBaseValue(QLineEdit* pLineEdit) const { return m_pUnitBinder != nullptr ? m_pUnitBinder->getBaseValue(pLineEdit) : 0.0; } bool nmWxWellEditor::isUnitInputValid(QLineEdit* pLineEdit) { return m_pUnitBinder != nullptr && m_pUnitBinder->isInputOK(pLineEdit, true, nullptr); } QString nmWxWellEditor::currentUnit(QLineEdit* pLineEdit) const { return m_pUnitBinder != nullptr ? m_pUnitBinder->getCurrentUnit(pLineEdit) : QString(); } iUnitGroup* nmWxWellEditor::unitGroup(QLineEdit* pLineEdit) const { return m_pUnitBinder != nullptr ? m_pUnitBinder->getUnitGroup(pLineEdit) : nullptr; } void nmWxWellEditor::accept() { if (!m_bInitialized) { return; } // 第一步:井名和参数必须全部通过校验,原始井此时仍未修改。 QString sWellName; QString sError; if (!validateWellName(sWellName, sError)) { m_pStatusLabel->setText(sError); m_pWellNameLineEdit->setFocus(); m_pWellNameLineEdit->selectAll(); updateAcceptState(); return; } if (!m_pParameterPanel->commitPendingEdits(sError)) { m_pStatusLabel->setText(sError); m_pParameterPanel->focusFirstInvalid(); updateAcceptState(); return; } // 第二步:只整理最终工作副本,真实数据由Binder在窗口返回后替换。 m_pSession->setWellName(sWellName); nmDataWellBase* pWell = m_pSession->currentWell(); if (nmDataVerticalFracturedWell* pVertical = dynamic_cast(pWell)) { pVertical->setFracs(); } if (nmDataHorizontalFracturedWell* pHorizontal = dynamic_cast(pWell)) { pHorizontal->setFracs(); } iDlgBase::accept(); } void nmWxWellEditor::reject() { iDlgBase::reject(); } void nmWxWellEditor::keyPressEvent(QKeyEvent* pEvent) { // 参数输入框按回车只结束编辑,不能绕过完整校验直接关闭窗口。 if (pEvent != nullptr && (pEvent->key() == Qt::Key_Enter || pEvent->key() == Qt::Key_Return)) { pEvent->ignore(); return; } iDlgBase::keyPressEvent(pEvent); } void nmWxWellEditor::slotWellTypeChanged(int nIndex) { if (m_bChangingWellType || !m_bInitialized || nIndex < 0) { return; } NM_WELL_MODEL ePreviousType = m_pSession->currentWellType(); NM_WELL_MODEL eNewType = static_cast( m_pWellTypeComboBox->itemData(nIndex).toInt()); if (eNewType == ePreviousType) { return; } // 第一步:离开当前井型前保存全部有效输入,非法时回退选择。 QString sError; if (!m_pParameterPanel->commitPendingEdits(sError)) { m_bChangingWellType = true; m_pWellTypeComboBox->setCurrentIndex( m_pWellTypeComboBox->findData( static_cast(ePreviousType))); m_bChangingWellType = false; m_pStatusLabel->setText(sError); m_pParameterPanel->focusFirstInvalid(); return; } // 第二步:恢复目标井型缓存,首次进入时由会话保留该井型默认几何。 if (!m_pSession->switchWellType(eNewType, sError)) { m_bChangingWellType = true; m_pWellTypeComboBox->setCurrentIndex( m_pWellTypeComboBox->findData( static_cast(ePreviousType))); m_bChangingWellType = false; m_pStatusLabel->setText(sError); return; } // 第三步:同步井名,并从目标工作副本恢复井型专属状态。 m_pSession->setWellName(m_pWellNameLineEdit->text()); nmDataWellBase* pWell = m_pSession->currentWell(); reloadTimeDependentSkinControls(pWell); if (!m_pParameterPanel->setWellData(pWell, sError)) { m_pStatusLabel->setText(sError); return; } m_pTrajectoryDisplay->setCurrentWellboreData(pWell); createController(); m_pPressureFlowPage->setWell(pWell); updateAcceptState(); } void nmWxWellEditor::slotCategoryButtonClicked(int nPage) { if (!m_bInitialized || m_pStackedWidget == nullptr || nPage < 0 || nPage >= NM_WELL_EDITOR_PAGE_COUNT) { return; } if (nPage == m_nCurrentPage) { return; } // 查看页只读取当前工作副本,不提交或校验参数页中的临时输入。 if (nPage == NM_WELL_EDITOR_PAGE_PRESSURE_FLOW) { reloadPressureFlowPage(); } m_pStackedWidget->setCurrentIndex(nPage); m_nCurrentPage = nPage; } void nmWxWellEditor::slotWellNameEdited(const QString& sWellName) { if (m_pSession != nullptr) { m_pSession->setWellName(sWellName); } updateAcceptState(); } void nmWxWellEditor::slotTimeDependentToggled(bool bChecked) { m_pTimeDependentButton->setEnabled(bChecked); if (m_pSession != nullptr) { m_pSession->setTimeDependentSkin(bChecked); } } void nmWxWellEditor::reloadTimeDependentSkinControls( nmDataWellBase* pWell) { bool bEnabled = pWell != nullptr && pWell->isTimeDependentSkin(); // 程序回填不应再次写会话,避免覆盖目标井型缓存。 bool bSignalsWereBlocked = m_pTimeDependentCheckBox->blockSignals(true); m_pTimeDependentCheckBox->setChecked(bEnabled); m_pTimeDependentCheckBox->blockSignals(bSignalsWereBlocked); m_pTimeDependentButton->setEnabled(bEnabled); } void nmWxWellEditor::reloadPressureFlowPage() { if (m_pPressureFlowPage == nullptr || m_pSession == nullptr) { return; } nmDataWellBase* pWell = m_pSession->currentWell(); m_pPressureFlowPage->setWell(pWell); m_pPressureFlowPage->refreshData(); } void nmWxWellEditor::slotEditTimeDependentSkin() { if (!m_bInitialized || m_pSession->currentWell() == nullptr) { return; } // 第一步:子窗口打开前先提交主窗口输入,保证两处看到同一份数据。 QString sError; if (!m_pParameterPanel->commitPendingEdits(sError)) { m_pStatusLabel->setText(sError); m_pParameterPanel->focusFirstInvalid(); return; } // 第二步:旧子窗口会直接修改传入井,打开前保存完整射孔快照。 nmDataWellBase* pWell = m_pSession->currentWell(); QVector vecOriginalPerforations; for (int nIndex = 0; nIndex < pWell->getPerforationCount(); ++nIndex) { vecOriginalPerforations.append( pWell->getPerforationCopy(nIndex)); } nmWxTimeDependentSkin oDialog(pWell, this); oDialog.setWindowModality(Qt::WindowModal); if (oDialog.exec() != QDialog::Accepted) { // 第三步:取消时恢复数量和内容,保证会话工作副本也具备回滚语义。 while (pWell->getPerforationCount() > vecOriginalPerforations.size()) { pWell->removePerforation( pWell->getPerforationCount() - 1); } int nCommonCount = qMin( pWell->getPerforationCount(), vecOriginalPerforations.size()); for (int nIndex = 0; nIndex < nCommonCount; ++nIndex) { pWell->updatePerforation( nIndex, vecOriginalPerforations[nIndex]); } for (int nIndex = nCommonCount; nIndex < vecOriginalPerforations.size(); ++nIndex) { pWell->addPerforation(new nmDataPerforation( vecOriginalPerforations[nIndex])); } } m_pParameterPanel->reloadFromWell(); } void nmWxWellEditor::slotPanelValidityChanged(bool bValid) { Q_UNUSED(bValid); updateAcceptState(); } void nmWxWellEditor::slotPanelValidationMessageChanged( const QString& sMessage) { m_sPanelValidationMessage = sMessage; updateAcceptState(); } void nmWxWellEditor::createUi() { QVBoxLayout* pMainLayout = new QVBoxLayout(this); pMainLayout->setContentsMargins(12, 10, 12, 0); pMainLayout->setSpacing(0); pMainLayout->addWidget(createCategoryBar()); m_pStackedWidget = new QStackedWidget(this); // 第一步:井参数页完整保留原有参数区、轨迹视图和分割比例。 QWidget* pParameterPage = new QWidget(m_pStackedWidget); QVBoxLayout* pPageLayout = new QVBoxLayout(pParameterPage); pPageLayout->setContentsMargins(0, 6, 0, 0); pPageLayout->setSpacing(8); m_pMainSplitter = new QSplitter(Qt::Horizontal, pParameterPage); m_pMainSplitter->setChildrenCollapsible(false); QWidget* pParameterContainer = new QWidget(m_pMainSplitter); QVBoxLayout* pParameterLayout = new QVBoxLayout(pParameterContainer); pParameterLayout->setContentsMargins(0, 0, 0, 0); pParameterLayout->setSpacing(8); QScrollArea* pScrollArea = new QScrollArea(pParameterContainer); pScrollArea->setWidgetResizable(true); pScrollArea->setFrameShape(QFrame::NoFrame); m_pParameterPanel = new nmWellParameterPanel(this, pScrollArea); pParameterLayout->addWidget( createWellInformationWidget(pParameterContainer)); m_pWellTypeComboBox->addItem( tr("Vertical well"), static_cast(Vertical_Well)); m_pWellTypeComboBox->addItem( tr("Fractured vertical well"), static_cast(Vertical_Fractured_Well)); m_pWellTypeComboBox->addItem( tr("Multistage fractured horizontal well"), static_cast(Horizontal_Fractured_Well)); pScrollArea->setWidget(m_pParameterPanel); pParameterLayout->addWidget(pScrollArea, 1); // 第二步:右侧轨迹视图占据剩余空间,避免参数区拉伸破坏绘图比例。 m_pTrajectoryDisplay = new nmWxWellboreTrajectoryDisplay(m_pMainSplitter); m_pTrajectoryDisplay->set3DViewVisible(false); m_pMainSplitter->addWidget(pParameterContainer); m_pMainSplitter->addWidget(m_pTrajectoryDisplay); m_pMainSplitter->setStretchFactor(0, 0); m_pMainSplitter->setStretchFactor(1, 1); QList listSizes; listSizes << 410 << 730; m_pMainSplitter->setSizes(listSizes); pPageLayout->addWidget(m_pMainSplitter, 1); // 第二步:只读压力流量页与参数页常驻同一堆栈,切换时不重建控件。 m_pPressureFlowPage = new nmWellPressureFlowPage(m_pStackedWidget); m_pStackedWidget->addWidget(pParameterPage); m_pStackedWidget->addWidget(m_pPressureFlowPage); m_pStackedWidget->setCurrentIndex( NM_WELL_EDITOR_PAGE_PARAMETERS); pMainLayout->addWidget(m_pStackedWidget, 1); // 第三步:底部命令区在两个页面间共享,并集中连接编辑状态信号。 pMainLayout->addWidget(createFooter()); connect(m_pWellNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(slotWellNameEdited(QString))); connect(m_pWellTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotWellTypeChanged(int))); connect(m_pParameterPanel, SIGNAL(sigValidityChanged(bool)), this, SLOT(slotPanelValidityChanged(bool))); connect(m_pParameterPanel, SIGNAL(sigValidationMessageChanged(QString)), this, SLOT(slotPanelValidationMessageChanged(QString))); } QWidget* nmWxWellEditor::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_WELL_EDITOR_PAGE_PARAMETERS, tr("Well parameters"), "NmWell", pBasicGroup)); pBarLayout->addWidget(pBasicGroup); pBarLayout->addWidget(createRibbonSeparator(pCategoryBar)); QHBoxLayout* pDataLayout = nullptr; QWidget* pDataGroup = createRibbonGroup( tr("Data"), pCategoryBar, pDataLayout); pDataLayout->insertWidget(0, createCategoryButton( NM_WELL_EDITOR_PAGE_PRESSURE_FLOW, tr("Pressure and flow"), "tAllData", pDataGroup)); pBarLayout->addWidget(pDataGroup); 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* nmWxWellEditor::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* nmWxWellEditor::createRibbonSeparator(QWidget* pParent) { 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* nmWxWellEditor::createCategoryButton( int nPage, const QString& sText, const QString& sIconName, QWidget* pParent) { QToolButton* pButton = new QToolButton(pParent); pButton->setText(sText); pButton->setIcon(zxLoadIcon(sIconName)); pButton->setIconSize(QSize(28, 28)); pButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); pButton->setCheckable(false); pButton->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed); m_pCategoryButtonGroup->addButton(pButton, nPage); return pButton; } QToolButton* nmWxWellEditor::createHelpButton(QWidget* pParent) { QToolButton* pButton = new QToolButton(pParent); pButton->setText(tr("Help")); pButton->setIcon(zxLoadIcon("Help")); pButton->setIconSize(QSize(28, 28)); pButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); pButton->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed); connect(pButton, SIGNAL(clicked()), this, SLOT(slotHelp())); return pButton; } QWidget* nmWxWellEditor::createWellInformationWidget(QWidget* pParent) { QGroupBox* pInformationGroup = new QGroupBox( tr("Well information"), pParent); QGridLayout* pInformationLayout = new QGridLayout(pInformationGroup); pInformationLayout->setContentsMargins(8, 15, 8, 10); pInformationLayout->setHorizontalSpacing(6); pInformationLayout->setVerticalSpacing(6); pInformationLayout->setColumnStretch(0, 0); pInformationLayout->setColumnStretch(1, 8); pInformationLayout->setColumnStretch(2, 5); pInformationLayout->setColumnMinimumWidth( 1, nmParameterField::editorMinimumWidth()); pInformationLayout->setColumnMinimumWidth( 2, nmParameterField::unitMinimumWidth()); QLabel* pCodeLabel = new QLabel(tr("Well number"), pInformationGroup); QLabel* pNameLabel = new QLabel(tr("Well name"), pInformationGroup); QLabel* pCategoryLabel = new QLabel( tr("Well category"), pInformationGroup); QLabel* pTypeLabel = new QLabel(tr("Well model"), pInformationGroup); QLabel* pTimeDependentLabel = new QLabel( tr("Time-dependent skin"), pInformationGroup); pCodeLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); pNameLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); pCategoryLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); pTypeLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); pTimeDependentLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter); m_pWellCodeLineEdit = new QLineEdit(pInformationGroup); m_pWellNameLineEdit = new QLineEdit(pInformationGroup); m_pWellCategoryLineEdit = new QLineEdit(pInformationGroup); m_pWellTypeComboBox = new QComboBox(pInformationGroup); m_pWellCodeLineEdit->setReadOnly(true); m_pWellCategoryLineEdit->setReadOnly(true); m_pWellCodeLineEdit->setToolTip( tr("Project-level unique well identifier")); QList listEditors; listEditors << m_pWellCodeLineEdit << m_pWellNameLineEdit << m_pWellCategoryLineEdit << m_pWellTypeComboBox; for (int nIndex = 0; nIndex < listEditors.size(); ++nIndex) { listEditors[nIndex]->setFixedHeight( nmParameterField::editorHeight()); listEditors[nIndex]->setMinimumWidth( nmParameterField::editorMinimumWidth()); listEditors[nIndex]->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed); } // 时间变表皮属于井级配置,开关和详情入口共同放在值列。 QWidget* pTimeDependentEditor = new QWidget(pInformationGroup); QHBoxLayout* pTimeDependentLayout = new QHBoxLayout( pTimeDependentEditor); pTimeDependentLayout->setContentsMargins(0, 0, 0, 0); pTimeDependentLayout->setSpacing(4); m_pTimeDependentCheckBox = new QCheckBox(pTimeDependentEditor); m_pTimeDependentButton = new QToolButton(pTimeDependentEditor); QString sIconDir = QCoreApplication::applicationDirPath() .section('/', 0, -2) + "/Res/Icon/"; m_pTimeDependentButton->setIcon( QIcon(sIconDir + "NmAnalDesign3.png")); m_pTimeDependentButton->setIconSize(QSize(20, 20)); m_pTimeDependentButton->setFixedSize(26, 26); m_pTimeDependentButton->setAutoRaise(true); m_pTimeDependentButton->setToolTip( tr("Edit time-dependent skin")); m_pTimeDependentButton->setEnabled(false); pTimeDependentLayout->addWidget(m_pTimeDependentCheckBox); pTimeDependentLayout->addWidget(m_pTimeDependentButton); pTimeDependentLayout->addStretch(1); pInformationLayout->addWidget(pCodeLabel, 0, 0); pInformationLayout->addWidget(m_pWellCodeLineEdit, 0, 1); pInformationLayout->addWidget(pNameLabel, 1, 0); pInformationLayout->addWidget(m_pWellNameLineEdit, 1, 1); pInformationLayout->addWidget(pCategoryLabel, 2, 0); pInformationLayout->addWidget(m_pWellCategoryLineEdit, 2, 1); pInformationLayout->addWidget(pTypeLabel, 3, 0); pInformationLayout->addWidget(m_pWellTypeComboBox, 3, 1); pInformationLayout->addWidget(pTimeDependentLabel, 4, 0); pInformationLayout->addWidget(pTimeDependentEditor, 4, 1); connect(m_pTimeDependentCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotTimeDependentToggled(bool))); connect(m_pTimeDependentButton, SIGNAL(clicked()), this, SLOT(slotEditTimeDependentSkin())); return pInformationGroup; } QWidget* nmWxWellEditor::createFooter() { QWidget* pFooter = new QWidget(this); QHBoxLayout* pFooterLayout = new QHBoxLayout(pFooter); pFooterLayout->setContentsMargins(4, 5, 4, 5); pFooterLayout->setSpacing(8); // 左侧帮助命令与储层特性对话框保持相同样式和行为。 m_pFooterHelpButton = new QToolButton(pFooter); m_pFooterHelpButton->setText(tr("Help")); m_pFooterHelpButton->setIcon(zxLoadIcon("Help")); m_pFooterHelpButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon); m_pFooterHelpButton->setAutoRaise(true); // 中间状态区使用主题调色板,仅将错误文字标红。 m_pStatusLabel = new QLabel(pFooter); m_pStatusLabel->setWordWrap(true); QPalette oStatusPalette = m_pStatusLabel->palette(); oStatusPalette.setColor(QPalette::WindowText, QColor(Qt::red)); m_pStatusLabel->setPalette(oStatusPalette); // 右侧使用标准确定和取消角色,维持平台统一交互。 m_pButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, pFooter); 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_pFooterHelpButton); pFooterLayout->addWidget(m_pStatusLabel, 1); pFooterLayout->addWidget(m_pButtonBox); connect(m_pFooterHelpButton, SIGNAL(clicked()), this, SLOT(slotHelp())); connect(m_pButtonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(m_pButtonBox, SIGNAL(rejected()), this, SLOT(reject())); return pFooter; } void nmWxWellEditor::slotHelp() { ZxBaseUtil::startHelp(); } void nmWxWellEditor::createController() { // 控制器持有当前工作副本指针,井型切换后必须销毁并重新绑定。 delete m_pWellEditorController; m_pWellEditorController = nullptr; if (m_pSession == nullptr || m_pSession->currentWell() == nullptr || m_pTrajectoryDisplay == nullptr) { return; } m_pWellEditorController = new nmWellEditorController( m_pSession->currentWell(), m_pTrajectoryDisplay, this); setupControllerConnections(); } void nmWxWellEditor::setupControllerConnections() { if (m_pWellEditorController == nullptr || m_pTrajectoryDisplay == nullptr) { return; } // 第一步:工具栏只发出模式请求,由控制器决定具体编辑行为。 connect(m_pTrajectoryDisplay, SIGNAL(requestedEditWellMode(int)), m_pWellEditorController, SLOT(onRequestEditWellMode(int))); connect(m_pTrajectoryDisplay, SIGNAL(requestedEditPerforationsMode(int)), m_pWellEditorController, SLOT(onRequestEditPerforationsMode(int))); connect(m_pTrajectoryDisplay, SIGNAL(requestedNewPerforationMode(int)), m_pWellEditorController, SLOT(onRequestNewPerforationMode(int))); connect(m_pTrajectoryDisplay, SIGNAL(requestedDeletePerforationMode(int)), m_pWellEditorController, SLOT(onRequestDeletePerforationMode(int))); connect(m_pTrajectoryDisplay, SIGNAL(requestedSnapPerforationMode(int)), m_pWellEditorController, SLOT(onRequestSnapPerforationMode(int))); // 第二步:视图产生的几何修改统一交给控制器写入工作副本。 connect(m_pTrajectoryDisplay, SIGNAL(sigClearViewStates()), m_pWellEditorController, SLOT(slotClearViewStates())); connect(m_pTrajectoryDisplay, SIGNAL(currentViewChanged(int)), m_pWellEditorController, SLOT(onViewChanged(int))); connect(m_pTrajectoryDisplay, SIGNAL(sigWellboreTrajectoryPointMoved(QPointF)), m_pWellEditorController, SLOT(slotWellboreTrajectoryPointMoved(QPointF))); connect(m_pTrajectoryDisplay, SIGNAL(sigPerforationDragFinished(nmDataPerforation*,double,double)), m_pWellEditorController, SLOT(slotPerforationDragFinished(nmDataPerforation*,double,double))); connect(m_pTrajectoryDisplay, SIGNAL(sigTryAddNewPerforation(QPointF,double,double,QLineF)), m_pWellEditorController, SLOT(slotTryAddNewPerforation(QPointF,double,double,QLineF))); connect(m_pTrajectoryDisplay, SIGNAL(sigTryDeletePerforation(nmDataPerforation*)), m_pWellEditorController, SLOT(slotDeletePerforation(nmDataPerforation*))); connect(m_pTrajectoryDisplay, SIGNAL(sigFractureGeometryDragFinished(double,double)), m_pWellEditorController, SLOT(slotFractureGeometryChanged(double,double))); connect(m_pTrajectoryDisplay, SIGNAL(sigRequestFractureRectUpdate(qreal,qreal)), m_pWellEditorController, SLOT(slotHandleFractureRectUpdate(qreal,qreal))); connect(m_pTrajectoryDisplay, SIGNAL(sigFractureDragFinished(const QMap>&)), m_pWellEditorController, SLOT(slotUpdatePerforations(const QMap>&))); connect(m_pTrajectoryDisplay, SIGNAL(sigFractureHalfLengthChanged(double)), m_pWellEditorController, SLOT(slotUpdateFractureHalfLength(double))); connect(m_pTrajectoryDisplay, SIGNAL(sigWellboreDragFinished(QPointF)), m_pWellEditorController, SLOT(slotUpdateWellborePos(QPointF))); connect(m_pTrajectoryDisplay, SIGNAL(sigFractureGeometryChanged(double,double)), m_pWellEditorController, SLOT(slotHFWellFractureGeometryChanged(double,double))); } bool nmWxWellEditor::validateWellName(QString& sTrimmedName, QString& sError) const { // 第一步:规范化用户输入并检查空值及文件名规则。 sTrimmedName = m_pWellNameLineEdit != nullptr ? m_pWellNameLineEdit->text().trimmed() : QString(); if (sTrimmedName.isEmpty()) { sError = tr("Well name cannot be empty."); return false; } if (!isLegalFileName(sTrimmedName)) { sError = tr("Well name contains invalid file name characters."); return false; } // 第二步:只与上下文中的其他井比较,允许当前井保持原名称。 QStringList listOtherNames = m_pSession != nullptr ? m_pSession->otherWellNames() : QStringList(); for (int nIndex = 0; nIndex < listOtherNames.size(); ++nIndex) { if (QString::compare(sTrimmedName, listOtherNames[nIndex].trimmed(), Qt::CaseInsensitive) == 0) { sError = tr("A well with this name already exists."); return false; } } sError.clear(); return true; } bool nmWxWellEditor::isLegalFileName(const QString& sWellName) const { // 第一步:拒绝特殊路径片段、非法字符、控制字符和结尾句点。 if (sWellName == "." || sWellName == ".." || sWellName.endsWith('.')) { return false; } if (sWellName.contains(QRegExp("[<>:\"/\\\\|?*]"))) { return false; } for (int nIndex = 0; nIndex < sWellName.size(); ++nIndex) { if (sWellName[nIndex].unicode() < 32) { return false; } } // 第二步:Windows保留设备名即使带扩展名也不能作为井文件名称。 QString sStem = sWellName.section('.', 0, 0).toUpper(); QRegExp oReserved( "^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$"); return !oReserved.exactMatch(sStem); } void nmWxWellEditor::updateAcceptState() { if (m_pButtonBox == nullptr) { return; } // 第一步:汇总井名和当前参数面板状态,统一控制确认入口。 QString sWellName; QString sNameError; bool bNameValid = m_pSession != nullptr && validateWellName(sWellName, sNameError); bool bPanelValid = m_pParameterPanel != nullptr && m_pParameterPanel->isInputValid(); QPushButton* pOkButton = m_pButtonBox->button(QDialogButtonBox::Ok); if (pOkButton != nullptr) { pOkButton->setEnabled( m_bInitialized && bNameValid && bPanelValid); } // 第二步:底部只显示最高优先级错误,避免多处提示互相覆盖。 if (!bNameValid) { m_pStatusLabel->setText(sNameError); } else if (!bPanelValid) { m_pStatusLabel->setText(m_sPanelValidationMessage.isEmpty() ? tr("Please correct the invalid parameter values.") : m_sPanelValidationMessage); } else { m_pStatusLabel->clear(); } }