#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_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_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::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_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_pScalarBarAction, m_pScaleGridAction, m_pCellEdgesAction, m_pWellMarkersAction, m_pWellNamesAction }; for(int nIndex = 0; nIndex < 5; ++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 < 5; ++nIndex) { m_pToolBar->addAction(pBusinessActions[nIndex]); } m_pToolBar->addSeparator(); if(pCopyAction != nullptr) m_pToolBar->addAction(pCopyAction); } 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_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_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; }