From 68f41943e60ad3c330cf4251135257d26638abf4 Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Mon, 19 Oct 2026 09:40:46 +0200 Subject: [PATCH] #1136: Add global curve style settings and per-item rules --- .../Tools/Summary/RiaSummaryTools.cpp | 41 ++ .../Tools/Summary/RiaSummaryTools.h | 6 +- .../ProjectDataModel/RimPlotCurve.h | 12 +- .../RimPlotCurveAppearance.cpp | 45 +- .../ProjectDataModel/RimPlotCurveAppearance.h | 9 +- .../ProjectDataModel/RimProject.cpp | 12 + .../ProjectDataModel/RimProject.h | 3 + .../Summary/CMakeLists_files.cmake | 4 + .../Summary/RimCurveStyleRule.cpp | 567 ++++++++++++++++++ .../Summary/RimCurveStyleRule.h | 138 +++++ .../Summary/RimCurveStyleRules.cpp | 535 +++++++++++++++++ .../Summary/RimCurveStyleRules.h | 113 ++++ .../Summary/RimCurveStyleSettings.cpp | 197 ++++++ .../Summary/RimCurveStyleSettings.h | 79 +++ .../Summary/RimSavedCurveAppearance.cpp | 76 +++ .../Summary/RimSavedCurveAppearance.h | 53 ++ ApplicationLibCode/UnitTests/CMakeLists.txt | 1 + .../UnitTests/RimCurveStyleSettings-Test.cpp | 32 + 18 files changed, 1915 insertions(+), 8 deletions(-) create mode 100644 ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleRule.cpp create mode 100644 ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleRule.h create mode 100644 ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleRules.cpp create mode 100644 ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleRules.h create mode 100644 ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleSettings.cpp create mode 100644 ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleSettings.h create mode 100644 ApplicationLibCode/ProjectDataModel/Summary/RimSavedCurveAppearance.cpp create mode 100644 ApplicationLibCode/ProjectDataModel/Summary/RimSavedCurveAppearance.h create mode 100644 ApplicationLibCode/UnitTests/RimCurveStyleSettings-Test.cpp diff --git a/ApplicationLibCode/Application/Tools/Summary/RiaSummaryTools.cpp b/ApplicationLibCode/Application/Tools/Summary/RiaSummaryTools.cpp index a1f705223e..d650dd21ed 100644 --- a/ApplicationLibCode/Application/Tools/Summary/RiaSummaryTools.cpp +++ b/ApplicationLibCode/Application/Tools/Summary/RiaSummaryTools.cpp @@ -68,6 +68,47 @@ std::vector RiaSummaryTools::singleTopLevelSummaryCases() return {}; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::set RiaSummaryTools::allSummaryCaseNames() +{ + std::set summaryCaseFilenames; + + for ( RimSummaryCase* rimCase : RimProject::current()->allSummaryCases() ) + { + summaryCaseFilenames.insert( rimCase->summaryHeaderFilename().toUtf8().constData() ); + } + + return summaryCaseFilenames; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::set RiaSummaryTools::allSummaryWellNames() +{ + std::set summaryWellNames; + + for ( RimSummaryCase* rimCase : RimProject::current()->allSummaryCases() ) + { + if ( !rimCase ) continue; + + RifSummaryReaderInterface* reader = rimCase->summaryReader(); + if ( !reader ) continue; + + for ( const auto& address : reader->allResultAddresses() ) + { + if ( address.category() == RifEclipseSummaryAddressDefines::SummaryCategory::SUMMARY_WELL ) + { + summaryWellNames.insert( address.wellName() ); + } + } + } + + return summaryWellNames; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/Application/Tools/Summary/RiaSummaryTools.h b/ApplicationLibCode/Application/Tools/Summary/RiaSummaryTools.h index 6c18b0d495..fe0c0c1d1d 100644 --- a/ApplicationLibCode/Application/Tools/Summary/RiaSummaryTools.h +++ b/ApplicationLibCode/Application/Tools/Summary/RiaSummaryTools.h @@ -27,6 +27,8 @@ #include #include +#include +#include #include class RifEclipseSummaryAddress; @@ -60,7 +62,9 @@ public: static std::vector singleTopLevelSummaryCases(); - static void notifyCalculatedCurveNameHasChanged( int calculationId, const QString& currentCurveName ); + static std::set allSummaryCaseNames(); + static std::set allSummaryWellNames(); + static void notifyCalculatedCurveNameHasChanged( int calculationId, const QString& currentCurveName ); static RimSummaryPlot* parentSummaryPlot( caf::PdmObject* object ); static RimSummaryMultiPlot* parentSummaryMultiPlot( caf::PdmObject* object ); diff --git a/ApplicationLibCode/ProjectDataModel/RimPlotCurve.h b/ApplicationLibCode/ProjectDataModel/RimPlotCurve.h index 06f6975cda..5fd5880779 100644 --- a/ApplicationLibCode/ProjectDataModel/RimPlotCurve.h +++ b/ApplicationLibCode/ProjectDataModel/RimPlotCurve.h @@ -75,11 +75,13 @@ public: void setSymbolSize( int sizeInPixels ); void setLineThickness( int thickness ); void resetAppearance(); - virtual Qt::BrushStyle fillStyle() const; - void setFillStyle( Qt::BrushStyle brushStyle ); - void setFillColor( const cvf::Color3f& fillColor ); - void setFillColorOpacity( float opacity ); - void setCurveColorOpacity( float opacity ); + + RimPlotCurveAppearance* curveAppearance() const { return m_curveAppearance(); } + virtual Qt::BrushStyle fillStyle() const; + void setFillStyle( Qt::BrushStyle brushStyle ); + void setFillColor( const cvf::Color3f& fillColor ); + void setFillColorOpacity( float opacity ); + void setCurveColorOpacity( float opacity ); bool isChecked() const; void setCheckState( bool isChecked ); diff --git a/ApplicationLibCode/ProjectDataModel/RimPlotCurveAppearance.cpp b/ApplicationLibCode/ProjectDataModel/RimPlotCurveAppearance.cpp index c6a8dc1fc1..130e83b1c6 100644 --- a/ApplicationLibCode/ProjectDataModel/RimPlotCurveAppearance.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimPlotCurveAppearance.cpp @@ -20,11 +20,12 @@ #include "RiaColorTools.h" +#include "cafPdmDefaultObjectFactory.h" #include "cafPdmUiComboBoxEditor.h" #include "cafPdmUiCommandSystemProxy.h" #include "cafPdmUiDoubleSliderEditor.h" -CAF_PDM_XML_ABSTRACT_SOURCE_INIT( RimPlotCurveAppearance, "PlotCurveAppearance" ); +CAF_PDM_XML_SOURCE_INIT( RimPlotCurveAppearance, "PlotCurveAppearance" ); namespace caf { @@ -236,9 +237,43 @@ void RimPlotCurveAppearance::defineUiOrdering( QString uiConfigName, caf::PdmUiO uiOrdering.add( &m_curveInterpolation ); m_curveInterpolation.uiCapability()->setUiHidden( !m_interpolationVisible ); + for ( auto field : std::vector{ &m_curveColor, + &m_curveColorOpacity, + &m_pointSymbol, + &m_symbolEdgeColor, + &m_symbolSize, + &m_symbolSkipPixelDistance, + &m_lineStyle, + &m_curveThickness, + &m_fillStyle, + &m_fillColor, + &m_fillColorOpacity, + &m_curveInterpolation } ) + { + field->uiCapability()->setUiReadOnly( m_uiLocked ); + } + + // Keep the fields that are irrelevant for the current symbol and line style read-only + const bool noSymbol = m_pointSymbol() == RiuPlotCurveSymbol::SYMBOL_NONE; + const bool noLine = m_lineStyle() == RiuQwtPlotCurveDefines::LineStyleEnum::STYLE_NONE; + m_symbolSize.uiCapability()->setUiReadOnly( m_uiLocked || noSymbol ); + m_symbolSkipPixelDistance.uiCapability()->setUiReadOnly( m_uiLocked || noSymbol ); + m_curveThickness.uiCapability()->setUiReadOnly( m_uiLocked || noLine ); + m_curveInterpolation.uiCapability()->setUiReadOnly( m_uiLocked || noLine ); + + if ( m_curveFittingToleranceVisible && m_uiLocked ) m_curveFittingTolerance.uiCapability()->setUiReadOnly( true ); + uiOrdering.skipRemainingFields(); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimPlotCurveAppearance::setUiLocked( bool isLocked ) +{ + m_uiLocked = isLocked; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -401,6 +436,14 @@ int RimPlotCurveAppearance::lineThickness() const return m_curveThickness; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimPlotCurveAppearance::copyFrom( const RimPlotCurveAppearance& other ) +{ + xmlCapability()->readObjectFromXmlString( other.xmlCapability()->writeObjectToXmlString(), caf::PdmDefaultObjectFactory::instance() ); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/RimPlotCurveAppearance.h b/ApplicationLibCode/ProjectDataModel/RimPlotCurveAppearance.h index 2316a756c9..22d54796d7 100644 --- a/ApplicationLibCode/ProjectDataModel/RimPlotCurveAppearance.h +++ b/ApplicationLibCode/ProjectDataModel/RimPlotCurveAppearance.h @@ -79,7 +79,10 @@ public: void setSymbolLabelPosition( RiuPlotCurveSymbol::LabelPosition labelPosition ); RiuPlotCurveSymbol::LabelPosition symbolLabelPosition() const; - void resetAppearance(); + void resetAppearance(); + + // Copies the visual settings from another appearance without emitting signals + void copyFrom( const RimPlotCurveAppearance& other ); Qt::BrushStyle fillStyle() const; void setFillStyle( Qt::BrushStyle brushStyle ); @@ -102,6 +105,9 @@ public: void setFillOptionsVisible( bool isVisible ); void setCurveFittingToleranceVisible( bool isVisible ); + // Shows all fields as read-only, used when the appearance is controlled elsewhere + void setUiLocked( bool isLocked ); + void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; protected: @@ -135,4 +141,5 @@ protected: bool m_interpolationVisible; bool m_fillOptionsVisible; bool m_curveFittingToleranceVisible; + bool m_uiLocked = false; }; diff --git a/ApplicationLibCode/ProjectDataModel/RimProject.cpp b/ApplicationLibCode/ProjectDataModel/RimProject.cpp index 977d610b67..7ea25ff877 100644 --- a/ApplicationLibCode/ProjectDataModel/RimProject.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimProject.cpp @@ -101,6 +101,7 @@ #include "RimWellPath.h" #include "RimWellPathCollection.h" #include "RimWellTargetMapping.h" +#include "Summary/RimCurveStyleRules.h" #include "Tools/RimAutomationSettings.h" #include "VerticalFlowPerformance/RimVfpDataCollection.h" #include "VerticalFlowPerformance/RimVfpPlotCollection.h" @@ -148,6 +149,9 @@ RimProject::RimProject() CAF_PDM_InitFieldNoDefault( &m_mainPlotCollection, "MainPlotCollection", "Plots" ); CAF_PDM_InitFieldNoDefault( &m_pinnedFieldCollection, "PinnedFieldCollection", "PinnedFieldCollection" ); + CAF_PDM_InitFieldNoDefault( &m_curveStyleRules, "CurveStyleRules", "Curve Style Rules" ); + m_curveStyleRules = new RimCurveStyleRules; + CAF_PDM_InitFieldNoDefault( &viewLinkerCollection, "LinkedViews", "Linked Views", ":/LinkView.svg" ); viewLinkerCollection = new RimViewLinkerCollection; @@ -346,6 +350,14 @@ RimQuickAccessCollection* RimProject::pinnedFieldCollection() const return m_pinnedFieldCollection(); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimCurveStyleRules* RimProject::curveStyleRules() const +{ + return m_curveStyleRules(); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/RimProject.h b/ApplicationLibCode/ProjectDataModel/RimProject.h index 095ccdb6ee..6a5d8f0c47 100644 --- a/ApplicationLibCode/ProjectDataModel/RimProject.h +++ b/ApplicationLibCode/ProjectDataModel/RimProject.h @@ -71,6 +71,7 @@ class RimCompletionTemplateCollection; class RimPlotTemplateFolderItem; class RimGridCalculationCollection; class RimQuickAccessCollection; +class RimCurveStyleRules; class RimAutomationSettings; class RimJobCollection; class RimEnsembleFileSetCollection; @@ -194,6 +195,7 @@ public: void updatesAfterProjectFileIsRead(); RimQuickAccessCollection* pinnedFieldCollection() const; + RimCurveStyleRules* curveStyleRules() const; RimAutomationSettings* automationSettings() const; RimEnsembleFileSetCollection* ensembleFileSetCollection() const; RimJobCollection* jobCollection() const; @@ -208,6 +210,7 @@ protected: private: caf::PdmChildField m_mainPlotCollection; caf::PdmChildField m_pinnedFieldCollection; + caf::PdmChildField m_curveStyleRules; caf::PdmChildField m_automationSettings; caf::PdmChildField m_ensembleFileSetCollection; caf::PdmChildField m_jobCollection; diff --git a/ApplicationLibCode/ProjectDataModel/Summary/CMakeLists_files.cmake b/ApplicationLibCode/ProjectDataModel/Summary/CMakeLists_files.cmake index 7cafead3b4..0d03ab04d4 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/CMakeLists_files.cmake +++ b/ApplicationLibCode/ProjectDataModel/Summary/CMakeLists_files.cmake @@ -8,6 +8,10 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RimSummaryCurve.cpp ${CMAKE_CURRENT_LIST_DIR}/RimSummaryCurvesData.cpp ${CMAKE_CURRENT_LIST_DIR}/RimSummaryCurveAppearanceCalculator.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimCurveStyleSettings.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimCurveStyleRule.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimCurveStyleRules.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimSavedCurveAppearance.cpp ${CMAKE_CURRENT_LIST_DIR}/RimSummaryCurveAutoName.cpp ${CMAKE_CURRENT_LIST_DIR}/RimSummaryPlotAxisFormatter.cpp ${CMAKE_CURRENT_LIST_DIR}/RimSummaryCurveCollection.cpp diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleRule.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleRule.cpp new file mode 100644 index 0000000000..f3ee0e0aa0 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleRule.cpp @@ -0,0 +1,567 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RimCurveStyleRule.h" + +#include "Summary/RiaSummaryTools.h" + +#include "RimCurveStyleRules.h" +#include "RimEnsembleCurveSet.h" +#include "RimEnsembleCurveSetCollection.h" +#include "RimPlotCurve.h" +#include "RimProject.h" +#include "RimSummaryCase.h" +#include "RimSummaryCurve.h" +#include "RimSummaryEnsemble.h" +#include "RimSummaryPlot.h" + +#include "RiuPlotCurveSymbolImageCreator.h" + +#include +#include +#include +#include + +#include + +namespace caf +{ +template <> +void caf::AppEnum::setUp() +{ + addItem( RimCurveStyleRule::Mode::AUTO, "AUTO", "Auto" ); + addItem( RimCurveStyleRule::Mode::CUSTOM, "CUSTOM", "Custom" ); + setDefault( RimCurveStyleRule::Mode::AUTO ); +} +} // namespace caf + +CAF_PDM_SOURCE_INIT( RimCurveStyleRule, "RimCurveStyleRule" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimCurveStyleRule::RimCurveStyleRule() +{ + CAF_PDM_InitObject( "Curve Style Rule" ); + + CAF_PDM_InitFieldNoDefault( &m_dimension, "Dimension", "Type" ); + CAF_PDM_InitFieldNoDefault( &m_itemName, "ItemName", "Name" ); + + CAF_PDM_InitFieldNoDefault( &m_mode, "Mode", "Assignment" ); + // m_mode.uiCapability()->setUiIconFromResourceString( ":/edit.svg" ); + + CAF_PDM_InitFieldNoDefault( &m_appearance, "CurveAppearance", "Curve Appearance" ); + m_appearance = new RimPlotCurveAppearance; + m_appearance.uiCapability()->setUiTreeChildrenHidden( true ); + connectAppearanceSignal(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRule::connectAppearanceSignal() +{ + if ( m_appearance() ) m_appearance->appearanceChanged.connect( this, &RimCurveStyleRule::onAppearanceChanged ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRule::initAfterRead() +{ + connectAppearanceSignal(); +} + +//-------------------------------------------------------------------------------------------------- +/// Editing any appearance value turns the item into a custom item +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRule::onAppearanceChanged( const caf::SignalEmitter* emitter ) +{ + m_mode = Mode::CUSTOM; + + if ( auto collection = firstAncestorOrThisOfType() ) + { + collection->rulesChanged(); + collection->updateConnectedEditors(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRule::applyToCurve( RimPlotCurve* curve, bool applyColor, bool applySymbol ) const +{ + if ( m_mode() != Mode::CUSTOM ) return; + + auto curveAppearance = curve->curveAppearance(); + const auto color = curveAppearance->color(); + const auto symbol = curveAppearance->symbol(); + const auto label = curveAppearance->symbolLabel(); + const auto labelPosition = curveAppearance->symbolLabelPosition(); + + curveAppearance->copyFrom( *m_appearance ); + + // Color and symbol are only controlled by the rule when its dimension is the one selected for them + if ( !applyColor ) curveAppearance->setColor( color ); + if ( !applySymbol ) curveAppearance->setSymbol( symbol ); + + // Labels are specific to each curve + curveAppearance->setSymbolLabel( label ); + curveAppearance->setSymbolLabelPosition( labelPosition ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::optional RimCurveStyleRule::overriddenColor() const +{ + if ( m_mode() != Mode::CUSTOM ) return std::nullopt; + return m_appearance->color(); +} + +namespace +{ +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool matchesItem( RimCurveStyleRule::Dimension dimension, const QString& itemName, const RifEclipseSummaryAddress& address, const std::string& caseName ) +{ + return QString::fromStdString( RimCurveStyleSettings::dimensionValue( dimension, address, caseName ) ) == itemName; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::string ensembleCaseName( RimEnsembleCurveSet* curveSet ) +{ + return curveSet->summaryEnsemble() ? curveSet->summaryEnsemble()->name().toStdString() : std::string(); +} +} // namespace + +//-------------------------------------------------------------------------------------------------- +/// Shows what the global appearance gives for the item. The assigned color and symbol are used even if no +/// plot has a matching curve. +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRule::updateAppearanceFromCurves() +{ + copyAppearanceFromCurves(); + if ( m_autoColor ) m_appearance->setColor( *m_autoColor ); + if ( m_autoSymbol ) m_appearance->setSymbol( *m_autoSymbol ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRule::setAutoAssigned( const std::optional& color, + const std::optional& symbol ) +{ + m_autoColor = color; + m_autoSymbol = symbol; +} + +//-------------------------------------------------------------------------------------------------- +/// Copies the appearance of the first matching curve +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRule::copyAppearanceFromCurves() +{ + auto project = RimProject::current(); + if ( !project ) return; + + for ( auto plot : project->descendantsIncludingThisOfType() ) + { + if ( !plot->useGlobalCurveAppearance() ) continue; + + for ( auto curve : plot->summaryCurves() ) + { + if ( matchesItem( m_dimension(), m_itemName(), curve->summaryAddressY(), RimCurveStyleSettings::caseName( curve->summaryCaseY() ) ) ) + { + m_appearance->copyFrom( *curve->curveAppearance() ); + return; + } + } + + for ( auto curveSet : plot->ensembleCurveSetCollection()->curveSets() ) + { + if ( matchesItem( m_dimension(), m_itemName(), curveSet->summaryAddressY(), ensembleCaseName( curveSet ) ) ) + { + m_appearance->setColor( curveSet->color() ); + return; + } + } + } +} +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimCurveStyleRule::Dimension RimCurveStyleRule::dimension() const +{ + return m_dimension(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimCurveStyleRule::itemName() const +{ + return m_itemName(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRule::setDimension( Dimension dimension ) +{ + m_dimension = dimension; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRule::setItemName( const QString& itemName ) +{ + m_itemName = itemName; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +namespace +{ +std::optional> availableNamesCache; +} // namespace + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimCurveStyleRule::AvailableNamesCache::AvailableNamesCache() + : m_isOwner( !availableNamesCache ) +{ + if ( m_isOwner ) availableNamesCache.emplace(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimCurveStyleRule::AvailableNamesCache::~AvailableNamesCache() +{ + if ( m_isOwner ) availableNamesCache.reset(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QStringList RimCurveStyleRule::availableNames( Dimension dimension ) +{ + if ( availableNamesCache ) + { + auto it = availableNamesCache->find( dimension ); + if ( it != availableNamesCache->end() ) return it->second; + } + + std::set names; + if ( dimension == Dimension::WELL ) + { + for ( const auto& wellName : RiaSummaryTools::allSummaryWellNames() ) + { + names.insert( QString::fromStdString( wellName ) ); + } + } + else if ( dimension == Dimension::CASE ) + { + if ( auto project = RimProject::current() ) + { + for ( auto ensemble : project->summaryEnsembles() ) + { + names.insert( ensemble->name() ); + } + for ( auto summaryCase : project->allSummaryCases() ) + { + if ( !summaryCase->ensemble() ) names.insert( summaryCase->displayCaseName() ); + } + } + } + else if ( auto project = RimProject::current() ) + { + for ( auto summaryCase : project->allSummaryCases() ) + { + auto reader = summaryCase->summaryReader(); + if ( !reader ) continue; + + for ( const auto& address : reader->allResultAddresses() ) + { + const auto value = RimCurveStyleSettings::dimensionValue( dimension, address, {} ); + if ( !value.empty() ) names.insert( QString::fromStdString( value ) ); + } + } + } + + QStringList result; + for ( const auto& name : names ) + { + result.push_back( name ); + } + + if ( availableNamesCache ) ( *availableNamesCache )[dimension] = result; + + return result; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::map RimCurveStyleRule::appearancesFromPlots( Dimension dimension ) +{ + std::map appearances; + + auto project = RimProject::current(); + if ( !project ) return appearances; + + for ( auto plot : project->descendantsIncludingThisOfType() ) + { + if ( !plot->useGlobalCurveAppearance() ) continue; + + for ( auto curve : plot->summaryCurves() ) + { + const auto value = RimCurveStyleSettings::dimensionValue( dimension, + curve->summaryAddressY(), + RimCurveStyleSettings::caseName( curve->summaryCaseY() ) ); + + auto& appearance = appearances[QString::fromStdString( value )]; + if ( !appearance.color ) appearance.color = curve->color(); + if ( !appearance.symbol ) appearance.symbol = curve->symbol(); + } + + for ( auto curveSet : plot->ensembleCurveSetCollection()->curveSets() ) + { + const auto value = RimCurveStyleSettings::dimensionValue( dimension, curveSet->summaryAddressY(), ensembleCaseName( curveSet ) ); + + auto& appearance = appearances[QString::fromStdString( value )]; + if ( !appearance.color ) appearance.color = curveSet->color(); + } + } + + return appearances; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimCurveStyleRule::isCustom() const +{ + return m_mode() == Mode::CUSTOM; +} +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRule::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) +{ + // The available names depend on the type + if ( changedField == &m_dimension ) m_itemName = QString(); + if ( changedField == &m_mode && m_mode() == Mode::AUTO ) updateAppearanceFromCurves(); + + if ( auto collection = firstAncestorOrThisOfType() ) + { + collection->rulesChanged(); + collection->updateConnectedEditors(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRule::appendFieldsToUiOrdering( const QString& uiConfigName, caf::PdmUiOrdering& uiOrdering ) +{ + uiOrdering.add( &m_mode ); + + // Auto shows the values given by the global appearance, editing any value makes the item custom + if ( m_mode() == Mode::AUTO ) updateAppearanceFromCurves(); + + auto appearanceGroup = uiOrdering.addNewGroup( "Curve Style" ); + m_appearance->uiOrdering( "AppearanceOrdering", *appearanceGroup ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRule::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) +{ + uiOrdering.add( &m_dimension ); + uiOrdering.add( &m_itemName ); + uiOrdering.skipRemainingFields(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +caf::IconProvider RimCurveStyleRule::swatchIcon( const cvf::Color3f& color ) +{ + QPixmap pixmap( 16, 16 ); + pixmap.fill( Qt::transparent ); + + QPainter painter( &pixmap ); + painter.setPen( Qt::darkGray ); + painter.setBrush( QColor::fromRgbF( color.r(), color.g(), color.b() ) ); + painter.drawRect( 1, 1, 13, 13 ); + + return caf::IconProvider( pixmap ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +caf::IconProvider RimCurveStyleRule::paletteIcon() +{ + QPixmap pixmap( 16, 16 ); + pixmap.fill( Qt::transparent ); + + QLinearGradient gradient( 0, 0, 16, 0 ); + gradient.setColorAt( 0.0, Qt::red ); + gradient.setColorAt( 0.5, Qt::green ); + gradient.setColorAt( 1.0, Qt::blue ); + + QPainter painter( &pixmap ); + painter.setPen( Qt::darkGray ); + painter.setBrush( gradient ); + painter.drawRect( 1, 1, 13, 13 ); + + return caf::IconProvider( pixmap ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +caf::IconProvider RimCurveStyleRule::symbolIcon() +{ + QPixmap pixmap( 16, 16 ); + pixmap.fill( Qt::transparent ); + + QPainter painter( &pixmap ); + painter.setRenderHint( QPainter::Antialiasing ); + painter.setPen( Qt::black ); + painter.setBrush( Qt::NoBrush ); + painter.drawEllipse( 2, 2, 5, 5 ); + painter.drawRect( 9, 2, 5, 5 ); + painter.drawLine( 2, 14, 8, 9 ); + painter.drawLine( 8, 9, 14, 14 ); + painter.drawLine( 2, 14, 14, 14 ); + + return caf::IconProvider( pixmap ); +} + +namespace +{ +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString dimensionIconPath( RimCurveStyleRule::Dimension dimension ) +{ + using D = RimCurveStyleRule::Dimension; + switch ( dimension ) + { + case D::CASE: + return ":/SummaryCase.svg"; + case D::VECTOR: + return ":/summary/components/images/misc.svg"; + case D::WELL: + case D::LGR: + return ":/summary/components/images/well.svg"; + case D::GROUP: + return ":/summary/components/images/group.svg"; + case D::NETWORK: + return ":/summary/components/images/network.svg"; + case D::REGION: + return ":/summary/components/images/region.svg"; + case D::REGION_2_REGION: + return ":/summary/components/images/region-region.svg"; + case D::AQUIFER: + return ":/summary/components/images/aquifer.svg"; + case D::BLOCK: + case D::CONNECTION: + return ":/summary/components/images/block.svg"; + case D::SEGMENT: + return ":/summary/components/images/segment.svg"; + default: + return ":/DataVector.svg"; + } +} +} // namespace + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +caf::IconProvider RimCurveStyleRule::dimensionColorIcon( Dimension dimension, + const std::optional& color, + const std::optional& symbol ) +{ + QPixmap pixmap( 52, 16 ); + pixmap.fill( Qt::transparent ); + + QPainter painter( &pixmap ); + painter.drawPixmap( 0, 0, QIcon( dimensionIconPath( dimension ) ).pixmap( 16, 16 ) ); + + const QColor qColor = color ? QColor::fromRgbF( color->r(), color->g(), color->b() ) : QColor( Qt::black ); + + int x = 19; + if ( color ) + { + painter.setPen( Qt::darkGray ); + painter.setBrush( qColor ); + painter.drawRect( x, 1, 13, 13 ); + x += 17; + } + + if ( symbol && *symbol != RiuPlotCurveSymbol::SYMBOL_NONE ) + { + painter.drawImage( x, 1, RiuPlotCurveSymbolImageCreator::createSymbolImage( *symbol, QSize( 14, 14 ), qColor ) ); + } + + return caf::IconProvider( pixmap ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::optional RimCurveStyleRule::overriddenSymbol() const +{ + if ( m_mode() != Mode::CUSTOM ) return std::nullopt; + return m_appearance->symbol(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +caf::IconProvider RimCurveStyleRule::dimensionIcon( Dimension dimension ) +{ + return caf::IconProvider( dimensionIconPath( dimension ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QList RimCurveStyleRule::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) +{ + QList options; + + if ( fieldNeedingOptions == &m_mode ) + { + options.push_back( caf::PdmOptionItemInfo( "Auto", Mode::AUTO, false, caf::IconProvider( ":/Refresh.svg" ) ) ); + options.push_back( caf::PdmOptionItemInfo( "Custom", Mode::CUSTOM, false, caf::IconProvider( ":/edit.svg" ) ) ); + } + + return options; +} \ No newline at end of file diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleRule.h b/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleRule.h new file mode 100644 index 0000000000..e6eede6c7b --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleRule.h @@ -0,0 +1,138 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "RimCurveStyleSettings.h" +#include "RimPlotCurveAppearance.h" + +#include "RiuPlotCurveSymbol.h" + +#include "cafAppEnum.h" +#include "cafIconProvider.h" +#include "cafPdmField.h" +#include "cafPdmFieldCvfColor.h" +#include "cafPdmObject.h" + +#include "cvfColor3.h" + +#include +#include + +#include +#include + +class RimPlotCurve; + +//================================================================================================== +/// Project-stored curve appearance for one specific summary case, ensemble or well. The values are +/// edited in the curve appearance dock window. +//================================================================================================== +class RimCurveStyleRule : public caf::PdmObject +{ + CAF_PDM_HEADER_INIT; + +public: + using Dimension = RimCurveStyleSettings::Dimension; + + enum class Mode + { + AUTO, + CUSTOM + }; + + RimCurveStyleRule(); + + Dimension dimension() const; + QString itemName() const; + + void setDimension( Dimension dimension ); + void setItemName( const QString& itemName ); + + // Applies the rule to the curve if it is custom. Color and symbol are only applied when requested + void applyToCurve( RimPlotCurve* curve, bool applyColor, bool applySymbol ) const; + + static QStringList availableNames( Dimension dimension ); + + // Caches availableNames() while in scope, as finding them can require scanning all summary addresses + class AvailableNamesCache + { + public: + AvailableNamesCache(); + ~AvailableNamesCache(); + AvailableNamesCache( const AvailableNamesCache& ) = delete; + AvailableNamesCache& operator=( const AvailableNamesCache& ) = delete; + + private: + bool m_isOwner; + }; + + struct PlotAppearance + { + std::optional color; + std::optional symbol; + }; + + // The appearance of the first curve for each item in plots using the global appearance + static std::map appearancesFromPlots( Dimension dimension ); + + static caf::IconProvider swatchIcon( const cvf::Color3f& color ); + static caf::IconProvider paletteIcon(); + static caf::IconProvider symbolIcon(); + static caf::IconProvider dimensionIcon( Dimension dimension ); + + // Dimension icon followed by color and/or symbol swatches, when given. The symbol is drawn with the color if present. + static caf::IconProvider dimensionColorIcon( Dimension dimension, + const std::optional& color, + const std::optional& symbol = std::nullopt ); + bool isCustom() const; + + // The custom symbol, if the rule is custom + std::optional overriddenSymbol() const; + + // Adds the mode, color and symbol fields, to be edited for the object selected in the collection + void appendFieldsToUiOrdering( const QString& uiConfigName, caf::PdmUiOrdering& uiOrdering ); + + // The properties currently assigned automatically by the global appearance, shown while the item is Auto + void setAutoAssigned( const std::optional& color, const std::optional& symbol ); + void updateAppearanceFromCurves(); + + // The custom color, if the rule is custom + std::optional overriddenColor() const; + +protected: + void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override; + QList calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override; + void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; + void initAfterRead() override; + +private: + void copyAppearanceFromCurves(); + void connectAppearanceSignal(); + void onAppearanceChanged( const caf::SignalEmitter* emitter ); + +private: + caf::PdmField> m_dimension; + caf::PdmField m_itemName; + caf::PdmField> m_mode; + + // Color, symbol, line and fill settings that can be overridden + caf::PdmChildField m_appearance; + + std::optional m_autoColor; + std::optional m_autoSymbol; +}; \ No newline at end of file diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleRules.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleRules.cpp new file mode 100644 index 0000000000..89af274b84 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleRules.cpp @@ -0,0 +1,535 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RimCurveStyleRules.h" + +#include "RiaGuiApplication.h" + +#include "RimColorLegend.h" +#include "RimColorLegendCollection.h" +#include "RimColorLegendItem.h" +#include "RimCurveStyleRule.h" +#include "RimCurveStyleSettings.h" +#include "RimEnsembleCurveSet.h" +#include "RimEnsembleCurveSetCollection.h" +#include "RimProject.h" +#include "RimSummaryCurve.h" +#include "RimSummaryCurveAppearanceCalculator.h" +#include "RimSummaryPlot.h" + +#include "RiuPlotCurveSymbolImageCreator.h" +#include "RiuPlotMainWindow.h" + +#include "cafPdmUiLabel.h" +#include "cafPdmUiListEditor.h" + +#include +#include +#include + +CAF_PDM_SOURCE_INIT( RimCurveStyleRules, "RimCurveStyleRules" ); + +namespace caf +{ +template <> +void caf::AppEnum::setUp() +{ + using T = RimCurveStyleRules::AppearanceType; + addItem( T::COLOR, "COLOR", "Color" ); + addItem( T::SYMBOL, "SYMBOL", "Symbol" ); + setDefault( T::COLOR ); +} +} // namespace caf + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimCurveStyleRules::RimCurveStyleRules() +{ + CAF_PDM_InitObject( "Curve Style Rules" ); + + CAF_PDM_InitField( &m_useGlobalAppearance, + "UseGlobalAppearance", + false, + "Use Global Curve Appearance", + "", + "Assign curve appearance in all summary plots from the global curve appearance definition" ); + + CAF_PDM_InitFieldNoDefault( &m_settings, "Settings", "Settings" ); + m_settings.uiCapability()->setUiTreeChildrenHidden( true ); + m_settings.uiCapability()->setUiHidden( true ); + m_settings = new RimCurveStyleSettings; + + CAF_PDM_InitFieldNoDefault( &m_savedAppearances, "SavedAppearances", "Saved Appearances" ); + m_savedAppearances.uiCapability()->setUiTreeChildrenHidden( true ); + m_savedAppearances.uiCapability()->setUiHidden( true ); + + CAF_PDM_InitFieldNoDefault( &m_rules, "Rules", "Rules" ); + m_rules.uiCapability()->setUiTreeChildrenHidden( true ); + m_rules.uiCapability()->setUiHidden( true ); + + CAF_PDM_InitFieldNoDefault( &m_appearanceType, "AppearanceType", "Appearance Property" ); + m_appearanceType.xmlCapability()->disableIO(); + + CAF_PDM_InitFieldNoDefault( &m_autoColorLegend, "AutoColorLegend", "Color Palette" ); + + CAF_PDM_InitField( &m_symbolLegend, "SymbolLegend", 0, "Symbol Sequence" ); + m_symbolLegend.uiCapability()->setUiEditorTypeName( caf::PdmUiListEditor::uiEditorTypeName() ); + m_symbolLegend.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::LabelPosition::TOP ); + m_symbolLegend.uiCapability()->setUiReadOnly( true ); + m_symbolLegend.uiCapability()->setAttribute( caf::PdmUiListEditor::Keys::HEIGHT_HINT, 120 ); + m_symbolLegend.xmlCapability()->disableIO(); + + CAF_PDM_InitField( &m_currentType, + "CurrentType", + caf::AppEnum( RimCurveStyleRule::Dimension::CASE ), + "Differentiate By" ); + m_currentType.xmlCapability()->disableIO(); + + CAF_PDM_InitField( &m_currentItemIndex, "CurrentItemIndex", 0, "Items" ); + m_currentItemIndex.uiCapability()->setUiEditorTypeName( caf::PdmUiListEditor::uiEditorTypeName() ); + m_currentItemIndex.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::LabelPosition::TOP ); + m_currentItemIndex.uiCapability()->setAttribute( caf::PdmUiListEditor::Keys::HEIGHT_HINT, 200 ); + m_currentItemIndex.xmlCapability()->disableIO(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimCurveStyleSettings* RimCurveStyleRules::settings() const +{ + return m_settings(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimCurveStyleRules::rules() const +{ + return m_rules.childrenByType(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimCurveStyleRules::rulesFor( const RifEclipseSummaryAddress& address, const std::string& caseName ) const +{ + const auto colorDimension = m_settings->colorDimension(); + const auto symbolDimension = m_settings->symbolDimension(); + + std::vector result; + for ( auto rule : m_rules ) + { + if ( !rule->isCustom() ) continue; + + const auto dimension = rule->dimension(); + if ( dimension != colorDimension && dimension != symbolDimension ) continue; + + const auto value = RimCurveStyleSettings::dimensionValue( dimension, address, caseName ); + if ( !value.empty() && QString::fromStdString( value ) == rule->itemName() ) result.push_back( rule ); + } + + // Fixed precedence by dimension, so the result does not depend on the order the rules were created in + std::stable_sort( result.begin(), + result.end(), + []( const RimCurveStyleRule* a, const RimCurveStyleRule* b ) { return a->dimension() < b->dimension(); } ); + + return result; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRules::rulesChanged() +{ + auto project = RimProject::current(); + if ( !project ) return; + + RimCurveStyleRule::AvailableNamesCache namesCache; + for ( auto plot : project->descendantsIncludingThisOfType() ) + { + plot->updateCurveAppearanceFromGlobalSettings(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRules::addGlobalAppearanceLink( caf::PdmUiOrdering& uiOrdering, const QString& text ) +{ + auto label = uiOrdering.addNewLabel( text ); + label->setLinkActivatedCallback( + []( const QString& ) + { + if ( auto window = RiaGuiApplication::instance()->mainPlotWindow() ) window->showCurveAppearance(); + } ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimCurveStyleRules::useGlobalAppearance() const +{ + return m_useGlobalAppearance(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRules::setUseGlobalAppearance( bool enable ) +{ + if ( m_useGlobalAppearance() == enable ) return; + + m_useGlobalAppearance = enable; + applyGlobalAppearanceMode(); +} + +//-------------------------------------------------------------------------------------------------- +/// Applies the global appearance to all plots when enabled, and restores the original appearance when disabled +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRules::applyGlobalAppearanceMode() +{ + auto project = RimProject::current(); + if ( !project ) return; + + if ( m_useGlobalAppearance() ) + { + RimCurveStyleRule::AvailableNamesCache namesCache; + + m_savedAppearances.deleteChildren(); + for ( auto plot : project->descendantsIncludingThisOfType() ) + { + saveAppearance( plot ); + plot->updateCurveAppearanceFromGlobalSettings(); + } + } + else + { + restoreAppearances(); + m_savedAppearances.deleteChildren(); + } + + updateConnectedEditors(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRules::saveAppearance( RimSummaryPlot* plot ) +{ + for ( auto curve : plot->summaryCurves() ) + { + auto saved = new RimSavedCurveAppearance; + saved->setCurve( curve ); + m_savedAppearances.push_back( saved ); + } + + for ( auto curveSet : plot->ensembleCurveSetCollection()->curveSets() ) + { + auto saved = new RimSavedCurveAppearance; + saved->setEnsembleCurveSet( curveSet ); + m_savedAppearances.push_back( saved ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// Plots without any saved appearance, typically created while the global appearance was enabled, get the +/// calculated default appearance +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRules::restoreAppearances() +{ + auto project = RimProject::current(); + if ( !project ) return; + + std::set restoredPlots; + for ( auto saved : m_savedAppearances ) + { + if ( auto restored = saved->restore() ) + { + if ( auto plot = restored->firstAncestorOrThisOfType() ) restoredPlots.insert( plot ); + } + } + + for ( auto plot : project->descendantsIncludingThisOfType() ) + { + if ( !restoredPlots.contains( plot ) ) plot->applyDefaultCurveAppearances(); + + plot->updateConnectedEditors(); + plot->loadDataAndUpdate(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::optional RimCurveStyleRules::autoLegendColor( int index ) const +{ + if ( !m_autoColorLegend() || index < 0 ) return std::nullopt; + + const auto items = m_autoColorLegend()->colorLegendItems(); + if ( items.empty() ) return std::nullopt; + + return items[index % items.size()]->color(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QList RimCurveStyleRules::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) +{ + QList options; + + if ( fieldNeedingOptions == &m_autoColorLegend ) + { + options.push_back( caf::PdmOptionItemInfo( "Default Palette", nullptr ) ); + if ( auto project = RimProject::current() ) + { + for ( auto legend : project->colorLegendCollection()->allColorLegends() ) + { + options.push_back( caf::PdmOptionItemInfo( legend->colorLegendName(), legend, false, legend->paletteIconProvider() ) ); + } + } + } + else if ( fieldNeedingOptions == &m_symbolLegend ) + { + const int count = static_cast( caf::AppEnum::size() ) - 1; + for ( int i = 0; i < count; i++ ) + { + const auto symbol = RimSummaryCurveAppearanceCalculator::cycledSymbol( i ); + const auto image = RiuPlotCurveSymbolImageCreator::createSymbolImage( symbol, QSize( 14, 14 ), QColor( Qt::black ) ); + options.push_back( caf::PdmOptionItemInfo( caf::AppEnum::uiText( symbol ), + i, + false, + caf::IconProvider( QPixmap::fromImage( image ) ) ) ); + } + } + else if ( fieldNeedingOptions == &m_currentType ) + { + using D = RimCurveStyleRule::Dimension; + for ( size_t i = 0; i < caf::AppEnum::size(); i++ ) + { + const auto dimension = caf::AppEnum::fromIndex( i ); + options.push_back( caf::PdmOptionItemInfo( caf::AppEnum::uiText( dimension ), static_cast( dimension ) ) ); + } + } + else if ( fieldNeedingOptions == &m_currentItemIndex ) + { + RimCurveStyleRule::AvailableNamesCache namesCache; + + const bool isColorDim = m_appearanceType() == AppearanceType::COLOR; + const bool isSymbolDim = m_appearanceType() == AppearanceType::SYMBOL; + const auto plotAppearances = RimCurveStyleRule::appearancesFromPlots( m_currentType() ); + for ( const auto& name : RimCurveStyleRule::availableNames( m_currentType() ) ) + { + const auto customRule = findRule( m_currentType(), name ); + const bool isCustom = customRule && customRule->isCustom(); + const int index = static_cast( options.size() ); + std::optional autoColor; + std::optional autoSymbol; + assignedAppearance( name, index, plotAppearances, autoColor, autoSymbol ); + + std::optional color; + if ( isColorDim && autoColor ) color = isCustom ? customRule->overriddenColor().value_or( *autoColor ) : autoColor; + + std::optional symbol; + if ( isSymbolDim && autoSymbol ) symbol = isCustom ? customRule->overriddenSymbol().value_or( *autoSymbol ) : autoSymbol; + const auto icon = RimCurveStyleRule::dimensionColorIcon( m_currentType(), color, symbol ); + options.push_back( + caf::PdmOptionItemInfo( isCustom ? name + " [Custom]" : name + " [Auto]", static_cast( options.size() ), false, icon ) ); + } + } + + return options; +} + +//-------------------------------------------------------------------------------------------------- +/// The color and symbol the global appearance assigns to the item at the given position +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRules::assignedAppearance( const QString& name, + int index, + const std::map& plotAppearances, + std::optional& color, + std::optional& symbol ) const +{ + const bool projectWide = m_currentType() == RimCurveStyleRule::Dimension::CASE || m_currentType() == RimCurveStyleRule::Dimension::WELL; + + const auto fromPlots = plotAppearances.find( name ); + const bool inPlots = fromPlots != plotAppearances.end(); + + if ( projectWide ) color = RimSummaryCurveAppearanceCalculator::cycledPaletteColor( index ); + if ( !color && inPlots ) color = fromPlots->second.color; + if ( !color ) color = RimSummaryCurveAppearanceCalculator::cycledPaletteColor( index ); + + if ( projectWide ) symbol = RimSummaryCurveAppearanceCalculator::cycledSymbol( index ); + if ( !symbol && inPlots ) symbol = fromPlots->second.symbol; + if ( !symbol ) symbol = RimSummaryCurveAppearanceCalculator::cycledSymbol( index ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimCurveStyleRule* RimCurveStyleRules::findRule( RimCurveStyleRule::Dimension dimension, const QString& name ) const +{ + for ( auto item : m_rules ) + { + if ( item->dimension() == dimension && item->itemName() == name ) return item; + } + + return nullptr; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRules::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) +{ + RimCurveStyleRule::AvailableNamesCache namesCache; + + if ( changedField == &m_useGlobalAppearance ) + { + applyGlobalAppearanceMode(); + return; + } + + if ( changedField == &m_autoColorLegend ) + { + updateConnectedEditors(); + rulesChanged(); + return; + } + + if ( changedField == &m_appearanceType ) + { + m_currentType = currentDimension(); + m_currentItemIndex = 0; + updateRulesForCurrentItem(); + updateConnectedEditors(); + return; + } + + if ( changedField == &m_currentType ) + { + if ( auto settings = m_settings() ) + { + if ( m_appearanceType() == AppearanceType::COLOR ) + settings->setColorDimension( m_currentType() ); + else + settings->setSymbolDimension( m_currentType() ); + } + m_currentItemIndex = 0; + rulesChanged(); + } + + if ( changedField == &m_currentType || changedField == &m_currentItemIndex ) updateRulesForCurrentItem(); + + updateConnectedEditors(); +} + +//-------------------------------------------------------------------------------------------------- +/// Removes Auto rules for other items, as they hold no settings, and creates the rule edited for the current item +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRules::updateRulesForCurrentItem() +{ + const QString itemName = currentItemName(); + + for ( auto rule : m_rules.childrenByType() ) + { + if ( rule->isCustom() || ( rule->dimension() == m_currentType() && rule->itemName() == itemName ) ) continue; + + m_rules.removeChild( rule ); + delete rule; + } + + createRuleForCurrentItem(); +} + +//-------------------------------------------------------------------------------------------------- +/// Rules are Auto until changed to Custom +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRules::createRuleForCurrentItem() +{ + const QString itemName = currentItemName(); + if ( itemName.isEmpty() || findRule( m_currentType(), itemName ) ) return; + + auto rule = new RimCurveStyleRule; + rule->setDimension( m_currentType() ); + rule->setItemName( itemName ); + m_rules.push_back( rule ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimCurveStyleRules::currentItemName() const +{ + const auto names = RimCurveStyleRule::availableNames( m_currentType() ); + const int index = m_currentItemIndex(); + return index >= 0 && index < names.size() ? names[index] : QString(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimCurveStyleRule::Dimension RimCurveStyleRules::currentDimension() const +{ + auto settings = m_settings(); + if ( !settings ) return RimCurveStyleRule::Dimension::NONE; + + return m_appearanceType() == AppearanceType::COLOR ? settings->colorDimension() : settings->symbolDimension(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleRules::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) +{ + RimCurveStyleRule::AvailableNamesCache namesCache; + + // The dimension is stored in the global settings, which can also be changed elsewhere + m_currentType = currentDimension(); + + uiOrdering.add( &m_useGlobalAppearance ); + uiOrdering.add( &m_appearanceType ); + uiOrdering.add( &m_currentType ); + + auto rulesGroup = uiOrdering.addNewGroup( "Assigned Appearance" ); + if ( m_appearanceType() == AppearanceType::COLOR ) + rulesGroup->add( &m_autoColorLegend ); + else + rulesGroup->add( &m_symbolLegend ); + rulesGroup->add( &m_currentItemIndex ); + + // The item shown before the user selects one also needs a rule to be edited + createRuleForCurrentItem(); + + const QString itemName = currentItemName(); + if ( auto selected = findRule( m_currentType(), itemName ) ) + { + std::optional autoColor; + std::optional autoSymbol; + assignedAppearance( itemName, m_currentItemIndex(), RimCurveStyleRule::appearancesFromPlots( m_currentType() ), autoColor, autoSymbol ); + selected->setAutoAssigned( m_appearanceType() == AppearanceType::COLOR ? autoColor : std::nullopt, + m_appearanceType() == AppearanceType::SYMBOL ? autoSymbol : std::nullopt ); + if ( !selected->isCustom() ) selected->updateAppearanceFromCurves(); + + auto appearanceGroup = uiOrdering.addNewGroup( "Selected Item" ); + selected->appendFieldsToUiOrdering( uiConfigName, *appearanceGroup ); + } + + uiOrdering.skipRemainingFields(); +} \ No newline at end of file diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleRules.h b/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleRules.h new file mode 100644 index 0000000000..42913008ae --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleRules.h @@ -0,0 +1,113 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "RimCurveStyleRule.h" +#include "RimPlotCurveAppearance.h" +#include "RimSavedCurveAppearance.h" + +#include "cafPdmChildArrayField.h" +#include "cafPdmChildField.h" +#include "cafPdmField.h" +#include "cafPdmObject.h" +#include "cafPdmPtrField.h" + +#include "cvfColor3.h" + +#include +#include +#include + +class RimColorLegend; +class RimEnsembleCurveSet; +class RimPlotCurve; +class RimSummaryPlot; +class RifEclipseSummaryAddress; + +//================================================================================================== +/// Project-stored list of per-item style rules for the global curve appearance. +//================================================================================================== +class RimCurveStyleRules : public caf::PdmObject +{ + CAF_PDM_HEADER_INIT; + +public: + RimCurveStyleRules(); + + std::vector rules() const; + + // Custom rules for the dimensions selected for color or symbol, ordered so that later entries win: by dimension + std::vector rulesFor( const RifEclipseSummaryAddress& address, const std::string& caseName ) const; + + RimCurveStyleSettings* settings() const; + + // Re-applies the global appearance to all plots using it + void rulesChanged(); + + // Color from the project color legend used for auto colors, if one is selected + std::optional autoLegendColor( int index ) const; + + // Configures a field as a label with a link that shows the global curve appearance + static void addGlobalAppearanceLink( caf::PdmUiOrdering& uiOrdering, const QString& text ); + + // The global appearance applies to all summary plots in the project + bool useGlobalAppearance() const; + void setUseGlobalAppearance( bool enable ); + +protected: + QList calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override; + void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override; + void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; + +public: + enum class AppearanceType + { + COLOR, + SYMBOL + }; + +private: + void assignedAppearance( const QString& name, + int index, + const std::map& plotAppearances, + std::optional& color, + std::optional& symbol ) const; + RimCurveStyleRule* findRule( RimCurveStyleRule::Dimension dimension, const QString& name ) const; + QString currentItemName() const; + void updateRulesForCurrentItem(); + void createRuleForCurrentItem(); + + void applyGlobalAppearanceMode(); + void saveAppearance( RimSummaryPlot* plot ); + void restoreAppearances(); + + // The curve appearance before the global appearance was enabled, restored when disabled + caf::PdmChildArrayField m_savedAppearances; + caf::PdmField m_useGlobalAppearance; + + // The dimension controlling the selected appearance type, as stored in the global settings + RimCurveStyleRule::Dimension currentDimension() const; + + caf::PdmChildField m_settings; + caf::PdmChildArrayField m_rules; + caf::PdmField> m_appearanceType; + caf::PdmField> m_currentType; + caf::PdmPtrField m_autoColorLegend; + caf::PdmField m_symbolLegend; + caf::PdmField m_currentItemIndex; +}; diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleSettings.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleSettings.cpp new file mode 100644 index 0000000000..17d4336195 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleSettings.cpp @@ -0,0 +1,197 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RimCurveStyleSettings.h" + +#include "RifEclipseSummaryAddress.h" + +#include "RimCurveStyleRules.h" +#include "RimSummaryCase.h" +#include "RimSummaryEnsemble.h" + +namespace caf +{ +template <> +void caf::AppEnum::setUp() +{ + using D = RimCurveStyleSettings::Dimension; + addItem( D::NONE, "NONE", "None" ); + addItem( D::CASE, "CASE", "Summary Case" ); + addItem( D::VECTOR, "VECTOR", "Vector" ); + addItem( D::WELL, "WELL", "Well" ); + addItem( D::GROUP, "GROUP", "Group" ); + addItem( D::NETWORK, "NETWORK", "Network" ); + addItem( D::REGION, "REGION", "Region" ); + addItem( D::REGION_2_REGION, "REGION_2_REGION", "Region-Region" ); + addItem( D::AQUIFER, "AQUIFER", "Aquifer" ); + addItem( D::LGR, "LGR", "LGR" ); + addItem( D::BLOCK, "BLOCK", "Block" ); + addItem( D::CONNECTION, "CONNECTION", "Connection" ); + addItem( D::SEGMENT, "SEGMENT", "Segment" ); + setDefault( D::NONE ); +} +} // namespace caf + +CAF_PDM_SOURCE_INIT( RimCurveStyleSettings, "RimCurveStyleSettings" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimCurveStyleSettings::RimCurveStyleSettings() +{ + CAF_PDM_InitObject( "Global Curve Appearance" ); + + CAF_PDM_InitField( &m_colorDimension, "ColorDimension", DimensionEnum( Dimension::CASE ), "Color", "", "Data dimension controlling the curve color" ); + CAF_PDM_InitField( &m_symbolDimension, + "SymbolDimension", + DimensionEnum( Dimension::NONE ), + "Symbol", + "", + "Data dimension controlling the curve symbol" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimCurveStyleSettings::Dimension RimCurveStyleSettings::colorDimension() const +{ + return m_colorDimension(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimCurveStyleSettings::Dimension RimCurveStyleSettings::symbolDimension() const +{ + return m_symbolDimension(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleSettings::setColorDimension( Dimension dimension ) +{ + m_colorDimension = dimension; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleSettings::setSymbolDimension( Dimension dimension ) +{ + m_symbolDimension = dimension; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::string RimCurveStyleSettings::caseName( const RimSummaryCase* summaryCase ) +{ + if ( !summaryCase ) return {}; + if ( summaryCase->ensemble() ) return summaryCase->ensemble()->name().toStdString(); + return summaryCase->displayCaseName().toStdString(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::string RimCurveStyleSettings::dimensionValue( Dimension dimension, const RifEclipseSummaryAddress& address, const std::string& caseName ) +{ + using Category = RifEclipseSummaryAddressDefines::SummaryCategory; + + switch ( dimension ) + { + case Dimension::CASE: + return caseName; + + case Dimension::VECTOR: + { + std::string name = address.vectorName(); + if ( address.isHistoryVector() && !name.empty() ) name.pop_back(); + return name; + } + + case Dimension::WELL: + return address.wellName(); + + case Dimension::GROUP: + return address.groupName(); + + case Dimension::NETWORK: + return address.networkName(); + + case Dimension::REGION: + return address.category() == Category::SUMMARY_REGION ? address.itemUiText() : std::string(); + + case Dimension::REGION_2_REGION: + return address.category() == Category::SUMMARY_REGION_2_REGION ? address.itemUiText() : std::string(); + + case Dimension::AQUIFER: + return address.category() == Category::SUMMARY_AQUIFER ? address.itemUiText() : std::string(); + + case Dimension::LGR: + return address.lgrName(); + + case Dimension::BLOCK: + return address.category() == Category::SUMMARY_BLOCK || address.category() == Category::SUMMARY_BLOCK_LGR ? address.itemUiText() + : std::string(); + + case Dimension::CONNECTION: + return address.category() == Category::SUMMARY_WELL_COMPLETION || address.category() == Category::SUMMARY_WELL_CONNECTION || + address.category() == Category::SUMMARY_WELL_CONNECTION_LGR + ? address.itemUiText() + : std::string(); + + case Dimension::SEGMENT: + return address.category() == Category::SUMMARY_WELL_SEGMENT ? address.itemUiText() : std::string(); + + default: + return std::string(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleSettings::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) +{ + // The collection list shows colors that depend on the global appearance + if ( auto collection = firstAncestorOrThisOfType() ) + { + collection->rulesChanged(); + collection->updateConnectedEditors(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleSettings::appendFieldsToUiOrdering( caf::PdmUiOrdering& uiOrdering ) +{ + uiOrdering.add( &m_colorDimension ); + uiOrdering.add( &m_symbolDimension ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCurveStyleSettings::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) +{ + appendFieldsToUiOrdering( uiOrdering ); + uiOrdering.skipRemainingFields(); +} diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleSettings.h b/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleSettings.h new file mode 100644 index 0000000000..b8dc2967af --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimCurveStyleSettings.h @@ -0,0 +1,79 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "cafAppEnum.h" +#include "cafPdmField.h" +#include "cafPdmObject.h" + +#include + +class RifEclipseSummaryAddress; +class RimSummaryCase; + +//================================================================================================== +/// Global definition of which data dimension controls the curve color and the curve symbol. +/// Used by summary plots that have "Use Global Curve Appearance" enabled. +//================================================================================================== +class RimCurveStyleSettings : public caf::PdmObject +{ + CAF_PDM_HEADER_INIT; + +public: + enum class Dimension + { + NONE, + CASE, + VECTOR, + WELL, + GROUP, + NETWORK, + REGION, + REGION_2_REGION, + AQUIFER, + LGR, + BLOCK, + CONNECTION, + SEGMENT + }; + using DimensionEnum = caf::AppEnum; + + RimCurveStyleSettings(); + + Dimension colorDimension() const; + Dimension symbolDimension() const; + void setColorDimension( Dimension dimension ); + void setSymbolDimension( Dimension dimension ); + + // The value of the dimension for the address, empty if the address has no value for the dimension + static std::string dimensionValue( Dimension dimension, const RifEclipseSummaryAddress& address, const std::string& caseName ); + + // The case name used by dimensionValue: ensemble name for ensemble members, otherwise the display name + static std::string caseName( const RimSummaryCase* summaryCase ); + + // Adds the color and symbol fields, used both here and when shown together with other items + void appendFieldsToUiOrdering( caf::PdmUiOrdering& uiOrdering ); + +protected: + void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; + void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override; + +private: + caf::PdmField m_colorDimension; + caf::PdmField m_symbolDimension; +}; diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSavedCurveAppearance.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimSavedCurveAppearance.cpp new file mode 100644 index 0000000000..c488e150ff --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSavedCurveAppearance.cpp @@ -0,0 +1,76 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RimSavedCurveAppearance.h" + +#include "RimEnsembleCurveSet.h" +#include "RimPlotCurve.h" +#include "RimPlotCurveAppearance.h" + +CAF_PDM_SOURCE_INIT( RimSavedCurveAppearance, "RimSavedCurveAppearance" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimSavedCurveAppearance::RimSavedCurveAppearance() +{ + CAF_PDM_InitObject( "Saved Curve Appearance" ); + + CAF_PDM_InitFieldNoDefault( &m_curve, "Curve", "Curve" ); + CAF_PDM_InitFieldNoDefault( &m_appearance, "CurveAppearance", "Curve Appearance" ); + CAF_PDM_InitFieldNoDefault( &m_ensembleCurveSet, "EnsembleCurveSet", "Ensemble Curve Set" ); + CAF_PDM_InitFieldNoDefault( &m_ensembleColor, "EnsembleColor", "Ensemble Color" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimSavedCurveAppearance::setCurve( RimPlotCurve* curve ) +{ + m_curve = curve; + m_appearance = curve->curveAppearance()->copyObject(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimSavedCurveAppearance::setEnsembleCurveSet( RimEnsembleCurveSet* curveSet ) +{ + m_ensembleCurveSet = curveSet; + m_ensembleColor = curveSet->color(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +caf::PdmObject* RimSavedCurveAppearance::restore() const +{ + if ( m_curve() && m_appearance() ) + { + m_curve()->curveAppearance()->copyFrom( *m_appearance() ); + return m_curve(); + } + + if ( m_ensembleCurveSet() ) + { + m_ensembleCurveSet()->setColorAndUpdateCurves( m_ensembleColor() ); + return m_ensembleCurveSet(); + } + + return nullptr; +} diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSavedCurveAppearance.h b/ApplicationLibCode/ProjectDataModel/Summary/RimSavedCurveAppearance.h new file mode 100644 index 0000000000..58f906bde7 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSavedCurveAppearance.h @@ -0,0 +1,53 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "cafPdmChildField.h" +#include "cafPdmField.h" +#include "cafPdmFieldCvfColor.h" +#include "cafPdmObject.h" +#include "cafPdmPtrField.h" + +class RimEnsembleCurveSet; +class RimPlotCurve; +class RimPlotCurveAppearance; + +//================================================================================================== +/// The appearance of a curve or ensemble curve set before the global curve appearance was enabled. Stored in the +/// project so it can be restored after a reload, and referenced by PDM pointers so deleted objects are not matched. +//================================================================================================== +class RimSavedCurveAppearance : public caf::PdmObject +{ + CAF_PDM_HEADER_INIT; + +public: + RimSavedCurveAppearance(); + + void setCurve( RimPlotCurve* curve ); + void setEnsembleCurveSet( RimEnsembleCurveSet* curveSet ); + + // Restores the saved appearance and returns the curve or curve set, nullptr if it no longer exists + caf::PdmObject* restore() const; + +private: + caf::PdmPtrField m_curve; + caf::PdmChildField m_appearance; + + caf::PdmPtrField m_ensembleCurveSet; + caf::PdmField m_ensembleColor; +}; diff --git a/ApplicationLibCode/UnitTests/CMakeLists.txt b/ApplicationLibCode/UnitTests/CMakeLists.txt index 865b82ae99..554a5469f7 100644 --- a/ApplicationLibCode/UnitTests/CMakeLists.txt +++ b/ApplicationLibCode/UnitTests/CMakeLists.txt @@ -147,6 +147,7 @@ set(SOURCE_UNITTEST_FILES ${CMAKE_CURRENT_LIST_DIR}/opm-summary-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifEclipseTextFileReader-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaSummaryStringTools-Test.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimCurveStyleSettings-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaVariableMapper-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifPressureDepthTextFileReader-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifRevealCsvSectionSummaryReader-Test.cpp diff --git a/ApplicationLibCode/UnitTests/RimCurveStyleSettings-Test.cpp b/ApplicationLibCode/UnitTests/RimCurveStyleSettings-Test.cpp new file mode 100644 index 0000000000..fda1e1d7e1 --- /dev/null +++ b/ApplicationLibCode/UnitTests/RimCurveStyleSettings-Test.cpp @@ -0,0 +1,32 @@ +#include "gtest/gtest.h" + +#include "RifEclipseSummaryAddress.h" +#include "RimCurveStyleSettings.h" + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RimCurveStyleSettingsTest, DefaultsControlColorByCase ) +{ + RimCurveStyleSettings settings; + + EXPECT_EQ( RimCurveStyleSettings::Dimension::CASE, settings.colorDimension() ); + EXPECT_EQ( RimCurveStyleSettings::Dimension::NONE, settings.symbolDimension() ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RimCurveStyleSettingsTest, DimensionValues ) +{ + using D = RimCurveStyleSettings::Dimension; + + const auto well = RifEclipseSummaryAddress::wellAddress( "WOPR", "A-1" ); + const auto region = RifEclipseSummaryAddress::regionAddress( "ROPR", 3 ); + + EXPECT_EQ( "A-1", RimCurveStyleSettings::dimensionValue( D::WELL, well, "case" ) ); + EXPECT_EQ( "case", RimCurveStyleSettings::dimensionValue( D::CASE, well, "case" ) ); + EXPECT_EQ( "WOPR", RimCurveStyleSettings::dimensionValue( D::VECTOR, well, "case" ) ); + EXPECT_TRUE( RimCurveStyleSettings::dimensionValue( D::REGION, well, "case" ).empty() ); + EXPECT_EQ( "3", RimCurveStyleSettings::dimensionValue( D::REGION, region, "case" ) ); +}