Files
ResInsight/Fwk/AppFwk/cafUserInterface/cafPdmUiFormLayoutObjectEditor.cpp
Magne Sjaastad 277322cbcb #14884 Reparent hidden field labels when rebuilding form layout
A hidden label is never added to a layout, so it stayed parented to the
group box it was created in. When the field moved directly to another
group, the old group box was deleted together with the label, and the
next update hit a null label in PdmUiCheckBoxEditor.

Add GroupVisibilityByEnum to cafTestApplication to reproduce the crash.
2026-10-09 13:31:39 +02:00

858 lines
37 KiB
C++

//##################################################################################################
//
// Custom Visualization Core library
// Copyright (C) 2017 Ceetron Solutions AS
//
// This library may be used under the terms of either the GNU General Public License or
// the GNU Lesser General Public License as follows:
//
// GNU General Public License Usage
// This library 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.
//
// This library 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.
//
// GNU Lesser General Public License Usage
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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 Lesser General Public License at <<http://www.gnu.org/licenses/lgpl-2.1.html>>
// for more details.
//
//##################################################################################################
#include "cafPdmUiFormLayoutObjectEditor.h"
#include "cafPdmChildArrayField.h"
#include "cafPdmChildField.h"
#include "cafPdmLogging.h"
#include "cafPdmObjectHandle.h"
#include "cafPdmUiButton.h"
#include "cafPdmUiButtonBox.h"
#include "cafPdmUiFieldEditorHandle.h"
#include "cafPdmUiFieldEditorHelper.h"
#include "cafPdmUiFieldHandle.h"
#include "cafPdmUiLabel.h"
#include "cafPdmUiListEditor.h"
#include "cafPdmUiObjectHandle.h"
#include "cafPdmUiOrdering.h"
#include "cafPdmXmlObjectHandle.h"
#include "cafAssert.h"
#include "QMinimizePanel.h"
#include <QCoreApplication>
#include <QDialogButtonBox>
#include <QFrame>
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
namespace
{
//--------------------------------------------------------------------------------------------------
/// Schedule deletion of a group box without destroying the widgets inside it synchronously.
///
/// A plain delete destroys the child widgets immediately, which defeats the deleteLater() used by
/// PdmUiFieldEditorHandle to keep field editor widgets alive. When the rebuild is triggered from inside a
/// widget event handler, Qt is still using those widgets further up the stack.
/// See https://github.com/OPM/ResInsight/issues/14505
///
/// The group box is hidden and detached from its parent before the deferred delete is scheduled, so it is
/// out of the layout, the visual tree and the focus chain immediately. This is what plain deleteLater()
/// failed to do in https://github.com/OPM/ResInsight/issues/9719
//--------------------------------------------------------------------------------------------------
auto scheduleGroupBoxDeletion = []( QMinimizePanel* groupBox )
{
if ( !groupBox ) return;
groupBox->hide();
groupBox->setParent( nullptr );
groupBox->deleteLater();
};
} // namespace
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
caf::PdmUiFormLayoutObjectEditor::PdmUiFormLayoutObjectEditor()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
caf::PdmUiFormLayoutObjectEditor::~PdmUiFormLayoutObjectEditor()
{
// If there are field editor present, the usage of this editor has not cleared correctly
// The intended usage is to call the method setPdmObject(NULL) before closing the dialog
if ( !m_fieldViews.empty() )
{
CAF_PDM_LOG_WARNING( QString( "UI Form Layout Editor: Destructor called with %1 field views still present. "
"Call setPdmObject(nullptr) before destruction." )
.arg( m_fieldViews.size() ) );
}
CAF_ASSERT( m_fieldViews.empty() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool caf::PdmUiFormLayoutObjectEditor::recursivelyConfigureAndUpdateUiOrderingInNewGridLayout( const PdmUiOrdering& uiOrdering,
QWidget* containerWidget,
const QString& uiConfigName,
bool parentRowIsLast )
{
QSize beforeSize = containerWidget->sizeHint();
ensureWidgetContainsEmptyGridLayout( containerWidget );
int stretch =
recursivelyConfigureAndUpdateUiOrderingInGridLayout( uiOrdering, containerWidget, uiConfigName, parentRowIsLast );
QSize afterSize = containerWidget->sizeHint();
if ( beforeSize != afterSize )
{
containerWidget->adjustSize();
}
return stretch > 0;
}
//--------------------------------------------------------------------------------------------------
/// Add all widgets at a recursion level in the form.
/// Returns the stretch factor that should be applied at the level above.
//--------------------------------------------------------------------------------------------------
int caf::PdmUiFormLayoutObjectEditor::recursivelyConfigureAndUpdateUiOrderingInGridLayout( const PdmUiOrdering& uiOrdering,
QWidget* containerWidgetWithGridLayout,
const QString& uiConfigName,
bool parentRowIsLast )
{
int sumRowStretch = 0;
CAF_ASSERT( containerWidgetWithGridLayout );
QWidget* previousTabOrderWidget = nullptr;
// Currently, only QGridLayout is supported
QGridLayout* parentLayout = dynamic_cast<QGridLayout*>( containerWidgetWithGridLayout->layout() );
CAF_ASSERT( parentLayout );
PdmUiOrdering::TableLayout tableLayout = uiOrdering.calculateTableLayout( uiConfigName );
int totalRows = static_cast<int>( tableLayout.size() );
int totalColumns = uiOrdering.nrOfColumns( tableLayout );
for ( int currentRowIndex = 0; currentRowIndex < totalRows; ++currentRowIndex )
{
int currentColumn = 0;
// True when this row is the bottom-most row of the entire form. Editors that can grow vertically
// use this to fill the remaining space rather than being constrained to a fixed height hint.
bool rowIsLast = parentRowIsLast && ( currentRowIndex == totalRows - 1 );
const PdmUiOrdering::RowLayout& uiItemsInRow = tableLayout[currentRowIndex];
int columnsRequiredForCurrentRow = uiOrdering.nrOfRequiredColumnsInRow( uiItemsInRow );
int nrOfExpandingItemsInRow = uiOrdering.nrOfExpandingItemsInRow( uiItemsInRow );
int spareColumnsInRow = totalColumns - columnsRequiredForCurrentRow;
std::div_t columnsDiv = { 0, 0 };
if ( spareColumnsInRow && nrOfExpandingItemsInRow )
{
columnsDiv = std::div( spareColumnsInRow, nrOfExpandingItemsInRow );
}
for ( size_t i = 0; i < uiItemsInRow.size(); ++i )
{
PdmUiItem* currentItem = uiItemsInRow[i].first;
PdmUiOrdering::LayoutOptions currentLayout = uiItemsInRow[i].second;
int minimumItemColumnSpan = 0, minimumLabelColumnSpan = 0, minimumFieldColumnSpan = 0;
uiOrdering.nrOfColumnsRequiredForItem( uiItemsInRow[i],
&minimumItemColumnSpan,
&minimumLabelColumnSpan,
&minimumFieldColumnSpan );
bool isExpandingItem = currentLayout.totalColumnSpan == PdmUiOrdering::MAX_COLUMN_SPAN;
int spareColumnsToAssign = 0;
if ( isExpandingItem )
{
spareColumnsToAssign += columnsDiv.quot;
if ( i == 0 ) spareColumnsToAssign += columnsDiv.rem;
}
int itemColumnSpan = minimumItemColumnSpan + spareColumnsToAssign;
if ( currentItem->isUiGroup() )
{
int groupStretchFactor = recursivelyAddGroupToGridLayout( currentItem,
containerWidgetWithGridLayout,
uiConfigName,
parentLayout,
currentRowIndex,
currentColumn,
itemColumnSpan,
rowIsLast );
parentLayout->setRowStretch( currentRowIndex, groupStretchFactor );
currentColumn += itemColumnSpan;
sumRowStretch += groupStretchFactor;
}
else if ( auto* label = dynamic_cast<PdmUiLabel*>( currentItem ) )
{
if ( auto qLabel = createLabel( containerWidgetWithGridLayout, *label, uiConfigName ) )
{
parentLayout->addWidget( qLabel, currentRowIndex, currentColumn, 1, itemColumnSpan, Qt::AlignTop );
currentColumn += itemColumnSpan;
}
else
{
CAF_PDM_LOG_ERROR( QString( "UI Form Layout Editor: Failed to create label for text '%1'." )
.arg( label->uiName( uiConfigName ) ) );
}
}
else if ( auto* button = dynamic_cast<PdmUiButton*>( currentItem ) )
{
if ( auto qButton = createButton( containerWidgetWithGridLayout, *button, uiConfigName ) )
{
Qt::Alignment alignment = button->fillWidth() ? Qt::Alignment() : button->alignment();
parentLayout->addWidget( qButton, currentRowIndex, currentColumn, 1, itemColumnSpan, alignment );
currentColumn += itemColumnSpan;
}
else
{
CAF_PDM_LOG_ERROR( QString( "UI Form Layout Editor: Failed to create button for text '%1'." )
.arg( button->uiName( uiConfigName ) ) );
}
}
else if ( auto* buttonBox = dynamic_cast<PdmUiButtonBox*>( currentItem ) )
{
// No alignment: stretched to fill its cell, its own internal layout packs the
// buttons -- the same mechanism a real QDialog's button row uses.
QDialogButtonBox* qButtonBox = createButtonBox( containerWidgetWithGridLayout, *buttonBox );
parentLayout->addWidget( qButtonBox, currentRowIndex, currentColumn, 1, itemColumnSpan );
currentColumn += itemColumnSpan;
}
else
{
PdmUiFieldEditorHandle* fieldEditor = nullptr;
PdmUiFieldHandle* field = dynamic_cast<PdmUiFieldHandle*>( currentItem );
if ( field )
fieldEditor = findOrCreateFieldEditor( containerWidgetWithGridLayout, field, uiConfigName );
if ( fieldEditor )
{
// Also assign required item space that isn't taken up by field and label
spareColumnsToAssign += minimumItemColumnSpan - ( minimumLabelColumnSpan + minimumFieldColumnSpan );
// Place the widget(s) into the correct parent and layout
QWidget* fieldCombinedWidget = fieldEditor->combinedWidget();
if ( fieldCombinedWidget )
{
parentLayout->addWidget( fieldCombinedWidget, currentRowIndex, currentColumn, 1, itemColumnSpan );
parentLayout->setRowStretch( currentRowIndex, fieldEditor->rowStretchFactor() );
sumRowStretch += fieldEditor->rowStretchFactor();
}
else
{
QWidget* fieldEditorWidget = fieldEditor->editorWidget();
if ( !fieldEditorWidget ) continue;
int fieldColumnSpan = minimumFieldColumnSpan;
QWidget* fieldLabelWidget = fieldEditor->labelWidget();
PdmUiItemInfo::LabelPosition labelPos = PdmUiItemInfo::LabelPosition::HIDDEN;
if ( fieldLabelWidget )
{
labelPos = field->uiLabelPosition( uiConfigName );
if ( labelPos == PdmUiItemInfo::LabelPosition::HIDDEN )
{
// Not added to the layout, so reparent explicitly. Otherwise the label stays in a
// previous group box and is deleted with it when the field moves to another group.
fieldLabelWidget->setParent( containerWidgetWithGridLayout );
fieldLabelWidget->hide();
}
else if ( labelPos == PdmUiItemInfo::LabelPosition::TOP )
{
QVBoxLayout* labelAndFieldVerticalLayout = new QVBoxLayout();
parentLayout->addLayout( labelAndFieldVerticalLayout,
currentRowIndex,
currentColumn,
1,
itemColumnSpan,
Qt::AlignTop );
labelAndFieldVerticalLayout->addWidget( fieldLabelWidget, 0, Qt::AlignTop );
labelAndFieldVerticalLayout->addWidget( fieldEditorWidget, 1, Qt::AlignTop );
// Apply margins determined by the editor type
// fieldLabelWidget->setContentsMargins(fieldEditor->labelContentMargins());
currentColumn += itemColumnSpan;
}
else
{
CAF_ASSERT( labelPos == PdmUiItemInfo::LabelPosition::LEFT );
int leftLabelColumnSpan = minimumLabelColumnSpan;
if ( currentLayout.leftLabelColumnSpan == PdmUiOrdering::MAX_COLUMN_SPAN &&
currentLayout.totalColumnSpan != PdmUiOrdering::MAX_COLUMN_SPAN )
{
leftLabelColumnSpan += spareColumnsToAssign;
spareColumnsToAssign = 0;
}
else if ( currentLayout.leftLabelColumnSpan == PdmUiOrdering::MAX_COLUMN_SPAN )
{
leftLabelColumnSpan += spareColumnsToAssign / 2;
spareColumnsToAssign -= spareColumnsToAssign / 2;
}
parentLayout->addWidget( fieldLabelWidget,
currentRowIndex,
currentColumn,
1,
leftLabelColumnSpan,
Qt::AlignTop );
currentColumn += leftLabelColumnSpan;
// Apply margins determined by the editor type
fieldLabelWidget->setContentsMargins( fieldEditor->labelContentMargins() );
}
}
if ( labelPos != PdmUiItemInfo::LabelPosition::TOP ) // Already added if TOP
{
fieldColumnSpan += spareColumnsToAssign;
CAF_ASSERT( fieldColumnSpan >= 1 && "Need at least one column for the field" );
fieldColumnSpan = std::max( 1, fieldColumnSpan );
parentLayout->addWidget( fieldEditorWidget,
currentRowIndex,
currentColumn,
1,
fieldColumnSpan,
Qt::AlignTop );
currentColumn += fieldColumnSpan;
}
if ( previousTabOrderWidget )
{
QWidget::setTabOrder( previousTabOrderWidget, fieldEditorWidget );
}
previousTabOrderWidget = fieldEditorWidget;
parentLayout->setRowStretch( currentRowIndex, fieldEditor->rowStretchFactor() );
sumRowStretch += fieldEditor->rowStretchFactor();
}
fieldEditor->setIsInLastFormRow( rowIsLast );
fieldEditor->updateUi( uiConfigName );
}
}
}
CAF_ASSERT( currentColumn <= totalColumns );
}
containerWidgetWithGridLayout->updateGeometry();
// The magnitude of the stretch should not be sent up, only if there was stretch or not
return sumRowStretch;
}
//--------------------------------------------------------------------------------------------------
/// Create a group and add widgets. Return true if the containing row needs to be stretched.
//--------------------------------------------------------------------------------------------------
int caf::PdmUiFormLayoutObjectEditor::recursivelyAddGroupToGridLayout( PdmUiItem* currentItem,
QWidget* containerWidgetWithGridLayout,
const QString& uiConfigName,
QGridLayout* parentLayout,
int currentRowIndex,
int currentColumn,
int itemColumnSpan,
bool parentRowIsLast )
{
PdmUiGroup* group = static_cast<PdmUiGroup*>( currentItem );
QMinimizePanel* groupBox = findOrCreateGroupBox( containerWidgetWithGridLayout, group, uiConfigName );
int stretch =
recursivelyConfigureAndUpdateUiOrderingInGridLayout( *group, groupBox->contentFrame(), uiConfigName, parentRowIsLast );
/// Insert the group box at the correct position of the parent layout
parentLayout->addWidget( groupBox, currentRowIndex, currentColumn, 1, itemColumnSpan );
return stretch;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool caf::PdmUiFormLayoutObjectEditor::isUiGroupExpanded( const PdmUiGroup* uiGroup ) const
{
if ( uiGroup->hasForcedExpandedState() ) return uiGroup->forcedExpandedState();
auto kwMapPair = m_objectKeywordGroupUiNameExpandedState.find( pdmObject()->xmlCapability()->classKeyword() );
if ( kwMapPair != m_objectKeywordGroupUiNameExpandedState.end() )
{
QString keyword = uiGroup->keyword();
auto uiNameExpStatePair = kwMapPair->second.find( keyword );
if ( uiNameExpStatePair != kwMapPair->second.end() )
{
return uiNameExpStatePair->second;
}
}
return uiGroup->isExpandedByDefault();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QMinimizePanel* caf::PdmUiFormLayoutObjectEditor::findOrCreateGroupBox( QWidget* parent,
PdmUiGroup* group,
const QString& uiConfigName )
{
QString groupBoxKey = group->keyword();
QMinimizePanel* groupBox = nullptr;
// Find or create groupBox
auto it = m_groupBoxes.find( groupBoxKey );
if ( it == m_groupBoxes.end() )
{
auto newBoxIt = m_newGroupBoxes.find( groupBoxKey );
if ( newBoxIt != m_newGroupBoxes.end() )
{
// This error happens when the same group is added to the same parent multiple times
// https://github.com/OPM/ResInsight/issues/11731
// Suggested approach: Make sure that all objects and groups in PdmUiOrdering have unique names.
auto message = "Detected duplicate group box with keyword: " + groupBoxKey;
CAF_PDM_LOG_ERROR( QString( "UI Form Layout Editor: %1. This may cause layout issues. Ensure unique group "
"names in PdmUiOrdering." )
.arg( message ) );
CAF_ASSERT( false && message.toStdString().data() );
}
groupBox = new QMinimizePanel( parent );
groupBox->enableFrame( group->enableFrame() );
groupBox->setTitle( group->uiName( uiConfigName ) );
groupBox->setObjectName( group->keyword() );
connect( groupBox, SIGNAL( expandedChanged( bool ) ), this, SLOT( groupBoxExpandedStateToggled( bool ) ) );
m_newGroupBoxes[groupBoxKey] = groupBox;
}
else
{
groupBox = it->second;
CAF_ASSERT( groupBox );
m_newGroupBoxes[groupBoxKey] = groupBox;
}
QMargins contentMargins;
if ( group->enableFrame() )
{
contentMargins = QMargins( 6, 6, 6, 6 );
}
ensureWidgetContainsEmptyGridLayout( groupBox->contentFrame(), contentMargins );
// Set Expanded state
bool isExpanded = isUiGroupExpanded( group );
groupBox->setExpanded( isExpanded );
// Update the title to be able to support dynamic group names
groupBox->setTitle( group->uiName( uiConfigName ) );
groupBox->updateGeometry();
return groupBox;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QLabel* caf::PdmUiFormLayoutObjectEditor::createLabel( QWidget* parent, const PdmUiLabel& label, const QString& uiConfigName )
{
QLabel* qLabel = new QLabel( parent );
qLabel->setText( label.uiName( uiConfigName ) );
qLabel->setWordWrap( true );
m_labels.push_back( qLabel );
return qLabel;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QPushButton* caf::PdmUiFormLayoutObjectEditor::createButton( QWidget* parent,
const PdmUiButton& button,
const QString& uiConfigName )
{
QPushButton* qButton = new QPushButton( parent );
qButton->setText( button.uiName( uiConfigName ) );
qButton->setEnabled( !button.isUiReadOnly( uiConfigName ) );
qButton->setToolTip( button.uiToolTip( uiConfigName ) );
auto hPolicy = button.fillWidth() ? QSizePolicy::Expanding : QSizePolicy::Maximum;
qButton->setSizePolicy( hPolicy, QSizePolicy::Fixed );
// Set icon if available
auto icon = button.uiIcon( uiConfigName );
if ( icon && !icon->isNull() )
{
qButton->setIcon( *icon );
}
// Connect callback if available
auto callback = button.clickCallback();
if ( callback )
{
QObject::connect( qButton, &QPushButton::clicked, [callback]() { callback(); } );
}
m_buttons.push_back( qButton );
return qButton;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QDialogButtonBox* caf::PdmUiFormLayoutObjectEditor::createButtonBox( QWidget* parent, const PdmUiButtonBox& buttonBox )
{
QDialogButtonBox* qButtonBox = new QDialogButtonBox( parent );
// ActionRole preserves add order (no OS-specific OK/Cancel reordering) while still getting
// QDialogButtonBox's normal leading-stretch-then-packed-buttons layout.
for ( const auto& spec : buttonBox.buttons() )
{
QPushButton* qButton = qButtonBox->addButton( spec.text, QDialogButtonBox::ActionRole );
qButton->setEnabled( spec.enabled );
qButton->setToolTip( spec.toolTip );
auto callback = spec.callback;
if ( callback )
{
QObject::connect( qButton, &QPushButton::clicked, [callback]() { callback(); } );
}
}
m_buttonBoxes.push_back( qButtonBox );
return qButtonBox;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
caf::PdmUiFieldEditorHandle* caf::PdmUiFormLayoutObjectEditor::findOrCreateFieldEditor( QWidget* parent,
PdmUiFieldHandle* field,
const QString& uiConfigName )
{
caf::PdmUiFieldEditorHandle* fieldEditor = nullptr;
std::map<PdmFieldHandle*, PdmUiFieldEditorHandle*>::iterator it = m_fieldViews.find( field->fieldHandle() );
if ( it == m_fieldViews.end() )
{
fieldEditor = PdmUiFieldEditorHelper::createFieldEditorForField( field, uiConfigName );
if ( fieldEditor )
{
m_fieldViews[field->fieldHandle()] = fieldEditor;
fieldEditor->setContainingEditor( this );
fieldEditor->setUiField( field );
fieldEditor->createWidgets( parent );
}
else
{
// This happens if no editor is available for a given field
// If the macro for registering the editor is put as the single statement
// in a cpp file, a dummy static class must be used to make sure the compile unit
// is included
//
// See cafPdmUiCoreColor3f and cafPdmUiCoreVec3d
//
// Default editors are registered in cafPdmUiDefaultObjectEditor.cpp
//
// Exclude childArray and child object fields from this warning. A PdmChildArrayFieldHandle field
// typically use a list or table editor, see RimWellPathGeometryDef::m_wellTargets
bool isChildArrayField = dynamic_cast<PdmChildArrayFieldHandle*>( field->fieldHandle() ) != nullptr;
bool isChildObjectField = dynamic_cast<PdmChildFieldHandle*>( field->fieldHandle() ) != nullptr;
if ( !isChildArrayField && !isChildObjectField )
{
QString fieldTypeName = field ? field->fieldHandle()->keyword() : "unknown";
CAF_PDM_LOG_ERROR(
QString( "UI Form Layout Editor: No field editor available for field type '%1' in config '%2'. "
"Check that the field editor is properly registered." )
.arg( fieldTypeName )
.arg( uiConfigName.isEmpty() ? "default" : uiConfigName ) );
}
}
}
else
{
fieldEditor = it->second;
}
if ( fieldEditor )
{
m_usedFields.insert( field->fieldHandle() );
}
return fieldEditor;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::PdmUiFormLayoutObjectEditor::ensureWidgetContainsEmptyGridLayout( QWidget* containerWidget,
QMargins contentMargins,
int horizontalSpacing )
{
CAF_ASSERT( containerWidget );
QLayout* layout = containerWidget->layout();
if ( layout != nullptr )
{
// Remove all items from the layout, then reparent the layout to a temporary
// This is because you cannot remove a layout from a widget but it gets moved when reparenting.
QLayoutItem* item;
while ( ( item = layout->takeAt( 0 ) ) != 0 )
{
delete item;
}
QWidget().setLayout( layout );
}
QGridLayout* gridLayout = new QGridLayout;
gridLayout->setContentsMargins( contentMargins );
if ( horizontalSpacing >= 0 )
{
gridLayout->setHorizontalSpacing( horizontalSpacing );
}
containerWidget->setLayout( gridLayout );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::PdmUiFormLayoutObjectEditor::groupBoxExpandedStateToggled( bool isExpanded )
{
if ( !this->pdmObject()->xmlCapability() ) return;
QString objKeyword = this->pdmObject()->xmlCapability()->classKeyword();
QMinimizePanel* panel = dynamic_cast<QMinimizePanel*>( this->sender() );
if ( !panel ) return;
m_objectKeywordGroupUiNameExpandedState[objKeyword][panel->objectName()] = isExpanded;
// Required to update the layout when the group box is expanded
// https://github.com/OPM/ResInsight/issues/12119
updateUi();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::PdmUiFormLayoutObjectEditor::cleanupBeforeSettingPdmObject()
{
// Trigger a hide event the editor to allow it to clean up. This is required to persist the changes in
// the editor when changing to a different dock widget. Hide all editors before deleting them.
// https://github.com/OPM/ResInsight/issues/12599
for ( const auto& [fieldHandle, fieldEditor] : m_fieldViews )
{
if ( fieldEditor && fieldEditor->editorWidget() )
{
fieldEditor->editorWidget()->hide();
}
}
// Delete all field editors, make sure this is done after the field editors have been hidden
for ( const auto& [fieldHandle, fieldEditor] : m_fieldViews )
{
if ( fieldEditor )
{
delete fieldEditor;
}
}
m_fieldViews.clear();
m_newGroupBoxes.clear();
std::map<QString, QPointer<QMinimizePanel>>::iterator groupIt;
for ( groupIt = m_groupBoxes.begin(); groupIt != m_groupBoxes.end(); ++groupIt )
{
QMinimizePanel* groupBox = groupIt->second;
if ( groupBox )
{
scheduleGroupBoxDeletion( groupBox );
}
}
m_groupBoxes.clear();
deleteLabelsAndButtons();
// Note: Layouts are now managed by Qt's parent-child ownership system.
// When added to parent layouts via addLayout(), Qt automatically handles cleanup.
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::PdmUiFormLayoutObjectEditor::deleteLabelsAndButtons()
{
for ( auto& label : m_labels )
{
if ( label )
{
label->deleteLater();
}
}
m_labels.clear();
for ( auto& button : m_buttons )
{
if ( button )
{
button->deleteLater();
}
}
m_buttons.clear();
for ( auto& buttonBox : m_buttonBoxes )
{
if ( buttonBox )
{
buttonBox->deleteLater();
}
}
m_buttonBoxes.clear();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::PdmUiFormLayoutObjectEditor::configureAndUpdateUi( const QString& uiConfigName )
{
deleteLabelsAndButtons();
caf::PdmUiOrdering config;
if ( pdmObject() )
{
caf::PdmUiObjectHandle* uiObject = uiObj( pdmObject() );
if ( uiObject )
{
uiObject->uiOrdering( uiConfigName, config );
}
}
// Set all fieldViews to be unvisited
m_usedFields.clear();
// Set all group Boxes to be unvisited
m_newGroupBoxes.clear();
recursivelyConfigureAndUpdateTopLevelUiOrdering( config, uiConfigName );
// Remove all fieldViews not mentioned by the configuration from the layout
std::vector<PdmFieldHandle*> fvhToRemoveFromMap;
for ( auto oldFvIt = m_fieldViews.begin(); oldFvIt != m_fieldViews.end(); ++oldFvIt )
{
if ( m_usedFields.count( oldFvIt->first ) == 0 )
{
// The old field editor is not present anymore, get rid of it
delete oldFvIt->second;
fvhToRemoveFromMap.push_back( oldFvIt->first );
}
}
for ( size_t i = 0; i < fvhToRemoveFromMap.size(); ++i )
{
m_fieldViews.erase( fvhToRemoveFromMap[i] );
}
// Remove all unmentioned group boxes
std::map<QString, QPointer<QMinimizePanel>>::iterator itOld;
std::map<QString, QPointer<QMinimizePanel>>::iterator itNew;
for ( itOld = m_groupBoxes.begin(); itOld != m_groupBoxes.end(); ++itOld )
{
itNew = m_newGroupBoxes.find( itOld->first );
if ( itNew == m_newGroupBoxes.end() )
{
// The old groupBox is not present anymore, get rid of it
if ( !itOld->second.isNull() ) scheduleGroupBoxDeletion( itOld->second );
}
}
m_groupBoxes = m_newGroupBoxes;
// Notify pdm object when widgets have been created
caf::PdmUiObjectHandle* uiObject = uiObj( pdmObject() );
if ( uiObject )
{
uiObject->onEditorWidgetsCreated();
}
}
//--------------------------------------------------------------------------------------------------
/// Unused. Should probably remove
//--------------------------------------------------------------------------------------------------
void caf::PdmUiFormLayoutObjectEditor::recursiveVerifyUniqueNames( const std::vector<PdmUiItem*>& uiItems,
const QString& uiConfigName,
std::set<QString>* fieldKeywordNames,
std::set<QString>* groupNames )
{
for ( size_t i = 0; i < uiItems.size(); ++i )
{
if ( uiItems[i]->isUiGroup() )
{
PdmUiGroup* group = static_cast<PdmUiGroup*>( uiItems[i] );
const std::vector<PdmUiItem*>& groupChildren = group->uiItems();
QString groupBoxKey = group->keyword();
if ( groupNames->find( groupBoxKey ) != groupNames->end() )
{
// It is not supported to have two groups with identical names
CAF_ASSERT( false );
}
else
{
groupNames->insert( groupBoxKey );
}
recursiveVerifyUniqueNames( groupChildren, uiConfigName, fieldKeywordNames, groupNames );
}
else
{
PdmUiFieldHandle* field = dynamic_cast<PdmUiFieldHandle*>( uiItems[i] );
QString fieldKeyword = field->fieldHandle()->keyword();
if ( fieldKeywordNames->find( fieldKeyword ) != fieldKeywordNames->end() )
{
// It is not supported to have two fields with identical names
CAF_ASSERT( false );
}
else
{
fieldKeywordNames->insert( fieldKeyword );
}
}
}
}