Attributes forms: make sure an internal moved index moves all its children by serializing the editor element (Available Widgets items don't need the whole editor serialization, so each model has its own mime type ); avoid serializing redundant child items, since a parent will anyway include all its children; avoid drag and drop of Category items (widgetType) from Available Widgets

This commit is contained in:
Germán Carrillo 2025-04-05 13:23:44 -05:00
parent 2a32d0c2ef
commit 26a358e574
4 changed files with 365 additions and 243 deletions

View File

@ -20,6 +20,7 @@
#include "qgseditorwidgetregistry.h"
#include "qgsattributeeditoraction.h"
#include "qgsattributeeditorcontainer.h"
#include "qgsattributeeditorfield.h"
#include "qgsattributeeditorrelation.h"
#include "qgsattributeeditorqmlelement.h"
#include "qgsattributeeditorhtmlelement.h"
@ -352,10 +353,11 @@ bool QgsAttributesFormTreeNode::setData( int role, const QVariant &value )
// QgsAttributesFormModel implementation
//
QgsAttributesFormModel::QgsAttributesFormModel( QgsVectorLayer *layer, QObject *parent )
QgsAttributesFormModel::QgsAttributesFormModel( QgsVectorLayer *layer, QgsProject *project, QObject *parent )
: QAbstractItemModel( parent )
, mRootNode( std::make_unique< QgsAttributesFormTreeNode >() )
, mLayer( layer )
, mProject( project )
{
}
@ -387,46 +389,6 @@ int QgsAttributesFormModel::columnCount( const QModelIndex &parent ) const
return 1;
}
QStringList QgsAttributesFormModel::mimeTypes() const
{
return QStringList() << QStringLiteral( "application/x-qgsattributetabledesignerelement" );
}
QMimeData *QgsAttributesFormModel::mimeData( const QModelIndexList &indexes ) const
{
if ( indexes.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 );
// Sort indexes since their order reflects selection order
QModelIndexList sortedIndexes = indexes;
std::sort( sortedIndexes.begin(), sortedIndexes.end() );
for ( const QModelIndex &index : std::as_const( sortedIndexes ) )
{
if ( index.isValid() )
{
const QString nodeId = index.data( QgsAttributesFormModel::NodeIdRole ).toString();
const QString nodeName = index.data( QgsAttributesFormModel::NodeNameRole ).toString();
int nodeType = index.data( QgsAttributesFormModel::NodeTypeRole ).toInt();
const auto nodeData = index.data( QgsAttributesFormModel::NodeDataRole );
stream << nodeId << nodeType << nodeName << nodeData;
}
}
data->setData( format, encoded );
return data;
}
QModelIndex QgsAttributesFormModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( !hasIndex( row, column, parent ) )
@ -473,8 +435,7 @@ QModelIndex QgsAttributesFormModel::firstRecursiveMatchingModelIndex( const QgsA
//
QgsAttributesAvailableWidgetsModel::QgsAttributesAvailableWidgetsModel( QgsVectorLayer *layer, QgsProject *project, QObject *parent )
: QgsAttributesFormModel( layer, parent )
, mProject( project )
: QgsAttributesFormModel( layer, project, parent )
{
}
@ -483,7 +444,15 @@ Qt::ItemFlags QgsAttributesAvailableWidgetsModel::flags( const QModelIndex &inde
if ( !index.isValid() )
return Qt::NoItemFlags;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
Qt::ItemFlags flags = Qt::ItemIsEnabled;
const auto indexType = static_cast< QgsAttributesFormTreeData::AttributesFormTreeNodeType >( index.data( QgsAttributesFormModel::NodeTypeRole ).toInt() );
if ( indexType != QgsAttributesFormTreeData::WidgetType )
{
flags = flags | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable;
}
return flags;
}
QVariant QgsAttributesAvailableWidgetsModel::headerData( int section, Qt::Orientation orientation, int role ) const
@ -680,6 +649,45 @@ Qt::DropActions QgsAttributesAvailableWidgetsModel::supportedDragActions() const
return Qt::CopyAction;
}
QStringList QgsAttributesAvailableWidgetsModel::mimeTypes() const
{
return QStringList() << QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" );
}
QMimeData *QgsAttributesAvailableWidgetsModel::mimeData( const QModelIndexList &indexes ) const
{
if ( indexes.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 );
// Sort indexes since their order reflects selection order
QModelIndexList sortedIndexes = indexes;
std::sort( sortedIndexes.begin(), sortedIndexes.end() );
for ( const QModelIndex &index : std::as_const( sortedIndexes ) )
{
if ( index.isValid() )
{
const QString nodeId = index.data( QgsAttributesFormModel::NodeIdRole ).toString();
const QString nodeName = index.data( QgsAttributesFormModel::NodeNameRole ).toString();
int nodeType = index.data( QgsAttributesFormModel::NodeTypeRole ).toInt();
stream << nodeId << nodeType << nodeName;
}
}
data->setData( format, encoded );
return data;
}
QModelIndex QgsAttributesAvailableWidgetsModel::fieldContainer() const
{
if ( mRootNode->childCount() > 0 )
@ -722,8 +730,8 @@ QModelIndex QgsAttributesAvailableWidgetsModel::fieldModelIndex( const QString &
// QgsAttributesFormLayoutModel
//
QgsAttributesFormLayoutModel::QgsAttributesFormLayoutModel( QgsVectorLayer *layer, QObject *parent )
: QgsAttributesFormModel( layer, parent )
QgsAttributesFormLayoutModel::QgsAttributesFormLayoutModel( QgsVectorLayer *layer, QgsProject *project, QObject *parent )
: QgsAttributesFormModel( layer, project, parent )
{
}
@ -761,7 +769,7 @@ void QgsAttributesFormLayoutModel::populate()
endResetModel();
}
void QgsAttributesFormLayoutModel::loadAttributeEditorElementItem( QgsAttributeEditorElement *const editorElement, QgsAttributesFormTreeNode *parent )
void QgsAttributesFormLayoutModel::loadAttributeEditorElementItem( QgsAttributeEditorElement *const editorElement, QgsAttributesFormTreeNode *parent, const int position )
{
auto setCommonProperties = [editorElement]( QgsAttributesFormTreeData::DnDTreeNodeData &itemData ) {
itemData.setShowLabel( editorElement->showLabel() );
@ -828,8 +836,14 @@ void QgsAttributesFormLayoutModel::loadAttributeEditorElementItem( QgsAttributeE
relationEditorConfig.label = relationEditor->label();
itemData.setRelationEditorConfiguration( relationEditorConfig );
editorItem->setData( NodeIdRole, relationEditor->relation().id() );
editorItem->setData( NodeNameRole, relationEditor->relation().name() );
QgsRelation relation = relationEditor->relation();
if ( !relation.isValid() )
{
relation = mProject->relationManager()->relation( editorElement->name() );
}
editorItem->setData( NodeIdRole, relation.id() );
editorItem->setData( NodeNameRole, relation.name() );
editorItem->setData( NodeTypeRole, QgsAttributesFormTreeData::Relation );
editorItem->setData( NodeDataRole, itemData );
@ -937,7 +951,14 @@ void QgsAttributesFormLayoutModel::loadAttributeEditorElementItem( QgsAttributeE
}
}
parent->addChildNode( std::move( editorItem ) );
if ( position >= 0 && position < parent->childCount() )
{
parent->insertChildNode( position, std::move( editorItem ) );
}
else
{
parent->addChildNode( std::move( editorItem ) );
}
}
QVariant QgsAttributesFormLayoutModel::data( const QModelIndex &index, int role ) const
@ -1071,6 +1092,100 @@ Qt::DropActions QgsAttributesFormLayoutModel::supportedDropActions() const
return Qt::DropAction::CopyAction | Qt::DropAction::MoveAction;
}
QStringList QgsAttributesFormLayoutModel::mimeTypes() const
{
return QStringList() << QStringLiteral( "application/x-qgsattributesformlayoutelement" ) << QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" );
}
QModelIndexList QgsAttributesFormLayoutModel::curateIndexesForMimeData( const QModelIndexList &indexes ) const
{
QModelIndexList containerList;
for ( const auto index : indexes )
{
const auto indexType = static_cast< QgsAttributesFormTreeData::AttributesFormTreeNodeType >( index.data( QgsAttributesFormModel::NodeTypeRole ).toInt() );
if ( indexType == QgsAttributesFormTreeData::Container )
{
containerList << index;
}
}
if ( containerList.size() == 0 )
return indexes;
QModelIndexList curatedIndexes;
// Iterate searching if current index is child of any container in containerList (recursively)
for ( const auto index : indexes )
{
QModelIndex parent = index.parent();
bool redundantChild = false;
while ( parent.isValid() )
{
if ( containerList.contains( parent ) )
{
redundantChild = true;
break;
}
parent = parent.parent();
}
if ( !redundantChild )
curatedIndexes << index;
}
return curatedIndexes;
}
QMimeData *QgsAttributesFormLayoutModel::mimeData( const QModelIndexList &indexes ) const
{
if ( indexes.count() == 0 )
return nullptr;
// Discard redundant indexes
QModelIndexList curatedIndexes;
if ( indexes.count() > 1 )
{
curatedIndexes = curateIndexesForMimeData( indexes );
}
else
{
curatedIndexes = indexes;
}
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 );
// Sort indexes since their order reflects selection order
std::sort( curatedIndexes.begin(), curatedIndexes.end() );
for ( const QModelIndex &index : std::as_const( curatedIndexes ) )
{
if ( index.isValid() )
{
QDomDocument doc;
QDomElement rootElem = doc.createElement( QStringLiteral( "form_layout_mime" ) );
QgsAttributeEditorElement *editor = createAttributeEditorWidget( index, nullptr );
QDomElement editorElem = editor->toDomElement( doc );
rootElem.appendChild( editorElem );
doc.appendChild( rootElem );
stream << doc.toString( -1 );
}
}
data->setData( format, encoded );
return data;
}
bool QgsAttributesFormLayoutModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
{
Q_UNUSED( column )
@ -1084,34 +1199,59 @@ bool QgsAttributesFormLayoutModel::dropMimeData( const QMimeData *data, Qt::Drop
{
bDropSuccessful = true;
}
else if ( data->hasFormat( QStringLiteral( "application/x-qgsattributetabledesignerelement" ) ) )
else if ( data->hasFormat( QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" ) ) )
{
QByteArray itemData = data->data( QStringLiteral( "application/x-qgsattributetabledesignerelement" ) );
Q_ASSERT( action == Qt::CopyAction ); // External drop
QByteArray itemData = data->data( QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" ) );
QDataStream stream( &itemData, QIODevice::ReadOnly );
QgsAttributesFormTreeData::DnDTreeNodeData itemElement;
while ( !stream.atEnd() )
{
QString nodeId;
int nodeTypeInt;
QString nodeName;
QgsAttributesFormTreeData::DnDTreeNodeData nodeData;
stream >> nodeId >> nodeTypeInt >> nodeName >> nodeData;
stream >> nodeId >> nodeTypeInt >> nodeName;
const auto nodeType = static_cast< QgsAttributesFormTreeData::AttributesFormTreeNodeType >( nodeTypeInt );
insertChild( parent, row + rows, nodeId, nodeType, nodeName, nodeData );
insertChild( parent, row + rows, nodeId, nodeType, nodeName );
bDropSuccessful = true;
QModelIndex addedIndex = index( row + rows, 0, parent );
if ( action == Qt::CopyAction ) // External drop
{
emit externalNodeDropped( addedIndex );
}
else if ( action == Qt::MoveAction ) // Internal move
{
emit internalNodeDropped( addedIndex );
}
emit externalNodeDropped( addedIndex );
rows++;
}
}
else if ( data->hasFormat( QStringLiteral( "application/x-qgsattributesformlayoutelement" ) ) )
{
Q_ASSERT( action == Qt::MoveAction ); // Internal move
QByteArray itemData = data->data( QStringLiteral( "application/x-qgsattributesformlayoutelement" ) );
QDataStream stream( &itemData, QIODevice::ReadOnly );
while ( !stream.atEnd() )
{
QString text;
stream >> text;
QDomDocument doc;
if ( !doc.setContent( text ) )
continue;
const QDomElement rootElem = doc.documentElement();
if ( rootElem.tagName() != QLatin1String( "form_layout_mime" ) || !rootElem.hasChildNodes() )
continue;
const QDomElement childElem = rootElem.firstChild().toElement();
// Build editor element from XML and add/insert it to parent
QgsAttributeEditorElement *editor = QgsAttributeEditorElement::create( childElem, mLayer->id(), mLayer->fields(), QgsReadWriteContext() );
beginInsertRows( parent, row + rows, row + rows );
loadAttributeEditorElementItem( editor, nodeForIndex( parent ), row + rows );
endInsertRows();
bDropSuccessful = true;
QModelIndex addedIndex = index( row + rows, 0, parent );
emit internalNodeDropped( addedIndex );
rows++;
}
@ -1164,6 +1304,124 @@ QList< QgsAddAttributeFormContainerDialog::ContainerPair > QgsAttributesFormLayo
return containerList;
}
QgsAttributeEditorElement *QgsAttributesFormLayoutModel::createAttributeEditorWidget( const QModelIndex &index, QgsAttributeEditorElement *parent ) const
{
QgsAttributeEditorElement *widgetDef = nullptr;
const QgsAttributesFormTreeData::DnDTreeNodeData itemData = index.data( QgsAttributesFormModel::NodeDataRole ).value<QgsAttributesFormTreeData::DnDTreeNodeData>();
const int indexType = static_cast< QgsAttributesFormTreeData::AttributesFormTreeNodeType >( index.data( QgsAttributesFormModel::NodeTypeRole ).toInt() );
const QString indexName = index.data( QgsAttributesFormModel::NodeNameRole ).toString();
const QString indexId = index.data( QgsAttributesFormModel::NodeIdRole ).toString();
switch ( indexType )
{
case QgsAttributesFormTreeData::Field:
{
const int fieldIndex = mLayer->fields().lookupField( indexName );
widgetDef = new QgsAttributeEditorField( indexName, fieldIndex, parent );
break;
}
case QgsAttributesFormTreeData::Action:
{
const QgsAction action { mLayer->actions()->action( indexId ) };
widgetDef = new QgsAttributeEditorAction( action, parent );
break;
}
case QgsAttributesFormTreeData::Relation:
{
const QgsRelation relation = mProject->relationManager()->relation( indexId );
QgsAttributeEditorRelation *relDef = new QgsAttributeEditorRelation( relation, parent );
const QgsAttributesFormTreeData::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 QgsAttributesFormTreeData::Container:
{
QgsAttributeEditorContainer *container = new QgsAttributeEditorContainer( indexName, parent, itemData.backgroundColor() );
container->setColumnCount( itemData.columnCount() );
// only top-level containers can be tabs
Qgis::AttributeEditorContainerType type = itemData.containerType();
bool isTopLevel = !index.parent().isValid();
if ( type == Qgis::AttributeEditorContainerType::Tab && !isTopLevel )
{
// a tab 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() );
QModelIndex childIndex;
for ( int t = 0; t < rowCount( index ); t++ )
{
childIndex = this->index( t, 0, index );
QgsAttributeEditorElement *element { createAttributeEditorWidget( childIndex, container ) };
if ( element )
container->addChildElement( element );
}
widgetDef = container;
break;
}
case QgsAttributesFormTreeData::QmlWidget:
{
QgsAttributeEditorQmlElement *element = new QgsAttributeEditorQmlElement( indexName, parent );
element->setQmlCode( itemData.qmlElementEditorConfiguration().qmlCode );
widgetDef = element;
break;
}
case QgsAttributesFormTreeData::HtmlWidget:
{
QgsAttributeEditorHtmlElement *element = new QgsAttributeEditorHtmlElement( indexName, parent );
element->setHtmlCode( itemData.htmlElementEditorConfiguration().htmlCode );
widgetDef = element;
break;
}
case QgsAttributesFormTreeData::TextWidget:
{
QgsAttributeEditorTextElement *element = new QgsAttributeEditorTextElement( indexName, parent );
element->setText( itemData.textElementEditorConfiguration().text );
widgetDef = element;
break;
}
case QgsAttributesFormTreeData::SpacerWidget:
{
QgsAttributeEditorSpacerElement *element = new QgsAttributeEditorSpacerElement( indexName, parent );
element->setDrawLine( itemData.spacerElementEditorConfiguration().drawLine );
widgetDef = element;
break;
}
case QgsAttributesFormTreeData::WidgetType:
default:
break;
}
if ( widgetDef )
{
widgetDef->setShowLabel( itemData.showLabel() );
widgetDef->setLabelStyle( itemData.labelStyle() );
widgetDef->setHorizontalStretch( itemData.horizontalStretch() );
widgetDef->setVerticalStretch( itemData.verticalStretch() );
}
return widgetDef;
}
QList< QgsAddAttributeFormContainerDialog::ContainerPair > QgsAttributesFormLayoutModel::listOfContainers() const
{
return recursiveListOfContainers( mRootNode.get() );
@ -1187,7 +1445,7 @@ void QgsAttributesFormLayoutModel::addContainer( QModelIndex &parent, const QStr
endInsertRows();
}
void QgsAttributesFormLayoutModel::insertChild( const QModelIndex &parent, int row, QString &nodeId, QgsAttributesFormTreeData::AttributesFormTreeNodeType nodeType, QString &nodeName, QgsAttributesFormTreeData::DnDTreeNodeData nodeData )
void QgsAttributesFormLayoutModel::insertChild( const QModelIndex &parent, int row, QString &nodeId, QgsAttributesFormTreeData::AttributesFormTreeNodeType nodeType, QString &nodeName )
{
if ( row < 0 )
return;
@ -1198,7 +1456,6 @@ void QgsAttributesFormLayoutModel::insertChild( const QModelIndex &parent, int r
node->setData( QgsAttributesFormModel::NodeIdRole, nodeId );
node->setData( QgsAttributesFormModel::NodeTypeRole, nodeType );
node->setData( QgsAttributesFormModel::NodeNameRole, nodeName );
node->setData( QgsAttributesFormModel::NodeDataRole, nodeData );
nodeForIndex( parent )->insertChildNode( row, std::move( node ) );
endInsertRows();
@ -1214,24 +1471,3 @@ void QgsAttributesFormLayoutModel::setShowAliases( bool show )
mShowAliases = show;
emit dataChanged( QModelIndex(), QModelIndex(), QVector<int>() << Qt::DisplayRole << Qt::ForegroundRole << Qt::FontRole );
}
/*
* Serialization helpers for DesigerTreeItemData so we can stuff this easily into QMimeData
*/
QDataStream &operator<<( QDataStream &stream, const QgsAttributesFormTreeData::DnDTreeNodeData &data )
{
QVariant streamData = QVariant::fromValue<QgsAttributesFormTreeData::DnDTreeNodeData>( data );
stream << streamData;
return stream;
}
QDataStream &operator>>( QDataStream &stream, QgsAttributesFormTreeData::DnDTreeNodeData &data )
{
QVariant streamData;
stream >> streamData;
data = streamData.value< QgsAttributesFormTreeData::DnDTreeNodeData >();
return stream;
}

View File

@ -544,12 +544,11 @@ class GUI_EXPORT QgsAttributesFormModel : public QAbstractItemModel
};
/**
* Constructor for QgsAttributesFormModel, with the given \a layer object and
* the given \a parent.
* Constructor for QgsAttributesFormModel, with the given \a parent.
*
* The \a layer is the data source to populate the model.
* The given \a layer and \a project are data sources to populate the model.
*/
explicit QgsAttributesFormModel( QgsVectorLayer *layer, QObject *parent = nullptr );
explicit QgsAttributesFormModel( QgsVectorLayer *layer, QgsProject *project, QObject *parent = nullptr );
~QgsAttributesFormModel() override;
@ -560,10 +559,6 @@ class GUI_EXPORT QgsAttributesFormModel : public QAbstractItemModel
int rowCount( const QModelIndex &parent = QModelIndex() ) const override;
int columnCount( const QModelIndex &parent = QModelIndex() ) const override;
// Drag and drop support (common methods)
QStringList mimeTypes() const override;
QMimeData *mimeData( const QModelIndexList &indexes ) const override;
// Other methods
/**
* Returns the first top-level model index that matches the given \a nodeType and \a nodeId.
@ -595,6 +590,7 @@ class GUI_EXPORT QgsAttributesFormModel : public QAbstractItemModel
std::unique_ptr< QgsAttributesFormTreeNode > mRootNode;
QgsVectorLayer *mLayer;
QgsProject *mProject;
};
@ -625,6 +621,8 @@ class GUI_EXPORT QgsAttributesAvailableWidgetsModel : public QgsAttributesFormMo
// Drag and drop support
Qt::DropActions supportedDragActions() const override;
QStringList mimeTypes() const override;
QMimeData *mimeData( const QModelIndexList &indexes ) const override;
// Other methods
@ -649,9 +647,6 @@ class GUI_EXPORT QgsAttributesAvailableWidgetsModel : public QgsAttributesFormMo
public slots:
void populate() override;
private:
QgsProject *mProject;
};
@ -671,8 +666,9 @@ class GUI_EXPORT QgsAttributesFormLayoutModel : public QgsAttributesFormModel
* Constructor for QgsAttributesFormLayoutModel, with the given \a parent.
*
* The given \a layer is the data source to populate the model.
* The given \a project is used to extract information about relations.
*/
explicit QgsAttributesFormLayoutModel( QgsVectorLayer *layer, QObject *parent = nullptr );
explicit QgsAttributesFormLayoutModel( QgsVectorLayer *layer, QgsProject *project, QObject *parent = nullptr );
Qt::ItemFlags flags( const QModelIndex &index ) const override;
QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override;
@ -690,10 +686,17 @@ class GUI_EXPORT QgsAttributesFormLayoutModel : public QgsAttributesFormModel
// Drag and drop support
Qt::DropActions supportedDragActions() const override;
Qt::DropActions supportedDropActions() const override;
QStringList mimeTypes() const override;
QMimeData *mimeData( const QModelIndexList &indexes ) const override;
bool dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent ) override;
bool removeRows( int row, int count, const QModelIndex &parent = QModelIndex() ) override;
// Other methods
/**
* Creates a new attribute editor element based on the definition stored in a form layout model \a index.
*/
QgsAttributeEditorElement *createAttributeEditorWidget( const QModelIndex &index, QgsAttributeEditorElement *parent ) const;
/**
* Returns a list of containers stored in the model, structured as pairs (name, container model index).
*/
@ -719,9 +722,9 @@ class GUI_EXPORT QgsAttributesFormLayoutModel : public QgsAttributesFormModel
/**
* Inserts a new child to \a parent model index at the given \a row position.
*
* The child is constructed from the given \a nodeId, \a nodeType, \a nodeName and \a nodeData.
* The child is constructed from the given \a nodeId, \a nodeType and \a nodeName.
*/
void insertChild( const QModelIndex &parent, int row, QString &nodeId, QgsAttributesFormTreeData::AttributesFormTreeNodeType nodeType, QString &nodeName, QgsAttributesFormTreeData::DnDTreeNodeData nodeData );
void insertChild( const QModelIndex &parent, int row, QString &nodeId, QgsAttributesFormTreeData::AttributesFormTreeNodeType nodeType, QString &nodeName );
/**
* Returns whether field aliases are preferred over field names as item text.
@ -749,16 +752,21 @@ class GUI_EXPORT QgsAttributesFormLayoutModel : public QgsAttributesFormModel
private:
void updateAliasForFieldNodesRecursive( QgsAttributesFormTreeNode *parent, const QString &fieldName, const QString &fieldAlias );
QList< QgsAddAttributeFormContainerDialog::ContainerPair > recursiveListOfContainers( QgsAttributesFormTreeNode *parent ) const;
void loadAttributeEditorElementItem( QgsAttributeEditorElement *const editorElement, QgsAttributesFormTreeNode *parent );
void loadAttributeEditorElementItem( QgsAttributeEditorElement *const editorElement, QgsAttributesFormTreeNode *parent, const int position = -1 );
/**
* Creates a list of indexes filtering children whose parents are already included.
*
* This discards redundant indexes before creating MimeData, because a parent will
* include all its children, grandchildren, great-grandchildren, etc.
*
* \param indexes Input list of indexes, potentially with redundant indexes.
*/
QModelIndexList curateIndexesForMimeData( const QModelIndexList &indexes ) const;
bool mShowAliases = false;
};
QDataStream &operator<<( QDataStream &stream, const QgsAttributesFormTreeData::DnDTreeNodeData &data );
QDataStream &operator>>( QDataStream &stream, QgsAttributesFormTreeData::DnDTreeNodeData &data );
Q_DECLARE_METATYPE( QgsAttributesFormTreeData::RelationEditorConfiguration )
Q_DECLARE_METATYPE( QgsAttributesFormTreeData::FieldConfig )
Q_DECLARE_METATYPE( QgsAttributesFormTreeData::DnDTreeNodeData )

View File

@ -15,8 +15,6 @@
#include "qgsactionmanager.h"
#include "qgsaddtaborgroup.h"
#include "qgsattributeeditorspacerelement.h"
#include "qgsattributeeditortextelement.h"
#include "qgsattributesformproperties.h"
#include "moc_qgsattributesformproperties.cpp"
#include "qgsattributetypedialog.h"
@ -29,14 +27,8 @@
#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"
@ -80,7 +72,7 @@ QgsAttributesFormProperties::QgsAttributesFormProperties( QgsVectorLayer *layer,
formLayoutWidgetLayout->addWidget( mFormLayoutTreeView );
formLayoutWidgetLayout->setContentsMargins( 0, 0, 0, 0 );
mFormLayoutModel = new QgsAttributesFormLayoutModel( mLayer, this );
mFormLayoutModel = new QgsAttributesFormLayoutModel( mLayer, QgsProject().instance(), this );
mFormLayoutTreeView->setModel( mFormLayoutModel );
connect( mAvailableWidgetsTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsAttributesFormProperties::onAttributeSelectionChanged );
@ -658,123 +650,6 @@ void QgsAttributesFormProperties::removeTabOrGroupButton()
}
}
QgsAttributeEditorElement *QgsAttributesFormProperties::createAttributeEditorWidget( const QModelIndex &index, QgsAttributeEditorElement *parent, bool isTopLevel )
{
QgsAttributeEditorElement *widgetDef = nullptr;
const QgsAttributesFormTreeData::DnDTreeNodeData itemData = index.data( QgsAttributesFormModel::NodeDataRole ).value<QgsAttributesFormTreeData::DnDTreeNodeData>();
const int indexType = static_cast< QgsAttributesFormTreeData::AttributesFormTreeNodeType >( index.data( QgsAttributesFormModel::NodeTypeRole ).toInt() );
const QString indexName = index.data( QgsAttributesFormModel::NodeNameRole ).toString();
const QString indexId = index.data( QgsAttributesFormModel::NodeIdRole ).toString();
switch ( indexType )
{
case QgsAttributesFormTreeData::Field:
{
const int fieldIndex = mLayer->fields().lookupField( indexName );
widgetDef = new QgsAttributeEditorField( indexName, fieldIndex, parent );
break;
}
case QgsAttributesFormTreeData::Action:
{
const QgsAction action { mLayer->actions()->action( indexId ) };
widgetDef = new QgsAttributeEditorAction( action, parent );
break;
}
case QgsAttributesFormTreeData::Relation:
{
const QgsRelation relation = QgsProject::instance()->relationManager()->relation( indexId );
QgsAttributeEditorRelation *relDef = new QgsAttributeEditorRelation( relation, parent );
const QgsAttributesFormTreeData::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 QgsAttributesFormTreeData::Container:
{
QgsAttributeEditorContainer *container = new QgsAttributeEditorContainer( indexName, 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 tab 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() );
QModelIndex childIndex;
for ( int t = 0; t < mFormLayoutModel->rowCount( index ); t++ )
{
childIndex = mFormLayoutModel->index( t, 0, index );
QgsAttributeEditorElement *element { createAttributeEditorWidget( childIndex, container, false ) };
if ( element )
container->addChildElement( element );
}
widgetDef = container;
break;
}
case QgsAttributesFormTreeData::QmlWidget:
{
QgsAttributeEditorQmlElement *element = new QgsAttributeEditorQmlElement( indexName, parent );
element->setQmlCode( itemData.qmlElementEditorConfiguration().qmlCode );
widgetDef = element;
break;
}
case QgsAttributesFormTreeData::HtmlWidget:
{
QgsAttributeEditorHtmlElement *element = new QgsAttributeEditorHtmlElement( indexName, parent );
element->setHtmlCode( itemData.htmlElementEditorConfiguration().htmlCode );
widgetDef = element;
break;
}
case QgsAttributesFormTreeData::TextWidget:
{
QgsAttributeEditorTextElement *element = new QgsAttributeEditorTextElement( indexName, parent );
element->setText( itemData.textElementEditorConfiguration().text );
widgetDef = element;
break;
}
case QgsAttributesFormTreeData::SpacerWidget:
{
QgsAttributeEditorSpacerElement *element = new QgsAttributeEditorSpacerElement( indexName, parent );
element->setDrawLine( itemData.spacerElementEditorConfiguration().drawLine );
widgetDef = element;
break;
}
case QgsAttributesFormTreeData::WidgetType:
default:
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>();
@ -918,7 +793,7 @@ void QgsAttributesFormProperties::apply()
for ( int t = 0; t < mFormLayoutModel->rowCount(); t++ )
{
QModelIndex index = mFormLayoutModel->index( t, 0 );
QgsAttributeEditorElement *editorElement { createAttributeEditorWidget( index, nullptr, true ) };
QgsAttributeEditorElement *editorElement { mFormLayoutModel->createAttributeEditorWidget( index, nullptr ) };
if ( editorElement )
editFormConfig.addTab( editorElement );
}
@ -1071,16 +946,22 @@ void QgsAttributesFormLayoutTreeView::handleExternalDroppedNode( QModelIndex &in
}
}
void QgsAttributesFormLayoutTreeView::handleInternalDroppedNode( QModelIndex & )
void QgsAttributesFormLayoutTreeView::handleInternalDroppedNode( QModelIndex &index )
{
selectionModel()->clearCurrentIndex();
const auto nodeType = static_cast< QgsAttributesFormTreeData::AttributesFormTreeNodeType >( index.data( QgsAttributesFormModel::NodeTypeRole ).toInt() );
if ( nodeType == QgsAttributesFormTreeData::Container )
{
expandRecursively( index );
}
}
void QgsAttributesFormLayoutTreeView::dragEnterEvent( QDragEnterEvent *event )
{
const QMimeData *data = event->mimeData();
if ( data->hasFormat( QStringLiteral( "application/x-qgsattributetabledesignerelement" ) ) )
if ( data->hasFormat( QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" ) )
|| data->hasFormat( QStringLiteral( "application/x-qgsattributesformlayoutelement" ) ) )
{
// Inner drag and drop actions are always MoveAction
if ( event->source() == this )
@ -1104,7 +985,8 @@ void QgsAttributesFormLayoutTreeView::dragMoveEvent( QDragMoveEvent *event )
{
const QMimeData *data = event->mimeData();
if ( data->hasFormat( QStringLiteral( "application/x-qgsattributetabledesignerelement" ) ) )
if ( data->hasFormat( QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" ) )
|| data->hasFormat( QStringLiteral( "application/x-qgsattributesformlayoutelement" ) ) )
{
// Inner drag and drop actions are always MoveAction
if ( event->source() == this )
@ -1122,7 +1004,8 @@ void QgsAttributesFormLayoutTreeView::dragMoveEvent( QDragMoveEvent *event )
void QgsAttributesFormLayoutTreeView::dropEvent( QDropEvent *event )
{
if ( !event->mimeData()->hasFormat( QStringLiteral( "application/x-qgsattributetabledesignerelement" ) ) )
if ( !( event->mimeData()->hasFormat( QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" ) )
|| event->mimeData()->hasFormat( QStringLiteral( "application/x-qgsattributesformlayoutelement" ) ) ) )
return;
if ( event->source() == this )

View File

@ -66,11 +66,6 @@ class GUI_EXPORT QgsAttributesFormProperties : public QWidget, public QgsExpress
explicit QgsAttributesFormProperties( QgsVectorLayer *layer, QWidget *parent = nullptr );
/**
* Creates a new attribute editor element based on the definition stored in a form layout model \a index.
*/
QgsAttributeEditorElement *createAttributeEditorWidget( const QModelIndex &index, QgsAttributeEditorElement *parent, bool isTopLevel = false );
void init();
/**