mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-24 22:27:55 -05:00
873 lines
38 KiB
C++
873 lines
38 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 );
|
|
|
|
if ( auto callback = label.linkActivatedCallback() )
|
|
{
|
|
qLabel->setTextFormat( Qt::RichText );
|
|
qLabel->setTextInteractionFlags( Qt::LinksAccessibleByMouse );
|
|
QObject::connect( qLabel, &QLabel::linkActivated, qLabel, [callback]( const QString& link ) { callback( link ); } );
|
|
}
|
|
|
|
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() );
|
|
|
|
// The editor's field was deleted and a new field was allocated at the same address
|
|
if ( it != m_fieldViews.end() && it->second->uiField() != field )
|
|
{
|
|
delete it->second;
|
|
m_fieldViews.erase( it );
|
|
it = m_fieldViews.end();
|
|
}
|
|
|
|
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 );
|
|
}
|
|
}
|
|
}
|
|
}
|