You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmSubWxs/nmWxPebiResultViewWidget.cpp

527 lines
18 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "nmWxPebiResultViewWidget.h"
#include "nmDataAnalyzeManager.h"
#include "nmPebiResultSceneController.h"
#include "nmPebiResultSnapshot.h"
#include "nmVTKViewCommandController.h"
#include "nmWxPebiResultControlBar.h"
#include "nmWxVTKRenderContainerWidget.h"
#include <QAction>
#include <QCoreApplication>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QMenu>
#include <QSize>
#include <QSizePolicy>
#include <QThread>
#include <QToolBar>
#include <QVBoxLayout>
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_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<const nmPebiResultSnapshot>(),
QString(), false);
}
void nmWxPebiResultViewWidget::appendContextMenuActions(QMenu* pMenu)
{
if(pMenu == nullptr) {
return;
}
pMenu->addAction(m_pScalarBarAction);
pMenu->addAction(m_pScaleGridAction);
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::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_pWellNamesAction = new QAction(
QIcon(resultViewIconPath("NmShowWellNames.png")),
tr("Well Names"), this);
QAction* const pBusinessActions[] = {
m_pScalarBarAction, m_pScaleGridAction, m_pWellNamesAction
};
for(int nIndex = 0; nIndex < 3; ++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 < 3; ++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_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<const nmPebiResultSnapshot>(),
QString(), false);
return;
}
const QSharedPointer<const nmPebiResultSnapshot> 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<const nmPebiResultSnapshot>& 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_pWellNamesAction, m_pSceneController->areWellNamesVisible());
const bool bEnabled = m_pSceneController->hasSnapshot();
m_pScalarBarAction->setEnabled(bEnabled);
m_pScaleGridAction->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;
}