QGIS API Documentation 3.43.0-Master (5250e42f050)
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
461 emitDataChangedRecursively( QModelIndex(), QVector<int>() << Qt::DisplayRole << Qt::ForegroundRole << Qt::FontRole );
462}
463
464void QgsAttributesFormModel::emitDataChangedRecursively( const QModelIndex &parent, const QVector<int> &roles )
465{
466 emit dataChanged( index( 0, 0, parent ), index( rowCount( parent ) - 1, 0, parent ), roles );
467 for ( int i = 0; i < rowCount( parent ); i++ )
468 {
469 const QModelIndex childIndex = index( i, 0, parent );
470 if ( hasChildren( childIndex ) )
471 {
472 emitDataChangedRecursively( childIndex, roles );
473 }
474 }
475}
476
477
479 : QgsAttributesFormModel( layer, project, parent )
480{
481}
482
483Qt::ItemFlags QgsAttributesAvailableWidgetsModel::flags( const QModelIndex &index ) const
484{
485 if ( !index.isValid() )
486 return Qt::NoItemFlags;
487
488 Qt::ItemFlags flags = Qt::ItemIsEnabled;
489
490 const auto indexType = static_cast< QgsAttributesFormData::AttributesFormItemType >( index.data( QgsAttributesFormModel::ItemTypeRole ).toInt() );
491 if ( indexType != QgsAttributesFormData::WidgetType )
492 {
493 flags = flags | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable;
494 }
495
496 return flags;
497}
498
499QVariant QgsAttributesAvailableWidgetsModel::headerData( int section, Qt::Orientation orientation, int role ) const
500{
501 Q_UNUSED( section )
502 return orientation == Qt::Horizontal && role == Qt::DisplayRole ? tr( "Available Widgets" ) : QVariant {};
503}
504
506{
507 if ( !mLayer )
508 return;
509
510 beginResetModel();
511 mRootItem->deleteChildren();
512
513 // Load fields
514
515 auto itemFields = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::WidgetType, QStringLiteral( "Fields" ), tr( "Fields" ) );
516
517 const QgsFields fields = mLayer->fields();
518 for ( int i = 0; i < fields.size(); ++i )
519 {
520 const QgsField field = fields.at( i );
522 itemData.setShowLabel( true );
523
525
526 auto item = std::make_unique< QgsAttributesFormItem >();
527 item->setData( ItemFieldConfigRole, cfg );
528 item->setData( ItemNameRole, field.name() );
529 item->setData( ItemIdRole, field.name() ); // Field names act as ids
530 item->setData( ItemDisplayRole, field.alias() );
532 item->setData( ItemDataRole, itemData );
533 item->setIcon( fields.iconForField( i, true ) );
534
535 itemFields->addChild( std::move( item ) );
536 }
537
538 mRootItem->addChild( std::move( itemFields ) );
539
540 // Load relations
541
542 auto itemRelations = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::WidgetType, QStringLiteral( "Relations" ), tr( "Relations" ) );
543
544 const QList<QgsRelation> relations = mProject->relationManager()->referencedRelations( mLayer );
545
546 for ( const QgsRelation &relation : relations )
547 {
548 QString name;
549 const QgsPolymorphicRelation polymorphicRelation = relation.polymorphicRelation();
550 if ( polymorphicRelation.isValid() )
551 {
552 name = QStringLiteral( "%1 (%2)" ).arg( relation.name(), polymorphicRelation.name() );
553 }
554 else
555 {
556 name = relation.name();
557 }
559 itemData.setShowLabel( true );
560
561 auto itemRelation = std::make_unique< QgsAttributesFormItem >();
562 itemRelation->setData( ItemTypeRole, QgsAttributesFormData::Relation );
563 itemRelation->setData( ItemNameRole, name );
564 itemRelation->setData( ItemIdRole, relation.id() );
565 itemRelation->setData( ItemDataRole, itemData );
566 itemRelations->addChild( std::move( itemRelation ) );
567 }
568
569 mRootItem->addChild( std::move( itemRelations ) );
570
571 // Load form actions
572
573 auto itemActions = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::WidgetType, QStringLiteral( "Actions" ), tr( "Actions" ) );
574 mRootItem->addChild( std::move( itemActions ) );
575 populateActionItems( mLayer->actions()->actions() );
576
577 // Other widgets
578
579 auto itemOtherWidgets = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::WidgetType, QStringLiteral( "Other" ), tr( "Other Widgets" ) );
580
582 itemData.setShowLabel( true );
583 auto itemQml = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::QmlWidget, itemData, QStringLiteral( "QML Widget" ), tr( "QML Widget" ) );
584 itemOtherWidgets->addChild( std::move( itemQml ) );
585
587 itemHtmlData.setShowLabel( true );
588 auto itemHtml = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::HtmlWidget, itemHtmlData, QStringLiteral( "HTML Widget" ), tr( "HTML Widget" ) );
589 itemOtherWidgets->addChild( std::move( itemHtml ) );
590
592 itemTextData.setShowLabel( true );
593 auto itemText = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::TextWidget, itemTextData, QStringLiteral( "Text Widget" ), tr( "Text Widget" ) );
594 itemOtherWidgets->addChild( std::move( itemText ) );
595
597 itemTextData.setShowLabel( false );
598 auto itemSpacer = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::SpacerWidget, QStringLiteral( "Spacer Widget" ), tr( "Spacer Widget" ) );
599 itemOtherWidgets->addChild( std::move( itemSpacer ) );
600
601 mRootItem->addChild( std::move( itemOtherWidgets ) );
602
603 endResetModel();
604}
605
606void QgsAttributesAvailableWidgetsModel::populateLayerActions( const QList< QgsAction > actions )
607{
608 QModelIndex actionsIndex = actionContainer();
609 QgsAttributesFormItem *itemActions = itemForIndex( actionsIndex );
610
611 beginRemoveRows( actionsIndex, 0, itemActions->childCount() );
612 itemActions->deleteChildren();
613 endRemoveRows();
614
615 int count = 0;
616 for ( const auto &action : std::as_const( actions ) )
617 {
618 if ( action.isValid() && action.runable() && ( action.actionScopes().contains( QStringLiteral( "Feature" ) ) || action.actionScopes().contains( QStringLiteral( "Layer" ) ) ) )
619 {
620 count++;
621 }
622 }
623
624 beginInsertRows( actionsIndex, 0, count );
625 populateActionItems( actions );
626 endInsertRows();
627}
628
629void QgsAttributesAvailableWidgetsModel::populateActionItems( const QList<QgsAction> actions )
630{
631 QModelIndex actionsIndex = actionContainer();
632 QgsAttributesFormItem *itemActions = itemForIndex( actionsIndex );
633
634 for ( const auto &action : std::as_const( actions ) )
635 {
636 if ( action.isValid() && action.runable() && ( action.actionScopes().contains( QStringLiteral( "Feature" ) ) || action.actionScopes().contains( QStringLiteral( "Layer" ) ) ) )
637 {
638 const QString actionTitle { action.shortTitle().isEmpty() ? action.name() : action.shortTitle() };
639
641 itemData.setShowLabel( true );
642
643 auto itemAction = std::make_unique< QgsAttributesFormItem >();
644 itemAction->setData( ItemIdRole, action.id().toString() );
645 itemAction->setData( ItemTypeRole, QgsAttributesFormData::Action );
646 itemAction->setData( ItemNameRole, actionTitle );
647 itemAction->setData( ItemDataRole, itemData );
648
649 itemActions->addChild( std::move( itemAction ) );
650 }
651 }
652}
653
654QVariant QgsAttributesAvailableWidgetsModel::data( const QModelIndex &index, int role ) const
655{
656 if ( !index.isValid() )
657 return QVariant();
658
660 if ( !item )
661 return QVariant();
662
663 // Relations may be broken due to missing layers or references.
664 // Make those stand out from valid ones.
665 bool invalidRelation = false;
666 if ( ( role == Qt::ToolTipRole || role == Qt::ForegroundRole ) && item->type() == QgsAttributesFormData::Relation )
667 {
668 invalidRelation = !QgsProject::instance()->relationManager()->relation( item->id() ).isValid();
669 }
670
671 switch ( role )
672 {
673 case Qt::DisplayRole:
674 {
675 if ( !showAliases() && item->type() == QgsAttributesFormData::Field )
676 {
677 return item->name();
678 }
679
680 return item->displayName().isEmpty() ? item->name() : item->displayName();
681 }
682
683 case Qt::ToolTipRole:
684 {
686 {
687 const auto cfg = item->data( ItemFieldConfigRole ).value<QgsAttributesFormData::FieldConfig>();
688 if ( !cfg.mAlias.isEmpty() )
689 return tr( "%1 (%2)" ).arg( item->name(), cfg.mAlias );
690 else
691 return item->name();
692 }
693
694 if ( item->type() == QgsAttributesFormData::Relation && invalidRelation )
695 {
696 // Relation name will be displayed, inform users why it's red via tooltip
697 return tr( "Invalid relation" );
698 }
699
700 return QVariant();
701 }
702
703 case Qt::DecorationRole:
704 return item->icon();
705
706 case Qt::BackgroundRole:
707 {
709 return QBrush( Qt::lightGray );
710
711 return QVariant();
712 }
713
714 case Qt::ForegroundRole:
715 {
716 if ( item->type() == QgsAttributesFormData::Field )
717 {
718 if ( showAliases() && item->displayName().isEmpty() )
719 {
720 return QBrush( QColor( Qt::lightGray ) );
721 }
722 }
723
724 if ( item->type() == QgsAttributesFormData::Relation && invalidRelation )
725 {
726 return QBrush( QColor( 255, 0, 0 ) );
727 }
728
729 return QVariant();
730 }
731
732 case Qt::FontRole:
733 {
734 if ( item->type() == QgsAttributesFormData::Field )
735 {
736 if ( showAliases() && item->displayName().isEmpty() )
737 {
738 QFont font = QFont();
739 font.setItalic( true );
740 return font;
741 }
742 }
743 return QVariant();
744 }
745
746 case ItemDataRole:
748 case ItemNameRole:
749 case ItemTypeRole:
750 case ItemIdRole:
751 case ItemDisplayRole:
752 return item->data( role );
753
754 default:
755 return QVariant();
756 }
757}
758
759bool QgsAttributesAvailableWidgetsModel::setData( const QModelIndex &index, const QVariant &value, int role )
760{
761 if ( !index.isValid() )
762 return false;
763
765 bool result = item->setData( role, value );
766
767 if ( result )
768 emit dataChanged( index, index, { role } );
769
770 return result;
771}
772
774{
775 return Qt::CopyAction;
776}
777
779{
780 return QStringList() << QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" );
781}
782
783QMimeData *QgsAttributesAvailableWidgetsModel::mimeData( const QModelIndexList &indexes ) const
784{
785 if ( indexes.count() == 0 )
786 return nullptr;
787
788 const QStringList types = mimeTypes();
789 if ( types.isEmpty() )
790 return nullptr;
791
792 QMimeData *data = new QMimeData();
793 const QString format = types.at( 0 );
794 QByteArray encoded;
795 QDataStream stream( &encoded, QIODevice::WriteOnly );
796
797 // Sort indexes since their order reflects selection order
798 QModelIndexList sortedIndexes = indexes;
799
800 std::sort( sortedIndexes.begin(), sortedIndexes.end(), [this]( const QModelIndex &a, const QModelIndex &b ) {
801 return indexLessThan( a, b );
802 } );
803
804 for ( const QModelIndex &index : std::as_const( sortedIndexes ) )
805 {
806 if ( index.isValid() )
807 {
808 const QString itemId = index.data( QgsAttributesFormModel::ItemIdRole ).toString();
809 const QString itemName = index.data( QgsAttributesFormModel::ItemNameRole ).toString();
810 int itemType = index.data( QgsAttributesFormModel::ItemTypeRole ).toInt();
811
812 stream << itemId << itemType << itemName;
813 }
814 }
815
816 data->setData( format, encoded );
817 return data;
818}
819
821{
822 if ( mRootItem->childCount() > 0 )
823 {
824 const int row = 0;
825 QgsAttributesFormItem *item = mRootItem->child( row );
826 if ( item && item->name() == QLatin1String( "Fields" ) && item->type() == QgsAttributesFormData::WidgetType )
827 return createIndex( row, 0, item );
828 }
829 return QModelIndex();
830}
831
833{
834 if ( mRootItem->childCount() > 1 )
835 {
836 const int row = 1;
837 QgsAttributesFormItem *item = mRootItem->child( row );
838 if ( item && item->name() == QLatin1String( "Relations" ) && item->type() == QgsAttributesFormData::WidgetType )
839 return createIndex( row, 0, item );
840 }
841 return QModelIndex();
842}
843
845{
846 if ( mRootItem->childCount() > 2 )
847 {
848 const int row = 2;
849 QgsAttributesFormItem *item = mRootItem->child( row );
850 if ( item && item->name() == QLatin1String( "Actions" ) && item->type() == QgsAttributesFormData::WidgetType )
851 return createIndex( row, 0, item );
852 }
853 return QModelIndex();
854}
855
856QModelIndex QgsAttributesAvailableWidgetsModel::fieldModelIndex( const QString &fieldName ) const
857{
858 if ( mRootItem->childCount() == 0 )
859 return QModelIndex();
860
861 QgsAttributesFormItem *fieldItems = mRootItem->child( 0 );
862 if ( !fieldItems || fieldItems->name() != QLatin1String( "Fields" ) || fieldItems->type() != QgsAttributesFormData::WidgetType )
863 return QModelIndex();
864
865 QgsAttributesFormItem *item = fieldItems->firstTopChild( QgsAttributesFormData::Field, fieldName );
866 return item ? createIndex( item->row(), 0, item ) : QModelIndex();
867}
868
869
871 : QgsAttributesFormModel( layer, project, parent )
872{
873}
874
875QVariant QgsAttributesFormLayoutModel::headerData( int section, Qt::Orientation orientation, int role ) const
876{
877 Q_UNUSED( section )
878 return orientation == Qt::Horizontal && role == Qt::DisplayRole ? tr( "Form Layout" ) : QVariant {};
879}
880
881Qt::ItemFlags QgsAttributesFormLayoutModel::flags( const QModelIndex &index ) const
882{
883 if ( !index.isValid() )
884 return Qt::ItemIsDropEnabled;
885
886 Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
887
890 flags |= Qt::ItemIsDropEnabled;
891
892 return flags;
893}
894
896{
897 if ( !mLayer )
898 return;
899
900 beginResetModel();
901 mRootItem->deleteChildren();
902
903 const auto editorElements = mLayer->editFormConfig().tabs();
904 for ( QgsAttributeEditorElement *editorElement : editorElements )
905 {
906 loadAttributeEditorElementItem( editorElement, mRootItem.get() );
907 }
908
909 endResetModel();
910}
911
912void QgsAttributesFormLayoutModel::loadAttributeEditorElementItem( QgsAttributeEditorElement *const editorElement, QgsAttributesFormItem *parent, const int position )
913{
914 auto setCommonProperties = [editorElement]( QgsAttributesFormData::AttributeFormItemData &itemData ) {
915 itemData.setShowLabel( editorElement->showLabel() );
916 itemData.setLabelStyle( editorElement->labelStyle() );
917 itemData.setHorizontalStretch( editorElement->horizontalStretch() );
918 itemData.setVerticalStretch( editorElement->verticalStretch() );
919 };
920
921 auto editorItem = std::make_unique< QgsAttributesFormItem >();
922
923 switch ( editorElement->type() )
924 {
926 {
928 setCommonProperties( itemData );
929
930 editorItem->setData( ItemNameRole, editorElement->name() );
931 editorItem->setData( ItemIdRole, editorElement->name() ); // Field names act as ids
932 editorItem->setData( ItemTypeRole, QgsAttributesFormData::Field );
933 editorItem->setData( ItemDataRole, itemData );
934
935 const int fieldIndex = mLayer->fields().indexOf( editorElement->name() );
936 if ( fieldIndex != -1 )
937 {
938 editorItem->setData( ItemDisplayRole, mLayer->fields().field( fieldIndex ).alias() );
939 }
940
941 break;
942 }
943
945 {
946 const QgsAttributeEditorAction *actionEditor = static_cast<const QgsAttributeEditorAction *>( editorElement );
947 const QgsAction action { actionEditor->action( mLayer ) };
948 if ( action.isValid() )
949 {
951 setCommonProperties( itemData );
952
953 editorItem->setData( ItemIdRole, action.id().toString() );
954 editorItem->setData( ItemNameRole, action.shortTitle().isEmpty() ? action.name() : action.shortTitle() );
955 editorItem->setData( ItemTypeRole, QgsAttributesFormData::Action );
956 editorItem->setData( ItemDataRole, itemData );
957 }
958 else
959 {
960 QgsDebugError( QStringLiteral( "Invalid form action" ) );
961 }
962 break;
963 }
964
966 {
968 setCommonProperties( itemData );
969
970 const QgsAttributeEditorRelation *relationEditor = static_cast<const QgsAttributeEditorRelation *>( editorElement );
972 relationEditorConfig.mRelationWidgetType = relationEditor->relationWidgetTypeId();
973 relationEditorConfig.mRelationWidgetConfig = relationEditor->relationEditorConfiguration();
974 relationEditorConfig.nmRelationId = relationEditor->nmRelationId();
975 relationEditorConfig.forceSuppressFormPopup = relationEditor->forceSuppressFormPopup();
976 relationEditorConfig.label = relationEditor->label();
977 itemData.setRelationEditorConfiguration( relationEditorConfig );
978
979 QgsRelation relation = relationEditor->relation();
980 if ( relation.id().isEmpty() )
981 {
982 // If relation is coming from an internal move, we lose the id.
983 // Go to relation manager and bring relation properties.
984 relation = mProject->relationManager()->relation( editorElement->name() );
985 }
986
987 editorItem->setData( ItemIdRole, relation.id() );
988 editorItem->setData( ItemNameRole, relation.name() );
989 editorItem->setData( ItemDisplayRole, relationEditorConfig.label );
990 editorItem->setData( ItemTypeRole, QgsAttributesFormData::Relation );
991 editorItem->setData( ItemDataRole, itemData );
992
993 break;
994 }
995
997 {
999 setCommonProperties( itemData );
1000
1001 editorItem->setData( ItemNameRole, editorElement->name() );
1002 editorItem->setData( ItemIdRole, editorElement->name() ); // Containers don't have id, use name to make them searchable
1003 editorItem->setData( ItemTypeRole, QgsAttributesFormData::Container );
1004
1005 const QgsAttributeEditorContainer *container = static_cast<const QgsAttributeEditorContainer *>( editorElement );
1006 if ( !container )
1007 break;
1008
1009 itemData.setColumnCount( container->columnCount() );
1010 itemData.setContainerType( container->type() );
1011 itemData.setBackgroundColor( container->backgroundColor() );
1012 itemData.setVisibilityExpression( container->visibilityExpression() );
1013 itemData.setCollapsedExpression( container->collapsedExpression() );
1014 itemData.setCollapsed( container->collapsed() );
1015
1016 editorItem->setData( ItemDataRole, itemData );
1017
1018 const QList<QgsAttributeEditorElement *> children = container->children();
1019 for ( QgsAttributeEditorElement *childElement : children )
1020 {
1021 loadAttributeEditorElementItem( childElement, editorItem.get() );
1022 }
1023 break;
1024 }
1025
1027 {
1028 const QgsAttributeEditorQmlElement *qmlElementEditor = static_cast<const QgsAttributeEditorQmlElement *>( editorElement );
1030 setCommonProperties( itemData );
1031
1033 qmlEdConfig.qmlCode = qmlElementEditor->qmlCode();
1034 itemData.setQmlElementEditorConfiguration( qmlEdConfig );
1035
1036 editorItem->setData( ItemNameRole, editorElement->name() );
1037 editorItem->setData( ItemTypeRole, QgsAttributesFormData::QmlWidget );
1038 editorItem->setData( ItemDataRole, itemData );
1039 break;
1040 }
1041
1043 {
1044 const QgsAttributeEditorHtmlElement *htmlElementEditor = static_cast<const QgsAttributeEditorHtmlElement *>( editorElement );
1046 setCommonProperties( itemData );
1047
1049 htmlEdConfig.htmlCode = htmlElementEditor->htmlCode();
1050 itemData.setHtmlElementEditorConfiguration( htmlEdConfig );
1051
1052 editorItem->setData( ItemNameRole, editorElement->name() );
1053 editorItem->setData( ItemTypeRole, QgsAttributesFormData::HtmlWidget );
1054 editorItem->setData( ItemDataRole, itemData );
1055 break;
1056 }
1057
1059 {
1060 const QgsAttributeEditorTextElement *textElementEditor = static_cast<const QgsAttributeEditorTextElement *>( editorElement );
1062 setCommonProperties( itemData );
1063
1065 textEdConfig.text = textElementEditor->text();
1066 itemData.setTextElementEditorConfiguration( textEdConfig );
1067
1068 editorItem->setData( ItemNameRole, editorElement->name() );
1069 editorItem->setData( ItemTypeRole, QgsAttributesFormData::TextWidget );
1070 editorItem->setData( ItemDataRole, itemData );
1071 break;
1072 }
1073
1075 {
1076 const QgsAttributeEditorSpacerElement *spacerElementEditor = static_cast<const QgsAttributeEditorSpacerElement *>( editorElement );
1078 setCommonProperties( itemData );
1079 itemData.setShowLabel( false );
1080
1082 spacerEdConfig.drawLine = spacerElementEditor->drawLine();
1083 itemData.setSpacerElementEditorConfiguration( spacerEdConfig );
1084
1085 editorItem->setData( ItemNameRole, editorElement->name() );
1086 editorItem->setData( ItemTypeRole, QgsAttributesFormData::SpacerWidget );
1087 editorItem->setData( ItemDataRole, itemData );
1088 break;
1089 }
1090
1092 {
1093 QgsDebugError( QStringLiteral( "Not loading invalid attribute editor type..." ) );
1094 break;
1095 }
1096 }
1097
1098 if ( position >= 0 && position < parent->childCount() )
1099 {
1100 parent->insertChild( position, std::move( editorItem ) );
1101 }
1102 else
1103 {
1104 parent->addChild( std::move( editorItem ) );
1105 }
1106}
1107
1108QVariant QgsAttributesFormLayoutModel::data( const QModelIndex &index, int role ) const
1109{
1110 if ( !index.isValid() )
1111 return QVariant();
1112
1113 if ( role == ItemFieldConfigRole ) // This model doesn't store data for that role
1114 return false;
1115
1117 if ( !item )
1118 return QVariant();
1119
1120 // Fields may be present in the form layout configuration
1121 // even if their corresponding layer fields were deleted.
1122 // Make those stand out from existent ones.
1123 const int fieldIndex = mLayer->fields().indexOf( item->name() );
1124 const bool invalidField = fieldIndex == -1;
1125
1126 // Relations may be broken due to missing layers or references.
1127 // Make those stand out from valid ones.
1128 bool invalidRelation = false;
1129 if ( ( role == Qt::DisplayRole || role == Qt::ToolTipRole || role == Qt::ForegroundRole ) && item->type() == QgsAttributesFormData::Relation )
1130 {
1131 invalidRelation = !QgsProject::instance()->relationManager()->relation( item->id() ).isValid();
1132 }
1133
1134 switch ( role )
1135 {
1136 case Qt::DisplayRole:
1137 {
1138 if ( item->type() == QgsAttributesFormData::Relation && invalidRelation )
1139 {
1140 // Invalid relations can have an id, if that's the case, we have a name.
1141 // Only set a new name if id is missing.
1142 if ( item->id().isEmpty() )
1143 {
1144 return tr( "Invalid relation" );
1145 }
1146 }
1147
1148 if ( !showAliases() && ( item->type() == QgsAttributesFormData::Field || item->type() == QgsAttributesFormData::Relation ) )
1149 {
1150 return item->name();
1151 }
1152
1153 return item->displayName().isEmpty() ? item->name() : item->displayName();
1154 }
1155
1156 case Qt::ToolTipRole:
1157 {
1158 if ( item->type() == QgsAttributesFormData::Field )
1159 {
1160 if ( invalidField )
1161 {
1162 return tr( "Invalid field" );
1163 }
1164 else
1165 {
1166 return item->name();
1167 }
1168 }
1169
1170 if ( item->type() == QgsAttributesFormData::Relation && invalidRelation )
1171 {
1172 if ( !item->id().isEmpty() )
1173 {
1174 // The relation name is shown, let's inform users via tooltip why it's red
1175 return tr( "Invalid relation" );
1176 }
1177 }
1178
1179 return QVariant();
1180 }
1181
1182 case Qt::DecorationRole:
1183 return item->icon();
1184
1185 case Qt::BackgroundRole:
1186 {
1187 if ( item->type() == QgsAttributesFormData::Container )
1188 return QBrush( Qt::lightGray );
1189
1190 return QVariant();
1191 }
1192
1193 case Qt::ForegroundRole:
1194 {
1195 if ( item->type() == QgsAttributesFormData::Field )
1196 {
1197 if ( invalidField )
1198 {
1199 return QBrush( QColor( 255, 0, 0 ) );
1200 }
1201 else if ( showAliases() && item->displayName().isEmpty() )
1202 {
1203 return QBrush( QColor( Qt::lightGray ) );
1204 }
1205 }
1206
1207 if ( item->type() == QgsAttributesFormData::Relation )
1208 {
1209 if ( invalidRelation )
1210 {
1211 return QBrush( QColor( 255, 0, 0 ) );
1212 }
1213 else if ( showAliases() && item->displayName().isEmpty() )
1214 {
1215 return QBrush( QColor( Qt::lightGray ) );
1216 }
1217 }
1218
1219 return QVariant();
1220 }
1221
1222 case Qt::FontRole:
1223 {
1224 if ( item->type() == QgsAttributesFormData::Field )
1225 {
1226 if ( !invalidField && showAliases() && item->displayName().isEmpty() )
1227 {
1228 QFont font = QFont();
1229 font.setItalic( true );
1230 return font;
1231 }
1232 }
1233
1234 if ( item->type() == QgsAttributesFormData::Relation )
1235 {
1236 if ( !invalidRelation && showAliases() && item->displayName().isEmpty() )
1237 {
1238 QFont font = QFont();
1239 font.setItalic( true );
1240 return font;
1241 }
1242 }
1243
1244 return QVariant();
1245 }
1246
1247 case ItemDataRole:
1248 case ItemNameRole:
1249 case ItemIdRole:
1250 case ItemTypeRole:
1251 case ItemDisplayRole:
1252 return item->data( role );
1253
1254 default:
1255 return QVariant();
1256 }
1257}
1258
1259bool QgsAttributesFormLayoutModel::setData( const QModelIndex &index, const QVariant &value, int role )
1260{
1261 if ( !index.isValid() )
1262 return false;
1263
1264 if ( role == ItemFieldConfigRole ) // This model doesn't store data for that role
1265 return false;
1266
1268 bool result = item->setData( role, value );
1269
1270 if ( result )
1271 emit dataChanged( index, index, { role } );
1272
1273 return result;
1274}
1275
1276bool QgsAttributesFormLayoutModel::removeRows( int row, int count, const QModelIndex &parent )
1277{
1278 if ( row < 0 )
1279 return false;
1280
1282
1283 if ( row > item->childCount() - count )
1284 return false;
1285
1286 beginRemoveRows( parent, row, row + count - 1 );
1287 for ( int r = 0; r < count; ++r )
1288 item->deleteChildAtIndex( row );
1289 endRemoveRows();
1290 return true;
1291}
1292
1293bool QgsAttributesFormLayoutModel::removeRow( int row, const QModelIndex &parent )
1294{
1295 beginRemoveRows( parent, row, row );
1297 item->deleteChildAtIndex( row );
1298 endRemoveRows();
1299 return true;
1300}
1301
1303{
1304 return Qt::MoveAction;
1305}
1306
1308{
1309 return Qt::DropAction::CopyAction | Qt::DropAction::MoveAction;
1310}
1311
1313{
1314 return QStringList() << QStringLiteral( "application/x-qgsattributesformlayoutelement" ) << QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" );
1315}
1316
1317QModelIndexList QgsAttributesFormLayoutModel::curateIndexesForMimeData( const QModelIndexList &indexes ) const
1318{
1319 QModelIndexList containerList;
1320 for ( const auto index : indexes )
1321 {
1322 const auto indexType = static_cast< QgsAttributesFormData::AttributesFormItemType >( index.data( QgsAttributesFormModel::ItemTypeRole ).toInt() );
1323 if ( indexType == QgsAttributesFormData::Container )
1324 {
1325 containerList << index;
1326 }
1327 }
1328
1329 if ( containerList.size() == 0 )
1330 return indexes;
1331
1332 QModelIndexList curatedIndexes;
1333
1334 // Iterate searching if current index is child of any container in containerList (recursively)
1335 for ( const auto index : indexes )
1336 {
1337 QModelIndex parent = index.parent();
1338 bool redundantChild = false;
1339
1340 while ( parent.isValid() )
1341 {
1342 if ( containerList.contains( parent ) )
1343 {
1344 redundantChild = true;
1345 break;
1346 }
1347
1348 parent = parent.parent();
1349 }
1350
1351 if ( !redundantChild )
1352 curatedIndexes << index;
1353 }
1354
1355 return curatedIndexes;
1356}
1357
1358QMimeData *QgsAttributesFormLayoutModel::mimeData( const QModelIndexList &indexes ) const
1359{
1360 if ( indexes.count() == 0 )
1361 return nullptr;
1362
1363 // Discard redundant indexes
1364 QModelIndexList curatedIndexes;
1365 if ( indexes.count() > 1 )
1366 {
1367 curatedIndexes = curateIndexesForMimeData( indexes );
1368 }
1369 else
1370 {
1371 curatedIndexes = indexes;
1372 }
1373
1374 const QStringList types = mimeTypes();
1375 if ( types.isEmpty() )
1376 return nullptr;
1377
1378 QMimeData *data = new QMimeData();
1379 const QString format = types.at( 0 );
1380 QByteArray encoded;
1381 QDataStream stream( &encoded, QIODevice::WriteOnly );
1382
1383 // Sort indexes since their order reflects selection order
1384 std::sort( curatedIndexes.begin(), curatedIndexes.end(), [this]( const QModelIndex &a, const QModelIndex &b ) {
1385 return indexLessThan( a, b );
1386 } );
1387
1388 for ( const QModelIndex &index : std::as_const( curatedIndexes ) )
1389 {
1390 if ( index.isValid() )
1391 {
1392 QDomDocument doc;
1393
1394 QDomElement rootElem = doc.createElement( QStringLiteral( "form_layout_mime" ) );
1396 QDomElement editorElem = editor->toDomElement( doc );
1397 rootElem.appendChild( editorElem );
1398
1399 doc.appendChild( rootElem );
1400 stream << doc.toString( -1 );
1401 }
1402 }
1403
1404 data->setData( format, encoded );
1405 return data;
1406}
1407
1408bool QgsAttributesFormLayoutModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
1409{
1410 Q_UNUSED( column )
1411 bool isDropSuccessful = false;
1412 int rows = 0;
1413
1414 if ( row == -1 ) // Dropped at invalid index
1415 row = rowCount( parent ); // Let's append the item
1416
1417 if ( action == Qt::IgnoreAction )
1418 {
1419 isDropSuccessful = true;
1420 }
1421 else if ( data->hasFormat( QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" ) ) )
1422 {
1423 Q_ASSERT( action == Qt::CopyAction ); // External drop
1424 QByteArray itemData = data->data( QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" ) );
1425 QDataStream stream( &itemData, QIODevice::ReadOnly );
1426
1427 while ( !stream.atEnd() )
1428 {
1429 QString itemId;
1430 int itemTypeInt;
1431 QString itemName;
1432 stream >> itemId >> itemTypeInt >> itemName;
1433
1434 const auto itemType = static_cast< QgsAttributesFormData::AttributesFormItemType >( itemTypeInt );
1435 insertChild( parent, row + rows, itemId, itemType, itemName );
1436
1437 isDropSuccessful = true;
1438
1439 QModelIndex addedIndex = index( row + rows, 0, parent );
1440 emit externalItemDropped( addedIndex );
1441
1442 rows++;
1443 }
1444 }
1445 else if ( data->hasFormat( QStringLiteral( "application/x-qgsattributesformlayoutelement" ) ) )
1446 {
1447 Q_ASSERT( action == Qt::MoveAction ); // Internal move
1448 QByteArray itemData = data->data( QStringLiteral( "application/x-qgsattributesformlayoutelement" ) );
1449 QDataStream stream( &itemData, QIODevice::ReadOnly );
1450
1451 while ( !stream.atEnd() )
1452 {
1453 QString text;
1454 stream >> text;
1455
1456 QDomDocument doc;
1457 if ( !doc.setContent( text ) )
1458 continue;
1459 const QDomElement rootElem = doc.documentElement();
1460 if ( rootElem.tagName() != QLatin1String( "form_layout_mime" ) || !rootElem.hasChildNodes() )
1461 continue;
1462 const QDomElement childElem = rootElem.firstChild().toElement();
1463
1464 // Build editor element from XML and add/insert it to parent
1466 beginInsertRows( parent, row + rows, row + rows );
1467 loadAttributeEditorElementItem( editor, itemForIndex( parent ), row + rows );
1468 endInsertRows();
1469
1470 isDropSuccessful = true;
1471
1472 QModelIndex addedIndex = index( row + rows, 0, parent );
1473 emit internalItemDropped( addedIndex );
1474
1475 rows++;
1476 }
1477 }
1478
1479 return isDropSuccessful;
1480}
1481
1482void QgsAttributesFormLayoutModel::updateAliasForFieldItemsRecursive( QgsAttributesFormItem *parent, const QString &fieldName, const QString &fieldAlias )
1483{
1484 for ( int i = 0; i < parent->childCount(); i++ )
1485 {
1486 QgsAttributesFormItem *child = parent->child( i );
1487 if ( child->name() == fieldName && child->type() == QgsAttributesFormData::Field )
1488 {
1489 child->setData( ItemDisplayRole, fieldAlias );
1490 const QModelIndex index = createIndex( child->row(), 0, child );
1491 emit dataChanged( index, index );
1492 }
1493
1494 if ( child->childCount() > 0 )
1495 {
1496 updateAliasForFieldItemsRecursive( child, fieldName, fieldAlias );
1497 }
1498 }
1499}
1500
1501void QgsAttributesFormLayoutModel::updateAliasForFieldItems( const QString &fieldName, const QString &fieldAlias )
1502{
1503 updateAliasForFieldItemsRecursive( mRootItem.get(), fieldName, fieldAlias );
1504}
1505
1506QList< QgsAddAttributeFormContainerDialog::ContainerPair > QgsAttributesFormLayoutModel::recursiveListOfContainers( QgsAttributesFormItem *parent ) const
1507{
1508 QList< QgsAddAttributeFormContainerDialog::ContainerPair > containerList;
1509 for ( int i = 0; i < parent->childCount(); i++ )
1510 {
1511 QgsAttributesFormItem *child = parent->child( i );
1512 if ( child->type() == QgsAttributesFormData::Container )
1513 {
1514 containerList << QgsAddAttributeFormContainerDialog::ContainerPair( child->name(), createIndex( child->row(), 0, child ) );
1515 }
1516
1517 if ( child->childCount() > 0 )
1518 {
1519 containerList.append( recursiveListOfContainers( child ) );
1520 }
1521 }
1522
1523 return containerList;
1524}
1525
1527{
1528 QgsAttributeEditorElement *widgetDef = nullptr;
1529
1531 const int indexType = static_cast< QgsAttributesFormData::AttributesFormItemType >( index.data( QgsAttributesFormModel::ItemTypeRole ).toInt() );
1532 const QString indexName = index.data( QgsAttributesFormModel::ItemNameRole ).toString();
1533 const QString indexId = index.data( QgsAttributesFormModel::ItemIdRole ).toString();
1534
1535 switch ( indexType )
1536 {
1538 {
1539 const int fieldIndex = mLayer->fields().lookupField( indexName );
1540 widgetDef = new QgsAttributeEditorField( indexName, fieldIndex, parent );
1541 break;
1542 }
1543
1545 {
1546 const QgsAction action { mLayer->actions()->action( indexId ) };
1547 widgetDef = new QgsAttributeEditorAction( action, parent );
1548 break;
1549 }
1550
1552 {
1553 const QgsRelation relation = mProject->relationManager()->relation( indexId );
1554
1557 relDef->setRelationWidgetTypeId( relationEditorConfig.mRelationWidgetType );
1558 relDef->setRelationEditorConfiguration( relationEditorConfig.mRelationWidgetConfig );
1559 relDef->setNmRelationId( relationEditorConfig.nmRelationId );
1560 relDef->setForceSuppressFormPopup( relationEditorConfig.forceSuppressFormPopup );
1561 relDef->setLabel( relationEditorConfig.label );
1562 widgetDef = relDef;
1563 break;
1564 }
1565
1567 {
1568 QgsAttributeEditorContainer *container = new QgsAttributeEditorContainer( indexName, parent, itemData.backgroundColor() );
1569 container->setColumnCount( itemData.columnCount() );
1570 // only top-level containers can be tabs
1572 bool isTopLevel = !index.parent().isValid();
1573 if ( type == Qgis::AttributeEditorContainerType::Tab && !isTopLevel )
1574 {
1575 // a tab container found which isn't at the top level -- reset it to a group box instead
1577 }
1578 container->setType( type );
1579 container->setCollapsed( itemData.collapsed() );
1580 container->setCollapsedExpression( itemData.collapsedExpression() );
1581 container->setVisibilityExpression( itemData.visibilityExpression() );
1582 container->setBackgroundColor( itemData.backgroundColor() );
1583
1584 QModelIndex childIndex;
1585 for ( int t = 0; t < rowCount( index ); t++ )
1586 {
1587 childIndex = this->index( t, 0, index );
1588 QgsAttributeEditorElement *element { createAttributeEditorWidget( childIndex, container ) };
1589 if ( element )
1590 container->addChildElement( element );
1591 }
1592 widgetDef = container;
1593 break;
1594 }
1595
1597 {
1599 element->setQmlCode( itemData.qmlElementEditorConfiguration().qmlCode );
1600 widgetDef = element;
1601 break;
1602 }
1603
1605 {
1608 widgetDef = element;
1609 break;
1610 }
1611
1613 {
1615 element->setText( itemData.textElementEditorConfiguration().text );
1616 widgetDef = element;
1617 break;
1618 }
1619
1621 {
1624 widgetDef = element;
1625 break;
1626 }
1627
1629 default:
1630 break;
1631 }
1632
1633 if ( widgetDef )
1634 {
1635 widgetDef->setShowLabel( itemData.showLabel() );
1636 widgetDef->setLabelStyle( itemData.labelStyle() );
1637 widgetDef->setHorizontalStretch( itemData.horizontalStretch() );
1638 widgetDef->setVerticalStretch( itemData.verticalStretch() );
1639 }
1640
1641 return widgetDef;
1642}
1643
1644QList< QgsAddAttributeFormContainerDialog::ContainerPair > QgsAttributesFormLayoutModel::listOfContainers() const
1645{
1646 return recursiveListOfContainers( mRootItem.get() );
1647}
1648
1649void QgsAttributesFormLayoutModel::addContainer( QModelIndex &parent, const QString &name, int columnCount, Qgis::AttributeEditorContainerType type )
1650{
1651 beginInsertRows( parent, rowCount( parent ), rowCount( parent ) );
1652
1653 QgsAttributesFormItem *parentItem = itemForIndex( parent );
1654
1655 std::unique_ptr< QgsAttributesFormItem > containerItem = std::make_unique< QgsAttributesFormItem >( QgsAttributesFormData::Container, name, QString(), parentItem );
1656
1658 itemData.setColumnCount( columnCount );
1660
1661 containerItem->setData( QgsAttributesFormModel::ItemDataRole, itemData );
1662 containerItem->setData( QgsAttributesFormModel::ItemIdRole, name ); // Make it searchable
1663 parentItem->addChild( std::move( containerItem ) );
1664
1665 endInsertRows();
1666}
1667
1668void QgsAttributesFormLayoutModel::insertChild( const QModelIndex &parent, int row, const QString &itemId, QgsAttributesFormData::AttributesFormItemType itemType, const QString &itemName )
1669{
1670 if ( row < 0 )
1671 return;
1672
1673 beginInsertRows( parent, row, row );
1674 std::unique_ptr< QgsAttributesFormItem > item = std::make_unique< QgsAttributesFormItem >();
1675
1676 item->setData( QgsAttributesFormModel::ItemIdRole, itemId );
1677 item->setData( QgsAttributesFormModel::ItemTypeRole, itemType );
1678 item->setData( QgsAttributesFormModel::ItemNameRole, itemName );
1679
1680 itemForIndex( parent )->insertChild( row, std::move( item ) );
1681 endInsertRows();
1682}
1683
1684
1686 : QSortFilterProxyModel( parent )
1687{
1688}
1689
1691{
1692 mModel = model;
1693 QSortFilterProxyModel::setSourceModel( mModel );
1694}
1695
1697{
1698 return mFilterText;
1699}
1700
1701void QgsAttributesFormProxyModel::setFilterText( const QString &filterText )
1702{
1703 // Since we want to allow refreshing the filter when, e.g.,
1704 // users switch to aliases, then we allow this method to be
1705 // executed even if previous and new filters are equal
1706
1707 mFilterText = filterText.trimmed();
1708 invalidate();
1709}
1710
1711bool QgsAttributesFormProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const
1712{
1713 if ( mFilterText.isEmpty() )
1714 return true;
1715
1716 QModelIndex sourceIndex = sourceModel()->index( sourceRow, 0, sourceParent );
1717 if ( !sourceIndex.isValid() )
1718 return false;
1719
1720 // If name or alias match, accept it before any other checks
1721 if ( sourceIndex.data( QgsAttributesFormModel::ItemNameRole ).toString().contains( mFilterText, Qt::CaseInsensitive ) || sourceIndex.data( QgsAttributesFormModel::ItemDisplayRole ).toString().contains( mFilterText, Qt::CaseInsensitive ) )
1722 return true;
1723
1724 // Child is accepted if any of its parents is accepted
1725 QModelIndex parent = sourceIndex.parent();
1726 while ( parent.isValid() )
1727 {
1728 if ( parent.data( QgsAttributesFormModel::ItemNameRole ).toString().contains( mFilterText, Qt::CaseInsensitive ) || parent.data( QgsAttributesFormModel::ItemDisplayRole ).toString().contains( mFilterText, Qt::CaseInsensitive ) )
1729 return true;
1730
1731 parent = parent.parent();
1732 }
1733
1734 return false;
1735}
AttributeEditorContainerType
Attribute editor container types.
Definition qgis.h:5329
@ 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.
void emitDataChangedRecursively(const QModelIndex &parent=QModelIndex(), const QVector< int > &roles=QVector< int >())
Emits dataChanged signal for all parent items in a model.
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 filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
void setFilterText(const QString &filterText=QString())
Sets the filter text.
const QString filterText() const
Returns the text used to filter source model items.
QgsAttributesFormProxyModel(QObject *parent=nullptr)
Constructor for QgsAttributesFormProxyModel, with the given parent.
void setAttributesFormSourceModel(QgsAttributesFormModel *model)
Sets the source model for the proxy model.
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