diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm
index 461b8f77..e7a2d926 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 d84bb1c5..9659e0c4 100644
--- a/Bin/Config/Lang/cn/nmNum_cn.ts
+++ b/Bin/Config/Lang/cn/nmNum_cn.ts
@@ -2883,6 +2883,19 @@ Reason: %1
Loop enabled启用循环
Speed (steps/second)速度(步/秒)
+
+ nmWxPebiResultViewWidget
+ No result data.暂无结果数据。
+ Result Grid结果网格
+ Color Scale色标
+ Scale Grid比例网格
+ Cell Edges单元边线
+ Well Markers井位标记
+ Well Names井名
+ Unable to display the latest result.无法显示最新结果。
+ Unable to display the latest result: %1无法显示最新结果:%1
+ Current inputs have changed. Showing the last successful result.当前输入已修改,正在显示上一次成功求解的结果。
+
nmWxPebiGridViewWidget
No grid data.暂无网格数据。
diff --git a/Include/nmNum/nmData/nmDataAnalyzeManager.h b/Include/nmNum/nmData/nmDataAnalyzeManager.h
index 53912c20..33e472ed 100644
--- a/Include/nmNum/nmData/nmDataAnalyzeManager.h
+++ b/Include/nmNum/nmData/nmDataAnalyzeManager.h
@@ -565,9 +565,15 @@ public:
void markPebiGridBuilt();
/** @brief 仅在后台划分期间输入未变化时登记网格成功。 */
bool markPebiGridBuiltIfCurrent(quint64 nGridInputRevision);
- /** @brief 几何或求解井集合变化后,使 PEBI 网格和结果失效。 */
+ /**
+ * @brief 几何或求解井集合变化后,使 PEBI 网格和结果失效。
+ * @note 只能在 Manager 所属线程调用;Release 下越线程请求会被拒绝。
+ */
void invalidatePebiGrid();
- /** @brief 产量、PVT 等变化后,仅使 PEBI 结果失效。 */
+ /**
+ * @brief 产量、PVT 等变化后,仅使 PEBI 结果失效。
+ * @note 只能在 Manager 所属线程调用;Release 下越线程请求会被拒绝。
+ */
void invalidatePebiResults();
/** @brief 判断当前 PEBI 网格是否与最新输入一致。 */
bool isPebiGridValid() const;
@@ -786,12 +792,34 @@ public:
bool bSucceeded);
/** @brief 并行结果快照或其结果井选择已经改变。 */
void sigPebiResultSnapshotChanged();
+ /**
+ * @brief 结果快照身份、存在性或相对当前输入的新鲜度已经改变。
+ * @note 当前结果井选择不发送本信号;新结果视图只监听本信号。
+ */
+ void sigPebiResultStateChanged();
private slots:
/** @brief 根据参数面板实际编辑的属性类型,使对应 PEBI 输入版本失效。 */
void onRegisteredAttributeEdited(QString sName, QVariant oValue);
private:
+ /** @brief 一次 Manager 修改前捕获的可观察结果状态。 */
+ struct PebiResultStateSnapshot
+ {
+ QSharedPointer m_pSnapshot;
+ QString m_sSnapshotId;
+ bool m_bCurrent;
+ };
+
+ /** @brief 断言并在 Release 中拒绝越线程的 PEBI 结果状态修改。 */
+ bool ensurePebiResultOwnerThread(const char* pOperation) const;
+ /** @brief 在已经确认属于 Manager 的线程中发送结果状态通知。 */
+ void emitPebiResultStateChangedOnOwnerThread();
+ /** @brief 捕获快照共享身份、稳定 ID 和相对输入版本的新鲜度。 */
+ PebiResultStateSnapshot capturePebiResultState() const;
+ /** @brief 仅在修改前后的可观察结果状态确实不同时发送新状态信号。 */
+ void notifyPebiResultStateChangedIfNeeded(
+ const PebiResultStateSnapshot& oPreviousState);
/** @brief 按 UUID 给当前实时井统一绑定或解除最后结果快照弱引用。 */
void rebindPebiResultSnapshotToWells();
/** @brief 在候选 Manager 内完整读取并校验一套 v5 成果。 */
diff --git a/Include/nmNum/nmSubWnd/nmSubWndPostprocessing.h b/Include/nmNum/nmSubWnd/nmSubWndPostprocessing.h
index 747c5eb4..8e06b720 100644
--- a/Include/nmNum/nmSubWnd/nmSubWndPostprocessing.h
+++ b/Include/nmNum/nmSubWnd/nmSubWndPostprocessing.h
@@ -3,16 +3,29 @@
#include "iSubWndBaseFit.h"
#include "nmSubWnd_global.h"
+#include
+
class nmDataDemo;
+class nmDataAnalyzeManager;
+class nmWxPebiResultViewWidget;
class NM_SUB_WND_EXPORT nmSubWndPostprocessing : public iSubWndBaseFit
{
Q_OBJECT
public:
- explicit nmSubWndPostprocessing(QWidget *parent = 0, QString sExt = "");
+ /** @brief 显式绑定创建窗口时的分析 Manager;空指针创建稳定空结果窗口。 */
+ explicit nmSubWndPostprocessing(
+ nmDataAnalyzeManager* pDataManager,
+ QWidget *parent = 0,
+ QString sExt = "");
~nmSubWndPostprocessing();
+ /** @brief 切换独立窗口观察的 Manager,并立即转发给已创建的结果控件。 */
+ void setDataManager(nmDataAnalyzeManager* pDataManager);
+ /** @brief 返回当前显式绑定的 Manager;对象销毁后自动返回空指针。 */
+ nmDataAnalyzeManager* getDataManager() const;
+
// 配置窗体
virtual void configWnd(QString sPrevInfo);
@@ -23,4 +36,10 @@ public:
public:
// 执行命令:通常来自Ribbon
virtual bool runCmdBy(QString sName, QString sID);
+
+private:
+ /** @brief 独立窗口只观察显式注入的 Manager,不回退到全局当前实例。 */
+ QPointer m_pDataManager;
+ /** @brief 主界面中的结果组合控件,由 Qt 窗口对象树拥有。 */
+ QPointer m_pResultView;
};
diff --git a/Include/nmNum/nmSubWnd/nmSubWndUtils.h b/Include/nmNum/nmSubWnd/nmSubWndUtils.h
index 98281c49..86afec72 100644
--- a/Include/nmNum/nmSubWnd/nmSubWndUtils.h
+++ b/Include/nmNum/nmSubWnd/nmSubWndUtils.h
@@ -133,6 +133,14 @@ public:
QString sRstName, \
const ZxMainWindow* pMainWnd);
+ /**
+ * @brief 统一安装或同步 PEBI 三维结果视图和只读结果参数页。
+ * @note 不要求当前已有快照;空快照会清空两个页面中的旧结果内容。
+ */
+ static bool ensurePebiResultWidgets(
+ iSubWndFitting* pSubWndFit,
+ nmDataAnalyzeManager* pDataManager);
+
/// @brief 获取缓存的TabWidget指针,确保弹窗与MDI切换使用同一个容器,指针比较才能正确
ZxTabWidget* getTabWidget() const { return m_pTabWx; }
diff --git a/Include/nmNum/nmSubWxs/nmPebiResultPlaybackController.h b/Include/nmNum/nmSubWxs/nmPebiResultPlaybackController.h
index 3e3022a4..771a4552 100644
--- a/Include/nmNum/nmSubWxs/nmPebiResultPlaybackController.h
+++ b/Include/nmNum/nmSubWxs/nmPebiResultPlaybackController.h
@@ -6,6 +6,7 @@
#include
#include
#include
+#include
class QTimer;
class nmPebiResultSceneController;
@@ -37,7 +38,7 @@ public:
virtual ~nmPebiResultPlaybackController();
/**
- * @brief 按 Snapshot ID 同步场景状态。
+ * @brief 按 Scene 成功绑定序号同步场景状态。
*
* 新快照和无快照会停止播放;同快照普通重同步保留有效播放状态。
*/
@@ -70,6 +71,8 @@ public:
bool isAvailable() const;
/** @brief 返回控制器最近一次同步到的快照 ID。 */
QString getObservedSnapshotId() const;
+ /** @brief 返回最近一次普通 Scene 同步观察到的成功绑定序号。 */
+ quint64 getObservedBindingRevision() const;
/** @brief 直接转发 Scene 中按 Catalog 排序的可用属性 ID。 */
QStringList getAvailablePropertyIds() const;
/** @brief 直接转发 Scene 已成功渲染的当前属性 ID。 */
@@ -127,6 +130,10 @@ private:
QTimer* m_pTimer;
/** @brief 最近一次同步成功的 Snapshot ID,用于识别快照替换。 */
QString m_sObservedSnapshotId;
+ /** @brief 最近一次普通 Scene 同步观察到的成功绑定序号。 */
+ quint64 m_nObservedBindingRevision;
+ /** @brief 当前活动 Timer 启动时捕获的 Scene 成功绑定序号。 */
+ quint64 m_nPlaybackBindingRevision;
/** @brief 同一快照内最后一次成功提交的 pressure 零基帧。 */
int m_nLastPressureFrameIndex;
/** @brief 当前窗口播放速度,单位为步/秒。 */
diff --git a/Include/nmNum/nmSubWxs/nmPebiResultSceneController.h b/Include/nmNum/nmSubWxs/nmPebiResultSceneController.h
index 40abdcb1..e5791842 100644
--- a/Include/nmNum/nmSubWxs/nmPebiResultSceneController.h
+++ b/Include/nmNum/nmSubWxs/nmPebiResultSceneController.h
@@ -7,6 +7,7 @@
#include
#include
#include
+#include
class nmPebiResultSceneControllerPrivate;
class nmPebiResultSnapshot;
@@ -52,11 +53,13 @@ public:
/**
* @brief 事务式提交当前属性的颜色、范围、比例和越界颜色设置。
+ * @param nExpectedBindingRevision 对话框打开时记录的成功绑定序号。
* @param sExpectedSnapshotId 对话框打开时记录的快照 ID。
* @param sExpectedPropertyId 对话框打开时记录的属性 ID。
* @note 身份或设置校验失败时不修改场景且不 Render;相同设置直接成功。
*/
bool applyCurrentDisplaySettings(
+ quint64 nExpectedBindingRevision,
const QString& sExpectedSnapshotId,
const QString& sExpectedPropertyId,
const nmPebiResultDisplaySettings& oSettings,
@@ -77,6 +80,11 @@ public:
/** @brief 返回当前是否持有可用快照且稳定结果图层仍完整。 */
bool hasSnapshot() const;
+ /**
+ * @brief 返回本窗口最近一次成功提交不同快照对象后的单调绑定序号。
+ * @note 初始值为 0;clear、失败和相同对象重复绑定均不改变该值。
+ */
+ quint64 getSnapshotBindingRevision() const;
/** @brief 返回当前快照稳定 ID;无可用快照时返回空字符串。 */
QString getCurrentSnapshotId() const;
/** @brief 按 Catalog 顺序返回当前快照真实存在的属性 ID。 */
diff --git a/Include/nmNum/nmSubWxs/nmWxPebiResultColorScaleSettingsDlg.h b/Include/nmNum/nmSubWxs/nmWxPebiResultColorScaleSettingsDlg.h
index d676fd38..02e1ed83 100644
--- a/Include/nmNum/nmSubWxs/nmWxPebiResultColorScaleSettingsDlg.h
+++ b/Include/nmNum/nmSubWxs/nmWxPebiResultColorScaleSettingsDlg.h
@@ -6,6 +6,7 @@
#include
#include
+#include
class QCheckBox;
class QComboBox;
@@ -18,8 +19,9 @@ class nmPebiResultSceneController;
/**
* @brief Saphir 风格的当前结果属性颜色设置对话框。
*
- * 对话框保存打开时的 Snapshot ID 和 Property ID,每次 Apply/OK 都交给
- * SceneController 原子复核。Cancel 只丢弃尚未 Apply 的编辑,不回滚已提交状态。
+ * 对话框保存打开时的绑定序号、Snapshot ID 和 Property ID,每次 Apply/OK
+ * 都交给 SceneController 原子复核。Cancel 只丢弃尚未 Apply 的编辑,
+ * 不回滚已经提交的状态。
*/
class NM_SUB_WXS_EXPORT nmWxPebiResultColorScaleSettingsDlg
: public QDialog
@@ -90,6 +92,7 @@ private:
void setErrorText(const QString& sError);
nmPebiResultSceneController* m_pSceneController; ///< 非所有权 Scene 真源。
+ quint64 m_nExpectedBindingRevision; ///< 打开时捕获的 Scene 成功绑定序号。
QString m_sExpectedSnapshotId; ///< 打开时捕获的快照稳定 ID。
QString m_sExpectedPropertyId; ///< 打开时捕获的属性稳定 ID。
QString m_sDisplayUnit; ///< 当前属性默认显示单位,仅用于标签。
diff --git a/Include/nmNum/nmSubWxs/nmWxPebiResultControlBar.h b/Include/nmNum/nmSubWxs/nmWxPebiResultControlBar.h
index 1c73c9e6..a519df4e 100644
--- a/Include/nmNum/nmSubWxs/nmWxPebiResultControlBar.h
+++ b/Include/nmNum/nmSubWxs/nmWxPebiResultControlBar.h
@@ -5,6 +5,7 @@
#include
#include
+#include
class QComboBox;
class QToolButton;
@@ -42,6 +43,8 @@ public:
nmPebiResultPlaybackController* getPlaybackController() const;
signals:
+ /** @brief 一次明确的用户操作已经成功提交,供组合窗口清除旧普通错误。 */
+ void operationSucceeded();
/** @brief 转发回放控制器错误,供后续组合窗口显示非阻塞提示。 */
void operationFailed(const QString& sError);
@@ -110,6 +113,8 @@ private:
QToolButton* m_pSettingsButton;
/** @brief 当前属性项对应的 Snapshot ID,仅用于避免重复重建 UI。 */
QString m_sDisplayedSnapshotId;
+ /** @brief 属性和时间列表最近一次完整展示的 Scene 成功绑定序号。 */
+ quint64 m_nDisplayedBindingRevision;
/** @brief 当前属性框已展示的有序 ID,仅用于避免重复重建 UI。 */
QStringList m_listDisplayedPropertyIds;
/** @brief 当前时间框对应的动态属性 ID。 */
diff --git a/Include/nmNum/nmSubWxs/nmWxPebiResultViewWidget.h b/Include/nmNum/nmSubWxs/nmWxPebiResultViewWidget.h
new file mode 100644
index 00000000..cde79d00
--- /dev/null
+++ b/Include/nmNum/nmSubWxs/nmWxPebiResultViewWidget.h
@@ -0,0 +1,134 @@
+#ifndef NMWXPEBIRESULTVIEWWIDGET_H
+#define NMWXPEBIRESULTVIEWWIDGET_H
+
+#include "nmSubWxs_global.h"
+
+#include
+#include
+#include
+#include
+
+class QAction;
+class QLabel;
+class QMenu;
+class QToolBar;
+
+class nmDataAnalyzeManager;
+class nmPebiResultSceneController;
+class nmPebiResultSnapshot;
+class nmWxPebiResultControlBar;
+class nmWxVTKRenderContainerWidget;
+
+/**
+ * @brief 独立的 PEBI 结果场景组合窗口。
+ *
+ * 本控件显式观察 DataManager,只组合通用渲染容器、结果 SceneController、
+ * 底部控制条和右侧 QAction。Manager 当前状态与 Scene 最后成功显示状态分别
+ * 缓存,因此新快照绑定失败时可以继续检查旧画面。
+ */
+class NM_SUB_WXS_EXPORT nmWxPebiResultViewWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ /**
+ * @param pDataManager 非所有权对象,可为空;销毁后窗口自动进入空状态。
+ * @note 非空 Manager 必须与本窗口同属 Qt GUI 线程。
+ */
+ explicit nmWxPebiResultViewWidget(
+ nmDataAnalyzeManager* pDataManager,
+ QWidget* pParent = nullptr);
+ /** @brief 按控制条、SceneController、渲染容器的顺序解除窗口资源。 */
+ virtual ~nmWxPebiResultViewWidget();
+
+ /**
+ * @brief 切换显式 Manager;相同指针只同步,不清空当前相机和场景。
+ * @note 跨线程 Manager 会被拒绝,避免同步读取快照引用时发生数据竞争。
+ */
+ void setDataManager(nmDataAnalyzeManager* pDataManager);
+ /** @brief 返回当前观察的 Manager;对象销毁后自动返回空指针。 */
+ nmDataAnalyzeManager* getDataManager() const;
+ /** @brief 合并重入请求并读取 Manager 最新结果状态。 */
+ void synchronizeFromDataManager();
+
+private slots:
+ /** @brief 响应快照身份、存在性或新鲜度变化,不响应结果井选择。 */
+ void onPebiResultStateChanged();
+ /** @brief Manager 析构时不再读取其成员,直接停止播放并清理场景。 */
+ void onDataManagerDestroyed(QObject* pObject);
+ /** @brief 将业务显隐 QAction 追加到通用右键菜单。 */
+ void appendContextMenuActions(QMenu* pMenu);
+ /** @brief 转发非阻塞操作错误,但不覆盖绑定失败或过期提示。 */
+ void onControlOperationFailed(const QString& sError);
+ /** @brief 明确操作成功后清除旧普通错误,不影响绑定失败或过期状态。 */
+ void onControlOperationSucceeded();
+
+ /** @brief 切换结果网格显隐。 */
+ void onMeshToggled(bool bVisible);
+ /** @brief 切换色标显隐。 */
+ void onScalarBarToggled(bool bVisible);
+ /** @brief 切换比例网格显隐。 */
+ void onScaleGridToggled(bool bVisible);
+ /** @brief 切换单元边线显隐。 */
+ void onCellEdgesToggled(bool bVisible);
+ /** @brief 切换二维井位标记显隐。 */
+ void onWellMarkersToggled(bool bVisible);
+ /** @brief 切换二维井名显隐。 */
+ void onWellNamesToggled(bool bVisible);
+
+private:
+ Q_DISABLE_COPY(nmWxPebiResultViewWidget)
+
+ /** @brief 创建唯一渲染容器、SceneController、工具栏、提示栏和控制条。 */
+ void createUi();
+ /** @brief 执行一次非重入的 Manager 最新状态读取和事务提交。 */
+ void synchronizeOnce();
+ /** @brief 停止播放、清理 Scene,并把所有业务控件同步为空状态。 */
+ void clearSceneAndControls(const QString& sEmptyMessage);
+ /** @brief 保存本轮已经观察到的 Manager 快照身份和新鲜度。 */
+ void rememberObservedState(
+ const QSharedPointer& pSnapshot,
+ const QString& sSnapshotId,
+ bool bCurrent);
+ /** @brief 从 SceneController 镜像六个业务 QAction 的勾选和可用状态。 */
+ void synchronizeBusinessActions();
+ /** @brief 根据“绑定失败、过期、操作错误”的固定优先级刷新提示栏。 */
+ void refreshStatusBar();
+ /** @brief 断开当前 Manager 的两个连接,不访问已经析构的对象。 */
+ void disconnectDataManager();
+
+ QPointer m_pDataManager; ///< 当前显式注入的 Manager。
+ nmWxVTKRenderContainerWidget* m_pRenderContainer; ///< 唯一通用 VTK 容器。
+ nmPebiResultSceneController* m_pSceneController; ///< 唯一结果场景控制器。
+ nmWxPebiResultControlBar* m_pControlBar; ///< 独占回放控制器和 Timer 的底栏。
+ QToolBar* m_pToolBar; ///< Qt 父对象拥有的右侧竖向工具栏。
+ QLabel* m_pStatusLabel; ///< 过期或错误的非阻塞提示栏。
+
+ QAction* m_pMeshAction; ///< 结果网格显隐动作。
+ QAction* m_pScalarBarAction; ///< 色标显隐动作。
+ QAction* m_pScaleGridAction; ///< 比例网格显隐动作。
+ QAction* m_pCellEdgesAction; ///< 单元边线显隐动作。
+ QAction* m_pWellMarkersAction; ///< 二维井位标记显隐动作。
+ QAction* m_pWellNamesAction; ///< 二维井名显隐动作。
+
+ /** @brief Manager 最近一次通知后观察到的快照强引用。 */
+ QSharedPointer m_pObservedSnapshot;
+ /** @brief Manager 最近一次通知后观察到的稳定 Snapshot ID。 */
+ QString m_sObservedSnapshotId;
+ /** @brief 最近观察到的快照是否仍与当前输入版本一致。 */
+ bool m_bObservedSnapshotCurrent;
+ /** @brief 是否已经建立过一份可用于去重的 Manager 观察状态。 */
+ bool m_bObservedStateValid;
+
+ /** @brief Scene 最后一次成功绑定的快照强引用,与观察状态相互独立。 */
+ QSharedPointer m_pDisplayedSnapshot;
+ /** @brief Scene 最后一次成功绑定的稳定 Snapshot ID。 */
+ QString m_sDisplayedSnapshotId;
+
+ QString m_sBindingError; ///< 最新 Manager 快照绑定失败的高优先级错误。
+ QString m_sOperationError; ///< 当前控制条普通操作错误。
+ bool m_bSynchronizing; ///< 防止 VTK 提交期间递归进入同步。
+ bool m_bSyncPending; ///< 重入时记录一次待处理请求,结束后循环读取最新状态。
+};
+
+#endif // NMWXPEBIRESULTVIEWWIDGET_H
diff --git a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp
index 8f72dd08..c8572862 100644
--- a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp
+++ b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp
@@ -1529,6 +1529,9 @@ void nmDataAnalyzeManager::endBackgroundUse()
nmDataWellBase *nmDataAnalyzeManager::createWell(NM_WELL_MODEL eWellType)
{
+ if(!ensurePebiResultOwnerThread("createWell")) {
+ return nullptr;
+ }
// 根据当前Fitting窗口来获取对应的井相关数据
iSubWndFitting* pSubWndFitting = nmDataAnalyzeManager::getCurrentFitting();
nmDataAnalyzeContextProvider* pContextProvider = nmDataAnalyzeContext::provider();
@@ -1571,7 +1574,7 @@ nmDataWellBase *nmDataAnalyzeManager::createWell(NM_WELL_MODEL eWellType)
// Map 中增加真实井会改变井约束点和求解器井槽位,旧网格必须立即失效。
m_vWellData.append(pWellData);
- m_oNumericalAnalysisCase.invalidateGrid();
+ invalidatePebiGrid();
emit dataChanged();
return pWellData;
}
@@ -1658,6 +1661,9 @@ bool nmDataAnalyzeManager::canRemoveWell(
bool nmDataAnalyzeManager::removeWell(nmDataWellBase* pWellData)
{
+ if(!ensurePebiResultOwnerThread("removeWell")) {
+ return false;
+ }
// 固定主井及非法指针必须在任何版本递增、图元解绑和对象删除前拒绝。
// 该检查是数据层最终兜底,不能只依赖 Map 界面的提前拦截。
if(!canRemoveWell(pWellData)) {
@@ -1670,7 +1676,7 @@ bool nmDataAnalyzeManager::removeWell(nmDataWellBase* pWellData)
// 第二步:Map 中删除任意真实井都会改变网格井集合。即使它是自动参与的
// 无产量观察井,也必须让旧网格、旧求解器顺序和旧结果一起失效。
- m_oNumericalAnalysisCase.invalidateGrid();
+ invalidatePebiGrid();
// 第三步:删除井对象前,先从“包含其他井”中移除对应 WellCode。
QVector vecIncludedWells =
@@ -2006,6 +2012,9 @@ nmDataWellBase* nmDataAnalyzeManager::findWellByInstanceId(
void nmDataAnalyzeManager::initCurWellData()
{
+ if(!ensurePebiResultOwnerThread("initCurWellData")) {
+ return;
+ }
// 获取当前默认井数据
ZxDataWell* pWellData = zxCurWell;
if(pWellData == nullptr) {
@@ -2178,12 +2187,15 @@ void nmDataAnalyzeManager::initCurWellData()
if(m_oNumericalAnalysisCase.getPrimaryWellCode().isEmpty() &&
m_pCurDataWell != nullptr &&
!m_pCurDataWell->getWellCode().isEmpty()) {
+ const PebiResultStateSnapshot oPreviousResultState =
+ capturePebiResultState();
const QString sPrimaryWellCode = m_pCurDataWell->getWellCode();
m_oNumericalAnalysisCase.setPrimaryWellCode(sPrimaryWellCode);
m_oNumericalAnalysisCase.setPrimaryWellMode(
m_pCurDataWell->getFlowSegmentCount() > 0
? NM_CaseWell_RateControlled
: NM_CaseWell_Observation);
+ notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
}
}
@@ -2428,6 +2440,9 @@ nmDataWellBase* nmDataAnalyzeManager::appendWellData(ZxDataWell* pWellData)
void nmDataAnalyzeManager::appendNmWellData(nmDataWellBase* pWellData)
{
+ if(!ensurePebiResultOwnerThread("appendNmWellData")) {
+ return;
+ }
if (pWellData == nullptr)
return;
bool bWellAdded = false;
@@ -2441,7 +2456,7 @@ void nmDataAnalyzeManager::appendNmWellData(nmDataWellBase* pWellData)
// 使旧网格失效。已由 createWell() 登记的井不重复增加输入版本号。
if(bWellAdded)
{
- m_oNumericalAnalysisCase.invalidateGrid();
+ invalidatePebiGrid();
}
// 通知面板新增井分组
@@ -2462,6 +2477,9 @@ void nmDataAnalyzeManager::appendNmWellData(nmDataWellBase* pWellData)
bool nmDataAnalyzeManager::replaceWellData(nmDataWellBase* pOldWell,
nmDataWellBase* pNewWell)
{
+ if(!ensurePebiResultOwnerThread("replaceWellData")) {
+ return false;
+ }
// 先完成所有无副作用的前置校验,失败时保持双方所有权不变。
if (pOldWell == nullptr || pNewWell == nullptr ||
pOldWell == pNewWell || m_vWellData.contains(pNewWell) ||
@@ -2495,7 +2513,7 @@ bool nmDataAnalyzeManager::replaceWellData(nmDataWellBase* pOldWell,
}
m_vWellData.insert(qMin(nOldIndex, m_vWellData.size()), pNewWell);
// 井型改变会改变 PEBI 几何输入,旧求解器顺序和网格必须整体失效。
- m_oNumericalAnalysisCase.invalidateGrid();
+ invalidatePebiGrid();
if (bWasCurrentWell)
{
m_pCurDataWell = pNewWell;
@@ -3894,11 +3912,14 @@ NM_Grid_Type nmDataAnalyzeManager::getGridType()
void nmDataAnalyzeManager::setGridType(NM_Grid_Type newGridType)
{
+ if(!ensurePebiResultOwnerThread("setGridType")) {
+ return;
+ }
if(m_eGridType == newGridType) {
return;
}
m_eGridType = newGridType;
- m_oNumericalAnalysisCase.invalidateGrid();
+ invalidatePebiGrid();
}
NM_SOLVER_MODEL_TYPE nmDataAnalyzeManager::getSolverModelType() const
@@ -3908,11 +3929,14 @@ NM_SOLVER_MODEL_TYPE nmDataAnalyzeManager::getSolverModelType() const
void nmDataAnalyzeManager::setSolverModelType(NM_SOLVER_MODEL_TYPE newSolverModelType)
{
+ if(!ensurePebiResultOwnerThread("setSolverModelType")) {
+ return;
+ }
if(m_eSolverModelType == newSolverModelType) {
return;
}
m_eSolverModelType = newSolverModelType;
- m_oNumericalAnalysisCase.invalidateResults();
+ invalidatePebiResults();
}
int nmDataAnalyzeManager::getPebiSolverType() const
@@ -3922,11 +3946,14 @@ int nmDataAnalyzeManager::getPebiSolverType() const
void nmDataAnalyzeManager::setPebiSolverType(int nSolverType)
{
+ if(!ensurePebiResultOwnerThread("setPebiSolverType")) {
+ return;
+ }
if((nSolverType == PebiSolverCpuAccelerated ||
nSolverType == PebiSolverOriginal) &&
m_nPebiSolverType != nSolverType) {
m_nPebiSolverType = nSolverType;
- m_oNumericalAnalysisCase.invalidateResults();
+ invalidatePebiResults();
}
}
@@ -3937,11 +3964,14 @@ int nmDataAnalyzeManager::getPebiOmpThreads() const
void nmDataAnalyzeManager::setPebiOmpThreads(int nOmpThreads)
{
+ if(!ensurePebiResultOwnerThread("setPebiOmpThreads")) {
+ return;
+ }
if(nOmpThreads == 1 || nOmpThreads == 2 || nOmpThreads == 4
|| nOmpThreads == 8 || nOmpThreads == 16) {
if(m_nPebiOmpThreads != nOmpThreads) {
m_nPebiOmpThreads = nOmpThreads;
- m_oNumericalAnalysisCase.invalidateResults();
+ invalidatePebiResults();
}
}
}
@@ -3953,10 +3983,13 @@ int nmDataAnalyzeManager::getPebiIluReuseSteps() const
void nmDataAnalyzeManager::setPebiIluReuseSteps(int nIluReuseSteps)
{
+ if(!ensurePebiResultOwnerThread("setPebiIluReuseSteps")) {
+ return;
+ }
if(nIluReuseSteps >= 1 && nIluReuseSteps <= 100 &&
m_nPebiIluReuseSteps != nIluReuseSteps) {
m_nPebiIluReuseSteps = nIluReuseSteps;
- m_oNumericalAnalysisCase.invalidateResults();
+ invalidatePebiResults();
}
}
@@ -4161,9 +4194,12 @@ nmDataLayer* nmDataAnalyzeManager::getLayerData()
void nmDataAnalyzeManager::setLayers(QVector vecLayers)
{
+ if(!ensurePebiResultOwnerThread("setLayers")) {
+ return;
+ }
m_vecLayers = vecLayers;
// 几何分层会改变储层离散输入,旧网格和旧结果不再有效。
- m_oNumericalAnalysisCase.invalidateGrid();
+ invalidatePebiGrid();
emit dataChanged();
}
@@ -4311,16 +4347,22 @@ nmDataAnalyzeManager::getNumericalAnalysisCase() const
void nmDataAnalyzeManager::setPrimaryWellCode(const QString& sWellCode)
{
+ if(!ensurePebiResultOwnerThread("setPrimaryWellCode")) {
+ return;
+ }
if(!sWellCode.isEmpty() && findWellByCode(sWellCode) == nullptr) {
return;
}
+ const PebiResultStateSnapshot oPreviousResultState =
+ capturePebiResultState();
const quint64 nOldRevision =
m_oNumericalAnalysisCase.getGridInputRevision();
m_oNumericalAnalysisCase.setPrimaryWellCode(sWellCode);
if(m_oNumericalAnalysisCase.getGridInputRevision() != nOldRevision) {
emit dataChanged();
}
+ notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
}
QString nmDataAnalyzeManager::getPrimaryWellCode() const
@@ -4330,12 +4372,18 @@ QString nmDataAnalyzeManager::getPrimaryWellCode() const
void nmDataAnalyzeManager::setIncludeOtherWells(bool bInclude)
{
+ if(!ensurePebiResultOwnerThread("setIncludeOtherWells")) {
+ return;
+ }
+ const PebiResultStateSnapshot oPreviousResultState =
+ capturePebiResultState();
const bool bOldInclude = m_oNumericalAnalysisCase.getIncludeOtherWells();
m_oNumericalAnalysisCase.setIncludeOtherWells(bInclude);
if(bOldInclude != m_oNumericalAnalysisCase.getIncludeOtherWells()) {
// 开关只改变当前求解输入。最后结果下拉框继续使用快照冻结的 UUID 列表。
emit dataChanged();
}
+ notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
}
bool nmDataAnalyzeManager::getIncludeOtherWells() const
@@ -4345,6 +4393,11 @@ bool nmDataAnalyzeManager::getIncludeOtherWells() const
void nmDataAnalyzeManager::setPebiGridControl(double dGridControl)
{
+ if(!ensurePebiResultOwnerThread("setPebiGridControl")) {
+ return;
+ }
+ const PebiResultStateSnapshot oPreviousResultState =
+ capturePebiResultState();
const quint64 nOldGridInputRevision =
m_oNumericalAnalysisCase.getGridInputRevision();
m_oNumericalAnalysisCase.setPebiGridControl(dGridControl);
@@ -4353,6 +4406,7 @@ void nmDataAnalyzeManager::setPebiGridControl(double dGridControl)
// 分析方案已完成网格和结果失效,这里只通知已打开的网格窗口刷新。
emit dataChanged();
}
+ notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
}
double nmDataAnalyzeManager::getPebiGridControl() const
@@ -4363,6 +4417,11 @@ double nmDataAnalyzeManager::getPebiGridControl() const
void nmDataAnalyzeManager::setIncludedCalculationWells(
const QVector& vecWells)
{
+ if(!ensurePebiResultOwnerThread("setIncludedCalculationWells")) {
+ return;
+ }
+ const PebiResultStateSnapshot oPreviousResultState =
+ capturePebiResultState();
const QVector vecOldWells =
m_oNumericalAnalysisCase.getIncludedWells();
QVector vecValidWells;
@@ -4399,6 +4458,7 @@ void nmDataAnalyzeManager::setIncludedCalculationWells(
// 第三步:这里只通知当前输入变化,不修改独立快照中的结果井选择。
emit dataChanged();
}
+ notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
}
QVector
@@ -4610,12 +4670,24 @@ bool nmDataAnalyzeManager::markPebiGridBuiltIfCurrent(
void nmDataAnalyzeManager::invalidatePebiGrid()
{
+ if(!ensurePebiResultOwnerThread("invalidatePebiGrid")) {
+ return;
+ }
+ const PebiResultStateSnapshot oPreviousResultState =
+ capturePebiResultState();
m_oNumericalAnalysisCase.invalidateGrid();
+ notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
}
void nmDataAnalyzeManager::invalidatePebiResults()
{
+ if(!ensurePebiResultOwnerThread("invalidatePebiResults")) {
+ return;
+ }
+ const PebiResultStateSnapshot oPreviousResultState =
+ capturePebiResultState();
m_oNumericalAnalysisCase.invalidateResults();
+ notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
}
bool nmDataAnalyzeManager::isPebiGridValid() const
@@ -4649,6 +4721,59 @@ bool nmDataAnalyzeManager::isPebiResultSnapshotCurrent() const
m_oNumericalAnalysisCase.getResultInputRevision();
}
+nmDataAnalyzeManager::PebiResultStateSnapshot
+nmDataAnalyzeManager::capturePebiResultState() const
+{
+ PebiResultStateSnapshot oState;
+ oState.m_bCurrent = false;
+ if(!ensurePebiResultOwnerThread("capturePebiResultState")) {
+ return oState;
+ }
+ oState.m_pSnapshot = m_pPebiResultSnapshot;
+ oState.m_sSnapshotId = m_pPebiResultSnapshot.isNull()
+ ? QString() : m_pPebiResultSnapshot->getSnapshotId();
+ oState.m_bCurrent = isPebiResultSnapshotCurrent();
+ return oState;
+}
+
+void nmDataAnalyzeManager::notifyPebiResultStateChangedIfNeeded(
+ const PebiResultStateSnapshot& oPreviousState)
+{
+ if(!ensurePebiResultOwnerThread(
+ "notifyPebiResultStateChangedIfNeeded")) {
+ return;
+ }
+ const PebiResultStateSnapshot oCurrentState =
+ capturePebiResultState();
+ const bool bSnapshotIdentityChanged =
+ oPreviousState.m_pSnapshot.data() !=
+ oCurrentState.m_pSnapshot.data() ||
+ oPreviousState.m_sSnapshotId != oCurrentState.m_sSnapshotId;
+ if(bSnapshotIdentityChanged ||
+ oPreviousState.m_bCurrent != oCurrentState.m_bCurrent) {
+ emitPebiResultStateChangedOnOwnerThread();
+ }
+}
+
+bool nmDataAnalyzeManager::ensurePebiResultOwnerThread(
+ const char* pOperation) const
+{
+ const bool bOwnerThread = QThread::currentThread() == thread();
+ Q_ASSERT_X(bOwnerThread, "nmDataAnalyzeManager", pOperation);
+ if(!bOwnerThread) {
+ // 结果快照引用和输入版本均未加锁;Release 也必须在首次读取前拒绝。
+ qWarning() << "Rejected cross-thread PEBI result state mutation:"
+ << pOperation;
+ }
+ return bOwnerThread;
+}
+
+void nmDataAnalyzeManager::emitPebiResultStateChangedOnOwnerThread()
+{
+ Q_ASSERT(QThread::currentThread() == thread());
+ emit sigPebiResultStateChanged();
+}
+
QString nmDataAnalyzeManager::getCurrentResultWellInstanceId() const
{
return m_sCurrentResultWellInstanceId;
@@ -4806,12 +4931,15 @@ bool nmDataAnalyzeManager::commitPebiResultSnapshot(
return false;
}
+ const PebiResultStateSnapshot oPreviousResultState =
+ capturePebiResultState();
// 发布点只替换只读引用;先前快照由仍在查看它的窗口延迟释放。
m_pPebiResultSnapshot = pPublishedCandidate;
m_sCurrentResultWellInstanceId = sNextWellInstanceId;
pCandidate.clear();
rebindPebiResultSnapshotToWells();
emit sigPebiResultSnapshotChanged();
+ notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
return true;
}
@@ -4843,16 +4971,22 @@ void nmDataAnalyzeManager::rebindPebiResultSnapshotToWells()
void nmDataAnalyzeManager::clearPebiResultSnapshot()
{
+ if(!ensurePebiResultOwnerThread("clearPebiResultSnapshot")) {
+ return;
+ }
if(m_pPebiResultSnapshot.isNull() &&
m_sCurrentResultWellInstanceId.isEmpty())
{
return;
}
+ const PebiResultStateSnapshot oPreviousResultState =
+ capturePebiResultState();
m_pPebiResultSnapshot.clear();
m_sCurrentResultWellInstanceId.clear();
rebindPebiResultSnapshotToWells();
emit sigPebiResultSnapshotChanged();
+ notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
}
// 设置当前查看井
@@ -4869,9 +5003,12 @@ nmDataWellBase* nmDataAnalyzeManager::getCurWellData()
void nmDataAnalyzeManager::notifyDataChanged()
{
+ if(!ensurePebiResultOwnerThread("notifyDataChanged")) {
+ return;
+ }
// 本入口用于几何、井集合等网格输入;仅影响求解的参数应调用
// invalidatePebiResults(),避免无意义地重建当前有效网格。
- m_oNumericalAnalysisCase.invalidateGrid();
+ invalidatePebiGrid();
emit dataChanged();
}
@@ -4880,6 +5017,9 @@ void nmDataAnalyzeManager::onRegisteredAttributeEdited(
QString sName,
QVariant oValue)
{
+ if(!ensurePebiResultOwnerThread("onRegisteredAttributeEdited")) {
+ return;
+ }
Q_UNUSED(oValue);
if(isPebiGridInputAttributeName(sName)) {
@@ -4894,7 +5034,10 @@ void nmDataAnalyzeManager::onRegisteredAttributeEdited(
void nmDataAnalyzeManager::notifyGeometryListChanged()
{
- m_oNumericalAnalysisCase.invalidateGrid();
+ if(!ensurePebiResultOwnerThread("notifyGeometryListChanged")) {
+ return;
+ }
+ invalidatePebiGrid();
emit sigGeometryListChanged();
emit dataChanged();
}
@@ -5206,9 +5349,13 @@ QVector nmDataAnalyzeManager::getPropertyInterpo
void nmDataAnalyzeManager::setPropertyInterpolationDataSets(
const QVector& dataSets)
{
+ if(!ensurePebiResultOwnerThread(
+ "setPropertyInterpolationDataSets")) {
+ return;
+ }
m_vecPropertyInterpolationDataSets = dataSets;
// 属性插值只覆盖求解器单元属性,不改变二维 PEBI 拓扑。
- m_oNumericalAnalysisCase.invalidateResults();
+ invalidatePebiResults();
}
// 从 JSON 文件读取数据到 C++ 对象
@@ -6642,6 +6789,9 @@ bool nmDataAnalyzeManager::validateNmResultV3Window(
bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir)
{
+ if(!ensurePebiResultOwnerThread("loadNmResult")) {
+ return false;
+ }
try {
// v5 先在独立候选对象上完成全部解析和业务校验。
// 任一步失败时只析构候选,当前窗口已经发布的数据保持不变。
@@ -6660,10 +6810,13 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir)
}
}
+ const PebiResultStateSnapshot oPreviousResultState =
+ capturePebiResultState();
swapLoadedProjectState(oLoadedManager);
rebuildLoadedAttributeRegistry();
rebindPebiResultSnapshotToWells();
emit sigPebiResultSnapshotChanged();
+ notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
return true;
}
catch(const std::bad_alloc&) {
diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp
index eff98bcb..17af29f3 100644
--- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp
+++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp
@@ -37,6 +37,7 @@
#include "nmSubWndMain.h"
#include "nmPlotDialogContext.h"
#include "nmPlotDialogContextProvider.h"
+#include "nmSubWndUtils.h"
#include "nmWxGridVTKContainerWidget.h"
#include "nmWxSelectWellsDlg.h"
@@ -60,7 +61,6 @@
#include "nmReservoirPropertiesDataSource.h"
#include "nmWxReservoirPropertiesEditor.h"
#include "nmWxPropertyInterpolationDlg.h"
-#include "nmWxPostprocessingAnimationWidget.h"
#include "nmWxGeoRefDlg.h"
#include "iSubWndFitting.h"
@@ -2070,77 +2070,8 @@ bool nmSubWndMain::populateResultWidgets(
iSubWndFitting* pSubWndFit,
nmDataAnalyzeManager* pDataManager)
{
- if(pSubWndFit == nullptr || pDataManager == nullptr ||
- pDataManager->getGridType() != NM_Grid_PEBI ||
- nmDataAnalyzeManager::findManagerByFitting(pSubWndFit) != pDataManager) {
- return false;
- }
-
- QString sErrorMessage;
- QWidget* p3DWidget = pSubWndFit->getFitSubRstWxOf(
- FSRT_Nm3D, true, &sErrorMessage);
- if(p3DWidget == nullptr) {
- return false;
- }
-
- nmWxPostprocessingAnimationWidget* pVtkWidget =
- p3DWidget->findChild();
- if(pVtkWidget != nullptr) {
- // 复用既有渲染窗口和 VTK 管道,只切换本轮已整体提交的数据。
- pVtkWidget->refreshResult();
- } else {
- pVtkWidget = new nmWxPostprocessingAnimationWidget(NULL, pDataManager);
- QLayout* p3DLayout = p3DWidget->layout();
- if(p3DLayout == nullptr) {
- p3DLayout = new QVBoxLayout(p3DWidget);
- } else {
- QLayoutItem* pItem = nullptr;
- while((pItem = p3DLayout->takeAt(0)) != nullptr) {
- if(pItem->widget() != nullptr) {
- delete pItem->widget();
- }
- delete pItem;
- }
- }
- p3DLayout->addWidget(pVtkWidget);
- }
-
- // 结果参数界面
- QWidget* pResultPage = pSubWndFit->getFitSubRstWxOf(
- FSRT_NmRst, true, &sErrorMessage);
- if(pResultPage == nullptr) {
- return false;
- }
-
- nmWxParaPropertyPebi* pResultWidget =
- pResultPage->findChild();
- if(pResultWidget == nullptr) {
- // 与左侧共用属性面板类,结果区使用独立实例和只读参数列表。
- pResultWidget = new nmWxParaPropertyPebi;
- pResultWidget->initUI();
- pResultWidget->setDataManager(pDataManager);
- // 保持 8 月 24 日版本的稳定顺序:离屏完成参数构建后再加入可见布局。
- pResultWidget->rebuildResultParas();
-
- QLayout* pResultLayout = pResultPage->layout();
- if(pResultLayout == nullptr) {
- pResultLayout = new QVBoxLayout(pResultPage);
- } else {
- QLayoutItem* pItem = nullptr;
- while((pItem = pResultLayout->takeAt(0)) != nullptr) {
- if(pItem->widget() != nullptr) {
- delete pItem->widget();
- }
- delete pItem;
- }
- }
- pResultLayout->addWidget(pResultWidget);
- return true;
- }
-
- pResultWidget->setDataManager(pDataManager);
- pResultWidget->rebuildResultParas();
- return true;
+ return nmSubWndUtils::ensurePebiResultWidgets(
+ pSubWndFit, pDataManager);
}
void nmSubWndMain::schedulePendingResultWidgets()
diff --git a/Src/nmNum/nmSubWnd/nmSubWndPostprocessing.cpp b/Src/nmNum/nmSubWnd/nmSubWndPostprocessing.cpp
index 71c9860a..8ad8376c 100644
--- a/Src/nmNum/nmSubWnd/nmSubWndPostprocessing.cpp
+++ b/Src/nmNum/nmSubWnd/nmSubWndPostprocessing.cpp
@@ -32,19 +32,24 @@
#endif
#include "nmDataDemo.h"
+#include "nmDataAnalyzeManager.h"
#include "nmDataLogFile.h"
#include "nmGuiPlot.h"
#include "nmSubWndPostprocessing.h"
-#include "nmWxPostprocessingWidget.h"
-#include "nmWxPostprocessingAnimationWidget.h"
+#include "nmWxPebiResultViewWidget.h"
#include
#include
-nmSubWndPostprocessing::nmSubWndPostprocessing(QWidget *parent, QString sExt) :
- iSubWndBaseFit(parent, sExt)
+nmSubWndPostprocessing::nmSubWndPostprocessing(
+ nmDataAnalyzeManager* pDataManager,
+ QWidget *parent,
+ QString sExt) :
+ iSubWndBaseFit(parent, sExt),
+ m_pDataManager(pDataManager),
+ m_pResultView(nullptr)
{
m_sMdiWndType = s_MdiType_DataAnal;
setWindowTitle(tr("nmSubWndPostprocessing"));
@@ -56,6 +61,21 @@ nmSubWndPostprocessing::~nmSubWndPostprocessing()
#endif
}
+void nmSubWndPostprocessing::setDataManager(
+ nmDataAnalyzeManager* pDataManager)
+{
+ m_pDataManager = pDataManager;
+ if(!m_pResultView.isNull()) {
+ // 已有独立窗口复用同一个渲染基础设施,只切换其显式数据来源。
+ m_pResultView->setDataManager(pDataManager);
+ }
+}
+
+nmDataAnalyzeManager* nmSubWndPostprocessing::getDataManager() const
+{
+ return m_pDataManager.data();
+}
+
void nmSubWndPostprocessing::configWnd(QString sPrevInfo)
{
iSubWndBaseFit::configWnd(sPrevInfo);
@@ -90,13 +110,10 @@ void nmSubWndPostprocessing::initUI()
QWidget* nmSubWndPostprocessing::initUiMainWx()
{
- // 渲染
- //QString sDir = ZxBaseUtil::getCurWellDirOf("Nm/Solver");
- //QString sFile = sDir + "oilPost.vtk";
-// nmWxPostprocessingWidget* vtkWidget = new nmWxPostprocessingWidget(NULL);
-// vtkWidget->loadVTKFile(sFile);
- nmWxPostprocessingAnimationWidget* vtkWidget = new nmWxPostprocessingAnimationWidget(NULL);
- return vtkWidget;
+ // 业务窗口只组合新结果视图,Manager 为空时由控件显示稳定空状态。
+ m_pResultView = new nmWxPebiResultViewWidget(
+ m_pDataManager.data(), NULL);
+ return m_pResultView.data();
}
bool nmSubWndPostprocessing::runCmdBy(QString sName, QString sID)
diff --git a/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp b/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp
index 3cd810da..2b05a8d0 100644
--- a/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp
+++ b/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp
@@ -3,9 +3,12 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
+#include
#include "zxSysUtils.h"
#include "ZxUiBase.h"
@@ -36,7 +39,7 @@
#include "nmWxGridDlg.h"
-#include "nmWxPostprocessingAnimationWidget.h"
+#include "nmWxPebiResultViewWidget.h"
#include "nmGuiPlot.h"
#include "ZxObjBase.h"
@@ -162,6 +165,17 @@ iSubWnd* nmSubWndUtils::makesureSubWnd(QString sID, QString sExt, \
{
bCreatedNewOne = false;
+ // 独立后处理窗口允许先以空 Manager 创建。再次点击入口时,
+ // 必须把当前分析显式转发给既有窗口,不能沿用旧绑定。
+ if(_isSame(sID, "5116")) {
+ nmSubWndPostprocessing* pPostprocessing =
+ dynamic_cast(pSubWnd);
+ if(pPostprocessing != nullptr) {
+ pPostprocessing->setDataManager(
+ nmDataAnalyzeManager::getCurrentInstance());
+ }
+ }
+
// TODO 根据需要,是否需要激活当前
if (i != pTabWx->currentIndex())
{
@@ -235,7 +249,11 @@ iSubWnd* nmSubWndUtils::makesureSubWnd(QString sID, QString sExt, \
pSubWnd = pSubWndGrid;
} else if(nID == 5116) {
// 后处理
- nmSubWndPostprocessing* pSubWndPostProcessing = new nmSubWndPostprocessing(NULL, sExt);
+ nmDataAnalyzeManager* pDataManager =
+ nmDataAnalyzeManager::getCurrentInstance();
+ // 没有当前分析时仍创建空窗口,后续可通过显式 setDataManager() 绑定。
+ nmSubWndPostprocessing* pSubWndPostProcessing =
+ new nmSubWndPostprocessing(pDataManager, NULL, sExt);
pSubWnd = pSubWndPostProcessing;
}
@@ -447,6 +465,86 @@ void nmSubWndUtils::setupNumericalDockWidgets(nmDataAnalyzeManager* pDataManager
pWxDockNm2->setWidget(pParaWx);
}
+bool nmSubWndUtils::ensurePebiResultWidgets(
+ iSubWndFitting* pSubWndFit,
+ nmDataAnalyzeManager* pDataManager)
+{
+ if(pSubWndFit == nullptr || pDataManager == nullptr ||
+ pDataManager->getGridType() != NM_Grid_PEBI ||
+ nmDataAnalyzeManager::findManagerByFitting(pSubWndFit) !=
+ pDataManager) {
+ return false;
+ }
+
+ QString sErrorMessage;
+ QWidget* p3DPage = pSubWndFit->getFitSubRstWxOf(
+ FSRT_Nm3D, true, &sErrorMessage);
+ QWidget* pResultPage = pSubWndFit->getFitSubRstWxOf(
+ FSRT_NmRst, true, &sErrorMessage);
+ if(p3DPage == nullptr || pResultPage == nullptr) {
+ return false;
+ }
+
+ nmWxPebiResultViewWidget* pResultView =
+ p3DPage->findChild();
+ if(pResultView == nullptr) {
+ // 首次安装时复用载体已有布局,只移除旧业务控件和布局项。
+ QLayout* p3DLayout = p3DPage->layout();
+ if(p3DLayout == nullptr) {
+ QVBoxLayout* pNewLayout = new QVBoxLayout(p3DPage);
+ pNewLayout->setContentsMargins(0, 0, 0, 0);
+ pNewLayout->setSpacing(0);
+ p3DLayout = pNewLayout;
+ } else {
+ QLayoutItem* pItem = nullptr;
+ while((pItem = p3DLayout->takeAt(0)) != nullptr) {
+ if(pItem->widget() != nullptr) {
+ delete pItem->widget();
+ }
+ delete pItem;
+ }
+ }
+ pResultView = new nmWxPebiResultViewWidget(
+ pDataManager, p3DPage);
+ p3DLayout->addWidget(pResultView);
+ } else {
+ // 相同 Manager 只会同步;不同 Manager 才由组合窗口完整清场再绑定。
+ pResultView->setDataManager(pDataManager);
+ }
+
+ nmWxParaPropertyPebi* pResultParameters =
+ pResultPage->findChild();
+ if(pResultParameters == nullptr) {
+ QLayout* pResultLayout = pResultPage->layout();
+ if(pResultLayout == nullptr) {
+ QVBoxLayout* pNewLayout = new QVBoxLayout(pResultPage);
+ pNewLayout->setContentsMargins(0, 0, 0, 0);
+ pNewLayout->setSpacing(0);
+ pResultLayout = pNewLayout;
+ } else {
+ QLayoutItem* pItem = nullptr;
+ while((pItem = pResultLayout->takeAt(0)) != nullptr) {
+ if(pItem->widget() != nullptr) {
+ delete pItem->widget();
+ }
+ delete pItem;
+ }
+ }
+ // 保持既有稳定顺序:先在可见布局之外完成只读参数构建,再挂入页面。
+ pResultParameters = new nmWxParaPropertyPebi();
+ pResultParameters->initUI();
+ pResultParameters->setDataManager(pDataManager);
+ pResultParameters->rebuildResultParas();
+ pResultLayout->addWidget(pResultParameters);
+ return true;
+ }
+
+ // 结果参数页没有监听快照状态信号,每次生产入口同步都必须显式重建。
+ pResultParameters->setDataManager(pDataManager);
+ pResultParameters->rebuildResultParas();
+ return true;
+}
+
bool nmSubWndUtils::fillNmDockWxs(iSubWnd* pSubWnd)
{
iSubWndFitting* pSubWndF = dynamic_cast(pSubWnd);
@@ -1062,113 +1160,19 @@ bool nmSubWndUtils::loadRsts(iSubWnd* pSubWnd, \
return false;
}
- if(pWxDockNm1->widget() != nullptr &&
- pWxDockNm2->widget() != nullptr) {
- return true; // 与 develop 一致,已有数值面板不重复创建。
+ if(pWxDockNm1->widget() == nullptr ||
+ pWxDockNm2->widget() == nullptr) {
+ // 缺失左侧数值面板时先完成 Dock 装配,再统一维护两个结果页面。
+ nmSubWndMain* pSubWndMain = new nmSubWndMain(NULL, "");
+ ZxMainWindow* pMainWnd1 = const_cast(pMainWnd);
+ pSubWndMain->setMainWindow(pMainWnd1);
+ setupNumericalDockWidgets(pDataManager, pSubWndMain,
+ pWxDockNm1, pWxDockNm2);
}
- // 创建Map,暂时
- nmSubWndMain* pSubWndMain = new nmSubWndMain(NULL, "");
- ZxMainWindow* pMainWnd1 = const_cast(pMainWnd);
- pSubWndMain->setMainWindow(pMainWnd1);
- setupNumericalDockWidgets(pDataManager, pSubWndMain,
- pWxDockNm1, pWxDockNm2);
-
- // v3 场图只以独立快照是否存在为准,不再依赖旧时间步 Map。
- if(pDataManager->hasPebiResultSnapshot()) {
- QString sErrorMessage;
- // 刷新场图
- QWidget* p3DWidget = pSubWndF->getFitSubRstWxOf(FSRT_Nm3D, true, &sErrorMessage);
-
- // 创建 vtkWidget
- nmWxPostprocessingAnimationWidget* pVtkWidget = nullptr;
-
- pVtkWidget = new nmWxPostprocessingAnimationWidget(
- nullptr, pDataManager);
-
- if(p3DWidget == nullptr || pVtkWidget == nullptr) {
- delete pVtkWidget;
- return false;
- }
-
- // 先清理
- // 获取当前布局
- QLayout* p3DLayout = p3DWidget->layout();
-
- if(p3DLayout) {
- // 移除布局中的所有控件
- QLayoutItem* item;
-
- while((item = p3DLayout->takeAt(0))) {
- if(item->widget()) {
- // 删除控件
- delete item->widget();
- }
-
- delete item; // 删除布局项
- }
-
- // 删除布局
- delete p3DLayout;
- }
-
- // 确保布局被移除
- p3DWidget->setLayout(nullptr);
-
- // 创建一个垂直布局管理器
- QVBoxLayout* p3DnewLayout = new QVBoxLayout(p3DWidget);
-
- // 将现有的控件添加到布局中
- p3DnewLayout->addWidget(pVtkWidget);
-
- // 设置 widget 的布局
- p3DWidget->setLayout(p3DnewLayout);
-
- // 刷新结果参数
- // 结果参数界面
- QWidget* pRstWidget = pSubWndF->getFitSubRstWxOf(FSRT_NmRst, true, &sErrorMessage);
- if(pRstWidget == nullptr) {
- return false;
- }
-
- // 与左侧共用属性面板类,结果区使用独立实例和只读参数列表。
- nmWxParaPropertyPebi* pResultWidget = new nmWxParaPropertyPebi();
- pResultWidget->initUI();
- pResultWidget->setDataManager(pDataManager);
- pResultWidget->rebuildResultParas();
-
- // 先清理
- // 获取当前布局
- QLayout* pRstLayout = pRstWidget->layout();
-
- if(pRstLayout) {
- // 移除布局中的所有控件
- QLayoutItem* item;
-
- while((item = pRstLayout->takeAt(0))) {
- if(item->widget()) {
- // 删除控件
- delete item->widget();
- }
-
- delete item; // 删除布局项
- }
-
- // 删除布局
- delete pRstLayout;
- }
-
- // 确保布局被移除
- pRstWidget->setLayout(nullptr);
-
- // 创建一个垂直布局管理器
- QVBoxLayout* pRstnewLayout = new QVBoxLayout(pRstWidget);
-
- // 将现有的控件添加到布局中
- pRstnewLayout->addWidget(pResultWidget);
-
- // 设置 widget 的布局
- pRstWidget->setLayout(pRstnewLayout);
+ // 即使两个 Dock 已存在,也必须刷新三维结果和只读结果参数页。
+ if(!ensurePebiResultWidgets(pSubWndF, pDataManager)) {
+ return false;
}
}
diff --git a/Src/nmNum/nmSubWxs/nmPebiResultPlaybackController.cpp b/Src/nmNum/nmSubWxs/nmPebiResultPlaybackController.cpp
index 3b9b6dc8..427bbb35 100644
--- a/Src/nmNum/nmSubWxs/nmPebiResultPlaybackController.cpp
+++ b/Src/nmNum/nmSubWxs/nmPebiResultPlaybackController.cpp
@@ -43,6 +43,8 @@ nmPebiResultPlaybackController::nmPebiResultPlaybackController(
: QObject(pParent),
m_pSceneController(pSceneController),
m_pTimer(new QTimer(this)),
+ m_nObservedBindingRevision(0),
+ m_nPlaybackBindingRevision(0),
m_nLastPressureFrameIndex(0),
m_nStepsPerSecond(DefaultPlaybackSpeed),
m_bLoopEnabled(false),
@@ -62,10 +64,11 @@ nmPebiResultPlaybackController::~nmPebiResultPlaybackController()
void nmPebiResultPlaybackController::synchronizeFromScene()
{
- const QString sPreviousSnapshotId = m_sObservedSnapshotId;
+ const quint64 nPreviousBindingRevision =
+ m_nObservedBindingRevision;
const bool bAvailable = synchronizeSceneIdentity();
const bool bSnapshotChanged = bAvailable &&
- sPreviousSnapshotId != m_sObservedSnapshotId;
+ nPreviousBindingRevision != m_nObservedBindingRevision;
// 无快照或新快照必须终止旧回放;同快照同步只校正 Scene 已提交状态。
if(!bAvailable || bSnapshotChanged) {
@@ -213,6 +216,16 @@ bool nmPebiResultPlaybackController::togglePlayback()
}
}
+ const quint64 nBindingRevision =
+ m_pSceneController->getSnapshotBindingRevision();
+ if(nBindingRevision == 0 ||
+ nBindingRevision != m_nObservedBindingRevision) {
+ return finishFailedOperation(playbackText(
+ "Result playback is unavailable."));
+ }
+
+ // Timer 归属只在全部校验和末帧复位成功后发布,防止旧回调推进新绑定。
+ m_nPlaybackBindingRevision = nBindingRevision;
m_pTimer->setInterval(calculatePlaybackInterval());
m_pTimer->start();
m_bPlaying = true;
@@ -263,6 +276,11 @@ QString nmPebiResultPlaybackController::getObservedSnapshotId() const
return m_sObservedSnapshotId;
}
+quint64 nmPebiResultPlaybackController::getObservedBindingRevision() const
+{
+ return m_nObservedBindingRevision;
+}
+
QStringList nmPebiResultPlaybackController::getAvailablePropertyIds() const
{
return isAvailable()
@@ -342,14 +360,23 @@ bool nmPebiResultPlaybackController::isPlaying() const
void nmPebiResultPlaybackController::onPlaybackTimeout()
{
- const QString sPreviousSnapshotId = m_sObservedSnapshotId;
- if(!synchronizeSceneIdentity()) {
+ // 先验证 Timer 启动时捕获的绑定,已经排队的旧事件不得推进后来绑定的快照。
+ const bool bTimerOwnsCurrentBinding =
+ m_bPlaying && m_pTimer != nullptr && m_pTimer->isActive() &&
+ m_pSceneController != nullptr &&
+ m_pSceneController->hasSnapshot() &&
+ m_nPlaybackBindingRevision != 0 &&
+ m_pSceneController->getSnapshotBindingRevision() ==
+ m_nPlaybackBindingRevision;
+ if(!bTimerOwnsCurrentBinding) {
stopTimerWithoutSignal();
emit stateChanged();
return;
}
- if(sPreviousSnapshotId != m_sObservedSnapshotId) {
- // 旧 Timer 已排队的回调不得推进刚提交的新快照。
+
+ // Timer 归属通过后再做普通身份同步;任何不一致都只停止,不提交下一帧。
+ if(!synchronizeSceneIdentity() || !m_bPlaying ||
+ m_nPlaybackBindingRevision == 0) {
stopTimerWithoutSignal();
emit stateChanged();
return;
@@ -400,6 +427,7 @@ bool nmPebiResultPlaybackController::stopTimerWithoutSignal()
m_pTimer->stop();
}
m_bPlaying = false;
+ m_nPlaybackBindingRevision = 0;
return bWasPlaying;
}
@@ -409,22 +437,28 @@ bool nmPebiResultPlaybackController::synchronizeSceneIdentity()
!m_pSceneController->hasSnapshot()) {
stopTimerWithoutSignal();
m_sObservedSnapshotId.clear();
+ m_nObservedBindingRevision = 0;
m_nLastPressureFrameIndex = 0;
return false;
}
const QString sSnapshotId =
m_pSceneController->getCurrentSnapshotId();
- if(sSnapshotId.isEmpty()) {
+ const quint64 nBindingRevision =
+ m_pSceneController->getSnapshotBindingRevision();
+ if(sSnapshotId.isEmpty() || nBindingRevision == 0) {
stopTimerWithoutSignal();
m_sObservedSnapshotId.clear();
+ m_nObservedBindingRevision = 0;
m_nLastPressureFrameIndex = 0;
return false;
}
- if(m_sObservedSnapshotId != sSnapshotId) {
+ if(m_nObservedBindingRevision != nBindingRevision ||
+ m_sObservedSnapshotId != sSnapshotId) {
stopTimerWithoutSignal();
m_sObservedSnapshotId = sSnapshotId;
+ m_nObservedBindingRevision = nBindingRevision;
// 新快照由 SceneController 固定提交压力第 0 帧;无压力分支仍从 0 开始。
m_nLastPressureFrameIndex =
isPressureProperty(getCurrentPropertyId()) &&
diff --git a/Src/nmNum/nmSubWxs/nmPebiResultSceneController.cpp b/Src/nmNum/nmSubWxs/nmPebiResultSceneController.cpp
index fffe4004..14d84c70 100644
--- a/Src/nmNum/nmSubWxs/nmPebiResultSceneController.cpp
+++ b/Src/nmNum/nmSubWxs/nmPebiResultSceneController.cpp
@@ -398,6 +398,7 @@ public:
m_pWellLayer(nullptr),
m_pAdapter(nullptr),
m_nCurrentFrameIndex(-1),
+ m_nSnapshotBindingRevision(0),
m_bHasPropertyRange(false),
m_bHasBounds(false),
m_bMeshVisible(true),
@@ -874,6 +875,8 @@ public:
/** @brief 按稳定属性 ID 保存的窗口级样式;clear 不删除。 */
QMap m_mapDisplaySettings;
int m_nCurrentFrameIndex;
+ /** @brief 不同快照对象完整提交成功后的单调序号,clear 和失败均不回退。 */
+ quint64 m_nSnapshotBindingRevision;
double m_dBusinessRange[2];
double m_dDisplayRange[2];
double m_dBounds[6];
@@ -1123,6 +1126,8 @@ bool nmPebiResultSceneController::setSnapshot(
Q_UNUSED(bCenterReset);
// showReadyState 是本提交路径唯一的 Render 入口。
m_pPrivate->m_pContainer->showReadyState();
+ // 候选管线、相机和首次 Render 已完整提交,最后才发布新绑定序号。
+ ++m_pPrivate->m_nSnapshotBindingRevision;
return true;
}
@@ -1239,6 +1244,7 @@ bool nmPebiResultSceneController::applyPropertyFrame(
}
bool nmPebiResultSceneController::applyCurrentDisplaySettings(
+ quint64 nExpectedBindingRevision,
const QString& sExpectedSnapshotId,
const QString& sExpectedPropertyId,
const nmPebiResultDisplaySettings& oSettings,
@@ -1254,6 +1260,8 @@ bool nmPebiResultSceneController::applyCurrentDisplaySettings(
// 身份核对和实际提交处在同一个 GUI 线程调用中,旧对话框不能污染新快照。
if(!isResultSceneGuiThread() || !hasSnapshot() ||
+ nExpectedBindingRevision == 0 ||
+ nExpectedBindingRevision != getSnapshotBindingRevision() ||
sExpectedSnapshotId.isEmpty() ||
sExpectedPropertyId.isEmpty() ||
sExpectedSnapshotId != getCurrentSnapshotId() ||
@@ -1427,6 +1435,12 @@ bool nmPebiResultSceneController::hasSnapshot() const
m_pPrivate->m_pWellLayer->hasActiveAnnotations();
}
+quint64 nmPebiResultSceneController::getSnapshotBindingRevision() const
+{
+ return m_pPrivate != nullptr
+ ? m_pPrivate->m_nSnapshotBindingRevision : 0;
+}
+
QString nmPebiResultSceneController::getCurrentSnapshotId() const
{
return hasSnapshot()
diff --git a/Src/nmNum/nmSubWxs/nmWxPebiResultColorScaleSettingsDlg.cpp b/Src/nmNum/nmSubWxs/nmWxPebiResultColorScaleSettingsDlg.cpp
index c881d570..c23bf501 100644
--- a/Src/nmNum/nmSubWxs/nmWxPebiResultColorScaleSettingsDlg.cpp
+++ b/Src/nmNum/nmSubWxs/nmWxPebiResultColorScaleSettingsDlg.cpp
@@ -79,6 +79,7 @@ nmWxPebiResultColorScaleSettingsDlg(
QWidget* pParent)
: QDialog(pParent),
m_pSceneController(pSceneController),
+ m_nExpectedBindingRevision(0),
m_bManualRangeEdited(false),
m_bInitialized(false),
m_pColorMapCombo(nullptr),
@@ -324,6 +325,8 @@ bool nmWxPebiResultColorScaleSettingsDlg::initializeFromScene()
if(m_pSceneController == nullptr || !m_pSceneController->hasSnapshot()) {
return false;
}
+ m_nExpectedBindingRevision =
+ m_pSceneController->getSnapshotBindingRevision();
m_sExpectedSnapshotId = m_pSceneController->getCurrentSnapshotId();
m_sExpectedPropertyId = m_pSceneController->getCurrentPropertyId();
m_sDisplayUnit = nmPebiResultPropertyDisplayText::getDefaultDisplayUnit(
@@ -331,7 +334,8 @@ bool nmWxPebiResultColorScaleSettingsDlg::initializeFromScene()
m_pUnitLabel->setText(m_sDisplayUnit.isEmpty()
? tr("Dimensionless") : m_sDisplayUnit);
double dVisibleRange[2] = { 0.0, 0.0 };
- if(m_sExpectedSnapshotId.isEmpty() || m_sExpectedPropertyId.isEmpty() ||
+ if(m_nExpectedBindingRevision == 0 ||
+ m_sExpectedSnapshotId.isEmpty() || m_sExpectedPropertyId.isEmpty() ||
!m_pSceneController->getCurrentPropertyRange(m_dPropertyRange) ||
!m_pSceneController->getCurrentDisplayRange(dVisibleRange) ||
!m_pSceneController->getCurrentDisplaySettings(
@@ -421,6 +425,7 @@ bool nmWxPebiResultColorScaleSettingsDlg::applyEditor()
QString sError;
if(!collectEditedSettings(oSettings, sError) ||
!m_pSceneController->applyCurrentDisplaySettings(
+ m_nExpectedBindingRevision,
m_sExpectedSnapshotId, m_sExpectedPropertyId,
oSettings, &sError)) {
setErrorText(sError);
diff --git a/Src/nmNum/nmSubWxs/nmWxPebiResultControlBar.cpp b/Src/nmNum/nmSubWxs/nmWxPebiResultControlBar.cpp
index 1eefd9cc..c4888478 100644
--- a/Src/nmNum/nmSubWxs/nmWxPebiResultControlBar.cpp
+++ b/Src/nmNum/nmSubWxs/nmWxPebiResultControlBar.cpp
@@ -55,6 +55,7 @@ nmWxPebiResultControlBar::nmWxPebiResultControlBar(
m_pLastButton(nullptr),
m_pPlayPauseButton(nullptr),
m_pSettingsButton(nullptr),
+ m_nDisplayedBindingRevision(0),
m_nDisplayedTimeFrameCount(-1)
{
Q_ASSERT(pSceneController != nullptr);
@@ -102,8 +103,10 @@ void nmWxPebiResultControlBar::onPropertyIndexChanged(int nIndex)
if(m_pPlaybackController == nullptr || nIndex < 0) {
return;
}
- m_pPlaybackController->selectProperty(
- m_pPropertyCombo->itemData(nIndex).toString());
+ if(m_pPlaybackController->selectProperty(
+ m_pPropertyCombo->itemData(nIndex).toString())) {
+ emit operationSucceeded();
+ }
}
void nmWxPebiResultControlBar::onTimeIndexChanged(int nIndex)
@@ -111,8 +114,10 @@ void nmWxPebiResultControlBar::onTimeIndexChanged(int nIndex)
if(m_pPlaybackController == nullptr || nIndex < 0) {
return;
}
- m_pPlaybackController->selectFrame(
- m_pTimeCombo->itemData(nIndex).toInt());
+ if(m_pPlaybackController->selectFrame(
+ m_pTimeCombo->itemData(nIndex).toInt())) {
+ emit operationSucceeded();
+ }
}
void nmWxPebiResultControlBar::onColorSettingsClicked()
@@ -128,6 +133,8 @@ void nmWxPebiResultControlBar::onColorSettingsClicked()
m_pSceneController, this);
connect(&oDialog, SIGNAL(displaySettingsApplied()),
this, SLOT(refreshColorPreview()));
+ connect(&oDialog, SIGNAL(displaySettingsApplied()),
+ this, SIGNAL(operationSucceeded()));
connect(&oDialog, SIGNAL(operationFailed(QString)),
this, SLOT(forwardOperationFailure(QString)));
oDialog.exec();
@@ -136,27 +143,37 @@ void nmWxPebiResultControlBar::onColorSettingsClicked()
void nmWxPebiResultControlBar::onFirstFrameClicked()
{
- m_pPlaybackController->selectFirstFrame();
+ if(m_pPlaybackController->selectFirstFrame()) {
+ emit operationSucceeded();
+ }
}
void nmWxPebiResultControlBar::onPreviousFrameClicked()
{
- m_pPlaybackController->selectPreviousFrame();
+ if(m_pPlaybackController->selectPreviousFrame()) {
+ emit operationSucceeded();
+ }
}
void nmWxPebiResultControlBar::onNextFrameClicked()
{
- m_pPlaybackController->selectNextFrame();
+ if(m_pPlaybackController->selectNextFrame()) {
+ emit operationSucceeded();
+ }
}
void nmWxPebiResultControlBar::onLastFrameClicked()
{
- m_pPlaybackController->selectLastFrame();
+ if(m_pPlaybackController->selectLastFrame()) {
+ emit operationSucceeded();
+ }
}
void nmWxPebiResultControlBar::onPlayPauseClicked()
{
- m_pPlaybackController->togglePlayback();
+ if(m_pPlaybackController->togglePlayback()) {
+ emit operationSucceeded();
+ }
}
void nmWxPebiResultControlBar::onAnimationSettingsClicked()
@@ -167,9 +184,11 @@ void nmWxPebiResultControlBar::onAnimationSettingsClicked()
m_pPlaybackController->getPlaybackSpeed(),
m_pPlaybackController->isLoopEnabled(), this);
if(oDialog.exec() == QDialog::Accepted) {
- m_pPlaybackController->setPlaybackSettings(
- oDialog.getPlaybackSpeed(),
- oDialog.isLoopEnabled());
+ if(m_pPlaybackController->setPlaybackSettings(
+ oDialog.getPlaybackSpeed(),
+ oDialog.isLoopEnabled())) {
+ emit operationSucceeded();
+ }
}
}
@@ -184,6 +203,7 @@ void nmWxPebiResultControlBar::refreshFromController()
m_pPropertyCombo->blockSignals(bPropertyBlocked);
clearTimeItems();
m_sDisplayedSnapshotId.clear();
+ m_nDisplayedBindingRevision = 0;
m_listDisplayedPropertyIds.clear();
m_pPropertyCombo->setEnabled(false);
m_pColorSettingsButton->setEnabled(false);
@@ -202,14 +222,18 @@ void nmWxPebiResultControlBar::refreshFromController()
const QString sSnapshotId =
m_pPlaybackController->getObservedSnapshotId();
+ const quint64 nBindingRevision =
+ m_pPlaybackController->getObservedBindingRevision();
const QStringList listPropertyIds =
m_pPlaybackController->getAvailablePropertyIds();
const bool bSnapshotChanged =
+ m_nDisplayedBindingRevision != nBindingRevision ||
m_sDisplayedSnapshotId != sSnapshotId;
if(bSnapshotChanged ||
m_listDisplayedPropertyIds != listPropertyIds) {
rebuildPropertyItems(listPropertyIds);
m_sDisplayedSnapshotId = sSnapshotId;
+ m_nDisplayedBindingRevision = nBindingRevision;
m_listDisplayedPropertyIds = listPropertyIds;
// 即使属性和帧数相同,新快照的真实时间值也可能不同,不能复用旧列表。
diff --git a/Src/nmNum/nmSubWxs/nmWxPebiResultViewWidget.cpp b/Src/nmNum/nmSubWxs/nmWxPebiResultViewWidget.cpp
new file mode 100644
index 00000000..59cbff22
--- /dev/null
+++ b/Src/nmNum/nmSubWxs/nmWxPebiResultViewWidget.cpp
@@ -0,0 +1,577 @@
+#include "nmWxPebiResultViewWidget.h"
+
+#include "nmDataAnalyzeManager.h"
+#include "nmPebiResultSceneController.h"
+#include "nmPebiResultSnapshot.h"
+#include "nmVTKViewCommandController.h"
+#include "nmWxPebiResultControlBar.h"
+#include "nmWxVTKRenderContainerWidget.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace {
+
+QString resultViewIconPath(const QString& sFileName)
+{
+ const QString sApplicationDir = QCoreApplication::applicationDirPath();
+ return sApplicationDir.section('/', 0, -2) +
+ "/Res/Icon/" + sFileName;
+}
+
+bool isResultViewGuiThread()
+{
+ QCoreApplication* pApplication = QCoreApplication::instance();
+ return pApplication == nullptr ||
+ pApplication->thread() == QThread::currentThread();
+}
+
+void setActionCheckedWithoutSignal(QAction* pAction, bool bChecked)
+{
+ if(pAction == nullptr) {
+ return;
+ }
+ const bool bBlocked = pAction->blockSignals(true);
+ pAction->setChecked(bChecked);
+ pAction->blockSignals(bBlocked);
+}
+
+}
+
+nmWxPebiResultViewWidget::nmWxPebiResultViewWidget(
+ nmDataAnalyzeManager* pDataManager,
+ QWidget* pParent)
+ : QWidget(pParent),
+ m_pRenderContainer(nullptr),
+ m_pSceneController(nullptr),
+ m_pControlBar(nullptr),
+ m_pToolBar(nullptr),
+ m_pStatusLabel(nullptr),
+ m_pMeshAction(nullptr),
+ m_pScalarBarAction(nullptr),
+ m_pScaleGridAction(nullptr),
+ m_pCellEdgesAction(nullptr),
+ m_pWellMarkersAction(nullptr),
+ m_pWellNamesAction(nullptr),
+ m_bObservedSnapshotCurrent(false),
+ m_bObservedStateValid(false),
+ m_bSynchronizing(false),
+ m_bSyncPending(false)
+{
+ createUi();
+ setDataManager(pDataManager);
+}
+
+nmWxPebiResultViewWidget::~nmWxPebiResultViewWidget()
+{
+ disconnectDataManager();
+ if(m_pControlBar != nullptr) {
+ m_pControlBar->stopPlayback();
+ delete m_pControlBar;
+ m_pControlBar = nullptr;
+ }
+ if(m_pSceneController != nullptr) {
+ m_pSceneController->clear();
+ delete m_pSceneController;
+ m_pSceneController = nullptr;
+ }
+ // 渲染容器、工具栏、状态栏和 QAction 最后由 QWidget 对象树统一释放。
+}
+
+void nmWxPebiResultViewWidget::setDataManager(
+ nmDataAnalyzeManager* pDataManager)
+{
+ Q_ASSERT(isResultViewGuiThread());
+ Q_ASSERT(pDataManager == nullptr ||
+ pDataManager->thread() == QThread::currentThread());
+ if(!isResultViewGuiThread() ||
+ (pDataManager != nullptr &&
+ pDataManager->thread() != QThread::currentThread())) {
+ // 窗口会同步读取不可变快照引用,不能跨线程直接访问 Manager。
+ return;
+ }
+ if(m_pDataManager.data() == pDataManager) {
+ // 生产入口可能重复安装同一 Manager;这里只读最新状态,不能清空相机。
+ synchronizeFromDataManager();
+ return;
+ }
+
+ disconnectDataManager();
+ clearSceneAndControls(tr("No result data."));
+ m_pDataManager = pDataManager;
+ if(!m_pDataManager.isNull()) {
+ connect(m_pDataManager.data(), SIGNAL(sigPebiResultStateChanged()),
+ this, SLOT(onPebiResultStateChanged()));
+ connect(m_pDataManager.data(), SIGNAL(destroyed(QObject*)),
+ this, SLOT(onDataManagerDestroyed(QObject*)));
+ }
+ synchronizeFromDataManager();
+}
+
+nmDataAnalyzeManager* nmWxPebiResultViewWidget::getDataManager() const
+{
+ return m_pDataManager.data();
+}
+
+void nmWxPebiResultViewWidget::synchronizeFromDataManager()
+{
+ Q_ASSERT(isResultViewGuiThread());
+ Q_ASSERT(m_pDataManager.isNull() ||
+ m_pDataManager->thread() == QThread::currentThread());
+ if(!isResultViewGuiThread() ||
+ (!m_pDataManager.isNull() &&
+ m_pDataManager->thread() != QThread::currentThread())) {
+ return;
+ }
+ if(m_bSynchronizing) {
+ // 同步期间只记录一次待处理请求,当前事务结束后重新读取 Manager 最新值。
+ m_bSyncPending = true;
+ return;
+ }
+
+ m_bSynchronizing = true;
+ do {
+ m_bSyncPending = false;
+ synchronizeOnce();
+ } while(m_bSyncPending);
+ m_bSynchronizing = false;
+}
+
+void nmWxPebiResultViewWidget::onPebiResultStateChanged()
+{
+ synchronizeFromDataManager();
+}
+
+void nmWxPebiResultViewWidget::onDataManagerDestroyed(QObject* pObject)
+{
+ Q_UNUSED(pObject);
+ // destroyed() 到达时 QPointer 可能已经清空,槽内绝不再读取 Manager。
+ m_pDataManager = nullptr;
+ if(m_bSynchronizing) {
+ // 极端嵌套事件下不得在快照提交栈内递归清场,退出当前事务后再读空状态。
+ m_bSyncPending = true;
+ return;
+ }
+ clearSceneAndControls(tr("No result data."));
+ // 记录稳定空状态,后续重复同步空 Manager 时不再清场或切换状态页。
+ rememberObservedState(
+ QSharedPointer(),
+ QString(), false);
+}
+
+void nmWxPebiResultViewWidget::appendContextMenuActions(QMenu* pMenu)
+{
+ if(pMenu == nullptr) {
+ return;
+ }
+ pMenu->addAction(m_pMeshAction);
+ pMenu->addAction(m_pScalarBarAction);
+ pMenu->addAction(m_pScaleGridAction);
+ pMenu->addAction(m_pCellEdgesAction);
+ pMenu->addAction(m_pWellMarkersAction);
+ pMenu->addAction(m_pWellNamesAction);
+}
+
+void nmWxPebiResultViewWidget::onControlOperationFailed(
+ const QString& sError)
+{
+ // 高优先级状态存在时丢弃普通错误,避免其在过期提示解除后突然出现。
+ if(!m_sBindingError.isEmpty() ||
+ (m_bObservedStateValid && !m_bObservedSnapshotCurrent)) {
+ return;
+ }
+ m_sOperationError = sError;
+ refreshStatusBar();
+}
+
+void nmWxPebiResultViewWidget::onControlOperationSucceeded()
+{
+ if(m_sOperationError.isEmpty()) {
+ return;
+ }
+ // 这里只清除最低优先级的普通操作错误;刷新函数仍优先显示绑定失败和过期。
+ m_sOperationError.clear();
+ refreshStatusBar();
+}
+
+void nmWxPebiResultViewWidget::onMeshToggled(bool bVisible)
+{
+ if(m_pSceneController != nullptr && m_pSceneController->hasSnapshot()) {
+ m_pSceneController->setMeshVisible(bVisible);
+ }
+}
+
+void nmWxPebiResultViewWidget::onScalarBarToggled(bool bVisible)
+{
+ if(m_pSceneController != nullptr && m_pSceneController->hasSnapshot()) {
+ m_pSceneController->setScalarBarVisible(bVisible);
+ }
+}
+
+void nmWxPebiResultViewWidget::onScaleGridToggled(bool bVisible)
+{
+ if(m_pSceneController != nullptr && m_pSceneController->hasSnapshot()) {
+ m_pSceneController->setScaleGridVisible(bVisible);
+ }
+}
+
+void nmWxPebiResultViewWidget::onCellEdgesToggled(bool bVisible)
+{
+ if(m_pSceneController != nullptr && m_pSceneController->hasSnapshot()) {
+ m_pSceneController->setCellEdgesVisible(bVisible);
+ }
+}
+
+void nmWxPebiResultViewWidget::onWellMarkersToggled(bool bVisible)
+{
+ if(m_pSceneController != nullptr && m_pSceneController->hasSnapshot()) {
+ m_pSceneController->setWellMarkersVisible(bVisible);
+ }
+}
+
+void nmWxPebiResultViewWidget::onWellNamesToggled(bool bVisible)
+{
+ if(m_pSceneController != nullptr && m_pSceneController->hasSnapshot()) {
+ m_pSceneController->setWellNamesVisible(bVisible);
+ }
+}
+
+void nmWxPebiResultViewWidget::createUi()
+{
+ QVBoxLayout* pMainLayout = new QVBoxLayout(this);
+ pMainLayout->setContentsMargins(0, 0, 0, 0);
+ pMainLayout->setSpacing(0);
+
+ QWidget* pRenderArea = new QWidget(this);
+ QHBoxLayout* pRenderLayout = new QHBoxLayout(pRenderArea);
+ pRenderLayout->setContentsMargins(0, 0, 0, 0);
+ pRenderLayout->setSpacing(0);
+
+ // 通用容器是本窗口唯一创建 QVTKWidget、Renderer 和 RenderWindow 的对象。
+ m_pRenderContainer = new nmWxVTKRenderContainerWidget(pRenderArea);
+ m_pRenderContainer->setSizePolicy(
+ QSizePolicy::Expanding, QSizePolicy::Expanding);
+ m_pRenderContainer->setViewCommandCapabilities(
+ nmVTKViewCommandController::ViewCommand_ZoomIn |
+ nmVTKViewCommandController::ViewCommand_ZoomOut |
+ nmVTKViewCommandController::ViewCommand_Fit |
+ nmVTKViewCommandController::ViewCommand_Top |
+ nmVTKViewCommandController::ViewCommand_Front |
+ nmVTKViewCommandController::ViewCommand_SetRotationCenter |
+ nmVTKViewCommandController::ViewCommand_SaveImage |
+ nmVTKViewCommandController::ViewCommand_CopyImage |
+ nmVTKViewCommandController::ViewCommand_Print |
+ nmVTKViewCommandController::ViewCommand_PrintPreview);
+ m_pSceneController = new nmPebiResultSceneController(m_pRenderContainer);
+
+ m_pToolBar = new QToolBar(pRenderArea);
+ m_pToolBar->setOrientation(Qt::Vertical);
+ m_pToolBar->setMovable(false);
+ m_pToolBar->setFloatable(false);
+ m_pToolBar->setIconSize(QSize(32, 32));
+ m_pToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
+ m_pToolBar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
+
+ m_pMeshAction = new QAction(QIcon(resultViewIconPath("NmGrid.png")),
+ tr("Result Grid"), this);
+ m_pScalarBarAction = new QAction(
+ QIcon(resultViewIconPath("NmColorScale.png")),
+ tr("Color Scale"), this);
+ m_pScaleGridAction = new QAction(
+ QIcon(resultViewIconPath("NmScaleGrid.png")),
+ tr("Scale Grid"), this);
+ m_pCellEdgesAction = new QAction(
+ QIcon(resultViewIconPath("SpecialLineVisible.png")),
+ tr("Cell Edges"), this);
+ m_pWellMarkersAction = new QAction(
+ QIcon(resultViewIconPath("NmWell.png")),
+ tr("Well Markers"), this);
+ m_pWellNamesAction = new QAction(
+ QIcon(resultViewIconPath("NmShow.png")),
+ tr("Well Names"), this);
+
+ QAction* const pBusinessActions[] = {
+ m_pMeshAction, m_pScalarBarAction, m_pScaleGridAction,
+ m_pCellEdgesAction, m_pWellMarkersAction, m_pWellNamesAction
+ };
+ for(int nIndex = 0; nIndex < 6; ++nIndex) {
+ pBusinessActions[nIndex]->setCheckable(true);
+ pBusinessActions[nIndex]->setToolTip(
+ pBusinessActions[nIndex]->text());
+ }
+
+ nmVTKViewCommandController* pCommandController =
+ m_pRenderContainer->getViewCommandController();
+ if(pCommandController != nullptr) {
+ QAction* pZoomOutAction = pCommandController->getAction(
+ nmVTKViewCommandController::ViewCommand_ZoomOut);
+ QAction* pFitAction = pCommandController->getAction(
+ nmVTKViewCommandController::ViewCommand_Fit);
+ QAction* pZoomInAction = pCommandController->getAction(
+ nmVTKViewCommandController::ViewCommand_ZoomIn);
+ QAction* pTopAction = pCommandController->getAction(
+ nmVTKViewCommandController::ViewCommand_Top);
+ QAction* pFrontAction = pCommandController->getAction(
+ nmVTKViewCommandController::ViewCommand_Front);
+ QAction* pPickCenterAction = pCommandController->getAction(
+ nmVTKViewCommandController::ViewCommand_SetRotationCenter);
+ QAction* pCopyAction = pCommandController->getAction(
+ nmVTKViewCommandController::ViewCommand_CopyImage);
+
+ if(pZoomOutAction != nullptr) m_pToolBar->addAction(pZoomOutAction);
+ if(pFitAction != nullptr) m_pToolBar->addAction(pFitAction);
+ if(pZoomInAction != nullptr) m_pToolBar->addAction(pZoomInAction);
+ m_pToolBar->addSeparator();
+ if(pTopAction != nullptr) m_pToolBar->addAction(pTopAction);
+ if(pFrontAction != nullptr) m_pToolBar->addAction(pFrontAction);
+ if(pPickCenterAction != nullptr) m_pToolBar->addAction(pPickCenterAction);
+ m_pToolBar->addSeparator();
+ for(int nIndex = 0; nIndex < 6; ++nIndex) {
+ m_pToolBar->addAction(pBusinessActions[nIndex]);
+ }
+ m_pToolBar->addSeparator();
+ if(pCopyAction != nullptr) m_pToolBar->addAction(pCopyAction);
+ }
+
+ connect(m_pMeshAction, SIGNAL(toggled(bool)),
+ this, SLOT(onMeshToggled(bool)));
+ connect(m_pScalarBarAction, SIGNAL(toggled(bool)),
+ this, SLOT(onScalarBarToggled(bool)));
+ connect(m_pScaleGridAction, SIGNAL(toggled(bool)),
+ this, SLOT(onScaleGridToggled(bool)));
+ connect(m_pCellEdgesAction, SIGNAL(toggled(bool)),
+ this, SLOT(onCellEdgesToggled(bool)));
+ connect(m_pWellMarkersAction, SIGNAL(toggled(bool)),
+ this, SLOT(onWellMarkersToggled(bool)));
+ connect(m_pWellNamesAction, SIGNAL(toggled(bool)),
+ this, SLOT(onWellNamesToggled(bool)));
+ connect(m_pRenderContainer, SIGNAL(contextMenuAboutToShow(QMenu*)),
+ this, SLOT(appendContextMenuActions(QMenu*)));
+
+ pRenderLayout->addWidget(m_pRenderContainer, 1);
+ pRenderLayout->addWidget(m_pToolBar);
+ pMainLayout->addWidget(pRenderArea, 1);
+
+ m_pStatusLabel = new QLabel(this);
+ m_pStatusLabel->setWordWrap(true);
+ m_pStatusLabel->setContentsMargins(8, 4, 8, 4);
+ m_pStatusLabel->hide();
+ pMainLayout->addWidget(m_pStatusLabel);
+
+ m_pControlBar = new nmWxPebiResultControlBar(
+ m_pSceneController, this);
+ connect(m_pControlBar, SIGNAL(operationFailed(QString)),
+ this, SLOT(onControlOperationFailed(QString)));
+ connect(m_pControlBar, SIGNAL(operationSucceeded()),
+ this, SLOT(onControlOperationSucceeded()));
+ pMainLayout->addWidget(m_pControlBar);
+
+ synchronizeBusinessActions();
+ m_pRenderContainer->showEmptyState(tr("No result data."));
+}
+
+void nmWxPebiResultViewWidget::synchronizeOnce()
+{
+ if(m_pDataManager.isNull()) {
+ if(m_bObservedStateValid && m_pObservedSnapshot.isNull()) {
+ return;
+ }
+ clearSceneAndControls(tr("No result data."));
+ rememberObservedState(
+ QSharedPointer(),
+ QString(), false);
+ return;
+ }
+
+ const QSharedPointer pSnapshot =
+ m_pDataManager->getPebiResultSnapshot();
+ const QString sSnapshotId = pSnapshot.isNull()
+ ? QString() : pSnapshot->getSnapshotId();
+ const bool bCurrent = !pSnapshot.isNull() &&
+ m_pDataManager->isPebiResultSnapshotCurrent();
+ const bool bSameIdentity = m_bObservedStateValid &&
+ m_pObservedSnapshot.data() == pSnapshot.data() &&
+ m_sObservedSnapshotId == sSnapshotId;
+ const bool bSameState = bSameIdentity &&
+ m_bObservedSnapshotCurrent == bCurrent;
+ if(bSameState) {
+ return;
+ }
+
+ const bool bBecameStale = bSameIdentity &&
+ m_bObservedSnapshotCurrent && !bCurrent;
+ m_sOperationError.clear();
+
+ if(pSnapshot.isNull()) {
+ clearSceneAndControls(tr("No result data."));
+ rememberObservedState(pSnapshot, sSnapshotId, false);
+ return;
+ }
+
+ if(!bSameIdentity) {
+ // 候选提交前停止本窗口 Timer;失败时旧 Scene 和最后压力帧记忆仍保留。
+ m_pControlBar->stopPlayback();
+ QString sError;
+ const bool bBound = m_pSceneController->setSnapshot(
+ pSnapshot, &sError);
+ rememberObservedState(pSnapshot, sSnapshotId, bCurrent);
+ if(bBound) {
+ m_pDisplayedSnapshot = pSnapshot;
+ m_sDisplayedSnapshotId = sSnapshotId;
+ m_sBindingError.clear();
+ // 新成功绑定序号会让 Playback 和 ControlBar 同时重置各自缓存。
+ m_pControlBar->synchronizeFromScene();
+ synchronizeBusinessActions();
+ refreshStatusBar();
+ return;
+ }
+
+ m_sBindingError = sError.isEmpty()
+ ? tr("Unable to display the latest result.")
+ : tr("Unable to display the latest result: %1").arg(sError);
+ if(!m_pSceneController->hasSnapshot()) {
+ m_pDisplayedSnapshot.clear();
+ m_sDisplayedSnapshotId.clear();
+ }
+ m_pControlBar->synchronizeFromScene();
+ synchronizeBusinessActions();
+ if(m_pSceneController->hasSnapshot()) {
+ // 旧画面保持 Ready;这里只显示红色非阻塞提示,不能追加 Render。
+ refreshStatusBar();
+ } else {
+ m_pStatusLabel->hide();
+ m_pRenderContainer->showErrorState(m_sBindingError);
+ }
+ return;
+ }
+
+ rememberObservedState(pSnapshot, sSnapshotId, bCurrent);
+ if(bBecameStale) {
+ // 只在“当前 -> 过期”的边沿暂停一次;用户之后可以手动重新播放旧结果。
+ m_pControlBar->stopPlayback();
+ }
+ refreshStatusBar();
+}
+
+void nmWxPebiResultViewWidget::clearSceneAndControls(
+ const QString& sEmptyMessage)
+{
+ if(m_pControlBar != nullptr) {
+ m_pControlBar->stopPlayback();
+ }
+ if(m_pSceneController != nullptr) {
+ m_pSceneController->clear();
+ }
+ m_pObservedSnapshot.clear();
+ m_sObservedSnapshotId.clear();
+ m_bObservedSnapshotCurrent = false;
+ m_bObservedStateValid = false;
+ m_pDisplayedSnapshot.clear();
+ m_sDisplayedSnapshotId.clear();
+ m_sBindingError.clear();
+ m_sOperationError.clear();
+ if(m_pControlBar != nullptr) {
+ m_pControlBar->synchronizeFromScene();
+ }
+ synchronizeBusinessActions();
+ if(m_pStatusLabel != nullptr) {
+ m_pStatusLabel->hide();
+ }
+ if(m_pRenderContainer != nullptr) {
+ m_pRenderContainer->showEmptyState(sEmptyMessage);
+ }
+}
+
+void nmWxPebiResultViewWidget::rememberObservedState(
+ const QSharedPointer& pSnapshot,
+ const QString& sSnapshotId,
+ bool bCurrent)
+{
+ m_pObservedSnapshot = pSnapshot;
+ m_sObservedSnapshotId = sSnapshotId;
+ m_bObservedSnapshotCurrent = bCurrent;
+ m_bObservedStateValid = true;
+}
+
+void nmWxPebiResultViewWidget::synchronizeBusinessActions()
+{
+ if(m_pSceneController == nullptr) {
+ return;
+ }
+ setActionCheckedWithoutSignal(
+ m_pMeshAction, m_pSceneController->isMeshVisible());
+ setActionCheckedWithoutSignal(
+ m_pScalarBarAction, m_pSceneController->isScalarBarVisible());
+ setActionCheckedWithoutSignal(
+ m_pScaleGridAction, m_pSceneController->isScaleGridVisible());
+ setActionCheckedWithoutSignal(
+ m_pCellEdgesAction, m_pSceneController->areCellEdgesVisible());
+ setActionCheckedWithoutSignal(
+ m_pWellMarkersAction, m_pSceneController->areWellMarkersVisible());
+ setActionCheckedWithoutSignal(
+ m_pWellNamesAction, m_pSceneController->areWellNamesVisible());
+
+ const bool bEnabled = m_pSceneController->hasSnapshot();
+ m_pMeshAction->setEnabled(bEnabled);
+ m_pScalarBarAction->setEnabled(bEnabled);
+ m_pScaleGridAction->setEnabled(bEnabled);
+ m_pCellEdgesAction->setEnabled(bEnabled);
+ m_pWellMarkersAction->setEnabled(bEnabled);
+ m_pWellNamesAction->setEnabled(bEnabled);
+}
+
+void nmWxPebiResultViewWidget::refreshStatusBar()
+{
+ if(m_pStatusLabel == nullptr || m_pSceneController == nullptr ||
+ !m_pSceneController->hasSnapshot()) {
+ if(m_pStatusLabel != nullptr) {
+ m_pStatusLabel->hide();
+ }
+ return;
+ }
+
+ if(!m_sBindingError.isEmpty()) {
+ m_pStatusLabel->setStyleSheet(
+ "QLabel { color: #a32626; background: #fdecec; } ");
+ m_pStatusLabel->setText(m_sBindingError);
+ m_pStatusLabel->show();
+ return;
+ }
+ if(m_bObservedStateValid && !m_bObservedSnapshotCurrent) {
+ m_pStatusLabel->setStyleSheet(
+ "QLabel { color: #6b5200; background: #fff4bf; } ");
+ m_pStatusLabel->setText(tr(
+ "Current inputs have changed. Showing the last successful result."));
+ m_pStatusLabel->show();
+ return;
+ }
+ if(!m_sOperationError.isEmpty()) {
+ m_pStatusLabel->setStyleSheet(
+ "QLabel { color: #a32626; background: #fdecec; } ");
+ m_pStatusLabel->setText(m_sOperationError);
+ m_pStatusLabel->show();
+ return;
+ }
+ m_pStatusLabel->hide();
+}
+
+void nmWxPebiResultViewWidget::disconnectDataManager()
+{
+ nmDataAnalyzeManager* pDataManager = m_pDataManager.data();
+ if(pDataManager != nullptr) {
+ disconnect(pDataManager, SIGNAL(sigPebiResultStateChanged()),
+ this, SLOT(onPebiResultStateChanged()));
+ disconnect(pDataManager, SIGNAL(destroyed(QObject*)),
+ this, SLOT(onDataManagerDestroyed(QObject*)));
+ }
+ m_pDataManager = nullptr;
+}