mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-24 22:27:55 -05:00
#13987 PDM: Add caf::PdmNestedCollection framework
Introduce a shared base for tree-shaped folder containers that hold a list of leaf items together with a list of subcontainers of the same kind. Replaces the per-class plumbing (subcollections field, top-folder flag, add/remove/find sub-collection, factory) that is duplicated across RimSurfaceCollection, RimPolygonCollection and similar candidates. - caf::PdmNestedCollectionBase: non-templated PdmObject base exposing collection-name, add-new-subcollection, leaf-check and topmost-folder marking for generic command features and script methods. - caf::PdmNestedCollection<SelfT, ItemT>: CRTP template that owns the typed m_subCollections field and inherits the m_items array from PdmObjectCollection<ItemT>. Provides allItems(), find-sub-collection, and a typed factory hook. - RimNestedMirrorCollectionInView: view-side analogue for in-view collections that mirror a nested source collection, with orphan-sweep + find-or-create + reorder sync. Design rationale (templated vs non-templated, break-even on derived class count) lives in docs/pdm-nested-collection-design.md. No callers migrated in this commit.
This commit is contained in:
1 parent
4055954144
commit
92da6633bb
9 files changed
+934
No files matched your search
@@ -71,6 +71,8 @@ set(SOURCE_GROUP_HEADER_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimNamedObject.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimCheckableNamedObject.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimCheckableObject.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimNestedMirrorCollectionInView.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimNestedMirrorCollectionInView.inl
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimGridTimeHistoryCurve.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimEclipseGeometrySelectionItem.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimDialogData.h
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2026 Equinor ASA
|
||||
//
|
||||
// ResInsight is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE.
|
||||
//
|
||||
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
|
||||
// for more details.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RimCheckableNamedObject.h"
|
||||
|
||||
#include "cafPdmChildArrayField.h"
|
||||
#include "cafPdmPtrField.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
//==================================================================================================
|
||||
///
|
||||
/// View-side analogue of caf::PdmNestedCollection.
|
||||
///
|
||||
/// Generic CRTP template for in-view collections that mirror a nested source collection
|
||||
/// (a caf::PdmNestedCollection-derived type). Provides a flat items vector, a vector of
|
||||
/// sub-collections of the same (derived) type, a source-collection pointer, and a sync
|
||||
/// engine that mirrors the source tree by orphan-sweeping, find-or-creating wrappers,
|
||||
/// and reordering to match the source.
|
||||
///
|
||||
/// Template parameters:
|
||||
/// SelfT - The derived class (CRTP).
|
||||
/// SourceT - The source collection type. Must expose collectionName(), and the data
|
||||
/// needed by the sourceSubCollections()/sourceItems() hooks.
|
||||
/// ItemViewT - View-side item wrapper type. Must declare a SourceItemT typedef and a
|
||||
/// sourceItem() accessor returning SourceItemT*.
|
||||
///
|
||||
/// Derived classes must call CAF_PDM_InitFieldNoDefault for the inherited fields
|
||||
/// m_itemsInView, m_collectionsInView and m_sourceCollection, so that the XML keywords
|
||||
/// stay derived-class-specific.
|
||||
//==================================================================================================
|
||||
template <typename SelfT, typename SourceT, typename ItemViewT>
|
||||
class RimNestedMirrorCollectionInView : public RimCheckableNamedObject
|
||||
{
|
||||
public:
|
||||
using SourceItemT = typename ItemViewT::SourceItemT;
|
||||
|
||||
SourceT* sourceCollection() const;
|
||||
void setSourceCollection( SourceT* source );
|
||||
|
||||
// Public sync entry. Refreshes the view tree from the current source.
|
||||
void updateFromSource();
|
||||
|
||||
protected:
|
||||
RimNestedMirrorCollectionInView();
|
||||
~RimNestedMirrorCollectionInView() override;
|
||||
|
||||
// Pure virtual hooks - derived class adapts to its source type.
|
||||
virtual std::vector<SourceT*> sourceSubCollections() const = 0;
|
||||
virtual std::vector<SourceItemT*> sourceItems() const = 0;
|
||||
virtual ItemViewT* createItemInView( SourceItemT* src ) = 0;
|
||||
|
||||
// Default impl: new SelfT() + setSourceCollection(src). Override for extra wiring.
|
||||
virtual SelfT* createSubCollectionInView( SourceT* src );
|
||||
|
||||
// Recursive sync: sub-collections, then items, then editor refresh.
|
||||
void updateAllViewItems();
|
||||
|
||||
SelfT* findCollectionInViewForSource( const SourceT* src ) const;
|
||||
ItemViewT* findItemInViewForSource( const SourceItemT* src ) const;
|
||||
|
||||
caf::PdmChildArrayField<ItemViewT*> m_itemsInView;
|
||||
caf::PdmChildArrayField<SelfT*> m_collectionsInView;
|
||||
caf::PdmPtrField<SourceT*> m_sourceCollection;
|
||||
};
|
||||
|
||||
#include "RimNestedMirrorCollectionInView.inl"
|
||||
@@ -0,0 +1,192 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2026 Equinor ASA
|
||||
//
|
||||
// ResInsight is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE.
|
||||
//
|
||||
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
|
||||
// for more details.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename SourceT, typename ItemViewT>
|
||||
RimNestedMirrorCollectionInView<SelfT, SourceT, ItemViewT>::RimNestedMirrorCollectionInView()
|
||||
{
|
||||
// m_itemsInView, m_collectionsInView and m_sourceCollection must be initialized by the
|
||||
// derived class using CAF_PDM_InitFieldNoDefault, so that the XML keywords are stable
|
||||
// for that class.
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename SourceT, typename ItemViewT>
|
||||
RimNestedMirrorCollectionInView<SelfT, SourceT, ItemViewT>::~RimNestedMirrorCollectionInView()
|
||||
{
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename SourceT, typename ItemViewT>
|
||||
SourceT* RimNestedMirrorCollectionInView<SelfT, SourceT, ItemViewT>::sourceCollection() const
|
||||
{
|
||||
return m_sourceCollection();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename SourceT, typename ItemViewT>
|
||||
void RimNestedMirrorCollectionInView<SelfT, SourceT, ItemViewT>::setSourceCollection( SourceT* source )
|
||||
{
|
||||
m_sourceCollection = source;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename SourceT, typename ItemViewT>
|
||||
void RimNestedMirrorCollectionInView<SelfT, SourceT, ItemViewT>::updateFromSource()
|
||||
{
|
||||
updateAllViewItems();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename SourceT, typename ItemViewT>
|
||||
SelfT* RimNestedMirrorCollectionInView<SelfT, SourceT, ItemViewT>::createSubCollectionInView( SourceT* src )
|
||||
{
|
||||
auto* sub = new SelfT();
|
||||
sub->setSourceCollection( src );
|
||||
return sub;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename SourceT, typename ItemViewT>
|
||||
SelfT* RimNestedMirrorCollectionInView<SelfT, SourceT, ItemViewT>::findCollectionInViewForSource( const SourceT* src ) const
|
||||
{
|
||||
for ( auto coll : m_collectionsInView )
|
||||
{
|
||||
if ( coll && coll->sourceCollection() == src ) return coll;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename SourceT, typename ItemViewT>
|
||||
ItemViewT* RimNestedMirrorCollectionInView<SelfT, SourceT, ItemViewT>::findItemInViewForSource( const SourceItemT* src ) const
|
||||
{
|
||||
for ( auto item : m_itemsInView )
|
||||
{
|
||||
if ( item && item->sourceItem() == src ) return item;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Recursive sync of sub-collections and items, mirroring the source tree.
|
||||
///
|
||||
/// Step 1 - sub-collections: drop wrappers whose source pointer is null, then iterate
|
||||
/// sourceSubCollections() and find-or-create one wrapper per source, reordering to match.
|
||||
/// Recurse into each child so nested levels mirror as well.
|
||||
///
|
||||
/// Step 2 - items: same shape. Drop orphans, iterate sourceItems(), find-or-create.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename SourceT, typename ItemViewT>
|
||||
void RimNestedMirrorCollectionInView<SelfT, SourceT, ItemViewT>::updateAllViewItems()
|
||||
{
|
||||
// Refresh display name from source so the view tree label tracks the source.
|
||||
if ( m_sourceCollection )
|
||||
{
|
||||
this->setName( m_sourceCollection->collectionName() );
|
||||
}
|
||||
|
||||
// --- Sub-collections ---
|
||||
{
|
||||
std::vector<SelfT*> existing = m_collectionsInView.childrenByType();
|
||||
for ( auto* coll : existing )
|
||||
{
|
||||
if ( !coll->sourceCollection() )
|
||||
{
|
||||
m_collectionsInView.removeChild( coll );
|
||||
delete coll;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<SelfT*> ordered;
|
||||
if ( m_sourceCollection )
|
||||
{
|
||||
for ( auto* srcSub : sourceSubCollections() )
|
||||
{
|
||||
if ( !srcSub ) continue;
|
||||
|
||||
SelfT* viewSub = findCollectionInViewForSource( srcSub );
|
||||
if ( viewSub == nullptr )
|
||||
{
|
||||
viewSub = createSubCollectionInView( srcSub );
|
||||
}
|
||||
ordered.push_back( viewSub );
|
||||
}
|
||||
}
|
||||
|
||||
m_collectionsInView.clearWithoutDelete();
|
||||
for ( auto* viewSub : ordered )
|
||||
{
|
||||
m_collectionsInView.push_back( viewSub );
|
||||
viewSub->updateAllViewItems();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Items ---
|
||||
{
|
||||
std::vector<ItemViewT*> existing = m_itemsInView.childrenByType();
|
||||
for ( auto* item : existing )
|
||||
{
|
||||
if ( !item->sourceItem() )
|
||||
{
|
||||
m_itemsInView.removeChild( item );
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ItemViewT*> ordered;
|
||||
if ( m_sourceCollection )
|
||||
{
|
||||
for ( auto* srcItem : sourceItems() )
|
||||
{
|
||||
if ( !srcItem ) continue;
|
||||
|
||||
ItemViewT* viewItem = findItemInViewForSource( srcItem );
|
||||
if ( viewItem == nullptr )
|
||||
{
|
||||
viewItem = createItemInView( srcItem );
|
||||
}
|
||||
ordered.push_back( viewItem );
|
||||
}
|
||||
}
|
||||
|
||||
m_itemsInView.clearWithoutDelete();
|
||||
for ( auto* viewItem : ordered )
|
||||
{
|
||||
m_itemsInView.push_back( viewItem );
|
||||
}
|
||||
}
|
||||
|
||||
this->updateConnectedEditors();
|
||||
}
|
||||
@@ -25,6 +25,10 @@ set(PROJECT_FILES
|
||||
cafPdmObject.h
|
||||
cafPdmObjectCollection.h
|
||||
cafPdmObjectCollection.inl
|
||||
cafPdmNestedCollection.h
|
||||
cafPdmNestedCollection.inl
|
||||
cafPdmNestedCollectionBase.h
|
||||
cafPdmNestedCollectionBase.cpp
|
||||
)
|
||||
|
||||
add_library(${PROJECT_NAME} ${PROJECT_FILES})
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2026 Equinor ASA
|
||||
//
|
||||
// ResInsight is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE.
|
||||
//
|
||||
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
|
||||
// for more details.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cafPdmChildArrayField.h"
|
||||
#include "cafPdmField.h"
|
||||
#include "cafPdmNestedCollectionBase.h"
|
||||
#include "cafPdmObjectCollection.h" // for DerivedFromPdmObject concept
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace caf
|
||||
{
|
||||
|
||||
//==================================================================================================
|
||||
///
|
||||
/// Generic templated nested collection base class
|
||||
///
|
||||
/// CRTP template for tree-shaped PDM containers (folder of items + folder of folders). Derives
|
||||
/// from caf::PdmNestedCollectionBase, which provides the non-templated PdmObject anchor (so
|
||||
/// generic script methods like AddFolder can be registered once on the base and inherit through
|
||||
/// every concrete derivation).
|
||||
///
|
||||
/// Template parameters:
|
||||
/// SelfT - The derived class (CRTP). Must inherit from caf::PdmObject.
|
||||
/// ItemT - Item type held in the flat items vector. Must inherit from caf::PdmObject.
|
||||
///
|
||||
/// Derived classes must call CAF_PDM_InitFieldNoDefault for the inherited fields
|
||||
/// m_items, m_collectionName and m_subCollections so the XML keywords stay derived-class
|
||||
/// specific (matching the existing PdmObjectCollection<T> convention).
|
||||
///
|
||||
//==================================================================================================
|
||||
template <typename SelfT, typename ItemT>
|
||||
class PdmNestedCollection : public PdmNestedCollectionBase
|
||||
{
|
||||
public:
|
||||
// Item access
|
||||
std::vector<ItemT*> items() const;
|
||||
size_t count() const;
|
||||
bool isEmpty() const;
|
||||
PdmChildArrayField<ItemT*>& itemsField();
|
||||
const PdmChildArrayField<ItemT*>& itemsField() const;
|
||||
|
||||
// Item CRUD
|
||||
void addItem( ItemT* item );
|
||||
void insertItem( ItemT* insertBefore, ItemT* item );
|
||||
void deleteItem( ItemT* item );
|
||||
void deleteAllItems();
|
||||
ItemT* createDefaultItem();
|
||||
|
||||
void updateConnectedEditors();
|
||||
|
||||
// Subcollection access
|
||||
std::vector<SelfT*> subCollections() const;
|
||||
|
||||
// Subcollection CRUD
|
||||
void addSubCollection( SelfT* sub );
|
||||
PdmObject* addNewSubCollection() override;
|
||||
SelfT* findSubCollectionByName( const QString& name ) const;
|
||||
|
||||
// Returns items held by this collection and recursively by all subcollections.
|
||||
// For items at this level only, use items().
|
||||
std::vector<ItemT*> allItems() const;
|
||||
|
||||
protected:
|
||||
PdmNestedCollection();
|
||||
~PdmNestedCollection() override;
|
||||
|
||||
// Hook for derived classes; called when m_items changes.
|
||||
virtual void onItemsChanged();
|
||||
|
||||
void fieldChangedByUi( const PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
|
||||
void defineUiOrdering( QString uiConfigName, PdmUiOrdering& uiOrdering ) override;
|
||||
void defineEditorAttribute( const PdmFieldHandle* field, QString uiConfigName, PdmUiEditorAttribute* attribute ) override;
|
||||
void onChildDeleted( PdmChildArrayFieldHandle* childArray, std::vector<PdmObjectHandle*>& referringObjects ) override;
|
||||
|
||||
PdmChildArrayField<SelfT*> m_subCollections;
|
||||
PdmChildArrayField<ItemT*> m_items;
|
||||
};
|
||||
|
||||
} // namespace caf
|
||||
|
||||
#include "cafPdmNestedCollection.inl"
|
||||
@@ -0,0 +1,296 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2026 Equinor ASA
|
||||
//
|
||||
// ResInsight is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE.
|
||||
//
|
||||
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
|
||||
// for more details.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "cafPdmUiTableViewEditor.h"
|
||||
|
||||
namespace caf
|
||||
{
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
PdmNestedCollection<SelfT, ItemT>::PdmNestedCollection()
|
||||
{
|
||||
static_assert( DerivedFromPdmObject<SelfT>, "SelfT must inherit from caf::PdmObject" );
|
||||
static_assert( DerivedFromPdmObject<ItemT>, "ItemT must inherit from caf::PdmObject" );
|
||||
// m_items, m_collectionName and m_subCollections must be initialized by the derived class
|
||||
// using CAF_PDM_InitFieldNoDefault, so that the XML keywords are stable for that class.
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
PdmNestedCollection<SelfT, ItemT>::~PdmNestedCollection()
|
||||
{
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
std::vector<ItemT*> PdmNestedCollection<SelfT, ItemT>::items() const
|
||||
{
|
||||
return m_items.childrenByType();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
size_t PdmNestedCollection<SelfT, ItemT>::count() const
|
||||
{
|
||||
return m_items.size();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
bool PdmNestedCollection<SelfT, ItemT>::isEmpty() const
|
||||
{
|
||||
return m_items.empty();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
PdmChildArrayField<ItemT*>& PdmNestedCollection<SelfT, ItemT>::itemsField()
|
||||
{
|
||||
return m_items;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
const PdmChildArrayField<ItemT*>& PdmNestedCollection<SelfT, ItemT>::itemsField() const
|
||||
{
|
||||
return m_items;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
void PdmNestedCollection<SelfT, ItemT>::addItem( ItemT* item )
|
||||
{
|
||||
if ( item )
|
||||
{
|
||||
m_items.push_back( item );
|
||||
onItemsChanged();
|
||||
updateConnectedEditors();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
void PdmNestedCollection<SelfT, ItemT>::insertItem( ItemT* insertBefore, ItemT* item )
|
||||
{
|
||||
if ( !item ) return;
|
||||
|
||||
size_t index = m_items.indexOf( insertBefore );
|
||||
if ( index < m_items.size() )
|
||||
m_items.insert( index, item );
|
||||
else
|
||||
m_items.push_back( item );
|
||||
|
||||
onItemsChanged();
|
||||
updateConnectedEditors();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
void PdmNestedCollection<SelfT, ItemT>::deleteItem( ItemT* item )
|
||||
{
|
||||
if ( item )
|
||||
{
|
||||
m_items.removeChild( item );
|
||||
delete item;
|
||||
updateConnectedEditors();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
void PdmNestedCollection<SelfT, ItemT>::deleteAllItems()
|
||||
{
|
||||
m_items.deleteChildren();
|
||||
updateConnectedEditors();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
ItemT* PdmNestedCollection<SelfT, ItemT>::createDefaultItem()
|
||||
{
|
||||
auto* item = new ItemT();
|
||||
addItem( item );
|
||||
return item;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
void PdmNestedCollection<SelfT, ItemT>::updateConnectedEditors()
|
||||
{
|
||||
this->uiCapability()->updateConnectedEditors();
|
||||
m_items.uiCapability()->updateConnectedEditors();
|
||||
m_subCollections.uiCapability()->updateConnectedEditors();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
std::vector<SelfT*> PdmNestedCollection<SelfT, ItemT>::subCollections() const
|
||||
{
|
||||
return m_subCollections.childrenByType();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
void PdmNestedCollection<SelfT, ItemT>::addSubCollection( SelfT* sub )
|
||||
{
|
||||
if ( sub )
|
||||
{
|
||||
m_subCollections.push_back( sub );
|
||||
this->updateConnectedEditors();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
PdmObject* PdmNestedCollection<SelfT, ItemT>::addNewSubCollection()
|
||||
{
|
||||
auto* sub = new SelfT();
|
||||
addSubCollection( sub );
|
||||
return sub;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
SelfT* PdmNestedCollection<SelfT, ItemT>::findSubCollectionByName( const QString& name ) const
|
||||
{
|
||||
for ( auto coll : m_subCollections )
|
||||
{
|
||||
if ( coll && coll->collectionName() == name ) return coll;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
std::vector<ItemT*> PdmNestedCollection<SelfT, ItemT>::allItems() const
|
||||
{
|
||||
std::vector<ItemT*> result = items();
|
||||
for ( auto* sub : subCollections() )
|
||||
{
|
||||
if ( !sub ) continue;
|
||||
auto subItems = sub->allItems();
|
||||
result.insert( result.end(), subItems.begin(), subItems.end() );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
void PdmNestedCollection<SelfT, ItemT>::onItemsChanged()
|
||||
{
|
||||
// Default implementation does nothing; derived classes may override.
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
void PdmNestedCollection<SelfT, ItemT>::fieldChangedByUi( const PdmFieldHandle* changedField,
|
||||
const QVariant& oldValue,
|
||||
const QVariant& newValue )
|
||||
{
|
||||
if ( changedField == &m_items )
|
||||
{
|
||||
onItemsChanged();
|
||||
}
|
||||
|
||||
updateConnectedEditors();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
void PdmNestedCollection<SelfT, ItemT>::defineUiOrdering( QString uiConfigName, PdmUiOrdering& uiOrdering )
|
||||
{
|
||||
uiOrdering.add( &m_collectionName );
|
||||
uiOrdering.add( &m_subCollections );
|
||||
uiOrdering.add( &m_items );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
void PdmNestedCollection<SelfT, ItemT>::defineEditorAttribute( const PdmFieldHandle* field,
|
||||
QString uiConfigName,
|
||||
PdmUiEditorAttribute* attribute )
|
||||
{
|
||||
if ( field == &m_items )
|
||||
{
|
||||
auto tvAttribute = dynamic_cast<PdmUiTableViewEditorAttribute*>( attribute );
|
||||
if ( tvAttribute )
|
||||
{
|
||||
tvAttribute->resizePolicy = PdmUiTableViewEditorAttribute::RESIZE_TO_FILL_CONTAINER;
|
||||
tvAttribute->alwaysEnforceResizePolicy = true;
|
||||
tvAttribute->minimumHeight = 300;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
template <typename SelfT, typename ItemT>
|
||||
void PdmNestedCollection<SelfT, ItemT>::onChildDeleted( PdmChildArrayFieldHandle* childArray,
|
||||
std::vector<PdmObjectHandle*>& referringObjects )
|
||||
{
|
||||
updateConnectedEditors();
|
||||
}
|
||||
|
||||
} // namespace caf
|
||||
@@ -0,0 +1,100 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2026 Equinor ASA
|
||||
//
|
||||
// ResInsight is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE.
|
||||
//
|
||||
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
|
||||
// for more details.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "cafPdmNestedCollectionBase.h"
|
||||
|
||||
#include "cafIconProvider.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QStyle>
|
||||
|
||||
namespace caf
|
||||
{
|
||||
|
||||
CAF_PDM_ABSTRACT_SOURCE_INIT( PdmNestedCollectionBase, "PdmNestedCollectionBase" );
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
PdmNestedCollectionBase::PdmNestedCollectionBase()
|
||||
: m_isTopmostFolder( false )
|
||||
{
|
||||
CAF_PDM_InitObject( "Nested Collection" );
|
||||
|
||||
// m_collectionName is initialized by derived classes with a derived-specific XML keyword,
|
||||
// matching the existing per-class scriptable-field convention.
|
||||
|
||||
// Apply the platform-native folder icon as the per-instance default so every nested
|
||||
// collection looks like a folder in the project tree. Derived classes that want a branded
|
||||
// icon (e.g. the top-level instance) override via setUiIcon / createTopmost(). In console
|
||||
// mode there is no QApplication / QStyle, so we fall back to whatever static icon the
|
||||
// derived class supplied to CAF_PDM_InitObject.
|
||||
if ( qobject_cast<QApplication*>( QApplication::instance() ) )
|
||||
{
|
||||
if ( QStyle* style = QApplication::style() )
|
||||
{
|
||||
QPixmap pix = style->standardIcon( QStyle::SP_DirIcon ).pixmap( 16, 16 );
|
||||
uiCapability()->setUiIcon( IconProvider( pix ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
PdmNestedCollectionBase::~PdmNestedCollectionBase()
|
||||
{
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString PdmNestedCollectionBase::collectionName() const
|
||||
{
|
||||
return m_collectionName.value();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void PdmNestedCollectionBase::setCollectionName( const QString& name )
|
||||
{
|
||||
m_collectionName.setValue( name );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void PdmNestedCollectionBase::setAsTopmostFolder()
|
||||
{
|
||||
m_collectionName.uiCapability()->setUiHidden( true );
|
||||
m_collectionName.xmlCapability()->disableIO();
|
||||
setDeletable( false );
|
||||
m_isTopmostFolder = true;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
PdmFieldHandle* PdmNestedCollectionBase::userDescriptionField()
|
||||
{
|
||||
if ( m_isTopmostFolder ) return nullptr;
|
||||
return &m_collectionName;
|
||||
}
|
||||
|
||||
} // namespace caf
|
||||
@@ -0,0 +1,74 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2026 Equinor ASA
|
||||
//
|
||||
// ResInsight is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE.
|
||||
//
|
||||
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
|
||||
// for more details.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cafPdmField.h"
|
||||
#include "cafPdmObject.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace caf
|
||||
{
|
||||
|
||||
//==================================================================================================
|
||||
///
|
||||
/// Non-templated PdmObject base class for tree-shaped containers.
|
||||
///
|
||||
/// Provides the type-erased interface (collection name, add-new-subcollection, leaf check) that
|
||||
/// generic command features and script methods need. The templated caf::PdmNestedCollection<>
|
||||
/// derives from this and supplies typed storage and accessors.
|
||||
///
|
||||
/// Concrete (instantiable) so the CAF factory can create a dummy instance for the Python
|
||||
/// generator. The base's addNewSubCollection() is a no-op returning nullptr; the templated
|
||||
/// derived class overrides it with a real implementation.
|
||||
///
|
||||
//==================================================================================================
|
||||
class PdmNestedCollectionBase : public PdmObject
|
||||
{
|
||||
CAF_PDM_HEADER_INIT;
|
||||
|
||||
public:
|
||||
PdmNestedCollectionBase();
|
||||
~PdmNestedCollectionBase() override;
|
||||
|
||||
QString collectionName() const;
|
||||
void setCollectionName( const QString& name );
|
||||
|
||||
// Whether this container accepts sub-collections. Leaf containers (e.g. file-backed
|
||||
// folders) override to return false; generic UI / script features hide the corresponding
|
||||
// action when this is false.
|
||||
virtual bool canAddSubCollection() const { return true; }
|
||||
|
||||
// Creates a subcollection, adds it to this collection, and returns it. Default returns
|
||||
// nullptr; concrete derivations (typically PdmNestedCollection<>) override.
|
||||
virtual PdmObject* addNewSubCollection() { return nullptr; }
|
||||
|
||||
// Marks this instance as the topmost folder: hides and disables IO on the collection name,
|
||||
// makes the object non-deletable.
|
||||
void setAsTopmostFolder();
|
||||
|
||||
protected:
|
||||
PdmFieldHandle* userDescriptionField() override;
|
||||
|
||||
PdmField<QString> m_collectionName;
|
||||
|
||||
bool m_isTopmostFolder;
|
||||
};
|
||||
|
||||
} // namespace caf
|
||||
@@ -0,0 +1,81 @@
|
||||
# `caf::PdmNestedCollection` — design notes
|
||||
|
||||
## Context
|
||||
|
||||
`caf::PdmNestedCollection` is the base class for tree-shaped PDM containers — a
|
||||
folder of items that can also contain folders of the same kind. The design is a
|
||||
non-template concrete base + a CRTP template:
|
||||
|
||||
```cpp
|
||||
class PdmNestedCollectionBase : public PdmObject
|
||||
{
|
||||
QString collectionName() const;
|
||||
void setCollectionName( const QString& name );
|
||||
virtual bool canAddSubCollection() const;
|
||||
virtual PdmObject* addNewSubCollection();
|
||||
void setAsTopmostFolder();
|
||||
PdmField<QString> m_collectionName;
|
||||
};
|
||||
|
||||
template <typename SelfT, typename ItemT>
|
||||
class PdmNestedCollection : public PdmNestedCollectionBase
|
||||
{
|
||||
// typed accessors + storage for items and subcollections
|
||||
PdmChildArrayField<SelfT*> m_subCollections;
|
||||
PdmChildArrayField<ItemT*> m_items;
|
||||
};
|
||||
```
|
||||
|
||||
`PdmNestedCollectionBase` is the shared PdmObject ancestor — the anchor point
|
||||
where the CAF method factory can register generic script methods (like
|
||||
`AddFolder`) once and have them surface on every derived nested collection via
|
||||
the inheritance walk and Python class hierarchy.
|
||||
|
||||
`PdmNestedCollection<SelfT, ItemT>` adds typed storage and accessors. `SelfT` is
|
||||
the CRTP self type (so subcollections are typed `SelfT*`); `ItemT` is the leaf
|
||||
item type. The template does not inherit `PdmObjectCollection<ItemT>` — m_items
|
||||
is declared here directly, so the inheritance chain stays linear and free of
|
||||
diamond shapes.
|
||||
|
||||
## Why both a non-template base and a CRTP template
|
||||
|
||||
Two needs that don't fit into a single layer:
|
||||
|
||||
- **CAF method registration is keyed by `classKeyword`** — only a concrete
|
||||
non-template class can serve as the anchor. Hence the non-template
|
||||
`PdmNestedCollectionBase`. Without it, generic script methods (`AddFolder`)
|
||||
would have to be re-registered per derived class.
|
||||
|
||||
- **Typed storage and accessors** — `m_subCollections : PdmChildArrayField<SelfT*>`
|
||||
needs `SelfT`, which only exists as a template parameter. Hence the CRTP
|
||||
template layer on top of the base.
|
||||
|
||||
## Alternative considered: non-templated base only
|
||||
|
||||
```cpp
|
||||
class PdmNestedCollection : public PdmObject
|
||||
{
|
||||
...
|
||||
virtual PdmObject* createSubCollection() const = 0;
|
||||
// m_subCollectionsField / m_itemsField stored as type-erased handles
|
||||
};
|
||||
```
|
||||
|
||||
Each derived class would own its typed `PdmChildArrayField<DerivedType*>
|
||||
m_subCollections` and `PdmChildArrayField<ItemType*> m_items`, register them
|
||||
with the base in the constructor, and override `createSubCollection()`. About
|
||||
~25 lines of boilerplate per derived class beyond what the current design needs:
|
||||
two field declarations, two `registerXxxField` calls, an override of
|
||||
`createSubCollection`, re-declared typed accessors.
|
||||
|
||||
At the projected 8–10 derived classes that's ~200–250 lines of repeated code.
|
||||
The hybrid (non-template base + template) design pays the cost once in the
|
||||
framework and gets typed access plus generic registration for free.
|
||||
|
||||
## When to revisit
|
||||
|
||||
- If the count of nested-collection types drops to 1–2 — the template tier no
|
||||
longer earns its keep, and a pure non-template base wins on simplicity.
|
||||
- If template instantiation cost shows up in build profiling for the PDM layer.
|
||||
- If the typed accessors are rarely used in practice and most call sites
|
||||
immediately cast to `PdmObject*` anyway.
|
||||
Reference in new issue
Block a user