diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index 32bc151..3092a7f 100644 Binary files a/Bin/Config/Lang/cn/nmNum_cn.qm and b/Bin/Config/Lang/cn/nmNum_cn.qm differ diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts index e5a7886..138c4fa 100644 --- a/Bin/Config/Lang/cn/nmNum_cn.ts +++ b/Bin/Config/Lang/cn/nmNum_cn.ts @@ -1,4 +1,4 @@ - + @@ -6781,4 +6781,78 @@ Average pressure in contour: %2 MPa Kriging 结果中包含无效数值。 + + nmReservoirParameterField + The parameter layout is unavailable.参数布局不可用。 + Parameter definition not found: %1未找到参数定义:%1 + Failed to bind the unit for parameter: %1参数单位绑定失败:%1 + Dimensionless无量纲 + + + nmReservoirParameterPage + Reservoir parameters储层参数 + Well parameters井参数 + Fault parameters断层参数 + Fracture parameters裂缝参数 + Composite region parameters复合区参数 + Region mark parameters区域标记参数 + Select well:选择井: + Select fault:选择断层: + Select fracture:选择裂缝: + Select composite region:选择复合区: + Select region mark:选择区域标记: + Select object:选择对象: + No editable objects in this category.此分类中没有可编辑对象。 + + + nmWxReservoirPropertiesEditor + Reservoir properties settings储层特性设置 + Reservoir properties储层参数 + Help帮助 + OK确定 + Cancel取消 + Basic基础 + Wells + Geometry objects几何对象 + Reservoir parameters储层参数 + Well parameters井参数 + Faults断层 + Fractures裂缝 + Composite regions复合区 + Region marks区域标记 + Please correct the invalid parameter values.请修正无效的参数值。 + Reservoir type储层类型 + Homogeneous均质储层 + Dual porosity pseudo steady state双重介质拟稳态 + Fault flow model断层流动模型 + Region flow model复合区流动模型 + Leaky泄漏 + Composite limit复合边界 + Fracture flow model裂缝流动模型 + Finite conductivity有限导流 + Infinite conductivity无限导流 + Vertical well直井 + Vertical fractured well垂直压裂井 + Horizontal fractured well水平压裂井 + Position and wellbore位置与井筒 + Storage and fracture储集与裂缝 + Basic properties基础特性 + Fluid parameters流体参数 + Compression and saturation压缩与饱和度 + Dual-medium parameters双重介质参数 + Geometry几何位置 + Flow properties流动属性 + Conductivity properties导流属性 + Region properties区域属性 + + + nmSubWndMain + Reservoir properties储层参数 + The current numerical well-test data is unavailable.当前数值试井数据不可用。 + + + QObject + The current numerical well-test data is unavailable.当前数值试井数据不可用。 + The reservoir properties editing session is unavailable.储层参数编辑会话不可用。 + diff --git a/Bin/XmlFiles/ModelParaDefinesNM_cn.xml b/Bin/XmlFiles/ModelParaDefinesNM_cn.xml index 367fb1c..8788ebc 100644 --- a/Bin/XmlFiles/ModelParaDefinesNM_cn.xml +++ b/Bin/XmlFiles/ModelParaDefinesNM_cn.xml @@ -17,8 +17,11 @@ - + + + + @@ -32,11 +35,11 @@ - + - - + + @@ -44,7 +47,8 @@ - + + @@ -52,6 +56,7 @@ + @@ -85,12 +90,12 @@ - + - + @@ -99,7 +104,7 @@ - + diff --git a/Bin/XmlFiles/ModelParaDefinesNM_en.xml b/Bin/XmlFiles/ModelParaDefinesNM_en.xml index fb7e17f..02be24f 100644 --- a/Bin/XmlFiles/ModelParaDefinesNM_en.xml +++ b/Bin/XmlFiles/ModelParaDefinesNM_en.xml @@ -18,8 +18,11 @@ - + + + + @@ -33,11 +36,11 @@ - + - - + + @@ -45,7 +48,8 @@ - + + @@ -53,6 +57,7 @@ + @@ -86,12 +91,12 @@ - + - + @@ -100,7 +105,7 @@ - + diff --git a/Include/nmNum/nmSubWxs/nmReservoirParameterCatalog.h b/Include/nmNum/nmSubWxs/nmReservoirParameterCatalog.h new file mode 100644 index 0000000..0824746 --- /dev/null +++ b/Include/nmNum/nmSubWxs/nmReservoirParameterCatalog.h @@ -0,0 +1,143 @@ +#pragma once + +#include "nmSubWxs_global.h" +#include "nmDefines.h" + +#include +#include + +class nmAttrRegistry; +struct nmReservoirPropertiesSnapshot; + +/** @brief 储层参数编辑页面分类。 */ +enum nmReservoirPropertiesCategory +{ + NM_RESERVOIR_PAGE_RESERVOIR = 0, ///< 储层。 + NM_RESERVOIR_PAGE_WELL, ///< 井。 + NM_RESERVOIR_PAGE_FAULT, ///< 断层。 + NM_RESERVOIR_PAGE_FRACTURE, ///< 裂缝。 + NM_RESERVOIR_PAGE_COMPOSITE_REGION, ///< 复合区。 + NM_RESERVOIR_PAGE_REGION_MARK, ///< 区域标记。 + NM_RESERVOIR_PAGE_COUNT ///< 分类数量。 +}; + +/** @brief 参数对象的具体类型。 */ +enum nmReservoirPropertiesObjectType +{ + NM_RESERVOIR_OBJECT_RESERVOIR = 0, ///< 储层对象。 + NM_RESERVOIR_OBJECT_VERTICAL_WELL, ///< 直井对象。 + NM_RESERVOIR_OBJECT_VERTICAL_FRAC_WELL, ///< 垂直压裂井对象。 + NM_RESERVOIR_OBJECT_HORIZONTAL_FRAC_WELL,///< 水平压裂井对象。 + NM_RESERVOIR_OBJECT_REGION_MARK, ///< 区域标记对象。 + NM_RESERVOIR_OBJECT_FAULT, ///< 断层对象。 + NM_RESERVOIR_OBJECT_FRACTURE, ///< 裂缝对象。 + NM_RESERVOIR_OBJECT_REGION ///< 复合区对象。 +}; + +/** @brief 参数字段的编辑器类型。 */ +enum nmReservoirParameterEditorType +{ + NM_RESERVOIR_EDITOR_NUMBER = 0, ///< 浮点数输入框。 + NM_RESERVOIR_EDITOR_INTEGER, ///< 整数输入框。 + NM_RESERVOIR_EDITOR_ENUM ///< 稳定code枚举下拉框。 +}; + +/** @brief 枚举下拉框选项。 */ +struct NM_SUB_WXS_EXPORT nmReservoirEnumOption +{ + QString m_sCode; ///< 不随语言变化的业务代码。 + QString m_sText; ///< 当前语言下的显示文本。 +}; + +/** + * @brief 单个参数字段的界面描述。 + * @note 稳定参数ID用于会话读写,基础参数ID用于读取XML单位和范围。 + */ +struct NM_SUB_WXS_EXPORT nmReservoirParameterFieldInfo +{ + QString m_sParameterId; ///< 编辑会话中的稳定参数ID。 + QString m_sBaseParameterId; ///< 中英文参数XML中的基础参数ID。 + QString m_sLabel; ///< 枚举字段的显示名称;数值字段留空时读取XML别名。 + nmReservoirParameterEditorType m_eEditorType; ///< 字段编辑器类型。 + bool m_bUnitEnabled; ///< 是否为该字段创建单位下拉框。 + QList m_listOptions; ///< 枚举字段的稳定选项。 +}; + +/** @brief 参数分组的界面描述。 */ +struct NM_SUB_WXS_EXPORT nmReservoirParameterGroupInfo +{ + QString m_sTitle; ///< 分组标题。 + int m_nRow; ///< 分组在页面网格中的行。 + int m_nColumn; ///< 分组在页面网格中的列。 + int m_nRowSpan; ///< 分组纵向跨越的行数。 + int m_nColumnSpan; ///< 分组横跨的列数。 + bool m_bValueRightAligned; ///< 数值输入是否右对齐。 + QList m_listFields; ///< 分组内按显示顺序排列的字段。 +}; + +/** @brief 单个可编辑对象的完整界面描述。 */ +struct NM_SUB_WXS_EXPORT nmReservoirPropertyObjectInfo +{ + nmReservoirPropertiesCategory m_eCategory; ///< 对象所属页面分类。 + nmReservoirPropertiesObjectType m_eObjectType; ///< 对象具体类型。 + int m_nSourceIndex; ///< 对象在工作副本容器中的索引。 + QString m_sObjectCode; ///< 编辑会话内稳定的对象编码。 + QString m_sDisplayName; ///< 对象显示名称。 + QList m_listGroups; ///< 原型式分组和字段描述。 +}; + +/** + * @brief 储层参数规范目录。 + * @note 对象映射、稳定ID、页面分组和联动所需的基础ID全部在此集中维护。 + */ +class NM_SUB_WXS_EXPORT nmReservoirParameterCatalog +{ +public: + /** + * @brief 将工作副本中的属性注册到独立注册表。 + * @param pRegistry 编辑会话独占的属性注册表。 + * @param oSnapshot 可写工作副本。 + */ + static void registerSnapshot(nmAttrRegistry* pRegistry, + nmReservoirPropertiesSnapshot& oSnapshot); + + /** + * @brief 构建指定分类的对象和原型式分组描述。 + * @param eCategory 页面分类。 + * @param oSnapshot 当前工作副本。 + * @return 按界面显示顺序排列的对象描述。 + */ + static QList createObjectInfos( + nmReservoirPropertiesCategory eCategory, + nmReservoirPropertiesSnapshot& oSnapshot); + + /** + * @brief 将数据类中的存储文本转换为稳定枚举code。 + * @param sParameterId 枚举参数ID。 + * @param sStoredValue 数据类中的存储文本。 + * @return 稳定业务code。 + */ + static QString enumCodeFromStoredValue(const QString& sParameterId, + const QString& sStoredValue); + + /** + * @brief 将稳定枚举code转换为数据类的存储文本。 + * @param sParameterId 枚举参数ID。 + * @param sCode 稳定业务code。 + * @return 数据类使用的存储文本。 + */ + static QString storedValueFromEnumCode(const QString& sParameterId, + const QString& sCode); + + /** @brief 读取工作副本中的枚举code。 */ + static QString enumCode(const nmReservoirPropertiesSnapshot& oSnapshot, + const QString& sParameterId); + + /** @brief 修改工作副本中的枚举code。 */ + static bool setEnumCode(nmReservoirPropertiesSnapshot& oSnapshot, + const QString& sParameterId, + const QString& sCode); + +private: + nmReservoirParameterCatalog(); +}; diff --git a/Include/nmNum/nmSubWxs/nmReservoirParameterField.h b/Include/nmNum/nmSubWxs/nmReservoirParameterField.h new file mode 100644 index 0000000..8e7a3fe --- /dev/null +++ b/Include/nmNum/nmSubWxs/nmReservoirParameterField.h @@ -0,0 +1,155 @@ +#pragma once + +#include "nmSubWxs_global.h" +#include "nmReservoirParameterCatalog.h" + +#include +#include + +class iParameter; +class iUnitGroup; +class QComboBox; +class QGridLayout; +class QLabel; +class QLineEdit; + +/** + * @brief 储层参数字段所需的最小单位服务接口。 + * @note 主对话框负责适配iDlgBase,字段不直接依赖对话框基类或全局对象。 + */ +class NM_SUB_WXS_EXPORT nmIReservoirParameterUnitService +{ +public: + /** @brief 析构单位服务接口。 */ + virtual ~nmIReservoirParameterUnitService() {} + + /** @brief 按NM参数定义绑定一个数值编辑器和单位下拉框。 */ + virtual bool bindUnitField(QLineEdit* pLineEdit, + QComboBox* pUnitComboBox, + const QString& sBaseParameterId) = 0; + + /** @brief 将基准单位数值写入指定编辑器。 */ + virtual void setUnitBaseValue(QLineEdit* pLineEdit, + double dBaseValue) = 0; + + /** @brief 读取指定编辑器的基准单位数值。 */ + virtual double unitBaseValue(QLineEdit* pLineEdit) const = 0; + + /** @brief 校验指定单位编辑器。 */ + virtual bool isUnitInputValid(QLineEdit* pLineEdit) = 0; + + /** @brief 返回指定编辑器当前显示单位。 */ + virtual QString currentUnit(QLineEdit* pLineEdit) const = 0; + + /** @brief 返回指定编辑器的单位组,不转移所有权。 */ + virtual iUnitGroup* unitGroup(QLineEdit* pLineEdit) const = 0; +}; + +/** + * @brief 原型式参数行的轻量逻辑单元。 + * @note 本类不创建外框或子面板,只把标签、编辑器和单位控件直接加入分组网格。 + */ +class NM_SUB_WXS_EXPORT nmReservoirParameterField : public QObject +{ +public: + /** + * @brief 构造参数字段。 + * @param oFieldInfo 稳定字段描述。 + * @param pUnitService 主对话框注入的单位服务,不转移所有权。 + * @param pParent 字段生命周期父对象。 + */ + nmReservoirParameterField( + const nmReservoirParameterFieldInfo& oFieldInfo, + nmIReservoirParameterUnitService* pUnitService, + QObject* pParent = nullptr); + + /** @brief 析构字段,不单独删除由分组控件拥有的界面控件。 */ + virtual ~nmReservoirParameterField(); + + /** + * @brief 将字段的三个直接控件加入分组网格。 + * @param pLayout 所属分组的网格布局。 + * @param nRow 字段所在行。 + * @param sError 初始化失败原因。 + * @return 参数元数据和单位绑定均成功时返回true。 + */ + bool initialize(QGridLayout* pLayout, int nRow, QString& sError); + + /** @brief 返回编辑会话使用的稳定参数ID。 */ + QString parameterId() const; + + /** @brief 返回XML参数定义使用的基础参数ID。 */ + QString baseParameterId() const; + + /** @brief 返回字段是否为稳定code枚举。 */ + bool isEnum() const; + + /** @brief 设置数值输入文字是否右对齐。 */ + void setValueRightAligned(bool bRightAligned); + + /** @brief 返回数值编辑列的统一最小宽度。 */ + static int editorMinimumWidth(); + + /** @brief 返回单位列的统一最小宽度。 */ + static int unitMinimumWidth(); + + /** @brief 返回输入框和下拉框的统一紧凑高度。 */ + static int editorHeight(); + + /** @brief 设置基准单位下的数值并刷新显示。 */ + void setBaseValue(const QVariant& oValue); + + /** + * @brief 获取基准单位下的当前数值。 + * @param bValid 输出当前输入是否有效。 + * @return 有效时返回基准值,否则返回无效QVariant。 + */ + QVariant baseValue(bool& bValid); + + /** @brief 设置枚举稳定code;不会触发业务联动。 */ + void setEnumCode(const QString& sCode); + + /** @brief 返回当前枚举稳定code。 */ + QString enumCode() const; + + /** @brief 校验当前输入,并更新参数范围提示。 */ + bool validate(); + + /** @brief 同时显示或隐藏该参数行的三个直接控件。 */ + void setFieldVisible(bool bVisible); + + /** @brief 返回参数行当前是否参与编辑和校验。 */ + bool isFieldVisible() const; + + /** @brief 返回数值输入框,供页面集中连接变化信号。 */ + QLineEdit* valueEditor() const; + + /** @brief 返回枚举下拉框,供页面集中连接变化信号。 */ + QComboBox* enumEditor() const; + +private: + /** @brief 创建数值输入框及单位控件。 */ + bool createNumericEditor(QGridLayout* pLayout, + int nRow, + QString& sError); + + /** @brief 创建稳定code枚举下拉框。 */ + void createEnumEditor(QGridLayout* pLayout, int nRow); + + /** @brief 按XML精度格式化无单位或整数参数。 */ + QString formatBaseValue(double dValue) const; + + /** @brief 更新输入框的有效状态和帮助文字。 */ + void updateValidationAppearance(bool bValid); + + nmReservoirParameterFieldInfo m_oFieldInfo; ///< 稳定字段描述。 + nmIReservoirParameterUnitService* m_pUnitService; ///< 注入的单位服务,不拥有所有权。 + iParameter* m_pParameter; ///< 共享XML参数定义,不拥有所有权。 + QLabel* m_pLabel; ///< 参数名称标签。 + QLineEdit* m_pLineEdit; ///< 数值输入框。 + QComboBox* m_pValueComboBox; ///< 枚举值下拉框。 + QComboBox* m_pUnitComboBox; ///< 可换算单位下拉框。 + QLabel* m_pUnitLabel; ///< 无单位字段的空白对齐占位。 + bool m_bVisible; ///< 参数行是否参与编辑。 + bool m_bUpdating; ///< 程序写值时阻止业务信号。 +}; diff --git a/Include/nmNum/nmSubWxs/nmReservoirParameterPage.h b/Include/nmNum/nmSubWxs/nmReservoirParameterPage.h new file mode 100644 index 0000000..10b80f8 --- /dev/null +++ b/Include/nmNum/nmSubWxs/nmReservoirParameterPage.h @@ -0,0 +1,128 @@ +#pragma once + +#include "nmSubWxs_global.h" +#include "nmReservoirParameterCatalog.h" + +#include +#include +#include + +class nmIReservoirParameterUnitService; +class nmReservoirParameterField; +class nmReservoirPropertiesSession; +class QComboBox; +class QGridLayout; +class QLabel; +class QVBoxLayout; + +/** + * @brief 储层参数分类的原型式共享页面。 + * @note 页面是普通QWidget,只负责一层内容布局,不拥有单位系统或真实数据。 + */ +class NM_SUB_WXS_EXPORT nmReservoirParameterPage : public QWidget +{ + Q_OBJECT + +public: + /** + * @brief 构造共享参数页面。 + * @param eCategory 页面分类。 + * @param pSession 编辑会话,不转移所有权。 + * @param pUnitService 主对话框单位服务,不转移所有权。 + * @param pParent 父窗口。 + */ + nmReservoirParameterPage(nmReservoirPropertiesCategory eCategory, + nmReservoirPropertiesSession* pSession, + nmIReservoirParameterUnitService* pUnitService, + QWidget* pParent = nullptr); + + /** @brief 析构参数页面。 */ + virtual ~nmReservoirParameterPage(); + + /** + * @brief 重新读取当前分类对象并显示第一个对象。 + * @param sError 输出字段构建错误。 + * @return 页面构建成功返回true。 + */ + bool reloadObjects(QString& sError); + + /** + * @brief 校验并将当前可见数值写入会话工作副本。 + * @return 输入全部有效且写入成功时返回true。 + */ + bool commitPendingEdits(); + + /** @brief 返回当前页面的可见输入是否全部有效。 */ + bool isInputValid(); + + /** @brief 返回当前分类是否存在可编辑对象。 */ + bool hasEditableObjects() const; + + /** @brief 返回页面分类。 */ + nmReservoirPropertiesCategory category() const; + +signals: + /** @brief 当前页有效状态发生变化。 */ + void sigValidityChanged(bool bValid); + +private slots: + /** @brief 切换当前编辑对象。 */ + void slotObjectChanged(int nIndex); + + /** @brief 数值输入变化后刷新整体有效状态。 */ + void slotFieldValueEdited(); + + /** @brief 枚举code变化后保存并执行集中联动。 */ + void slotEnumIndexChanged(int nIndex); + +private: + /** @brief 创建页标题、对象选择器和单层分组区域。 */ + void createUi(); + + /** @brief 根据当前分类返回标题。 */ + QString categoryTitle() const; + + /** @brief 根据当前分类返回对象选择标签。 */ + QString objectSelectorLabel() const; + + /** @brief 构建指定对象的全部分组和字段。 */ + bool buildObject(int nObjectIndex, QString& sError); + + /** @brief 删除上一个对象的分组控件和字段对象。 */ + void clearObjectEditors(); + + /** @brief 创建一个原型式参数分组。 */ + bool buildGroup(const nmReservoirParameterGroupInfo& oGroupInfo, + QString& sError); + + /** @brief 从工作副本加载字段初始值。 */ + void loadFieldValue(nmReservoirParameterField* pField); + + /** @brief 根据当前稳定code统一刷新字段可见性。 */ + void applyVisibilityRules(); + + /** @brief 设置指定稳定参数ID的字段可见性。 */ + void setFieldVisible(const QString& sParameterId, bool bVisible); + + /** @brief 刷新有效状态并通知主对话框。 */ + void updateValidity(); + + nmReservoirPropertiesCategory m_eCategory; ///< 页面分类。 + nmReservoirPropertiesSession* m_pSession; ///< 编辑会话,不拥有所有权。 + nmIReservoirParameterUnitService* m_pUnitService; ///< 主对话框单位服务,不拥有所有权。 + QList m_listObjectInfos; ///< 当前分类对象描述。 + int m_nCurrentObjectIndex; ///< 当前对象索引。 + bool m_bUpdatingObject; ///< 阻止对象下拉框回滚重入。 + bool m_bBuildingFields; ///< 阻止加载数据时触发业务信号。 + + QLabel* m_pTitleLabel; ///< 页面标题。 + QLabel* m_pObjectLabel; ///< 对象选择标签。 + QComboBox* m_pObjectComboBox; ///< 多对象选择下拉框。 + QWidget* m_pGroupsWidget; ///< 无边框的分组布局载体。 + QGridLayout* m_pGroupsLayout; ///< 两列分组布局。 + QVBoxLayout* m_pMainLayout; ///< 页面主布局。 + QLabel* m_pEmptyLabel; ///< 空对象提示。 + QList m_listFields; ///< 当前对象全部字段。 + QMap m_mapFields; ///< 稳定ID到字段映射。 + QMap m_mapEditorFields; ///< 编辑器到字段映射。 +}; diff --git a/Include/nmNum/nmSubWxs/nmReservoirPropertiesDataSource.h b/Include/nmNum/nmSubWxs/nmReservoirPropertiesDataSource.h new file mode 100644 index 0000000..02df385 --- /dev/null +++ b/Include/nmNum/nmSubWxs/nmReservoirPropertiesDataSource.h @@ -0,0 +1,92 @@ +#pragma once + +#include "nmSubWxs_global.h" + +#include "nmDataReservoir.h" +#include "nmDataVerticalWell.h" +#include "nmDataVerticalFracturedWell.h" +#include "nmDataHorizontalFracturedWell.h" +#include "nmDataRegionMark.h" +#include "nmDataFault.h" +#include "nmDataFracture.h" +#include "nmDataRegion.h" + +class nmDataAnalyzeManager; + +/** + * @brief 储层参数编辑快照。 + * @note 快照保存一次编辑会话所需的全部数据副本,不包含普通水平井。 + */ +struct NM_SUB_WXS_EXPORT nmReservoirPropertiesSnapshot +{ + /** @brief 构造空快照。 */ + nmReservoirPropertiesSnapshot(); + + nmDataReservoir m_oReservoir; ///< 储层参数副本。 + QVector m_vecVerticalWells; ///< 直井参数副本。 + QVector m_vecVerticalFracturedWells; ///< 垂直压裂井参数副本。 + QVector m_vecHorizontalFracturedWells; ///< 水平压裂井参数副本。 + QVector m_vecRegionMarks; ///< 区域标记参数副本。 + QVector m_vecFaults; ///< 断层参数副本。 + QVector m_vecFractures; ///< 裂缝参数副本,包含DFN以保持提交索引稳定。 + QVector m_vecRegions; ///< 复合区参数副本。 +}; + +/** + * @brief 储层参数数据源接口。 + * @note 对话框只依赖此接口,不直接访问全局数据管理器。 + */ +class NM_SUB_WXS_EXPORT nmIReservoirPropertiesDataSource +{ +public: + /** @brief 析构数据源接口。 */ + virtual ~nmIReservoirPropertiesDataSource(); + + /** + * @brief 读取当前分析窗口的数据快照。 + * @param oSnapshot 输出的工作快照。 + * @param sError 输出的错误信息。 + * @return 读取成功返回true。 + */ + virtual bool loadSnapshot(nmReservoirPropertiesSnapshot& oSnapshot, + QString& sError) const = 0; + + /** + * @brief 一次性提交编辑后的工作快照。 + * @param oSnapshot 待提交的工作快照。 + * @param bGeometryChanged 井坐标或几何参数是否发生变化。 + * @param sError 输出的错误信息。 + * @return 提交成功返回true。 + */ + virtual bool commitSnapshot(const nmReservoirPropertiesSnapshot& oSnapshot, + bool bGeometryChanged, + QString& sError) = 0; +}; + +/** + * @brief 基于nmDataAnalyzeManager的数据源适配器。 + */ +class NM_SUB_WXS_EXPORT nmAnalyzeManagerReservoirDataSource : + public nmIReservoirPropertiesDataSource +{ +public: + /** + * @brief 构造数据源适配器。 + * @param pDataManager 当前分析窗口的数据管理器,不转移所有权。 + */ + explicit nmAnalyzeManagerReservoirDataSource(nmDataAnalyzeManager* pDataManager); + + /** @brief 析构数据源适配器。 */ + virtual ~nmAnalyzeManagerReservoirDataSource(); + + virtual bool loadSnapshot(nmReservoirPropertiesSnapshot& oSnapshot, + QString& sError) const; + + virtual bool commitSnapshot(const nmReservoirPropertiesSnapshot& oSnapshot, + bool bGeometryChanged, + QString& sError); + +private: + nmDataAnalyzeManager* m_pDataManager; ///< 当前分析窗口的数据管理器,不拥有所有权。 +}; + diff --git a/Include/nmNum/nmSubWxs/nmReservoirPropertiesSession.h b/Include/nmNum/nmSubWxs/nmReservoirPropertiesSession.h new file mode 100644 index 0000000..fa93af9 --- /dev/null +++ b/Include/nmNum/nmSubWxs/nmReservoirPropertiesSession.h @@ -0,0 +1,98 @@ +#pragma once + +#include "nmSubWxs_global.h" +#include "nmReservoirPropertiesDataSource.h" +#include "nmReservoirParameterCatalog.h" + +#include +#include + +class nmAttrRegistry; + +/** + * @brief 储层参数编辑会话。 + * @note 会话持有独立工作副本和独立属性注册表;取消时直接销毁会话即可。 + */ +class NM_SUB_WXS_EXPORT nmReservoirPropertiesSession +{ +public: + /** + * @brief 构造编辑会话。 + * @param pDataSource 参数数据源,不转移所有权。 + */ + explicit nmReservoirPropertiesSession(nmIReservoirPropertiesDataSource* pDataSource); + + /** @brief 析构编辑会话及其独立注册表。 */ + ~nmReservoirPropertiesSession(); + + /** + * @brief 从数据源创建原始快照和工作副本。 + * @param sError 输出的错误信息。 + * @return 初始化成功返回true。 + */ + bool initialize(QString& sError); + + /** + * @brief 获取指定分类的对象描述。 + * @param eCategory 页面分类。 + * @return 对象描述列表。 + */ + QList objectInfos( + nmReservoirPropertiesCategory eCategory); + + /** + * @brief 获取工作副本中的参数值。 + * @param sParameterId 稳定参数ID。 + * @return 参数值;不存在时返回无效QVariant。 + */ + QVariant value(const QString& sParameterId) const; + + /** + * @brief 修改工作副本中的参数值。 + * @param sParameterId 稳定参数ID。 + * @param oValue 基准单位下的新值。 + * @return 参数存在并完成修改时返回true。 + */ + bool setValue(const QString& sParameterId, const QVariant& oValue); + + /** + * @brief 批量修改工作副本中的参数值。 + * @param mapValues 稳定参数ID到基准值的映射。 + */ + void setValues(const QMap& mapValues); + + /** + * @brief 获取枚举参数的稳定code。 + * @param sParameterId 稳定枚举参数ID。 + * @return 稳定业务code。 + */ + QString enumCode(const QString& sParameterId) const; + + /** + * @brief 修改枚举参数的稳定code。 + * @param sParameterId 稳定枚举参数ID。 + * @param sCode 稳定业务code。 + * @return 修改成功返回true。 + */ + bool setEnumCode(const QString& sParameterId, const QString& sCode); + + /** + * @brief 提交工作副本。 + * @param sError 输出的错误信息。 + * @return 提交成功返回true。 + */ + bool commit(QString& sError); + + /** @brief 获取只读工作副本,供后续导入预览或测试使用。 */ + const nmReservoirPropertiesSnapshot& workingSnapshot() const; + +private: + /** @brief 判断井坐标或几何参数是否发生变化。 */ + bool isGeometryChanged() const; + + nmIReservoirPropertiesDataSource* m_pDataSource; ///< 参数数据源,不拥有所有权。 + nmAttrRegistry* m_pAttrRegistry; ///< 编辑会话独占的属性注册表。 + nmReservoirPropertiesSnapshot m_oOriginalSnapshot; ///< 打开对话框时的原始快照。 + nmReservoirPropertiesSnapshot m_oWorkingSnapshot; ///< 所有界面编辑写入的工作副本。 + bool m_bInitialized; ///< 会话是否初始化成功。 +}; diff --git a/Include/nmNum/nmSubWxs/nmWxReservoirPropertiesEditor.h b/Include/nmNum/nmSubWxs/nmWxReservoirPropertiesEditor.h new file mode 100644 index 0000000..c76658d --- /dev/null +++ b/Include/nmNum/nmSubWxs/nmWxReservoirPropertiesEditor.h @@ -0,0 +1,148 @@ +#pragma once + +#include "iDlgBase.h" +#include "nmSubWxs_global.h" +#include "nmReservoirParameterCatalog.h" +#include "nmReservoirParameterField.h" + +class nmIReservoirPropertiesDataSource; +class nmReservoirPropertiesSession; +class nmReservoirParameterPage; +class QButtonGroup; +class QDialogButtonBox; +class QFrame; +class QHBoxLayout; +class QLabel; +class QStackedWidget; +class QToolButton; + +/** + * @brief 数值试井储层特性参数编辑器。 + * @note 对话框是唯一可见面板,负责原型式导航、单位服务、整体校验和统一提交。 + */ +class NM_SUB_WXS_EXPORT nmWxReservoirPropertiesEditor : + public iDlgBase, + public nmIReservoirParameterUnitService +{ + Q_OBJECT + +public: + /** + * @brief 构造储层特性参数编辑器。 + * @param pDataSource 参数数据源,不转移所有权。 + * @param pParent 父窗口。 + */ + nmWxReservoirPropertiesEditor( + nmIReservoirPropertiesDataSource* pDataSource, + QWidget* pParent = nullptr); + + /** @brief 析构编辑器。 */ + virtual ~nmWxReservoirPropertiesEditor(); + + /** + * @brief 初始化编辑会话和全部页面。 + * @param sError 输出初始化错误。 + * @return 初始化成功返回true。 + */ + bool initialize(QString& sError); + + /** @brief 通过iDlgBase按NM参数系列绑定单位控件。 */ + virtual bool bindUnitField(QLineEdit* pLineEdit, + QComboBox* pUnitComboBox, + const QString& sBaseParameterId); + + /** @brief 将基准单位数值写入字段。 */ + virtual void setUnitBaseValue(QLineEdit* pLineEdit, + double dBaseValue); + + /** @brief 读取字段的基准单位数值。 */ + virtual double unitBaseValue(QLineEdit* pLineEdit) const; + + /** @brief 校验单位字段输入。 */ + virtual bool isUnitInputValid(QLineEdit* pLineEdit); + + /** @brief 返回字段当前显示单位。 */ + virtual QString currentUnit(QLineEdit* pLineEdit) const; + + /** @brief 返回字段单位组,不转移所有权。 */ + virtual iUnitGroup* unitGroup(QLineEdit* pLineEdit) const; + +public slots: + /** @brief 校验并一次性提交工作副本。 */ + virtual void accept(); + + /** @brief 丢弃工作副本并关闭窗口。 */ + virtual void reject(); + +private slots: + /** @brief 切换顶部参数分类。 */ + void slotCategoryButtonClicked(int nCategory); + + /** @brief 根据各页面状态更新确定按钮。 */ + void slotPageValidityChanged(bool bValid); + + /** @brief 进入当前界面的上下文帮助模式。 */ + void slotHelp(); + +private: + /** @brief 创建编辑器的原型式固定布局。 */ + void createUi(); + + /** @brief 创建顶部分组导航。 */ + QWidget* createCategoryBar(); + + /** + * @brief 创建一个带底部分组标题的ribbon区域。 + * @param sCaption 分组标题。 + * @param pParent 父控件。 + * @param pCommandLayout 输出的命令按钮布局,不转移所有权。 + * @return 创建完成的分组控件。 + */ + QWidget* createRibbonGroup(const QString& sCaption, + QWidget* pParent, + QHBoxLayout*& pCommandLayout); + + /** + * @brief 创建框架风格的导航分组竖向分隔线。 + * @param pParent 分隔线所属的顶部导航面板。 + * @return 创建完成的Qt原生分隔线。 + */ + QFrame* createRibbonSeparator(QWidget* pParent); + + /** + * @brief 创建一个参数分类按钮。 + * @param eCategory 分类枚举。 + * @param sText 按钮文字。 + * @param sIconName 框架图标名称。 + * @param pParent 所属ribbon分组。 + * @return 创建完成的工具按钮。 + */ + QToolButton* createCategoryButton( + nmReservoirPropertiesCategory eCategory, + const QString& sText, + const QString& sIconName, + QWidget* pParent); + + /** @brief 创建帮助命令按钮。 */ + QToolButton* createHelpButton(QWidget* pParent); + + /** @brief 创建六个共享参数页面。 */ + void createPages(); + + /** @brief 根据各分类的实际对象数量更新顶部按钮启用状态。 */ + void updateCategoryButtonStates(); + + /** @brief 校验所有页面并更新确定按钮。 */ + bool validateAllPages(); + + nmIReservoirPropertiesDataSource* m_pDataSource; ///< 参数数据源,不拥有所有权。 + nmReservoirPropertiesSession* m_pSession; ///< 对话框独占编辑会话。 + QButtonGroup* m_pCategoryButtonGroup; ///< 顶部分类按钮及稳定页面ID映射。 + QStackedWidget* m_pStackedWidget; ///< 无边框分类页面堆栈。 + QDialogButtonBox* m_pButtonBox; ///< 确定和取消按钮区。 + QToolButton* m_pFooterHelpButton; ///< 底部帮助命令。 + QLabel* m_pStatusLabel; ///< 校验或提交错误提示。 + nmReservoirParameterPage* m_pPages[NM_RESERVOIR_PAGE_COUNT]; ///< 六个参数页。 + int m_nCurrentCategory; ///< 当前显示分类。 + bool m_bInitialized; ///< 编辑器是否初始化完成。 +}; diff --git a/Src/nmNum/nmData/nmDataAutomaticFitting.cpp b/Src/nmNum/nmData/nmDataAutomaticFitting.cpp index a4876ed..3af9c33 100644 --- a/Src/nmNum/nmData/nmDataAutomaticFitting.cpp +++ b/Src/nmNum/nmData/nmDataAutomaticFitting.cpp @@ -13,7 +13,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting() m_swiSelected = false; // 默认不选中 // 初始化参数最大值 - m_permeabilityMax = nmDataAttribute("Permeability Max", 10.0, "Darcy"); // 1000 mD + m_permeabilityMax = nmDataAttribute("Permeability Max", 10.0, "D"); m_skinMax = nmDataAttribute("Skin Max", 10.0, ""); // 100 m_wellboreStorageMax = nmDataAttribute("Wellbore Storage Max", 2.0, "m^3/MPa"); m_porosityMax = nmDataAttribute("Porosity Max", 0.5, ""); // 50% @@ -23,7 +23,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting() m_swiMax = nmDataAttribute("Swi Max", 1.0, ""); // 初始化参数最小值 - m_permeabilityMin = nmDataAttribute("Permeability Min", 0.001, "Darcy"); // 0.001 mD + m_permeabilityMin = nmDataAttribute("Permeability Min", 0.001, "D"); m_skinMin = nmDataAttribute("Skin Min", -10.0, ""); // 允许负表皮 m_wellboreStorageMin = nmDataAttribute("Wellbore Storage Min", 1e-4, "m^3/MPa"); m_porosityMin = nmDataAttribute("Porosity Min", 0.01, ""); // 1% @@ -164,6 +164,8 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue) // 反序列化参数最大值 if (jsonValue.HasMember("PermeabilityMax") && jsonValue["PermeabilityMax"].IsObject()) { m_permeabilityMax.FromJsonValue(jsonValue["PermeabilityMax"]); + // 自动拟合上下限与求解器使用同一基准单位,不按旧JSON单位文本换算。 + m_permeabilityMax.setUnit("D"); } if (jsonValue.HasMember("SkinMax") && jsonValue["SkinMax"].IsObject()) { m_skinMax.FromJsonValue(jsonValue["SkinMax"]); @@ -190,6 +192,7 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue) // 反序列化参数最小值 if (jsonValue.HasMember("PermeabilityMin") && jsonValue["PermeabilityMin"].IsObject()) { m_permeabilityMin.FromJsonValue(jsonValue["PermeabilityMin"]); + m_permeabilityMin.setUnit("D"); } if (jsonValue.HasMember("SkinMin") && jsonValue["SkinMin"].IsObject()) { m_skinMin.FromJsonValue(jsonValue["SkinMin"]); diff --git a/Src/nmNum/nmData/nmDataReservoir.cpp b/Src/nmNum/nmData/nmDataReservoir.cpp index e07e007..0122f55 100644 --- a/Src/nmNum/nmData/nmDataReservoir.cpp +++ b/Src/nmNum/nmData/nmDataReservoir.cpp @@ -1,17 +1,18 @@ -#include "nmDataReservoir.h" +#include "nmDataReservoir.h" #include "ZxSerializer.h" nmDataReservoir::nmDataReservoir() { // 基础属性 m_initialPressure = nmDataAttribute("Initial Pressure",40.0, "MPa", UNIT_TYPE_PRESSURE, QStringList(), QStringList() << "psia" << "Pa" << "kPa" << "atm" << "bara" << "kg/cm^2" << "m" << "psig" << "bar" << "MPa" << "kPag"); - m_permeability = nmDataAttribute("Permeability", 0.025, "Darcy", UNIT_TYPE_PERMEABILITY, QStringList(), QStringList() << "md" << "Darcy" << "m^2" << "cm^2" << "um^2"); + // 求解器直接读取该数值,内部基准单位固定为D;显示换算由框架单位绑定器负责。 + m_permeability = nmDataAttribute("Permeability", 0.025, "D"); m_thickness = nmDataAttribute("Thickness", 10.0, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "ft" << "m" << "cm" << "mm" << "in" << "0.1 in" << "mile" << "km"); m_porosity = nmDataAttribute("Porosity", 0.1, "", UNIT_TYPE_DIMENSIONLESS, QStringList(), QStringList()); m_kxKy = nmDataAttribute("Kx/Ky", 1.0, "", UNIT_TYPE_DIMENSIONLESS, QStringList(), QStringList()); // 油相PVT单值 m_Bo = nmDataAttribute("Bo", 1.2, ""); - m_Miuo = nmDataAttribute("Miuo", 0.5, "", UNIT_TYPE_DIMENSIONLESS, QStringList(), QStringList()); + m_Miuo = nmDataAttribute("Miuo", 0.5, "mPa.s"); // 气相PVT单值 m_Bg = nmDataAttribute("Bg", 1.0, "", UNIT_TYPE_DIMENSIONLESS, QStringList(), QStringList()); @@ -22,8 +23,8 @@ nmDataReservoir::nmDataReservoir() { m_Miuw = nmDataAttribute("Miuw", 1.0, "", UNIT_TYPE_DIMENSIONLESS, QStringList(), QStringList()); // 压缩系数 - m_Ct = nmDataAttribute("Ct", 0.001, "", UNIT_TYPE_DIMENSIONLESS, QStringList(), QStringList()); - m_Cf = nmDataAttribute("Cf", 0.0001, "", UNIT_TYPE_DIMENSIONLESS, QStringList(), QStringList()); + m_Ct = nmDataAttribute("Ct", 0.001, "1/MPa"); + m_Cf = nmDataAttribute("Cf", 0.0001, "1/MPa"); // 储层类型与传导率 m_reservoirType = nmDataAttribute("Reservoir type", "Homogeneous", "", UNIT_TYPE_DIMENSIONLESS, QStringList() << "Homogeneous" << "Dual porosity pseudo steady state", QStringList()); @@ -147,6 +148,8 @@ void nmDataReservoir::FromJsonValue(const rapidjson::Value& jsonValue) } if (jsonValue.HasMember("Permeability") && jsonValue["Permeability"].IsObject()) { m_permeability.FromJsonValue(jsonValue["Permeability"]); + // JSON中的单位文本不参与兼容换算,读取后的内部单位始终按求解器标准固定为D。 + m_permeability.setUnit("D"); } if (jsonValue.HasMember("Thickness") && jsonValue["Thickness"].IsObject()) { m_thickness.FromJsonValue(jsonValue["Thickness"]); @@ -240,11 +243,11 @@ bool nmDataReservoir::_parseData(VecVariant vec, int& n) { m_initialPressure = nmDataAttribute("Initial Pressure", vec[n++].toString(), "MPa"); m_reservoirType = nmDataAttribute("Reservoir Type", vec[n++].toString(), ""); m_Bo = nmDataAttribute("Bo", vec[n++].toString(), ""); - m_Miuo = nmDataAttribute("Miuo", vec[n++].toString(), "mPa.s(cp)"); - m_permeability = nmDataAttribute("Permeability", vec[n++].toString(), "mD"); + m_Miuo = nmDataAttribute("Miuo", vec[n++].toString(), "mPa.s"); + m_permeability = nmDataAttribute("Permeability", vec[n++].toString(), "D"); m_thickness = nmDataAttribute("Thickness", vec[n++].toString(), "m"); m_porosity = nmDataAttribute("Porosity", vec[n++].toString(), ""); - m_Ct = nmDataAttribute("Ct", vec[n++].toString(), ""); + m_Ct = nmDataAttribute("Ct", vec[n++].toString(), "1/MPa"); m_kxKy = nmDataAttribute("Kx/Ky", vec[n++].toString(), ""); return true; } @@ -280,11 +283,12 @@ void nmDataReservoir::onDeserialize(ZxSerializer* ser) { ser->read("InitialPressure", tempValue); m_initialPressure = nmDataAttribute("Initial Pressure", tempValue, "MPa"); ser->read("ReservoirType", tempValue); m_reservoirType = nmDataAttribute("Reservoir Type", tempValue, ""); ser->read("Bo", tempValue); m_Bo = nmDataAttribute("Bo", tempValue, ""); - ser->read("Miuo", tempValue); m_Miuo = nmDataAttribute("Miuo", tempValue, "mPa.s(cp)"); - ser->read("Permeability", tempValue); m_permeability = nmDataAttribute("Permeability", tempValue, "mD"); + ser->read("Miuo", tempValue); m_Miuo = nmDataAttribute("Miuo", tempValue, "mPa.s"); + // 序列化格式只保存数值,读取时明确按求解器基准单位D解释。 + ser->read("Permeability", tempValue); m_permeability = nmDataAttribute("Permeability", tempValue, "D"); ser->read("Thickness", tempValue); m_thickness = nmDataAttribute("Thickness", tempValue, "m"); ser->read("Porosity", tempValue); m_porosity = nmDataAttribute("Porosity", tempValue, ""); - ser->read("NetToGross", tempValue); m_Ct = nmDataAttribute("Ct", tempValue, ""); + ser->read("Ct", tempValue); m_Ct = nmDataAttribute("Ct", tempValue, "1/MPa"); ser->read("KxKy", tempValue); m_kxKy = nmDataAttribute("Kx/Ky", tempValue, ""); } diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp index 1f2b236..c955245 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp @@ -56,7 +56,8 @@ #include "nmWxRCDialog.h" #include "nmWxWellLayerDlg.h" #include "nmWxGeometryLayerDlg.h" -#include "nmWxReservoirPropertiesDlg.h" +#include "nmReservoirPropertiesDataSource.h" +#include "nmWxReservoirPropertiesEditor.h" #include "nmWxPropertyInterpolationDlg.h" #include "nmWxPostprocessingAnimationWidget.h" #include "nmWxGeoRefDlg.h" @@ -1961,10 +1962,29 @@ bool nmSubWndMain::onConfirmClosing() void nmSubWndMain::reservoirCharacteristics() { - nmWxReservoirPropertiesDlg dlg; - dlg.exec(); + // 第一步:取得当前分析窗口对应的数据管理器,避免新对话框访问全局单例。 + nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getCurrentInstance(); + if (pDataManager == nullptr) + { + QMessageBox::warning(this, tr("Reservoir properties"), + tr("The current numerical well-test data is unavailable.")); + return; + } + + // 第二步:在入口处组装数据源和编辑器,两个对象的生命周期覆盖整个模态会话。 + nmAnalyzeManagerReservoirDataSource oDataSource(pDataManager); + nmWxReservoirPropertiesEditor oEditor(&oDataSource, this); + + // 第三步:先建立工作副本;初始化失败时不打开不完整的参数界面。 + QString sError; + if (!oEditor.initialize(sError)) + { + QMessageBox::warning(this, tr("Reservoir properties"), sError); + return; + } - // TODO: 修改完参数应该刷新画布 + // 第四步:确定时由编辑会话一次性提交,取消时真实数据保持不变。 + oEditor.exec(); } void nmSubWndMain::geoLayering() diff --git a/Src/nmNum/nmSubWxs/nmReservoirParameterCatalog.cpp b/Src/nmNum/nmSubWxs/nmReservoirParameterCatalog.cpp new file mode 100644 index 0000000..4115645 --- /dev/null +++ b/Src/nmNum/nmSubWxs/nmReservoirParameterCatalog.cpp @@ -0,0 +1,852 @@ +#include "nmReservoirParameterCatalog.h" + +#include "nmReservoirPropertiesDataSource.h" +#include "nmAttrRegistry.h" +#include "nmDataAttribute.h" +#include "nmDataPerforation.h" + +#include + +namespace +{ + +/* + * Qt 4.8的lupdate无法跟踪editorText()的间接参数,以下标记只用于 + * 收集中英文资源;运行时仍由同一翻译上下文返回文字。 + */ +static const char* const s_pCatalogTranslationMarkers[] = +{ + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Reservoir type"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Homogeneous"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Dual porosity pseudo steady state"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Fault flow model"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Region flow model"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Leaky"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Composite limit"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Fracture flow model"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Finite conductivity"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Infinite conductivity"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Position and wellbore"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Storage and fracture"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Basic properties"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Fluid parameters"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Compression and saturation"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Reservoir parameters"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Vertical well"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Vertical fractured well"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Horizontal fractured well"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Dual-medium parameters"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Geometry"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Flow properties"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Conductivity properties"), + QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Region properties") +}; + +/** @brief 返回当前语言下的界面文字。 */ +QString editorText(const char* pText) +{ + Q_UNUSED(s_pCatalogTranslationMarkers); + return QCoreApplication::translate("nmWxReservoirPropertiesEditor", pText); +} + +/** @brief 创建一个稳定枚举选项。 */ +nmReservoirEnumOption enumOption(const QString& sCode, const char* pText) +{ + nmReservoirEnumOption oOption; + oOption.m_sCode = sCode; + oOption.m_sText = editorText(pText); + return oOption; +} + +/** @brief 创建一个数值字段描述。 */ +nmReservoirParameterFieldInfo numericField( + const QString& sParameterId, + const QString& sBaseParameterId, + nmReservoirParameterEditorType eEditorType) +{ + nmReservoirParameterFieldInfo oField; + oField.m_sParameterId = sParameterId; + oField.m_sBaseParameterId = sBaseParameterId; + oField.m_eEditorType = eEditorType; + oField.m_bUnitEnabled = true; + return oField; +} + +/** @brief 创建一个浮点数字段描述。 */ +nmReservoirParameterFieldInfo numericField( + const QString& sParameterId, + const QString& sBaseParameterId) +{ + return numericField(sParameterId, sBaseParameterId, + NM_RESERVOIR_EDITOR_NUMBER); +} + +/** @brief 创建一个明确不显示单位的浮点数字段。 */ +nmReservoirParameterFieldInfo unitlessNumericField( + const QString& sParameterId, + const QString& sBaseParameterId) +{ + nmReservoirParameterFieldInfo oField = + numericField(sParameterId, sBaseParameterId); + oField.m_bUnitEnabled = false; + return oField; +} + +/** @brief 创建储层类型字段。 */ +nmReservoirParameterFieldInfo reservoirModelField( + const QString& sParameterId) +{ + nmReservoirParameterFieldInfo oField; + oField.m_sParameterId = sParameterId; + oField.m_sLabel = editorText("Reservoir type"); + oField.m_eEditorType = NM_RESERVOIR_EDITOR_ENUM; + oField.m_bUnitEnabled = false; + oField.m_listOptions.append(enumOption("homogeneous", "Homogeneous")); + oField.m_listOptions.append(enumOption( + "dual_porosity_pss", "Dual porosity pseudo steady state")); + return oField; +} + +/** @brief 创建断层或复合区流动模型字段。 */ +nmReservoirParameterFieldInfo leakageModelField( + const QString& sParameterId, + const char* pLabel) +{ + nmReservoirParameterFieldInfo oField; + oField.m_sParameterId = sParameterId; + oField.m_sLabel = editorText(pLabel); + oField.m_eEditorType = NM_RESERVOIR_EDITOR_ENUM; + oField.m_bUnitEnabled = false; + oField.m_listOptions.append(enumOption("leaky", "Leaky")); + oField.m_listOptions.append(enumOption( + "composite_limit", "Composite limit")); + return oField; +} + +/** @brief 创建裂缝导流模型字段。 */ +nmReservoirParameterFieldInfo fractureModelField( + const QString& sParameterId) +{ + nmReservoirParameterFieldInfo oField; + oField.m_sParameterId = sParameterId; + oField.m_sLabel = editorText("Fracture flow model"); + oField.m_eEditorType = NM_RESERVOIR_EDITOR_ENUM; + oField.m_bUnitEnabled = false; + oField.m_listOptions.append(enumOption( + "finite_conductivity", "Finite conductivity")); + oField.m_listOptions.append(enumOption( + "infinite_conductivity", "Infinite conductivity")); + return oField; +} + +/** @brief 创建页面上的一个参数分组。 */ +nmReservoirParameterGroupInfo parameterGroup( + const char* pTitle, + int nRow, + int nColumn, + int nColumnSpan) +{ + nmReservoirParameterGroupInfo oGroup; + oGroup.m_sTitle = editorText(pTitle); + oGroup.m_nRow = nRow; + oGroup.m_nColumn = nColumn; + oGroup.m_nRowSpan = 1; + oGroup.m_nColumnSpan = nColumnSpan; + oGroup.m_bValueRightAligned = false; + return oGroup; +} + +/** @brief 为参数ID添加对象编码前缀。 */ +QString prefixed(const QString& sObjectCode, const char* pParameterId) +{ + return sObjectCode + "_" + QString::fromLatin1(pParameterId); +} + +/** @brief 生成无井编码时的会话编码。 */ +QString wellCode(const nmDataWellBase& oWell, + const QString& sFallbackPrefix, + int nIndex) +{ + QString sCode = oWell.getWellCode(); + if (sCode.isEmpty()) + { + sCode = QString("%1%2").arg(sFallbackPrefix) + .arg(nIndex + 1, 4, 10, QChar('0')); + } + return sCode; +} + +/** @brief 创建井对象的分组描述。 */ +void createWellGroups(nmReservoirPropertyObjectInfo& oInfo, + NM_WELL_MODEL eWellType, + bool bHasPerforation) +{ + nmReservoirParameterGroupInfo oPosition = + parameterGroup("Position and wellbore", 0, 0, 1); + oPosition.m_listFields + << numericField(prefixed(oInfo.m_sObjectCode, "W_X"), "W_X") + << numericField(prefixed(oInfo.m_sObjectCode, "W_Y"), "W_Y") + << numericField(prefixed(oInfo.m_sObjectCode, "W_Rw"), "W_Rw"); + if (bHasPerforation) + { + oPosition.m_listFields.append(numericField( + prefixed(oInfo.m_sObjectCode, "W_Skin"), "W_Skin")); + } + + nmReservoirParameterGroupInfo oStorage = + parameterGroup("Storage and fracture", 0, 1, 1); + oStorage.m_listFields.append(numericField( + prefixed(oInfo.m_sObjectCode, "W_C"), "W_C")); + if (eWellType == Vertical_Fractured_Well) + { + oStorage.m_listFields + << numericField(prefixed(oInfo.m_sObjectCode, + "W_FractureHalfLength"), + "W_FractureHalfLength") + << numericField(prefixed(oInfo.m_sObjectCode, + "W_FractureAngle"), + "W_FractureAngle") + << numericField(prefixed(oInfo.m_sObjectCode, + "W_Dfc"), "W_Dfc"); + } + else if (eWellType == Horizontal_Fractured_Well) + { + oStorage.m_listFields + << numericField(prefixed(oInfo.m_sObjectCode, + "W_WellLength"), + "W_WellLength") + << numericField(prefixed(oInfo.m_sObjectCode, + "W_DrainAngle"), + "W_DrainAngle") + << numericField(prefixed(oInfo.m_sObjectCode, + "W_NumberOfFractures"), + "W_NumberOfFractures", + NM_RESERVOIR_EDITOR_INTEGER) + << numericField(prefixed(oInfo.m_sObjectCode, + "W_FractureHalfLength"), + "W_FractureHalfLength") + << numericField(prefixed(oInfo.m_sObjectCode, + "W_FractureAngle"), + "W_FractureAngle") + << numericField(prefixed(oInfo.m_sObjectCode, + "W_Dfc"), "W_Dfc"); + } + oInfo.m_listGroups << oPosition << oStorage; +} + +/** @brief 注册一口井的全部可编辑数值属性。 */ +void registerWell(nmAttrRegistry* pRegistry, + nmDataWellBase& oWell, + const QString& sCode) +{ + pRegistry->regAttr(prefixed(sCode, "W_X"), &oWell.getX()); + pRegistry->regAttr(prefixed(sCode, "W_Y"), &oWell.getY()); + pRegistry->regAttr(prefixed(sCode, "W_Rw"), &oWell.getRadius()); + pRegistry->regAttr(prefixed(sCode, "W_C"), &oWell.getWellboreStorage()); + if (oWell.getPerforationCount() > 0 && + oWell.getPerforation(0) != nullptr) + { + pRegistry->regAttr(prefixed(sCode, "W_Skin"), + &oWell.getPerforation(0)->getSkin()); + } +} + +/** @brief 判断对象描述中是否包含指定枚举字段。 */ +bool containsEnumField(const nmReservoirPropertyObjectInfo& oInfo, + const QString& sParameterId) +{ + for (int nGroupIndex = 0; + nGroupIndex < oInfo.m_listGroups.size(); + ++nGroupIndex) + { + const nmReservoirParameterGroupInfo& oGroup = + oInfo.m_listGroups[nGroupIndex]; + for (int nFieldIndex = 0; + nFieldIndex < oGroup.m_listFields.size(); + ++nFieldIndex) + { + const nmReservoirParameterFieldInfo& oField = + oGroup.m_listFields[nFieldIndex]; + if (oField.m_eEditorType == NM_RESERVOIR_EDITOR_ENUM && + oField.m_sParameterId == sParameterId) + { + return true; + } + } + } + return false; +} + +} + +void nmReservoirParameterCatalog::registerSnapshot( + nmAttrRegistry* pRegistry, + nmReservoirPropertiesSnapshot& oSnapshot) +{ + if (pRegistry == nullptr) + { + return; + } + + // 第一步:重新建立编辑会话独占的注册表,杜绝与实时数据交叉连接。 + pRegistry->clear(); + pRegistry->regAttr("h", &oSnapshot.m_oReservoir.getThickness()); + pRegistry->regAttr("Pi", &oSnapshot.m_oReservoir.getInitialPressure()); + pRegistry->regAttr("K", &oSnapshot.m_oReservoir.getPermeability()); + pRegistry->regAttr("phi", &oSnapshot.m_oReservoir.getPorosity()); + pRegistry->regAttr("Cti", &oSnapshot.m_oReservoir.getCt()); + pRegistry->regAttr("Cf", &oSnapshot.m_oReservoir.getCf()); + pRegistry->regAttr("Soi", &oSnapshot.m_oReservoir.getSoi()); + pRegistry->regAttr("Swi", &oSnapshot.m_oReservoir.getSwi()); + pRegistry->regAttr("Bo", &oSnapshot.m_oReservoir.getBo()); + pRegistry->regAttr("Miuo", &oSnapshot.m_oReservoir.getMiuo()); + pRegistry->regAttr("KxKy", &oSnapshot.m_oReservoir.getKxKy()); + + // 第二步:注册求解器支持的三类井及其压裂参数。 + for (int nIndex = 0; + nIndex < oSnapshot.m_vecVerticalWells.size(); + ++nIndex) + { + nmDataVerticalWell& oWell = oSnapshot.m_vecVerticalWells[nIndex]; + registerWell(pRegistry, oWell, wellCode(oWell, "VWELL", nIndex)); + } + for (int nIndex = 0; + nIndex < oSnapshot.m_vecVerticalFracturedWells.size(); + ++nIndex) + { + nmDataVerticalFracturedWell& oWell = + oSnapshot.m_vecVerticalFracturedWells[nIndex]; + QString sCode = wellCode(oWell, "VFWELL", nIndex); + registerWell(pRegistry, oWell, sCode); + pRegistry->regAttr(prefixed(sCode, "W_FractureHalfLength"), + &oWell.getFractureHalfLength()); + pRegistry->regAttr(prefixed(sCode, "W_FractureAngle"), + &oWell.getFractureAngle()); + pRegistry->regAttr(prefixed(sCode, "W_Dfc"), &oWell.getDfc()); + } + for (int nIndex = 0; + nIndex < oSnapshot.m_vecHorizontalFracturedWells.size(); + ++nIndex) + { + nmDataHorizontalFracturedWell& oWell = + oSnapshot.m_vecHorizontalFracturedWells[nIndex]; + QString sCode = wellCode(oWell, "HFWELL", nIndex); + registerWell(pRegistry, oWell, sCode); + pRegistry->regAttr(prefixed(sCode, "W_WellLength"), + &oWell.getWellLength()); + pRegistry->regAttr(prefixed(sCode, "W_DrainAngle"), + &oWell.getDrainAngle()); + pRegistry->regAttr(prefixed(sCode, "W_NumberOfFractures"), + &oWell.getNumberOfFractures()); + pRegistry->regAttr(prefixed(sCode, "W_FractureHalfLength"), + &oWell.getFractureHalfLength()); + pRegistry->regAttr(prefixed(sCode, "W_FractureAngle"), + &oWell.getFractureAngle()); + pRegistry->regAttr(prefixed(sCode, "W_Dfc"), &oWell.getDfc()); + } + + // 第三步:注册区域标记和复合区数值参数。 + for (int nIndex = 0; + nIndex < oSnapshot.m_vecRegionMarks.size(); + ++nIndex) + { + QString sCode = QString("RMARK%1") + .arg(nIndex + 1, 4, 10, QChar('0')); + nmDataRegionMark& oMark = oSnapshot.m_vecRegionMarks[nIndex]; + pRegistry->regAttr(prefixed(sCode, "RM_ComW"), &oMark.getComW()); + pRegistry->regAttr(prefixed(sCode, "RM_ComKr"), &oMark.getComKr()); + pRegistry->regAttr(prefixed(sCode, "RM_NetToGross"), + &oMark.getNetToGross()); + } + for (int nIndex = 0; + nIndex < oSnapshot.m_vecRegions.size(); + ++nIndex) + { + QString sCode = QString("REGION%1") + .arg(nIndex + 1, 4, 10, QChar('0')); + pRegistry->regAttr(prefixed(sCode, "R_Leakage"), + &oSnapshot.m_vecRegions[nIndex] + .getRegionLeakage()); + } + + // 第四步:注册断层和普通裂缝;DFN由专用功能维护。 + for (int nIndex = 0; + nIndex < oSnapshot.m_vecFaults.size(); + ++nIndex) + { + QString sCode = QString("FAULT%1") + .arg(nIndex + 1, 4, 10, QChar('0')); + nmDataFault& oFault = oSnapshot.m_vecFaults[nIndex]; + pRegistry->regAttr(prefixed(sCode, "FT_X0"), &oFault.getStartX()); + pRegistry->regAttr(prefixed(sCode, "FT_Y0"), &oFault.getStartY()); + pRegistry->regAttr(prefixed(sCode, "FT_X1"), &oFault.getEndX()); + pRegistry->regAttr(prefixed(sCode, "FT_Y1"), &oFault.getEndY()); + pRegistry->regAttr(prefixed(sCode, "FT_Leakage"), + &oFault.getFaultLeakage()); + } + + int nVisibleFracture = 0; + for (int nIndex = 0; + nIndex < oSnapshot.m_vecFractures.size(); + ++nIndex) + { + nmDataFracture& oFracture = oSnapshot.m_vecFractures[nIndex]; + if (oFracture.getFractureType().getValue().toString() == "DFN") + { + continue; + } + QString sCode = QString("FRAC%1") + .arg(++nVisibleFracture, 4, 10, QChar('0')); + pRegistry->regAttr(prefixed(sCode, "F_X0"), + &oFracture.getStartX()); + pRegistry->regAttr(prefixed(sCode, "F_Y0"), + &oFracture.getStartY()); + pRegistry->regAttr(prefixed(sCode, "F_X1"), + &oFracture.getEndX()); + pRegistry->regAttr(prefixed(sCode, "F_Y1"), + &oFracture.getEndY()); + pRegistry->regAttr(prefixed(sCode, "F_FC"), + &oFracture.getFractureDfc()); + pRegistry->regAttr(prefixed(sCode, "F_DW"), + &oFracture.getFractureDw()); + } +} + +QList +nmReservoirParameterCatalog::createObjectInfos( + nmReservoirPropertiesCategory eCategory, + nmReservoirPropertiesSnapshot& oSnapshot) +{ + QList listInfos; + + if (eCategory == NM_RESERVOIR_PAGE_RESERVOIR) + { + nmReservoirPropertyObjectInfo oInfo; + oInfo.m_eCategory = eCategory; + oInfo.m_eObjectType = NM_RESERVOIR_OBJECT_RESERVOIR; + oInfo.m_nSourceIndex = 0; + oInfo.m_sObjectCode = "RESERVOIR"; + oInfo.m_sDisplayName = editorText("Reservoir parameters"); + + nmReservoirParameterGroupInfo oBasic = + parameterGroup("Basic properties", 0, 0, 1); + oBasic.m_nRowSpan = 2; + oBasic.m_bValueRightAligned = true; + oBasic.m_listFields + << numericField("Pi", "Pi") + << numericField("K", "K") + << numericField("h", "h") + << numericField("phi", "phi"); + + nmReservoirParameterGroupInfo oFluid = + parameterGroup("Fluid parameters", 0, 1, 1); + oFluid.m_listFields + << reservoirModelField("ReservoirType") + << numericField("Miuo", "Miuo") + << unitlessNumericField("Bo", "Bo") + << numericField("KxKy", "KxKy"); + + nmReservoirParameterGroupInfo oCompression = + parameterGroup("Compression and saturation", 1, 1, 1); + oCompression.m_listFields + << numericField("Cti", "Cti") + << numericField("Cf", "Cf") + << numericField("Soi", "Soi") + << numericField("Swi", "Swi"); + oInfo.m_listGroups << oBasic << oFluid << oCompression; + listInfos.append(oInfo); + return listInfos; + } + + if (eCategory == NM_RESERVOIR_PAGE_WELL) + { + for (int nIndex = 0; + nIndex < oSnapshot.m_vecVerticalWells.size(); + ++nIndex) + { + nmDataVerticalWell& oWell = + oSnapshot.m_vecVerticalWells[nIndex]; + nmReservoirPropertyObjectInfo oInfo; + oInfo.m_eCategory = eCategory; + oInfo.m_eObjectType = NM_RESERVOIR_OBJECT_VERTICAL_WELL; + oInfo.m_nSourceIndex = nIndex; + oInfo.m_sObjectCode = wellCode(oWell, "VWELL", nIndex); + oInfo.m_sDisplayName = oWell.getWellName() + " - " + + editorText("Vertical well"); + createWellGroups(oInfo, Vertical_Well, + oWell.getPerforationCount() > 0); + listInfos.append(oInfo); + } + for (int nIndex = 0; + nIndex < oSnapshot.m_vecVerticalFracturedWells.size(); + ++nIndex) + { + nmDataVerticalFracturedWell& oWell = + oSnapshot.m_vecVerticalFracturedWells[nIndex]; + nmReservoirPropertyObjectInfo oInfo; + oInfo.m_eCategory = eCategory; + oInfo.m_eObjectType = + NM_RESERVOIR_OBJECT_VERTICAL_FRAC_WELL; + oInfo.m_nSourceIndex = nIndex; + oInfo.m_sObjectCode = wellCode(oWell, "VFWELL", nIndex); + oInfo.m_sDisplayName = oWell.getWellName() + " - " + + editorText("Vertical fractured well"); + createWellGroups(oInfo, Vertical_Fractured_Well, + oWell.getPerforationCount() > 0); + listInfos.append(oInfo); + } + for (int nIndex = 0; + nIndex < oSnapshot.m_vecHorizontalFracturedWells.size(); + ++nIndex) + { + nmDataHorizontalFracturedWell& oWell = + oSnapshot.m_vecHorizontalFracturedWells[nIndex]; + nmReservoirPropertyObjectInfo oInfo; + oInfo.m_eCategory = eCategory; + oInfo.m_eObjectType = + NM_RESERVOIR_OBJECT_HORIZONTAL_FRAC_WELL; + oInfo.m_nSourceIndex = nIndex; + oInfo.m_sObjectCode = wellCode(oWell, "HFWELL", nIndex); + oInfo.m_sDisplayName = oWell.getWellName() + " - " + + editorText("Horizontal fractured well"); + createWellGroups(oInfo, Horizontal_Fractured_Well, + oWell.getPerforationCount() > 0); + listInfos.append(oInfo); + } + return listInfos; + } + + if (eCategory == NM_RESERVOIR_PAGE_REGION_MARK) + { + for (int nIndex = 0; + nIndex < oSnapshot.m_vecRegionMarks.size(); + ++nIndex) + { + nmReservoirPropertyObjectInfo oInfo; + oInfo.m_eCategory = eCategory; + oInfo.m_eObjectType = NM_RESERVOIR_OBJECT_REGION_MARK; + oInfo.m_nSourceIndex = nIndex; + oInfo.m_sObjectCode = QString("RMARK%1") + .arg(nIndex + 1, 4, 10, QChar('0')); + oInfo.m_sDisplayName = + oSnapshot.m_vecRegionMarks[nIndex].getRegionMarkName(); + + nmReservoirParameterGroupInfo oDual = + parameterGroup("Dual-medium parameters", 0, 0, 2); + oDual.m_listFields + << reservoirModelField(prefixed( + oInfo.m_sObjectCode, "RM_ReservoirType")) + << numericField(prefixed( + oInfo.m_sObjectCode, "RM_ComW"), "RM_ComW") + << numericField(prefixed( + oInfo.m_sObjectCode, "RM_ComKr"), "RM_ComKr") + << numericField(prefixed( + oInfo.m_sObjectCode, "RM_NetToGross"), + "RM_NetToGross"); + oInfo.m_listGroups.append(oDual); + listInfos.append(oInfo); + } + return listInfos; + } + + if (eCategory == NM_RESERVOIR_PAGE_FAULT) + { + for (int nIndex = 0; + nIndex < oSnapshot.m_vecFaults.size(); + ++nIndex) + { + nmReservoirPropertyObjectInfo oInfo; + oInfo.m_eCategory = eCategory; + oInfo.m_eObjectType = NM_RESERVOIR_OBJECT_FAULT; + oInfo.m_nSourceIndex = nIndex; + oInfo.m_sObjectCode = QString("FAULT%1") + .arg(nIndex + 1, 4, 10, QChar('0')); + oInfo.m_sDisplayName = + oSnapshot.m_vecFaults[nIndex].getFaultName(); + + nmReservoirParameterGroupInfo oGeometry = + parameterGroup("Geometry", 0, 0, 1); + oGeometry.m_listFields + << numericField(prefixed( + oInfo.m_sObjectCode, "FT_X0"), "FT_X0") + << numericField(prefixed( + oInfo.m_sObjectCode, "FT_Y0"), "FT_Y0") + << numericField(prefixed( + oInfo.m_sObjectCode, "FT_X1"), "FT_X1") + << numericField(prefixed( + oInfo.m_sObjectCode, "FT_Y1"), "FT_Y1"); + + nmReservoirParameterGroupInfo oFlow = + parameterGroup("Flow properties", 0, 1, 1); + oFlow.m_listFields + << leakageModelField(prefixed( + oInfo.m_sObjectCode, "FT_FlowModel"), + "Fault flow model") + << numericField(prefixed( + oInfo.m_sObjectCode, "FT_Leakage"), + "FT_Leakage"); + oInfo.m_listGroups << oGeometry << oFlow; + listInfos.append(oInfo); + } + return listInfos; + } + + if (eCategory == NM_RESERVOIR_PAGE_FRACTURE) + { + int nVisibleFracture = 0; + for (int nIndex = 0; + nIndex < oSnapshot.m_vecFractures.size(); + ++nIndex) + { + nmDataFracture& oFracture = + oSnapshot.m_vecFractures[nIndex]; + if (oFracture.getFractureType().getValue().toString() == + "DFN") + { + continue; + } + nmReservoirPropertyObjectInfo oInfo; + oInfo.m_eCategory = eCategory; + oInfo.m_eObjectType = NM_RESERVOIR_OBJECT_FRACTURE; + oInfo.m_nSourceIndex = nIndex; + oInfo.m_sObjectCode = QString("FRAC%1") + .arg(++nVisibleFracture, 4, 10, QChar('0')); + oInfo.m_sDisplayName = oFracture.getFractureName(); + + nmReservoirParameterGroupInfo oGeometry = + parameterGroup("Geometry", 0, 0, 1); + oGeometry.m_listFields + << numericField(prefixed( + oInfo.m_sObjectCode, "F_X0"), "F_X0") + << numericField(prefixed( + oInfo.m_sObjectCode, "F_Y0"), "F_Y0") + << numericField(prefixed( + oInfo.m_sObjectCode, "F_X1"), "F_X1") + << numericField(prefixed( + oInfo.m_sObjectCode, "F_Y1"), "F_Y1"); + + nmReservoirParameterGroupInfo oFlow = + parameterGroup("Conductivity properties", 0, 1, 1); + oFlow.m_listFields + << fractureModelField(prefixed( + oInfo.m_sObjectCode, "F_FlowModel")) + << numericField(prefixed( + oInfo.m_sObjectCode, "F_FC"), "F_FC") + << numericField(prefixed( + oInfo.m_sObjectCode, "F_DW"), "F_DW"); + oInfo.m_listGroups << oGeometry << oFlow; + listInfos.append(oInfo); + } + return listInfos; + } + + if (eCategory == NM_RESERVOIR_PAGE_COMPOSITE_REGION) + { + for (int nIndex = 0; + nIndex < oSnapshot.m_vecRegions.size(); + ++nIndex) + { + nmReservoirPropertyObjectInfo oInfo; + oInfo.m_eCategory = eCategory; + oInfo.m_eObjectType = NM_RESERVOIR_OBJECT_REGION; + oInfo.m_nSourceIndex = nIndex; + oInfo.m_sObjectCode = QString("REGION%1") + .arg(nIndex + 1, 4, 10, QChar('0')); + oInfo.m_sDisplayName = + oSnapshot.m_vecRegions[nIndex].getRegoinName(); + + nmReservoirParameterGroupInfo oRegion = + parameterGroup("Region properties", 0, 0, 2); + oRegion.m_listFields + << leakageModelField(prefixed( + oInfo.m_sObjectCode, "R_FlowModel"), + "Region flow model") + << numericField(prefixed( + oInfo.m_sObjectCode, "R_Leakage"), + "R_Leakage"); + oInfo.m_listGroups.append(oRegion); + listInfos.append(oInfo); + } + } + return listInfos; +} + +QString nmReservoirParameterCatalog::enumCodeFromStoredValue( + const QString& sParameterId, + const QString& sStoredValue) +{ + QString sValue = sStoredValue.trimmed().toLower(); + if (sParameterId.endsWith("ReservoirType")) + { + return sValue.contains("dual") + ? "dual_porosity_pss" : "homogeneous"; + } + if (sParameterId.endsWith("F_FlowModel")) + { + return sValue.contains("infinite") + ? "infinite_conductivity" : "finite_conductivity"; + } + if (sParameterId.endsWith("FlowModel")) + { + return sValue.contains("leaky") ? "leaky" : "composite_limit"; + } + return QString(); +} + +QString nmReservoirParameterCatalog::storedValueFromEnumCode( + const QString& sParameterId, + const QString& sCode) +{ + if (sParameterId.endsWith("ReservoirType")) + { + return sCode == "dual_porosity_pss" + ? "Dual porosity pseudo steady state" : "Homogeneous"; + } + if (sParameterId.endsWith("F_FlowModel")) + { + return sCode == "infinite_conductivity" + ? "Infinite Conductivity" : "Finite Conductivity"; + } + if (sParameterId.endsWith("FlowModel")) + { + return sCode == "leaky" ? "Leaky" : "Composite limit"; + } + return QString(); +} + +QString nmReservoirParameterCatalog::enumCode( + const nmReservoirPropertiesSnapshot& oSnapshot, + const QString& sParameterId) +{ + if (sParameterId == "ReservoirType") + { + nmDataReservoir& oReservoir = + const_cast(oSnapshot.m_oReservoir); + return enumCodeFromStoredValue( + sParameterId, + oReservoir.getReservoirType().getValue().toString()); + } + + QList listAll; + nmReservoirPropertiesSnapshot& oWritable = + const_cast(oSnapshot); + listAll << createObjectInfos(NM_RESERVOIR_PAGE_FAULT, + oWritable) + << createObjectInfos(NM_RESERVOIR_PAGE_FRACTURE, + oWritable) + << createObjectInfos(NM_RESERVOIR_PAGE_COMPOSITE_REGION, + oWritable) + << createObjectInfos(NM_RESERVOIR_PAGE_REGION_MARK, + oWritable); + for (int nInfoIndex = 0; + nInfoIndex < listAll.size(); + ++nInfoIndex) + { + const nmReservoirPropertyObjectInfo& oInfo = + listAll[nInfoIndex]; + if (!containsEnumField(oInfo, sParameterId)) + { + continue; + } + if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_REGION_MARK) + { + nmDataRegionMark& oMark = + oWritable.m_vecRegionMarks[oInfo.m_nSourceIndex]; + return enumCodeFromStoredValue( + sParameterId, + oMark.getReservoirType().getValue().toString()); + } + if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_FAULT) + { + nmDataFault& oFault = + oWritable.m_vecFaults[oInfo.m_nSourceIndex]; + return enumCodeFromStoredValue( + sParameterId, + oFault.getFaultFlowModel().getValue().toString()); + } + if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_FRACTURE) + { + nmDataFracture& oFracture = + oWritable.m_vecFractures[oInfo.m_nSourceIndex]; + return enumCodeFromStoredValue( + sParameterId, + oFracture.getFractureFlowModel() + .getValue().toString()); + } + if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_REGION) + { + nmDataRegion& oRegion = + oWritable.m_vecRegions[oInfo.m_nSourceIndex]; + return enumCodeFromStoredValue( + sParameterId, + oRegion.getRegionFlowModel().getValue().toString()); + } + } + return QString(); +} + +bool nmReservoirParameterCatalog::setEnumCode( + nmReservoirPropertiesSnapshot& oSnapshot, + const QString& sParameterId, + const QString& sCode) +{ + QString sStoredValue = + storedValueFromEnumCode(sParameterId, sCode); + if (sStoredValue.isEmpty()) + { + return false; + } + + if (sParameterId == "ReservoirType") + { + oSnapshot.m_oReservoir.getReservoirType() + .setValue(sStoredValue); + return true; + } + + QList listAll; + listAll << createObjectInfos(NM_RESERVOIR_PAGE_FAULT, + oSnapshot) + << createObjectInfos(NM_RESERVOIR_PAGE_FRACTURE, + oSnapshot) + << createObjectInfos(NM_RESERVOIR_PAGE_COMPOSITE_REGION, + oSnapshot) + << createObjectInfos(NM_RESERVOIR_PAGE_REGION_MARK, + oSnapshot); + for (int nInfoIndex = 0; + nInfoIndex < listAll.size(); + ++nInfoIndex) + { + const nmReservoirPropertyObjectInfo& oInfo = + listAll[nInfoIndex]; + if (!containsEnumField(oInfo, sParameterId)) + { + continue; + } + if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_REGION_MARK) + { + oSnapshot.m_vecRegionMarks[oInfo.m_nSourceIndex] + .getReservoirType().setValue(sStoredValue); + return true; + } + if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_FAULT) + { + oSnapshot.m_vecFaults[oInfo.m_nSourceIndex] + .getFaultFlowModel().setValue(sStoredValue); + return true; + } + if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_FRACTURE) + { + oSnapshot.m_vecFractures[oInfo.m_nSourceIndex] + .getFractureFlowModel().setValue(sStoredValue); + return true; + } + if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_REGION) + { + oSnapshot.m_vecRegions[oInfo.m_nSourceIndex] + .getRegionFlowModel().setValue(sStoredValue); + return true; + } + } + return false; +} diff --git a/Src/nmNum/nmSubWxs/nmReservoirParameterField.cpp b/Src/nmNum/nmSubWxs/nmReservoirParameterField.cpp new file mode 100644 index 0000000..23a1755 --- /dev/null +++ b/Src/nmNum/nmSubWxs/nmReservoirParameterField.cpp @@ -0,0 +1,429 @@ +#include "nmReservoirParameterField.h" + +#include "iParaItemCtrl.h" +#include "iParameter.h" +#include "iSysParaHelper.h" +#include "mModuleDefines.h" + +#include +#include +#include +#include +#include +#include + +namespace +{ + +/* Qt 4.8的lupdate需要显式标记间接翻译字符串。 */ +static const char* const s_pFieldTranslationMarkers[] = +{ + QT_TRANSLATE_NOOP("nmReservoirParameterField", "The parameter layout is unavailable."), + QT_TRANSLATE_NOOP("nmReservoirParameterField", "Parameter definition not found: %1"), + QT_TRANSLATE_NOOP("nmReservoirParameterField", "Failed to bind the unit for parameter: %1") +}; + +/** @brief 返回轻量字段类使用的翻译文字。 */ +QString fieldText(const char* pText) +{ + Q_UNUSED(s_pFieldTranslationMarkers); + return QCoreApplication::translate( + "nmReservoirParameterField", pText); +} + +} + +nmReservoirParameterField::nmReservoirParameterField( + const nmReservoirParameterFieldInfo& oFieldInfo, + nmIReservoirParameterUnitService* pUnitService, + QObject* pParent) + : QObject(pParent), + m_oFieldInfo(oFieldInfo), + m_pUnitService(pUnitService), + m_pParameter(nullptr), + m_pLabel(nullptr), + m_pLineEdit(nullptr), + m_pValueComboBox(nullptr), + m_pUnitComboBox(nullptr), + m_pUnitLabel(nullptr), + m_bVisible(true), + m_bUpdating(false) +{ +} + +nmReservoirParameterField::~nmReservoirParameterField() +{ + m_pUnitService = nullptr; + m_pParameter = nullptr; + m_pLabel = nullptr; + m_pLineEdit = nullptr; + m_pValueComboBox = nullptr; + m_pUnitComboBox = nullptr; + m_pUnitLabel = nullptr; +} + +bool nmReservoirParameterField::initialize(QGridLayout* pLayout, + int nRow, + QString& sError) +{ + if (pLayout == nullptr) + { + sError = fieldText("The parameter layout is unavailable."); + return false; + } + + // 第一步:枚举字段不依赖XML数值定义,直接创建稳定code下拉框。 + if (isEnum()) + { + createEnumEditor(pLayout, nRow); + sError.clear(); + return true; + } + + // 第二步:数值字段必须取得NM系列的基础参数定义。 + m_pParameter = _paraHelper->getPara( + m_oFieldInfo.m_sBaseParameterId, s_Nm_Serie); + if (m_pParameter == nullptr) + { + sError = fieldText("Parameter definition not found: %1") + .arg(m_oFieldInfo.m_sBaseParameterId); + return false; + } + + // 第三步:创建原型中的标签、输入框和单位列,不增加字段外框。 + return createNumericEditor(pLayout, nRow, sError); +} + +QString nmReservoirParameterField::parameterId() const +{ + return m_oFieldInfo.m_sParameterId; +} + +QString nmReservoirParameterField::baseParameterId() const +{ + return m_oFieldInfo.m_sBaseParameterId; +} + +bool nmReservoirParameterField::isEnum() const +{ + return m_oFieldInfo.m_eEditorType == NM_RESERVOIR_EDITOR_ENUM; +} + +void nmReservoirParameterField::setValueRightAligned(bool bRightAligned) +{ + if (m_pLineEdit != nullptr) + { + m_pLineEdit->setAlignment( + (bRightAligned ? Qt::AlignRight : Qt::AlignLeft) | + Qt::AlignVCenter); + } +} + +int nmReservoirParameterField::editorMinimumWidth() +{ + return 140; +} + +int nmReservoirParameterField::unitMinimumWidth() +{ + return 86; +} + +int nmReservoirParameterField::editorHeight() +{ + return 25; +} + +bool nmReservoirParameterField::createNumericEditor( + QGridLayout* pLayout, + int nRow, + QString& sError) +{ + QWidget* pParentWidget = pLayout->parentWidget(); + m_pLabel = new QLabel(m_pParameter->m_sAlias, pParentWidget); + m_pLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + m_pLabel->setWordWrap(false); + m_pLabel->setSizePolicy(QSizePolicy::Preferred, + QSizePolicy::Fixed); + + m_pLineEdit = new QLineEdit(pParentWidget); + m_pLineEdit->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + m_pLineEdit->setFixedHeight(editorHeight()); + m_pLineEdit->setMinimumWidth(editorMinimumWidth()); + m_pLineEdit->setSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Fixed); + + pLayout->addWidget(m_pLabel, nRow, 0); + pLayout->addWidget(m_pLineEdit, nRow, 1); + + // 有单位参数直接注册到主对话框绑定器;字段只按控件指针读写, + // 因此多口井共用同一基础参数ID时也不会发生串值。 + if (m_oFieldInfo.m_bUnitEnabled && + !m_pParameter->m_sUnit.isEmpty()) + { + m_pUnitComboBox = new QComboBox(pParentWidget); + m_pUnitComboBox->setFixedHeight(editorHeight()); + m_pUnitComboBox->setMinimumWidth(unitMinimumWidth()); + m_pUnitComboBox->setSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Fixed); + pLayout->addWidget(m_pUnitComboBox, nRow, 2); + if (m_pUnitService == nullptr || + !m_pUnitService->bindUnitField( + m_pLineEdit, + m_pUnitComboBox, + m_oFieldInfo.m_sBaseParameterId)) + { + sError = fieldText( + "Failed to bind the unit for parameter: %1") + .arg(m_oFieldInfo.m_sBaseParameterId); + return false; + } + } + else + { + // 无单位参数保留空列用于对齐,不显示额外的单位说明文字。 + m_pUnitLabel = new QLabel(pParentWidget); + m_pUnitLabel->setMinimumWidth(unitMinimumWidth()); + m_pUnitLabel->setSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Fixed); + pLayout->addWidget(m_pUnitLabel, nRow, 2); + } + + updateValidationAppearance(true); + sError.clear(); + return true; +} + +void nmReservoirParameterField::createEnumEditor(QGridLayout* pLayout, + int nRow) +{ + QWidget* pParentWidget = pLayout->parentWidget(); + m_pLabel = new QLabel(m_oFieldInfo.m_sLabel, pParentWidget); + m_pLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + m_pLabel->setWordWrap(false); + m_pLabel->setSizePolicy(QSizePolicy::Preferred, + QSizePolicy::Fixed); + + m_pValueComboBox = new QComboBox(pParentWidget); + m_pValueComboBox->setFixedHeight(editorHeight()); + m_pValueComboBox->setMinimumWidth(editorMinimumWidth()); + m_pValueComboBox->setSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Fixed); + for (int nIndex = 0; + nIndex < m_oFieldInfo.m_listOptions.size(); + ++nIndex) + { + const nmReservoirEnumOption& oOption = + m_oFieldInfo.m_listOptions[nIndex]; + m_pValueComboBox->addItem(oOption.m_sText, oOption.m_sCode); + } + + // 枚举参数没有单位,空白单位列仅用于保持各行编辑器对齐。 + m_pUnitLabel = new QLabel(pParentWidget); + m_pUnitLabel->setMinimumWidth(unitMinimumWidth()); + m_pUnitLabel->setSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Fixed); + + pLayout->addWidget(m_pLabel, nRow, 0); + pLayout->addWidget(m_pValueComboBox, nRow, 1); + pLayout->addWidget(m_pUnitLabel, nRow, 2); +} + +void nmReservoirParameterField::setBaseValue(const QVariant& oValue) +{ + if (m_pLineEdit == nullptr || !oValue.isValid()) + { + return; + } + + m_bUpdating = true; + double dValue = oValue.toDouble(); + if (m_pUnitComboBox != nullptr && m_pUnitService != nullptr) + { + m_pUnitService->setUnitBaseValue(m_pLineEdit, dValue); + } + else + { + m_pLineEdit->setText(formatBaseValue(dValue)); + } + m_bUpdating = false; + validate(); +} + +QVariant nmReservoirParameterField::baseValue(bool& bValid) +{ + bValid = validate(); + if (!bValid || m_pLineEdit == nullptr) + { + return QVariant(); + } + + if (m_pUnitComboBox != nullptr && m_pUnitService != nullptr) + { + return QVariant(m_pUnitService->unitBaseValue(m_pLineEdit)); + } + + bool bNumberOk = false; + double dValue = m_pLineEdit->text().trimmed().toDouble(&bNumberOk); + return bNumberOk ? QVariant(dValue) : QVariant(); +} + +void nmReservoirParameterField::setEnumCode(const QString& sCode) +{ + if (m_pValueComboBox == nullptr) + { + return; + } + int nIndex = m_pValueComboBox->findData(sCode); + m_bUpdating = true; + m_pValueComboBox->setCurrentIndex(nIndex >= 0 ? nIndex : 0); + m_bUpdating = false; +} + +QString nmReservoirParameterField::enumCode() const +{ + if (m_pValueComboBox == nullptr || + m_pValueComboBox->currentIndex() < 0) + { + return QString(); + } + return m_pValueComboBox->itemData( + m_pValueComboBox->currentIndex()).toString(); +} + +bool nmReservoirParameterField::validate() +{ + if (!m_bVisible || isEnum()) + { + return true; + } + if (m_pLineEdit == nullptr || m_pParameter == nullptr) + { + return false; + } + + bool bValid = false; + if (m_pUnitComboBox != nullptr && m_pUnitService != nullptr) + { + bValid = m_pUnitService->isUnitInputValid(m_pLineEdit); + } + else + { + bool bNumberOk = false; + double dValue = m_pLineEdit->text().trimmed() + .toDouble(&bNumberOk); + bValid = bNumberOk && dValue == dValue && + dValue >= m_pParameter->m_dMin && + dValue <= m_pParameter->m_dMax; + if (bValid && + m_oFieldInfo.m_eEditorType == NM_RESERVOIR_EDITOR_INTEGER) + { + bValid = dValue == static_cast(dValue); + } + } + updateValidationAppearance(bValid); + return bValid; +} + +void nmReservoirParameterField::setFieldVisible(bool bVisible) +{ + m_bVisible = bVisible; + if (m_pLabel != nullptr) + { + m_pLabel->setVisible(bVisible); + } + if (m_pLineEdit != nullptr) + { + m_pLineEdit->setVisible(bVisible); + } + if (m_pValueComboBox != nullptr) + { + m_pValueComboBox->setVisible(bVisible); + } + if (m_pUnitComboBox != nullptr) + { + m_pUnitComboBox->setVisible(bVisible); + } + if (m_pUnitLabel != nullptr) + { + m_pUnitLabel->setVisible(bVisible); + } +} + +bool nmReservoirParameterField::isFieldVisible() const +{ + return m_bVisible; +} + +QLineEdit* nmReservoirParameterField::valueEditor() const +{ + return m_pLineEdit; +} + +QComboBox* nmReservoirParameterField::enumEditor() const +{ + return m_pValueComboBox; +} + +QString nmReservoirParameterField::formatBaseValue(double dValue) const +{ + if (m_pParameter == nullptr) + { + return QString::number(dValue, 'g', 15); + } + if (m_oFieldInfo.m_eEditorType == NM_RESERVOIR_EDITOR_INTEGER || + m_pParameter->m_nDigit == 0) + { + return QString::number(dValue, 'f', 0); + } + if (m_pParameter->m_bScientific) + { + return QString::number(dValue, 'e', m_pParameter->m_nDigit); + } + + QString sValue = QString::number( + dValue, 'f', m_pParameter->m_nDigit); + while (sValue.contains('.') && sValue.endsWith('0')) + { + sValue.chop(1); + } + if (sValue.endsWith('.')) + { + sValue.chop(1); + } + return sValue; +} + +void nmReservoirParameterField::updateValidationAppearance(bool bValid) +{ + Q_UNUSED(bValid); + if (m_pLineEdit == nullptr || m_pParameter == nullptr) + { + return; + } + + QString sCurrentUnit; + iUnitGroup* pUnitGroup = nullptr; + if (m_pUnitComboBox != nullptr && m_pUnitService != nullptr) + { + sCurrentUnit = m_pUnitService->currentUnit(m_pLineEdit); + pUnitGroup = m_pUnitService->unitGroup(m_pLineEdit); + } + QString sRange = iParaItemCtrl::getScaleStrOf( + m_pParameter, sCurrentUnit, pUnitGroup); + QString sHelp = m_pParameter->m_sDesc; + if (!sRange.isEmpty()) + { + if (!sHelp.isEmpty()) + { + sHelp += "\n"; + } + sHelp += sRange; + } + m_pLineEdit->setToolTip(sHelp); + m_pLineEdit->setWhatsThis(sHelp); + if (m_pLabel != nullptr) + { + m_pLabel->setWhatsThis(sHelp); + } +} diff --git a/Src/nmNum/nmSubWxs/nmReservoirParameterPage.cpp b/Src/nmNum/nmSubWxs/nmReservoirParameterPage.cpp new file mode 100644 index 0000000..51565fd --- /dev/null +++ b/Src/nmNum/nmSubWxs/nmReservoirParameterPage.cpp @@ -0,0 +1,539 @@ +#include "nmReservoirParameterPage.h" + +#include "nmReservoirParameterField.h" +#include "nmReservoirPropertiesSession.h" + +#include +#include +#include +#include +#include +#include +#include + +nmReservoirParameterPage::nmReservoirParameterPage( + nmReservoirPropertiesCategory eCategory, + nmReservoirPropertiesSession* pSession, + nmIReservoirParameterUnitService* pUnitService, + QWidget* pParent) + : QWidget(pParent), + m_eCategory(eCategory), + m_pSession(pSession), + m_pUnitService(pUnitService), + m_nCurrentObjectIndex(-1), + m_bUpdatingObject(false), + m_bBuildingFields(false), + m_pTitleLabel(nullptr), + m_pObjectLabel(nullptr), + m_pObjectComboBox(nullptr), + m_pGroupsWidget(nullptr), + m_pGroupsLayout(nullptr), + m_pMainLayout(nullptr), + m_pEmptyLabel(nullptr) +{ + createUi(); +} + +nmReservoirParameterPage::~nmReservoirParameterPage() +{ + m_listFields.clear(); + m_mapFields.clear(); + m_mapEditorFields.clear(); + m_pSession = nullptr; + m_pUnitService = nullptr; +} + +void nmReservoirParameterPage::createUi() +{ + // 第一步:创建原型中的单层内容头,储层页自动隐藏对象选择器。 + m_pMainLayout = new QVBoxLayout(this); + m_pMainLayout->setContentsMargins(8, 6, 8, 8); + m_pMainLayout->setSpacing(8); + + QWidget* pHeaderWidget = new QWidget(this); + QHBoxLayout* pHeaderLayout = new QHBoxLayout(pHeaderWidget); + pHeaderLayout->setContentsMargins(8, 4, 8, 4); + pHeaderLayout->setSpacing(9); + + m_pTitleLabel = new QLabel(categoryTitle(), pHeaderWidget); + pHeaderLayout->addWidget(m_pTitleLabel, 1); + + m_pObjectLabel = new QLabel(objectSelectorLabel(), pHeaderWidget); + m_pObjectComboBox = new QComboBox(pHeaderWidget); + m_pObjectComboBox->setMinimumWidth(260); + m_pObjectComboBox->setMinimumHeight(27); + pHeaderLayout->addWidget(m_pObjectLabel); + pHeaderLayout->addWidget(m_pObjectComboBox); + // 储层页只有一个固定对象,顶部分类按钮已经说明当前页面, + // 不再重复显示无操作价值的页标题;其他页面保留对象选择区。 + if (m_eCategory == NM_RESERVOIR_PAGE_RESERVOIR) + { + pHeaderWidget->hide(); + } + else + { + m_pMainLayout->addWidget(pHeaderWidget); + } + + // 第二步:分组载体没有边框,仅用于实现原型中的两列网格。 + m_pGroupsWidget = new QWidget(this); + m_pGroupsLayout = new QGridLayout(m_pGroupsWidget); + m_pGroupsLayout->setContentsMargins(0, 0, 0, 0); + m_pGroupsLayout->setHorizontalSpacing(14); + m_pGroupsLayout->setVerticalSpacing(14); + m_pGroupsLayout->setColumnStretch(0, 1); + m_pGroupsLayout->setColumnStretch(1, 1); + m_pMainLayout->addWidget(m_pGroupsWidget); + m_pMainLayout->addStretch(1); + + connect(m_pObjectComboBox, SIGNAL(currentIndexChanged(int)), + this, SLOT(slotObjectChanged(int))); +} + +QString nmReservoirParameterPage::categoryTitle() const +{ + switch (m_eCategory) + { + case NM_RESERVOIR_PAGE_RESERVOIR: + return tr("Reservoir parameters"); + case NM_RESERVOIR_PAGE_WELL: + return tr("Well parameters"); + case NM_RESERVOIR_PAGE_FAULT: + return tr("Fault parameters"); + case NM_RESERVOIR_PAGE_FRACTURE: + return tr("Fracture parameters"); + case NM_RESERVOIR_PAGE_COMPOSITE_REGION: + return tr("Composite region parameters"); + case NM_RESERVOIR_PAGE_REGION_MARK: + return tr("Region mark parameters"); + default: + return QString(); + } +} + +QString nmReservoirParameterPage::objectSelectorLabel() const +{ + switch (m_eCategory) + { + case NM_RESERVOIR_PAGE_WELL: + return tr("Select well:"); + case NM_RESERVOIR_PAGE_FAULT: + return tr("Select fault:"); + case NM_RESERVOIR_PAGE_FRACTURE: + return tr("Select fracture:"); + case NM_RESERVOIR_PAGE_COMPOSITE_REGION: + return tr("Select composite region:"); + case NM_RESERVOIR_PAGE_REGION_MARK: + return tr("Select region mark:"); + default: + return tr("Select object:"); + } +} + +bool nmReservoirParameterPage::reloadObjects(QString& sError) +{ + // 第一步:重新取得会话对象描述,并把稳定对象code放入UserRole。 + m_listObjectInfos = m_pSession != nullptr + ? m_pSession->objectInfos(m_eCategory) + : QList(); + m_bUpdatingObject = true; + m_pObjectComboBox->clear(); + for (int nIndex = 0; nIndex < m_listObjectInfos.size(); ++nIndex) + { + const nmReservoirPropertyObjectInfo& oInfo = + m_listObjectInfos[nIndex]; + m_pObjectComboBox->addItem(oInfo.m_sDisplayName, + oInfo.m_sObjectCode); + } + m_bUpdatingObject = false; + + bool bShowSelector = m_eCategory != NM_RESERVOIR_PAGE_RESERVOIR; + m_pObjectLabel->setVisible(bShowSelector); + m_pObjectComboBox->setVisible(bShowSelector); + + // 第二步:构建首个对象;空分类显示无额外面板的空状态。 + return buildObject(m_listObjectInfos.isEmpty() ? -1 : 0, sError); +} + +bool nmReservoirParameterPage::hasEditableObjects() const +{ + return !m_listObjectInfos.isEmpty(); +} + +void nmReservoirParameterPage::slotObjectChanged(int nIndex) +{ + if (m_bUpdatingObject || nIndex == m_nCurrentObjectIndex) + { + return; + } + + // 第一步:离开旧对象前校验并保存,非法输入时回退对象选择。 + int nPreviousIndex = m_nCurrentObjectIndex; + if (nPreviousIndex >= 0 && !commitPendingEdits()) + { + m_bUpdatingObject = true; + m_pObjectComboBox->setCurrentIndex(nPreviousIndex); + m_bUpdatingObject = false; + return; + } + + // 第二步:构建新对象;目录已在初始化阶段验证,正常切换不会失败。 + QString sError; + if (!buildObject(nIndex, sError)) + { + m_bUpdatingObject = true; + m_pObjectComboBox->setCurrentIndex(nPreviousIndex); + m_bUpdatingObject = false; + } +} + +bool nmReservoirParameterPage::buildObject(int nObjectIndex, + QString& sError) +{ + m_bBuildingFields = true; + clearObjectEditors(); + m_nCurrentObjectIndex = nObjectIndex; + + if (nObjectIndex < 0 || nObjectIndex >= m_listObjectInfos.size()) + { + m_pEmptyLabel = new QLabel( + tr("No editable objects in this category."), + m_pGroupsWidget); + m_pEmptyLabel->setAlignment(Qt::AlignCenter); + m_pEmptyLabel->setMinimumHeight(260); + m_pGroupsLayout->addWidget(m_pEmptyLabel, 0, 0, 1, 2); + m_bBuildingFields = false; + sError.clear(); + updateValidity(); + return true; + } + + // 第一步:按目录直接创建分组,页面不再包含对象类型专用布局代码。 + const nmReservoirPropertyObjectInfo& oObjectInfo = + m_listObjectInfos[nObjectIndex]; + for (int nGroupIndex = 0; + nGroupIndex < oObjectInfo.m_listGroups.size(); + ++nGroupIndex) + { + if (!buildGroup(oObjectInfo.m_listGroups[nGroupIndex], sError)) + { + m_bBuildingFields = false; + return false; + } + } + + // 第二步:加载工作副本,随后只调整行可见性,不重建页面。 + for (int nFieldIndex = 0; + nFieldIndex < m_listFields.size(); + ++nFieldIndex) + { + loadFieldValue(m_listFields[nFieldIndex]); + } + applyVisibilityRules(); + m_bBuildingFields = false; + sError.clear(); + updateValidity(); + return true; +} + +void nmReservoirParameterPage::clearObjectEditors() +{ + m_listFields.clear(); + m_mapFields.clear(); + m_mapEditorFields.clear(); + m_pEmptyLabel = nullptr; + + // 分组载体本身保留,只删除其直接子控件和布局项。 + while (m_pGroupsLayout->count() > 0) + { + QLayoutItem* pItem = m_pGroupsLayout->takeAt(0); + if (pItem->widget() != nullptr) + { + delete pItem->widget(); + } + delete pItem; + } +} + +bool nmReservoirParameterPage::buildGroup( + const nmReservoirParameterGroupInfo& oGroupInfo, + QString& sError) +{ + // 承载器始终横向铺满网格单元;只通过内部弹簧控制垂直位置, + // 避免直接设置对齐标志后分组框退回建议宽度。 + QWidget* pGroupContainer = new QWidget(m_pGroupsWidget); + pGroupContainer->setSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Expanding); + QVBoxLayout* pGroupContainerLayout = + new QVBoxLayout(pGroupContainer); + pGroupContainerLayout->setContentsMargins(0, 0, 0, 0); + pGroupContainerLayout->setSpacing(0); + + QGroupBox* pGroupBox = new QGroupBox( + oGroupInfo.m_sTitle, pGroupContainer); + // 参数框只在水平方向随页面宽度调整,垂直方向始终采用内容建议高度, + // 避免跨行分组被网格拉伸成大面积空白框。 + pGroupBox->setSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Fixed); + QGridLayout* pFieldLayout = new QGridLayout(pGroupBox); + pFieldLayout->setContentsMargins(8, 15, 8, 10); + pFieldLayout->setHorizontalSpacing(6); + pFieldLayout->setVerticalSpacing(6); + // 标签列由本组最长标签的自然宽度决定,数值列和单位列按比例使用剩余空间。 + pFieldLayout->setColumnStretch(0, 0); + pFieldLayout->setColumnStretch(1, 8); + pFieldLayout->setColumnStretch(2, 5); + pFieldLayout->setColumnMinimumWidth( + 1, nmReservoirParameterField::editorMinimumWidth()); + pFieldLayout->setColumnMinimumWidth( + 2, nmReservoirParameterField::unitMinimumWidth()); + + // 每个字段只把三个直接控件放入此网格,不产生字段外框或子面板。 + for (int nFieldIndex = 0; + nFieldIndex < oGroupInfo.m_listFields.size(); + ++nFieldIndex) + { + nmReservoirParameterField* pField = + new nmReservoirParameterField( + oGroupInfo.m_listFields[nFieldIndex], + m_pUnitService, + pGroupBox); + if (!pField->initialize(pFieldLayout, nFieldIndex, sError)) + { + delete pGroupContainer; + return false; + } + pField->setValueRightAligned( + oGroupInfo.m_bValueRightAligned); + if (pField->valueEditor() != nullptr) + { + connect(pField->valueEditor(), SIGNAL(textChanged(QString)), + this, SLOT(slotFieldValueEdited())); + m_mapEditorFields.insert(pField->valueEditor(), pField); + } + if (pField->enumEditor() != nullptr) + { + connect(pField->enumEditor(), SIGNAL(currentIndexChanged(int)), + this, SLOT(slotEnumIndexChanged(int))); + m_mapEditorFields.insert(pField->enumEditor(), pField); + } + m_listFields.append(pField); + m_mapFields.insert(pField->parameterId(), pField); + } + + // 跨越多行的分组在承载区域垂直居中,普通分组保持顶部排列。 + if (oGroupInfo.m_nRowSpan > 1) + { + pGroupContainerLayout->addStretch(1); + } + pGroupContainerLayout->addWidget(pGroupBox); + pGroupContainerLayout->addStretch(1); + + m_pGroupsLayout->addWidget( + pGroupContainer, + oGroupInfo.m_nRow, + oGroupInfo.m_nColumn, + oGroupInfo.m_nRowSpan, + oGroupInfo.m_nColumnSpan); + return true; +} + +void nmReservoirParameterPage::loadFieldValue( + nmReservoirParameterField* pField) +{ + if (pField == nullptr || m_pSession == nullptr) + { + return; + } + if (pField->isEnum()) + { + pField->setEnumCode(m_pSession->enumCode( + pField->parameterId())); + } + else + { + pField->setBaseValue(m_pSession->value( + pField->parameterId())); + } +} + +bool nmReservoirParameterPage::commitPendingEdits() +{ + if (m_pSession == nullptr) + { + return false; + } + + // 第一步:先完整校验,避免部分字段已写入而后续字段失败。 + if (!isInputValid()) + { + updateValidity(); + return false; + } + + // 第二步:逐行按编辑器指针获取基准值,再统一写入工作副本。 + QMap mapValues; + for (int nFieldIndex = 0; + nFieldIndex < m_listFields.size(); + ++nFieldIndex) + { + nmReservoirParameterField* pField = + m_listFields[nFieldIndex]; + if (pField == nullptr || pField->isEnum() || + !pField->isFieldVisible()) + { + continue; + } + bool bValid = false; + QVariant oValue = pField->baseValue(bValid); + if (!bValid || !oValue.isValid()) + { + updateValidity(); + return false; + } + mapValues.insert(pField->parameterId(), oValue); + } + m_pSession->setValues(mapValues); + updateValidity(); + return true; +} + +bool nmReservoirParameterPage::isInputValid() +{ + for (int nFieldIndex = 0; + nFieldIndex < m_listFields.size(); + ++nFieldIndex) + { + nmReservoirParameterField* pField = + m_listFields[nFieldIndex]; + if (pField != nullptr && pField->isFieldVisible() && + !pField->validate()) + { + return false; + } + } + return true; +} + +void nmReservoirParameterPage::slotFieldValueEdited() +{ + if (!m_bBuildingFields) + { + nmReservoirParameterField* pField = + m_mapEditorFields.value(sender(), nullptr); + if (pField != nullptr) + { + pField->validate(); + } + updateValidity(); + } +} + +void nmReservoirParameterPage::slotEnumIndexChanged(int nIndex) +{ + Q_UNUSED(nIndex); + if (m_bBuildingFields || m_pSession == nullptr) + { + return; + } + + nmReservoirParameterField* pField = + m_mapEditorFields.value(sender(), nullptr); + if (pField == nullptr) + { + return; + } + QString sParameterId = pField->parameterId(); + + // 第一步:联动前保存当前可见数值;非法时回滚枚举code。 + QString sPreviousCode = m_pSession->enumCode(sParameterId); + if (!commitPendingEdits()) + { + m_bBuildingFields = true; + pField->setEnumCode(sPreviousCode); + m_bBuildingFields = false; + return; + } + + // 第二步:只用稳定code更新工作副本,显示文字不参与业务判断。 + if (!m_pSession->setEnumCode(sParameterId, pField->enumCode())) + { + m_bBuildingFields = true; + pField->setEnumCode(sPreviousCode); + m_bBuildingFields = false; + return; + } + + // 第三步:仅显示或隐藏行,隐藏参数继续保留在工作副本中。 + applyVisibilityRules(); + updateValidity(); +} + +void nmReservoirParameterPage::applyVisibilityRules() +{ + if (m_pSession == nullptr || m_nCurrentObjectIndex < 0 || + m_nCurrentObjectIndex >= m_listObjectInfos.size()) + { + return; + } + + const nmReservoirPropertyObjectInfo& oInfo = + m_listObjectInfos[m_nCurrentObjectIndex]; + + // 区域标记为均质储层时隐藏双重介质参数。 + if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_REGION_MARK) + { + QString sModelId = oInfo.m_sObjectCode + + "_RM_ReservoirType"; + bool bDual = m_pSession->enumCode(sModelId) != "homogeneous"; + setFieldVisible(oInfo.m_sObjectCode + "_RM_ComW", bDual); + setFieldVisible(oInfo.m_sObjectCode + "_RM_ComKr", bDual); + setFieldVisible(oInfo.m_sObjectCode + "_RM_NetToGross", bDual); + } + + // 无限导流裂缝不显示有限导流能力和裂缝宽度参数。 + if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_FRACTURE) + { + QString sModelId = oInfo.m_sObjectCode + "_F_FlowModel"; + bool bFinite = m_pSession->enumCode(sModelId) != + "infinite_conductivity"; + setFieldVisible(oInfo.m_sObjectCode + "_F_FC", bFinite); + setFieldVisible(oInfo.m_sObjectCode + "_F_DW", bFinite); + } + + // 非泄漏断层或复合区不显示泄漏系数。 + if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_FAULT) + { + bool bLeaky = m_pSession->enumCode( + oInfo.m_sObjectCode + "_FT_FlowModel") == "leaky"; + setFieldVisible(oInfo.m_sObjectCode + "_FT_Leakage", bLeaky); + } + if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_REGION) + { + bool bLeaky = m_pSession->enumCode( + oInfo.m_sObjectCode + "_R_FlowModel") == "leaky"; + setFieldVisible(oInfo.m_sObjectCode + "_R_Leakage", bLeaky); + } +} + +void nmReservoirParameterPage::setFieldVisible( + const QString& sParameterId, + bool bVisible) +{ + nmReservoirParameterField* pField = + m_mapFields.value(sParameterId, nullptr); + if (pField != nullptr) + { + pField->setFieldVisible(bVisible); + } +} + +void nmReservoirParameterPage::updateValidity() +{ + emit sigValidityChanged(isInputValid()); +} + +nmReservoirPropertiesCategory nmReservoirParameterPage::category() const +{ + return m_eCategory; +} diff --git a/Src/nmNum/nmSubWxs/nmReservoirPropertiesDataSource.cpp b/Src/nmNum/nmSubWxs/nmReservoirPropertiesDataSource.cpp new file mode 100644 index 0000000..c10ada0 --- /dev/null +++ b/Src/nmNum/nmSubWxs/nmReservoirPropertiesDataSource.cpp @@ -0,0 +1,119 @@ +#include "nmReservoirPropertiesDataSource.h" + +#include "nmDataAnalyzeManager.h" +#include "nmDataWellBase.h" +#include "nmWxParameterProperty.h" + +nmReservoirPropertiesSnapshot::nmReservoirPropertiesSnapshot() +{ +} + +nmIReservoirPropertiesDataSource::~nmIReservoirPropertiesDataSource() +{ +} + +nmAnalyzeManagerReservoirDataSource::nmAnalyzeManagerReservoirDataSource( + nmDataAnalyzeManager* pDataManager) + : m_pDataManager(pDataManager) +{ +} + +nmAnalyzeManagerReservoirDataSource::~nmAnalyzeManagerReservoirDataSource() +{ + m_pDataManager = nullptr; +} + +bool nmAnalyzeManagerReservoirDataSource::loadSnapshot( + nmReservoirPropertiesSnapshot& oSnapshot, + QString& sError) const +{ + // 第一步:确认入口已经注入有效的数据管理器。 + if (m_pDataManager == nullptr) + { + sError = QObject::tr("The current numerical well-test data is unavailable."); + return false; + } + + // 第二步:复制储层和几何对象,确保界面编辑不会触碰实时数据。 + oSnapshot.m_oReservoir = m_pDataManager->getReservoirDataCopy(); + oSnapshot.m_vecRegionMarks = m_pDataManager->getRegionMarkDataListCopy(); + oSnapshot.m_vecFaults = m_pDataManager->getFaultDataListCopy(); + oSnapshot.m_vecFractures = m_pDataManager->getFractureDataListCopy(); + oSnapshot.m_vecRegions = m_pDataManager->getRegionDataListCopy(); + + oSnapshot.m_vecVerticalWells.clear(); + oSnapshot.m_vecVerticalFracturedWells.clear(); + oSnapshot.m_vecHorizontalFracturedWells.clear(); + + // 第三步:按最具体的井型复制数据,显式排除不受求解器支持的普通水平井。 + QVector vecWells = m_pDataManager->getWellDataList(); + for (int nIndex = 0; nIndex < vecWells.size(); ++nIndex) + { + nmDataWellBase* pWell = vecWells[nIndex]; + if (pWell == nullptr) + { + continue; + } + + nmDataVerticalFracturedWell* pVerticalFractured = + dynamic_cast(pWell); + nmDataHorizontalFracturedWell* pHorizontalFractured = + dynamic_cast(pWell); + nmDataVerticalWell* pVertical = dynamic_cast(pWell); + + if (pVerticalFractured != nullptr) + { + oSnapshot.m_vecVerticalFracturedWells.append(*pVerticalFractured); + } + else if (pHorizontalFractured != nullptr) + { + oSnapshot.m_vecHorizontalFracturedWells.append(*pHorizontalFractured); + } + else if (pVertical != nullptr) + { + oSnapshot.m_vecVerticalWells.append(*pVertical); + } + } + + sError.clear(); + return true; +} + +bool nmAnalyzeManagerReservoirDataSource::commitSnapshot( + const nmReservoirPropertiesSnapshot& oSnapshot, + bool bGeometryChanged, + QString& sError) +{ + // 第一步:提交前再次校验数据管理器生命周期。 + if (m_pDataManager == nullptr) + { + sError = QObject::tr("The current numerical well-test data is unavailable."); + return false; + } + + // 第二步:按对象类型批量替换现有数据,不改变对象地址和列表结构。 + m_pDataManager->updateVerticalWells(oSnapshot.m_vecVerticalWells); + m_pDataManager->updateVerticalFracturedWells(oSnapshot.m_vecVerticalFracturedWells); + m_pDataManager->updateHorizontalFracturedWells(oSnapshot.m_vecHorizontalFracturedWells); + m_pDataManager->updateFaultData(oSnapshot.m_vecFaults); + m_pDataManager->updateFractureData(oSnapshot.m_vecFractures); + m_pDataManager->updateRegionData(oSnapshot.m_vecRegions); + m_pDataManager->updateRegionMarkData(oSnapshot.m_vecRegionMarks); + m_pDataManager->updateReservoirData(oSnapshot.m_oReservoir); + + // 第三步:重建实时注册表映射,并在几何变化时统一通知画布和网格。 + m_pDataManager->syncWellAttrs(); + m_pDataManager->syncGeometryAttrs(); + if (bGeometryChanged) + { + m_pDataManager->notifyDataChanged(); + m_pDataManager->updateWellPlotByDataManager(); + } + + // 第四步:兼容仍在使用旧参数树的窗口,提交完成后只刷新一次表格入口。 + nmWxParameterProperty::notifyUpdateTable(); + + sError.clear(); + return true; +} + diff --git a/Src/nmNum/nmSubWxs/nmReservoirPropertiesSession.cpp b/Src/nmNum/nmSubWxs/nmReservoirPropertiesSession.cpp new file mode 100644 index 0000000..293b74c --- /dev/null +++ b/Src/nmNum/nmSubWxs/nmReservoirPropertiesSession.cpp @@ -0,0 +1,242 @@ +#include "nmReservoirPropertiesSession.h" + +#include "nmAttrRegistry.h" +#include "nmDataAttribute.h" + +namespace +{ + +/** @brief 比较两个QVariant数值,保留字符串和整数的精确语义。 */ +bool sameValue(const QVariant& oLeft, const QVariant& oRight) +{ + return oLeft == oRight; +} + +/** @brief 比较两口井的网格相关参数。 */ +bool sameWellGeometry(nmDataWellBase& oLeft, nmDataWellBase& oRight) +{ + return sameValue(oLeft.getX().getValue(), oRight.getX().getValue()) && + sameValue(oLeft.getY().getValue(), oRight.getY().getValue()) && + sameValue(oLeft.getRadius().getValue(), oRight.getRadius().getValue()); +} + +} + +nmReservoirPropertiesSession::nmReservoirPropertiesSession( + nmIReservoirPropertiesDataSource* pDataSource) + : m_pDataSource(pDataSource), + m_pAttrRegistry(new nmAttrRegistry), + m_bInitialized(false) +{ +} + +nmReservoirPropertiesSession::~nmReservoirPropertiesSession() +{ + delete m_pAttrRegistry; + m_pAttrRegistry = nullptr; + m_pDataSource = nullptr; +} + +bool nmReservoirPropertiesSession::initialize(QString& sError) +{ + // 第一步:读取一次真实数据,作为取消和变更检测的基线。 + if (m_pDataSource == nullptr || + !m_pDataSource->loadSnapshot(m_oOriginalSnapshot, sError)) + { + m_bInitialized = false; + return false; + } + + // 第二步:复制出独立工作副本,后续界面修改只写入此对象。 + m_oWorkingSnapshot = m_oOriginalSnapshot; + nmReservoirParameterCatalog::registerSnapshot(m_pAttrRegistry, + m_oWorkingSnapshot); + m_bInitialized = true; + sError.clear(); + return true; +} + +QList nmReservoirPropertiesSession::objectInfos( + nmReservoirPropertiesCategory eCategory) +{ + if (!m_bInitialized) + { + return QList(); + } + return nmReservoirParameterCatalog::createObjectInfos(eCategory, + m_oWorkingSnapshot); +} + +QVariant nmReservoirPropertiesSession::value(const QString& sParameterId) const +{ + if (!m_bInitialized || m_pAttrRegistry == nullptr) + { + return QVariant(); + } + QStringList listIds; + listIds << sParameterId; + return m_pAttrRegistry->collect(listIds).value(sParameterId, QVariant()); +} + +bool nmReservoirPropertiesSession::setValue(const QString& sParameterId, + const QVariant& oValue) +{ + if (!m_bInitialized || m_pAttrRegistry == nullptr || + !m_pAttrRegistry->registeredNames().contains(sParameterId)) + { + return false; + } + m_pAttrRegistry->setValue(sParameterId, oValue); + return true; +} + +void nmReservoirPropertiesSession::setValues( + const QMap& mapValues) +{ + QMap::const_iterator oIterator = mapValues.constBegin(); + for (; oIterator != mapValues.constEnd(); ++oIterator) + { + setValue(oIterator.key(), oIterator.value()); + } +} + +QString nmReservoirPropertiesSession::enumCode(const QString& sParameterId) const +{ + if (!m_bInitialized) + { + return QString(); + } + return nmReservoirParameterCatalog::enumCode(m_oWorkingSnapshot, + sParameterId); +} + +bool nmReservoirPropertiesSession::setEnumCode(const QString& sParameterId, + const QString& sCode) +{ + if (!m_bInitialized) + { + return false; + } + return nmReservoirParameterCatalog::setEnumCode(m_oWorkingSnapshot, + sParameterId, + sCode); +} + +bool nmReservoirPropertiesSession::commit(QString& sError) +{ + if (!m_bInitialized || m_pDataSource == nullptr) + { + sError = QObject::tr("The reservoir properties editing session is unavailable."); + return false; + } + + // 第一步:在提交前计算几何变更,供数据源决定是否重建网格和刷新画布。 + bool bGeometryChanged = isGeometryChanged(); + + // 第二步:将完整工作副本一次性交给数据源;失败时继续保留当前编辑内容。 + if (!m_pDataSource->commitSnapshot(m_oWorkingSnapshot, + bGeometryChanged, + sError)) + { + return false; + } + + // 第三步:提交成功后更新基线,防止同一会话重复识别旧变更。 + m_oOriginalSnapshot = m_oWorkingSnapshot; + sError.clear(); + return true; +} + +const nmReservoirPropertiesSnapshot& +nmReservoirPropertiesSession::workingSnapshot() const +{ + return m_oWorkingSnapshot; +} + +bool nmReservoirPropertiesSession::isGeometryChanged() const +{ + // 第一步:对象数量变化视为几何变化,虽然当前对话框本身不提供增删操作。 + if (m_oOriginalSnapshot.m_vecVerticalWells.size() != + m_oWorkingSnapshot.m_vecVerticalWells.size() || + m_oOriginalSnapshot.m_vecVerticalFracturedWells.size() != + m_oWorkingSnapshot.m_vecVerticalFracturedWells.size() || + m_oOriginalSnapshot.m_vecHorizontalFracturedWells.size() != + m_oWorkingSnapshot.m_vecHorizontalFracturedWells.size()) + { + return true; + } + + // 第二步:比较三类井的位置和井径。 + for (int nIndex = 0; nIndex < m_oWorkingSnapshot.m_vecVerticalWells.size(); ++nIndex) + { + nmDataVerticalWell& oOriginal = + const_cast(m_oOriginalSnapshot.m_vecVerticalWells[nIndex]); + nmDataVerticalWell& oWorking = + const_cast(m_oWorkingSnapshot.m_vecVerticalWells[nIndex]); + if (!sameWellGeometry(oOriginal, oWorking)) + { + return true; + } + } + for (int nIndex = 0; nIndex < m_oWorkingSnapshot.m_vecVerticalFracturedWells.size(); ++nIndex) + { + nmDataVerticalFracturedWell& oOriginal = const_cast( + m_oOriginalSnapshot.m_vecVerticalFracturedWells[nIndex]); + nmDataVerticalFracturedWell& oWorking = const_cast( + m_oWorkingSnapshot.m_vecVerticalFracturedWells[nIndex]); + if (!sameWellGeometry(oOriginal, oWorking) || + !sameValue(oOriginal.getFractureHalfLength().getValue(), + oWorking.getFractureHalfLength().getValue()) || + !sameValue(oOriginal.getFractureAngle().getValue(), + oWorking.getFractureAngle().getValue())) + { + return true; + } + } + for (int nIndex = 0; nIndex < m_oWorkingSnapshot.m_vecHorizontalFracturedWells.size(); ++nIndex) + { + nmDataHorizontalFracturedWell& oOriginal = + const_cast( + m_oOriginalSnapshot.m_vecHorizontalFracturedWells[nIndex]); + nmDataHorizontalFracturedWell& oWorking = + const_cast( + m_oWorkingSnapshot.m_vecHorizontalFracturedWells[nIndex]); + if (!sameWellGeometry(oOriginal, oWorking) || + !sameValue(oOriginal.getWellLength().getValue(), oWorking.getWellLength().getValue()) || + !sameValue(oOriginal.getDrainAngle().getValue(), oWorking.getDrainAngle().getValue()) || + !sameValue(oOriginal.getNumberOfFractures().getValue(), + oWorking.getNumberOfFractures().getValue()) || + !sameValue(oOriginal.getFractureHalfLength().getValue(), + oWorking.getFractureHalfLength().getValue()) || + !sameValue(oOriginal.getFractureAngle().getValue(), + oWorking.getFractureAngle().getValue())) + { + return true; + } + } + + // 第三步:断层和裂缝端点也直接参与网格,因此比较完整点坐标。 + if (m_oOriginalSnapshot.m_vecFaults.size() != m_oWorkingSnapshot.m_vecFaults.size() || + m_oOriginalSnapshot.m_vecFractures.size() != m_oWorkingSnapshot.m_vecFractures.size()) + { + return true; + } + for (int nIndex = 0; nIndex < m_oWorkingSnapshot.m_vecFaults.size(); ++nIndex) + { + if (m_oOriginalSnapshot.m_vecFaults[nIndex].getFaultPoints() != + m_oWorkingSnapshot.m_vecFaults[nIndex].getFaultPoints()) + { + return true; + } + } + for (int nIndex = 0; nIndex < m_oWorkingSnapshot.m_vecFractures.size(); ++nIndex) + { + if (m_oOriginalSnapshot.m_vecFractures[nIndex].getFracturePoints() != + m_oWorkingSnapshot.m_vecFractures[nIndex].getFracturePoints()) + { + return true; + } + } + return false; +} + diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp index 202d14c..3d31d8d 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp @@ -408,7 +408,7 @@ void nmWxAutomaticFitting::setupParameterTable() m_parameterTable->setItem(0, 2, new QTableWidgetItem(QString::number(automaticFittingData.getPermeabilityMin().getValue().toDouble()))); m_parameterTable->setItem(0, 3, new QTableWidgetItem(QString::number(reservoirData.getPermeability().getValue().toDouble()))); m_parameterTable->setItem(0, 4, new QTableWidgetItem(QString::number(automaticFittingData.getPermeabilityMax().getValue().toDouble()))); - m_parameterTable->setItem(0, 5, new QTableWidgetItem(tr("Darcy"))); + m_parameterTable->setItem(0, 5, new QTableWidgetItem(tr("D"))); // 表皮系数 (Skin) m_parameterTable->setItem(1, 0, new QTableWidgetItem("2")); diff --git a/Src/nmNum/nmSubWxs/nmWxReservoirPropertiesEditor.cpp b/Src/nmNum/nmSubWxs/nmWxReservoirPropertiesEditor.cpp new file mode 100644 index 0000000..d71d944 --- /dev/null +++ b/Src/nmNum/nmSubWxs/nmWxReservoirPropertiesEditor.cpp @@ -0,0 +1,544 @@ +#include "nmWxReservoirPropertiesEditor.h" + +#include "nmReservoirPropertiesDataSource.h" +#include "nmReservoirPropertiesSession.h" +#include "nmReservoirParameterPage.h" +#include "iUnitGroup.h" +#include "iUnitHelper.h" +#include "iUnitWxBinder.h" +#include "mModuleDefines.h" +#include "ZxBaseUtil.h" + +#include +#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) +{ + // 第一步:仍由框架绑定器读取参数定义、范围和常规单位组。 + if (!bindUnitPairWxs(pLineEdit, pUnitComboBox, + sBaseParameterId, false)) + { + return false; + } + + // 第二步:单位帮助器对单位名执行了不区分大小写的匹配,D可能先命中 + // 时间组中的d(天)。渗透率以mD这个无歧义单位定位正确单位组, + // 但基准单位仍明确保持为求解器使用的D。 + if (sBaseParameterId == QString::fromLatin1("K")) + { + iUnitGroup* pPermeabilityGroup = + iUnitHelper::getUnitGroupByUnit( + QString::fromLatin1("mD")); + zxBindWxPair* pBindPair = m_pUnitBinder != nullptr + ? m_pUnitBinder->getWxPairOf(pLineEdit) : nullptr; + if (pPermeabilityGroup == nullptr || pBindPair == nullptr || + pPermeabilityGroup->m_sType.compare( + QString::fromLatin1("permeability"), + Qt::CaseInsensitive) != 0 || + pPermeabilityGroup->indexOf( + QString::fromLatin1("D")) < 0) + { + return false; + } + + // 第三步:替换错误单位组并重建单位选项。后续数值设置、切换、 + // 范围校验和基准值读取继续全部通过框架绑定器完成。 + pBindPair->isUpdating = true; + pBindPair->pUnitGroup = pPermeabilityGroup; + pBindPair->sBaseUnit = QString::fromLatin1("D"); + pUnitComboBox->clear(); + pUnitComboBox->addItems(pPermeabilityGroup->getAllUnitNames()); + int nBaseUnitIndex = pUnitComboBox->findText( + QString::fromLatin1("D"), + Qt::MatchExactly | Qt::MatchCaseSensitive); + pUnitComboBox->setCurrentIndex(nBaseUnitIndex); + pBindPair->isUpdating = false; + } + return true; +} + +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() +{ + // 顶部和底部帮助命令共用Qt上下文帮助;字段说明和范围来自XML。 + QWhatsThis::enterWhatsThisMode(); +} + +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(); +} diff --git a/Src4/nmNum/nmSubWxs/nmSubWxs.pro b/Src4/nmNum/nmSubWxs/nmSubWxs.pro index 370bf5b..74d2e04 100644 --- a/Src4/nmNum/nmSubWxs/nmSubWxs.pro +++ b/Src4/nmNum/nmSubWxs/nmSubWxs.pro @@ -1,4 +1,6 @@ -QT += core sql gui widgets +QT += core sql gui + +greaterThan(QT_MAJOR_VERSION, 4) : QT += widgets greaterThan(QT_MAJOR_VERSION, 4) : QT += printsupport #greaterThan(QT_MAJOR_VERSION, 4) : QT += axcontainer printsupport