1 /***************************************************************************
2     qgsrelationeditor.cpp
3      --------------------------------------
4     Date                 : 17.5.2013
5     Copyright            : (C) 2013 Matthias Kuhn
6     Email                : matthias 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 "qgsrelationeditorwidget.h"
17 
18 #include "qgsapplication.h"
19 #include "qgsdistancearea.h"
20 #include "qgsfeatureiterator.h"
21 #include "qgsvectordataprovider.h"
22 #include "qgsexpression.h"
23 #include "qgsfeature.h"
24 #include "qgsfeatureselectiondlg.h"
25 #include "qgsgenericfeatureselectionmanager.h"
26 #include "qgsrelation.h"
27 #include "qgsvectorlayertools.h"
28 #include "qgsproject.h"
29 #include "qgstransactiongroup.h"
30 #include "qgslogger.h"
31 #include "qgsvectorlayerutils.h"
32 #include "qgsmapcanvas.h"
33 #include "qgsvectorlayerselectionmanager.h"
34 #include "qgsmaptooldigitizefeature.h"
35 #include "qgsexpressioncontextutils.h"
36 #include "qgsmessagebar.h"
37 #include "qgsmessagebaritem.h"
38 
39 #include <QHBoxLayout>
40 #include <QLabel>
41 #include <QMessageBox>
42 #include <QPushButton>
43 
44 /// @cond PRIVATE
45 ///
QgsFilteredSelectionManager(QgsVectorLayer * layer,const QgsFeatureRequest & request,QObject * parent)46 QgsFilteredSelectionManager::QgsFilteredSelectionManager( QgsVectorLayer *layer, const QgsFeatureRequest &request, QObject *parent )
47   : QgsVectorLayerSelectionManager( layer, parent )
48   , mRequest( request )
49 {
50   if ( ! layer )
51     return;
52 
53   for ( auto fid : layer->selectedFeatureIds() )
54     if ( mRequest.acceptFeature( layer->getFeature( fid ) ) )
55       mSelectedFeatureIds << fid;
56 
57   connect( layer, &QgsVectorLayer::selectionChanged, this, &QgsFilteredSelectionManager::onSelectionChanged );
58 }
59 
selectedFeatureIds() const60 const QgsFeatureIds &QgsFilteredSelectionManager::selectedFeatureIds() const
61 {
62   return mSelectedFeatureIds;
63 }
64 
selectedFeatureCount()65 int QgsFilteredSelectionManager::selectedFeatureCount()
66 {
67   return mSelectedFeatureIds.count();
68 }
69 
onSelectionChanged(const QgsFeatureIds & selected,const QgsFeatureIds & deselected,bool clearAndSelect)70 void QgsFilteredSelectionManager::onSelectionChanged( const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect )
71 {
72   QgsFeatureIds lselected = selected;
73   if ( clearAndSelect )
74   {
75     mSelectedFeatureIds.clear();
76   }
77   else
78   {
79     for ( auto fid : deselected )
80       mSelectedFeatureIds.remove( fid );
81   }
82 
83   for ( auto fid : selected )
84     if ( mRequest.acceptFeature( layer()->getFeature( fid ) ) )
85       mSelectedFeatureIds << fid;
86     else
87       lselected.remove( fid );
88 
89   emit selectionChanged( lselected, deselected, clearAndSelect );
90 }
91 
92 /// @endcond
93 
QgsRelationEditorWidget(QWidget * parent)94 QgsRelationEditorWidget::QgsRelationEditorWidget( QWidget *parent )
95   : QgsCollapsibleGroupBox( parent )
96 {
97   QVBoxLayout *topLayout = new QVBoxLayout( this );
98   topLayout->setContentsMargins( 0, 9, 0, 0 );
99   setLayout( topLayout );
100 
101   // buttons
102   QHBoxLayout *buttonLayout = new QHBoxLayout();
103   buttonLayout->setContentsMargins( 0, 0, 0, 0 );
104   // toggle editing
105   mToggleEditingButton = new QToolButton( this );
106   mToggleEditingButton->setObjectName( QStringLiteral( "mToggleEditingButton" ) );
107   mToggleEditingButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) );
108   mToggleEditingButton->setText( tr( "Toggle Editing" ) );
109   mToggleEditingButton->setEnabled( false );
110   mToggleEditingButton->setCheckable( true );
111   mToggleEditingButton->setToolTip( tr( "Toggle editing mode for child layer" ) );
112   buttonLayout->addWidget( mToggleEditingButton );
113   // save Edits
114   mSaveEditsButton = new QToolButton( this );
115   mSaveEditsButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveEdits.svg" ) ) );
116   mSaveEditsButton->setText( tr( "Save Child Layer Edits" ) );
117   mSaveEditsButton->setToolTip( tr( "Save child layer edits" ) );
118   mSaveEditsButton->setEnabled( true );
119   buttonLayout->addWidget( mSaveEditsButton );
120   // add feature with geometry
121   mAddFeatureGeometryButton = new QToolButton( this );
122   mAddFeatureGeometryButton->setObjectName( QStringLiteral( "mAddFeatureGeometryButton" ) );
123   buttonLayout->addWidget( mAddFeatureGeometryButton );
124   // add feature
125   mAddFeatureButton = new QToolButton( this );
126   mAddFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewTableRow.svg" ) ) );
127   mAddFeatureButton->setText( tr( "Add Child Feature" ) );
128   mAddFeatureButton->setToolTip( tr( "Add child feature" ) );
129   mAddFeatureButton->setObjectName( QStringLiteral( "mAddFeatureButton" ) );
130   buttonLayout->addWidget( mAddFeatureButton );
131   // duplicate feature
132   mDuplicateFeatureButton = new QToolButton( this );
133   mDuplicateFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDuplicateFeature.svg" ) ) );
134   mDuplicateFeatureButton->setText( tr( "Duplicate Child Feature" ) );
135   mDuplicateFeatureButton->setToolTip( tr( "Duplicate selected child feature" ) );
136   mDuplicateFeatureButton->setObjectName( QStringLiteral( "mDuplicateFeatureButton" ) );
137   buttonLayout->addWidget( mDuplicateFeatureButton );
138   // delete feature
139   mDeleteFeatureButton = new QToolButton( this );
140   mDeleteFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteSelected.svg" ) ) );
141   mDeleteFeatureButton->setText( tr( "Delete Child Feature" ) );
142   mDeleteFeatureButton->setToolTip( tr( "Delete selected child feature" ) );
143   mDeleteFeatureButton->setObjectName( QStringLiteral( "mDeleteFeatureButton" ) );
144   buttonLayout->addWidget( mDeleteFeatureButton );
145   // link feature
146   mLinkFeatureButton = new QToolButton( this );
147   mLinkFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionLink.svg" ) ) );
148   mLinkFeatureButton->setText( tr( "Link Existing Features" ) );
149   mLinkFeatureButton->setToolTip( tr( "Link existing child features" ) );
150   mLinkFeatureButton->setObjectName( QStringLiteral( "mLinkFeatureButton" ) );
151   buttonLayout->addWidget( mLinkFeatureButton );
152   // unlink feature
153   mUnlinkFeatureButton = new QToolButton( this );
154   mUnlinkFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionUnlink.svg" ) ) );
155   mUnlinkFeatureButton->setText( tr( "Unlink Feature" ) );
156   mUnlinkFeatureButton->setToolTip( tr( "Unlink selected child feature" ) );
157   mUnlinkFeatureButton->setObjectName( QStringLiteral( "mUnlinkFeatureButton" ) );
158   buttonLayout->addWidget( mUnlinkFeatureButton );
159   // zoom to linked feature
160   mZoomToFeatureButton = new QToolButton( this );
161   mZoomToFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomToSelected.svg" ) ) );
162   mZoomToFeatureButton->setText( tr( "Zoom To Feature" ) );
163   mZoomToFeatureButton->setToolTip( tr( "Zoom to selected child feature" ) );
164   mZoomToFeatureButton->setObjectName( QStringLiteral( "mZoomToFeatureButton" ) );
165   buttonLayout->addWidget( mZoomToFeatureButton );
166   // spacer
167   buttonLayout->addItem( new QSpacerItem( 0, 0, QSizePolicy::Expanding ) );
168   // form view
169   mFormViewButton = new QToolButton( this );
170   mFormViewButton->setText( tr( "Form View" ) );
171   mFormViewButton->setToolTip( tr( "Switch to form view" ) );
172   mFormViewButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPropertyItem.svg" ) ) );
173   mFormViewButton->setCheckable( true );
174   mFormViewButton->setChecked( mViewMode == QgsDualView::AttributeEditor );
175   buttonLayout->addWidget( mFormViewButton );
176   // table view
177   mTableViewButton = new QToolButton( this );
178   mTableViewButton->setText( tr( "Table View" ) );
179   mTableViewButton->setToolTip( tr( "Switch to table view" ) );
180   mTableViewButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOpenTable.svg" ) ) );
181   mTableViewButton->setCheckable( true );
182   mTableViewButton->setChecked( mViewMode == QgsDualView::AttributeTable );
183   buttonLayout->addWidget( mTableViewButton );
184   // button group
185   mViewModeButtonGroup = new QButtonGroup( this );
186   mViewModeButtonGroup->addButton( mFormViewButton, QgsDualView::AttributeEditor );
187   mViewModeButtonGroup->addButton( mTableViewButton, QgsDualView::AttributeTable );
188 
189   // add buttons layout
190   topLayout->addLayout( buttonLayout );
191 
192   mRelationLayout = new QGridLayout();
193   mRelationLayout->setContentsMargins( 0, 0, 0, 0 );
194   topLayout->addLayout( mRelationLayout );
195 
196   mDualView = new QgsDualView( this );
197   mDualView->setView( mViewMode );
198 
199   mRelationLayout->addWidget( mDualView );
200 
201   connect( this, &QgsCollapsibleGroupBoxBasic::collapsedStateChanged, this, &QgsRelationEditorWidget::onCollapsedStateChanged );
202   connect( mViewModeButtonGroup, static_cast<void ( QButtonGroup::* )( int )>( &QButtonGroup::buttonClicked ),
203            this, static_cast<void ( QgsRelationEditorWidget::* )( int )>( &QgsRelationEditorWidget::setViewMode ) );
204   connect( mToggleEditingButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::toggleEditing );
205   connect( mSaveEditsButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::saveEdits );
206   connect( mAddFeatureButton, &QAbstractButton::clicked, this, [this]() { addFeature(); } );
207   connect( mAddFeatureGeometryButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::addFeatureGeometry );
208   connect( mDuplicateFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::duplicateFeature );
209   connect( mDeleteFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::deleteSelectedFeatures );
210   connect( mLinkFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::linkFeature );
211   connect( mUnlinkFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::unlinkSelectedFeatures );
212   connect( mZoomToFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::zoomToSelectedFeatures );
213 
214   connect( mDualView, &QgsDualView::showContextMenuExternally, this, &QgsRelationEditorWidget::showContextMenu );
215 
216   // Set initial state for add/remove etc. buttons
217   updateButtons();
218 }
219 
setRelationFeature(const QgsRelation & relation,const QgsFeature & feature)220 void QgsRelationEditorWidget::setRelationFeature( const QgsRelation &relation, const QgsFeature &feature )
221 {
222   if ( mRelation.isValid() )
223   {
224     disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
225     disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
226   }
227 
228   mRelation = relation;
229   mFeature = feature;
230 
231   connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
232   connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
233 
234   updateTitle();
235 
236   updateButtons();
237 
238   setObjectName( QStringLiteral( "referenced/" ) + mRelation.name() );
239 
240   // If not yet initialized, it is not (yet) visible, so we don't load it to be faster (lazy loading)
241   // If it is already initialized, it has been set visible before and the currently shown feature is changing
242   // and the widget needs updating
243 
244   if ( mVisible )
245   {
246     QgsFeatureRequest myRequest = mRelation.getRelatedFeaturesRequest( mFeature );
247     initDualView( mRelation.referencingLayer(), myRequest );
248   }
249 }
250 
initDualView(QgsVectorLayer * layer,const QgsFeatureRequest & request)251 void QgsRelationEditorWidget::initDualView( QgsVectorLayer *layer, const QgsFeatureRequest &request )
252 {
253   QgsAttributeEditorContext ctx { mEditorContext };
254   ctx.setParentFormFeature( mFeature );
255   mDualView->init( layer, mEditorContext.mapCanvas(), request, ctx );
256   mFeatureSelectionMgr = new QgsFilteredSelectionManager( layer, request, mDualView );
257   mDualView->setFeatureSelectionManager( mFeatureSelectionMgr );
258 
259   connect( mFeatureSelectionMgr, &QgsIFeatureSelectionManager::selectionChanged, this, &QgsRelationEditorWidget::updateButtons );
260 
261   QIcon icon;
262   QString text;
263   if ( layer->geometryType() == QgsWkbTypes::PointGeometry )
264   {
265     icon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionCapturePoint.svg" ) );
266     text = tr( "Add Point child Feature" );
267   }
268   else if ( layer->geometryType() == QgsWkbTypes::LineGeometry )
269   {
270     icon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionCaptureLine.svg" ) );
271     text = tr( "Add Line child Feature" );
272   }
273   else if ( layer->geometryType() == QgsWkbTypes::PolygonGeometry )
274   {
275     icon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionCapturePolygon.svg" ) );
276     text = tr( "Add Polygon Feature" );
277   }
278 
279   mAddFeatureGeometryButton->setIcon( icon );
280   mAddFeatureGeometryButton->setText( text );
281   mAddFeatureGeometryButton->setToolTip( text );
282 
283   updateButtons();
284 }
285 
setRelations(const QgsRelation & relation,const QgsRelation & nmrelation)286 void QgsRelationEditorWidget::setRelations( const QgsRelation &relation, const QgsRelation &nmrelation )
287 {
288   if ( mRelation.isValid() )
289   {
290     disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
291     disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
292   }
293 
294   if ( mNmRelation.isValid() )
295   {
296     disconnect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
297     disconnect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
298   }
299 
300   mRelation = relation;
301   mNmRelation = nmrelation;
302 
303   if ( !mRelation.isValid() )
304     return;
305 
306   mLayerInSameTransactionGroup = false;
307 
308   const auto transactionGroups = QgsProject::instance()->transactionGroups();
309   for ( auto it = transactionGroups.constBegin(); it != transactionGroups.constEnd(); ++it )
310   {
311     if ( mNmRelation.isValid() )
312     {
313       if ( it.value()->layers().contains( mRelation.referencedLayer() ) &&
314            it.value()->layers().contains( mRelation.referencingLayer() ) &&
315            it.value()->layers().contains( mNmRelation.referencedLayer() ) )
316         mLayerInSameTransactionGroup = true;
317     }
318     else
319     {
320       if ( it.value()->layers().contains( mRelation.referencedLayer() ) &&
321            it.value()->layers().contains( mRelation.referencingLayer() ) )
322         mLayerInSameTransactionGroup = true;
323     }
324   }
325 
326   connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
327   connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
328 
329   if ( mNmRelation.isValid() )
330   {
331     connect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
332     connect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
333   }
334 
335   updateTitle();
336 
337   updateButtons();
338 
339   setObjectName( QStringLiteral( "referenced/" ) + mRelation.name() );
340 
341   updateUi();
342 }
343 
setEditorContext(const QgsAttributeEditorContext & context)344 void QgsRelationEditorWidget::setEditorContext( const QgsAttributeEditorContext &context )
345 {
346   mEditorContext = context;
347 
348   if ( context.mapCanvas() && context.cadDockWidget() )
349   {
350     mMapToolDigitize.reset( new QgsMapToolDigitizeFeature( context.mapCanvas(), context.cadDockWidget() ) );
351     mMapToolDigitize->setButton( mAddFeatureGeometryButton );
352   }
353 
354   updateButtons();
355 }
356 
editorContext() const357 QgsAttributeEditorContext QgsRelationEditorWidget::editorContext() const
358 {
359   return mEditorContext;
360 }
361 
featureSelectionManager()362 QgsIFeatureSelectionManager *QgsRelationEditorWidget::featureSelectionManager()
363 {
364   return mFeatureSelectionMgr;
365 }
366 
setViewMode(QgsDualView::ViewMode mode)367 void QgsRelationEditorWidget::setViewMode( QgsDualView::ViewMode mode )
368 {
369   mDualView->setView( mode );
370   mViewMode = mode;
371 }
372 
setFeature(const QgsFeature & feature,bool update)373 void QgsRelationEditorWidget::setFeature( const QgsFeature &feature, bool update )
374 {
375   mFeature = feature;
376 
377   mEditorContext.setFormFeature( feature );
378 
379   if ( update )
380     updateUi();
381 }
382 
updateButtons()383 void QgsRelationEditorWidget::updateButtons()
384 {
385   bool toggleEditingButtonEnabled = false;
386   bool editable = false;
387   bool linkable = false;
388   bool spatial = false;
389   bool selectionNotEmpty = mFeatureSelectionMgr ? mFeatureSelectionMgr->selectedFeatureCount() : false;
390 
391   if ( mRelation.isValid() )
392   {
393     bool canSupportEditing = mRelation.referencingLayer()->dataProvider()->capabilities() & QgsVectorDataProvider::EditingCapabilities;
394     canSupportEditing &= !mRelation.referencingLayer()->readOnly();
395     toggleEditingButtonEnabled = canSupportEditing;
396     editable = mRelation.referencingLayer()->isEditable();
397     linkable = mRelation.referencingLayer()->isEditable();
398     spatial = mRelation.referencingLayer()->isSpatial();
399   }
400 
401   if ( mNmRelation.isValid() )
402   {
403     bool canSupportEditing = mNmRelation.referencedLayer()->dataProvider()->capabilities() & QgsVectorDataProvider::EditingCapabilities;
404     canSupportEditing &= !mNmRelation.referencedLayer()->readOnly();
405     toggleEditingButtonEnabled |= canSupportEditing;
406     editable = mNmRelation.referencedLayer()->isEditable();
407     spatial = mNmRelation.referencedLayer()->isSpatial();
408   }
409 
410   mToggleEditingButton->setEnabled( toggleEditingButtonEnabled );
411   mAddFeatureButton->setEnabled( editable );
412   mAddFeatureGeometryButton->setEnabled( editable );
413   mDuplicateFeatureButton->setEnabled( editable && selectionNotEmpty );
414   mLinkFeatureButton->setEnabled( linkable );
415   mDeleteFeatureButton->setEnabled( editable && selectionNotEmpty );
416   mUnlinkFeatureButton->setEnabled( linkable && selectionNotEmpty );
417   mZoomToFeatureButton->setEnabled( selectionNotEmpty );
418   mToggleEditingButton->setChecked( editable );
419   mSaveEditsButton->setEnabled( editable || linkable );
420 
421   mToggleEditingButton->setVisible( !mLayerInSameTransactionGroup );
422   mLinkFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsAttributeEditorRelation::Button::Link ) );
423   mUnlinkFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsAttributeEditorRelation::Button::Unlink ) );
424   mSaveEditsButton->setVisible( mButtonsVisibility.testFlag( QgsAttributeEditorRelation::Button::SaveChildEdits ) && !mLayerInSameTransactionGroup );
425   mAddFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsAttributeEditorRelation::Button::AddChildFeature ) );
426   mAddFeatureGeometryButton->setVisible( mButtonsVisibility.testFlag( QgsAttributeEditorRelation::Button::AddChildFeature ) && mEditorContext.mapCanvas() && mEditorContext.cadDockWidget() && spatial );
427   mDuplicateFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsAttributeEditorRelation::Button::DuplicateChildFeature ) );
428   mDeleteFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsAttributeEditorRelation::Button::DeleteChildFeature ) );
429   mZoomToFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsAttributeEditorRelation::Button::ZoomToChildFeature ) && mEditorContext.mapCanvas() && spatial );
430 }
431 
addFeatureGeometry()432 void QgsRelationEditorWidget::addFeatureGeometry()
433 {
434   QgsVectorLayer *layer = nullptr;
435   if ( mNmRelation.isValid() )
436     layer = mNmRelation.referencedLayer();
437   else
438     layer = mRelation.referencingLayer();
439 
440   mMapToolDigitize->setLayer( layer );
441 
442   // window is always on top, so we hide it to digitize without seeing it
443   window()->setVisible( false );
444   setMapTool( mMapToolDigitize );
445 
446   connect( mMapToolDigitize, &QgsMapToolDigitizeFeature::digitizingCompleted, this, &QgsRelationEditorWidget::onDigitizingCompleted );
447   connect( mEditorContext.mapCanvas(), &QgsMapCanvas::keyPressed, this, &QgsRelationEditorWidget::onKeyPressed );
448 
449   if ( auto *lMainMessageBar = mEditorContext.mainMessageBar() )
450   {
451     QString displayString = QgsVectorLayerUtils::getFeatureDisplayString( layer, mFeature );
452 
453     QString title = tr( "Create child feature for parent %1 \"%2\"" ).arg( mRelation.referencedLayer()->name(), displayString );
454     QString msg = tr( "Digitize the geometry for the new feature on layer %1. Press &lt;ESC&gt; to cancel." )
455                   .arg( layer->name() );
456     mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
457     lMainMessageBar->pushItem( mMessageBarItem );
458   }
459 
460 }
461 
addFeature(const QgsGeometry & geometry)462 void QgsRelationEditorWidget::addFeature( const QgsGeometry &geometry )
463 {
464   QgsAttributeMap keyAttrs;
465 
466   const QgsVectorLayerTools *vlTools = mEditorContext.vectorLayerTools();
467 
468   if ( mNmRelation.isValid() )
469   {
470     // n:m Relation: first let the user create a new feature on the other table
471     // and autocreate a new linking feature.
472     QgsFeature f;
473     if ( vlTools->addFeature( mNmRelation.referencedLayer(), QgsAttributeMap(), geometry, &f ) )
474     {
475       // Fields of the linking table
476       const QgsFields fields = mRelation.referencingLayer()->fields();
477 
478       // Expression context for the linking table
479       QgsExpressionContext context = mRelation.referencingLayer()->createExpressionContext();
480 
481       QgsAttributeMap linkAttributes;
482       const auto constFieldPairs = mRelation.fieldPairs();
483       for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs )
484       {
485         int index = fields.indexOf( fieldPair.first );
486         linkAttributes.insert( index,  mFeature.attribute( fieldPair.second ) );
487       }
488 
489       const auto constNmFieldPairs = mNmRelation.fieldPairs();
490       for ( const QgsRelation::FieldPair &fieldPair : constNmFieldPairs )
491       {
492         int index = fields.indexOf( fieldPair.first );
493         linkAttributes.insert( index, f.attribute( fieldPair.second ) );
494       }
495       QgsFeature linkFeature = QgsVectorLayerUtils::createFeature( mRelation.referencingLayer(), QgsGeometry(), linkAttributes, &context );
496 
497       mRelation.referencingLayer()->addFeature( linkFeature );
498 
499       updateUi();
500     }
501   }
502   else
503   {
504     QgsFields fields = mRelation.referencingLayer()->fields();
505 
506     const auto constFieldPairs = mRelation.fieldPairs();
507     for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs )
508     {
509       keyAttrs.insert( fields.indexFromName( fieldPair.referencingField() ), mFeature.attribute( fieldPair.referencedField() ) );
510     }
511 
512     vlTools->addFeature( mDualView->masterModel()->layer(), keyAttrs, geometry );
513   }
514 }
515 
onDigitizingCompleted(const QgsFeature & feature)516 void QgsRelationEditorWidget::onDigitizingCompleted( const QgsFeature &feature )
517 {
518   addFeature( feature.geometry() );
519 
520   unsetMapTool();
521 }
522 
linkFeature()523 void QgsRelationEditorWidget::linkFeature()
524 {
525   QgsVectorLayer *layer = nullptr;
526 
527   if ( mNmRelation.isValid() )
528     layer = mNmRelation.referencedLayer();
529   else
530     layer = mRelation.referencingLayer();
531 
532   QgsFeatureSelectionDlg *selectionDlg = new QgsFeatureSelectionDlg( layer, mEditorContext, this );
533   selectionDlg->setAttribute( Qt::WA_DeleteOnClose );
534 
535   const QString displayString = QgsVectorLayerUtils::getFeatureDisplayString( mRelation.referencedLayer(), mFeature );
536   selectionDlg->setWindowTitle( tr( "Link existing child features for parent %1 \"%2\"" ).arg( mRelation.referencedLayer()->name(), displayString ) );
537 
538   connect( selectionDlg, &QDialog::accepted, this, &QgsRelationEditorWidget::onLinkFeatureDlgAccepted );
539   selectionDlg->show();
540 }
541 
onLinkFeatureDlgAccepted()542 void QgsRelationEditorWidget::onLinkFeatureDlgAccepted()
543 {
544   QgsFeatureSelectionDlg *selectionDlg = qobject_cast<QgsFeatureSelectionDlg *>( sender() );
545   if ( mNmRelation.isValid() )
546   {
547     QgsFeatureIterator it = mNmRelation.referencedLayer()->getFeatures(
548                               QgsFeatureRequest()
549                               .setFilterFids( selectionDlg->selectedFeatures() )
550                               .setSubsetOfAttributes( mNmRelation.referencedFields() ) );
551 
552     QgsFeature relatedFeature;
553 
554     QgsFeatureList newFeatures;
555 
556     // Fields of the linking table
557     const QgsFields fields = mRelation.referencingLayer()->fields();
558 
559     // Expression context for the linking table
560     QgsExpressionContext context = mRelation.referencingLayer()->createExpressionContext();
561 
562     QgsAttributeMap linkAttributes;
563     const auto constFieldPairs = mRelation.fieldPairs();
564     for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs )
565     {
566       int index = fields.indexOf( fieldPair.first );
567       linkAttributes.insert( index,  mFeature.attribute( fieldPair.second ) );
568     }
569 
570     while ( it.nextFeature( relatedFeature ) )
571     {
572       const auto constFieldPairs = mNmRelation.fieldPairs();
573       for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs )
574       {
575         int index = fields.indexOf( fieldPair.first );
576         linkAttributes.insert( index, relatedFeature.attribute( fieldPair.second ) );
577       }
578       const QgsFeature linkFeature = QgsVectorLayerUtils::createFeature( mRelation.referencingLayer(), QgsGeometry(), linkAttributes, &context );
579 
580       newFeatures << linkFeature;
581     }
582 
583     mRelation.referencingLayer()->addFeatures( newFeatures );
584     QgsFeatureIds ids;
585     const auto constNewFeatures = newFeatures;
586     for ( const QgsFeature &f : constNewFeatures )
587       ids << f.id();
588     mRelation.referencingLayer()->selectByIds( ids );
589   }
590   else
591   {
592     QMap<int, QVariant> keys;
593     const auto constFieldPairs = mRelation.fieldPairs();
594     for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs )
595     {
596       int idx = mRelation.referencingLayer()->fields().lookupField( fieldPair.referencingField() );
597       QVariant val = mFeature.attribute( fieldPair.referencedField() );
598       keys.insert( idx, val );
599     }
600 
601     const auto constSelectedFeatures = selectionDlg->selectedFeatures();
602     for ( QgsFeatureId fid : constSelectedFeatures )
603     {
604       QMapIterator<int, QVariant> it( keys );
605       while ( it.hasNext() )
606       {
607         it.next();
608         mRelation.referencingLayer()->changeAttributeValue( fid, it.key(), it.value() );
609       }
610     }
611   }
612 
613   updateUi();
614 }
615 
duplicateFeature()616 void QgsRelationEditorWidget::duplicateFeature()
617 {
618   QgsVectorLayer *layer = mRelation.referencingLayer();
619 
620   QgsFeatureIterator fit = layer->getFeatures( QgsFeatureRequest().setFilterFids( mFeatureSelectionMgr->selectedFeatureIds() ) );
621   QgsFeature f;
622   while ( fit.nextFeature( f ) )
623   {
624     QgsVectorLayerUtils::QgsDuplicateFeatureContext duplicatedFeatureContext;
625     QgsVectorLayerUtils::duplicateFeature( layer, f, QgsProject::instance(), duplicatedFeatureContext );
626   }
627 }
628 
deleteFeature(const QgsFeatureId featureid)629 void QgsRelationEditorWidget::deleteFeature( const QgsFeatureId featureid )
630 {
631   deleteFeatures( QgsFeatureIds() << featureid );
632 }
633 
deleteSelectedFeatures()634 void QgsRelationEditorWidget::deleteSelectedFeatures()
635 {
636   QgsFeatureIds selectedFids = mFeatureSelectionMgr->selectedFeatureIds();
637   deleteFeatures( selectedFids );
638 }
639 
deleteFeatures(const QgsFeatureIds & featureids)640 void QgsRelationEditorWidget::deleteFeatures( const QgsFeatureIds &featureids )
641 {
642   bool deleteFeatures = true;
643 
644   QgsVectorLayer *layer;
645   if ( mNmRelation.isValid() )
646   {
647     layer = mNmRelation.referencedLayer();
648 
649     // When deleting a linked feature within an N:M relation,
650     // check if the feature is linked to more than just one feature.
651     // In case it is linked more than just once, ask the user for confirmation
652     // as it is likely he was not aware of the implications and might delete
653     // there may be several linking entries deleted along.
654 
655     QgsFeatureRequest deletedFeaturesRequest;
656     deletedFeaturesRequest.setFilterFids( featureids );
657     deletedFeaturesRequest.setFlags( QgsFeatureRequest::NoGeometry );
658     deletedFeaturesRequest.setSubsetOfAttributes( QgsAttributeList() << mNmRelation.referencedFields().first() );
659 
660     QgsFeatureIterator deletedFeatures = layer->getFeatures( deletedFeaturesRequest );
661     QStringList deletedFeaturesPks;
662     QgsFeature feature;
663     while ( deletedFeatures.nextFeature( feature ) )
664     {
665       deletedFeaturesPks.append( QgsExpression::quotedValue( feature.attribute( mNmRelation.referencedFields().first() ) ) );
666     }
667 
668     QgsFeatureRequest linkingFeaturesRequest;
669     linkingFeaturesRequest.setFlags( QgsFeatureRequest::NoGeometry );
670     linkingFeaturesRequest.setNoAttributes();
671 
672     QString linkingFeaturesRequestExpression;
673     if ( !deletedFeaturesPks.empty() )
674     {
675       linkingFeaturesRequestExpression = QStringLiteral( "%1 IN (%2)" ).arg( QgsExpression::quotedColumnRef( mNmRelation.fieldPairs().first().first ), deletedFeaturesPks.join( ',' ) );
676       linkingFeaturesRequest.setFilterExpression( linkingFeaturesRequestExpression );
677 
678       QgsFeatureIterator relatedLinkingFeatures = mNmRelation.referencingLayer()->getFeatures( linkingFeaturesRequest );
679 
680       int relatedLinkingFeaturesCount = 0;
681       while ( relatedLinkingFeatures.nextFeature( feature ) )
682       {
683         relatedLinkingFeaturesCount++;
684       }
685 
686       if ( deletedFeaturesPks.size() == 1 && relatedLinkingFeaturesCount > 1 )
687       {
688         QMessageBox messageBox( QMessageBox::Question, tr( "Really delete entry?" ), tr( "The entry on %1 is still linked to %2 features on %3. Do you want to delete it?" ).arg( mNmRelation.referencedLayer()->name(), QString::number( relatedLinkingFeaturesCount ), mRelation.referencedLayer()->name() ), QMessageBox::NoButton, this );
689         messageBox.addButton( QMessageBox::Cancel );
690         QAbstractButton *deleteButton = messageBox.addButton( tr( "Delete" ),  QMessageBox::AcceptRole );
691 
692         messageBox.exec();
693         if ( messageBox.clickedButton() != deleteButton )
694           deleteFeatures = false;
695       }
696       else if ( deletedFeaturesPks.size() > 1 && relatedLinkingFeaturesCount > deletedFeaturesPks.size() )
697       {
698         QMessageBox messageBox( QMessageBox::Question, tr( "Really delete entries?" ), tr( "The %1 entries on %2 are still linked to %3 features on %4. Do you want to delete them?" ).arg( QString::number( deletedFeaturesPks.size() ), mNmRelation.referencedLayer()->name(), QString::number( relatedLinkingFeaturesCount ), mRelation.referencedLayer()->name() ), QMessageBox::NoButton, this );
699         messageBox.addButton( QMessageBox::Cancel );
700         QAbstractButton *deleteButton = messageBox.addButton( tr( "Delete" ), QMessageBox::AcceptRole );
701 
702         messageBox.exec();
703         if ( messageBox.clickedButton() != deleteButton )
704           deleteFeatures = false;
705       }
706     }
707   }
708   else
709   {
710     layer = mRelation.referencingLayer();
711   }
712 
713   QgsVectorLayerUtils::QgsDuplicateFeatureContext infoContext;
714   if ( QgsVectorLayerUtils::impactsCascadeFeatures( layer, featureids, QgsProject::instance(), infoContext ) )
715   {
716     QString childrenInfo;
717     int childrenCount = 0;
718     const auto infoContextLayers = infoContext.layers();
719     for ( QgsVectorLayer *chl : infoContextLayers )
720     {
721       childrenCount += infoContext.duplicatedFeatures( chl ).size();
722       childrenInfo += ( tr( "%1 feature(s) on layer \"%2\", " ).arg( infoContext.duplicatedFeatures( chl ).size() ).arg( chl->name() ) );
723     }
724 
725     // for extra safety to make sure we know that the delete can have impact on children and joins
726     int res = QMessageBox::question( this, tr( "Delete at least %1 feature(s) on other layer(s)" ).arg( childrenCount ),
727                                      tr( "Delete %1 feature(s) on layer \"%2\", %3 as well\nand all of its other descendants.\nDelete these features?" ).arg( featureids.count() ).arg( layer->name() ).arg( childrenInfo ),
728                                      QMessageBox::Yes | QMessageBox::No );
729     if ( res != QMessageBox::Yes )
730       deleteFeatures = false;
731   }
732 
733   if ( deleteFeatures )
734   {
735     QgsVectorLayer::DeleteContext context( true, QgsProject::instance() );
736     layer->deleteFeatures( featureids, &context );
737     const auto contextLayers = context.handledLayers();
738     if ( contextLayers.size() > 1 )
739     {
740       int deletedCount = 0;
741       QString feedbackMessage;
742       for ( QgsVectorLayer *contextLayer : contextLayers )
743       {
744         feedbackMessage += tr( "%1 on layer %2. " ).arg( context.handledFeatures( contextLayer ).size() ).arg( contextLayer->name() );
745         deletedCount += context.handledFeatures( contextLayer ).size();
746       }
747       mEditorContext.mainMessageBar()->pushMessage( tr( "%1 features deleted: %2" ).arg( deletedCount ).arg( feedbackMessage ), Qgis::Success );
748     }
749 
750     updateUi();
751   }
752 }
753 
unlinkFeature(const QgsFeatureId featureid)754 void QgsRelationEditorWidget::unlinkFeature( const QgsFeatureId featureid )
755 {
756   unlinkFeatures( QgsFeatureIds() << featureid );
757 }
758 
unlinkSelectedFeatures()759 void QgsRelationEditorWidget::unlinkSelectedFeatures()
760 {
761   unlinkFeatures( mFeatureSelectionMgr->selectedFeatureIds() );
762 }
763 
zoomToSelectedFeatures()764 void QgsRelationEditorWidget::zoomToSelectedFeatures()
765 {
766   QgsMapCanvas *c = mEditorContext.mapCanvas();
767   if ( !c )
768     return;
769 
770   c->zoomToFeatureIds(
771     mNmRelation.isValid() ? mNmRelation.referencedLayer() : mRelation.referencingLayer(),
772     mFeatureSelectionMgr->selectedFeatureIds()
773   );
774 }
775 
unlinkFeatures(const QgsFeatureIds & featureids)776 void QgsRelationEditorWidget::unlinkFeatures( const QgsFeatureIds &featureids )
777 {
778   if ( mNmRelation.isValid() )
779   {
780     QgsFeatureIterator selectedIterator = mNmRelation.referencedLayer()->getFeatures(
781                                             QgsFeatureRequest()
782                                             .setFilterFids( featureids )
783                                             .setSubsetOfAttributes( mNmRelation.referencedFields() ) );
784 
785     QgsFeature f;
786 
787     QStringList filters;
788 
789     while ( selectedIterator.nextFeature( f ) )
790     {
791       filters << '(' + mNmRelation.getRelatedFeaturesRequest( f ).filterExpression()->expression() + ')';
792     }
793 
794     QString filter = QStringLiteral( "(%1) AND (%2)" ).arg(
795                        mRelation.getRelatedFeaturesRequest( mFeature ).filterExpression()->expression(),
796                        filters.join( QLatin1String( " OR " ) ) );
797 
798     QgsFeatureIterator linkedIterator = mRelation.referencingLayer()->getFeatures( QgsFeatureRequest()
799                                         .setNoAttributes()
800                                         .setFilterExpression( filter ) );
801 
802     QgsFeatureIds fids;
803 
804     while ( linkedIterator.nextFeature( f ) )
805     {
806       fids << f.id();
807       QgsDebugMsgLevel( FID_TO_STRING( f.id() ), 4 );
808     }
809 
810     mRelation.referencingLayer()->deleteFeatures( fids );
811 
812     updateUi();
813   }
814   else
815   {
816     QMap<int, QgsField> keyFields;
817     const auto constFieldPairs = mRelation.fieldPairs();
818     for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs )
819     {
820       int idx = mRelation.referencingLayer()->fields().lookupField( fieldPair.referencingField() );
821       if ( idx < 0 )
822       {
823         QgsDebugMsg( QStringLiteral( "referencing field %1 not found" ).arg( fieldPair.referencingField() ) );
824         return;
825       }
826       QgsField fld = mRelation.referencingLayer()->fields().at( idx );
827       keyFields.insert( idx, fld );
828     }
829 
830     const auto constFeatureids = featureids;
831     for ( QgsFeatureId fid : constFeatureids )
832     {
833       QMapIterator<int, QgsField> it( keyFields );
834       while ( it.hasNext() )
835       {
836         it.next();
837         mRelation.referencingLayer()->changeAttributeValue( fid, it.key(), QVariant( it.value().type() ) );
838       }
839     }
840   }
841 }
842 
toggleEditing(bool state)843 void QgsRelationEditorWidget::toggleEditing( bool state )
844 {
845   if ( state )
846   {
847     mEditorContext.vectorLayerTools()->startEditing( mRelation.referencingLayer() );
848     if ( mNmRelation.isValid() )
849       mEditorContext.vectorLayerTools()->startEditing( mNmRelation.referencedLayer() );
850   }
851   else
852   {
853     mEditorContext.vectorLayerTools()->stopEditing( mRelation.referencingLayer() );
854     if ( mNmRelation.isValid() )
855       mEditorContext.vectorLayerTools()->stopEditing( mNmRelation.referencedLayer() );
856   }
857 
858   updateButtons();
859 }
860 
saveEdits()861 void QgsRelationEditorWidget::saveEdits()
862 {
863   mEditorContext.vectorLayerTools()->saveEdits( mRelation.referencingLayer() );
864   if ( mNmRelation.isValid() )
865     mEditorContext.vectorLayerTools()->saveEdits( mNmRelation.referencedLayer() );
866 }
867 
onCollapsedStateChanged(bool collapsed)868 void QgsRelationEditorWidget::onCollapsedStateChanged( bool collapsed )
869 {
870   if ( !collapsed )
871   {
872     mVisible = true;
873     updateUi();
874   }
875 }
876 
updateUi()877 void QgsRelationEditorWidget::updateUi()
878 {
879   // If not yet initialized, it is not (yet) visible, so we don't load it to be faster (lazy loading)
880   // If it is already initialized, it has been set visible before and the currently shown feature is changing
881   // and the widget needs updating
882 
883   if ( mVisible )
884   {
885     QgsFeatureRequest myRequest = mRelation.getRelatedFeaturesRequest( mFeature );
886 
887     if ( mNmRelation.isValid() )
888     {
889       QgsFeatureIterator it = mRelation.referencingLayer()->getFeatures( myRequest );
890 
891       QgsFeature fet;
892 
893       QStringList filters;
894 
895       while ( it.nextFeature( fet ) )
896       {
897         QString filter = mNmRelation.getReferencedFeatureRequest( fet ).filterExpression()->expression();
898         filters << filter.prepend( '(' ).append( ')' );
899       }
900 
901       QgsFeatureRequest nmRequest;
902 
903       nmRequest.setFilterExpression( filters.join( QLatin1String( " OR " ) ) );
904 
905       initDualView( mNmRelation.referencedLayer(), nmRequest );
906     }
907     else if ( mRelation.referencingLayer() )
908     {
909       initDualView( mRelation.referencingLayer(), myRequest );
910     }
911   }
912 }
913 
showLinkButton() const914 bool QgsRelationEditorWidget::showLinkButton() const
915 {
916   return mLinkFeatureButton->isVisible();
917 }
918 
setShowLinkButton(bool showLinkButton)919 void QgsRelationEditorWidget::setShowLinkButton( bool showLinkButton )
920 {
921   mLinkFeatureButton->setVisible( showLinkButton );
922 }
923 
showUnlinkButton() const924 bool QgsRelationEditorWidget::showUnlinkButton() const
925 {
926   return mUnlinkFeatureButton->isVisible();
927 }
928 
setShowSaveChildEditsButton(bool showChildEdits)929 void QgsRelationEditorWidget::setShowSaveChildEditsButton( bool showChildEdits )
930 {
931   mSaveEditsButton->setVisible( showChildEdits );
932 }
933 
showSaveChildEditsButton() const934 bool QgsRelationEditorWidget::showSaveChildEditsButton() const
935 {
936   return mSaveEditsButton->isVisible();
937 }
938 
setVisibleButtons(const QgsAttributeEditorRelation::Buttons & buttons)939 void QgsRelationEditorWidget::setVisibleButtons( const QgsAttributeEditorRelation::Buttons &buttons )
940 {
941   mButtonsVisibility = buttons;
942   updateButtons();
943 }
944 
visibleButtons() const945 QgsAttributeEditorRelation::Buttons QgsRelationEditorWidget::visibleButtons() const
946 {
947   QgsAttributeEditorRelation::Buttons buttons;
948   if ( mLinkFeatureButton->isVisible() )
949     buttons |= QgsAttributeEditorRelation::Button::Link;
950   if ( mUnlinkFeatureButton->isVisible() )
951     buttons |= QgsAttributeEditorRelation::Button::Unlink;
952   if ( mSaveEditsButton->isVisible() )
953     buttons |= QgsAttributeEditorRelation::Button::SaveChildEdits;
954   if ( mAddFeatureButton->isVisible() )
955     buttons |= QgsAttributeEditorRelation::Button::AddChildFeature;
956   if ( mDuplicateFeatureButton->isVisible() )
957     buttons |= QgsAttributeEditorRelation::Button::DuplicateChildFeature;
958   if ( mDeleteFeatureButton->isVisible() )
959     buttons |= QgsAttributeEditorRelation::Button::DeleteChildFeature;
960   if ( mZoomToFeatureButton->isVisible() )
961     buttons |= QgsAttributeEditorRelation::Button::ZoomToChildFeature;
962   return buttons;
963 }
964 
setForceSuppressFormPopup(bool forceSuppressFormPopup)965 void QgsRelationEditorWidget::setForceSuppressFormPopup( bool forceSuppressFormPopup )
966 {
967   mForceSuppressFormPopup = forceSuppressFormPopup;
968 }
969 
forceSuppressFormPopup() const970 bool QgsRelationEditorWidget::forceSuppressFormPopup() const
971 {
972   return mForceSuppressFormPopup;
973 }
974 
setNmRelationId(const QVariant & nmRelationId)975 void QgsRelationEditorWidget::setNmRelationId( const QVariant &nmRelationId )
976 {
977   mNmRelationId = nmRelationId;
978 }
979 
nmRelationId() const980 QVariant QgsRelationEditorWidget::nmRelationId() const
981 {
982   return mNmRelationId;
983 }
984 
label() const985 QString QgsRelationEditorWidget::label() const
986 {
987   return mLabel;
988 }
989 
setLabel(const QString & label)990 void QgsRelationEditorWidget::setLabel( const QString &label )
991 {
992   mLabel = label;
993 
994   updateTitle();
995 }
996 
setShowUnlinkButton(bool showUnlinkButton)997 void QgsRelationEditorWidget::setShowUnlinkButton( bool showUnlinkButton )
998 {
999   mUnlinkFeatureButton->setVisible( showUnlinkButton );
1000 }
1001 
parentFormValueChanged(const QString & attribute,const QVariant & newValue)1002 void QgsRelationEditorWidget::parentFormValueChanged( const QString &attribute, const QVariant &newValue )
1003 {
1004   mDualView->parentFormValueChanged( attribute, newValue );
1005 }
1006 
showLabel() const1007 bool QgsRelationEditorWidget::showLabel() const
1008 {
1009   return mShowLabel;
1010 }
1011 
setShowLabel(bool showLabel)1012 void QgsRelationEditorWidget::setShowLabel( bool showLabel )
1013 {
1014   mShowLabel = showLabel;
1015 
1016   updateTitle();
1017 }
1018 
showContextMenu(QgsActionMenu * menu,const QgsFeatureId fid)1019 void QgsRelationEditorWidget::showContextMenu( QgsActionMenu *menu, const QgsFeatureId fid )
1020 {
1021   if ( mRelation.referencingLayer()->isEditable() )
1022   {
1023     QAction *qAction = nullptr;
1024 
1025     qAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteSelected.svg" ) ),  tr( "Delete Feature" ) );
1026     connect( qAction, &QAction::triggered, this, [this, fid]() { deleteFeature( fid ); } );
1027 
1028     qAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionUnlink.svg" ) ),  tr( "Unlink Feature" ) );
1029     connect( qAction, &QAction::triggered, this, [this, fid]() { unlinkFeature( fid ); } );
1030   }
1031 }
1032 
setMapTool(QgsMapTool * mapTool)1033 void QgsRelationEditorWidget::setMapTool( QgsMapTool *mapTool )
1034 {
1035   QgsMapCanvas *mapCanvas = mEditorContext.mapCanvas();
1036 
1037   mapCanvas->setMapTool( mapTool );
1038   mapCanvas->window()->raise();
1039   mapCanvas->activateWindow();
1040   mapCanvas->setFocus();
1041   connect( mapTool, &QgsMapTool::deactivated, this, &QgsRelationEditorWidget::mapToolDeactivated );
1042 }
1043 
unsetMapTool()1044 void QgsRelationEditorWidget::unsetMapTool()
1045 {
1046   QgsMapCanvas *mapCanvas = mEditorContext.mapCanvas();
1047 
1048   // this will call mapToolDeactivated
1049   mapCanvas->unsetMapTool( mMapToolDigitize );
1050 
1051   disconnect( mapCanvas, &QgsMapCanvas::keyPressed, this, &QgsRelationEditorWidget::onKeyPressed );
1052   disconnect( mMapToolDigitize, &QgsMapToolDigitizeFeature::digitizingCompleted, this, &QgsRelationEditorWidget::onDigitizingCompleted );
1053 }
1054 
updateTitle()1055 void QgsRelationEditorWidget::updateTitle()
1056 {
1057   if ( mShowLabel && !mLabel.isEmpty() )
1058   {
1059     setTitle( mLabel );
1060   }
1061   else if ( mShowLabel && mRelation.isValid() )
1062   {
1063     setTitle( mRelation.name() );
1064   }
1065   else
1066   {
1067     setTitle( QString() );
1068   }
1069 }
1070 
feature() const1071 QgsFeature QgsRelationEditorWidget::feature() const
1072 {
1073   return mFeature;
1074 }
1075 
onKeyPressed(QKeyEvent * e)1076 void QgsRelationEditorWidget::onKeyPressed( QKeyEvent *e )
1077 {
1078   if ( e->key() == Qt::Key_Escape )
1079   {
1080     unsetMapTool();
1081   }
1082 }
1083 
mapToolDeactivated()1084 void QgsRelationEditorWidget::mapToolDeactivated()
1085 {
1086   window()->setVisible( true );
1087   window()->raise();
1088   window()->activateWindow();
1089 
1090   if ( mEditorContext.mainMessageBar() && mMessageBarItem )
1091   {
1092     mEditorContext.mainMessageBar()->popWidget( mMessageBarItem );
1093   }
1094   mMessageBarItem = nullptr;
1095 }
1096