Files
ResInsight/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimRftCorrelationReportPlot.cpp
T

522 lines
21 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimRftCorrelationReportPlot.h"
#include "RimParameterRftCrossPlot.h"
#include "RimRftTornadoPlot.h"
#include "RimWellLogTrack.h"
#include "RimWellRftPlot.h"
#include "RiuInterfaceToViewWindow.h"
#include "RiuPlotWidget.h"
#include "RiuQwtPlotWidget.h"
#include "DockAreaTitleBar.h"
#include "DockAreaWidget.h"
#include "DockManager.h"
#include "DockWidget.h"
#include "cafPdmUiCheckBoxEditor.h"
#include "cafPdmUiTreeOrdering.h"
#include "cafSelectionManager.h"
#include <QContextMenuEvent>
#include <QFrame>
#include <QSettings>
#include <QVBoxLayout>
static const char* RFT_DOCK_LAYOUT_REGISTRY_KEY = "RftCorrelationReportPlot/defaultDockLayout";
//--------------------------------------------------------------------------------------------------
/// Thin wrapper that implements RiuInterfaceToViewWindow for the dock manager frame.
//--------------------------------------------------------------------------------------------------
class RiuRftCorrelationReportPlotWidget : public QFrame, public RiuInterfaceToViewWindow
{
public:
RiuRftCorrelationReportPlotWidget( RimViewWindow* viewWindow, QWidget* parent = nullptr )
: QFrame( parent )
, m_viewWindow( viewWindow )
{
setLayout( new QVBoxLayout );
layout()->setContentsMargins( 0, 0, 0, 0 );
layout()->setSpacing( 0 );
}
RimViewWindow* ownerViewWindow() const override { return m_viewWindow; }
private:
RimViewWindow* m_viewWindow;
};
//--------------------------------------------------------------------------------------------------
/// Sets the report plot as the CAF-selected item whenever a context menu fires
/// anywhere inside the dock manager.
//--------------------------------------------------------------------------------------------------
class RftSelectionFixerOnContextMenu : public QObject
{
public:
RftSelectionFixerOnContextMenu( caf::PdmObject* item, QObject* parent )
: QObject( parent )
, m_item( item )
{
}
bool eventFilter( QObject*, QEvent* event ) override
{
if ( event->type() == QEvent::ContextMenu ) caf::SelectionManager::instance()->setSelectedItem( m_item );
return false;
}
private:
caf::PdmObject* m_item;
};
//==================================================================================================
//
//
//
//==================================================================================================
CAF_PDM_SOURCE_INIT( RimRftCorrelationReportPlot, "RftCorrelationReportPlot" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimRftCorrelationReportPlot::RimRftCorrelationReportPlot()
{
CAF_PDM_InitObject( "RFT Correlation Report Plot", ":/CorrelationReportPlot16x16.png" );
setDeletable( true );
CAF_PDM_InitFieldNoDefault( &m_name, "PlotWindowTitle", "Title" );
m_name.registerGetMethod( this, &RimRftCorrelationReportPlot::createDescription );
CAF_PDM_InitFieldNoDefault( &m_wellRftPlot, "WellRftPlot", "RFT Plot" );
CAF_PDM_InitFieldNoDefault( &m_parameterRftCrossPlot, "ParameterRftCrossPlot", "Cross Plot" );
CAF_PDM_InitFieldNoDefault( &m_tornadoPlot, "TornadoPlot", "Tornado Plot" );
CAF_PDM_InitField( &m_showDockTitleBars, "ShowDockTitleBars", false, "Show Title Bars" );
caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_showDockTitleBars );
CAF_PDM_InitField( &m_dockState, "DockState", QString(), "Dock State" );
m_dockState.uiCapability()->setUiHidden( true );
dockAsPlotWindow();
m_showWindow = true;
m_showPlotLegends = false;
m_wellRftPlot = new RimWellRftPlot;
m_wellRftPlot->removeWindowFromDock();
m_wellRftPlot->setShowWindow( true );
m_parameterRftCrossPlot = new RimParameterRftCrossPlot;
m_tornadoPlot = new RimRftTornadoPlot;
m_tornadoPlot->setParameterSelectedCallback( [this]( const QString& paramName ) { onTornadoParameterSelected( paramName ); } );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimRftCorrelationReportPlot::~RimRftCorrelationReportPlot()
{
removeWindowFromDock();
cleanupBeforeClose();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QWidget* RimRftCorrelationReportPlot::viewWidget()
{
return m_viewWidget;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimRftCorrelationReportPlot::description() const
{
return createDescription();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
caf::PdmFieldHandle* RimRftCorrelationReportPlot::userDescriptionField()
{
return &m_name;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimWellRftPlot* RimRftCorrelationReportPlot::wellRftPlot() const
{
return m_wellRftPlot();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimParameterRftCrossPlot* RimRftCorrelationReportPlot::crossPlot() const
{
return m_parameterRftCrossPlot();
}
//--------------------------------------------------------------------------------------------------
/// Initialize the owned RimWellRftPlot from the source plot's selected data sources.
/// We keep the fresh (curve-free) child plot and call initializeDataSources(source) so
/// syncCurvesFromUiSelection builds curves from scratch without touching unresolved copies.
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::initializeFromSourcePlot( RimWellRftPlot* source )
{
if ( !source ) return;
m_wellRftPlot->setSimWellOrWellPathName( source->simWellOrWellPathName() );
// A fresh RimWellRftPlot has no tracks; syncCurvesFromUiSelection exits early without one.
// Guard against duplicate track creation if this is called more than once.
if ( m_wellRftPlot->plotCount() == 0 )
{
auto* track = new RimWellLogTrack();
m_wellRftPlot->addPlot( track );
track->setDescription( QString( "Track %1" ).arg( m_wellRftPlot->plotCount() ) );
}
m_wellRftPlot->initializeDataSources( source );
// The correlation report operates on a single time step; trim any extras that
// initializeDataSources may have preselected when only a few were available.
auto selectedTimeSteps = m_wellRftPlot->selectedTimeSteps();
if ( selectedTimeSteps.size() > 1 )
{
m_wellRftPlot->setSelectedTimeSteps( { selectedTimeSteps.front() } );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimRftCorrelationReportPlot::createDescription() const
{
if ( m_parameterRftCrossPlot() )
{
const QString wellName = m_parameterRftCrossPlot()->wellName();
const QString param = m_parameterRftCrossPlot()->ensembleParameter();
if ( !wellName.isEmpty() && !param.isEmpty() )
{
return QString( "RFT Correlation: %1 vs %2" ).arg( param ).arg( wellName );
}
}
return "RFT Correlation Report";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::recreatePlotWidgets()
{
CAF_ASSERT( m_dockManager );
m_wellRftPlot->createPlotWidget( m_dockManager );
m_tornadoPlot->createPlotWidget( m_dockManager );
m_parameterRftCrossPlot->createPlotWidget( m_dockManager );
// Context menu fixer — ensures this report is selected in CAF on any right-click
delete m_contextMenuFilter;
m_contextMenuFilter = new RftSelectionFixerOnContextMenu( this, this );
if ( auto* w = m_wellRftPlot->viewWidget() ) w->installEventFilter( m_contextMenuFilter );
if ( auto* w = m_tornadoPlot->viewer() ) w->installEventFilter( m_contextMenuFilter );
if ( auto* w = m_parameterRftCrossPlot->viewer() ) w->installEventFilter( m_contextMenuFilter );
auto makeDockWidget = [&]( const QString& title, RimPlotWindow* plot, QWidget* widget ) -> ads::CDockWidget*
{
auto* dock = new ads::CDockWidget( title, m_dockManager );
dock->setWidget( widget, ads::CDockWidget::ForceNoScrollArea );
connect( dock,
&ads::CDockWidget::closed,
this,
[this, plot]()
{
plot->setShowWindow( false );
updateConnectedEditors();
} );
return dock;
};
m_rftDockWidget = makeDockWidget( "RFT Plot", m_wellRftPlot(), m_wellRftPlot->viewWidget() );
m_correlationDockWidget = makeDockWidget( "Tornado Plot", m_tornadoPlot(), m_tornadoPlot->viewer() );
m_crossPlotDockWidget = makeDockWidget( "Cross Plot", m_parameterRftCrossPlot(), m_parameterRftCrossPlot->viewer() );
// Restore saved dock state or apply hard-coded default layout
QByteArray stateToRestore;
if ( !m_dockState().isEmpty() )
{
stateToRestore = QByteArray::fromBase64( m_dockState().toLatin1() );
}
else
{
QSettings settings;
QVariant v = settings.value( RFT_DOCK_LAYOUT_REGISTRY_KEY );
if ( v.isValid() ) stateToRestore = v.toByteArray();
}
if ( !stateToRestore.isEmpty() )
{
m_dockManager->addDockWidget( ads::LeftDockWidgetArea, m_rftDockWidget );
auto* rightArea = m_dockManager->addDockWidget( ads::RightDockWidgetArea, m_correlationDockWidget );
m_dockManager->addDockWidget( ads::BottomDockWidgetArea, m_crossPlotDockWidget, rightArea );
m_dockManager->restoreState( stateToRestore, 1 );
}
else
{
// Default: RFT plot on the left, tornado top-right, cross plot bottom-right
m_dockManager->addDockWidget( ads::LeftDockWidgetArea, m_rftDockWidget );
auto* rightArea = m_dockManager->addDockWidget( ads::RightDockWidgetArea, m_correlationDockWidget );
m_dockManager->addDockWidget( ads::BottomDockWidgetArea, m_crossPlotDockWidget, rightArea );
}
m_rftDockWidget->toggleView( m_wellRftPlot->showWindow() );
m_correlationDockWidget->toggleView( m_tornadoPlot->showWindow() );
m_crossPlotDockWidget->toggleView( m_parameterRftCrossPlot->showWindow() );
updateDockTitleBarsVisibility();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::cleanupBeforeClose()
{
// Detach and delete legend curves before the QwtPlot inside the track widget is destroyed.
// QwtPlot has autoDelete=true, so any curves still attached when it is deleted are freed by QWT
// — leaving m_legendPlotCurves with dangling pointers on the next loadDataAndUpdate().
if ( m_wellRftPlot() ) m_wellRftPlot->cleanupLegendCurves();
if ( m_tornadoPlot() ) m_tornadoPlot->detachAllCurves();
if ( m_parameterRftCrossPlot() ) m_parameterRftCrossPlot->detachAllCurves();
m_rftDockWidget = nullptr;
m_correlationDockWidget = nullptr;
m_crossPlotDockWidget = nullptr;
if ( m_dockManager )
{
delete m_dockManager;
m_dockManager = nullptr;
}
if ( m_viewWidget )
{
m_viewWidget->setParent( nullptr );
delete m_viewWidget;
m_viewWidget = nullptr;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::setupBeforeSave()
{
if ( m_dockManager )
{
m_dockState = QString::fromLatin1( m_dockManager->saveState( 1 ).toBase64() );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::doRenderWindowContent( QPaintDevice* paintDevice )
{
if ( m_viewWidget ) m_viewWidget->render( paintDevice );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QWidget* RimRftCorrelationReportPlot::createViewWidget( QWidget* mainWindowParent )
{
auto* wrapper = new RiuRftCorrelationReportPlotWidget( this, mainWindowParent );
m_viewWidget = wrapper;
m_dockManager = new ads::CDockManager( wrapper );
m_dockManager->setStyleSheet( "ads--CDockSplitter::handle { width: 2px; height: 2px; background-color: #a0a0a0; }" );
wrapper->layout()->addWidget( m_dockManager );
recreatePlotWidgets();
return m_viewWidget;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::deleteViewWidget()
{
cleanupBeforeClose();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::onLoadDataAndUpdate()
{
updateDockWindowVisibility();
if ( m_showWindow )
{
m_wellRftPlot->loadDataAndUpdate();
syncTornadoInputsFromCrossPlot();
m_tornadoPlot->loadDataAndUpdate();
m_parameterRftCrossPlot->loadDataAndUpdate();
}
updateLayout();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
// Delegate cross-plot settings (ensemble, well, depth range, parameter) to the cross plot
m_parameterRftCrossPlot->uiOrdering( uiConfigName, uiOrdering );
auto* layoutGroup = uiOrdering.addNewGroup( "Dock Layout" );
layoutGroup->setCollapsedByDefault();
layoutGroup->add( &m_showDockTitleBars );
uiOrdering.skipRemainingFields( true );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString /*uiConfigName*/ )
{
uiTreeOrdering.add( m_wellRftPlot() );
uiTreeOrdering.add( m_tornadoPlot() );
uiTreeOrdering.add( m_parameterRftCrossPlot() );
uiTreeOrdering.skipRemainingChildren();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
{
if ( changedField == &m_showDockTitleBars )
{
updateDockTitleBarsVisibility();
return;
}
loadDataAndUpdate();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::childFieldChangedByUi( const caf::PdmFieldHandle* changedChildField )
{
if ( m_rftDockWidget && changedChildField == &m_wellRftPlot )
m_rftDockWidget->toggleView( m_wellRftPlot->showWindow() );
else if ( m_correlationDockWidget && changedChildField == &m_tornadoPlot )
m_correlationDockWidget->toggleView( m_tornadoPlot->showWindow() );
else if ( m_crossPlotDockWidget && changedChildField == &m_parameterRftCrossPlot )
{
m_crossPlotDockWidget->toggleView( m_parameterRftCrossPlot->showWindow() );
syncCrossPlotSelectionToRftPlot();
}
updateDockTitleBarsVisibility();
loadDataAndUpdate();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::syncCrossPlotSelectionToRftPlot()
{
if ( !m_wellRftPlot() || !m_parameterRftCrossPlot() ) return;
const QString wellName = m_parameterRftCrossPlot->wellName();
const QDateTime timeStep = m_parameterRftCrossPlot->selectedTimeStep();
if ( !wellName.isEmpty() ) m_wellRftPlot->setSimWellOrWellPathName( wellName );
if ( timeStep.isValid() ) m_wellRftPlot->setSelectedTimeSteps( { timeStep } );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimRftTornadoPlot* RimRftCorrelationReportPlot::tornadoPlot() const
{
return m_tornadoPlot();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimPlot*> RimRftCorrelationReportPlot::childPlotsForTextExport() const
{
return { crossPlot(), tornadoPlot() };
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::onTornadoParameterSelected( const QString& paramName )
{
if ( m_tornadoPlot() )
{
m_tornadoPlot->setSelectedParameter( paramName );
m_tornadoPlot->loadDataAndUpdate();
}
if ( m_parameterRftCrossPlot() )
{
m_parameterRftCrossPlot->setEnsembleParameter( paramName );
m_parameterRftCrossPlot->loadDataAndUpdate();
}
updateConnectedEditors();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::syncTornadoInputsFromCrossPlot()
{
if ( !m_tornadoPlot() || !m_parameterRftCrossPlot() ) return;
m_tornadoPlot->setEnsemble( m_parameterRftCrossPlot->ensemble() );
m_tornadoPlot->setWellName( m_parameterRftCrossPlot->wellName() );
m_tornadoPlot->setTimeStep( m_parameterRftCrossPlot->selectedTimeStep() );
m_tornadoPlot->setSelectedParameter( m_parameterRftCrossPlot->ensembleParameter() );
m_tornadoPlot->setEclipseCase( m_parameterRftCrossPlot->eclipseCase() );
m_tornadoPlot->setUseDepthRange( m_parameterRftCrossPlot->useDepthRange() );
m_tornadoPlot->setDepthRange( m_parameterRftCrossPlot->depthRangeMin(), m_parameterRftCrossPlot->depthRangeMax() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftCorrelationReportPlot::updateDockTitleBarsVisibility()
{
if ( !m_dockManager ) return;
for ( auto* area : m_dockManager->openedDockAreas() )
area->titleBar()->setVisible( m_showDockTitleBars() );
}