diff --git a/src/gui/vector/qgsattributesformmodel.cpp b/src/gui/vector/qgsattributesformmodel.cpp index d8b4c0474bf..fffc2eb8f2b 100644 --- a/src/gui/vector/qgsattributesformmodel.cpp +++ b/src/gui/vector/qgsattributesformmodel.cpp @@ -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(); + 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() << 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( data ); - stream << streamData; - return stream; -} - -QDataStream &operator>>( QDataStream &stream, QgsAttributesFormTreeData::DnDTreeNodeData &data ) -{ - QVariant streamData; - stream >> streamData; - data = streamData.value< QgsAttributesFormTreeData::DnDTreeNodeData >(); - - return stream; -} diff --git a/src/gui/vector/qgsattributesformmodel.h b/src/gui/vector/qgsattributesformmodel.h index e4c1acafb9d..fe17e56eabe 100644 --- a/src/gui/vector/qgsattributesformmodel.h +++ b/src/gui/vector/qgsattributesformmodel.h @@ -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 ) diff --git a/src/gui/vector/qgsattributesformproperties.cpp b/src/gui/vector/qgsattributesformproperties.cpp index 289c3072568..cb517dcf9b5 100644 --- a/src/gui/vector/qgsattributesformproperties.cpp +++ b/src/gui/vector/qgsattributesformproperties.cpp @@ -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(); - 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(); @@ -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 ) diff --git a/src/gui/vector/qgsattributesformproperties.h b/src/gui/vector/qgsattributesformproperties.h index a0686be0c25..b7a50cd75aa 100644 --- a/src/gui/vector/qgsattributesformproperties.h +++ b/src/gui/vector/qgsattributesformproperties.h @@ -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(); /**