diff --git a/ApplicationExeCode/Resources/GridAndSummaryEnsemble.svg b/ApplicationExeCode/Resources/GridAndSummaryEnsemble.svg
new file mode 100644
index 0000000000..a515af80ea
--- /dev/null
+++ b/ApplicationExeCode/Resources/GridAndSummaryEnsemble.svg
@@ -0,0 +1,19 @@
+
+
diff --git a/ApplicationExeCode/Resources/ResInsight.qrc b/ApplicationExeCode/Resources/ResInsight.qrc
index aee7bb79d6..a639764a1c 100644
--- a/ApplicationExeCode/Resources/ResInsight.qrc
+++ b/ApplicationExeCode/Resources/ResInsight.qrc
@@ -142,6 +142,7 @@
SummaryCases16x16.png
SummaryCurve16x16.png
SummaryCurveFilter16x16.png
+ GridAndSummaryEnsemble.svg
SummaryEnsemble.svg
SummaryGroup16x16.png
SummaryPlotLight16x16.png
diff --git a/ApplicationLibCode/Commands/CMakeLists_files.cmake b/ApplicationLibCode/Commands/CMakeLists_files.cmake
index dad7149947..3c8f3d4c58 100644
--- a/ApplicationLibCode/Commands/CMakeLists_files.cmake
+++ b/ApplicationLibCode/Commands/CMakeLists_files.cmake
@@ -101,6 +101,8 @@ set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RicCreateContourMapPolygonTools.h
${CMAKE_CURRENT_LIST_DIR}/RicPolygonFromImageDialog.h
${CMAKE_CURRENT_LIST_DIR}/RicCreateSummaryEnsembleFeature.h
+ ${CMAKE_CURRENT_LIST_DIR}/RicImportGridAndSummaryEnsembleDialog.h
+ ${CMAKE_CURRENT_LIST_DIR}/RicImportGridAndSummaryEnsembleFeature.h
${CMAKE_CURRENT_LIST_DIR}/RicHistogramPlotTools.h
${CMAKE_CURRENT_LIST_DIR}/RicCreateGridStatisticsPlotFeature.h
)
@@ -208,6 +210,8 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RicCreateContourMapPolygonTools.cpp
${CMAKE_CURRENT_LIST_DIR}/RicPolygonFromImageDialog.cpp
${CMAKE_CURRENT_LIST_DIR}/RicCreateSummaryEnsembleFeature.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/RicImportGridAndSummaryEnsembleDialog.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/RicImportGridAndSummaryEnsembleFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicHistogramPlotTools.cpp
${CMAKE_CURRENT_LIST_DIR}/RicCreateGridStatisticsPlotFeature.cpp
)
diff --git a/ApplicationLibCode/Commands/RicImportEnsembleFeature.h b/ApplicationLibCode/Commands/RicImportEnsembleFeature.h
index c723df58a2..e969930fd2 100644
--- a/ApplicationLibCode/Commands/RicImportEnsembleFeature.h
+++ b/ApplicationLibCode/Commands/RicImportEnsembleFeature.h
@@ -26,6 +26,7 @@
#include
class RimSummaryCase;
+class RimSummaryEnsemble;
//==================================================================================================
///
@@ -41,6 +42,12 @@ class RicImportEnsembleFeature : public caf::CmdFeature
RiaDefines::EnsembleGroupingMode groupingMode,
bool isEnsemble = false );
+ static RimSummaryEnsemble* importSingleEnsembleFileSet( const QStringList& fileNames,
+ bool useEnsembleNameDialog,
+ RiaDefines::EnsembleGroupingMode groupingMode,
+ RiaDefines::FileType fileType,
+ const QString& defaultEnsembleName = QString() );
+
private:
void onActionTriggered( bool isChecked ) override;
void setupActionLook( QAction* actionToSetup ) override;
@@ -51,10 +58,4 @@ class RicImportEnsembleFeature : public caf::CmdFeature
RiaDefines::EnsembleGroupingMode groupingMode,
RiaDefines::FileType fileType,
const QString& defaultEnsembleName = QString() );
-
- static RimSummaryEnsemble* importSingleEnsembleFileSet( const QStringList& fileNames,
- bool useEnsembleNameDialog,
- RiaDefines::EnsembleGroupingMode groupingMode,
- RiaDefines::FileType fileType,
- const QString& defaultEnsembleName = QString() );
};
diff --git a/ApplicationLibCode/Commands/RicImportGridAndSummaryEnsembleDialog.cpp b/ApplicationLibCode/Commands/RicImportGridAndSummaryEnsembleDialog.cpp
new file mode 100644
index 0000000000..6b695ed5d2
--- /dev/null
+++ b/ApplicationLibCode/Commands/RicImportGridAndSummaryEnsembleDialog.cpp
@@ -0,0 +1,758 @@
+/////////////////////////////////////////////////////////////////////////////////
+//
+// 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 "RicImportGridAndSummaryEnsembleDialog.h"
+
+#include "RicRecursiveFileSearchDialog.h"
+
+#include "RiaApplication.h"
+#include "RiaEnsembleNameTools.h"
+#include "RiaFilePathTools.h"
+#include "RiaFileSearchTools.h"
+#include "RiaPreferences.h"
+#include "RiaStdStringTools.h"
+#include "RiaStringListSerializer.h"
+
+#include "RiuFileDialogTools.h"
+#include "RiuTools.h"
+
+#include "cafAppEnum.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+RicImportGridAndSummaryEnsembleDialogResult
+ RicImportGridAndSummaryEnsembleDialog::runDialog( QWidget* parent, bool defaultGridChecked, bool defaultSummaryChecked )
+{
+ const QString pathRegistryKey = "RicImportGridAndSummaryEnsembleDialog_path";
+
+ auto* app = RiaApplication::instance();
+
+ RicImportGridAndSummaryEnsembleDialog dialog( parent );
+ {
+ QSignalBlocker blocker1( dialog.m_pathFilterField );
+
+ dialog.setWindowTitle( "Import Grid and Summary Ensemble" );
+
+ // Default path (index 0 fallback)
+ QString defaultDir = app->lastUsedDialogDirectory( "GRID_SUMMARY_ENSEMBLE" );
+ RiaFilePathTools::appendSeparatorIfNo( defaultDir );
+ defaultDir += "*";
+ dialog.m_pathFilterField->addItem( QDir::toNativeSeparators( defaultDir ) );
+
+ // Registry history starts at index 1
+ RicRecursiveFileSearchDialog::populateComboBoxHistoryFromRegistry( dialog.m_pathFilterField, pathRegistryKey );
+
+ // Use most recently used path from registry (index 1) when preference is enabled
+ if ( RiaPreferences::current()->useRecentlyUsedFolderAsDefault() && dialog.m_pathFilterField->count() > 1 )
+ {
+ dialog.m_pathFilterField->setCurrentIndex( 1 );
+ }
+
+ dialog.m_pathFilterField->setEditable( true );
+
+ for ( const auto& s : caf::AppEnum::uiTexts() )
+ {
+ dialog.m_ensembleGroupingMode->addItem( s );
+ }
+
+ dialog.m_createGridEnsembleCheckBox->setChecked( defaultGridChecked );
+ dialog.m_createSummaryEnsembleCheckBox->setChecked( defaultSummaryChecked );
+
+ dialog.updateEffectiveFilter();
+ dialog.clearFileList();
+ dialog.setOkButtonEnabled( false );
+
+ dialog.resize( 800, 150 );
+ }
+
+ if ( dialog.exec() != QDialog::Accepted )
+ {
+ return {};
+ }
+
+ // Save registry history
+ const int maxItemsInRegistry = 10;
+ {
+ RiaStringListSerializer s( pathRegistryKey );
+ s.addString( dialog.m_pathFilterField->currentText(), maxItemsInRegistry );
+ }
+
+ // Save last used directory
+ QString rootDir = dialog.rootDirWithSeparator();
+ if ( !rootDir.isEmpty() )
+ {
+ QString dirToSave = rootDir;
+ if ( dirToSave.endsWith( RiaFilePathTools::separator() ) ) dirToSave.chop( 1 );
+ app->setLastUsedDialogDirectory( "GRID_SUMMARY_ENSEMBLE", dirToSave );
+ }
+
+ // Collect checked realization items
+ RicImportGridAndSummaryEnsembleDialogResult result;
+ result.ok = true;
+ result.rootDir = dialog.rootDirWithSeparator();
+ result.pathFilter = dialog.pathFilterWithoutRoot();
+ result.groupingMode = dialog.ensembleGroupingMode();
+ result.createGridEnsemble = dialog.m_createGridEnsembleCheckBox->isChecked();
+ result.createSummaryEnsemble = dialog.m_createSummaryEnsembleCheckBox->isChecked();
+
+ auto* rootItem = dialog.m_filePathModel.invisibleRootItem();
+ for ( int i = 0; i < rootItem->rowCount(); ++i )
+ {
+ auto ensembleItem = rootItem->child( i );
+ if ( !ensembleItem ) continue;
+
+ for ( int j = 0; j < ensembleItem->rowCount(); ++j )
+ {
+ auto childItem = ensembleItem->child( j );
+ if ( !childItem || !childItem->isCheckable() ) continue;
+ if ( childItem->checkState() != Qt::Checked ) continue;
+
+ QString basePath = childItem->data( Qt::UserRole ).toString();
+ if ( basePath.isEmpty() ) continue;
+
+ auto it = dialog.m_foundRealizations.find( basePath );
+ if ( it == dialog.m_foundRealizations.end() ) continue;
+
+ if ( !it->gridFile.isEmpty() ) result.gridFiles.append( it->gridFile );
+ if ( !it->summaryFile.isEmpty() ) result.summaryFiles.append( it->summaryFile );
+ }
+ }
+
+ return result;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+RicImportGridAndSummaryEnsembleDialog::RicImportGridAndSummaryEnsembleDialog( QWidget* parent )
+ : QDialog( parent, RiuTools::defaultDialogFlags() )
+ , m_blockItemUpdates( false )
+{
+ // Create widgets
+ m_pathFilterField = new QComboBox();
+ m_browseButton = new QPushButton( "..." );
+ m_effectiveFilterLabel = new QLabel();
+ m_effectiveFilterLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
+ m_searchButton = new QPushButton( "Search" );
+ m_searchButton->setFixedWidth( 75 );
+
+ m_useRealizationStarCheckBox = new QCheckBox( "Use 'realization-*' in filter" );
+ m_ensembleGroupingMode = new QComboBox();
+
+ m_createGridEnsembleCheckBox = new QCheckBox( "Create Grid Ensemble" );
+ m_createSummaryEnsembleCheckBox = new QCheckBox( "Create Summary Ensemble" );
+ m_createGridEnsembleCheckBox->setChecked( true );
+ m_createSummaryEnsembleCheckBox->setChecked( true );
+
+ m_outputGroup = new QGroupBox( "Files Found" );
+ m_treeFilterLineEdit = new QLineEdit();
+ m_treeFilterLineEdit->setPlaceholderText( "Select Realizations: 1, 5-7, !4, 9-18:3" );
+ m_treeFilterButton = new QPushButton( "Apply" );
+ m_fileTreeView = new QTreeView();
+
+ m_buttons = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
+
+ // Tree view setup
+ m_fileTreeView->setModel( &m_filePathModel );
+ m_fileTreeView->setHeaderHidden( true );
+ m_fileTreeView->setSelectionMode( QAbstractItemView::ExtendedSelection );
+ m_fileTreeView->setContextMenuPolicy( Qt::CustomContextMenu );
+ m_fileTreeView->setVisible( false );
+ m_fileTreeView->setMinimumHeight( 350 );
+
+ m_browseButton->setFixedWidth( 25 );
+
+ // Initially hide filter widgets
+ m_treeFilterLineEdit->setVisible( false );
+ m_treeFilterButton->setVisible( false );
+
+ // Connect signals
+ connect( m_pathFilterField, SIGNAL( currentTextChanged( const QString& ) ), this, SLOT( slotPathFilterChanged( const QString& ) ) );
+ connect( m_pathFilterField, SIGNAL( editTextChanged( const QString& ) ), this, SLOT( slotPathFilterChanged( const QString& ) ) );
+
+ connect( m_browseButton, SIGNAL( clicked() ), this, SLOT( slotBrowseClicked() ) );
+ connect( m_useRealizationStarCheckBox, SIGNAL( clicked() ), this, SLOT( slotUseRealizationStarClicked() ) );
+ connect( m_searchButton, SIGNAL( clicked() ), this, SLOT( slotSearchClicked() ) );
+ connect( m_treeFilterButton, SIGNAL( clicked() ), this, SLOT( slotFilterTreeViewClicked() ) );
+ connect( m_treeFilterLineEdit, &QLineEdit::returnPressed, m_treeFilterButton, &QPushButton::click );
+ connect( m_treeFilterLineEdit, &QLineEdit::textEdited, m_treeFilterButton, &QPushButton::click );
+
+ connect( m_buttons, SIGNAL( accepted() ), this, SLOT( slotOkClicked() ) );
+ connect( m_buttons, SIGNAL( rejected() ), this, SLOT( slotCancelClicked() ) );
+
+ m_buttons->button( QDialogButtonBox::Ok )->setDefault( true );
+
+ // itemChanged: propagate check state to children and upward
+ QObject::connect( &m_filePathModel,
+ &QStandardItemModel::itemChanged,
+ [this]( QStandardItem* item )
+ {
+ if ( m_blockItemUpdates ) return;
+
+ if ( item->isCheckable() )
+ {
+ RicRecursiveFileSearchDialog::setCheckedStateChildItems( item, item->checkState() );
+ }
+
+ if ( item->checkState() == Qt::Checked )
+ {
+ auto parentItem = item->parent();
+ if ( parentItem )
+ {
+ m_blockItemUpdates = true;
+ parentItem->setCheckState( Qt::Checked );
+ m_blockItemUpdates = false;
+ }
+ }
+ } );
+
+ // Build layout
+ QVBoxLayout* dialogLayout = new QVBoxLayout();
+
+ // Search group
+ QGroupBox* searchGroup = new QGroupBox( "Search" );
+ QGridLayout* searchGridLayout = new QGridLayout();
+ int row = 0;
+
+ searchGridLayout->addWidget( new QLabel( "Path pattern" ), row, 0 );
+ searchGridLayout->addWidget( m_pathFilterField, row, 1, 1, 2 );
+ searchGridLayout->addWidget( m_browseButton, row, 3 );
+
+ row++;
+ {
+ QHBoxLayout* hLayout = new QHBoxLayout();
+ hLayout->addWidget( m_useRealizationStarCheckBox );
+ hLayout->addWidget( new QLabel( "Ensemble Grouping" ) );
+ hLayout->addWidget( m_ensembleGroupingMode );
+ hLayout->addStretch( 1 );
+ searchGridLayout->addLayout( hLayout, row, 1 );
+ }
+
+ row++;
+ searchGridLayout->addWidget( new QLabel( "Effective filter" ), row, 0 );
+ searchGridLayout->addWidget( m_effectiveFilterLabel, row, 1, 1, 2 );
+ searchGridLayout->addWidget( m_searchButton, row, 3 );
+
+ searchGroup->setLayout( searchGridLayout );
+
+ // Import group
+ QGroupBox* importGroup = new QGroupBox( "Import" );
+ QHBoxLayout* importLayout = new QHBoxLayout();
+ importLayout->addWidget( m_createGridEnsembleCheckBox );
+ importLayout->addWidget( m_createSummaryEnsembleCheckBox );
+ importLayout->addStretch( 1 );
+ importGroup->setLayout( importLayout );
+
+ // Files Found group
+ QGridLayout* outputGridLayout = new QGridLayout();
+ outputGridLayout->addWidget( m_treeFilterLineEdit, 0, 0 );
+ outputGridLayout->addWidget( m_treeFilterButton, 0, 1 );
+ outputGridLayout->addWidget( m_fileTreeView, 1, 0, 1, 2 );
+ m_outputGroup->setLayout( outputGridLayout );
+
+ dialogLayout->addWidget( searchGroup );
+ dialogLayout->addWidget( importGroup );
+ dialogLayout->addWidget( m_outputGroup );
+ dialogLayout->addWidget( m_buttons );
+
+ setLayout( dialogLayout );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QString RicImportGridAndSummaryEnsembleDialog::cleanPathFilter() const
+{
+ QString pathFilterText = m_pathFilterField->currentText().trimmed();
+ pathFilterText = RiaFilePathTools::toInternalSeparator( pathFilterText );
+ pathFilterText = RiaFilePathTools::removeDuplicatePathSeparators( pathFilterText );
+ pathFilterText.replace( QString( "**" ), QString( "*" ) );
+
+ if ( m_useRealizationStarCheckBox->isChecked() )
+ {
+ const QString pattern = "realization-\\d+";
+ QRegularExpression regexp( pattern, QRegularExpression::CaseInsensitiveOption );
+ pathFilterText.replace( regexp, "realization-*" );
+ }
+
+ return pathFilterText;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QString RicImportGridAndSummaryEnsembleDialog::rootDirWithSeparator() const
+{
+ QString rootDir = RiaFilePathTools::rootSearchPathFromSearchFilter( cleanPathFilter() );
+ return RiaFilePathTools::appendSeparatorIfNo( rootDir );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QString RicImportGridAndSummaryEnsembleDialog::pathFilterWithoutRoot() const
+{
+ QString pathFilter = cleanPathFilter();
+ QString rootDir = RiaFilePathTools::rootSearchPathFromSearchFilter( pathFilter );
+
+ pathFilter.remove( 0, rootDir.size() );
+ if ( pathFilter.startsWith( RiaFilePathTools::separator() ) ) pathFilter.remove( 0, 1 );
+ return pathFilter;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RicImportGridAndSummaryEnsembleDialog::updateEffectiveFilter()
+{
+ QString pathFilter = pathFilterWithoutRoot();
+ if ( pathFilter == "*" || pathFilter.endsWith( QString( RiaFilePathTools::separator() ) + "*" ) )
+ {
+ pathFilter.chop( 1 );
+ pathFilter = pathFilter + "...";
+ }
+
+ QString effFilter = QString( "%1%2/*.EGRID|SMSPEC|ESMRY" ).arg( rootDirWithSeparator() ).arg( pathFilter );
+ effFilter = RiaFilePathTools::removeDuplicatePathSeparators( effFilter );
+
+ m_effectiveFilterLabel->setText( QDir::toNativeSeparators( effFilter ) );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RicImportGridAndSummaryEnsembleDialog::setOkButtonEnabled( bool enabled )
+{
+ m_buttons->button( QDialogButtonBox::Ok )->setEnabled( enabled );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QStringList RicImportGridAndSummaryEnsembleDialog::findMatchingFiles( const QStringList& extensions )
+{
+ if ( cleanPathFilter().isEmpty() ) return {};
+
+ QString rootDir = rootDirWithSeparator();
+ QString pathFilter = pathFilterWithoutRoot();
+ if ( rootDir.size() > 1 && rootDir.endsWith( RiaFilePathTools::separator() ) ) rootDir.chop( 1 );
+
+ QStringList matchingFolders;
+ RiaFileSearchTools::findMatchingFoldersRecursively( rootDir, pathFilter, matchingFolders );
+
+ QStringList nameFilters;
+ for ( const auto& ext : extensions )
+ {
+ nameFilters.append( "*." + ext );
+ }
+
+ return RiaFileSearchTools::findFilesInFolders( matchingFolders, nameFilters );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RicImportGridAndSummaryEnsembleDialog::updateFileListWidget()
+{
+ m_filePathModel.clear();
+ m_foundRealizations.clear();
+
+ QStringList gridFiles = findMatchingFiles( { "EGRID" } );
+ QStringList summaryFiles = findMatchingFiles( { "SMSPEC", "ESMRY" } );
+
+ // Sort so ESMRY comes before SMSPEC (alphabetically E < S)
+ std::sort( summaryFiles.begin(), summaryFiles.end() );
+
+ QString rootDir = rootDirWithSeparator();
+ int rootLen = rootDir.size();
+
+ // Build m_foundRealizations map using relative base path (no extension) as key
+ for ( const auto& filePath : gridFiles )
+ {
+ QString rel = filePath.mid( rootLen );
+ QFileInfo fi( rel );
+ QString basePath = QDir::cleanPath( fi.path() + "/" + fi.completeBaseName() );
+ if ( !m_foundRealizations.contains( basePath ) )
+ {
+ m_foundRealizations[basePath].gridFile = filePath;
+ }
+ else if ( m_foundRealizations[basePath].gridFile.isEmpty() )
+ {
+ m_foundRealizations[basePath].gridFile = filePath;
+ }
+ }
+
+ for ( const auto& filePath : summaryFiles )
+ {
+ QString rel = filePath.mid( rootLen );
+ QFileInfo fi( rel );
+ QString basePath = QDir::cleanPath( fi.path() + "/" + fi.completeBaseName() );
+ if ( !m_foundRealizations.contains( basePath ) )
+ {
+ m_foundRealizations[basePath].summaryFile = filePath;
+ }
+ else if ( m_foundRealizations[basePath].summaryFile.isEmpty() )
+ {
+ // Only store first match per base (ESMRY wins since it sorts before SMSPEC alphabetically)
+ m_foundRealizations[basePath].summaryFile = filePath;
+ }
+ }
+
+ if ( m_foundRealizations.isEmpty() ) return;
+
+ auto mode = ensembleGroupingMode();
+
+ if ( mode == RiaDefines::EnsembleGroupingMode::NONE )
+ {
+ // Single "Files" ensemble item, one child per base path
+ auto rootItem = m_filePathModel.invisibleRootItem();
+ auto ensembleItem = new QStandardItem( "Files" );
+ ensembleItem->setCheckable( true );
+ ensembleItem->setCheckState( Qt::Checked );
+ rootItem->appendRow( ensembleItem );
+
+ for ( auto it = m_foundRealizations.begin(); it != m_foundRealizations.end(); ++it )
+ {
+ auto childItem = new QStandardItem( QDir::toNativeSeparators( it.key() ) );
+ childItem->setCheckable( true );
+ childItem->setCheckState( Qt::Checked );
+ childItem->setData( it.key(), Qt::UserRole );
+ ensembleItem->appendRow( childItem );
+ }
+ }
+ else
+ {
+ // Build list of representative files for grouping (grid if available, else summary)
+ QStringList representativeFiles;
+ QMap repFileToBasePath;
+ for ( auto it = m_foundRealizations.begin(); it != m_foundRealizations.end(); ++it )
+ {
+ QString rep = it.value().gridFile.isEmpty() ? it.value().summaryFile : it.value().gridFile;
+ if ( !rep.isEmpty() )
+ {
+ representativeFiles.append( rep );
+ repFileToBasePath[rep] = it.key();
+ }
+ }
+
+ // Sort numerically
+ QCollator collator;
+ collator.setNumericMode( true );
+ std::sort( representativeFiles.begin(), representativeFiles.end(), collator );
+
+ auto grouping = RiaEnsembleNameTools::groupFilesByEnsembleName( representativeFiles, mode );
+
+ auto rootItem = m_filePathModel.invisibleRootItem();
+ for ( const auto& [groupName, groupFiles] : grouping )
+ {
+ auto ensembleItem = new QStandardItem( QDir::toNativeSeparators( groupName ) );
+ ensembleItem->setCheckable( true );
+ ensembleItem->setCheckState( Qt::Checked );
+ rootItem->appendRow( ensembleItem );
+
+ for ( const auto& repFile : groupFiles )
+ {
+ QString basePath = repFileToBasePath.value( repFile );
+ if ( basePath.isEmpty() ) continue;
+
+ auto childItem = new QStandardItem( QDir::toNativeSeparators( basePath ) );
+ childItem->setCheckable( true );
+ childItem->setCheckState( Qt::Checked );
+ childItem->setData( basePath, Qt::UserRole );
+ ensembleItem->appendRow( childItem );
+ }
+ }
+ }
+
+ // Expand first item
+ if ( m_filePathModel.rowCount() > 0 )
+ {
+ QModelIndex index = m_filePathModel.index( 0, 0 );
+ m_fileTreeView->expand( index );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RicImportGridAndSummaryEnsembleDialog::clearFileList()
+{
+ m_foundRealizations.clear();
+ m_filePathModel.clear();
+ m_outputGroup->setTitle( "Files Found" );
+ setOkButtonEnabled( false );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+RiaDefines::EnsembleGroupingMode RicImportGridAndSummaryEnsembleDialog::ensembleGroupingMode() const
+{
+ if ( m_ensembleGroupingMode->currentIndex() == 0 ) return RiaDefines::EnsembleGroupingMode::FMU_FOLDER_STRUCTURE;
+ if ( m_ensembleGroupingMode->currentIndex() == 1 ) return RiaDefines::EnsembleGroupingMode::EVEREST_FOLDER_STRUCTURE;
+ if ( m_ensembleGroupingMode->currentIndex() == 2 ) return RiaDefines::EnsembleGroupingMode::NONE;
+ if ( m_ensembleGroupingMode->currentIndex() == 3 ) return RiaDefines::EnsembleGroupingMode::RESINSIGHT_OPMFLOW_STRUCTURE;
+ return RiaDefines::EnsembleGroupingMode::FMU_FOLDER_STRUCTURE;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RicImportGridAndSummaryEnsembleDialog::slotPathFilterChanged( const QString& /*text*/ )
+{
+ updateEffectiveFilter();
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RicImportGridAndSummaryEnsembleDialog::slotBrowseClicked()
+{
+ QString folder = RiuFileDialogTools::getExistingDirectory( this, "Select folder", rootDirWithSeparator() );
+ if ( folder.isEmpty() ) return;
+ RiaFilePathTools::appendSeparatorIfNo( folder );
+ folder += "*";
+ m_pathFilterField->addItem( QDir::toNativeSeparators( folder ) );
+ m_pathFilterField->setCurrentText( QDir::toNativeSeparators( folder ) );
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RicImportGridAndSummaryEnsembleDialog::slotUseRealizationStarClicked()
+{
+ updateEffectiveFilter();
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+//--------------------------------------------------------------------------------------------------
+/// Strip trailing realization number and separator from a filename stem, e.g.
+/// "ECLIPSE_001" -> "ECLIPSE", "MODEL-3" -> "MODEL", "BASE" -> "BASE"
+//--------------------------------------------------------------------------------------------------
+static QString stripTrailingRealizationNumber( const QString& name )
+{
+ static const QRegularExpression re( "[_\\-]?\\d+$" );
+ QString stripped = name;
+ stripped.remove( re );
+ return stripped;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QStringList RicImportGridAndSummaryEnsembleDialog::checkForMultipleFilenamesAndUncheckOutliers()
+{
+ QStringList warnings;
+
+ auto* rootItem = m_filePathModel.invisibleRootItem();
+ for ( int i = 0; i < rootItem->rowCount(); ++i )
+ {
+ auto ensembleItem = rootItem->child( i );
+ if ( !ensembleItem ) continue;
+
+ // Count occurrences of each canonical filename (trailing realization number stripped)
+ QMap canonicalCounts;
+ for ( int j = 0; j < ensembleItem->rowCount(); ++j )
+ {
+ auto childItem = ensembleItem->child( j );
+ if ( !childItem ) continue;
+
+ QString basePath = childItem->data( Qt::UserRole ).toString();
+ if ( basePath.isEmpty() ) continue;
+
+ QString canonical = stripTrailingRealizationNumber( QFileInfo( basePath ).fileName() );
+ canonicalCounts[canonical]++;
+ }
+
+ if ( canonicalCounts.size() <= 1 ) continue;
+
+ // Find the most common canonical filename
+ QString mostCommon;
+ int maxCount = 0;
+ for ( auto it = canonicalCounts.constBegin(); it != canonicalCounts.constEnd(); ++it )
+ {
+ if ( it.value() > maxCount )
+ {
+ maxCount = it.value();
+ mostCommon = it.key();
+ }
+ }
+
+ // Untick outliers
+ QStringList outlierNames;
+ m_blockItemUpdates = true;
+ for ( int j = 0; j < ensembleItem->rowCount(); ++j )
+ {
+ auto childItem = ensembleItem->child( j );
+ if ( !childItem ) continue;
+
+ QString basePath = childItem->data( Qt::UserRole ).toString();
+ QString canonical = stripTrailingRealizationNumber( QFileInfo( basePath ).fileName() );
+ if ( canonical != mostCommon )
+ {
+ childItem->setCheckState( Qt::Unchecked );
+ if ( !outlierNames.contains( canonical ) ) outlierNames.append( canonical );
+ }
+ }
+ m_blockItemUpdates = false;
+
+ QStringList allNames = canonicalCounts.keys();
+ warnings.append( QString( "Ensemble '%1' contains multiple filenames: '%2'.\n"
+ "Only '%3' (%4 realizations) is selected. Deselected: '%5'." )
+ .arg( ensembleItem->text() )
+ .arg( allNames.join( ", " ) )
+ .arg( mostCommon )
+ .arg( maxCount )
+ .arg( outlierNames.join( ", " ) ) );
+ }
+
+ return warnings;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RicImportGridAndSummaryEnsembleDialog::slotSearchClicked()
+{
+ clearFileList();
+
+ m_treeFilterLineEdit->setVisible( true );
+ m_treeFilterButton->setVisible( true );
+
+ if ( !m_fileTreeView->isVisible() )
+ {
+ m_fileTreeView->setVisible( true );
+ if ( height() < 550 ) resize( width(), 550 );
+ }
+
+ updateFileListWidget();
+
+ int realizationCount = m_foundRealizations.size();
+ if ( realizationCount > 0 )
+ {
+ m_outputGroup->setTitle( QString( "Files Found (%1)" ).arg( realizationCount ) );
+ setOkButtonEnabled( true );
+ m_buttons->button( QDialogButtonBox::Ok )->setFocus();
+ }
+ else
+ {
+ m_outputGroup->setTitle( "Files Found" );
+
+ // Add "No files found" status item
+ auto rootItem = m_filePathModel.invisibleRootItem();
+ auto statusItem = new QStandardItem( "No files found" );
+ rootItem->appendRow( statusItem );
+ }
+
+ QStringList warnings = checkForMultipleFilenamesAndUncheckOutliers();
+ if ( !warnings.isEmpty() )
+ {
+ QMessageBox msgBox( QMessageBox::Warning, "Multiple Filenames Found", warnings.join( "\n\n" ), QMessageBox::Ok, this );
+ // Force the dialog wide enough so LF-separated lines are not word-wrapped
+ auto* layout = qobject_cast( msgBox.layout() );
+ if ( layout )
+ {
+ auto* spacer = new QSpacerItem( 600, 0, QSizePolicy::Minimum, QSizePolicy::Expanding );
+ layout->addItem( spacer, layout->rowCount(), 0, 1, layout->columnCount() );
+ }
+ msgBox.exec();
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RicImportGridAndSummaryEnsembleDialog::slotFilterTreeViewClicked()
+{
+ QString filterText = m_treeFilterLineEdit->text();
+ auto values = RiaStdStringTools::valuesFromRangeSelection( filterText.toStdString() );
+
+ auto items = RicRecursiveFileSearchDialog::firstLevelItems( m_filePathModel.invisibleRootItem() );
+ for ( auto item : items )
+ {
+ if ( item->checkState() == Qt::Unchecked ) continue;
+
+ if ( filterText.isEmpty() )
+ {
+ RicRecursiveFileSearchDialog::setCheckedStateChildItems( item, Qt::Checked );
+ }
+ else
+ {
+ RicRecursiveFileSearchDialog::setCheckedStateChildItems( item, Qt::Unchecked );
+
+ for ( auto val : values )
+ {
+ QString searchString = "realization-" + QString::number( val );
+
+ QList matchingItems;
+ RicRecursiveFileSearchDialog::findItemsMatching( item, searchString, matchingItems );
+ for ( auto matchedItem : matchingItems )
+ {
+ matchedItem->setCheckState( Qt::Checked );
+ }
+ }
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RicImportGridAndSummaryEnsembleDialog::slotOkClicked()
+{
+ accept();
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RicImportGridAndSummaryEnsembleDialog::slotCancelClicked()
+{
+ reject();
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RicImportGridAndSummaryEnsembleDialog::showEvent( QShowEvent* event )
+{
+ m_searchButton->setFocus();
+ QDialog::showEvent( event );
+}
diff --git a/ApplicationLibCode/Commands/RicImportGridAndSummaryEnsembleDialog.h b/ApplicationLibCode/Commands/RicImportGridAndSummaryEnsembleDialog.h
new file mode 100644
index 0000000000..19653c2125
--- /dev/null
+++ b/ApplicationLibCode/Commands/RicImportGridAndSummaryEnsembleDialog.h
@@ -0,0 +1,116 @@
+/////////////////////////////////////////////////////////////////////////////////
+//
+// 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 "Summary/RiaSummaryDefines.h"
+
+#include
+#include
+#include
+#include
+#include
+
+class QCheckBox;
+class QComboBox;
+class QDialogButtonBox;
+class QGroupBox;
+class QLabel;
+class QLineEdit;
+class QPushButton;
+class QTreeView;
+
+struct RicImportGridAndSummaryEnsembleDialogResult
+{
+ bool ok;
+ QStringList gridFiles;
+ QStringList summaryFiles;
+ QString rootDir;
+ QString pathFilter;
+ RiaDefines::EnsembleGroupingMode groupingMode;
+ bool createGridEnsemble;
+ bool createSummaryEnsemble;
+};
+
+//==================================================================================================
+///
+//==================================================================================================
+class RicImportGridAndSummaryEnsembleDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ static RicImportGridAndSummaryEnsembleDialogResult runDialog( QWidget* parent, bool defaultGridChecked, bool defaultSummaryChecked );
+
+private:
+ explicit RicImportGridAndSummaryEnsembleDialog( QWidget* parent );
+
+ QString cleanPathFilter() const;
+ QString rootDirWithSeparator() const;
+ QString pathFilterWithoutRoot() const;
+ void updateEffectiveFilter();
+ void setOkButtonEnabled( bool enabled );
+
+ QStringList findMatchingFiles( const QStringList& extensions );
+
+ void updateFileListWidget();
+ void clearFileList();
+ QStringList checkForMultipleFilenamesAndUncheckOutliers();
+
+ RiaDefines::EnsembleGroupingMode ensembleGroupingMode() const;
+
+private slots:
+ void slotPathFilterChanged( const QString& text );
+ void slotBrowseClicked();
+ void slotUseRealizationStarClicked();
+ void slotSearchClicked();
+ void slotFilterTreeViewClicked();
+ void slotOkClicked();
+ void slotCancelClicked();
+ void showEvent( QShowEvent* event ) override;
+
+private:
+ struct RealizationFiles
+ {
+ QString gridFile;
+ QString summaryFile;
+ };
+
+ QComboBox* m_pathFilterField;
+ QPushButton* m_browseButton;
+ QCheckBox* m_useRealizationStarCheckBox;
+ QComboBox* m_ensembleGroupingMode;
+ QLabel* m_effectiveFilterLabel;
+ QPushButton* m_searchButton;
+
+ QCheckBox* m_createGridEnsembleCheckBox;
+ QCheckBox* m_createSummaryEnsembleCheckBox;
+
+ QGroupBox* m_outputGroup;
+ QLineEdit* m_treeFilterLineEdit;
+ QPushButton* m_treeFilterButton;
+ QTreeView* m_fileTreeView;
+
+ QDialogButtonBox* m_buttons;
+
+ QStandardItemModel m_filePathModel;
+
+ QMap m_foundRealizations;
+
+ bool m_blockItemUpdates;
+};
diff --git a/ApplicationLibCode/Commands/RicImportGridAndSummaryEnsembleFeature.cpp b/ApplicationLibCode/Commands/RicImportGridAndSummaryEnsembleFeature.cpp
new file mode 100644
index 0000000000..411af90a5f
--- /dev/null
+++ b/ApplicationLibCode/Commands/RicImportGridAndSummaryEnsembleFeature.cpp
@@ -0,0 +1,126 @@
+/////////////////////////////////////////////////////////////////////////////////
+//
+// 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 "RicImportGridAndSummaryEnsembleFeature.h"
+
+#include "RiaGuiApplication.h"
+#include "Summary/RiaSummaryPlotTools.h"
+
+#include "RicImportGridAndSummaryEnsembleDialog.h"
+#include "RicNewViewFeature.h"
+
+#include "EnsembleFileSet/RimEnsembleFileSetTools.h"
+#include "Rim3dView.h"
+#include "RimReservoirGridEnsemble.h"
+#include "RimSummaryEnsemble.h"
+#include "RimViewNameConfig.h"
+
+#include "RiuPlotMainWindow.h"
+#include "RiuPlotMainWindowTools.h"
+
+#include
+#include
+#include
+#include
+
+CAF_CMD_SOURCE_INIT( RicImportGridAndSummaryEnsembleFeature, "RicImportGridAndSummaryEnsembleFeature" );
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RicImportGridAndSummaryEnsembleFeature::onActionTriggered( bool isChecked )
+{
+ QPointer activeWindowBeforeImport = RiaGuiApplication::widgetToUseAsParent();
+
+ bool startedFromPlotWindow = ( RiaGuiApplication::activeMainWindow() == RiaGuiApplication::instance()->mainPlotWindow() );
+
+ auto result = RicImportGridAndSummaryEnsembleDialog::runDialog( activeWindowBeforeImport, !startedFromPlotWindow, startedFromPlotWindow );
+
+ if ( !result.ok ) return;
+ if ( !result.createGridEnsemble && !result.createSummaryEnsemble ) return;
+
+ // Build the union of base paths (extension-stripped) from both lists so every realization
+ // appears exactly once. findPathPattern requires each realization number to be unique across
+ // the input; duplicating paths with different extensions breaks the pattern detection.
+ auto stripExtension = []( const QString& path ) -> QString
+ {
+ int dot = path.lastIndexOf( '.' );
+ return dot != -1 ? path.left( dot ) : path;
+ };
+
+ QStringList strippedPaths;
+
+ for ( const auto& f : result.gridFiles )
+ strippedPaths << stripExtension( f );
+ for ( const auto& f : result.summaryFiles )
+ strippedPaths << stripExtension( f );
+
+ strippedPaths.removeDuplicates();
+
+ if ( strippedPaths.isEmpty() ) return;
+
+ auto fileSets = RimEnsembleFileSetTools::createEnsembleFileSets( strippedPaths, result.groupingMode );
+ if ( fileSets.empty() ) return;
+
+ bool gridEnsemblesCreated = false;
+ if ( result.createGridEnsemble && !result.gridFiles.isEmpty() )
+ {
+ auto gridEnsembles = RimEnsembleFileSetTools::createGridEnsemblesFromFileSets( fileSets );
+ if ( !gridEnsembles.empty() )
+ {
+ gridEnsemblesCreated = true;
+ auto firstEnsemble = gridEnsembles.front();
+ auto cases = firstEnsemble->cases();
+ if ( !cases.empty() )
+ {
+ auto view = RicNewViewFeature::addReservoirView( cases.front(), nullptr, firstEnsemble->viewCollection() );
+ if ( view ) view->nameConfig()->setAddCaseName( true );
+ }
+ }
+ }
+
+ if ( result.createSummaryEnsemble && !result.summaryFiles.isEmpty() )
+ {
+ auto summaryEnsembles = RimEnsembleFileSetTools::createSummaryEnsemblesFromFileSets( fileSets );
+ for ( auto ensemble : summaryEnsembles )
+ {
+ RiaSummaryPlotTools::createAndAppendDefaultSummaryMultiPlot( {}, { ensemble } );
+ }
+ if ( !summaryEnsembles.empty() ) RiuPlotMainWindowTools::showPlotMainWindow();
+ }
+
+ if ( gridEnsemblesCreated && activeWindowBeforeImport )
+ {
+ QTimer::singleShot( 500,
+ activeWindowBeforeImport,
+ [activeWindowBeforeImport]()
+ {
+ activeWindowBeforeImport->raise();
+ activeWindowBeforeImport->activateWindow();
+ } );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RicImportGridAndSummaryEnsembleFeature::setupActionLook( QAction* actionToSetup )
+{
+ actionToSetup->setIcon( QIcon( ":/GridAndSummaryEnsemble.svg" ) );
+ actionToSetup->setText( "Import Grid and Summary Ensemble" );
+}
diff --git a/ApplicationLibCode/Commands/RicImportGridAndSummaryEnsembleFeature.h b/ApplicationLibCode/Commands/RicImportGridAndSummaryEnsembleFeature.h
new file mode 100644
index 0000000000..e832de180f
--- /dev/null
+++ b/ApplicationLibCode/Commands/RicImportGridAndSummaryEnsembleFeature.h
@@ -0,0 +1,33 @@
+/////////////////////////////////////////////////////////////////////////////////
+//
+// 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 "cafCmdFeature.h"
+
+//==================================================================================================
+///
+//==================================================================================================
+class RicImportGridAndSummaryEnsembleFeature : public caf::CmdFeature
+{
+ CAF_CMD_HEADER_INIT;
+
+protected:
+ void onActionTriggered( bool isChecked ) override;
+ void setupActionLook( QAction* actionToSetup ) override;
+};
diff --git a/ApplicationLibCode/Commands/RicRecursiveFileSearchDialog.cpp b/ApplicationLibCode/Commands/RicRecursiveFileSearchDialog.cpp
index f44a7682dd..3bf7a4a0fb 100644
--- a/ApplicationLibCode/Commands/RicRecursiveFileSearchDialog.cpp
+++ b/ApplicationLibCode/Commands/RicRecursiveFileSearchDialog.cpp
@@ -59,69 +59,6 @@
namespace
{
-// Get all first level items, usually the ensemble items
-auto firstLevelItems = []( QStandardItem* rootItem ) -> QList
-{
- QList firstLevelItems;
-
- for ( int i = 0; i < rootItem->rowCount(); ++i )
- {
- QStandardItem* item = rootItem->child( i );
- if ( item )
- {
- firstLevelItems.append( item );
- }
- }
-
- return firstLevelItems;
-};
-
-//--------------------------------------------------------------------------------------------------
-///
-//--------------------------------------------------------------------------------------------------
-void setCheckedStateChildItems( QStandardItem* parentItem, Qt::CheckState checkState )
-{
- if ( !parentItem ) return;
-
- for ( int i = 0; i < parentItem->rowCount(); ++i )
- {
- auto childItem = parentItem->child( i );
- if ( childItem && childItem->isCheckable() )
- {
- childItem->setCheckState( checkState );
- }
-
- setCheckedStateChildItems( childItem, checkState );
- }
-}
-
-//--------------------------------------------------------------------------------------------------
-///
-//--------------------------------------------------------------------------------------------------
-void findItemsMatching( QStandardItem* parentItem, const QString& substring, QList& matchingItems )
-{
- if ( !parentItem ) return;
-
- for ( int i = 0; i < parentItem->rowCount(); ++i )
- {
- auto searchString = substring + "/";
-
- auto childItem = parentItem->child( i );
- if ( childItem )
- {
- auto textToMatch = childItem->text();
- textToMatch.replace( '\\', '/' );
-
- if ( childItem && textToMatch.contains( searchString, Qt::CaseInsensitive ) )
- {
- matchingItems.append( childItem );
- }
- }
-
- findItemsMatching( childItem, substring, matchingItems );
- }
-}
-
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -799,6 +736,60 @@ QString RicRecursiveFileSearchDialog::replaceWithRealizationStar( const QString&
return textWithStar;
}
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+QList RicRecursiveFileSearchDialog::firstLevelItems( QStandardItem* rootItem )
+{
+ QList items;
+ for ( int i = 0; i < rootItem->rowCount(); ++i )
+ {
+ QStandardItem* item = rootItem->child( i );
+ if ( item ) items.append( item );
+ }
+ return items;
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RicRecursiveFileSearchDialog::setCheckedStateChildItems( QStandardItem* parentItem, Qt::CheckState checkState )
+{
+ if ( !parentItem ) return;
+ for ( int i = 0; i < parentItem->rowCount(); ++i )
+ {
+ auto childItem = parentItem->child( i );
+ if ( childItem && childItem->isCheckable() )
+ {
+ childItem->setCheckState( checkState );
+ }
+ setCheckedStateChildItems( childItem, checkState );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+void RicRecursiveFileSearchDialog::findItemsMatching( QStandardItem* parentItem, const QString& substring, QList& matchingItems )
+{
+ if ( !parentItem ) return;
+ for ( int i = 0; i < parentItem->rowCount(); ++i )
+ {
+ auto searchString = substring + "/";
+ auto childItem = parentItem->child( i );
+ if ( childItem )
+ {
+ auto textToMatch = childItem->text();
+ textToMatch.replace( '\\', '/' );
+ if ( textToMatch.contains( searchString, Qt::CaseInsensitive ) )
+ {
+ matchingItems.append( childItem );
+ }
+ }
+ findItemsMatching( childItem, substring, matchingItems );
+ }
+}
+
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
diff --git a/ApplicationLibCode/Commands/RicRecursiveFileSearchDialog.h b/ApplicationLibCode/Commands/RicRecursiveFileSearchDialog.h
index bae7706a8c..cd2d738060 100644
--- a/ApplicationLibCode/Commands/RicRecursiveFileSearchDialog.h
+++ b/ApplicationLibCode/Commands/RicRecursiveFileSearchDialog.h
@@ -83,6 +83,11 @@ class RicRecursiveFileSearchDialog : public QDialog
static RiaDefines::FileType mapSummaryFileType( RicRecursiveFileSearchDialog::FileType fileType );
+ static QList firstLevelItems( QStandardItem* rootItem );
+ static void setCheckedStateChildItems( QStandardItem* parentItem, Qt::CheckState checkState );
+ static void findItemsMatching( QStandardItem* parentItem, const QString& substring, QList& matchingItems );
+ static void populateComboBoxHistoryFromRegistry( QComboBox* comboBox, const QString& registryKey );
+
private:
RicRecursiveFileSearchDialog( QWidget* parent, const std::vector& fileTypes );
~RicRecursiveFileSearchDialog() override;
@@ -113,8 +118,6 @@ class RicRecursiveFileSearchDialog : public QDialog
QStringList createFileNameFilterList();
static QString replaceWithRealizationStar( const QString& text );
- static void populateComboBoxHistoryFromRegistry( QComboBox* comboBox, const QString& registryKey );
-
static QStringList fileTypeToExtensionStrings( const std::vector& fileTypes );
private slots:
diff --git a/ApplicationLibCode/ProjectDataModel/EnsembleFileSet/RimEnsembleFileSetTools.cpp b/ApplicationLibCode/ProjectDataModel/EnsembleFileSet/RimEnsembleFileSetTools.cpp
index 4da25c239f..23fa670f0a 100644
--- a/ApplicationLibCode/ProjectDataModel/EnsembleFileSet/RimEnsembleFileSetTools.cpp
+++ b/ApplicationLibCode/ProjectDataModel/EnsembleFileSet/RimEnsembleFileSetTools.cpp
@@ -103,6 +103,7 @@ std::vector createGridEnsemblesFromFileSets( const st
project->assignIdToCaseGroup( ensemble );
eclipseCaseColl->reservoirGridEnsembles.push_back( ensemble );
+ ensemble->createGridCasesFromEnsembleFileSet();
ensemble->loadDataAndUpdate();
ensembles.push_back( ensemble );
}
diff --git a/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.cpp b/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.cpp
index f6a79ce179..b9e07db85c 100644
--- a/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.cpp
+++ b/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.cpp
@@ -482,6 +482,14 @@ RimEclipseView* RimReservoirGridEnsemble::addViewForCase( RimEclipseCase* eclips
return m_viewCollection->addView( eclipseCase );
}
+//--------------------------------------------------------------------------------------------------
+///
+//--------------------------------------------------------------------------------------------------
+RimEclipseViewCollection* RimReservoirGridEnsemble::viewCollection() const
+{
+ return m_viewCollection;
+}
+
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
diff --git a/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.h b/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.h
index d39c118fd6..e697955b64 100644
--- a/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.h
+++ b/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.h
@@ -106,6 +106,7 @@ class RimReservoirGridEnsemble : public RimNamedObject, public RimReservoirGridE
RimEclipseView* addViewForCase( RimEclipseCase* eclipseCase );
std::vector allViews() const;
std::set casesInViews() const override;
+ RimEclipseViewCollection* viewCollection() const;
// Well target mapping
void addWellTargetMapping( RimWellTargetMapping* wellTargetMapping );
diff --git a/ApplicationLibCode/UserInterface/RiuMainWindow.cpp b/ApplicationLibCode/UserInterface/RiuMainWindow.cpp
index 6473b7d0a6..81e0d89e53 100644
--- a/ApplicationLibCode/UserInterface/RiuMainWindow.cpp
+++ b/ApplicationLibCode/UserInterface/RiuMainWindow.cpp
@@ -596,6 +596,7 @@ void RiuMainWindow::createToolBars()
toolbar->addAction( cmdFeatureMgr->action( "RicImportEclipseCaseFeature" ) );
toolbar->addAction( cmdFeatureMgr->action( "RicOpenProjectFeature" ) );
toolbar->addAction( cmdFeatureMgr->action( "RicSaveProjectFeature" ) );
+ toolbar->addAction( cmdFeatureMgr->action( "RicImportGridAndSummaryEnsembleFeature" ) );
}
if ( RiaPreferences::current()->useUndoRedo() )
diff --git a/ApplicationLibCode/UserInterface/RiuMenuBarBuildTools.cpp b/ApplicationLibCode/UserInterface/RiuMenuBarBuildTools.cpp
index 6c4891e435..c43784150c 100644
--- a/ApplicationLibCode/UserInterface/RiuMenuBarBuildTools.cpp
+++ b/ApplicationLibCode/UserInterface/RiuMenuBarBuildTools.cpp
@@ -123,6 +123,9 @@ void RiuMenuBarBuildTools::addImportMenuWithActions( QObject* parent, QMenu* men
QMenu* importRoffMenu = importMenu->addMenu( QIcon( ":/Case48x48.png" ), "Roff Grid Models" );
importRoffMenu->addAction( cmdFeatureMgr->action( "RicImportRoffCaseFeature" ) );
+ importMenu->addSeparator();
+ importMenu->addAction( cmdFeatureMgr->action( "RicImportGridAndSummaryEnsembleFeature" ) );
+
importMenu->addSeparator();
QMenu* importSummaryMenu = importMenu->addMenu( QIcon( ":/SummaryCase.svg" ), "Summary Cases" );
importSummaryMenu->addAction( cmdFeatureMgr->action( "RicImportSummaryCaseFeature" ) );
diff --git a/ApplicationLibCode/UserInterface/RiuPlotMainWindow.cpp b/ApplicationLibCode/UserInterface/RiuPlotMainWindow.cpp
index f5b26a49fe..03e8a7a679 100644
--- a/ApplicationLibCode/UserInterface/RiuPlotMainWindow.cpp
+++ b/ApplicationLibCode/UserInterface/RiuPlotMainWindow.cpp
@@ -394,6 +394,7 @@ QStringList RiuPlotMainWindow::toolbarCommandIds( const QString& toolbarName )
commandIds << "RicImportEclipseCaseFeature";
commandIds << "RicImportSummaryCaseFeature";
commandIds << "RicImportEnsembleFeature";
+ commandIds << "RicImportGridAndSummaryEnsembleFeature";
commandIds << "RicOpenProjectFeature";
commandIds << "RicSaveProjectFeature";
}