///////////////////////////////////////////////////////////////////////////////// // // 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 #include #include //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RicImportGridAndSummaryEnsembleDialogResult RicImportGridAndSummaryEnsembleDialog::runDialog( QWidget* parent, bool defaultGridChecked, bool defaultSummaryChecked, const QString& initialDir ) { const QString pathRegistryKey = "RicImportGridAndSummaryEnsembleDialog_path"; const QString filePatternRegistryKey = "RicImportGridAndSummaryEnsembleDialog_filePattern"; 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 ); // File pattern field { QSettings settings; QString fp = settings.value( filePatternRegistryKey ).toString(); if ( !fp.isEmpty() ) dialog.m_filePatternField->setText( fp ); } if ( !initialDir.isEmpty() ) { // Pre-fill with the provided directory QString prefilledDir = initialDir; RiaFilePathTools::appendSeparatorIfNo( prefilledDir ); prefilledDir += "*"; dialog.m_pathFilterField->setCurrentText( QDir::toNativeSeparators( prefilledDir ) ); } else if ( RiaPreferences::current()->useRecentlyUsedFolderAsDefault() && dialog.m_pathFilterField->count() > 1 ) { // Use most recently used path from registry (index 1) when preference is enabled 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 ); } { QSettings settings; settings.setValue( filePatternRegistryKey, dialog.filePattern() ); } // 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.filePattern = dialog.filePattern(); 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_pathFilterField->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); m_browseButton = new QPushButton( "..." ); m_filePatternField = new QLineEdit(); m_filePatternField->setMinimumWidth( 120 ); m_filePatternField->setPlaceholderText( "*" ); 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_useRealizationStarCheckBox->setChecked( true ); 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_fileTreeView->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); 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_filePatternField, SIGNAL( textEdited( 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() ) ); auto updateOkFromCheckboxes = [this]() { bool anyChecked = m_createGridEnsembleCheckBox->isChecked() || m_createSummaryEnsembleCheckBox->isChecked(); setOkButtonEnabled( anyChecked && !m_foundRealizations.isEmpty() ); }; connect( m_createGridEnsembleCheckBox, &QCheckBox::toggled, this, updateOkFromCheckboxes ); connect( m_createSummaryEnsembleCheckBox, &QCheckBox::toggled, this, updateOkFromCheckboxes ); connect( m_treeFilterButton, SIGNAL( clicked() ), this, SLOT( slotFilterTreeViewClicked() ) ); connect( m_fileTreeView, SIGNAL( customContextMenuRequested( const QPoint& ) ), this, SLOT( slotFileListCustomMenuRequested( const QPoint& ) ) ); 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 ); { QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->addWidget( m_pathFilterField ); hLayout->addWidget( m_browseButton ); searchGridLayout->addLayout( hLayout, row, 1, 1, 3 ); } row++; { QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->addWidget( m_useRealizationStarCheckBox ); hLayout->addWidget( new QLabel( "Ensemble Grouping" ) ); hLayout->addWidget( m_ensembleGroupingMode ); hLayout->addWidget( new QLabel( "File pattern" ) ); hLayout->addWidget( m_filePatternField ); hLayout->addStretch( 1 ); searchGridLayout->addLayout( hLayout, row, 1, 1, 3 ); } row++; searchGridLayout->addWidget( new QLabel( "Effective filter" ), row, 0 ); { QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->addWidget( m_effectiveFilterLabel, 1 ); hLayout->addWidget( m_searchButton ); searchGridLayout->addLayout( hLayout, row, 1, 1, 3 ); } searchGridLayout->setColumnStretch( 1, 1 ); 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 ); outputGridLayout->setRowStretch( 0, 0 ); outputGridLayout->setRowStretch( 1, 1 ); m_outputGroup->setLayout( outputGridLayout ); dialogLayout->addWidget( searchGroup, 0 ); dialogLayout->addWidget( importGroup, 0 ); dialogLayout->addWidget( m_outputGroup, 1 ); dialogLayout->addWidget( m_buttons, 0 ); 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::filePattern() const { QString p = m_filePatternField->text().trimmed(); return p.isEmpty() ? "*" : p; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- 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 fp = filePattern(); QString effFilter = QString( "%1%2/%3.EGRID|%3.SMSPEC|%3.ESMRY" ).arg( rootDirWithSeparator() ).arg( pathFilter ).arg( fp ); 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 ); QString fp = filePattern(); QStringList nameFilters; for ( const auto& ext : extensions ) { nameFilters.append( fp + "." + 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(); auto ensembleLabel = []( const QString& name, int gridCount, int summaryCount ) { return QString( "%1 (%2 grids, %3 summary cases)" ).arg( name ).arg( gridCount ).arg( summaryCount ); }; if ( mode == RiaDefines::EnsembleGroupingMode::NONE ) { // Single "Files" ensemble item, one child per base path auto rootItem = m_filePathModel.invisibleRootItem(); auto ensembleItem = new QStandardItem(); ensembleItem->setCheckable( true ); ensembleItem->setCheckState( Qt::Checked ); rootItem->appendRow( ensembleItem ); int gridCount = 0, summaryCount = 0; for ( auto it = m_foundRealizations.begin(); it != m_foundRealizations.end(); ++it ) { if ( !it.value().gridFile.isEmpty() ) gridCount++; if ( !it.value().summaryFile.isEmpty() ) summaryCount++; auto childItem = new QStandardItem( QDir::toNativeSeparators( it.key() ) ); childItem->setCheckable( true ); childItem->setCheckState( Qt::Checked ); childItem->setData( it.key(), Qt::UserRole ); ensembleItem->appendRow( childItem ); } ensembleItem->setText( ensembleLabel( "Files", gridCount, summaryCount ) ); } 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(); bool isFirstGroup = true; for ( const auto& [groupName, groupFiles] : grouping ) { auto ensembleItem = new QStandardItem(); ensembleItem->setCheckable( true ); ensembleItem->setCheckState( isFirstGroup ? Qt::Checked : Qt::Unchecked ); rootItem->appendRow( ensembleItem ); isFirstGroup = false; int gridCount = 0, summaryCount = 0; for ( const auto& repFile : groupFiles ) { QString basePath = repFileToBasePath.value( repFile ); if ( basePath.isEmpty() ) continue; auto it = m_foundRealizations.find( basePath ); if ( it != m_foundRealizations.end() ) { if ( !it.value().gridFile.isEmpty() ) gridCount++; if ( !it.value().summaryFile.isEmpty() ) summaryCount++; } auto childItem = new QStandardItem( QDir::toNativeSeparators( basePath ) ); childItem->setCheckable( true ); childItem->setCheckState( ensembleItem->checkState() ); childItem->setData( basePath, Qt::UserRole ); ensembleItem->appendRow( childItem ); } ensembleItem->setText( ensembleLabel( QDir::toNativeSeparators( groupName ), gridCount, summaryCount ) ); } } // Expand first item only when a single ensemble is detected if ( m_filePathModel.rowCount() == 1 ) { 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 ); m_createGridEnsembleCheckBox->setEnabled( true ); m_createSummaryEnsembleCheckBox->setEnabled( true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- 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:\n'%2'\n\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 gridCount = 0; int summaryCount = 0; for ( const auto& r : m_foundRealizations ) { if ( !r.gridFile.isEmpty() ) gridCount++; if ( !r.summaryFile.isEmpty() ) summaryCount++; } const bool hasGrid = gridCount > 0; const bool hasSummary = summaryCount > 0; m_createGridEnsembleCheckBox->setEnabled( hasGrid ); m_createSummaryEnsembleCheckBox->setEnabled( hasSummary ); if ( !hasGrid ) m_createGridEnsembleCheckBox->setChecked( false ); if ( !hasSummary ) m_createSummaryEnsembleCheckBox->setChecked( false ); int realizationCount = m_foundRealizations.size(); if ( realizationCount > 0 ) { m_outputGroup->setTitle( QString( "Files Found (%1)" ).arg( realizationCount ) ); bool anyChecked = m_createGridEnsembleCheckBox->isChecked() || m_createSummaryEnsembleCheckBox->isChecked(); setOkButtonEnabled( anyChecked ); if ( anyChecked ) 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::slotFileListCustomMenuRequested( const QPoint& point ) { QMenu menu; QAction* action; action = new QAction( "On", this ); connect( action, SIGNAL( triggered() ), SLOT( slotTurnOnFileListItems() ) ); menu.addAction( action ); action = new QAction( "Off", this ); connect( action, SIGNAL( triggered() ), SLOT( slotTurnOffFileListItems() ) ); menu.addAction( action ); action = new QAction( "Toggle", this ); connect( action, SIGNAL( triggered() ), SLOT( slotToggleFileListItems() ) ); menu.addAction( action ); QPoint globalPoint = m_fileTreeView->mapToGlobal( point ); menu.exec( globalPoint ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicImportGridAndSummaryEnsembleDialog::slotTurnOnFileListItems() { auto selectionModel = m_fileTreeView->selectionModel(); auto indices = selectionModel->selectedIndexes(); for ( auto& index : indices ) { if ( index.isValid() ) { auto item = m_filePathModel.itemFromIndex( index ); if ( item && item->isCheckable() ) { item->setCheckState( Qt::Checked ); } } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicImportGridAndSummaryEnsembleDialog::slotTurnOffFileListItems() { auto selectionModel = m_fileTreeView->selectionModel(); auto indices = selectionModel->selectedIndexes(); for ( auto& index : indices ) { if ( index.isValid() ) { auto item = m_filePathModel.itemFromIndex( index ); if ( item && item->isCheckable() ) { item->setCheckState( Qt::Unchecked ); } } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicImportGridAndSummaryEnsembleDialog::slotToggleFileListItems() { auto selectionModel = m_fileTreeView->selectionModel(); auto indices = selectionModel->selectedIndexes(); for ( auto& index : indices ) { if ( index.isValid() ) { auto item = m_filePathModel.itemFromIndex( index ); if ( item && item->isCheckable() ) { item->setCheckState( item->checkState() == Qt::Checked ? Qt::Unchecked : Qt::Checked ); } } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicImportGridAndSummaryEnsembleDialog::slotOkClicked() { accept(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicImportGridAndSummaryEnsembleDialog::slotCancelClicked() { reject(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicImportGridAndSummaryEnsembleDialog::showEvent( QShowEvent* event ) { m_searchButton->setFocus(); QDialog::showEvent( event ); }