QGIS/src/gui/vector/qgsattributesformproperties.cpp
2025-02-12 17:10:56 +02:00

2364 lines
98 KiB
C++

/***************************************************************************
qgsattributesformproperties.cpp
---------------------
begin : August 2017
copyright : (C) 2017 by David Signer
email : david at opengis dot ch
***************************************************************************
* *
* This program 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 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsactionmanager.h"
#include "qgsaddtaborgroup.h"
#include "qgsattributeeditorspacerelement.h"
#include "qgsattributeeditortextelement.h"
#include "qgsattributesformproperties.h"
#include "moc_qgsattributesformproperties.cpp"
#include "qgsattributetypedialog.h"
#include "qgsattributeformcontaineredit.h"
#include "qgsattributewidgetedit.h"
#include "qgsattributesforminitcode.h"
#include "qgsqmlwidgetwrapper.h"
#include "qgshtmlwidgetwrapper.h"
#include "qgsapplication.h"
#include "qgscodeeditor.h"
#include "qgscodeeditorhtml.h"
#include "qgsexpressioncontextutils.h"
#include "qgsattributeeditoraction.h"
#include "qgsattributeeditorfield.h"
#include "qgsattributeeditorcontainer.h"
#include "qgsattributeeditorqmlelement.h"
#include "qgsattributeeditorhtmlelement.h"
#include "qgssettingsregistrycore.h"
#include "qgstextwidgetwrapper.h"
#include "qgsattributeeditorrelation.h"
#include "qgsgui.h"
#include "qgseditorwidgetregistry.h"
#include "qgscodeeditorexpression.h"
#include "qgsfieldcombobox.h"
#include "qgsexpressionfinder.h"
#include "qgsexpressionbuilderdialog.h"
#include "qgshelp.h"
#include "qgsxmlutils.h"
QgsAttributesFormProperties::QgsAttributesFormProperties( QgsVectorLayer *layer, QWidget *parent )
: QWidget( parent )
, mLayer( layer )
{
if ( !layer )
return;
setupUi( this );
mEditorLayoutComboBox->addItem( tr( "Autogenerate" ), QVariant::fromValue( Qgis::AttributeFormLayout::AutoGenerated ) );
mEditorLayoutComboBox->addItem( tr( "Drag and Drop Designer" ), QVariant::fromValue( Qgis::AttributeFormLayout::DragAndDrop ) );
mEditorLayoutComboBox->addItem( tr( "Provide ui-file" ), QVariant::fromValue( Qgis::AttributeFormLayout::UiFile ) );
// available widgets tree
QGridLayout *availableWidgetsWidgetLayout = new QGridLayout;
mAvailableWidgetsTree = new QgsAttributesDnDTree( mLayer );
availableWidgetsWidgetLayout->addWidget( mAvailableWidgetsTree );
availableWidgetsWidgetLayout->setContentsMargins( 0, 0, 0, 0 );
mAvailableWidgetsWidget->setLayout( availableWidgetsWidgetLayout );
mAvailableWidgetsTree->setSelectionMode( QAbstractItemView::SelectionMode::ExtendedSelection );
mAvailableWidgetsTree->setHeaderLabels( QStringList() << tr( "Available Widgets" ) );
mAvailableWidgetsTree->setType( QgsAttributesDnDTree::Type::Drag );
mAvailableWidgetsTree->setContextMenuPolicy( Qt::CustomContextMenu );
// form layout tree
QGridLayout *formLayoutWidgetLayout = new QGridLayout;
mFormLayoutTree = new QgsAttributesDnDTree( mLayer );
mFormLayoutWidget->setLayout( formLayoutWidgetLayout );
formLayoutWidgetLayout->addWidget( mFormLayoutTree );
formLayoutWidgetLayout->setContentsMargins( 0, 0, 0, 0 );
mFormLayoutTree->setHeaderLabels( QStringList() << tr( "Form Layout" ) );
mFormLayoutTree->setType( QgsAttributesDnDTree::Type::Drop );
connect( mAvailableWidgetsTree, &QTreeWidget::itemSelectionChanged, this, &QgsAttributesFormProperties::onAttributeSelectionChanged );
connect( mAvailableWidgetsTree, &QWidget::customContextMenuRequested, this, &QgsAttributesFormProperties::onContextMenuRequested );
connect( mFormLayoutTree, &QTreeWidget::itemSelectionChanged, this, &QgsAttributesFormProperties::onFormLayoutSelectionChanged );
connect( mAddTabOrGroupButton, &QAbstractButton::clicked, this, &QgsAttributesFormProperties::addContainer );
connect( mRemoveTabOrGroupButton, &QAbstractButton::clicked, this, &QgsAttributesFormProperties::removeTabOrGroupButton );
connect( mInvertSelectionButton, &QAbstractButton::clicked, this, &QgsAttributesFormProperties::onInvertSelectionButtonClicked );
connect( mEditorLayoutComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsAttributesFormProperties::mEditorLayoutComboBox_currentIndexChanged );
connect( pbnSelectEditForm, &QToolButton::clicked, this, &QgsAttributesFormProperties::pbnSelectEditForm_clicked );
connect( mTbInitCode, &QPushButton::clicked, this, &QgsAttributesFormProperties::mTbInitCode_clicked );
connect( mLayer, &QgsVectorLayer::updatedFields, this, [this] {
if ( !mBlockUpdates )
updatedFields();
} );
// Context menu and children actions
mAvailableWidgetsTreeContextMenu = new QMenu( this );
mActionCopyWidgetConfiguration = new QAction( tr( "Copy widget configuration" ), this );
mActionPasteWidgetConfiguration = new QAction( tr( "Paste widget configuration" ), this );
connect( mActionCopyWidgetConfiguration, &QAction::triggered, this, &QgsAttributesFormProperties::copyWidgetConfiguration );
connect( mActionPasteWidgetConfiguration, &QAction::triggered, this, &QgsAttributesFormProperties::pasteWidgetConfiguration );
mAvailableWidgetsTreeContextMenu->addAction( mActionCopyWidgetConfiguration );
mAvailableWidgetsTreeContextMenu->addAction( mActionPasteWidgetConfiguration );
mMessageBar = new QgsMessageBar();
mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
gridLayout->addWidget( mMessageBar, 0, 0 );
}
void QgsAttributesFormProperties::init()
{
initAvailableWidgetsTree();
initFormLayoutTree();
initLayoutConfig();
initInitPython();
initSuppressCombo();
}
void QgsAttributesFormProperties::initAvailableWidgetsTree()
{
mAvailableWidgetsTree->clear();
mAvailableWidgetsTree->setSortingEnabled( false );
mAvailableWidgetsTree->setSelectionBehavior( QAbstractItemView::SelectRows );
mAvailableWidgetsTree->setAcceptDrops( false );
mAvailableWidgetsTree->setDragDropMode( QAbstractItemView::DragOnly );
//load Fields
DnDTreeItemData catItemData = DnDTreeItemData( DnDTreeItemData::WidgetType, QStringLiteral( "Fields" ), QStringLiteral( "Fields" ) );
QTreeWidgetItem *catitem = mAvailableWidgetsTree->addItem( mAvailableWidgetsTree->invisibleRootItem(), catItemData );
const QgsFields fields = mLayer->fields();
for ( int i = 0; i < fields.size(); ++i )
{
const QgsField field = fields.at( i );
DnDTreeItemData itemData = DnDTreeItemData( DnDTreeItemData::Field, field.name(), field.name() );
itemData.setShowLabel( true );
FieldConfig cfg( mLayer, i );
QTreeWidgetItem *item = mAvailableWidgetsTree->addItem( catitem, itemData, -1, fields.iconForField( i, true ) );
item->setData( 0, FieldConfigRole, cfg );
item->setData( 0, FieldNameRole, field.name() );
QString tooltip;
if ( !field.alias().isEmpty() )
tooltip = tr( "%1 (%2)" ).arg( field.name(), field.alias() );
else
tooltip = field.name();
item->setToolTip( 0, tooltip );
}
catitem->setExpanded( true );
//load Relations
catItemData = DnDTreeItemData( DnDTreeItemData::WidgetType, QStringLiteral( "Relations" ), tr( "Relations" ) );
catitem = mAvailableWidgetsTree->addItem( mAvailableWidgetsTree->invisibleRootItem(), catItemData );
const QList<QgsRelation> relations = QgsProject::instance()->relationManager()->referencedRelations( mLayer );
for ( const QgsRelation &relation : relations )
{
QString name;
const QgsPolymorphicRelation polymorphicRelation = relation.polymorphicRelation();
if ( polymorphicRelation.isValid() )
{
name = QStringLiteral( "%1 (%2)" ).arg( relation.name(), polymorphicRelation.name() );
}
else
{
name = relation.name();
}
DnDTreeItemData itemData = DnDTreeItemData( DnDTreeItemData::Relation, relation.id(), name );
itemData.setShowLabel( true );
QTreeWidgetItem *item = mAvailableWidgetsTree->addItem( catitem, itemData );
item->setData( 0, FieldNameRole, relation.id() );
}
catitem->setExpanded( true );
// Form actions
catItemData = DnDTreeItemData( DnDTreeItemData::WidgetType, QStringLiteral( "Actions" ), tr( "Actions" ) );
catitem = mAvailableWidgetsTree->addItem( mAvailableWidgetsTree->invisibleRootItem(), catItemData );
const QList<QgsAction> actions { mLayer->actions()->actions() };
for ( const auto &action : std::as_const( actions ) )
{
if ( action.isValid() && action.runable() && ( action.actionScopes().contains( QStringLiteral( "Feature" ) ) || action.actionScopes().contains( QStringLiteral( "Layer" ) ) ) )
{
const QString actionTitle { action.shortTitle().isEmpty() ? action.name() : action.shortTitle() };
DnDTreeItemData itemData = DnDTreeItemData( DnDTreeItemData::Action, action.id().toString(), actionTitle );
itemData.setShowLabel( true );
mAvailableWidgetsTree->addItem( catitem, itemData );
}
}
// QML/HTML widget
catItemData = DnDTreeItemData( DnDTreeItemData::WidgetType, QStringLiteral( "Other" ), tr( "Other Widgets" ) );
catitem = mAvailableWidgetsTree->addItem( mAvailableWidgetsTree->invisibleRootItem(), catItemData );
DnDTreeItemData itemData = DnDTreeItemData( DnDTreeItemData::QmlWidget, QStringLiteral( "QML Widget" ), tr( "QML Widget" ) );
itemData.setShowLabel( true );
mAvailableWidgetsTree->addItem( catitem, itemData );
auto itemDataHtml { DnDTreeItemData( DnDTreeItemData::HtmlWidget, QStringLiteral( "HTML Widget" ), tr( "HTML Widget" ) ) };
itemDataHtml.setShowLabel( true );
mAvailableWidgetsTree->addItem( catitem, itemDataHtml );
auto itemDataText { DnDTreeItemData( DnDTreeItemData::TextWidget, QStringLiteral( "Text Widget" ), tr( "Text Widget" ) ) };
itemDataText.setShowLabel( true );
mAvailableWidgetsTree->addItem( catitem, itemDataText );
auto itemDataSpacer { DnDTreeItemData( DnDTreeItemData::SpacerWidget, QStringLiteral( "Spacer Widget" ), tr( "Spacer Widget" ) ) };
itemDataSpacer.setShowLabel( false );
mAvailableWidgetsTree->addItem( catitem, itemDataSpacer );
catitem->setExpanded( true );
}
void QgsAttributesFormProperties::initFormLayoutTree()
{
// tabs and groups info
mFormLayoutTree->clear();
mFormLayoutTree->setSortingEnabled( false );
mFormLayoutTree->setSelectionBehavior( QAbstractItemView::SelectRows );
mFormLayoutTree->setSelectionMode( QAbstractItemView::SelectionMode::ExtendedSelection );
mFormLayoutTree->setAcceptDrops( true );
mFormLayoutTree->setDragDropMode( QAbstractItemView::DragDrop );
const auto constTabs = mLayer->editFormConfig().tabs();
for ( QgsAttributeEditorElement *wdg : constTabs )
{
loadAttributeEditorTreeItem( wdg, mFormLayoutTree->invisibleRootItem(), mFormLayoutTree );
}
}
void QgsAttributesFormProperties::initSuppressCombo()
{
if ( QgsSettingsRegistryCore::settingsDigitizingDisableEnterAttributeValuesDialog->value() )
{
mFormSuppressCmbBx->addItem( tr( "Hide Form on Add Feature (global settings)" ), QVariant::fromValue( Qgis::AttributeFormSuppression::Default ) );
}
else
{
mFormSuppressCmbBx->addItem( tr( "Show Form on Add Feature (global settings)" ), QVariant::fromValue( Qgis::AttributeFormSuppression::Default ) );
}
mFormSuppressCmbBx->addItem( tr( "Hide Form on Add Feature" ), QVariant::fromValue( Qgis::AttributeFormSuppression::On ) );
mFormSuppressCmbBx->addItem( tr( "Show Form on Add Feature" ), QVariant::fromValue( Qgis::AttributeFormSuppression::Off ) );
mFormSuppressCmbBx->setCurrentIndex( mFormSuppressCmbBx->findData( QVariant::fromValue( mLayer->editFormConfig().suppress() ) ) );
}
QgsExpressionContext QgsAttributesFormProperties::createExpressionContext() const
{
QgsExpressionContext context;
context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) );
return context;
}
void QgsAttributesFormProperties::initLayoutConfig()
{
mEditorLayoutComboBox->setCurrentIndex( mEditorLayoutComboBox->findData( QVariant::fromValue( mLayer->editFormConfig().layout() ) ) );
mEditorLayoutComboBox_currentIndexChanged( mEditorLayoutComboBox->currentIndex() );
const QgsEditFormConfig cfg = mLayer->editFormConfig();
mEditFormLineEdit->setText( cfg.uiForm() );
}
void QgsAttributesFormProperties::initInitPython()
{
const QgsEditFormConfig cfg = mLayer->editFormConfig();
mInitCodeSource = cfg.initCodeSource();
mInitFunction = cfg.initFunction();
mInitFilePath = cfg.initFilePath();
mInitCode = cfg.initCode();
if ( mInitCode.isEmpty() )
{
mInitCode.append( tr( "# -*- coding: utf-8 -*-\n\"\"\"\n"
"QGIS forms can have a Python function that is called when the form is\n"
"opened.\n"
"\n"
"Use this function to add extra logic to your forms.\n"
"\n"
"Enter the name of the function in the \"Python Init function\"\n"
"field.\n"
"An example follows:\n"
"\"\"\"\n"
"from qgis.PyQt.QtWidgets import QWidget\n\n"
"def my_form_open(dialog, layer, feature):\n"
" geom = feature.geometry()\n"
" control = dialog.findChild(QWidget, \"MyLineEdit\")\n" ) );
}
}
void QgsAttributesFormProperties::loadAttributeTypeDialog()
{
if ( mAvailableWidgetsTree->selectedItems().count() != 1 )
return;
QTreeWidgetItem *item = mAvailableWidgetsTree->selectedItems().at( 0 );
const FieldConfig cfg = item->data( 0, FieldConfigRole ).value<FieldConfig>();
const QString fieldName = item->data( 0, FieldNameRole ).toString();
const int index = mLayer->fields().indexOf( fieldName );
if ( index < 0 )
return;
mAttributeTypeDialog = new QgsAttributeTypeDialog( mLayer, index, mAttributeTypeFrame );
loadAttributeTypeDialogFromConfiguration( cfg );
mAttributeTypeDialog->setDefaultValueExpression( mLayer->defaultValueDefinition( index ).expression() );
mAttributeTypeDialog->setApplyDefaultValueOnUpdate( mLayer->defaultValueDefinition( index ).applyOnUpdate() );
mAttributeTypeDialog->layout()->setContentsMargins( 0, 0, 0, 0 );
mAttributeTypeFrame->layout()->setContentsMargins( 0, 0, 0, 0 );
mAttributeTypeFrame->layout()->addWidget( mAttributeTypeDialog );
}
void QgsAttributesFormProperties::loadAttributeTypeDialogFromConfiguration( const FieldConfig &config )
{
const QgsFieldConstraints constraints = config.mFieldConstraints;
mAttributeTypeDialog->setAlias( config.mAlias );
mAttributeTypeDialog->setDataDefinedProperties( config.mDataDefinedProperties );
mAttributeTypeDialog->setComment( config.mComment );
mAttributeTypeDialog->setFieldEditable( config.mEditable );
mAttributeTypeDialog->setLabelOnTop( config.mLabelOnTop );
mAttributeTypeDialog->setReuseLastValues( config.mReuseLastValues );
mAttributeTypeDialog->setNotNull( constraints.constraints() & QgsFieldConstraints::ConstraintNotNull );
mAttributeTypeDialog->setNotNullEnforced( constraints.constraintStrength( QgsFieldConstraints::ConstraintNotNull ) == QgsFieldConstraints::ConstraintStrengthHard );
mAttributeTypeDialog->setUnique( constraints.constraints() & QgsFieldConstraints::ConstraintUnique );
mAttributeTypeDialog->setUniqueEnforced( constraints.constraintStrength( QgsFieldConstraints::ConstraintUnique ) == QgsFieldConstraints::ConstraintStrengthHard );
mAttributeTypeDialog->setSplitPolicy( config.mSplitPolicy );
mAttributeTypeDialog->setDuplicatePolicy( config.mDuplicatePolicy );
mAttributeTypeDialog->setMergePolicy( config.mMergePolicy );
QgsFieldConstraints::Constraints providerConstraints = QgsFieldConstraints::Constraints();
if ( constraints.constraintOrigin( QgsFieldConstraints::ConstraintNotNull ) == QgsFieldConstraints::ConstraintOriginProvider )
providerConstraints |= QgsFieldConstraints::ConstraintNotNull;
if ( constraints.constraintOrigin( QgsFieldConstraints::ConstraintUnique ) == QgsFieldConstraints::ConstraintOriginProvider )
providerConstraints |= QgsFieldConstraints::ConstraintUnique;
if ( constraints.constraintOrigin( QgsFieldConstraints::ConstraintExpression ) == QgsFieldConstraints::ConstraintOriginProvider )
providerConstraints |= QgsFieldConstraints::ConstraintExpression;
mAttributeTypeDialog->setProviderConstraints( providerConstraints );
mAttributeTypeDialog->setConstraintExpression( constraints.constraintExpression() );
mAttributeTypeDialog->setConstraintExpressionDescription( constraints.constraintDescription() );
mAttributeTypeDialog->setConstraintExpressionEnforced( constraints.constraintStrength( QgsFieldConstraints::ConstraintExpression ) == QgsFieldConstraints::ConstraintStrengthHard );
// Make sure the widget is refreshed, even if
// the new widget type matches the current one
mAttributeTypeDialog->setEditorWidgetConfig( config.mEditorWidgetConfig );
mAttributeTypeDialog->setEditorWidgetType( config.mEditorWidgetType, true );
}
void QgsAttributesFormProperties::storeAttributeTypeDialog()
{
if ( !mAttributeTypeDialog )
return;
if ( mAttributeTypeDialog->fieldIdx() < 0 || mAttributeTypeDialog->fieldIdx() >= mLayer->fields().count() )
return;
FieldConfig cfg;
cfg.mComment = mLayer->fields().at( mAttributeTypeDialog->fieldIdx() ).comment();
cfg.mEditable = mAttributeTypeDialog->fieldEditable();
cfg.mLabelOnTop = mAttributeTypeDialog->labelOnTop();
cfg.mReuseLastValues = mAttributeTypeDialog->reuseLastValues();
cfg.mAlias = mAttributeTypeDialog->alias();
cfg.mDataDefinedProperties = mAttributeTypeDialog->dataDefinedProperties();
QgsFieldConstraints constraints;
if ( mAttributeTypeDialog->notNull() )
{
constraints.setConstraint( QgsFieldConstraints::ConstraintNotNull );
}
else if ( mAttributeTypeDialog->notNullFromProvider() )
{
constraints.setConstraint( QgsFieldConstraints::ConstraintNotNull, QgsFieldConstraints::ConstraintOriginProvider );
}
if ( mAttributeTypeDialog->unique() )
{
constraints.setConstraint( QgsFieldConstraints::ConstraintUnique );
}
else if ( mAttributeTypeDialog->uniqueFromProvider() )
{
constraints.setConstraint( QgsFieldConstraints::ConstraintUnique, QgsFieldConstraints::ConstraintOriginProvider );
}
if ( !mAttributeTypeDialog->constraintExpression().isEmpty() )
{
constraints.setConstraint( QgsFieldConstraints::ConstraintExpression );
}
constraints.setConstraintExpression( mAttributeTypeDialog->constraintExpression(), mAttributeTypeDialog->constraintExpressionDescription() );
constraints.setConstraintStrength( QgsFieldConstraints::ConstraintNotNull, mAttributeTypeDialog->notNullEnforced() ? QgsFieldConstraints::ConstraintStrengthHard : QgsFieldConstraints::ConstraintStrengthSoft );
constraints.setConstraintStrength( QgsFieldConstraints::ConstraintUnique, mAttributeTypeDialog->uniqueEnforced() ? QgsFieldConstraints::ConstraintStrengthHard : QgsFieldConstraints::ConstraintStrengthSoft );
constraints.setConstraintStrength( QgsFieldConstraints::ConstraintExpression, mAttributeTypeDialog->constraintExpressionEnforced() ? QgsFieldConstraints::ConstraintStrengthHard : QgsFieldConstraints::ConstraintStrengthSoft );
// The call to mLayer->setDefaultValueDefinition will possibly emit updatedFields
// which will set mAttributeTypeDialog to nullptr so we need to store any value before calling it
cfg.mFieldConstraints = constraints;
cfg.mEditorWidgetType = mAttributeTypeDialog->editorWidgetType();
cfg.mEditorWidgetConfig = mAttributeTypeDialog->editorWidgetConfig();
cfg.mSplitPolicy = mAttributeTypeDialog->splitPolicy();
cfg.mDuplicatePolicy = mAttributeTypeDialog->duplicatePolicy();
cfg.mMergePolicy = mAttributeTypeDialog->mergePolicy();
const int fieldIndex = mAttributeTypeDialog->fieldIdx();
mLayer->setDefaultValueDefinition( fieldIndex, QgsDefaultValue( mAttributeTypeDialog->defaultValueExpression(), mAttributeTypeDialog->applyDefaultValueOnUpdate() ) );
const QString fieldName = mLayer->fields().at( fieldIndex ).name();
for ( auto itemIt = QTreeWidgetItemIterator( mAvailableWidgetsTree ); *itemIt; ++itemIt )
{
QTreeWidgetItem *item = *itemIt;
if ( item->data( 0, FieldNameRole ).toString() == fieldName )
item->setData( 0, FieldConfigRole, QVariant::fromValue<FieldConfig>( cfg ) );
}
}
void QgsAttributesFormProperties::storeAttributeWidgetEdit()
{
if ( !mAttributeWidgetEdit )
return;
mAttributeWidgetEdit->updateItemData();
}
void QgsAttributesFormProperties::loadAttributeWidgetEdit()
{
if ( mFormLayoutTree->selectedItems().count() != 1 )
return;
QTreeWidgetItem *currentItem = mFormLayoutTree->selectedItems().at( 0 );
mAttributeWidgetEdit = new QgsAttributeWidgetEdit( currentItem, this );
mAttributeTypeFrame->layout()->setContentsMargins( 0, 0, 0, 0 );
mAttributeTypeFrame->layout()->addWidget( mAttributeWidgetEdit );
}
void QgsAttributesFormProperties::loadInfoWidget( const QString &infoText )
{
mInfoTextWidget = new QLabel( infoText );
mAttributeTypeFrame->layout()->setContentsMargins( 0, 0, 0, 0 );
mAttributeTypeFrame->layout()->addWidget( mInfoTextWidget );
}
void QgsAttributesFormProperties::storeAttributeContainerEdit()
{
if ( !mAttributeContainerEdit )
return;
mAttributeContainerEdit->updateItemData();
}
void QgsAttributesFormProperties::loadAttributeContainerEdit()
{
if ( mFormLayoutTree->selectedItems().count() != 1 )
return;
QTreeWidgetItem *currentItem = mFormLayoutTree->selectedItems().at( 0 );
mAttributeContainerEdit = new QgsAttributeFormContainerEdit( currentItem, mLayer, this );
mAttributeContainerEdit->registerExpressionContextGenerator( this );
mAttributeContainerEdit->layout()->setContentsMargins( 0, 0, 0, 0 );
mAttributeTypeFrame->layout()->setContentsMargins( 0, 0, 0, 0 );
mAttributeTypeFrame->layout()->addWidget( mAttributeContainerEdit );
}
QTreeWidgetItem *QgsAttributesFormProperties::loadAttributeEditorTreeItem( QgsAttributeEditorElement *const widgetDef, QTreeWidgetItem *parent, QgsAttributesDnDTree *tree )
{
auto setCommonProperties = [widgetDef]( DnDTreeItemData &itemData ) {
itemData.setShowLabel( widgetDef->showLabel() );
itemData.setLabelStyle( widgetDef->labelStyle() );
itemData.setHorizontalStretch( widgetDef->horizontalStretch() );
itemData.setVerticalStretch( widgetDef->verticalStretch() );
};
QTreeWidgetItem *newWidget = nullptr;
switch ( widgetDef->type() )
{
case Qgis::AttributeEditorType::Field:
{
DnDTreeItemData itemData = DnDTreeItemData( DnDTreeItemData::Field, widgetDef->name(), widgetDef->name() );
setCommonProperties( itemData );
newWidget = tree->addItem( parent, itemData );
break;
}
case Qgis::AttributeEditorType::Action:
{
const QgsAttributeEditorAction *actionEditor = static_cast<const QgsAttributeEditorAction *>( widgetDef );
const QgsAction action { actionEditor->action( mLayer ) };
if ( action.isValid() )
{
DnDTreeItemData itemData = DnDTreeItemData( DnDTreeItemData::Action, action.id().toString(), action.shortTitle().isEmpty() ? action.name() : action.shortTitle() );
setCommonProperties( itemData );
newWidget = tree->addItem( parent, itemData );
}
else
{
QgsDebugError( QStringLiteral( "Invalid form action" ) );
}
break;
}
case Qgis::AttributeEditorType::Relation:
{
const QgsAttributeEditorRelation *relationEditor = static_cast<const QgsAttributeEditorRelation *>( widgetDef );
DnDTreeItemData itemData = DnDTreeItemData( DnDTreeItemData::Relation, relationEditor->relation().id(), relationEditor->relation().name() );
setCommonProperties( itemData );
RelationEditorConfiguration relEdConfig;
// relEdConfig.buttons = relationEditor->visibleButtons();
relEdConfig.mRelationWidgetType = relationEditor->relationWidgetTypeId();
relEdConfig.mRelationWidgetConfig = relationEditor->relationEditorConfiguration();
relEdConfig.nmRelationId = relationEditor->nmRelationId();
relEdConfig.forceSuppressFormPopup = relationEditor->forceSuppressFormPopup();
relEdConfig.label = relationEditor->label();
itemData.setRelationEditorConfiguration( relEdConfig );
newWidget = tree->addItem( parent, itemData );
break;
}
case Qgis::AttributeEditorType::Container:
{
DnDTreeItemData itemData( DnDTreeItemData::Container, widgetDef->name(), widgetDef->name() );
const QgsAttributeEditorContainer *container = static_cast<const QgsAttributeEditorContainer *>( widgetDef );
if ( !container )
break;
itemData.setColumnCount( container->columnCount() );
itemData.setContainerType( container->type() );
itemData.setBackgroundColor( container->backgroundColor() );
itemData.setVisibilityExpression( container->visibilityExpression() );
itemData.setCollapsedExpression( container->collapsedExpression() );
itemData.setCollapsed( container->collapsed() );
setCommonProperties( itemData );
newWidget = tree->addItem( parent, itemData );
const QList<QgsAttributeEditorElement *> children = container->children();
for ( QgsAttributeEditorElement *wdg : children )
{
loadAttributeEditorTreeItem( wdg, newWidget, tree );
}
break;
}
case Qgis::AttributeEditorType::QmlElement:
{
const QgsAttributeEditorQmlElement *qmlElementEditor = static_cast<const QgsAttributeEditorQmlElement *>( widgetDef );
DnDTreeItemData itemData = DnDTreeItemData( DnDTreeItemData::QmlWidget, widgetDef->name(), widgetDef->name() );
QmlElementEditorConfiguration qmlEdConfig;
qmlEdConfig.qmlCode = qmlElementEditor->qmlCode();
itemData.setQmlElementEditorConfiguration( qmlEdConfig );
setCommonProperties( itemData );
newWidget = tree->addItem( parent, itemData );
break;
}
case Qgis::AttributeEditorType::HtmlElement:
{
const QgsAttributeEditorHtmlElement *htmlElementEditor = static_cast<const QgsAttributeEditorHtmlElement *>( widgetDef );
DnDTreeItemData itemData = DnDTreeItemData( DnDTreeItemData::HtmlWidget, widgetDef->name(), widgetDef->name() );
HtmlElementEditorConfiguration htmlEdConfig;
htmlEdConfig.htmlCode = htmlElementEditor->htmlCode();
itemData.setHtmlElementEditorConfiguration( htmlEdConfig );
setCommonProperties( itemData );
newWidget = tree->addItem( parent, itemData );
break;
}
case Qgis::AttributeEditorType::TextElement:
{
const QgsAttributeEditorTextElement *textElementEditor = static_cast<const QgsAttributeEditorTextElement *>( widgetDef );
DnDTreeItemData itemData = DnDTreeItemData( DnDTreeItemData::TextWidget, widgetDef->name(), widgetDef->name() );
TextElementEditorConfiguration textEdConfig;
textEdConfig.text = textElementEditor->text();
itemData.setTextElementEditorConfiguration( textEdConfig );
setCommonProperties( itemData );
newWidget = tree->addItem( parent, itemData );
break;
}
case Qgis::AttributeEditorType::SpacerElement:
{
const QgsAttributeEditorSpacerElement *spacerElementEditor = static_cast<const QgsAttributeEditorSpacerElement *>( widgetDef );
DnDTreeItemData itemData = DnDTreeItemData( DnDTreeItemData::SpacerWidget, widgetDef->name(), widgetDef->name() );
SpacerElementEditorConfiguration spacerEdConfig;
spacerEdConfig.drawLine = spacerElementEditor->drawLine();
itemData.setSpacerElementEditorConfiguration( spacerEdConfig );
setCommonProperties( itemData );
itemData.setShowLabel( false );
newWidget = tree->addItem( parent, itemData );
break;
}
case Qgis::AttributeEditorType::Invalid:
{
QgsDebugError( QStringLiteral( "Not loading invalid attribute editor type..." ) );
break;
}
}
if ( newWidget )
newWidget->setExpanded( true );
return newWidget;
}
void QgsAttributesFormProperties::onAttributeSelectionChanged()
{
disconnect( mFormLayoutTree, &QTreeWidget::itemSelectionChanged, this, &QgsAttributesFormProperties::onFormLayoutSelectionChanged );
loadAttributeSpecificEditor( mAvailableWidgetsTree, mFormLayoutTree );
connect( mFormLayoutTree, &QTreeWidget::itemSelectionChanged, this, &QgsAttributesFormProperties::onFormLayoutSelectionChanged );
}
void QgsAttributesFormProperties::onFormLayoutSelectionChanged()
{
// when the selection changes in the DnD layout, sync the main tree
disconnect( mAvailableWidgetsTree, &QTreeWidget::itemSelectionChanged, this, &QgsAttributesFormProperties::onAttributeSelectionChanged );
loadAttributeSpecificEditor( mFormLayoutTree, mAvailableWidgetsTree );
connect( mAvailableWidgetsTree, &QTreeWidget::itemSelectionChanged, this, &QgsAttributesFormProperties::onAttributeSelectionChanged );
}
void QgsAttributesFormProperties::loadAttributeSpecificEditor( QgsAttributesDnDTree *emitter, QgsAttributesDnDTree *receiver )
{
const Qgis::AttributeFormLayout layout = mEditorLayoutComboBox->currentData().value<Qgis::AttributeFormLayout>();
if ( layout == Qgis::AttributeFormLayout::DragAndDrop )
{
storeAttributeWidgetEdit();
}
if ( mAttributeTypeDialog )
{
storeAttributeTypeDialog();
}
clearAttributeTypeFrame();
if ( emitter->selectedItems().count() != 1 )
{
receiver->clearSelection();
}
else
{
const DnDTreeItemData itemData = emitter->selectedItems().at( 0 )->data( 0, DnDTreeRole ).value<DnDTreeItemData>();
switch ( itemData.type() )
{
case DnDTreeItemData::Relation:
{
receiver->selectFirstMatchingItem( itemData );
if ( layout == Qgis::AttributeFormLayout::DragAndDrop )
{
loadAttributeWidgetEdit();
}
else
{
loadInfoWidget( tr( "This configuration is available in the Drag and Drop Designer" ) );
}
break;
}
case DnDTreeItemData::Field:
{
receiver->selectFirstMatchingItem( itemData );
if ( layout == Qgis::AttributeFormLayout::DragAndDrop )
{
loadAttributeWidgetEdit();
}
loadAttributeTypeDialog();
break;
}
case DnDTreeItemData::Container:
{
receiver->clearSelection();
loadAttributeContainerEdit();
break;
}
case DnDTreeItemData::Action:
{
receiver->selectFirstMatchingItem( itemData );
const QgsAction action { mLayer->actions()->action( itemData.name() ) };
loadInfoWidget( action.html() );
break;
}
case DnDTreeItemData::QmlWidget:
case DnDTreeItemData::HtmlWidget:
case DnDTreeItemData::TextWidget:
case DnDTreeItemData::SpacerWidget:
{
if ( layout != Qgis::AttributeFormLayout::DragAndDrop )
{
loadInfoWidget( tr( "This configuration is available with double-click in the Drag and Drop Designer" ) );
}
else
{
loadInfoWidget( tr( "This configuration is available with double-click" ) );
}
receiver->clearSelection();
break;
}
case DnDTreeItemData::WidgetType:
{
receiver->clearSelection();
break;
}
}
}
}
void QgsAttributesFormProperties::clearAttributeTypeFrame()
{
if ( mAttributeWidgetEdit )
{
mAttributeTypeFrame->layout()->removeWidget( mAttributeWidgetEdit );
mAttributeWidgetEdit->deleteLater();
mAttributeWidgetEdit = nullptr;
}
if ( mAttributeTypeDialog )
{
mAttributeTypeFrame->layout()->removeWidget( mAttributeTypeDialog );
mAttributeTypeDialog->deleteLater();
mAttributeTypeDialog = nullptr;
}
if ( mAttributeContainerEdit )
{
mAttributeTypeFrame->layout()->removeWidget( mAttributeContainerEdit );
mAttributeContainerEdit->deleteLater();
mAttributeContainerEdit = nullptr;
}
if ( mInfoTextWidget )
{
mAttributeTypeFrame->layout()->removeWidget( mInfoTextWidget );
mInfoTextWidget->deleteLater();
mInfoTextWidget = nullptr;
}
}
void QgsAttributesFormProperties::onInvertSelectionButtonClicked( bool checked )
{
Q_UNUSED( checked )
const auto selectedItemList { mFormLayoutTree->selectedItems() };
const auto rootItem { mFormLayoutTree->invisibleRootItem() };
for ( int i = 0; i < rootItem->childCount(); ++i )
{
rootItem->child( i )->setSelected( !selectedItemList.contains( rootItem->child( i ) ) );
}
}
void QgsAttributesFormProperties::addContainer()
{
QList<QgsAddAttributeFormContainerDialog::ContainerPair> existingContainerList;
for ( QTreeWidgetItemIterator it( mFormLayoutTree ); *it; ++it )
{
const DnDTreeItemData itemData = ( *it )->data( 0, DnDTreeRole ).value<DnDTreeItemData>();
if ( itemData.type() == DnDTreeItemData::Container )
{
existingContainerList.append( QgsAddAttributeFormContainerDialog::ContainerPair( itemData.name(), *it ) );
}
}
QTreeWidgetItem *currentItem = mFormLayoutTree->selectedItems().value( 0 );
QgsAddAttributeFormContainerDialog dialog( mLayer, existingContainerList, currentItem, this );
if ( !dialog.exec() )
return;
const QString name = dialog.name();
QTreeWidgetItem *parentContainerItem = dialog.parentContainerItem();
mFormLayoutTree->addContainer( parentContainerItem ? parentContainerItem : mFormLayoutTree->invisibleRootItem(), name, dialog.columnCount(), dialog.containerType() );
}
void QgsAttributesFormProperties::removeTabOrGroupButton()
{
// deleting an item may delete any number of nested child items -- so we delete
// them one at a time and then see if there's any selection left
while ( true )
{
const QList<QTreeWidgetItem *> items = mFormLayoutTree->selectedItems();
if ( items.empty() )
break;
delete items.at( 0 );
}
}
QgsAttributeEditorElement *QgsAttributesFormProperties::createAttributeEditorWidget( QTreeWidgetItem *item, QgsAttributeEditorElement *parent, bool isTopLevel )
{
QgsAttributeEditorElement *widgetDef = nullptr;
const DnDTreeItemData itemData = item->data( 0, DnDTreeRole ).value<DnDTreeItemData>();
switch ( itemData.type() )
{
//indexed here?
case DnDTreeItemData::Field:
{
const int idx = mLayer->fields().lookupField( itemData.name() );
widgetDef = new QgsAttributeEditorField( itemData.name(), idx, parent );
break;
}
case DnDTreeItemData::Action:
{
const QgsAction action { mLayer->actions()->action( itemData.name() ) };
widgetDef = new QgsAttributeEditorAction( action, parent );
break;
}
case DnDTreeItemData::Relation:
{
const QgsRelation relation = QgsProject::instance()->relationManager()->relation( itemData.name() );
QgsAttributeEditorRelation *relDef = new QgsAttributeEditorRelation( relation, parent );
const QgsAttributesFormProperties::RelationEditorConfiguration relationEditorConfig = itemData.relationEditorConfiguration();
relDef->setRelationWidgetTypeId( relationEditorConfig.mRelationWidgetType );
relDef->setRelationEditorConfiguration( relationEditorConfig.mRelationWidgetConfig );
relDef->setNmRelationId( relationEditorConfig.nmRelationId );
relDef->setForceSuppressFormPopup( relationEditorConfig.forceSuppressFormPopup );
relDef->setLabel( relationEditorConfig.label );
widgetDef = relDef;
break;
}
case DnDTreeItemData::Container:
{
QgsAttributeEditorContainer *container = new QgsAttributeEditorContainer( item->text( 0 ), parent, itemData.backgroundColor() );
container->setColumnCount( itemData.columnCount() );
// only top-level containers can be tabs
Qgis::AttributeEditorContainerType type = itemData.containerType();
if ( type == Qgis::AttributeEditorContainerType::Tab && !isTopLevel )
{
// a top container found which isn't at the top level -- reset it to a group box instead
type = Qgis::AttributeEditorContainerType::GroupBox;
}
container->setType( type );
container->setCollapsed( itemData.collapsed() );
container->setCollapsedExpression( itemData.collapsedExpression() );
container->setVisibilityExpression( itemData.visibilityExpression() );
container->setBackgroundColor( itemData.backgroundColor() );
for ( int t = 0; t < item->childCount(); t++ )
{
QgsAttributeEditorElement *element { createAttributeEditorWidget( item->child( t ), container, false ) };
if ( element )
container->addChildElement( element );
}
widgetDef = container;
break;
}
case DnDTreeItemData::QmlWidget:
{
QgsAttributeEditorQmlElement *element = new QgsAttributeEditorQmlElement( item->text( 0 ), parent );
element->setQmlCode( itemData.qmlElementEditorConfiguration().qmlCode );
widgetDef = element;
break;
}
case DnDTreeItemData::HtmlWidget:
{
QgsAttributeEditorHtmlElement *element = new QgsAttributeEditorHtmlElement( item->text( 0 ), parent );
element->setHtmlCode( itemData.htmlElementEditorConfiguration().htmlCode );
widgetDef = element;
break;
}
case DnDTreeItemData::TextWidget:
{
QgsAttributeEditorTextElement *element = new QgsAttributeEditorTextElement( item->text( 0 ), parent );
element->setText( itemData.textElementEditorConfiguration().text );
widgetDef = element;
break;
}
case DnDTreeItemData::SpacerWidget:
{
QgsAttributeEditorSpacerElement *element = new QgsAttributeEditorSpacerElement( item->text( 0 ), parent );
element->setDrawLine( itemData.spacerElementEditorConfiguration().drawLine );
widgetDef = element;
break;
}
case DnDTreeItemData::WidgetType:
break;
}
if ( widgetDef )
{
widgetDef->setShowLabel( itemData.showLabel() );
widgetDef->setLabelStyle( itemData.labelStyle() );
widgetDef->setHorizontalStretch( itemData.horizontalStretch() );
widgetDef->setVerticalStretch( itemData.verticalStretch() );
}
return widgetDef;
}
void QgsAttributesFormProperties::mEditorLayoutComboBox_currentIndexChanged( int )
{
const Qgis::AttributeFormLayout layout = mEditorLayoutComboBox->currentData().value<Qgis::AttributeFormLayout>();
switch ( layout )
{
case Qgis::AttributeFormLayout::AutoGenerated:
mFormLayoutWidget->setVisible( false );
mUiFileFrame->setVisible( false );
mAddTabOrGroupButton->setVisible( false );
mRemoveTabOrGroupButton->setVisible( false );
mInvertSelectionButton->setVisible( false );
break;
case Qgis::AttributeFormLayout::DragAndDrop:
mFormLayoutWidget->setVisible( true );
mUiFileFrame->setVisible( false );
mAddTabOrGroupButton->setVisible( true );
mRemoveTabOrGroupButton->setVisible( true );
mInvertSelectionButton->setVisible( true );
break;
case Qgis::AttributeFormLayout::UiFile:
// ui file
mFormLayoutWidget->setVisible( false );
mUiFileFrame->setVisible( true );
mAddTabOrGroupButton->setVisible( false );
mRemoveTabOrGroupButton->setVisible( false );
mInvertSelectionButton->setVisible( false );
break;
}
}
void QgsAttributesFormProperties::mTbInitCode_clicked()
{
QgsAttributesFormInitCode attributesFormInitCode;
attributesFormInitCode.setCodeSource( mInitCodeSource );
attributesFormInitCode.setInitCode( mInitCode );
attributesFormInitCode.setInitFilePath( mInitFilePath );
attributesFormInitCode.setInitFunction( mInitFunction );
if ( !attributesFormInitCode.exec() )
return;
mInitCodeSource = attributesFormInitCode.codeSource();
mInitCode = attributesFormInitCode.initCode();
mInitFilePath = attributesFormInitCode.initFilePath();
mInitFunction = attributesFormInitCode.initFunction();
}
void QgsAttributesFormProperties::pbnSelectEditForm_clicked()
{
QgsSettings myQSettings;
const QString lastUsedDir = myQSettings.value( QStringLiteral( "style/lastUIDir" ), QDir::homePath() ).toString();
const QString uifilename = QFileDialog::getOpenFileName( this, tr( "Select edit form" ), lastUsedDir, tr( "UI file" ) + " (*.ui)" );
if ( uifilename.isNull() )
return;
const QFileInfo fi( uifilename );
myQSettings.setValue( QStringLiteral( "style/lastUIDir" ), fi.path() );
mEditFormLineEdit->setText( uifilename );
}
void QgsAttributesFormProperties::store()
{
storeAttributeWidgetEdit();
storeAttributeContainerEdit();
storeAttributeTypeDialog();
}
void QgsAttributesFormProperties::apply()
{
mBlockUpdates++;
storeAttributeWidgetEdit();
storeAttributeContainerEdit();
storeAttributeTypeDialog();
QgsEditFormConfig editFormConfig = mLayer->editFormConfig();
QTreeWidgetItem *fieldContainer = mAvailableWidgetsTree->invisibleRootItem()->child( 0 );
for ( int i = 0; i < fieldContainer->childCount(); i++ )
{
QTreeWidgetItem *fieldItem = fieldContainer->child( i );
const FieldConfig cfg = fieldItem->data( 0, FieldConfigRole ).value<FieldConfig>();
const QString fieldName { fieldItem->data( 0, FieldNameRole ).toString() };
const int idx = mLayer->fields().indexOf( fieldName );
//continue in case field does not exist anymore
if ( idx < 0 )
continue;
editFormConfig.setReadOnly( idx, !cfg.mEditable );
editFormConfig.setLabelOnTop( idx, cfg.mLabelOnTop );
editFormConfig.setReuseLastValue( idx, cfg.mReuseLastValues );
if ( cfg.mDataDefinedProperties.count() > 0 )
{
editFormConfig.setDataDefinedFieldProperties( fieldName, cfg.mDataDefinedProperties );
}
mLayer->setEditorWidgetSetup( idx, QgsEditorWidgetSetup( cfg.mEditorWidgetType, cfg.mEditorWidgetConfig ) );
const QgsFieldConstraints constraints = cfg.mFieldConstraints;
mLayer->setConstraintExpression( idx, constraints.constraintExpression(), constraints.constraintDescription() );
if ( constraints.constraints() & QgsFieldConstraints::ConstraintNotNull )
{
mLayer->setFieldConstraint( idx, QgsFieldConstraints::ConstraintNotNull, constraints.constraintStrength( QgsFieldConstraints::ConstraintNotNull ) );
}
else
{
mLayer->removeFieldConstraint( idx, QgsFieldConstraints::ConstraintNotNull );
}
if ( constraints.constraints() & QgsFieldConstraints::ConstraintUnique )
{
mLayer->setFieldConstraint( idx, QgsFieldConstraints::ConstraintUnique, constraints.constraintStrength( QgsFieldConstraints::ConstraintUnique ) );
}
else
{
mLayer->removeFieldConstraint( idx, QgsFieldConstraints::ConstraintUnique );
}
if ( constraints.constraints() & QgsFieldConstraints::ConstraintExpression )
{
mLayer->setFieldConstraint( idx, QgsFieldConstraints::ConstraintExpression, constraints.constraintStrength( QgsFieldConstraints::ConstraintExpression ) );
}
else
{
mLayer->removeFieldConstraint( idx, QgsFieldConstraints::ConstraintExpression );
}
mLayer->setFieldAlias( idx, cfg.mAlias );
mLayer->setFieldSplitPolicy( idx, cfg.mSplitPolicy );
mLayer->setFieldDuplicatePolicy( idx, cfg.mDuplicatePolicy );
mLayer->setFieldMergePolicy( idx, cfg.mMergePolicy );
}
// tabs and groups
editFormConfig.clearTabs();
for ( int t = 0; t < mFormLayoutTree->invisibleRootItem()->childCount(); t++ )
{
QTreeWidgetItem *tabItem = mFormLayoutTree->invisibleRootItem()->child( t );
QgsAttributeEditorElement *editorElement { createAttributeEditorWidget( tabItem, nullptr, true ) };
if ( editorElement )
editFormConfig.addTab( editorElement );
}
editFormConfig.setUiForm( mEditFormLineEdit->text() );
editFormConfig.setLayout( mEditorLayoutComboBox->currentData().value<Qgis::AttributeFormLayout>() );
editFormConfig.setInitCodeSource( mInitCodeSource );
editFormConfig.setInitFunction( mInitFunction );
editFormConfig.setInitFilePath( mInitFilePath );
editFormConfig.setInitCode( mInitCode );
editFormConfig.setSuppress( mFormSuppressCmbBx->currentData().value<Qgis::AttributeFormSuppression>() );
// write the legacy config of relation widgets to support settings read by the API
QTreeWidgetItem *relationContainer = mAvailableWidgetsTree->invisibleRootItem()->child( 1 );
for ( int i = 0; i < relationContainer->childCount(); i++ )
{
QTreeWidgetItem *relationItem = relationContainer->child( i );
const DnDTreeItemData itemData = relationItem->data( 0, DnDTreeRole ).value<DnDTreeItemData>();
for ( int t = 0; t < mFormLayoutTree->invisibleRootItem()->childCount(); t++ )
{
QTreeWidgetItem *tabItem = mFormLayoutTree->invisibleRootItem()->child( t );
const DnDTreeItemData tabItemData = tabItem->data( 0, DnDTreeRole ).value<DnDTreeItemData>();
if ( tabItemData.type() == itemData.type() && tabItemData.name() == itemData.name() )
{
QVariantMap cfg;
cfg[QStringLiteral( "nm-rel" )] = tabItemData.relationEditorConfiguration().nmRelationId;
cfg[QStringLiteral( "force-suppress-popup" )] = tabItemData.relationEditorConfiguration().forceSuppressFormPopup;
editFormConfig.setWidgetConfig( tabItemData.name(), cfg );
break;
}
}
}
mLayer->setEditFormConfig( editFormConfig );
mBlockUpdates--;
}
/*
* FieldConfig implementation
*/
QgsAttributesFormProperties::FieldConfig::FieldConfig( QgsVectorLayer *layer, int idx )
{
mAlias = layer->fields().at( idx ).alias();
mDataDefinedProperties = layer->editFormConfig().dataDefinedFieldProperties( layer->fields().at( idx ).name() );
mComment = layer->fields().at( idx ).comment();
mEditable = !layer->editFormConfig().readOnly( idx );
mLabelOnTop = layer->editFormConfig().labelOnTop( idx );
mReuseLastValues = layer->editFormConfig().reuseLastValue( idx );
mFieldConstraints = layer->fields().at( idx ).constraints();
const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( layer, layer->fields().field( idx ).name() );
mEditorWidgetType = setup.type();
mEditorWidgetConfig = setup.config();
mSplitPolicy = layer->fields().at( idx ).splitPolicy();
mDuplicatePolicy = layer->fields().at( idx ).duplicatePolicy();
mMergePolicy = layer->fields().at( idx ).mergePolicy();
}
QgsAttributesFormProperties::FieldConfig::operator QVariant()
{
return QVariant::fromValue<QgsAttributesFormProperties::FieldConfig>( *this );
}
/*
* RelationEditorConfiguration implementation
*/
QgsAttributesFormProperties::RelationEditorConfiguration::operator QVariant()
{
return QVariant::fromValue<QgsAttributesFormProperties::RelationEditorConfiguration>( *this );
}
/*
* DnDTree implementation
*/
QTreeWidgetItem *QgsAttributesDnDTree::addContainer( QTreeWidgetItem *parent, const QString &title, int columnCount, Qgis::AttributeEditorContainerType type )
{
QTreeWidgetItem *newItem = new QTreeWidgetItem( QStringList() << title );
newItem->setBackground( 0, QBrush( Qt::lightGray ) );
newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled );
QgsAttributesFormProperties::DnDTreeItemData itemData( QgsAttributesFormProperties::DnDTreeItemData::Container, title, title );
itemData.setColumnCount( columnCount );
itemData.setContainerType( !parent ? Qgis::AttributeEditorContainerType::Tab : type );
newItem->setData( 0, QgsAttributesFormProperties::DnDTreeRole, itemData );
parent->addChild( newItem );
newItem->setExpanded( true );
return newItem;
}
QgsAttributesDnDTree::QgsAttributesDnDTree( QgsVectorLayer *layer, QWidget *parent )
: QTreeWidget( parent )
, mLayer( layer )
{
connect( this, &QTreeWidget::itemDoubleClicked, this, &QgsAttributesDnDTree::onItemDoubleClicked );
}
QTreeWidgetItem *QgsAttributesDnDTree::addItem( QTreeWidgetItem *parent, const QgsAttributesFormProperties::DnDTreeItemData &data, int index, const QIcon &icon )
{
QTreeWidgetItem *newItem = new QTreeWidgetItem( QStringList() << data.name() );
switch ( data.type() )
{
case QgsAttributesFormProperties::DnDTreeItemData::Action:
case QgsAttributesFormProperties::DnDTreeItemData::Field:
case QgsAttributesFormProperties::DnDTreeItemData::Relation:
case QgsAttributesFormProperties::DnDTreeItemData::QmlWidget:
case QgsAttributesFormProperties::DnDTreeItemData::HtmlWidget:
case QgsAttributesFormProperties::DnDTreeItemData::TextWidget:
case QgsAttributesFormProperties::DnDTreeItemData::SpacerWidget:
newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled );
break;
case QgsAttributesFormProperties::DnDTreeItemData::WidgetType:
case QgsAttributesFormProperties::DnDTreeItemData::Container:
{
newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled );
newItem->setBackground( 0, QBrush( Qt::lightGray ) );
}
break;
}
newItem->setData( 0, QgsAttributesFormProperties::DnDTreeRole, QVariant::fromValue( data ) );
newItem->setText( 0, data.displayName() );
newItem->setIcon( 0, icon );
if ( data.type() == QgsAttributesFormProperties::DnDTreeItemData::Relation )
{
const QgsRelation relation = QgsProject::instance()->relationManager()->relation( data.name() );
if ( !relation.isValid() || relation.referencedLayer() != mLayer )
{
newItem->setText( 0, tr( "Invalid relation" ) );
newItem->setForeground( 0, QColor( 255, 0, 0 ) );
}
}
if ( index < 0 )
parent->addChild( newItem );
else
parent->insertChild( index, newItem );
return newItem;
}
/**
* Is called when mouse is moved over attributes tree before a
* drop event. Used to inhibit dropping fields onto the root item.
*/
void QgsAttributesDnDTree::dragMoveEvent( QDragMoveEvent *event )
{
const QMimeData *data = event->mimeData();
if ( data->hasFormat( QStringLiteral( "application/x-qgsattributetabledesignerelement" ) ) )
{
QgsAttributesFormProperties::DnDTreeItemData itemElement;
QByteArray itemData = data->data( QStringLiteral( "application/x-qgsattributetabledesignerelement" ) );
QDataStream stream( &itemData, QIODevice::ReadOnly );
stream >> itemElement;
// Inner drag and drop actions are always MoveAction
if ( event->source() == this )
{
event->setDropAction( Qt::MoveAction );
}
}
else
{
event->ignore();
}
QTreeWidget::dragMoveEvent( event );
}
bool QgsAttributesDnDTree::dropMimeData( QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action )
{
bool bDropSuccessful = false;
if ( action == Qt::IgnoreAction )
{
bDropSuccessful = true;
}
else if ( data->hasFormat( QStringLiteral( "application/x-qgsattributetabledesignerelement" ) ) )
{
QByteArray itemData = data->data( QStringLiteral( "application/x-qgsattributetabledesignerelement" ) );
QDataStream stream( &itemData, QIODevice::ReadOnly );
QgsAttributesFormProperties::DnDTreeItemData itemElement;
while ( !stream.atEnd() )
{
stream >> itemElement;
QTreeWidgetItem *newItem;
if ( parent )
{
newItem = addItem( parent, itemElement, index++ );
bDropSuccessful = true;
}
else
{
newItem = addItem( invisibleRootItem(), itemElement, index++ );
bDropSuccessful = true;
}
if ( itemElement.type() == QgsAttributesFormProperties::DnDTreeItemData::QmlWidget )
{
onItemDoubleClicked( newItem, 0 );
}
if ( itemElement.type() == QgsAttributesFormProperties::DnDTreeItemData::HtmlWidget )
{
onItemDoubleClicked( newItem, 0 );
}
if ( itemElement.type() == QgsAttributesFormProperties::DnDTreeItemData::TextWidget )
{
onItemDoubleClicked( newItem, 0 );
}
if ( itemElement.type() == QgsAttributesFormProperties::DnDTreeItemData::SpacerWidget )
{
onItemDoubleClicked( newItem, 0 );
}
clearSelection();
newItem->setSelected( true );
}
}
return bDropSuccessful;
}
void QgsAttributesDnDTree::dropEvent( QDropEvent *event )
{
if ( !event->mimeData()->hasFormat( QStringLiteral( "application/x-qgsattributetabledesignerelement" ) ) )
return;
if ( event->source() == this )
{
event->setDropAction( Qt::MoveAction );
}
QTreeWidget::dropEvent( event );
}
QStringList QgsAttributesDnDTree::mimeTypes() const
{
return QStringList() << QStringLiteral( "application/x-qgsattributetabledesignerelement" );
}
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
QMimeData *QgsAttributesDnDTree::mimeData( const QList<QTreeWidgetItem *> items ) const
#else
QMimeData *QgsAttributesDnDTree::mimeData( const QList<QTreeWidgetItem *> &items ) const
#endif
{
if ( items.count() <= 0 )
return nullptr;
const QStringList types = mimeTypes();
if ( types.isEmpty() )
return nullptr;
QMimeData *data = new QMimeData();
const QString format = types.at( 0 );
QByteArray encoded;
QDataStream stream( &encoded, QIODevice::WriteOnly );
const auto constItems = items;
for ( const QTreeWidgetItem *item : constItems )
{
if ( item )
{
// Relevant information is always in the DnDTreeRole of the first column
const QgsAttributesFormProperties::DnDTreeItemData itemData = item->data( 0, QgsAttributesFormProperties::DnDTreeRole ).value<QgsAttributesFormProperties::DnDTreeItemData>();
stream << itemData;
}
}
data->setData( format, encoded );
return data;
}
void QgsAttributesDnDTree::onItemDoubleClicked( QTreeWidgetItem *item, int column )
{
Q_UNUSED( column )
QgsAttributesFormProperties::DnDTreeItemData itemData = item->data( 0, QgsAttributesFormProperties::DnDTreeRole ).value<QgsAttributesFormProperties::DnDTreeItemData>();
QGroupBox *baseData = new QGroupBox( tr( "Base configuration" ) );
QFormLayout *baseLayout = new QFormLayout();
baseData->setLayout( baseLayout );
QCheckBox *showLabelCheckbox = new QCheckBox( QStringLiteral( "Show label" ) );
showLabelCheckbox->setChecked( itemData.showLabel() );
baseLayout->addRow( showLabelCheckbox );
QWidget *baseWidget = new QWidget();
baseWidget->setLayout( baseLayout );
switch ( itemData.type() )
{
case QgsAttributesFormProperties::DnDTreeItemData::Action:
case QgsAttributesFormProperties::DnDTreeItemData::Container:
case QgsAttributesFormProperties::DnDTreeItemData::WidgetType:
case QgsAttributesFormProperties::DnDTreeItemData::Relation:
case QgsAttributesFormProperties::DnDTreeItemData::Field:
break;
case QgsAttributesFormProperties::DnDTreeItemData::QmlWidget:
{
if ( mType == QgsAttributesDnDTree::Type::Drag )
return;
QDialog dlg;
dlg.setObjectName( "QML Form Configuration Widget" );
QgsGui::enableAutoGeometryRestore( &dlg );
dlg.setWindowTitle( tr( "Configure QML Widget" ) );
QVBoxLayout *mainLayout = new QVBoxLayout( &dlg );
QSplitter *qmlSplitter = new QSplitter();
QWidget *qmlConfigWiget = new QWidget();
QVBoxLayout *layout = new QVBoxLayout( qmlConfigWiget );
layout->setContentsMargins( 0, 0, 0, 0 );
mainLayout->addWidget( qmlSplitter );
qmlSplitter->addWidget( qmlConfigWiget );
layout->addWidget( baseWidget );
QLineEdit *title = new QLineEdit( itemData.name() );
//qmlCode
QgsCodeEditor *qmlCode = new QgsCodeEditor( this );
qmlCode->setEditingTimeoutInterval( 250 );
qmlCode->setText( itemData.qmlElementEditorConfiguration().qmlCode );
QgsQmlWidgetWrapper *qmlWrapper = new QgsQmlWidgetWrapper( mLayer, nullptr, this );
QgsFeature previewFeature;
mLayer->getFeatures().nextFeature( previewFeature );
//update preview on text change
connect( qmlCode, &QgsCodeEditor::editingTimeout, this, [=] {
qmlWrapper->setQmlCode( qmlCode->text() );
qmlWrapper->reinitWidget();
qmlWrapper->setFeature( previewFeature );
} );
//templates
QComboBox *qmlObjectTemplate = new QComboBox();
qmlObjectTemplate->addItem( tr( "Free Text…" ) );
qmlObjectTemplate->addItem( tr( "Rectangle" ) );
qmlObjectTemplate->addItem( tr( "Pie Chart" ) );
qmlObjectTemplate->addItem( tr( "Bar Chart" ) );
connect( qmlObjectTemplate, qOverload<int>( &QComboBox::activated ), qmlCode, [=]( int index ) {
qmlCode->clear();
switch ( index )
{
case 0:
{
qmlCode->setText( QString() );
break;
}
case 1:
{
qmlCode->setText( QStringLiteral( "import QtQuick 2.0\n"
"\n"
"Rectangle {\n"
" width: 100\n"
" height: 100\n"
" color: \"steelblue\"\n"
" Text{ text: \"A rectangle\" }\n"
"}\n" ) );
break;
}
case 2:
{
qmlCode->setText( QStringLiteral( "import QtQuick 2.0\n"
"import QtCharts 2.0\n"
"\n"
"ChartView {\n"
" width: 400\n"
" height: 400\n"
"\n"
" PieSeries {\n"
" id: pieSeries\n"
" PieSlice { label: \"First slice\"; value: 25 }\n"
" PieSlice { label: \"Second slice\"; value: 45 }\n"
" PieSlice { label: \"Third slice\"; value: 30 }\n"
" }\n"
"}\n" ) );
break;
}
case 3:
{
qmlCode->setText( QStringLiteral( "import QtQuick 2.0\n"
"import QtCharts 2.0\n"
"\n"
"ChartView {\n"
" title: \"Bar series\"\n"
" width: 600\n"
" height:400\n"
" legend.alignment: Qt.AlignBottom\n"
" antialiasing: true\n"
" ValueAxis{\n"
" id: valueAxisY\n"
" min: 0\n"
" max: 15\n"
" }\n"
"\n"
" BarSeries {\n"
" id: mySeries\n"
" axisY: valueAxisY\n"
" axisX: BarCategoryAxis { categories: [\"2007\", \"2008\", \"2009\", \"2010\", \"2011\", \"2012\" ] }\n"
" BarSet { label: \"Bob\"; values: [2, 2, 3, 4, 5, 6] }\n"
" BarSet { label: \"Susan\"; values: [5, 1, 2, 4, 1, 7] }\n"
" BarSet { label: \"James\"; values: [3, 5, 8, 13, 5, 8] }\n"
" }\n"
"}\n" ) );
break;
}
default:
break;
}
} );
QgsFieldExpressionWidget *expressionWidget = new QgsFieldExpressionWidget;
expressionWidget->setButtonVisible( false );
expressionWidget->registerExpressionContextGenerator( this );
expressionWidget->setLayer( mLayer );
QToolButton *addFieldButton = new QToolButton();
addFieldButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/symbologyAdd.svg" ) ) );
QToolButton *editExpressionButton = new QToolButton();
editExpressionButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
editExpressionButton->setToolTip( tr( "Insert/Edit Expression" ) );
connect( addFieldButton, &QAbstractButton::clicked, this, [=] {
QString expression = expressionWidget->expression().trimmed().replace( '"', QLatin1String( "\\\"" ) );
if ( !expression.isEmpty() )
qmlCode->insertText( QStringLiteral( "expression.evaluate(\"%1\")" ).arg( expression ) );
} );
connect( editExpressionButton, &QAbstractButton::clicked, this, [=] {
QString expression = QgsExpressionFinder::findAndSelectActiveExpression( qmlCode, QStringLiteral( "expression\\.evaluate\\(\\s*\"(.*?)\\s*\"\\s*\\)" ) );
expression.replace( QLatin1String( "\\\"" ), QLatin1String( "\"" ) );
QgsExpressionContext context = createExpressionContext();
QgsExpressionBuilderDialog exprDlg( mLayer, expression, this, QStringLiteral( "generic" ), context );
exprDlg.setWindowTitle( tr( "Insert Expression" ) );
if ( exprDlg.exec() == QDialog::Accepted && !exprDlg.expressionText().trimmed().isEmpty() )
{
QString expression = exprDlg.expressionText().trimmed().replace( '"', QLatin1String( "\\\"" ) );
if ( !expression.isEmpty() )
qmlCode->insertText( QStringLiteral( "expression.evaluate(\"%1\")" ).arg( expression ) );
}
} );
layout->addWidget( new QLabel( tr( "Title" ) ) );
layout->addWidget( title );
QGroupBox *qmlCodeBox = new QGroupBox( tr( "QML Code" ) );
qmlCodeBox->setLayout( new QVBoxLayout );
qmlCodeBox->layout()->addWidget( qmlObjectTemplate );
QWidget *expressionWidgetBox = new QWidget();
qmlCodeBox->layout()->addWidget( expressionWidgetBox );
expressionWidgetBox->setLayout( new QHBoxLayout );
expressionWidgetBox->layout()->setContentsMargins( 0, 0, 0, 0 );
expressionWidgetBox->layout()->addWidget( expressionWidget );
expressionWidgetBox->layout()->addWidget( addFieldButton );
expressionWidgetBox->layout()->addWidget( editExpressionButton );
expressionWidgetBox->layout()->addWidget( editExpressionButton );
layout->addWidget( qmlCodeBox );
layout->addWidget( qmlCode );
QScrollArea *qmlPreviewBox = new QgsScrollArea();
qmlPreviewBox->setMinimumWidth( 200 );
qmlPreviewBox->setWidget( qmlWrapper->widget() );
//emit to load preview for the first time
emit qmlCode->editingTimeout();
qmlSplitter->addWidget( qmlPreviewBox );
qmlSplitter->setChildrenCollapsible( false );
qmlSplitter->setHandleWidth( 6 );
qmlSplitter->setSizes( QList<int>() << 1 << 1 );
QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [=] {
QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#other-widgets" ) );
} );
mainLayout->addWidget( buttonBox );
if ( dlg.exec() )
{
QgsAttributesFormProperties::QmlElementEditorConfiguration qmlEdCfg;
qmlEdCfg.qmlCode = qmlCode->text();
itemData.setName( title->text() );
itemData.setQmlElementEditorConfiguration( qmlEdCfg );
itemData.setShowLabel( showLabelCheckbox->isChecked() );
item->setData( 0, QgsAttributesFormProperties::DnDTreeRole, itemData );
item->setText( 0, title->text() );
}
}
break;
case QgsAttributesFormProperties::DnDTreeItemData::HtmlWidget:
{
if ( mType == QgsAttributesDnDTree::Type::Drag )
return;
QDialog dlg;
dlg.setObjectName( "HTML Form Configuration Widget" );
QgsGui::enableAutoGeometryRestore( &dlg );
dlg.setWindowTitle( tr( "Configure HTML Widget" ) );
QVBoxLayout *mainLayout = new QVBoxLayout( &dlg );
QSplitter *htmlSplitter = new QSplitter();
QWidget *htmlConfigWiget = new QWidget();
QVBoxLayout *layout = new QVBoxLayout( htmlConfigWiget );
layout->setContentsMargins( 0, 0, 0, 0 );
mainLayout->addWidget( htmlSplitter );
htmlSplitter->addWidget( htmlConfigWiget );
htmlSplitter->setChildrenCollapsible( false );
htmlSplitter->setHandleWidth( 6 );
htmlSplitter->setSizes( QList<int>() << 1 << 1 );
layout->addWidget( baseWidget );
QLineEdit *title = new QLineEdit( itemData.name() );
//htmlCode
QgsCodeEditorHTML *htmlCode = new QgsCodeEditorHTML();
htmlCode->setSizePolicy( QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding );
htmlCode->setText( itemData.htmlElementEditorConfiguration().htmlCode );
QgsHtmlWidgetWrapper *htmlWrapper = new QgsHtmlWidgetWrapper( mLayer, nullptr, this );
QgsFeature previewFeature;
mLayer->getFeatures().nextFeature( previewFeature );
//update preview on text change
connect( htmlCode, &QgsCodeEditorHTML::textChanged, this, [=] {
htmlWrapper->setHtmlCode( htmlCode->text() );
htmlWrapper->reinitWidget();
htmlWrapper->setFeature( previewFeature );
} );
QgsFieldExpressionWidget *expressionWidget = new QgsFieldExpressionWidget;
expressionWidget->setButtonVisible( false );
expressionWidget->registerExpressionContextGenerator( this );
expressionWidget->setLayer( mLayer );
QToolButton *addFieldButton = new QToolButton();
addFieldButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/symbologyAdd.svg" ) ) );
QToolButton *editExpressionButton = new QToolButton();
editExpressionButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
editExpressionButton->setToolTip( tr( "Insert/Edit Expression" ) );
connect( addFieldButton, &QAbstractButton::clicked, this, [=] {
QString expression = expressionWidget->expression().trimmed().replace( '"', QLatin1String( "\\\"" ) );
if ( !expression.isEmpty() )
htmlCode->insertText( QStringLiteral( "<script>document.write(expression.evaluate(\"%1\"));</script>" ).arg( expression ) );
} );
connect( editExpressionButton, &QAbstractButton::clicked, this, [=] {
QString expression = QgsExpressionFinder::findAndSelectActiveExpression( htmlCode, QStringLiteral( "<script>\\s*document\\.write\\(\\s*expression\\.evaluate\\(\\s*\"(.*?)\\s*\"\\s*\\)\\s*\\)\\s*;?\\s*</script>" ) );
expression.replace( QLatin1String( "\\\"" ), QLatin1String( "\"" ) );
QgsExpressionContext context = createExpressionContext();
QgsExpressionBuilderDialog exprDlg( mLayer, expression, this, QStringLiteral( "generic" ), context );
exprDlg.setWindowTitle( tr( "Insert Expression" ) );
if ( exprDlg.exec() == QDialog::Accepted && !exprDlg.expressionText().trimmed().isEmpty() )
{
QString expression = exprDlg.expressionText().trimmed().replace( '"', QLatin1String( "\\\"" ) );
if ( !expression.isEmpty() )
htmlCode->insertText( QStringLiteral( "<script>document.write(expression.evaluate(\"%1\"));</script>" ).arg( expression ) );
}
} );
layout->addWidget( new QLabel( tr( "Title" ) ) );
layout->addWidget( title );
QGroupBox *expressionWidgetBox = new QGroupBox( tr( "HTML Code" ) );
layout->addWidget( expressionWidgetBox );
expressionWidgetBox->setLayout( new QHBoxLayout );
expressionWidgetBox->layout()->addWidget( expressionWidget );
expressionWidgetBox->layout()->addWidget( addFieldButton );
expressionWidgetBox->layout()->addWidget( editExpressionButton );
layout->addWidget( htmlCode );
QScrollArea *htmlPreviewBox = new QgsScrollArea();
htmlPreviewBox->setLayout( new QGridLayout );
htmlPreviewBox->setMinimumWidth( 200 );
htmlPreviewBox->layout()->addWidget( htmlWrapper->widget() );
//emit to load preview for the first time
emit htmlCode->textChanged();
htmlSplitter->addWidget( htmlPreviewBox );
htmlSplitter->setChildrenCollapsible( false );
htmlSplitter->setHandleWidth( 6 );
htmlSplitter->setSizes( QList<int>() << 1 << 1 );
QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [=] {
QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#other-widgets" ) );
} );
mainLayout->addWidget( buttonBox );
if ( dlg.exec() )
{
QgsAttributesFormProperties::HtmlElementEditorConfiguration htmlEdCfg;
htmlEdCfg.htmlCode = htmlCode->text();
itemData.setName( title->text() );
itemData.setHtmlElementEditorConfiguration( htmlEdCfg );
itemData.setShowLabel( showLabelCheckbox->isChecked() );
item->setData( 0, QgsAttributesFormProperties::DnDTreeRole, itemData );
item->setText( 0, title->text() );
}
break;
}
case QgsAttributesFormProperties::DnDTreeItemData::TextWidget:
{
if ( mType == QgsAttributesDnDTree::Type::Drag )
return;
QDialog dlg;
dlg.setObjectName( "Text Form Configuration Widget" );
QgsGui::enableAutoGeometryRestore( &dlg );
dlg.setWindowTitle( tr( "Configure Text Widget" ) );
QVBoxLayout *mainLayout = new QVBoxLayout( &dlg );
QSplitter *textSplitter = new QSplitter();
QWidget *textConfigWiget = new QWidget();
QVBoxLayout *layout = new QVBoxLayout( textConfigWiget );
layout->setContentsMargins( 0, 0, 0, 0 );
mainLayout->addWidget( textSplitter );
textSplitter->addWidget( textConfigWiget );
layout->addWidget( baseWidget );
QLineEdit *title = new QLineEdit( itemData.name() );
QgsCodeEditorHTML *text = new QgsCodeEditorHTML();
text->setSizePolicy( QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding );
text->setText( itemData.textElementEditorConfiguration().text );
QgsTextWidgetWrapper *textWrapper = new QgsTextWidgetWrapper( mLayer, nullptr, this );
QgsFeature previewFeature;
mLayer->getFeatures().nextFeature( previewFeature );
//update preview on text change
connect( text, &QgsCodeEditorExpression::textChanged, this, [=] {
textWrapper->setText( text->text() );
textWrapper->reinitWidget();
textWrapper->setFeature( previewFeature );
} );
QgsFieldExpressionWidget *expressionWidget = new QgsFieldExpressionWidget;
expressionWidget->setButtonVisible( false );
expressionWidget->registerExpressionContextGenerator( this );
expressionWidget->setLayer( mLayer );
QToolButton *addFieldButton = new QToolButton();
addFieldButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/symbologyAdd.svg" ) ) );
QToolButton *editExpressionButton = new QToolButton();
editExpressionButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
editExpressionButton->setToolTip( tr( "Insert/Edit Expression" ) );
connect( addFieldButton, &QAbstractButton::clicked, this, [=] {
QString expression = expressionWidget->expression().trimmed();
if ( !expression.isEmpty() )
text->insertText( QStringLiteral( "[%%1%]" ).arg( expression ) );
} );
connect( editExpressionButton, &QAbstractButton::clicked, this, [=] {
QString expression = QgsExpressionFinder::findAndSelectActiveExpression( text );
QgsExpressionContext context = createExpressionContext();
QgsExpressionBuilderDialog exprDlg( mLayer, expression, this, QStringLiteral( "generic" ), context );
exprDlg.setWindowTitle( tr( "Insert Expression" ) );
if ( exprDlg.exec() == QDialog::Accepted && !exprDlg.expressionText().trimmed().isEmpty() )
{
QString expression = exprDlg.expressionText().trimmed();
if ( !expression.isEmpty() )
text->insertText( QStringLiteral( "[%%1%]" ).arg( expression ) );
}
} );
layout->addWidget( new QLabel( tr( "Title" ) ) );
layout->addWidget( title );
QGroupBox *expressionWidgetBox = new QGroupBox( tr( "Text" ) );
layout->addWidget( expressionWidgetBox );
expressionWidgetBox->setLayout( new QHBoxLayout );
expressionWidgetBox->layout()->addWidget( expressionWidget );
expressionWidgetBox->layout()->addWidget( addFieldButton );
expressionWidgetBox->layout()->addWidget( editExpressionButton );
layout->addWidget( text );
QScrollArea *textPreviewBox = new QgsScrollArea();
textPreviewBox->setLayout( new QGridLayout );
textPreviewBox->setMinimumWidth( 200 );
textPreviewBox->layout()->addWidget( textWrapper->widget() );
//emit to load preview for the first time
emit text->textChanged();
textSplitter->addWidget( textPreviewBox );
textSplitter->setChildrenCollapsible( false );
textSplitter->setHandleWidth( 6 );
textSplitter->setSizes( QList<int>() << 1 << 1 );
QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [=] {
QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#other-widgets" ) );
} );
mainLayout->addWidget( buttonBox );
if ( dlg.exec() )
{
QgsAttributesFormProperties::TextElementEditorConfiguration textEdCfg;
textEdCfg.text = text->text();
itemData.setName( title->text() );
itemData.setTextElementEditorConfiguration( textEdCfg );
itemData.setShowLabel( showLabelCheckbox->isChecked() );
item->setData( 0, QgsAttributesFormProperties::DnDTreeRole, itemData );
item->setText( 0, title->text() );
}
break;
}
case QgsAttributesFormProperties::DnDTreeItemData::SpacerWidget:
{
if ( mType == QgsAttributesDnDTree::Type::Drag )
return;
QDialog dlg;
dlg.setObjectName( "Spacer Form Configuration Widget" );
QgsGui::enableAutoGeometryRestore( &dlg );
dlg.setWindowTitle( tr( "Configure Spacer Widget" ) );
QVBoxLayout *mainLayout = new QVBoxLayout();
mainLayout->addWidget( new QLabel( tr( "Title" ) ) );
QLineEdit *title = new QLineEdit( itemData.name() );
mainLayout->addWidget( title );
QHBoxLayout *cbLayout = new QHBoxLayout();
mainLayout->addLayout( cbLayout );
dlg.setLayout( mainLayout );
QCheckBox *cb = new QCheckBox { &dlg };
cb->setChecked( itemData.spacerElementEditorConfiguration().drawLine );
cbLayout->addWidget( new QLabel( tr( "Draw horizontal line" ), &dlg ) );
cbLayout->addWidget( cb );
QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [=] {
QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#other-widgets" ) );
} );
mainLayout->addWidget( buttonBox );
if ( dlg.exec() )
{
QgsAttributesFormProperties::SpacerElementEditorConfiguration spacerEdCfg;
spacerEdCfg.drawLine = cb->isChecked();
itemData.setSpacerElementEditorConfiguration( spacerEdCfg );
itemData.setShowLabel( false );
itemData.setName( title->text() );
item->setData( 0, QgsAttributesFormProperties::DnDTreeRole, itemData );
item->setText( 0, title->text() );
}
break;
}
}
}
QgsExpressionContext QgsAttributesDnDTree::createExpressionContext() const
{
QgsExpressionContext expContext;
expContext << QgsExpressionContextUtils::globalScope()
<< QgsExpressionContextUtils::projectScope( QgsProject::instance() );
if ( mLayer )
expContext << QgsExpressionContextUtils::layerScope( mLayer );
expContext.appendScope( QgsExpressionContextUtils::formScope() );
return expContext;
}
QgsAttributesDnDTree::Type QgsAttributesDnDTree::type() const
{
return mType;
}
void QgsAttributesDnDTree::setType( QgsAttributesDnDTree::Type value )
{
mType = value;
}
void QgsAttributesDnDTree::selectFirstMatchingItem( const QgsAttributesFormProperties::DnDTreeItemData &data )
{
QTreeWidgetItemIterator it( this );
while ( *it )
{
const QgsAttributesFormProperties::DnDTreeItemData rowData = ( *it )->data( 0, QgsAttributesFormProperties::DnDTreeRole ).value<QgsAttributesFormProperties::DnDTreeItemData>();
if ( data.type() == rowData.type() && data.name() == rowData.name() )
{
if ( selectedItems().count() == 1 && ( *it )->isSelected() == true )
{
// the selection is already good
}
else
{
clearSelection();
( *it )->setSelected( true );
}
return;
}
++it;
}
clearSelection();
}
/*
* Serialization helpers for DesigerTreeItemData so we can stuff this easily into QMimeData
*/
QDataStream &operator<<( QDataStream &stream, const QgsAttributesFormProperties::DnDTreeItemData &data )
{
stream << static_cast<quint32>( data.type() ) << data.name() << data.displayName();
return stream;
}
QDataStream &operator>>( QDataStream &stream, QgsAttributesFormProperties::DnDTreeItemData &data )
{
QString name;
QString displayName;
quint32 type;
stream >> type >> name >> displayName;
data.setType( static_cast<QgsAttributesFormProperties::DnDTreeItemData::Type>( type ) );
data.setName( name );
data.setDisplayName( displayName );
return stream;
}
Qgis::AttributeEditorContainerType QgsAttributesFormProperties::DnDTreeItemData::containerType() const
{
return mContainerType;
}
void QgsAttributesFormProperties::DnDTreeItemData::setContainerType( Qgis::AttributeEditorContainerType type )
{
mContainerType = type;
}
const QgsAttributeEditorElement::LabelStyle QgsAttributesFormProperties::DnDTreeItemData::labelStyle() const
{
return mLabelStyle;
}
void QgsAttributesFormProperties::DnDTreeItemData::setLabelStyle( const QgsAttributeEditorElement::LabelStyle &labelStyle )
{
mLabelStyle = labelStyle;
}
bool QgsAttributesFormProperties::DnDTreeItemData::showLabel() const
{
return mShowLabel;
}
void QgsAttributesFormProperties::DnDTreeItemData::setShowLabel( bool showLabel )
{
mShowLabel = showLabel;
}
QgsOptionalExpression QgsAttributesFormProperties::DnDTreeItemData::visibilityExpression() const
{
return mVisibilityExpression;
}
void QgsAttributesFormProperties::DnDTreeItemData::setVisibilityExpression( const QgsOptionalExpression &visibilityExpression )
{
mVisibilityExpression = visibilityExpression;
}
QgsOptionalExpression QgsAttributesFormProperties::DnDTreeItemData::collapsedExpression() const
{
return mCollapsedExpression;
}
void QgsAttributesFormProperties::DnDTreeItemData::setCollapsedExpression( const QgsOptionalExpression &collapsedExpression )
{
mCollapsedExpression = collapsedExpression;
}
QgsAttributesFormProperties::RelationEditorConfiguration QgsAttributesFormProperties::DnDTreeItemData::relationEditorConfiguration() const
{
return mRelationEditorConfiguration;
}
void QgsAttributesFormProperties::DnDTreeItemData::setRelationEditorConfiguration( const QgsAttributesFormProperties::RelationEditorConfiguration &relationEditorConfiguration )
{
mRelationEditorConfiguration = relationEditorConfiguration;
}
QgsAttributesFormProperties::QmlElementEditorConfiguration QgsAttributesFormProperties::DnDTreeItemData::qmlElementEditorConfiguration() const
{
return mQmlElementEditorConfiguration;
}
void QgsAttributesFormProperties::DnDTreeItemData::setQmlElementEditorConfiguration( const QmlElementEditorConfiguration &qmlElementEditorConfiguration )
{
mQmlElementEditorConfiguration = qmlElementEditorConfiguration;
}
QgsAttributesFormProperties::HtmlElementEditorConfiguration QgsAttributesFormProperties::DnDTreeItemData::htmlElementEditorConfiguration() const
{
return mHtmlElementEditorConfiguration;
}
void QgsAttributesFormProperties::DnDTreeItemData::setHtmlElementEditorConfiguration( const HtmlElementEditorConfiguration &htmlElementEditorConfiguration )
{
mHtmlElementEditorConfiguration = htmlElementEditorConfiguration;
}
QgsAttributesFormProperties::SpacerElementEditorConfiguration QgsAttributesFormProperties::DnDTreeItemData::spacerElementEditorConfiguration() const
{
return mSpacerElementEditorConfiguration;
}
void QgsAttributesFormProperties::DnDTreeItemData::setSpacerElementEditorConfiguration( SpacerElementEditorConfiguration spacerElementEditorConfiguration )
{
mSpacerElementEditorConfiguration = spacerElementEditorConfiguration;
}
QColor QgsAttributesFormProperties::DnDTreeItemData::backgroundColor() const
{
return mBackgroundColor;
}
void QgsAttributesFormProperties::DnDTreeItemData::setBackgroundColor( const QColor &backgroundColor )
{
mBackgroundColor = backgroundColor;
}
QgsAttributesFormProperties::TextElementEditorConfiguration QgsAttributesFormProperties::DnDTreeItemData::textElementEditorConfiguration() const
{
return mTextElementEditorConfiguration;
}
void QgsAttributesFormProperties::DnDTreeItemData::setTextElementEditorConfiguration( const QgsAttributesFormProperties::TextElementEditorConfiguration &textElementEditorConfiguration )
{
mTextElementEditorConfiguration = textElementEditorConfiguration;
}
void QgsAttributesFormProperties::updatedFields()
{
// Store configuration to insure changes made are kept after refreshing the list
QMap<QString, FieldConfig> fieldConfigs;
QTreeWidgetItem *fieldContainer = mAvailableWidgetsTree->invisibleRootItem()->child( 0 );
for ( int i = 0; i < fieldContainer->childCount(); i++ )
{
QTreeWidgetItem *fieldItem = fieldContainer->child( i );
const QString fieldName = fieldItem->data( 0, FieldNameRole ).toString();
const FieldConfig cfg = fieldItem->data( 0, FieldConfigRole ).value<FieldConfig>();
fieldConfigs[fieldName] = cfg;
}
initAvailableWidgetsTree();
fieldContainer = mAvailableWidgetsTree->invisibleRootItem()->child( 0 );
for ( int i = 0; i < fieldContainer->childCount(); i++ )
{
QTreeWidgetItem *fieldItem = fieldContainer->child( i );
const QString fieldName = fieldItem->data( 0, FieldNameRole ).toString();
if ( fieldConfigs.contains( fieldName ) )
{
fieldItem->setData( 0, FieldConfigRole, fieldConfigs[fieldName] );
}
}
}
void QgsAttributesFormProperties::onContextMenuRequested( QPoint point )
{
if ( mAvailableWidgetsTree->selectedItems().count() != 1 )
return;
QPoint globalPos = mAvailableWidgetsTree->viewport()->mapToGlobal( point );
QTreeWidgetItem *item = mAvailableWidgetsTree->itemAt( point );
const DnDTreeItemData itemData = item->data( 0, DnDTreeRole ).value<DnDTreeItemData>();
if ( itemData.type() == DnDTreeItemData::Field )
{
const QClipboard *clipboard = QApplication::clipboard();
const bool pasteEnabled = clipboard->mimeData()->hasFormat( QStringLiteral( "application/x-qgsattributetabledesignerelementclipboard" ) );
mActionPasteWidgetConfiguration->setEnabled( pasteEnabled );
mAvailableWidgetsTreeContextMenu->popup( globalPos );
}
}
void QgsAttributesFormProperties::copyWidgetConfiguration()
{
if ( mAvailableWidgetsTree->selectedItems().count() != 1 )
return;
const QTreeWidgetItem *item = mAvailableWidgetsTree->selectedItems().at( 0 );
const DnDTreeItemData itemData = item->data( 0, DnDTreeRole ).value<DnDTreeItemData>();
if ( itemData.type() != DnDTreeItemData::Field )
return;
const QString fieldName = item->data( 0, FieldNameRole ).toString();
const int index = mLayer->fields().indexOf( fieldName );
if ( index < 0 )
return;
const QgsField field = mLayer->fields().field( index );
// We won't copy field aliases nor comments
QDomDocument doc;
QDomElement documentElement = doc.createElement( QStringLiteral( "FormWidgetClipboard" ) );
documentElement.setAttribute( QStringLiteral( "name" ), field.name() );
// Editor widget setup
QgsEditorWidgetSetup widgetSetup = field.editorWidgetSetup();
QDomElement editWidgetElement = doc.createElement( QStringLiteral( "editWidget" ) );
documentElement.appendChild( editWidgetElement );
editWidgetElement.setAttribute( QStringLiteral( "type" ), widgetSetup.type() );
QDomElement editWidgetConfigElement = doc.createElement( QStringLiteral( "config" ) );
editWidgetConfigElement.appendChild( QgsXmlUtils::writeVariant( widgetSetup.config(), doc ) );
editWidgetElement.appendChild( editWidgetConfigElement );
// Split policy
QDomElement splitPolicyElement = doc.createElement( QStringLiteral( "splitPolicy" ) );
splitPolicyElement.setAttribute( QStringLiteral( "policy" ), qgsEnumValueToKey( field.splitPolicy() ) );
documentElement.appendChild( splitPolicyElement );
// Duplicate policy
QDomElement duplicatePolicyElement = doc.createElement( QStringLiteral( "duplicatePolicy" ) );
duplicatePolicyElement.setAttribute( QStringLiteral( "policy" ), qgsEnumValueToKey( field.duplicatePolicy() ) );
documentElement.appendChild( duplicatePolicyElement );
// Merge policy
QDomElement mergePolicyElement = doc.createElement( QStringLiteral( "mergePolicy" ) );
mergePolicyElement.setAttribute( QStringLiteral( "policy" ), qgsEnumValueToKey( field.mergePolicy() ) );
documentElement.appendChild( mergePolicyElement );
// Default expressions
QDomElement defaultElem = doc.createElement( QStringLiteral( "default" ) );
defaultElem.setAttribute( QStringLiteral( "expression" ), field.defaultValueDefinition().expression() );
defaultElem.setAttribute( QStringLiteral( "applyOnUpdate" ), field.defaultValueDefinition().applyOnUpdate() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
documentElement.appendChild( defaultElem );
// Constraints
QDomElement constraintElem = doc.createElement( QStringLiteral( "constraint" ) );
constraintElem.setAttribute( QStringLiteral( "constraints" ), field.constraints().constraints() );
constraintElem.setAttribute( QStringLiteral( "unique_strength" ), field.constraints().constraintStrength( QgsFieldConstraints::ConstraintUnique ) );
constraintElem.setAttribute( QStringLiteral( "notnull_strength" ), field.constraints().constraintStrength( QgsFieldConstraints::ConstraintNotNull ) );
constraintElem.setAttribute( QStringLiteral( "exp_strength" ), field.constraints().constraintStrength( QgsFieldConstraints::ConstraintExpression ) );
documentElement.appendChild( constraintElem );
// Constraint expressions
QDomElement constraintExpressionElem = doc.createElement( QStringLiteral( "constraintExpression" ) );
constraintExpressionElem.setAttribute( QStringLiteral( "exp" ), field.constraints().constraintExpression() );
constraintExpressionElem.setAttribute( QStringLiteral( "desc" ), field.constraints().constraintDescription() );
documentElement.appendChild( constraintExpressionElem );
// Widget general settings
QDomElement widgetGeneralSettingsElem = doc.createElement( QStringLiteral( "widgetGeneralSettings" ) );
widgetGeneralSettingsElem.setAttribute( QStringLiteral( "editable" ), !mLayer->editFormConfig().readOnly( index ) );
widgetGeneralSettingsElem.setAttribute( QStringLiteral( "reuse_last_values" ), mLayer->editFormConfig().labelOnTop( index ) );
widgetGeneralSettingsElem.setAttribute( QStringLiteral( "label_on_top" ), mLayer->editFormConfig().reuseLastValue( index ) );
documentElement.appendChild( widgetGeneralSettingsElem );
// Widget display section
if ( mAttributeWidgetEdit )
{
// Go for the corresponding form layout item and extract its display settings
if ( mFormLayoutTree->selectedItems().count() != 1 )
return;
const QTreeWidgetItem *itemLayout = mFormLayoutTree->selectedItems().at( 0 );
const DnDTreeItemData itemDataLayout = itemLayout->data( 0, DnDTreeRole ).value<DnDTreeItemData>();
QDomElement displayElement = doc.createElement( QStringLiteral( "widgetDisplay" ) );
displayElement.setAttribute( QStringLiteral( "showLabel" ), itemDataLayout.showLabel() );
displayElement.setAttribute( QStringLiteral( "horizontalStretch" ), itemDataLayout.horizontalStretch() );
displayElement.setAttribute( QStringLiteral( "verticalStretch" ), itemDataLayout.verticalStretch() );
displayElement.appendChild( itemDataLayout.labelStyle().writeXml( doc ) );
documentElement.appendChild( displayElement );
}
doc.appendChild( documentElement );
QMimeData *mimeData = new QMimeData;
mimeData->setData( QStringLiteral( "application/x-qgsattributetabledesignerelementclipboard" ), doc.toByteArray() );
QClipboard *clipboard = QApplication::clipboard();
clipboard->setMimeData( mimeData );
}
void QgsAttributesFormProperties::pasteWidgetConfiguration()
{
if ( mAvailableWidgetsTree->selectedItems().count() != 1 )
return;
QTreeWidgetItem *item = mAvailableWidgetsTree->selectedItems().at( 0 );
const QString fieldName = item->data( 0, FieldNameRole ).toString();
const int fieldIndex = mLayer->fields().indexOf( fieldName );
if ( fieldIndex < 0 )
return;
// Get base config from target item and ovewrite settings when possible
FieldConfig config = item->data( 0, FieldConfigRole ).value<FieldConfig>();
QDomDocument doc;
QClipboard *clipboard = QApplication::clipboard();
if ( doc.setContent( clipboard->mimeData()->data( QStringLiteral( "application/x-qgsattributetabledesignerelementclipboard" ) ) ) )
{
QDomElement docElem = doc.documentElement();
if ( docElem.tagName() != QLatin1String( "FormWidgetClipboard" ) )
return;
// When pasting, the target item has already been selected and
// has triggered attribute type dialog loading. Therefore, we'll
// only overwrite GUI settings instead of destroying and recreating
// the whole dialog.
// Editor widget configuration
const QDomElement fieldWidgetElement = docElem.firstChildElement( QStringLiteral( "editWidget" ) );
if ( !fieldWidgetElement.isNull() )
{
const QString widgetType = fieldWidgetElement.attribute( QStringLiteral( "type" ) );
// Only paste if source editor widget type is supported by target field
const QgsEditorWidgetFactory *factory = QgsGui::editorWidgetRegistry()->factory( widgetType );
if ( factory->supportsField( mLayer, fieldIndex ) )
{
const QDomElement configElement = fieldWidgetElement.firstChildElement( QStringLiteral( "config" ) );
if ( !configElement.isNull() )
{
const QDomElement optionsElem = configElement.childNodes().at( 0 ).toElement();
QVariantMap optionsMap = QgsXmlUtils::readVariant( optionsElem ).toMap();
QgsReadWriteContext context;
if ( widgetType == QLatin1String( "ValueRelation" ) )
{
optionsMap[QStringLiteral( "Value" )] = context.projectTranslator()->translate( QStringLiteral( "project:layers:%1:fields:%2:valuerelationvalue" ).arg( mLayer->id(), fieldName ), optionsMap[QStringLiteral( "Value" )].toString() );
}
config.mEditorWidgetType = widgetType;
config.mEditorWidgetConfig = optionsMap;
}
}
else
{
mMessageBar->pushMessage( QString(), tr( "Unable to paste widget configuration. The target field (%1) does not support the %2 widget type." ).arg( fieldName, widgetType ), Qgis::MessageLevel::Warning );
}
}
// Split policy
const QDomElement splitPolicyElement = docElem.firstChildElement( QStringLiteral( "splitPolicy" ) );
if ( !splitPolicyElement.isNull() )
{
const Qgis::FieldDomainSplitPolicy policy = qgsEnumKeyToValue( splitPolicyElement.attribute( QStringLiteral( "policy" ) ), Qgis::FieldDomainSplitPolicy::Duplicate );
config.mSplitPolicy = policy;
}
// Duplicate policy
const QDomElement duplicatePolicyElement = docElem.firstChildElement( QStringLiteral( "duplicatePolicy" ) );
if ( !duplicatePolicyElement.isNull() )
{
const Qgis::FieldDuplicatePolicy policy = qgsEnumKeyToValue( duplicatePolicyElement.attribute( QStringLiteral( "policy" ) ), Qgis::FieldDuplicatePolicy::Duplicate );
config.mDuplicatePolicy = policy;
}
// Merge policy
const QDomElement mergePolicyElement = docElem.firstChildElement( QStringLiteral( "mergePolicy" ) );
if ( !mergePolicyElement.isNull() )
{
const Qgis::FieldDomainMergePolicy policy = qgsEnumKeyToValue( mergePolicyElement.attribute( QStringLiteral( "policy" ) ), Qgis::FieldDomainMergePolicy::DefaultValue );
config.mMergePolicy = policy;
}
// Default expressions
const QDomElement defaultElement = docElem.firstChildElement( QStringLiteral( "default" ) );
if ( !defaultElement.isNull() )
{
mAttributeTypeDialog->setDefaultValueExpression( defaultElement.attribute( QStringLiteral( "expression" ) ) );
mAttributeTypeDialog->setApplyDefaultValueOnUpdate( defaultElement.attribute( QStringLiteral( "applyOnUpdate" ) ).toInt() );
}
// Constraints
// take target field constraints as a basis
QgsFieldConstraints fieldConstraints = config.mFieldConstraints;
const QDomElement constraintElement = docElem.firstChildElement( QStringLiteral( "constraint" ) );
if ( !constraintElement.isNull() )
{
const int intConstraints = constraintElement.attribute( QStringLiteral( "constraints" ), QStringLiteral( "0" ) ).toInt();
QgsFieldConstraints::Constraints constraints = static_cast< QgsFieldConstraints::Constraints >( intConstraints );
// always keep provider constraints intact
if ( fieldConstraints.constraintOrigin( QgsFieldConstraints::ConstraintNotNull ) != QgsFieldConstraints::ConstraintOriginProvider )
{
if ( constraints & QgsFieldConstraints::ConstraintNotNull )
fieldConstraints.setConstraint( QgsFieldConstraints::ConstraintNotNull, QgsFieldConstraints::ConstraintOriginLayer );
else
fieldConstraints.removeConstraint( QgsFieldConstraints::ConstraintNotNull );
}
if ( fieldConstraints.constraintOrigin( QgsFieldConstraints::ConstraintUnique ) != QgsFieldConstraints::ConstraintOriginProvider )
{
if ( constraints & QgsFieldConstraints::ConstraintUnique )
fieldConstraints.setConstraint( QgsFieldConstraints::ConstraintUnique, QgsFieldConstraints::ConstraintOriginLayer );
else
fieldConstraints.removeConstraint( QgsFieldConstraints::ConstraintUnique );
}
if ( fieldConstraints.constraintOrigin( QgsFieldConstraints::ConstraintExpression ) != QgsFieldConstraints::ConstraintOriginProvider )
{
if ( constraints & QgsFieldConstraints::ConstraintExpression )
fieldConstraints.setConstraint( QgsFieldConstraints::ConstraintExpression, QgsFieldConstraints::ConstraintOriginLayer );
else
fieldConstraints.removeConstraint( QgsFieldConstraints::ConstraintExpression );
}
const int uniqueStrength = constraintElement.attribute( QStringLiteral( "unique_strength" ), QStringLiteral( "1" ) ).toInt();
const int notNullStrength = constraintElement.attribute( QStringLiteral( "notnull_strength" ), QStringLiteral( "1" ) ).toInt();
const int expStrength = constraintElement.attribute( QStringLiteral( "exp_strength" ), QStringLiteral( "1" ) ).toInt();
fieldConstraints.setConstraintStrength( QgsFieldConstraints::ConstraintUnique, static_cast< QgsFieldConstraints::ConstraintStrength >( uniqueStrength ) );
fieldConstraints.setConstraintStrength( QgsFieldConstraints::ConstraintNotNull, static_cast< QgsFieldConstraints::ConstraintStrength >( notNullStrength ) );
fieldConstraints.setConstraintStrength( QgsFieldConstraints::ConstraintExpression, static_cast< QgsFieldConstraints::ConstraintStrength >( expStrength ) );
}
// Constraint expressions
// always keep provider constraints intact
if ( fieldConstraints.constraintOrigin( QgsFieldConstraints::ConstraintExpression ) != QgsFieldConstraints::ConstraintOriginProvider )
{
const QDomElement constraintExpressionElement = docElem.firstChildElement( QStringLiteral( "constraintExpression" ) );
if ( !constraintExpressionElement.isNull() )
{
QString expression = constraintExpressionElement.attribute( QStringLiteral( "exp" ), QString() );
QString description = constraintExpressionElement.attribute( QStringLiteral( "desc" ), QString() );
fieldConstraints.setConstraintExpression( expression, description );
}
}
config.mFieldConstraints = fieldConstraints;
const QDomElement widgetGeneralSettingsElement = docElem.firstChildElement( QStringLiteral( "widgetGeneralSettings" ) );
if ( !widgetGeneralSettingsElement.isNull() )
{
const int editable = widgetGeneralSettingsElement.attribute( QStringLiteral( "editable" ), QStringLiteral( "0" ) ).toInt();
const int reuse = widgetGeneralSettingsElement.attribute( QStringLiteral( "reuse_last_values" ), QStringLiteral( "0" ) ).toInt();
const int labelOnTop = widgetGeneralSettingsElement.attribute( QStringLiteral( "label_on_top" ), QStringLiteral( "0" ) ).toInt();
config.mEditable = editable;
config.mReuseLastValues = reuse;
config.mLabelOnTop = labelOnTop;
}
loadAttributeTypeDialogFromConfiguration( config );
// Widget display section
if ( mAttributeWidgetEdit )
{
const QDomElement displayElement = docElem.firstChildElement( QStringLiteral( "widgetDisplay" ) );
if ( !displayElement.isNull() )
{
const int showLabel = displayElement.attribute( QStringLiteral( "showLabel" ), QStringLiteral( "0" ) ).toInt();
const int horizontalStretch = displayElement.attribute( QStringLiteral( "horizontalStretch" ), QStringLiteral( "0" ) ).toInt();
const int verticalStretch = displayElement.attribute( QStringLiteral( "verticalStretch" ), QStringLiteral( "0" ) ).toInt();
QgsAttributeEditorElement::LabelStyle style;
style.readXml( displayElement );
// Update current GUI controls
mAttributeWidgetEdit->setShowLabel( showLabel );
mAttributeWidgetEdit->setHorizontalStretch( horizontalStretch );
mAttributeWidgetEdit->setVerticalStretch( verticalStretch );
mAttributeWidgetEdit->setLabelStyle( style );
}
}
}
}