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