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();
196 mReferencedFields << fieldPair.referencedField();
205 mAttributeEditorFrame->setObjectName( QStringLiteral(
"referencing/" ) +
relation.
name() );
210 mAttributeEditorFrame->setTitle( mReferencedLayer->
name() );
212 mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
217 updateAddEntryButton();
221 mInvalidLabel->show();
224 if ( mShown && isVisible() )
237 mFilterContainer->setEnabled( editable );
238 mComboBox->setEnabled( editable );
239 mComboBox->setEditable(
true );
240 mMapIdentificationButton->setEnabled( editable );
241 mRemoveFKButton->setEnabled( editable );
242 mIsEditable = editable;
252 if ( values.isEmpty() )
262 if ( !mReferencedLayer )
265 if ( mReadOnlySelector )
271 const QList<QgsRelation::FieldPair> fieldPairs = mRelation.
fieldPairs();
272 int fieldCount = std::min( fieldPairs.count(), values.count() );
273 for (
int i = 0; i < fieldCount; i++ )
275 int idx = mReferencingLayer->
fields().
lookupField( fieldPairs.at( i ).referencingField() );
276 attrs[idx] = values.at( i );
288 mForeignKeys.clear();
289 for (
const QString &fieldName : qgis::as_const( mReferencedFields ) )
290 mForeignKeys << mFeature.
attribute( fieldName );
295 QString title = expr.
evaluate( &context ).toString();
298 QStringList titleFields;
299 for (
const QString &fieldName : qgis::as_const( mReferencedFields ) )
300 titleFields << mFeature.
attribute( fieldName ).toString();
301 title = titleFields.join( QLatin1Char(
' ' ) );
303 mLineEdit->setText( title );
309 if ( mEmbedForm || mChainFilters )
317 const int count = std::min( mFilterComboBoxes.size(), mFilterFields.size() );
318 for (
int i = 0; i < count; i++ )
320 QVariant v = mFeature.
attribute( mFilterFields[i] );
321 QString f = v.isNull() ? nullValue.toString() : v.toString();
322 mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
327 mRemoveFKButton->setEnabled( mIsEditable );
328 highlightFeature( mFeature );
329 updateAttributeEditorFrame( mFeature );
337 if ( mChainFilters && !mFilterComboBoxes.isEmpty() )
339 QComboBox *cb = mFilterComboBoxes.first();
340 cb->setCurrentIndex( 0 );
341 disableChainedComboBoxes( cb );
344 if ( mReadOnlySelector )
351 nullText = tr(
"%1 (no selection)" ).arg( nullValue );
353 mLineEdit->setText( nullText );
354 QVariantList nullAttributes;
355 for (
const QString &fieldName : qgis::as_const( mReferencedFields ) )
357 Q_UNUSED( fieldName );
358 nullAttributes << QVariant( QVariant::Int );
360 mForeignKeys = nullAttributes;
367 mRemoveFKButton->setEnabled(
false );
376 if ( mReferencedLayer )
379 if ( mReadOnlySelector )
394 if ( mReadOnlySelector )
402 mRemoveFKButton->setEnabled(
false );
409 if ( fkeys.isEmpty() )
410 return QVariant( QVariant::Int );
412 return fkeys.at( 0 );
417 if ( mReadOnlySelector )
429 mEditorContext = context;
431 mMessageBar = messageBar;
434 mMapToolIdentify->
setButton( mMapIdentificationButton );
439 mMapToolDigitize->
setButton( mAddEntryButton );
440 updateAddEntryButton();
448 setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::MinimumExpanding );
449 mTopLayout->setAlignment( Qt::AlignTop );
452 mAttributeEditorFrame->setVisible( display );
453 mEmbedForm = display;
458 mChooserContainer->setHidden( readOnly );
459 mLineEdit->setVisible( readOnly );
460 mRemoveFKButton->setVisible( mAllowNull && readOnly );
461 mReadOnlySelector = readOnly;
478 mFilterFields = filterFields;
494 mFilterExpression = expression;
508 if ( !mReadOnlySelector && mReferencedLayer )
510 QApplication::setOverrideCursor( Qt::WaitCursor );
512 QSet<QString> requestedAttrs;
514 if ( !mFilterFields.isEmpty() )
516 for (
const QString &fieldName : qgis::as_const( mFilterFields ) )
523 QComboBox *cb =
new QComboBox();
524 cb->setProperty(
"Field", fieldName );
526 mFilterComboBoxes << cb;
527 QVariantList uniqueValues = qgis::setToList( mReferencedLayer->
uniqueValues( idx ) );
530 cb->addItem( nullValue.toString(), QVariant( mReferencedLayer->
fields().
at( idx ).
type() ) );
533 const auto constUniqueValues = uniqueValues;
534 for (
const QVariant &v : constUniqueValues )
536 cb->addItem( v.toString(), v );
539 connect( cb,
static_cast<void ( QComboBox::* )(
int )
>( &QComboBox::currentIndexChanged ),
this, &QgsRelationReferenceWidget::filterChanged );
542 requestedAttrs << fieldName;
544 mFilterLayout->addWidget( cb );
554 : mReferencedLayer->
getFeatures( mFilterExpression );
557 const int count = std::min( mFilterComboBoxes.count(), mFilterFields.count() );
558 for (
int i = 0; i < count - 1; i++ )
560 QVariant cv = ft.
attribute( mFilterFields.at( i ) );
561 QVariant nv = ft.
attribute( mFilterFields.at( i + 1 ) );
562 QString cf = cv.isNull() ? nullValue.toString() : cv.toString();
563 QString nf = nv.isNull() ? nullValue.toString() : nv.toString();
564 mFilterCache[mFilterFields[i]][cf] << nf;
568 if ( !mFilterComboBoxes.isEmpty() )
570 QComboBox *cb = mFilterComboBoxes.first();
571 cb->setCurrentIndex( 0 );
572 disableChainedComboBoxes( cb );
578 mFilterContainer->hide();
586 if ( ! mFilterExpression.isEmpty() )
591 if ( mChainFilters && mFeature.
isValid() )
593 for (
int i = 0; i < mFilterFields.size(); i++ )
595 QVariant v = mFeature.
attribute( mFilterFields[i] );
596 QString f = v.isNull() ? nullValue.toString() : v.toString();
597 mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
602 connect( mComboBox, qgis::overload<int>::of( &QComboBox::currentIndexChanged ),
this, &QgsRelationReferenceWidget::comboReferenceChanged );
604 QApplication::restoreOverrideCursor();
610 void QgsRelationReferenceWidget::highlightActionTriggered( QAction *action )
612 if ( action == mHighlightFeatureAction )
616 else if ( action == mScaleHighlightFeatureAction )
620 else if ( action == mPanHighlightFeatureAction )
635 attributeDialog.exec();
638 void QgsRelationReferenceWidget::highlightFeature(
QgsFeature f, CanvasExtent canvasExtent )
658 if ( canvasExtent ==
Scale )
671 else if ( canvasExtent ==
Pan )
681 mHighlight =
new QgsHighlight( mCanvas, f, mReferencedLayer );
685 QTimer *timer =
new QTimer(
this );
686 timer->setSingleShot(
true );
687 connect( timer, &QTimer::timeout,
this, &QgsRelationReferenceWidget::deleteHighlight );
688 timer->start( 3000 );
691 void QgsRelationReferenceWidget::deleteHighlight()
698 mHighlight =
nullptr;
703 if ( !mAllowMapIdentification || !mReferencedLayer )
712 mMapToolIdentify->
setLayer( mReferencedLayer );
713 setMapTool( mMapToolIdentify );
719 QString title = tr(
"Relation %1 for %2." ).arg( mRelation.
name(), mReferencingLayer->
name() );
720 QString msg = tr(
"Identify a feature of %1 to be associated. Press <ESC> to cancel." ).arg( mReferencedLayer->
name() );
722 mMessageBar->
pushItem( mMessageBarItem );
726 void QgsRelationReferenceWidget::comboReferenceChanged(
int index )
730 highlightFeature( mFeature );
731 updateAttributeEditorFrame( mFeature );
736 void QgsRelationReferenceWidget::updateAttributeEditorFrame(
const QgsFeature &feature )
738 mOpenFormButton->setEnabled( feature.
isValid() );
740 if ( mAttributeEditorFrame && mReferencedAttributeForm )
742 mReferencedAttributeForm->
setFeature( feature );
748 return mAllowAddFeatures;
754 updateAddEntryButton();
762 void QgsRelationReferenceWidget::featureIdentified(
const QgsFeature &feature )
764 if ( mReadOnlySelector )
768 context.setFeature( feature );
769 QString title = expr.evaluate( &context ).toString();
770 if ( expr.hasEvalError() )
772 QStringList titleFields;
773 for (
const QString &fieldName : qgis::as_const( mReferencedFields ) )
774 titleFields << mFeature.
attribute( fieldName ).toString();
775 title = titleFields.join( QLatin1Char(
' ' ) );
777 mLineEdit->setText( title );
778 mForeignKeys.clear();
780 for (
const QString &fieldName : qgis::as_const( mReferencedFields ) )
781 mForeignKeys << mFeature.
attribute( fieldName );
789 mRemoveFKButton->setEnabled( mIsEditable );
790 highlightFeature( feature );
791 updateAttributeEditorFrame( feature );
797 void QgsRelationReferenceWidget::setMapTool(
QgsMapTool *mapTool )
799 mCurrentMapTool = mapTool;
802 mWindowWidget = window();
804 mCanvas->window()->raise();
805 mCanvas->activateWindow();
810 void QgsRelationReferenceWidget::unsetMapTool()
813 if ( mCurrentMapTool )
818 if ( mCurrentMapTool == mMapToolDigitize )
830 void QgsRelationReferenceWidget::onKeyPressed( QKeyEvent *e )
832 if ( e->key() == Qt::Key_Escape )
838 void QgsRelationReferenceWidget::mapToolDeactivated()
842 mWindowWidget->raise();
843 mWindowWidget->activateWindow();
846 if ( mMessageBar && mMessageBarItem )
848 mMessageBar->
popWidget( mMessageBarItem );
850 mMessageBarItem =
nullptr;
853 void QgsRelationReferenceWidget::filterChanged()
857 QMap<QString, QString> filters;
860 QComboBox *scb = qobject_cast<QComboBox *>( sender() );
874 disableChainedComboBoxes( scb );
877 const auto constMFilterComboBoxes = mFilterComboBoxes;
878 for ( QComboBox *cb : constMFilterComboBoxes )
880 if ( cb->currentIndex() != 0 )
882 const QString fieldName = cb->property(
"Field" ).toString();
884 if ( cb->currentText() == nullValue.toString() )
886 filters[fieldName] = QStringLiteral(
"\"%1\" IS NULL" ).arg( fieldName );
898 QComboBox *ccb =
nullptr;
899 const auto constMFilterComboBoxes = mFilterComboBoxes;
900 for ( QComboBox *cb : constMFilterComboBoxes )
910 if ( ccb->currentIndex() != 0 )
912 const QString fieldName = cb->property(
"Field" ).toString();
914 cb->blockSignals(
true );
916 cb->addItem( cb->property(
"FieldAlias" ).toString() );
921 const auto txts { mFilterCache[ccb->property(
"Field" ).toString()][ccb->currentText()] };
922 for (
const QString &txt : txts )
924 QMap<QString, QString> filtersAttrs = filters;
930 expression += QLatin1String(
" AND " );
932 expression += filtersAttrs.isEmpty() ? QString() : QStringLiteral(
" ( " );
933 expression += filtersAttrs.values().join( QLatin1String(
" AND " ) );
934 expression += filtersAttrs.isEmpty() ? QString() : QStringLiteral(
" ) " );
941 while ( it.nextFeature( f ) )
943 if ( !featureIds.contains( f.
id() ) )
944 featureIds << f.
id();
955 cb->addItems( texts );
957 cb->setEnabled(
true );
958 cb->blockSignals(
false );
968 filterExpression += filters.isEmpty() ? QString() : QStringLiteral(
" ( " );
970 filterExpression += filters.isEmpty() ? QString() : QStringLiteral(
" ) " );
975 void QgsRelationReferenceWidget::addEntry()
977 if ( !mReferencedLayer )
994 mMapToolDigitize->
setLayer( mReferencedLayer );
995 setMapTool( mMapToolDigitize );
1002 QString title = tr(
"Relation %1 for %2." ).arg( mRelation.
name(), mReferencingLayer->
name() );
1005 QString msg = tr(
"Link feature to %1 \"%2\" : Digitize the geometry for the new feature on layer %3. Press <ESC> to cancel." )
1006 .arg( mReferencingLayer->
name(), displayString, mReferencedLayer->
name() );
1008 mMessageBar->
pushItem( mMessageBarItem );
1013 void QgsRelationReferenceWidget::entryAdded(
const QgsFeature &feat )
1019 if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
1023 if ( fieldIdx != -1 )
1025 attributes.insert( fieldIdx, mComboBox->currentText() );
1032 for (
const QString &fieldName : qgis::as_const( mReferencedFields ) )
1037 mAddEntryButton->setEnabled(
false );
1043 void QgsRelationReferenceWidget::updateAddEntryButton()
1045 mAddEntryButton->setVisible( mAllowAddFeatures && mMapToolDigitize );
1046 mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->
isEditable() );
1049 void QgsRelationReferenceWidget::disableChainedComboBoxes(
const QComboBox *scb )
1051 QComboBox *ccb =
nullptr;
1052 const auto constMFilterComboBoxes = mFilterComboBoxes;
1053 for ( QComboBox *cb : constMFilterComboBoxes )
1065 cb->setCurrentIndex( 0 );
1066 if ( ccb->currentIndex() == 0 )
1068 cb->setEnabled(
false );
1075 void QgsRelationReferenceWidget::emitForeignKeysChanged(
const QVariantList &foreignKeys,
bool force )
1089 return mReferencedLayerName;
1094 mReferencedLayerName = relationLayerName;
1099 return mReferencedLayerId;
1104 mReferencedLayerId = relationLayerId;
1109 return mReferencedLayerProviderKey;
1114 mReferencedLayerProviderKey = relationProviderKey;
1119 return mReferencedLayerDataSource;
1125 mReferencedLayerDataSource = resolver.writePath( relationDataSource );
1130 mFormFeature = formFeature;