18 #include <QPushButton>
20 #include <QHBoxLayout>
55 for (
int i = 0; i < list.size(); ++i )
57 if ( !list.at( i ).isNull() )
67 mTopLayout =
new QVBoxLayout(
this );
68 mTopLayout->setContentsMargins( 0, 0, 0, 0 );
70 setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::Fixed );
72 setLayout( mTopLayout );
74 QHBoxLayout *editLayout =
new QHBoxLayout();
75 editLayout->setContentsMargins( 0, 0, 0, 0 );
76 editLayout->setSpacing( 2 );
79 mChooserContainer =
new QWidget;
80 editLayout->addWidget( mChooserContainer );
81 QHBoxLayout *chooserLayout =
new QHBoxLayout;
82 chooserLayout->setContentsMargins( 0, 0, 0, 0 );
83 mFilterLayout =
new QHBoxLayout;
84 mFilterLayout->setContentsMargins( 0, 0, 0, 0 );
85 mFilterContainer =
new QWidget;
86 mFilterContainer->setLayout( mFilterLayout );
87 mChooserContainer->setLayout( chooserLayout );
88 chooserLayout->addWidget( mFilterContainer );
91 mChooserContainer->layout()->addWidget( mComboBox );
94 mLineEdit =
new QLineEdit();
95 mLineEdit->setReadOnly(
true );
96 editLayout->addWidget( mLineEdit );
99 mOpenFormButton =
new QToolButton();
101 mOpenFormButton->setText( tr(
"Open Related Feature Form" ) );
102 editLayout->addWidget( mOpenFormButton );
104 mAddEntryButton =
new QToolButton();
106 mAddEntryButton->setText( tr(
"Add New Entry" ) );
107 editLayout->addWidget( mAddEntryButton );
110 mHighlightFeatureButton =
new QToolButton(
this );
111 mHighlightFeatureButton->setPopupMode( QToolButton::MenuButtonPopup );
112 mHighlightFeatureAction =
new QAction(
QgsApplication::getThemeIcon( QStringLiteral(
"/mActionHighlightFeature.svg" ) ), tr(
"Highlight feature" ),
this );
113 mScaleHighlightFeatureAction =
new QAction(
QgsApplication::getThemeIcon( QStringLiteral(
"/mActionScaleHighlightFeature.svg" ) ), tr(
"Scale and highlight feature" ),
this );
114 mPanHighlightFeatureAction =
new QAction(
QgsApplication::getThemeIcon( QStringLiteral(
"/mActionPanHighlightFeature.svg" ) ), tr(
"Pan and highlight feature" ),
this );
115 mHighlightFeatureButton->addAction( mHighlightFeatureAction );
116 mHighlightFeatureButton->addAction( mScaleHighlightFeatureAction );
117 mHighlightFeatureButton->addAction( mPanHighlightFeatureAction );
118 mHighlightFeatureButton->setDefaultAction( mHighlightFeatureAction );
119 editLayout->addWidget( mHighlightFeatureButton );
122 mMapIdentificationButton =
new QToolButton(
this );
124 mMapIdentificationButton->setText( tr(
"Select on Map" ) );
125 mMapIdentificationButton->setCheckable(
true );
126 editLayout->addWidget( mMapIdentificationButton );
129 mRemoveFKButton =
new QToolButton(
this );
131 mRemoveFKButton->setText( tr(
"No Selection" ) );
132 editLayout->addWidget( mRemoveFKButton );
135 mTopLayout->addLayout( editLayout );
139 mAttributeEditorLayout =
new QVBoxLayout( mAttributeEditorFrame );
140 mAttributeEditorFrame->setLayout( mAttributeEditorLayout );
141 mAttributeEditorFrame->setSizePolicy( mAttributeEditorFrame->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding );
142 mTopLayout->addWidget( mAttributeEditorFrame );
145 mInvalidLabel =
new QLabel( tr(
"The relation is not valid. Please make sure your relation definitions are OK." ) );
146 mInvalidLabel->setWordWrap(
true );
147 QFont font = mInvalidLabel->font();
148 font.setItalic(
true );
149 mInvalidLabel->setStyleSheet( QStringLiteral(
"QLabel { color: red; } " ) );
150 mInvalidLabel->setFont( font );
151 mTopLayout->addWidget( mInvalidLabel );
155 mMapIdentificationButton->hide();
156 mHighlightFeatureButton->hide();
157 mAttributeEditorFrame->hide();
158 mInvalidLabel->hide();
159 mAddEntryButton->hide();
163 connect( mHighlightFeatureButton, &QToolButton::triggered,
this, &QgsRelationReferenceWidget::highlightActionTriggered );
166 connect( mAddEntryButton, &QAbstractButton::clicked,
this, &QgsRelationReferenceWidget::addEntry );
167 connect( mComboBox, &QComboBox::editTextChanged,
this, &QgsRelationReferenceWidget::updateAddEntryButton );
178 mAllowNull = allowNullValue;
179 mRemoveFKButton->setVisible( allowNullValue && mReadOnlySelector );
187 mInvalidLabel->hide();
195 mReferencedFields << fieldPair.referencedField();
202 mAttributeEditorFrame->setObjectName( QStringLiteral(
"referencing/" ) +
relation.
name() );
207 mAttributeEditorFrame->setTitle( mReferencedLayer->
name() );
209 mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
214 updateAddEntryButton();
218 mInvalidLabel->show();
221 if ( mShown && isVisible() )
234 mFilterContainer->setEnabled( editable );
235 mComboBox->setEnabled( editable );
236 mComboBox->setEditable(
true );
237 mMapIdentificationButton->setEnabled( editable );
238 mRemoveFKButton->setEnabled( editable );
239 mIsEditable = editable;
249 if ( values.isEmpty() )
259 if ( !mReferencedLayer )
262 if ( mReadOnlySelector )
268 const QList<QgsRelation::FieldPair> fieldPairs = mRelation.
fieldPairs();
269 int fieldCount = std::min( fieldPairs.count(), values.count() );
270 for (
int i = 0; i < fieldCount; i++ )
272 int idx = mReferencingLayer->
fields().
lookupField( fieldPairs.at( i ).referencingField() );
273 attrs[idx] = values.at( i );
285 mForeignKeys.clear();
286 for (
const QString &fieldName : qgis::as_const( mReferencedFields ) )
287 mForeignKeys << mFeature.
attribute( fieldName );
292 QString title = expr.
evaluate( &context ).toString();
295 QStringList titleFields;
296 for (
const QString &fieldName : qgis::as_const( mReferencedFields ) )
297 titleFields << mFeature.
attribute( fieldName ).toString();
298 title = titleFields.join( QStringLiteral(
" " ) );
300 mLineEdit->setText( title );
314 const int count = std::min( mFilterComboBoxes.size(), mFilterFields.size() );
315 for (
int i = 0; i < count; i++ )
317 QVariant v = mFeature.
attribute( mFilterFields[i] );
318 QString f = v.isNull() ? nullValue.toString() : v.toString();
319 mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
324 mRemoveFKButton->setEnabled( mIsEditable );
325 highlightFeature( mFeature );
326 updateAttributeEditorFrame( mFeature );
334 if ( mChainFilters && !mFilterComboBoxes.isEmpty() )
336 QComboBox *cb = mFilterComboBoxes.first();
337 cb->setCurrentIndex( 0 );
338 disableChainedComboBoxes( cb );
341 if ( mReadOnlySelector )
348 nullText = tr(
"%1 (no selection)" ).arg( nullValue );
350 mLineEdit->setText( nullText );
351 QVariantList nullAttributes;
352 for (
const QString &fieldName : qgis::as_const( mReferencedFields ) )
354 Q_UNUSED( fieldName );
355 nullAttributes << QVariant( QVariant::Int );
357 mForeignKeys = nullAttributes;
364 mRemoveFKButton->setEnabled(
false );
373 if ( mReferencedLayer )
376 if ( mReadOnlySelector )
391 if ( mReadOnlySelector )
399 mRemoveFKButton->setEnabled(
false );
406 if ( fkeys.isEmpty() )
407 return QVariant( QVariant::Int );
409 return fkeys.at( 0 );
414 if ( mReadOnlySelector )
426 mEditorContext = context;
428 mMessageBar = messageBar;
431 mMapToolIdentify->
setButton( mMapIdentificationButton );
436 mMapToolDigitize->
setButton( mAddEntryButton );
437 updateAddEntryButton();
445 setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::MinimumExpanding );
446 mTopLayout->setAlignment( Qt::AlignTop );
449 mAttributeEditorFrame->setVisible( display );
450 mEmbedForm = display;
455 mChooserContainer->setHidden( readOnly );
456 mLineEdit->setVisible( readOnly );
457 mRemoveFKButton->setVisible( mAllowNull && readOnly );
458 mReadOnlySelector = readOnly;
475 mFilterFields = filterFields;
500 if ( !mReadOnlySelector && mReferencedLayer )
502 QApplication::setOverrideCursor( Qt::WaitCursor );
504 QSet<QString> requestedAttrs;
506 if ( !mFilterFields.isEmpty() )
508 for (
const QString &fieldName : qgis::as_const( mFilterFields ) )
515 QComboBox *cb =
new QComboBox();
516 cb->setProperty(
"Field", fieldName );
518 mFilterComboBoxes << cb;
519 QVariantList uniqueValues = qgis::setToList( mReferencedLayer->
uniqueValues( idx ) );
522 cb->addItem( nullValue.toString(), QVariant( mReferencedLayer->
fields().
at( idx ).
type() ) );
525 const auto constUniqueValues = uniqueValues;
526 for (
const QVariant &v : constUniqueValues )
528 cb->addItem( v.toString(), v );
531 connect( cb,
static_cast<void ( QComboBox::* )(
int )
>( &QComboBox::currentIndexChanged ),
this, &QgsRelationReferenceWidget::filterChanged );
534 requestedAttrs << fieldName;
536 mFilterLayout->addWidget( cb );
547 const int count = std::min( mFilterComboBoxes.count(), mFilterFields.count() );
548 for (
int i = 0; i < count - 1; i++ )
550 QVariant cv = ft.
attribute( mFilterFields.at( i ) );
551 QVariant nv = ft.
attribute( mFilterFields.at( i + 1 ) );
552 QString cf = cv.isNull() ? nullValue.toString() : cv.toString();
553 QString nf = nv.isNull() ? nullValue.toString() : nv.toString();
554 mFilterCache[mFilterFields[i]][cf] << nf;
558 if ( !mFilterComboBoxes.isEmpty() )
560 QComboBox *cb = mFilterComboBoxes.first();
561 cb->setCurrentIndex( 0 );
562 disableChainedComboBoxes( cb );
568 mFilterContainer->hide();
578 if ( mChainFilters && mFeature.
isValid() )
580 for (
int i = 0; i < mFilterFields.size(); i++ )
582 QVariant v = mFeature.
attribute( mFilterFields[i] );
583 QString f = v.isNull() ? nullValue.toString() : v.toString();
584 mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
589 connect( mComboBox,
static_cast<void ( QComboBox::* )(
int )
>( &QComboBox::currentIndexChanged ),
this, &QgsRelationReferenceWidget::comboReferenceChanged );
591 QApplication::restoreOverrideCursor();
597 void QgsRelationReferenceWidget::highlightActionTriggered( QAction *action )
599 if ( action == mHighlightFeatureAction )
603 else if ( action == mScaleHighlightFeatureAction )
607 else if ( action == mPanHighlightFeatureAction )
622 attributeDialog.exec();
625 void QgsRelationReferenceWidget::highlightFeature(
QgsFeature f, CanvasExtent canvasExtent )
645 if ( canvasExtent ==
Scale )
658 else if ( canvasExtent ==
Pan )
668 mHighlight =
new QgsHighlight( mCanvas, f, mReferencedLayer );
672 QTimer *timer =
new QTimer(
this );
673 timer->setSingleShot(
true );
674 connect( timer, &QTimer::timeout,
this, &QgsRelationReferenceWidget::deleteHighlight );
675 timer->start( 3000 );
678 void QgsRelationReferenceWidget::deleteHighlight()
685 mHighlight =
nullptr;
690 if ( !mAllowMapIdentification || !mReferencedLayer )
699 mMapToolIdentify->
setLayer( mReferencedLayer );
700 setMapTool( mMapToolIdentify );
706 QString title = tr(
"Relation %1 for %2." ).arg( mRelation.
name(), mReferencingLayer->
name() );
707 QString msg = tr(
"Identify a feature of %1 to be associated. Press <ESC> to cancel." ).arg( mReferencedLayer->
name() );
709 mMessageBar->
pushItem( mMessageBarItem );
713 void QgsRelationReferenceWidget::comboReferenceChanged(
int index )
717 highlightFeature( mFeature );
718 updateAttributeEditorFrame( mFeature );
723 void QgsRelationReferenceWidget::updateAttributeEditorFrame(
const QgsFeature &feature )
725 mOpenFormButton->setEnabled( feature.
isValid() );
727 if ( mAttributeEditorFrame && mReferencedAttributeForm )
729 mReferencedAttributeForm->
setFeature( feature );
735 return mAllowAddFeatures;
741 updateAddEntryButton();
749 void QgsRelationReferenceWidget::featureIdentified(
const QgsFeature &feature )
751 if ( mReadOnlySelector )
755 context.setFeature( feature );
756 QString title = expr.evaluate( &context ).toString();
757 if ( expr.hasEvalError() )
759 QStringList titleFields;
760 for (
const QString &fieldName : qgis::as_const( mReferencedFields ) )
761 titleFields << mFeature.
attribute( fieldName ).toString();
762 title = titleFields.join( QStringLiteral(
" " ) );
764 mLineEdit->setText( title );
765 mForeignKeys.clear();
767 for (
const QString &fieldName : qgis::as_const( mReferencedFields ) )
768 mForeignKeys << mFeature.
attribute( fieldName );
776 mRemoveFKButton->setEnabled( mIsEditable );
777 highlightFeature( feature );
778 updateAttributeEditorFrame( feature );
784 void QgsRelationReferenceWidget::setMapTool(
QgsMapTool *mapTool )
786 mCurrentMapTool = mapTool;
789 mWindowWidget = window();
791 mCanvas->window()->raise();
792 mCanvas->activateWindow();
797 void QgsRelationReferenceWidget::unsetMapTool()
800 if ( mCurrentMapTool )
805 if ( mCurrentMapTool == mMapToolDigitize )
817 void QgsRelationReferenceWidget::onKeyPressed( QKeyEvent *e )
819 if ( e->key() == Qt::Key_Escape )
825 void QgsRelationReferenceWidget::mapToolDeactivated()
829 mWindowWidget->raise();
830 mWindowWidget->activateWindow();
833 if ( mMessageBar && mMessageBarItem )
835 mMessageBar->
popWidget( mMessageBarItem );
837 mMessageBarItem =
nullptr;
840 void QgsRelationReferenceWidget::filterChanged()
844 QMap<QString, QString> filters;
847 QComboBox *scb = qobject_cast<QComboBox *>( sender() );
853 QString filterExpression;
857 disableChainedComboBoxes( scb );
860 const auto constMFilterComboBoxes = mFilterComboBoxes;
861 for ( QComboBox *cb : constMFilterComboBoxes )
863 if ( cb->currentIndex() != 0 )
865 const QString fieldName = cb->property(
"Field" ).toString();
867 if ( cb->currentText() == nullValue.toString() )
869 filters[fieldName] = QStringLiteral(
"\"%1\" IS NULL" ).arg( fieldName );
881 QComboBox *ccb =
nullptr;
882 const auto constMFilterComboBoxes = mFilterComboBoxes;
883 for ( QComboBox *cb : constMFilterComboBoxes )
893 if ( ccb->currentIndex() != 0 )
895 const QString fieldName = cb->property(
"Field" ).toString();
897 cb->blockSignals(
true );
899 cb->addItem( cb->property(
"FieldAlias" ).toString() );
904 const auto txts { mFilterCache[ccb->property(
"Field" ).toString()][ccb->currentText()] };
905 for (
const QString &txt : txts )
907 QMap<QString, QString> filtersAttrs = filters;
909 QString expression = filtersAttrs.values().join( QStringLiteral(
" AND " ) );
917 while ( it.nextFeature( f ) )
919 if ( !featureIds.contains( f.
id() ) )
920 featureIds << f.
id();
931 cb->addItems( texts );
933 cb->setEnabled(
true );
934 cb->blockSignals(
false );
940 filterExpression = filters.values().join( QStringLiteral(
" AND " ) );
944 void QgsRelationReferenceWidget::addEntry()
946 if ( !mReferencedLayer )
963 mMapToolDigitize->
setLayer( mReferencedLayer );
964 setMapTool( mMapToolDigitize );
971 QString title = tr(
"Relation %1 for %2." ).arg( mRelation.
name(), mReferencingLayer->
name() );
974 QString msg = tr(
"Link feature to %1 \"%2\" : Digitize the geometry for the new feature on layer %3. Press <ESC> to cancel." )
975 .arg( mReferencingLayer->
name(), displayString, mReferencedLayer->
name() );
977 mMessageBar->
pushItem( mMessageBarItem );
982 void QgsRelationReferenceWidget::entryAdded(
const QgsFeature &feat )
988 if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
992 if ( fieldIdx != -1 )
994 attributes.insert( fieldIdx, mComboBox->currentText() );
1001 for (
const QString &fieldName : qgis::as_const( mReferencedFields ) )
1006 mAddEntryButton->setEnabled(
false );
1012 void QgsRelationReferenceWidget::updateAddEntryButton()
1014 mAddEntryButton->setVisible( mAllowAddFeatures && mMapToolDigitize );
1015 mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->
isEditable() );
1018 void QgsRelationReferenceWidget::disableChainedComboBoxes(
const QComboBox *scb )
1020 QComboBox *ccb =
nullptr;
1021 const auto constMFilterComboBoxes = mFilterComboBoxes;
1022 for ( QComboBox *cb : constMFilterComboBoxes )
1034 cb->setCurrentIndex( 0 );
1035 if ( ccb->currentIndex() == 0 )
1037 cb->setEnabled(
false );
1044 void QgsRelationReferenceWidget::emitForeignKeysChanged(
const QVariantList &foreignKeys,
bool force )
1046 if (
foreignKeys == mForeignKeys && force ==
false )
1058 return mReferencedLayerName;
1063 mReferencedLayerName = relationLayerName;
1068 return mReferencedLayerId;
1073 mReferencedLayerId = relationLayerId;
1078 return mReferencedLayerProviderKey;
1083 mReferencedLayerProviderKey = relationProviderKey;
1088 return mReferencedLayerDataSource;
1094 mReferencedLayerDataSource = resolver.writePath( relationDataSource );
1099 mFormFeature = formFeature;