QGIS API Documentation 3.43.0-Master (261ee7da134)
qgsattributesformmodel.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsattributesformmodel.cpp
3 ---------------------
4 begin : March 2025
5 copyright : (C) 2025 by Germán Carrillo
6 email : german at opengis dot ch
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsactionmanager.h"
18#include "moc_qgsattributesformmodel.cpp"
19#include "qgsgui.h"
29
30#include <QMimeData>
31
32
34{
35 if ( !layer || idx < 0 || idx >= layer->fields().count() )
36 return;
37
38 mAlias = layer->fields().at( idx ).alias();
40 mComment = layer->fields().at( idx ).comment();
41 mEditable = !layer->editFormConfig().readOnly( idx );
42 mLabelOnTop = layer->editFormConfig().labelOnTop( idx );
44 mFieldConstraints = layer->fields().at( idx ).constraints();
45 const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( layer, layer->fields().field( idx ).name() );
46 mEditorWidgetType = setup.type();
48 mSplitPolicy = layer->fields().at( idx ).splitPolicy();
49 mDuplicatePolicy = layer->fields().at( idx ).duplicatePolicy();
50 mMergePolicy = layer->fields().at( idx ).mergePolicy();
51}
52
53QgsAttributesFormData::FieldConfig::operator QVariant()
54{
55 return QVariant::fromValue<QgsAttributesFormData::FieldConfig>( *this );
56}
57
58QgsAttributesFormData::RelationEditorConfiguration::operator QVariant()
59{
60 return QVariant::fromValue<QgsAttributesFormData::RelationEditorConfiguration>( *this );
61}
62
67
72
77
82
84{
85 return mShowLabel;
86}
87
89{
90 mShowLabel = showLabel;
91}
92
97
99{
100 mVisibilityExpression = visibilityExpression;
101}
102
107
109{
110 mCollapsedExpression = collapsedExpression;
111}
112
117
119{
120 mRelationEditorConfiguration = relationEditorConfiguration;
121}
122
127
129{
130 mQmlElementEditorConfiguration = qmlElementEditorConfiguration;
131}
132
133
138
140{
141 mHtmlElementEditorConfiguration = htmlElementEditorConfiguration;
142}
143
148
150{
151 mSpacerElementEditorConfiguration = spacerElementEditorConfiguration;
152}
153
155{
156 return mBackgroundColor;
157}
158
160{
161 mBackgroundColor = backgroundColor;
162}
163
168
170{
171 mTextElementEditorConfiguration = textElementEditorConfiguration;
172}
173
174
176 : mName( name )
177 , mDisplayName( displayName )
178 , mType( itemType )
179 , mParent( parent )
180{}
181
183 : mName( name )
184 , mDisplayName( displayName )
185 , mType( itemType )
186 , mData( data )
187 , mParent( parent )
188{}
189
191{
192 if ( !mChildren.empty() && row >= 0 && row < childCount() )
193 return mChildren.at( row ).get();
194
195 return nullptr;
196}
197
199{
200 if ( !mChildren.empty() && itemId.trimmed().isEmpty() )
201 return nullptr;
202
203 // Search for first matching item by name
204 const auto it = std::find_if( mChildren.cbegin(), mChildren.cend(), [itemType, itemId]( const std::unique_ptr< QgsAttributesFormItem > &item ) {
205 return item->type() == itemType && item->id() == itemId;
206 } );
207
208 if ( it != mChildren.cend() )
209 return it->get();
210
211 return nullptr;
212}
213
215{
216 if ( !mChildren.empty() && itemId.trimmed().isEmpty() )
217 return nullptr;
218
219 for ( const auto &child : std::as_const( mChildren ) )
220 {
221 if ( child->type() == itemType && child->id() == itemId )
222 return child.get();
223
224 if ( child->childCount() > 0 )
225 {
226 QgsAttributesFormItem *item = child->firstChildRecursive( itemType, itemId );
227 if ( item )
228 return item;
229 }
230 }
231
232 return nullptr;
233}
234
236{
237 return static_cast< int >( mChildren.size() );
238}
239
241{
242 if ( !mParent )
243 return 0;
244
245 const auto it = std::find_if( mParent->mChildren.cbegin(), mParent->mChildren.cend(), [this]( const std::unique_ptr< QgsAttributesFormItem > &item ) {
246 return item.get() == this;
247 } );
248
249 if ( it != mParent->mChildren.cend() )
250 {
251 return static_cast< int >( std::distance( mParent->mChildren.cbegin(), it ) );
252 }
253
254 return -1;
255}
256
257void QgsAttributesFormItem::addChild( std::unique_ptr< QgsAttributesFormItem > &&item )
258{
259 if ( !item )
260 return;
261
262 if ( !item->mParent )
263 item->mParent = this;
264
265 mChildren.push_back( std::move( item ) );
266}
267
268void QgsAttributesFormItem::insertChild( int position, std::unique_ptr< QgsAttributesFormItem > &&item )
269{
270 if ( position < 0 || position > static_cast< int >( mChildren.size() ) || !item )
271 return;
272
273 if ( !item->mParent )
274 item->mParent = this;
275
276 mChildren.insert( mChildren.begin() + position, std::move( item ) );
277}
278
280{
281 if ( index >= 0 && index < static_cast< int >( mChildren.size() ) )
282 mChildren.erase( mChildren.begin() + index );
283}
284
286{
287 mChildren.clear();
288}
289
290QVariant QgsAttributesFormItem::data( int role ) const
291{
292 switch ( role )
293 {
295 return mType;
297 return QVariant::fromValue( mData );
299 return mName;
301 return mId;
303 return mDisplayName;
305 return QVariant::fromValue( mFieldConfigData );
306 default:
307 return QVariant();
308 }
309}
310
311bool QgsAttributesFormItem::setData( int role, const QVariant &value )
312{
313 switch ( role )
314 {
316 {
317 mData = value.value< QgsAttributesFormData::AttributeFormItemData >();
318 return true;
319 }
321 {
322 mName = value.toString();
323 return true;
324 }
326 {
327 mDisplayName = value.toString();
328 return true;
329 }
331 {
332 mType = static_cast<QgsAttributesFormData::AttributesFormItemType>( value.toInt() );
333 return true;
334 }
336 {
337 mId = value.toString();
338 return true;
339 }
341 {
342 mFieldConfigData = value.value< QgsAttributesFormData::FieldConfig >();
343 return true;
344 }
345 default:
346 return false;
347 }
348}
349
350
352 : QAbstractItemModel( parent )
353 , mRootItem( std::make_unique< QgsAttributesFormItem >() )
354 , mLayer( layer )
355 , mProject( project )
356{
357}
358
360
362{
363 if ( index.isValid() )
364 {
365 if ( auto *item = static_cast<QgsAttributesFormItem *>( index.internalPointer() ) )
366 return item;
367 }
368 return mRootItem.get();
369}
370
371int QgsAttributesFormModel::rowCount( const QModelIndex &parent ) const
372{
373 if ( parent.isValid() && parent.column() > 0 )
374 return 0;
375
376 const QgsAttributesFormItem *parentItem = itemForIndex( parent );
377
378 return parentItem ? parentItem->childCount() : 0;
379}
380
381int QgsAttributesFormModel::columnCount( const QModelIndex & ) const
382{
383 return 1;
384}
385
386bool QgsAttributesFormModel::indexLessThan( const QModelIndex &a, const QModelIndex &b ) const
387{
388 const QVector<int> pathA = rootToLeafPath( itemForIndex( a ) );
389 const QVector<int> pathB = rootToLeafPath( itemForIndex( b ) );
390
391 for ( int i = 0; i < std::min( pathA.size(), pathB.size() ); i++ )
392 {
393 if ( pathA.at( i ) != pathB.at( i ) )
394 {
395 return pathA.at( i ) < pathB.at( i );
396 }
397 }
398
399 return pathA.size() < pathB.size();
400}
401
403{
404 QVector<int> path;
405 if ( item != mRootItem.get() )
406 {
407 path << rootToLeafPath( item->parent() ) << item->row();
408 }
409 return path;
410}
411
412QModelIndex QgsAttributesFormModel::index( int row, int column, const QModelIndex &parent ) const
413{
414 if ( !hasIndex( row, column, parent ) )
415 return QModelIndex();
416
418 if ( !parentItem )
419 return QModelIndex();
420
421 if ( QgsAttributesFormItem *childItem = parentItem->child( row ) )
422 return createIndex( row, column, childItem );
423
424 return QModelIndex();
425}
426
427QModelIndex QgsAttributesFormModel::parent( const QModelIndex &index ) const
428{
429 if ( !index.isValid() )
430 return QModelIndex();
431
433 QgsAttributesFormItem *parentItem = childItem ? childItem->parent() : nullptr;
434
435 return ( parentItem != mRootItem.get() && parentItem != nullptr )
436 ? createIndex( parentItem->row(), 0, parentItem )
437 : QModelIndex();
438}
439
441{
442 QgsAttributesFormItem *item = mRootItem->firstTopChild( itemType, itemId );
443 return item ? createIndex( item->row(), 0, item ) : QModelIndex();
444}
445
447{
448 QgsAttributesFormItem *item = mRootItem->firstChildRecursive( itemType, itemId );
449 return item ? createIndex( item->row(), 0, item ) : QModelIndex();
450}
451
453{
454 return mShowAliases;
455}
456
458{
459 mShowAliases = show;
460 emit dataChanged( QModelIndex(), QModelIndex(), QVector<int>() << Qt::DisplayRole << Qt::ForegroundRole << Qt::FontRole );
461}
462
463
465 : QgsAttributesFormModel( layer, project, parent )
466{
467}
468
469Qt::ItemFlags QgsAttributesAvailableWidgetsModel::flags( const QModelIndex &index ) const
470{
471 if ( !index.isValid() )
472 return Qt::NoItemFlags;
473
474 Qt::ItemFlags flags = Qt::ItemIsEnabled;
475
476 const auto indexType = static_cast< QgsAttributesFormData::AttributesFormItemType >( index.data( QgsAttributesFormModel::ItemTypeRole ).toInt() );
477 if ( indexType != QgsAttributesFormData::WidgetType )
478 {
479 flags = flags | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable;
480 }
481
482 return flags;
483}
484
485QVariant QgsAttributesAvailableWidgetsModel::headerData( int section, Qt::Orientation orientation, int role ) const
486{
487 Q_UNUSED( section )
488 return orientation == Qt::Horizontal && role == Qt::DisplayRole ? tr( "Available Widgets" ) : QVariant {};
489}
490
492{
493 if ( !mLayer )
494 return;
495
496 beginResetModel();
497 mRootItem->deleteChildren();
498
499 // Load fields
500
501 auto itemFields = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::WidgetType, QStringLiteral( "Fields" ), tr( "Fields" ) );
502
503 const QgsFields fields = mLayer->fields();
504 for ( int i = 0; i < fields.size(); ++i )
505 {
506 const QgsField field = fields.at( i );
508 itemData.setShowLabel( true );
509
511
512 auto item = std::make_unique< QgsAttributesFormItem >();
513 item->setData( ItemFieldConfigRole, cfg );
514 item->setData( ItemNameRole, field.name() );
515 item->setData( ItemIdRole, field.name() ); // Field names act as ids
516 item->setData( ItemDisplayRole, field.alias() );
518 item->setData( ItemDataRole, itemData );
519 item->setIcon( fields.iconForField( i, true ) );
520
521 itemFields->addChild( std::move( item ) );
522 }
523
524 mRootItem->addChild( std::move( itemFields ) );
525
526 // Load relations
527
528 auto itemRelations = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::WidgetType, QStringLiteral( "Relations" ), tr( "Relations" ) );
529
530 const QList<QgsRelation> relations = mProject->relationManager()->referencedRelations( mLayer );
531
532 for ( const QgsRelation &relation : relations )
533 {
534 QString name;
535 const QgsPolymorphicRelation polymorphicRelation = relation.polymorphicRelation();
536 if ( polymorphicRelation.isValid() )
537 {
538 name = QStringLiteral( "%1 (%2)" ).arg( relation.name(), polymorphicRelation.name() );
539 }
540 else
541 {
542 name = relation.name();
543 }
545 itemData.setShowLabel( true );
546
547 auto itemRelation = std::make_unique< QgsAttributesFormItem >();
548 itemRelation->setData( ItemTypeRole, QgsAttributesFormData::Relation );
549 itemRelation->setData( ItemNameRole, name );
550 itemRelation->setData( ItemIdRole, relation.id() );
551 itemRelation->setData( ItemDataRole, itemData );
552 itemRelations->addChild( std::move( itemRelation ) );
553 }
554
555 mRootItem->addChild( std::move( itemRelations ) );
556
557 // Load form actions
558
559 auto itemActions = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::WidgetType, QStringLiteral( "Actions" ), tr( "Actions" ) );
560 mRootItem->addChild( std::move( itemActions ) );
561 populateActionItems( mLayer->actions()->actions() );
562
563 // Other widgets
564
565 auto itemOtherWidgets = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::WidgetType, QStringLiteral( "Other" ), tr( "Other Widgets" ) );
566
568 itemData.setShowLabel( true );
569 auto itemQml = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::QmlWidget, itemData, QStringLiteral( "QML Widget" ), tr( "QML Widget" ) );
570 itemOtherWidgets->addChild( std::move( itemQml ) );
571
573 itemHtmlData.setShowLabel( true );
574 auto itemHtml = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::HtmlWidget, itemHtmlData, QStringLiteral( "HTML Widget" ), tr( "HTML Widget" ) );
575 itemOtherWidgets->addChild( std::move( itemHtml ) );
576
578 itemTextData.setShowLabel( true );
579 auto itemText = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::TextWidget, itemTextData, QStringLiteral( "Text Widget" ), tr( "Text Widget" ) );
580 itemOtherWidgets->addChild( std::move( itemText ) );
581
583 itemTextData.setShowLabel( false );
584 auto itemSpacer = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::SpacerWidget, QStringLiteral( "Spacer Widget" ), tr( "Spacer Widget" ) );
585 itemOtherWidgets->addChild( std::move( itemSpacer ) );
586
587 mRootItem->addChild( std::move( itemOtherWidgets ) );
588
589 endResetModel();
590}
591
592void QgsAttributesAvailableWidgetsModel::populateLayerActions( const QList< QgsAction > actions )
593{
594 QModelIndex actionsIndex = actionContainer();
595 QgsAttributesFormItem *itemActions = itemForIndex( actionsIndex );
596
597 beginRemoveRows( actionsIndex, 0, itemActions->childCount() );
598 itemActions->deleteChildren();
599 endRemoveRows();
600
601 int count = 0;
602 for ( const auto &action : std::as_const( actions ) )
603 {
604 if ( action.isValid() && action.runable() && ( action.actionScopes().contains( QStringLiteral( "Feature" ) ) || action.actionScopes().contains( QStringLiteral( "Layer" ) ) ) )
605 {
606 count++;
607 }
608 }
609
610 beginInsertRows( actionsIndex, 0, count );
611 populateActionItems( actions );
612 endInsertRows();
613}
614
615void QgsAttributesAvailableWidgetsModel::populateActionItems( const QList<QgsAction> actions )
616{
617 QModelIndex actionsIndex = actionContainer();
618 QgsAttributesFormItem *itemActions = itemForIndex( actionsIndex );
619
620 for ( const auto &action : std::as_const( actions ) )
621 {
622 if ( action.isValid() && action.runable() && ( action.actionScopes().contains( QStringLiteral( "Feature" ) ) || action.actionScopes().contains( QStringLiteral( "Layer" ) ) ) )
623 {
624 const QString actionTitle { action.shortTitle().isEmpty() ? action.name() : action.shortTitle() };
625
627 itemData.setShowLabel( true );
628
629 auto itemAction = std::make_unique< QgsAttributesFormItem >();
630 itemAction->setData( ItemIdRole, action.id().toString() );
631 itemAction->setData( ItemTypeRole, QgsAttributesFormData::Action );
632 itemAction->setData( ItemNameRole, actionTitle );
633 itemAction->setData( ItemDataRole, itemData );
634
635 itemActions->addChild( std::move( itemAction ) );
636 }
637 }
638}
639
640QVariant QgsAttributesAvailableWidgetsModel::data( const QModelIndex &index, int role ) const
641{
642 if ( !index.isValid() )
643 return QVariant();
644
646 if ( !item )
647 return QVariant();
648
649 // Relations may be broken due to missing layers or references.
650 // Make those stand out from valid ones.
651 bool invalidRelation = false;
652 if ( ( role == Qt::ToolTipRole || role == Qt::ForegroundRole ) && item->type() == QgsAttributesFormData::Relation )
653 {
654 invalidRelation = !QgsProject::instance()->relationManager()->relation( item->id() ).isValid();
655 }
656
657 switch ( role )
658 {
659 case Qt::DisplayRole:
660 {
661 if ( !showAliases() && item->type() == QgsAttributesFormData::Field )
662 {
663 return item->name();
664 }
665
666 return item->displayName().isEmpty() ? item->name() : item->displayName();
667 }
668
669 case Qt::ToolTipRole:
670 {
672 {
673 const auto cfg = item->data( ItemFieldConfigRole ).value<QgsAttributesFormData::FieldConfig>();
674 if ( !cfg.mAlias.isEmpty() )
675 return tr( "%1 (%2)" ).arg( item->name(), cfg.mAlias );
676 else
677 return item->name();
678 }
679
680 if ( item->type() == QgsAttributesFormData::Relation && invalidRelation )
681 {
682 // Relation name will be displayed, inform users why it's red via tooltip
683 return tr( "Invalid relation" );
684 }
685
686 return QVariant();
687 }
688
689 case Qt::DecorationRole:
690 return item->icon();
691
692 case Qt::BackgroundRole:
693 {
695 return QBrush( Qt::lightGray );
696
697 return QVariant();
698 }
699
700 case Qt::ForegroundRole:
701 {
702 if ( item->type() == QgsAttributesFormData::Field )
703 {
704 if ( showAliases() && item->displayName().isEmpty() )
705 {
706 return QBrush( QColor( Qt::lightGray ) );
707 }
708 }
709
710 if ( item->type() == QgsAttributesFormData::Relation && invalidRelation )
711 {
712 return QBrush( QColor( 255, 0, 0 ) );
713 }
714
715 return QVariant();
716 }
717
718 case Qt::FontRole:
719 {
720 if ( item->type() == QgsAttributesFormData::Field )
721 {
722 if ( showAliases() && item->displayName().isEmpty() )
723 {
724 QFont font = QFont();
725 font.setItalic( true );
726 return font;
727 }
728 }
729 return QVariant();
730 }
731
732 case ItemDataRole:
734 case ItemNameRole:
735 case ItemTypeRole:
736 case ItemIdRole:
737 case ItemDisplayRole:
738 return item->data( role );
739
740 default:
741 return QVariant();
742 }
743}
744
745bool QgsAttributesAvailableWidgetsModel::setData( const QModelIndex &index, const QVariant &value, int role )
746{
747 if ( !index.isValid() )
748 return false;
749
751 bool result = item->setData( role, value );
752
753 if ( result )
754 emit dataChanged( index, index, { role } );
755
756 return result;
757}
758
760{
761 return Qt::CopyAction;
762}
763
765{
766 return QStringList() << QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" );
767}
768
769QMimeData *QgsAttributesAvailableWidgetsModel::mimeData( const QModelIndexList &indexes ) const
770{
771 if ( indexes.count() == 0 )
772 return nullptr;
773
774 const QStringList types = mimeTypes();
775 if ( types.isEmpty() )
776 return nullptr;
777
778 QMimeData *data = new QMimeData();
779 const QString format = types.at( 0 );
780 QByteArray encoded;
781 QDataStream stream( &encoded, QIODevice::WriteOnly );
782
783 // Sort indexes since their order reflects selection order
784 QModelIndexList sortedIndexes = indexes;
785
786 std::sort( sortedIndexes.begin(), sortedIndexes.end(), [this]( const QModelIndex &a, const QModelIndex &b ) {
787 return indexLessThan( a, b );
788 } );
789
790 for ( const QModelIndex &index : std::as_const( sortedIndexes ) )
791 {
792 if ( index.isValid() )
793 {
794 const QString itemId = index.data( QgsAttributesFormModel::ItemIdRole ).toString();
795 const QString itemName = index.data( QgsAttributesFormModel::ItemNameRole ).toString();
796 int itemType = index.data( QgsAttributesFormModel::ItemTypeRole ).toInt();
797
798 stream << itemId << itemType << itemName;
799 }
800 }
801
802 data->setData( format, encoded );
803 return data;
804}
805
807{
808 if ( mRootItem->childCount() > 0 )
809 {
810 const int row = 0;
811 QgsAttributesFormItem *item = mRootItem->child( row );
812 if ( item && item->name() == QLatin1String( "Fields" ) && item->type() == QgsAttributesFormData::WidgetType )
813 return createIndex( row, 0, item );
814 }
815 return QModelIndex();
816}
817
819{
820 if ( mRootItem->childCount() > 1 )
821 {
822 const int row = 1;
823 QgsAttributesFormItem *item = mRootItem->child( row );
824 if ( item && item->name() == QLatin1String( "Relations" ) && item->type() == QgsAttributesFormData::WidgetType )
825 return createIndex( row, 0, item );
826 }
827 return QModelIndex();
828}
829
831{
832 if ( mRootItem->childCount() > 2 )
833 {
834 const int row = 2;
835 QgsAttributesFormItem *item = mRootItem->child( row );
836 if ( item && item->name() == QLatin1String( "Actions" ) && item->type() == QgsAttributesFormData::WidgetType )
837 return createIndex( row, 0, item );
838 }
839 return QModelIndex();
840}
841
842QModelIndex QgsAttributesAvailableWidgetsModel::fieldModelIndex( const QString &fieldName ) const
843{
844 if ( mRootItem->childCount() == 0 )
845 return QModelIndex();
846
847 QgsAttributesFormItem *fieldItems = mRootItem->child( 0 );
848 if ( !fieldItems || fieldItems->name() != QLatin1String( "Fields" ) || fieldItems->type() != QgsAttributesFormData::WidgetType )
849 return QModelIndex();
850
851 QgsAttributesFormItem *item = fieldItems->firstTopChild( QgsAttributesFormData::Field, fieldName );
852 return item ? createIndex( item->row(), 0, item ) : QModelIndex();
853}
854
855
857 : QgsAttributesFormModel( layer, project, parent )
858{
859}
860
861QVariant QgsAttributesFormLayoutModel::headerData( int section, Qt::Orientation orientation, int role ) const
862{
863 Q_UNUSED( section )
864 return orientation == Qt::Horizontal && role == Qt::DisplayRole ? tr( "Form Layout" ) : QVariant {};
865}
866
867Qt::ItemFlags QgsAttributesFormLayoutModel::flags( const QModelIndex &index ) const
868{
869 if ( !index.isValid() )
870 return Qt::ItemIsDropEnabled;
871
872 Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
873
876 flags |= Qt::ItemIsDropEnabled;
877
878 return flags;
879}
880
882{
883 if ( !mLayer )
884 return;
885
886 beginResetModel();
887 mRootItem->deleteChildren();
888
889 const auto editorElements = mLayer->editFormConfig().tabs();
890 for ( QgsAttributeEditorElement *editorElement : editorElements )
891 {
892 loadAttributeEditorElementItem( editorElement, mRootItem.get() );
893 }
894
895 endResetModel();
896}
897
898void QgsAttributesFormLayoutModel::loadAttributeEditorElementItem( QgsAttributeEditorElement *const editorElement, QgsAttributesFormItem *parent, const int position )
899{
900 auto setCommonProperties = [editorElement]( QgsAttributesFormData::AttributeFormItemData &itemData ) {
901 itemData.setShowLabel( editorElement->showLabel() );
902 itemData.setLabelStyle( editorElement->labelStyle() );
903 itemData.setHorizontalStretch( editorElement->horizontalStretch() );
904 itemData.setVerticalStretch( editorElement->verticalStretch() );
905 };
906
907 auto editorItem = std::make_unique< QgsAttributesFormItem >();
908
909 switch ( editorElement->type() )
910 {
912 {
914 setCommonProperties( itemData );
915
916 editorItem->setData( ItemNameRole, editorElement->name() );
917 editorItem->setData( ItemIdRole, editorElement->name() ); // Field names act as ids
918 editorItem->setData( ItemTypeRole, QgsAttributesFormData::Field );
919 editorItem->setData( ItemDataRole, itemData );
920
921 const int fieldIndex = mLayer->fields().indexOf( editorElement->name() );
922 if ( fieldIndex != -1 )
923 {
924 editorItem->setData( ItemDisplayRole, mLayer->fields().field( fieldIndex ).alias() );
925 }
926
927 break;
928 }
929
931 {
932 const QgsAttributeEditorAction *actionEditor = static_cast<const QgsAttributeEditorAction *>( editorElement );
933 const QgsAction action { actionEditor->action( mLayer ) };
934 if ( action.isValid() )
935 {
937 setCommonProperties( itemData );
938
939 editorItem->setData( ItemIdRole, action.id().toString() );
940 editorItem->setData( ItemNameRole, action.shortTitle().isEmpty() ? action.name() : action.shortTitle() );
941 editorItem->setData( ItemTypeRole, QgsAttributesFormData::Action );
942 editorItem->setData( ItemDataRole, itemData );
943 }
944 else
945 {
946 QgsDebugError( QStringLiteral( "Invalid form action" ) );
947 }
948 break;
949 }
950
952 {
954 setCommonProperties( itemData );
955
956 const QgsAttributeEditorRelation *relationEditor = static_cast<const QgsAttributeEditorRelation *>( editorElement );
958 relationEditorConfig.mRelationWidgetType = relationEditor->relationWidgetTypeId();
959 relationEditorConfig.mRelationWidgetConfig = relationEditor->relationEditorConfiguration();
960 relationEditorConfig.nmRelationId = relationEditor->nmRelationId();
961 relationEditorConfig.forceSuppressFormPopup = relationEditor->forceSuppressFormPopup();
962 relationEditorConfig.label = relationEditor->label();
963 itemData.setRelationEditorConfiguration( relationEditorConfig );
964
965 QgsRelation relation = relationEditor->relation();
966 if ( relation.id().isEmpty() )
967 {
968 // If relation is coming from an internal move, we lose the id.
969 // Go to relation manager and bring relation properties.
970 relation = mProject->relationManager()->relation( editorElement->name() );
971 }
972
973 editorItem->setData( ItemIdRole, relation.id() );
974 editorItem->setData( ItemNameRole, relation.name() );
975 editorItem->setData( ItemDisplayRole, relationEditorConfig.label );
976 editorItem->setData( ItemTypeRole, QgsAttributesFormData::Relation );
977 editorItem->setData( ItemDataRole, itemData );
978
979 break;
980 }
981
983 {
985 setCommonProperties( itemData );
986
987 editorItem->setData( ItemNameRole, editorElement->name() );
988 editorItem->setData( ItemIdRole, editorElement->name() ); // Containers don't have id, use name to make them searchable
989 editorItem->setData( ItemTypeRole, QgsAttributesFormData::Container );
990
991 const QgsAttributeEditorContainer *container = static_cast<const QgsAttributeEditorContainer *>( editorElement );
992 if ( !container )
993 break;
994
995 itemData.setColumnCount( container->columnCount() );
996 itemData.setContainerType( container->type() );
997 itemData.setBackgroundColor( container->backgroundColor() );
998 itemData.setVisibilityExpression( container->visibilityExpression() );
999 itemData.setCollapsedExpression( container->collapsedExpression() );
1000 itemData.setCollapsed( container->collapsed() );
1001
1002 editorItem->setData( ItemDataRole, itemData );
1003
1004 const QList<QgsAttributeEditorElement *> children = container->children();
1005 for ( QgsAttributeEditorElement *childElement : children )
1006 {
1007 loadAttributeEditorElementItem( childElement, editorItem.get() );
1008 }
1009 break;
1010 }
1011
1013 {
1014 const QgsAttributeEditorQmlElement *qmlElementEditor = static_cast<const QgsAttributeEditorQmlElement *>( editorElement );
1016 setCommonProperties( itemData );
1017
1019 qmlEdConfig.qmlCode = qmlElementEditor->qmlCode();
1020 itemData.setQmlElementEditorConfiguration( qmlEdConfig );
1021
1022 editorItem->setData( ItemNameRole, editorElement->name() );
1023 editorItem->setData( ItemTypeRole, QgsAttributesFormData::QmlWidget );
1024 editorItem->setData( ItemDataRole, itemData );
1025 break;
1026 }
1027
1029 {
1030 const QgsAttributeEditorHtmlElement *htmlElementEditor = static_cast<const QgsAttributeEditorHtmlElement *>( editorElement );
1032 setCommonProperties( itemData );
1033
1035 htmlEdConfig.htmlCode = htmlElementEditor->htmlCode();
1036 itemData.setHtmlElementEditorConfiguration( htmlEdConfig );
1037
1038 editorItem->setData( ItemNameRole, editorElement->name() );
1039 editorItem->setData( ItemTypeRole, QgsAttributesFormData::HtmlWidget );
1040 editorItem->setData( ItemDataRole, itemData );
1041 break;
1042 }
1043
1045 {
1046 const QgsAttributeEditorTextElement *textElementEditor = static_cast<const QgsAttributeEditorTextElement *>( editorElement );
1048 setCommonProperties( itemData );
1049
1051 textEdConfig.text = textElementEditor->text();
1052 itemData.setTextElementEditorConfiguration( textEdConfig );
1053
1054 editorItem->setData( ItemNameRole, editorElement->name() );
1055 editorItem->setData( ItemTypeRole, QgsAttributesFormData::TextWidget );
1056 editorItem->setData( ItemDataRole, itemData );
1057 break;
1058 }
1059
1061 {
1062 const QgsAttributeEditorSpacerElement *spacerElementEditor = static_cast<const QgsAttributeEditorSpacerElement *>( editorElement );
1064 setCommonProperties( itemData );
1065 itemData.setShowLabel( false );
1066
1068 spacerEdConfig.drawLine = spacerElementEditor->drawLine();
1069 itemData.setSpacerElementEditorConfiguration( spacerEdConfig );
1070
1071 editorItem->setData( ItemNameRole, editorElement->name() );
1072 editorItem->setData( ItemTypeRole, QgsAttributesFormData::SpacerWidget );
1073 editorItem->setData( ItemDataRole, itemData );
1074 break;
1075 }
1076
1078 {
1079 QgsDebugError( QStringLiteral( "Not loading invalid attribute editor type..." ) );
1080 break;
1081 }
1082 }
1083
1084 if ( position >= 0 && position < parent->childCount() )
1085 {
1086 parent->insertChild( position, std::move( editorItem ) );
1087 }
1088 else
1089 {
1090 parent->addChild( std::move( editorItem ) );
1091 }
1092}
1093
1094QVariant QgsAttributesFormLayoutModel::data( const QModelIndex &index, int role ) const
1095{
1096 if ( !index.isValid() )
1097 return QVariant();
1098
1099 if ( role == ItemFieldConfigRole ) // This model doesn't store data for that role
1100 return false;
1101
1103 if ( !item )
1104 return QVariant();
1105
1106 // Fields may be present in the form layout configuration
1107 // even if their corresponding layer fields were deleted.
1108 // Make those stand out from existent ones.
1109 const int fieldIndex = mLayer->fields().indexOf( item->name() );
1110 const bool invalidField = fieldIndex == -1;
1111
1112 // Relations may be broken due to missing layers or references.
1113 // Make those stand out from valid ones.
1114 bool invalidRelation = false;
1115 if ( ( role == Qt::DisplayRole || role == Qt::ToolTipRole || role == Qt::ForegroundRole ) && item->type() == QgsAttributesFormData::Relation )
1116 {
1117 invalidRelation = !QgsProject::instance()->relationManager()->relation( item->id() ).isValid();
1118 }
1119
1120 switch ( role )
1121 {
1122 case Qt::DisplayRole:
1123 {
1124 if ( item->type() == QgsAttributesFormData::Relation && invalidRelation )
1125 {
1126 // Invalid relations can have an id, if that's the case, we have a name.
1127 // Only set a new name if id is missing.
1128 if ( item->id().isEmpty() )
1129 {
1130 return tr( "Invalid relation" );
1131 }
1132 }
1133
1134 if ( !showAliases() && ( item->type() == QgsAttributesFormData::Field || item->type() == QgsAttributesFormData::Relation ) )
1135 {
1136 return item->name();
1137 }
1138
1139 return item->displayName().isEmpty() ? item->name() : item->displayName();
1140 }
1141
1142 case Qt::ToolTipRole:
1143 {
1144 if ( item->type() == QgsAttributesFormData::Field )
1145 {
1146 if ( invalidField )
1147 {
1148 return tr( "Invalid field" );
1149 }
1150 else
1151 {
1152 return item->name();
1153 }
1154 }
1155
1156 if ( item->type() == QgsAttributesFormData::Relation && invalidRelation )
1157 {
1158 if ( !item->id().isEmpty() )
1159 {
1160 // The relation name is shown, let's inform users via tooltip why it's red
1161 return tr( "Invalid relation" );
1162 }
1163 }
1164
1165 return QVariant();
1166 }
1167
1168 case Qt::DecorationRole:
1169 return item->icon();
1170
1171 case Qt::BackgroundRole:
1172 {
1173 if ( item->type() == QgsAttributesFormData::Container )
1174 return QBrush( Qt::lightGray );
1175
1176 return QVariant();
1177 }
1178
1179 case Qt::ForegroundRole:
1180 {
1181 if ( item->type() == QgsAttributesFormData::Field )
1182 {
1183 if ( invalidField )
1184 {
1185 return QBrush( QColor( 255, 0, 0 ) );
1186 }
1187 else if ( showAliases() && item->displayName().isEmpty() )
1188 {
1189 return QBrush( QColor( Qt::lightGray ) );
1190 }
1191 }
1192
1193 if ( item->type() == QgsAttributesFormData::Relation )
1194 {
1195 if ( invalidRelation )
1196 {
1197 return QBrush( QColor( 255, 0, 0 ) );
1198 }
1199 else if ( showAliases() && item->displayName().isEmpty() )
1200 {
1201 return QBrush( QColor( Qt::lightGray ) );
1202 }
1203 }
1204
1205 return QVariant();
1206 }
1207
1208 case Qt::FontRole:
1209 {
1210 if ( item->type() == QgsAttributesFormData::Field )
1211 {
1212 if ( !invalidField && showAliases() && item->displayName().isEmpty() )
1213 {
1214 QFont font = QFont();
1215 font.setItalic( true );
1216 return font;
1217 }
1218 }
1219
1220 if ( item->type() == QgsAttributesFormData::Relation )
1221 {
1222 if ( !invalidRelation && showAliases() && item->displayName().isEmpty() )
1223 {
1224 QFont font = QFont();
1225 font.setItalic( true );
1226 return font;
1227 }
1228 }
1229
1230 return QVariant();
1231 }
1232
1233 case ItemDataRole:
1234 case ItemNameRole:
1235 case ItemIdRole:
1236 case ItemTypeRole:
1237 case ItemDisplayRole:
1238 return item->data( role );
1239
1240 default:
1241 return QVariant();
1242 }
1243}
1244
1245bool QgsAttributesFormLayoutModel::setData( const QModelIndex &index, const QVariant &value, int role )
1246{
1247 if ( !index.isValid() )
1248 return false;
1249
1250 if ( role == ItemFieldConfigRole ) // This model doesn't store data for that role
1251 return false;
1252
1254 bool result = item->setData( role, value );
1255
1256 if ( result )
1257 emit dataChanged( index, index, { role } );
1258
1259 return result;
1260}
1261
1262bool QgsAttributesFormLayoutModel::removeRows( int row, int count, const QModelIndex &parent )
1263{
1264 if ( row < 0 )
1265 return false;
1266
1268
1269 if ( row > item->childCount() - count )
1270 return false;
1271
1272 beginRemoveRows( parent, row, row + count - 1 );
1273 for ( int r = 0; r < count; ++r )
1274 item->deleteChildAtIndex( row );
1275 endRemoveRows();
1276 return true;
1277}
1278
1279bool QgsAttributesFormLayoutModel::removeRow( int row, const QModelIndex &parent )
1280{
1281 beginRemoveRows( parent, row, row );
1283 item->deleteChildAtIndex( row );
1284 endRemoveRows();
1285 return true;
1286}
1287
1289{
1290 return Qt::MoveAction;
1291}
1292
1294{
1295 return Qt::DropAction::CopyAction | Qt::DropAction::MoveAction;
1296}
1297
1299{
1300 return QStringList() << QStringLiteral( "application/x-qgsattributesformlayoutelement" ) << QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" );
1301}
1302
1303QModelIndexList QgsAttributesFormLayoutModel::curateIndexesForMimeData( const QModelIndexList &indexes ) const
1304{
1305 QModelIndexList containerList;
1306 for ( const auto index : indexes )
1307 {
1308 const auto indexType = static_cast< QgsAttributesFormData::AttributesFormItemType >( index.data( QgsAttributesFormModel::ItemTypeRole ).toInt() );
1309 if ( indexType == QgsAttributesFormData::Container )
1310 {
1311 containerList << index;
1312 }
1313 }
1314
1315 if ( containerList.size() == 0 )
1316 return indexes;
1317
1318 QModelIndexList curatedIndexes;
1319
1320 // Iterate searching if current index is child of any container in containerList (recursively)
1321 for ( const auto index : indexes )
1322 {
1323 QModelIndex parent = index.parent();
1324 bool redundantChild = false;
1325
1326 while ( parent.isValid() )
1327 {
1328 if ( containerList.contains( parent ) )
1329 {
1330 redundantChild = true;
1331 break;
1332 }
1333
1334 parent = parent.parent();
1335 }
1336
1337 if ( !redundantChild )
1338 curatedIndexes << index;
1339 }
1340
1341 return curatedIndexes;
1342}
1343
1344QMimeData *QgsAttributesFormLayoutModel::mimeData( const QModelIndexList &indexes ) const
1345{
1346 if ( indexes.count() == 0 )
1347 return nullptr;
1348
1349 // Discard redundant indexes
1350 QModelIndexList curatedIndexes;
1351 if ( indexes.count() > 1 )
1352 {
1353 curatedIndexes = curateIndexesForMimeData( indexes );
1354 }
1355 else
1356 {
1357 curatedIndexes = indexes;
1358 }
1359
1360 const QStringList types = mimeTypes();
1361 if ( types.isEmpty() )
1362 return nullptr;
1363
1364 QMimeData *data = new QMimeData();
1365 const QString format = types.at( 0 );
1366 QByteArray encoded;
1367 QDataStream stream( &encoded, QIODevice::WriteOnly );
1368
1369 // Sort indexes since their order reflects selection order
1370 std::sort( curatedIndexes.begin(), curatedIndexes.end(), [this]( const QModelIndex &a, const QModelIndex &b ) {
1371 return indexLessThan( a, b );
1372 } );
1373
1374 for ( const QModelIndex &index : std::as_const( curatedIndexes ) )
1375 {
1376 if ( index.isValid() )
1377 {
1378 QDomDocument doc;
1379
1380 QDomElement rootElem = doc.createElement( QStringLiteral( "form_layout_mime" ) );
1382 QDomElement editorElem = editor->toDomElement( doc );
1383 rootElem.appendChild( editorElem );
1384
1385 doc.appendChild( rootElem );
1386 stream << doc.toString( -1 );
1387 }
1388 }
1389
1390 data->setData( format, encoded );
1391 return data;
1392}
1393
1394bool QgsAttributesFormLayoutModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
1395{
1396 Q_UNUSED( column )
1397 bool isDropSuccessful = false;
1398 int rows = 0;
1399
1400 if ( row == -1 ) // Dropped at invalid index
1401 row = rowCount( parent ); // Let's append the item
1402
1403 if ( action == Qt::IgnoreAction )
1404 {
1405 isDropSuccessful = true;
1406 }
1407 else if ( data->hasFormat( QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" ) ) )
1408 {
1409 Q_ASSERT( action == Qt::CopyAction ); // External drop
1410 QByteArray itemData = data->data( QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" ) );
1411 QDataStream stream( &itemData, QIODevice::ReadOnly );
1412
1413 while ( !stream.atEnd() )
1414 {
1415 QString itemId;
1416 int itemTypeInt;
1417 QString itemName;
1418 stream >> itemId >> itemTypeInt >> itemName;
1419
1420 const auto itemType = static_cast< QgsAttributesFormData::AttributesFormItemType >( itemTypeInt );
1421 insertChild( parent, row + rows, itemId, itemType, itemName );
1422
1423 isDropSuccessful = true;
1424
1425 QModelIndex addedIndex = index( row + rows, 0, parent );
1426 emit externalItemDropped( addedIndex );
1427
1428 rows++;
1429 }
1430 }
1431 else if ( data->hasFormat( QStringLiteral( "application/x-qgsattributesformlayoutelement" ) ) )
1432 {
1433 Q_ASSERT( action == Qt::MoveAction ); // Internal move
1434 QByteArray itemData = data->data( QStringLiteral( "application/x-qgsattributesformlayoutelement" ) );
1435 QDataStream stream( &itemData, QIODevice::ReadOnly );
1436
1437 while ( !stream.atEnd() )
1438 {
1439 QString text;
1440 stream >> text;
1441
1442 QDomDocument doc;
1443 if ( !doc.setContent( text ) )
1444 continue;
1445 const QDomElement rootElem = doc.documentElement();
1446 if ( rootElem.tagName() != QLatin1String( "form_layout_mime" ) || !rootElem.hasChildNodes() )
1447 continue;
1448 const QDomElement childElem = rootElem.firstChild().toElement();
1449
1450 // Build editor element from XML and add/insert it to parent
1452 beginInsertRows( parent, row + rows, row + rows );
1453 loadAttributeEditorElementItem( editor, itemForIndex( parent ), row + rows );
1454 endInsertRows();
1455
1456 isDropSuccessful = true;
1457
1458 QModelIndex addedIndex = index( row + rows, 0, parent );
1459 emit internalItemDropped( addedIndex );
1460
1461 rows++;
1462 }
1463 }
1464
1465 return isDropSuccessful;
1466}
1467
1468void QgsAttributesFormLayoutModel::updateAliasForFieldItemsRecursive( QgsAttributesFormItem *parent, const QString &fieldName, const QString &fieldAlias )
1469{
1470 for ( int i = 0; i < parent->childCount(); i++ )
1471 {
1472 QgsAttributesFormItem *child = parent->child( i );
1473 if ( child->name() == fieldName && child->type() == QgsAttributesFormData::Field )
1474 {
1475 child->setData( ItemDisplayRole, fieldAlias );
1476 const QModelIndex index = createIndex( child->row(), 0, child );
1477 emit dataChanged( index, index );
1478 }
1479
1480 if ( child->childCount() > 0 )
1481 {
1482 updateAliasForFieldItemsRecursive( child, fieldName, fieldAlias );
1483 }
1484 }
1485}
1486
1487void QgsAttributesFormLayoutModel::updateAliasForFieldItems( const QString &fieldName, const QString &fieldAlias )
1488{
1489 updateAliasForFieldItemsRecursive( mRootItem.get(), fieldName, fieldAlias );
1490}
1491
1492QList< QgsAddAttributeFormContainerDialog::ContainerPair > QgsAttributesFormLayoutModel::recursiveListOfContainers( QgsAttributesFormItem *parent ) const
1493{
1494 QList< QgsAddAttributeFormContainerDialog::ContainerPair > containerList;
1495 for ( int i = 0; i < parent->childCount(); i++ )
1496 {
1497 QgsAttributesFormItem *child = parent->child( i );
1498 if ( child->type() == QgsAttributesFormData::Container )
1499 {
1500 containerList << QgsAddAttributeFormContainerDialog::ContainerPair( child->name(), createIndex( child->row(), 0, child ) );
1501 }
1502
1503 if ( child->childCount() > 0 )
1504 {
1505 containerList.append( recursiveListOfContainers( child ) );
1506 }
1507 }
1508
1509 return containerList;
1510}
1511
1513{
1514 QgsAttributeEditorElement *widgetDef = nullptr;
1515
1517 const int indexType = static_cast< QgsAttributesFormData::AttributesFormItemType >( index.data( QgsAttributesFormModel::ItemTypeRole ).toInt() );
1518 const QString indexName = index.data( QgsAttributesFormModel::ItemNameRole ).toString();
1519 const QString indexId = index.data( QgsAttributesFormModel::ItemIdRole ).toString();
1520
1521 switch ( indexType )
1522 {
1524 {
1525 const int fieldIndex = mLayer->fields().lookupField( indexName );
1526 widgetDef = new QgsAttributeEditorField( indexName, fieldIndex, parent );
1527 break;
1528 }
1529
1531 {
1532 const QgsAction action { mLayer->actions()->action( indexId ) };
1533 widgetDef = new QgsAttributeEditorAction( action, parent );
1534 break;
1535 }
1536
1538 {
1539 const QgsRelation relation = mProject->relationManager()->relation( indexId );
1540
1543 relDef->setRelationWidgetTypeId( relationEditorConfig.mRelationWidgetType );
1544 relDef->setRelationEditorConfiguration( relationEditorConfig.mRelationWidgetConfig );
1545 relDef->setNmRelationId( relationEditorConfig.nmRelationId );
1546 relDef->setForceSuppressFormPopup( relationEditorConfig.forceSuppressFormPopup );
1547 relDef->setLabel( relationEditorConfig.label );
1548 widgetDef = relDef;
1549 break;
1550 }
1551
1553 {
1554 QgsAttributeEditorContainer *container = new QgsAttributeEditorContainer( indexName, parent, itemData.backgroundColor() );
1555 container->setColumnCount( itemData.columnCount() );
1556 // only top-level containers can be tabs
1558 bool isTopLevel = !index.parent().isValid();
1559 if ( type == Qgis::AttributeEditorContainerType::Tab && !isTopLevel )
1560 {
1561 // a tab container found which isn't at the top level -- reset it to a group box instead
1563 }
1564 container->setType( type );
1565 container->setCollapsed( itemData.collapsed() );
1566 container->setCollapsedExpression( itemData.collapsedExpression() );
1567 container->setVisibilityExpression( itemData.visibilityExpression() );
1568 container->setBackgroundColor( itemData.backgroundColor() );
1569
1570 QModelIndex childIndex;
1571 for ( int t = 0; t < rowCount( index ); t++ )
1572 {
1573 childIndex = this->index( t, 0, index );
1574 QgsAttributeEditorElement *element { createAttributeEditorWidget( childIndex, container ) };
1575 if ( element )
1576 container->addChildElement( element );
1577 }
1578 widgetDef = container;
1579 break;
1580 }
1581
1583 {
1585 element->setQmlCode( itemData.qmlElementEditorConfiguration().qmlCode );
1586 widgetDef = element;
1587 break;
1588 }
1589
1591 {
1594 widgetDef = element;
1595 break;
1596 }
1597
1599 {
1601 element->setText( itemData.textElementEditorConfiguration().text );
1602 widgetDef = element;
1603 break;
1604 }
1605
1607 {
1610 widgetDef = element;
1611 break;
1612 }
1613
1615 default:
1616 break;
1617 }
1618
1619 if ( widgetDef )
1620 {
1621 widgetDef->setShowLabel( itemData.showLabel() );
1622 widgetDef->setLabelStyle( itemData.labelStyle() );
1623 widgetDef->setHorizontalStretch( itemData.horizontalStretch() );
1624 widgetDef->setVerticalStretch( itemData.verticalStretch() );
1625 }
1626
1627 return widgetDef;
1628}
1629
1630QList< QgsAddAttributeFormContainerDialog::ContainerPair > QgsAttributesFormLayoutModel::listOfContainers() const
1631{
1632 return recursiveListOfContainers( mRootItem.get() );
1633}
1634
1635void QgsAttributesFormLayoutModel::addContainer( QModelIndex &parent, const QString &name, int columnCount, Qgis::AttributeEditorContainerType type )
1636{
1637 beginInsertRows( parent, rowCount( parent ), rowCount( parent ) );
1638
1639 QgsAttributesFormItem *parentItem = itemForIndex( parent );
1640
1641 std::unique_ptr< QgsAttributesFormItem > containerItem = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::Container, name, QString(), parentItem );
1642
1644 itemData.setColumnCount( columnCount );
1646
1647 containerItem->setData( QgsAttributesFormModel::ItemDataRole, itemData );
1648 containerItem->setData( QgsAttributesFormModel::ItemIdRole, name ); // Make it searchable
1649 parentItem->addChild( std::move( containerItem ) );
1650
1651 endInsertRows();
1652}
1653
1654void QgsAttributesFormLayoutModel::insertChild( const QModelIndex &parent, int row, const QString &itemId, QgsAttributesFormData::AttributesFormItemType itemType, const QString &itemName )
1655{
1656 if ( row < 0 )
1657 return;
1658
1659 beginInsertRows( parent, row, row );
1660 std::unique_ptr< QgsAttributesFormItem > item = std::make_unique< QgsAttributesFormItem >();
1661
1662 item->setData( QgsAttributesFormModel::ItemIdRole, itemId );
1663 item->setData( QgsAttributesFormModel::ItemTypeRole, itemType );
1664 item->setData( QgsAttributesFormModel::ItemNameRole, itemName );
1665
1666 itemForIndex( parent )->insertChild( row, std::move( item ) );
1667 endInsertRows();
1668}
AttributeEditorContainerType
Attribute editor container types.
Definition qgis.h:5279
@ Action
A layer action element.
@ QmlElement
A QML element.
@ HtmlElement
A HTML element.
@ TextElement
A text element.
@ SpacerElement
A spacer element.
QList< QgsAction > actions(const QString &actionScope=QString()) const
Returns a list of actions that are available in the given action scope.
QgsAction action(QUuid id) const
Gets an action by its id.
Utility class that encapsulates an action based on vector attributes.
Definition qgsaction.h:37
QPair< QString, QModelIndex > ContainerPair
This element will load a layer action onto the form.
const QgsAction & action(const QgsVectorLayer *layer) const
Returns the (possibly lazy loaded) action for the given layer.
A container for attribute editors, used to group them visually in the attribute form if it is set to ...
virtual void addChildElement(QgsAttributeEditorElement *element)
Add a child element to this container.
QgsOptionalExpression visibilityExpression() const
The visibility expression is used in the attribute form to show or hide this container based on an ex...
void setColumnCount(int columnCount)
Set the number of columns in this group.
void setVisibilityExpression(const QgsOptionalExpression &visibilityExpression)
The visibility expression is used in the attribute form to show or hide this container based on an ex...
QgsOptionalExpression collapsedExpression() const
The collapsed expression is used in the attribute form to set the collapsed status of the group box c...
bool collapsed() const
For group box containers returns true if this group box is collapsed.
Qgis::AttributeEditorContainerType type() const
Returns the container type.
void setType(Qgis::AttributeEditorContainerType type)
Sets the container type.
void setCollapsedExpression(const QgsOptionalExpression &collapsedExpression)
The collapsed expression is used in the attribute form to set the collapsed status of the group box o...
QList< QgsAttributeEditorElement * > children() const
Gets a list of the children elements of this container.
QColor backgroundColor() const
Returns the background color of the container.
void setCollapsed(bool collapsed)
For group box containers sets if this group box is collapsed.
int columnCount() const
Gets the number of columns in this group.
void setBackgroundColor(const QColor &backgroundColor)
Sets the background color to backgroundColor.
An abstract base class for any elements of a drag and drop form.
void setHorizontalStretch(int stretch)
Sets the horizontal stretch factor for the element.
QDomElement toDomElement(QDomDocument &doc) const
Gets the XML Dom element to save this element.
LabelStyle labelStyle() const
Returns the label style.
void setLabelStyle(const LabelStyle &labelStyle)
Sets the labelStyle.
Qgis::AttributeEditorType type() const
The type of this element.
int verticalStretch() const
Returns the vertical stretch factor for the element.
bool showLabel() const
Controls if this element should be labeled with a title (field, relation or groupname).
QString name() const
Returns the name of this element.
static QgsAttributeEditorElement * create(const QDomElement &element, const QString &layerId, const QgsFields &fields, const QgsReadWriteContext &context, QgsAttributeEditorElement *parent=nullptr)
Constructs the editor element from the given element.
void setVerticalStretch(int stretch)
Sets the vertical stretch factor for the element.
void setShowLabel(bool showLabel)
Controls if this element should be labeled with a title (field, relation or groupname).
int horizontalStretch() const
Returns the horizontal stretch factor for the element.
This element will load a field's widget onto the form.
An attribute editor widget that will represent arbitrary HTML code.
QString htmlCode() const
The Html code that will be represented within this widget.
void setHtmlCode(const QString &htmlCode)
Sets the HTML code that will be represented within this widget to htmlCode.
An attribute editor widget that will represent arbitrary QML code.
QString qmlCode() const
The QML code that will be represented within this widget.
void setQmlCode(const QString &qmlCode)
Sets the QML code that will be represented within this widget to qmlCode.
This element will load a relation editor onto the form.
void setNmRelationId(const QVariant &nmRelationId=QVariant())
Sets nmRelationId for the relation id of the second relation involved in an N:M relation.
void setRelationWidgetTypeId(const QString &relationWidgetTypeId)
Sets the relation widget type.
const QgsRelation & relation() const
Gets the id of the relation which shall be embedded.
QVariantMap relationEditorConfiguration() const
Returns the relation editor widget configuration.
void setForceSuppressFormPopup(bool forceSuppressFormPopup)
Sets force suppress form popup status to forceSuppressFormPopup.
QVariant nmRelationId() const
Determines the relation id of the second relation involved in an N:M relation.
bool forceSuppressFormPopup() const
Determines the force suppress form popup status.
QString relationWidgetTypeId() const
Returns the current relation widget type id.
void setRelationEditorConfiguration(const QVariantMap &config)
Sets the relation editor configuration.
void setLabel(const QString &label=QString())
Sets label for this element If it's empty it takes the relation id as label.
QString label() const
Determines the label of this element.
An attribute editor widget that will represent a spacer.
void setDrawLine(bool drawLine)
Sets a flag to define if the spacer element will contain an horizontal line.
bool drawLine() const
Returns true if the spacer element will contain an horizontal line.
An attribute editor widget that will represent arbitrary text code.
void setText(const QString &text)
Sets the text that will be represented within this widget to text.
QString text() const
The Text that will be represented within this widget.
QgsAttributesAvailableWidgetsModel(QgsVectorLayer *layer, QgsProject *project, QObject *parent=nullptr)
Constructor for QgsAttributesAvailableWidgetsModel, with the given parent.
Qt::DropActions supportedDragActions() const override
Qt::ItemFlags flags(const QModelIndex &index) const override
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
QMimeData * mimeData(const QModelIndexList &indexes) const override
QModelIndex fieldModelIndex(const QString &fieldName) const
Returns the model index that corresponds to the field with the given fieldName.
QModelIndex actionContainer() const
Returns the action container in this model, expected to be placed at the third top-level row.
void populateLayerActions(const QList< QgsAction > actions)
Refresh layer actions in the model to keep an updated action list.
QModelIndex fieldContainer() const
Returns the field container in this model, expected to be placed at the first top-level row.
QModelIndex relationContainer() const
Returns the relation container in this model, expected to be placed at the second top-level row.
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
Main class to store and transfer editor data contained in a QgsAttributesFormModel.
HtmlElementEditorConfiguration htmlElementEditorConfiguration() const
Returns the HTML editor configuration.
QgsOptionalExpression collapsedExpression() const
Returns the optional expression that dynamically controls the collapsed status of a group box contain...
TextElementEditorConfiguration textElementEditorConfiguration() const
Returns the editor configuration for text element.
bool collapsed() const
For group box containers returns if this group box is collapsed.
SpacerElementEditorConfiguration spacerElementEditorConfiguration() const
Returns the spacer element configuration.
const QgsAttributeEditorElement::LabelStyle labelStyle() const
Returns the label style.
void setColumnCount(int count)
Sets the number of columns for a container.
void setHtmlElementEditorConfiguration(const HtmlElementEditorConfiguration &htmlElementEditorConfiguration)
Sets the HTML editor configuration.
void setBackgroundColor(const QColor &backgroundColor)
Sets the background color of a container.
int columnCount() const
Returns the number of columns in a container.
bool showLabel() const
Returns whether the widget's label is to be shown.
void setCollapsedExpression(const QgsOptionalExpression &collapsedExpression)
Sets the optional collapsedExpression that dynamically controls the collapsed status of a group box c...
void setShowLabel(bool showLabel)
Sets whether the label for the widget should be shown.
void setQmlElementEditorConfiguration(const QmlElementEditorConfiguration &qmlElementEditorConfiguration)
Sets the QML editor configuration.
int horizontalStretch() const
Returns the horizontal stretch factor for the element.
void setHorizontalStretch(int stretch)
Sets the horizontal stretch factor for the element.
void setContainerType(Qgis::AttributeEditorContainerType type)
Sets the container type.
RelationEditorConfiguration relationEditorConfiguration() const
Returns the relation editor configuration.
void setRelationEditorConfiguration(const RelationEditorConfiguration &relationEditorConfiguration)
Sets the relation editor configuration.
void setVisibilityExpression(const QgsOptionalExpression &visibilityExpression)
Sets the optional visibilityExpression that dynamically controls the visibility status of a container...
Qgis::AttributeEditorContainerType containerType() const
Returns the container type.
void setLabelStyle(const QgsAttributeEditorElement::LabelStyle &labelStyle)
Sets the label style to labelStyle.
int verticalStretch() const
Returns the vertical stretch factor for the element.
void setVerticalStretch(int stretch)
Sets the vertical stretch factor for the element.
void setCollapsed(bool collapsed)
For group box containers sets if this group box is collapsed.
void setSpacerElementEditorConfiguration(SpacerElementEditorConfiguration spacerElementEditorConfiguration)
Sets the the spacer element configuration to spacerElementEditorConfiguration.
QmlElementEditorConfiguration qmlElementEditorConfiguration() const
Returns the QML editor configuration.
QColor backgroundColor() const
Returns the background color of a container.
void setTextElementEditorConfiguration(const TextElementEditorConfiguration &textElementEditorConfiguration)
Sets the editor configuration for text element to textElementEditorConfiguration.
QgsOptionalExpression visibilityExpression() const
Returns the expression to control the visibility status of a container.
AttributesFormItemType
Custom item types.
@ Container
Container for the form, which may be tab, group or row.
@ Relation
Relation between two vector layers.
@ Field
Vector layer field.
@ SpacerWidget
Spacer widget type,.
@ WidgetType
In the available widgets tree, the type of widget.
@ TextWidget
Text widget type,.
Holds parent-child relations as well as item data contained in a QgsAttributesFormModel.
void insertChild(int position, std::unique_ptr< QgsAttributesFormItem > &&item)
Inserts a child item to the item at a given position.
QIcon icon() const
Returns the icon of the item.
QgsAttributesFormItem()=default
QString name() const
Returns the name of the item.
int childCount() const
Returns the number of children items for the given item.
QgsAttributesFormItem * child(int row)
Access the child item located at row position.
bool setData(int role, const QVariant &value)
Stores a data value in a given role inside the item.
QgsAttributesFormItem * parent()
Returns the parent object of the item.
QgsAttributesFormItem * firstChildRecursive(const QgsAttributesFormData::AttributesFormItemType &itemType, const QString &itemId) const
Access the first child item that matches itemType and itemId, recursively.
int row() const
Returns the position of the item regarding its parent.
QgsAttributesFormData::AttributesFormItemType type() const
Returns the type of the item.
QString id() const
Returns the id of the item.
QVariant data(int role) const
Returns the data stored in the item, corresponding to the given role.
QString displayName() const
Returns the display name of the item.
void addChild(std::unique_ptr< QgsAttributesFormItem > &&child)
Appends a child to this item.
QgsAttributesFormItem * firstTopChild(const QgsAttributesFormData::AttributesFormItemType itemType, const QString &itemId) const
Access the first top-level child item that matches itemType and itemId.
void deleteChildAtIndex(int index)
Deletes the child of the item placed at the given index.
void deleteChildren()
Deletes all child items from this item.
void externalItemDropped(QModelIndex &index)
Informs that items were inserted (via drop) in the model from another model.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
Qt::DropActions supportedDragActions() const override
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
void updateAliasForFieldItems(const QString &fieldName, const QString &fieldAlias)
Updates the aliases of all matching fields in the model.
QStringList mimeTypes() const override
QgsAttributesFormLayoutModel(QgsVectorLayer *layer, QgsProject *project, QObject *parent=nullptr)
Constructor for QgsAttributesFormLayoutModel, with the given parent.
QMimeData * mimeData(const QModelIndexList &indexes) const override
bool removeRow(int row, const QModelIndex &parent=QModelIndex())
Removes the index located at row within the given parent.
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override
Qt::DropActions supportedDropActions() const override
bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex()) override
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
void addContainer(QModelIndex &parent, const QString &name, int columnCount, Qgis::AttributeEditorContainerType type)
Adds a new container to parent.
QList< QgsAddAttributeFormContainerDialog::ContainerPair > listOfContainers() const
Returns a list of containers stored in the model, structured as pairs (name, container model index).
QgsAttributeEditorElement * createAttributeEditorWidget(const QModelIndex &index, QgsAttributeEditorElement *parent) const
Creates a new attribute editor element based on the definition stored in a form layout model index.
Qt::ItemFlags flags(const QModelIndex &index) const override
void internalItemDropped(QModelIndex &index)
Informs that items were moved (via drop) in the model from the same model.
void insertChild(const QModelIndex &parent, int row, const QString &itemId, QgsAttributesFormData::AttributesFormItemType itemType, const QString &itemName)
Inserts a new child to parent model index at the given row position.
Abstract class for tree models allowing for configuration of attributes forms.
int rowCount(const QModelIndex &parent=QModelIndex()) const override
std::unique_ptr< QgsAttributesFormItem > mRootItem
bool showAliases() const
Returns whether field aliases are preferred over field names as item text.
@ ItemFieldConfigRole
Prior to QGIS 3.44, this was available as FieldConfigRole.
@ ItemDisplayRole
Display text for the item.
@ ItemNameRole
Prior to QGIS 3.44, this was available as FieldNameRole.
@ ItemDataRole
Prior to QGIS 3.44, this was available as DnDTreeRole.
@ ItemIdRole
Items may have ids to ease comparison. Used by Relations, fields, actions and containers.
void setShowAliases(bool show)
Sets whether field aliases should be preferred over field names as item text.
QModelIndex firstRecursiveMatchingModelIndex(const QgsAttributesFormData::AttributesFormItemType &itemType, const QString &itemId) const
Returns the first model index that matches the given itemType and itemId, recursively.
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
QgsAttributesFormModel(QgsVectorLayer *layer, QgsProject *project, QObject *parent=nullptr)
Constructor for QgsAttributesFormModel, with the given parent.
QModelIndex parent(const QModelIndex &index) const override
~QgsAttributesFormModel() override
QModelIndex firstTopMatchingModelIndex(const QgsAttributesFormData::AttributesFormItemType &itemType, const QString &itemId) const
Returns the first top-level model index that matches the given itemType and itemId.
bool indexLessThan(const QModelIndex &a, const QModelIndex &b) const
Auxiliary function to sort indexes, returning true if index a is less than index b.
int columnCount(const QModelIndex &parent=QModelIndex()) const override
QVector< int > rootToLeafPath(QgsAttributesFormItem *item) const
Returns a QVector of iterative positions from root item to the given item.
QgsAttributesFormItem * itemForIndex(const QModelIndex &index) const
Returns the underlying item that corresponds to the given index.
bool reuseLastValue(int index) const
If this returns true, the widget at the given index will remember the previously entered value from t...
bool readOnly(int idx) const
This returns true if the field is manually set to read only or if the field does not support editing ...
QgsPropertyCollection dataDefinedFieldProperties(const QString &fieldName) const
Returns data defined properties for fieldName.
bool labelOnTop(int idx) const
If this returns true, the widget at the given index will receive its label on the previous line while...
QList< QgsAttributeEditorElement * > tabs() const
Returns a list of tabs for EditorLayout::TabLayout obtained from the invisible root container.
QgsEditorWidgetSetup findBest(const QgsVectorLayer *vl, const QString &fieldName) const
Find the best editor widget and its configuration for a given field.
Holder for the widget type and its configuration for a field.
QString type() const
Returns the widget type to use.
QVariantMap config() const
Returns the widget configuration.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:53
QString name
Definition qgsfield.h:62
Qgis::FieldDomainSplitPolicy splitPolicy() const
Returns the field's split policy, which indicates how field values should be handled during a split o...
Definition qgsfield.cpp:761
QString alias
Definition qgsfield.h:63
Qgis::FieldDuplicatePolicy duplicatePolicy() const
Returns the field's duplicate policy, which indicates how field values should be handled during a dup...
Definition qgsfield.cpp:771
Qgis::FieldDomainMergePolicy mergePolicy() const
Returns the field's merge policy, which indicates how field values should be handled during a merge o...
Definition qgsfield.cpp:781
QString comment
Definition qgsfield.h:61
QgsFieldConstraints constraints
Definition qgsfield.h:65
Container of fields for a vector layer.
Definition qgsfields.h:46
int count
Definition qgsfields.h:50
Q_INVOKABLE int indexOf(const QString &fieldName) const
Gets the field index from the field name.
QgsField field(int fieldIdx) const
Returns the field at particular index (must be in range 0..N-1).
int size() const
Returns number of items.
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
QIcon iconForField(int fieldIdx, bool considerOrigin=false) const
Returns an icon corresponding to a field index, based on the field's type and source.
static QgsEditorWidgetRegistry * editorWidgetRegistry()
Returns the global editor widget registry, used for managing all known edit widget factories.
Definition qgsgui.cpp:95
QString id
Definition qgsmaplayer.h:80
An expression with an additional enabled flag.
A relation where the referenced (parent) layer is calculated based on fields from the referencing (ch...
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:107
QgsRelationManager * relationManager
Definition qgsproject.h:117
static QgsProject * instance()
Returns the QgsProject singleton instance.
A container for the context for various read/write operations on objects.
QList< QgsRelation > referencedRelations(const QgsVectorLayer *layer=nullptr) const
Gets all relations where this layer is the referenced part (i.e.
Q_INVOKABLE QgsRelation relation(const QString &id) const
Gets access to a relation by its id.
Represents a relationship between two vector layers.
Definition qgsrelation.h:44
QString name
Definition qgsrelation.h:50
QString id
Definition qgsrelation.h:47
Represents a vector layer which manages a vector based dataset.
QgsActionManager * actions()
Returns all layer actions defined on this layer.
QgsEditFormConfig editFormConfig
#define QgsDebugError(str)
Definition qgslogger.h:40
The TabStyle struct defines color and font overrides for form fields, tabs and groups labels.
Holds the configuration for a field.
Qgis::FieldDuplicatePolicy mDuplicatePolicy
QMap< QString, QVariant > mEditorWidgetConfig
Qgis::FieldDomainSplitPolicy mSplitPolicy
Qgis::FieldDomainMergePolicy mMergePolicy