QGIS API Documentation  3.8.0-Zanzibar (11aff65)
qgsrelationeditorwidget.cpp
Go to the documentation of this file.
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 
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"
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 
34 #include <QHBoxLayout>
35 #include <QLabel>
36 #include <QMessageBox>
37 
39  : QgsCollapsibleGroupBox( parent )
40 {
41  QVBoxLayout *topLayout = new QVBoxLayout( this );
42  topLayout->setContentsMargins( 0, 9, 0, 0 );
43  setLayout( topLayout );
44 
45  // buttons
46  QHBoxLayout *buttonLayout = new QHBoxLayout();
47  buttonLayout->setContentsMargins( 0, 0, 0, 0 );
48  // toggle editing
49  mToggleEditingButton = new QToolButton( this );
50  mToggleEditingButton->setObjectName( QStringLiteral( "mToggleEditingButton" ) );
51  mToggleEditingButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) );
52  mToggleEditingButton->setText( tr( "Toggle Editing" ) );
53  mToggleEditingButton->setEnabled( false );
54  mToggleEditingButton->setCheckable( true );
55  mToggleEditingButton->setToolTip( tr( "Toggle editing mode for child layer" ) );
56  buttonLayout->addWidget( mToggleEditingButton );
57  // save Edits
58  mSaveEditsButton = new QToolButton( this );
59  mSaveEditsButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveEdits.svg" ) ) );
60  mSaveEditsButton->setText( tr( "Save Child Layer Edits" ) );
61  mSaveEditsButton->setToolTip( tr( "Save child layer edits" ) );
62  mSaveEditsButton->setEnabled( true );
63  buttonLayout->addWidget( mSaveEditsButton );
64  // add feature
65  mAddFeatureButton = new QToolButton( this );
66  mAddFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewTableRow.svg" ) ) );
67  mAddFeatureButton->setText( tr( "Add Child Feature" ) );
68  mAddFeatureButton->setToolTip( tr( "Add child feature" ) );
69  mAddFeatureButton->setObjectName( QStringLiteral( "mAddFeatureButton" ) );
70  buttonLayout->addWidget( mAddFeatureButton );
71  // duplicate feature
72  mDuplicateFeatureButton = new QToolButton( this );
73  mDuplicateFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDuplicateFeature.svg" ) ) );
74  mDuplicateFeatureButton->setText( tr( "Duplicate Child Feature" ) );
75  mDuplicateFeatureButton->setToolTip( tr( "Duplicate child feature" ) );
76  mDuplicateFeatureButton->setObjectName( QStringLiteral( "mDuplicateFeatureButton" ) );
77  buttonLayout->addWidget( mDuplicateFeatureButton );
78  // delete feature
79  mDeleteFeatureButton = new QToolButton( this );
80  mDeleteFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteSelected.svg" ) ) );
81  mDeleteFeatureButton->setText( tr( "Delete Child Feature" ) );
82  mDeleteFeatureButton->setToolTip( tr( "Delete child feature" ) );
83  mDeleteFeatureButton->setObjectName( QStringLiteral( "mDeleteFeatureButton" ) );
84  buttonLayout->addWidget( mDeleteFeatureButton );
85  // link feature
86  mLinkFeatureButton = new QToolButton( this );
87  mLinkFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionLink.svg" ) ) );
88  mLinkFeatureButton->setText( tr( "Link Existing Features" ) );
89  mLinkFeatureButton->setToolTip( tr( "Link existing child features" ) );
90  mLinkFeatureButton->setObjectName( QStringLiteral( "mLinkFeatureButton" ) );
91  buttonLayout->addWidget( mLinkFeatureButton );
92  // unlink feature
93  mUnlinkFeatureButton = new QToolButton( this );
94  mUnlinkFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionUnlink.svg" ) ) );
95  mUnlinkFeatureButton->setText( tr( "Unlink Feature" ) );
96  mUnlinkFeatureButton->setToolTip( tr( "Unlink child feature" ) );
97  mUnlinkFeatureButton->setObjectName( QStringLiteral( "mUnlinkFeatureButton" ) );
98  buttonLayout->addWidget( mUnlinkFeatureButton );
99  // zoom to linked feature
100  mZoomToFeatureButton = new QToolButton( this );
101  mZoomToFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomToSelected.svg" ) ) );
102  mZoomToFeatureButton->setText( tr( "Zoom To Feature" ) );
103  mZoomToFeatureButton->setToolTip( tr( "Zoom to child feature" ) );
104  mZoomToFeatureButton->setObjectName( QStringLiteral( "mZoomToFeatureButton" ) );
105  buttonLayout->addWidget( mZoomToFeatureButton );
106  // spacer
107  buttonLayout->addItem( new QSpacerItem( 0, 0, QSizePolicy::Expanding ) );
108  // form view
109  mFormViewButton = new QToolButton( this );
110  mFormViewButton->setText( tr( "Form View" ) );
111  mFormViewButton->setToolTip( tr( "Switch to form view" ) );
112  mFormViewButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPropertyItem.svg" ) ) );
113  mFormViewButton->setCheckable( true );
114  mFormViewButton->setChecked( mViewMode == QgsDualView::AttributeEditor );
115  buttonLayout->addWidget( mFormViewButton );
116  // table view
117  mTableViewButton = new QToolButton( this );
118  mTableViewButton->setText( tr( "Table View" ) );
119  mTableViewButton->setToolTip( tr( "Switch to table view" ) );
120  mTableViewButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOpenTable.svg" ) ) );
121  mTableViewButton->setCheckable( true );
122  mTableViewButton->setChecked( mViewMode == QgsDualView::AttributeTable );
123  buttonLayout->addWidget( mTableViewButton );
124  // button group
125  mViewModeButtonGroup = new QButtonGroup( this );
126  mViewModeButtonGroup->addButton( mFormViewButton, QgsDualView::AttributeEditor );
127  mViewModeButtonGroup->addButton( mTableViewButton, QgsDualView::AttributeTable );
128 
129  // add buttons layout
130  topLayout->addLayout( buttonLayout );
131 
132  mRelationLayout = new QGridLayout();
133  mRelationLayout->setContentsMargins( 0, 0, 0, 0 );
134  topLayout->addLayout( mRelationLayout );
135 
136  mDualView = new QgsDualView( this );
137  mDualView->setView( mViewMode );
138  mFeatureSelectionMgr = new QgsGenericFeatureSelectionManager( mDualView );
139  mDualView->setFeatureSelectionManager( mFeatureSelectionMgr );
140 
141  mRelationLayout->addWidget( mDualView );
142 
143  connect( this, &QgsCollapsibleGroupBoxBasic::collapsedStateChanged, this, &QgsRelationEditorWidget::onCollapsedStateChanged );
144  connect( mViewModeButtonGroup, static_cast<void ( QButtonGroup::* )( int )>( &QButtonGroup::buttonClicked ),
145  this, static_cast<void ( QgsRelationEditorWidget::* )( int )>( &QgsRelationEditorWidget::setViewMode ) );
146  connect( mToggleEditingButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::toggleEditing );
147  connect( mSaveEditsButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::saveEdits );
148  connect( mAddFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::addFeature );
149  connect( mDuplicateFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::duplicateFeature );
150  connect( mDeleteFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::deleteSelectedFeatures );
151  connect( mLinkFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::linkFeature );
152  connect( mUnlinkFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::unlinkSelectedFeatures );
153  connect( mZoomToFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::zoomToSelectedFeatures );
154  connect( mFeatureSelectionMgr, &QgsIFeatureSelectionManager::selectionChanged, this, &QgsRelationEditorWidget::updateButtons );
155 
156  connect( mDualView, &QgsDualView::showContextMenuExternally, this, &QgsRelationEditorWidget::showContextMenu );
157 
158  // Set initial state for add/remove etc. buttons
159  updateButtons();
160 }
161 
163 {
164  if ( mRelation.isValid() )
165  {
166  disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
167  disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
168  }
169 
170  mRelation = relation;
171  mFeature = feature;
172 
173  connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
174  connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
175 
176  if ( mShowLabel )
177  setTitle( relation.name() );
178 
179  QgsVectorLayer *lyr = relation.referencingLayer();
180 
181  bool canChangeAttributes = lyr->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeAttributeValues;
182  if ( canChangeAttributes && !lyr->readOnly() )
183  {
184  mToggleEditingButton->setEnabled( true );
185  updateButtons();
186  }
187  else
188  {
189  mToggleEditingButton->setEnabled( false );
190  }
191 
192  setObjectName( QStringLiteral( "referenced/" ) + mRelation.name() );
193 
194  // If not yet initialized, it is not (yet) visible, so we don't load it to be faster (lazy loading)
195  // If it is already initialized, it has been set visible before and the currently shown feature is changing
196  // and the widget needs updating
197 
198  if ( mVisible )
199  {
200  QgsFeatureRequest myRequest = mRelation.getRelatedFeaturesRequest( mFeature );
201  mDualView->init( mRelation.referencingLayer(), nullptr, myRequest, mEditorContext );
202  }
203 }
204 
205 void QgsRelationEditorWidget::setRelations( const QgsRelation &relation, const QgsRelation &nmrelation )
206 {
207  if ( mRelation.isValid() )
208  {
209  disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
210  disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
211  }
212 
213  if ( mNmRelation.isValid() )
214  {
215  disconnect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
216  disconnect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
217  }
218 
219  mRelation = relation;
220  mNmRelation = nmrelation;
221 
222  if ( !mRelation.isValid() )
223  return;
224 
225  mToggleEditingButton->setVisible( true );
226 
227  const auto transactionGroups = QgsProject::instance()->transactionGroups();
228  for ( auto it = transactionGroups.constBegin(); it != transactionGroups.constEnd(); ++it )
229  {
230  if ( it.value()->layers().contains( mRelation.referencingLayer() ) )
231  {
232  mToggleEditingButton->setVisible( false );
233  mSaveEditsButton->setVisible( false );
234  }
235  }
236 
237  connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
238  connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
239 
240  if ( mNmRelation.isValid() )
241  {
242  connect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
243  connect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
244  }
245 
246  setTitle( relation.name() );
247 
248  QgsVectorLayer *lyr = relation.referencingLayer();
249 
250  bool canChangeAttributes = lyr->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeAttributeValues;
251  if ( canChangeAttributes && !lyr->readOnly() )
252  {
253  mToggleEditingButton->setEnabled( true );
254  updateButtons();
255  }
256  else
257  {
258  mToggleEditingButton->setEnabled( false );
259  }
260 
261  if ( mNmRelation.isValid() )
262  mZoomToFeatureButton->setVisible( mNmRelation.referencedLayer()->isSpatial() );
263  else
264  mZoomToFeatureButton->setVisible( mRelation.referencingLayer()->isSpatial() );
265 
266  setObjectName( QStringLiteral( "referenced/" ) + mRelation.name() );
267 
268  updateUi();
269 }
270 
272 {
273  mEditorContext = context;
274 }
275 
277 {
278  return mFeatureSelectionMgr;
279 }
280 
282 {
283  mDualView->setView( mode );
284  mViewMode = mode;
285 }
286 
288 {
289  mFeature = feature;
290 
291  updateUi();
292 }
293 
294 void QgsRelationEditorWidget::updateButtons()
295 {
296  bool editable = false;
297  bool linkable = false;
298  bool selectionNotEmpty = mFeatureSelectionMgr->selectedFeatureCount();
299 
300  if ( mRelation.isValid() )
301  {
302  editable = mRelation.referencingLayer()->isEditable();
303  linkable = mRelation.referencingLayer()->isEditable();
304  }
305 
306  if ( mNmRelation.isValid() )
307  {
308  editable = mNmRelation.referencedLayer()->isEditable();
309  }
310 
311  mAddFeatureButton->setEnabled( editable );
312  mDuplicateFeatureButton->setEnabled( editable && selectionNotEmpty );
313  mLinkFeatureButton->setEnabled( linkable );
314  mDeleteFeatureButton->setEnabled( editable && selectionNotEmpty );
315  mUnlinkFeatureButton->setEnabled( linkable && selectionNotEmpty );
316 
317  mZoomToFeatureButton->setVisible(
318  mEditorContext.mapCanvas() && (
319  (
320  mNmRelation.isValid() &&
323  )
324  ||
325  (
326  mRelation.isValid() &&
329  )
330  )
331  );
332 
333  mZoomToFeatureButton->setEnabled( selectionNotEmpty );
334 
335  mToggleEditingButton->setChecked( editable );
336  mSaveEditsButton->setEnabled( editable );
337 }
338 
339 void QgsRelationEditorWidget::addFeature()
340 {
341  QgsAttributeMap keyAttrs;
342 
343  const QgsVectorLayerTools *vlTools = mEditorContext.vectorLayerTools();
344 
345  if ( mNmRelation.isValid() )
346  {
347  // n:m Relation: first let the user create a new feature on the other table
348  // and autocreate a new linking feature.
349  QgsFeature f;
350  if ( vlTools->addFeature( mNmRelation.referencedLayer(), QgsAttributeMap(), QgsGeometry(), &f ) )
351  {
352  // Fields of the linking table
353  const QgsFields fields = mRelation.referencingLayer()->fields();
354 
355  // Expression context for the linking table
357 
358  QgsAttributeMap linkAttributes;
359  const auto constFieldPairs = mRelation.fieldPairs();
360  for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs )
361  {
362  int index = fields.indexOf( fieldPair.first );
363  linkAttributes.insert( index, mFeature.attribute( fieldPair.second ) );
364  }
365 
366  const auto constNmFieldPairs = mNmRelation.fieldPairs();
367  for ( const QgsRelation::FieldPair &fieldPair : constNmFieldPairs )
368  {
369  int index = fields.indexOf( fieldPair.first );
370  linkAttributes.insert( index, f.attribute( fieldPair.second ) );
371  }
372  QgsFeature linkFeature = QgsVectorLayerUtils::createFeature( mRelation.referencingLayer(), QgsGeometry(), linkAttributes, &context );
373 
374  mRelation.referencingLayer()->addFeature( linkFeature );
375 
376  updateUi();
377  }
378  }
379  else
380  {
381  QgsFields fields = mRelation.referencingLayer()->fields();
382 
383  const auto constFieldPairs = mRelation.fieldPairs();
384  for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs )
385  {
386  keyAttrs.insert( fields.indexFromName( fieldPair.referencingField() ), mFeature.attribute( fieldPair.referencedField() ) );
387  }
388 
389  vlTools->addFeature( mDualView->masterModel()->layer(), keyAttrs );
390  }
391 }
392 
393 void QgsRelationEditorWidget::linkFeature()
394 {
395  QgsVectorLayer *layer = nullptr;
396 
397  if ( mNmRelation.isValid() )
398  layer = mNmRelation.referencedLayer();
399  else
400  layer = mRelation.referencingLayer();
401 
402  QgsFeatureSelectionDlg selectionDlg( layer, mEditorContext, this );
403 
404  if ( selectionDlg.exec() )
405  {
406  if ( mNmRelation.isValid() )
407  {
408  QgsFeatureIterator it = mNmRelation.referencedLayer()->getFeatures(
410  .setFilterFids( selectionDlg.selectedFeatures() )
411  .setSubsetOfAttributes( mNmRelation.referencedFields() ) );
412 
413  QgsFeature relatedFeature;
414 
415  QgsFeatureList newFeatures;
416 
417  // Fields of the linking table
418  const QgsFields fields = mRelation.referencingLayer()->fields();
419 
420  // Expression context for the linking table
422 
423  QgsAttributeMap linkAttributes;
424  const auto constFieldPairs = mRelation.fieldPairs();
425  for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs )
426  {
427  int index = fields.indexOf( fieldPair.first );
428  linkAttributes.insert( index, mFeature.attribute( fieldPair.second ) );
429  }
430 
431  while ( it.nextFeature( relatedFeature ) )
432  {
433  const auto constFieldPairs = mNmRelation.fieldPairs();
434  for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs )
435  {
436  int index = fields.indexOf( fieldPair.first );
437  linkAttributes.insert( index, relatedFeature.attribute( fieldPair.second ) );
438  }
439  const QgsFeature linkFeature = QgsVectorLayerUtils::createFeature( mRelation.referencingLayer(), QgsGeometry(), linkAttributes, &context );
440 
441  newFeatures << linkFeature;
442  }
443 
444  mRelation.referencingLayer()->addFeatures( newFeatures );
445  QgsFeatureIds ids;
446  const auto constNewFeatures = newFeatures;
447  for ( const QgsFeature &f : constNewFeatures )
448  ids << f.id();
449  mRelation.referencingLayer()->selectByIds( ids );
450 
451 
452  updateUi();
453  }
454  else
455  {
456  QMap<int, QVariant> keys;
457  const auto constFieldPairs = mRelation.fieldPairs();
458  for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs )
459  {
460  int idx = mRelation.referencingLayer()->fields().lookupField( fieldPair.referencingField() );
461  QVariant val = mFeature.attribute( fieldPair.referencedField() );
462  keys.insert( idx, val );
463  }
464 
465  const auto constSelectedFeatures = selectionDlg.selectedFeatures();
466  for ( QgsFeatureId fid : constSelectedFeatures )
467  {
468  QMapIterator<int, QVariant> it( keys );
469  while ( it.hasNext() )
470  {
471  it.next();
472  mRelation.referencingLayer()->changeAttributeValue( fid, it.key(), it.value() );
473  }
474  }
475  }
476  }
477 }
478 
479 void QgsRelationEditorWidget::duplicateFeature()
480 {
481  QgsVectorLayer *layer = mRelation.referencingLayer();
482 
483  QgsFeatureIterator fit = layer->getFeatures( QgsFeatureRequest().setFilterFids( mFeatureSelectionMgr->selectedFeatureIds() ) );
484  QgsFeature f;
485  while ( fit.nextFeature( f ) )
486  {
487  QgsVectorLayerUtils::QgsDuplicateFeatureContext duplicatedFeatureContext;
488  QgsVectorLayerUtils::duplicateFeature( layer, f, QgsProject::instance(), 0, duplicatedFeatureContext );
489  }
490 }
491 
492 void QgsRelationEditorWidget::deleteFeature( const QgsFeatureId featureid )
493 {
494  deleteFeatures( QgsFeatureIds() << featureid );
495 }
496 
497 void QgsRelationEditorWidget::deleteSelectedFeatures()
498 {
499  deleteFeatures( mFeatureSelectionMgr->selectedFeatureIds() );
500 }
501 
502 void QgsRelationEditorWidget::deleteFeatures( const QgsFeatureIds &featureids )
503 {
504  bool deleteFeatures = true;
505 
506  QgsVectorLayer *layer;
507  if ( mNmRelation.isValid() )
508  {
509  layer = mNmRelation.referencedLayer();
510 
511  // When deleting a linked feature within an N:M relation,
512  // check if the feature is linked to more than just one feature.
513  // In case it is linked more than just once, ask the user for confirmation
514  // as it is likely he was not aware of the implications and might either
515  // leave the dataset in a corrupted state (referential integrity) or if
516  // the fk constraint is ON CASCADE DELETE, there may be several linking
517  // entries deleted along.
518 
519  QgsFeatureRequest deletedFeaturesRequest;
520  deletedFeaturesRequest.setFilterFids( featureids );
521  deletedFeaturesRequest.setFlags( QgsFeatureRequest::NoGeometry );
522  deletedFeaturesRequest.setSubsetOfAttributes( QgsAttributeList() << mNmRelation.referencedFields().first() );
523 
524  QgsFeatureIterator deletedFeatures = layer->getFeatures( deletedFeaturesRequest );
525  QStringList deletedFeaturesPks;
526  QgsFeature feature;
527  while ( deletedFeatures.nextFeature( feature ) )
528  {
529  deletedFeaturesPks.append( QgsExpression::quotedValue( feature.attribute( mNmRelation.referencedFields().first() ) ) );
530  }
531 
532  QgsFeatureRequest linkingFeaturesRequest;
533  linkingFeaturesRequest.setFlags( QgsFeatureRequest::NoGeometry );
534  linkingFeaturesRequest.setNoAttributes();
535 
536  QString linkingFeaturesRequestExpression;
537  if ( !deletedFeaturesPks.empty() )
538  {
539  linkingFeaturesRequestExpression = QStringLiteral( "%1 IN (%2)" ).arg( QgsExpression::quotedColumnRef( mNmRelation.fieldPairs().first().first ), deletedFeaturesPks.join( ',' ) );
540  linkingFeaturesRequest.setFilterExpression( linkingFeaturesRequestExpression );
541 
542  QgsFeatureIterator relatedLinkingFeatures = mNmRelation.referencingLayer()->getFeatures( linkingFeaturesRequest );
543 
544  int relatedLinkingFeaturesCount = 0;
545  while ( relatedLinkingFeatures.nextFeature( feature ) )
546  {
547  relatedLinkingFeaturesCount++;
548  }
549 
550  if ( deletedFeaturesPks.size() == 1 && relatedLinkingFeaturesCount > 1 )
551  {
552  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 );
553  messageBox.addButton( QMessageBox::Cancel );
554  QAbstractButton *deleteButton = messageBox.addButton( tr( "Delete" ), QMessageBox::AcceptRole );
555 
556  messageBox.exec();
557  if ( messageBox.clickedButton() != deleteButton )
558  deleteFeatures = false;
559  }
560  else if ( deletedFeaturesPks.size() > 1 && relatedLinkingFeaturesCount > deletedFeaturesPks.size() )
561  {
562  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 );
563  messageBox.addButton( QMessageBox::Cancel );
564  QAbstractButton *deleteButton = messageBox.addButton( tr( "Delete" ), QMessageBox::AcceptRole );
565 
566  messageBox.exec();
567  if ( messageBox.clickedButton() != deleteButton )
568  deleteFeatures = false;
569  }
570  }
571  }
572  else
573  {
574  layer = mRelation.referencingLayer();
575  }
576 
577  if ( deleteFeatures )
578  {
579  layer->deleteFeatures( featureids );
580  updateUi();
581  }
582 }
583 
584 void QgsRelationEditorWidget::unlinkFeature( const QgsFeatureId featureid )
585 {
586  unlinkFeatures( QgsFeatureIds() << featureid );
587 }
588 
589 void QgsRelationEditorWidget::unlinkSelectedFeatures()
590 {
591  unlinkFeatures( mFeatureSelectionMgr->selectedFeatureIds() );
592 }
593 
594 void QgsRelationEditorWidget::zoomToSelectedFeatures()
595 {
596  QgsMapCanvas *c = mEditorContext.mapCanvas();
597  if ( !c )
598  return;
599 
600  c->zoomToFeatureIds(
601  mNmRelation.isValid() ? mNmRelation.referencedLayer() : mRelation.referencingLayer(),
602  mFeatureSelectionMgr->selectedFeatureIds()
603  );
604 }
605 
606 void QgsRelationEditorWidget::unlinkFeatures( const QgsFeatureIds &featureids )
607 {
608  if ( mNmRelation.isValid() )
609  {
610  QgsFeatureIterator selectedIterator = mNmRelation.referencedLayer()->getFeatures(
612  .setFilterFids( featureids )
613  .setSubsetOfAttributes( mNmRelation.referencedFields() ) );
614 
615  QgsFeature f;
616 
617  QStringList filters;
618 
619  while ( selectedIterator.nextFeature( f ) )
620  {
621  filters << '(' + mNmRelation.getRelatedFeaturesRequest( f ).filterExpression()->expression() + ')';
622  }
623 
624  QString filter = QStringLiteral( "(%1) AND (%2)" ).arg(
625  mRelation.getRelatedFeaturesRequest( mFeature ).filterExpression()->expression(),
626  filters.join( QStringLiteral( " OR " ) ) );
627 
628  QgsFeatureIterator linkedIterator = mRelation.referencingLayer()->getFeatures( QgsFeatureRequest()
629  .setNoAttributes()
630  .setFilterExpression( filter ) );
631 
632  QgsFeatureIds fids;
633 
634  while ( linkedIterator.nextFeature( f ) )
635  {
636  fids << f.id();
637  QgsDebugMsgLevel( FID_TO_STRING( f.id() ), 4 );
638  }
639 
640  mRelation.referencingLayer()->deleteFeatures( fids );
641 
642  updateUi();
643  }
644  else
645  {
646  QMap<int, QgsField> keyFields;
647  const auto constFieldPairs = mRelation.fieldPairs();
648  for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs )
649  {
650  int idx = mRelation.referencingLayer()->fields().lookupField( fieldPair.referencingField() );
651  if ( idx < 0 )
652  {
653  QgsDebugMsg( QStringLiteral( "referencing field %1 not found" ).arg( fieldPair.referencingField() ) );
654  return;
655  }
656  QgsField fld = mRelation.referencingLayer()->fields().at( idx );
657  keyFields.insert( idx, fld );
658  }
659 
660  const auto constFeatureids = featureids;
661  for ( QgsFeatureId fid : constFeatureids )
662  {
663  QMapIterator<int, QgsField> it( keyFields );
664  while ( it.hasNext() )
665  {
666  it.next();
667  mRelation.referencingLayer()->changeAttributeValue( fid, it.key(), QVariant( it.value().type() ) );
668  }
669  }
670  }
671 }
672 
673 void QgsRelationEditorWidget::toggleEditing( bool state )
674 {
675  if ( state )
676  {
677  mEditorContext.vectorLayerTools()->startEditing( mRelation.referencingLayer() );
678  if ( mNmRelation.isValid() )
679  mEditorContext.vectorLayerTools()->startEditing( mNmRelation.referencedLayer() );
680  }
681  else
682  {
683  mEditorContext.vectorLayerTools()->stopEditing( mRelation.referencingLayer() );
684  if ( mNmRelation.isValid() )
685  mEditorContext.vectorLayerTools()->stopEditing( mNmRelation.referencedLayer() );
686  }
687 }
688 
689 void QgsRelationEditorWidget::saveEdits()
690 {
691  mEditorContext.vectorLayerTools()->saveEdits( mRelation.referencingLayer() );
692  if ( mNmRelation.isValid() )
693  mEditorContext.vectorLayerTools()->saveEdits( mNmRelation.referencedLayer() );
694 }
695 
696 void QgsRelationEditorWidget::onCollapsedStateChanged( bool collapsed )
697 {
698  if ( !collapsed )
699  {
700  mVisible = true;
701  updateUi();
702  }
703 }
704 
705 void QgsRelationEditorWidget::updateUi()
706 {
707  // If not yet initialized, it is not (yet) visible, so we don't load it to be faster (lazy loading)
708  // If it is already initialized, it has been set visible before and the currently shown feature is changing
709  // and the widget needs updating
710 
711  if ( mVisible )
712  {
713  QgsFeatureRequest myRequest = mRelation.getRelatedFeaturesRequest( mFeature );
714 
715  if ( mNmRelation.isValid() )
716  {
717  QgsFeatureIterator it = mRelation.referencingLayer()->getFeatures( myRequest );
718 
719  QgsFeature fet;
720 
721  QStringList filters;
722 
723  while ( it.nextFeature( fet ) )
724  {
725  QString filter = mNmRelation.getReferencedFeatureRequest( fet ).filterExpression()->expression();
726  filters << filter.prepend( '(' ).append( ')' );
727  }
728 
729  QgsFeatureRequest nmRequest;
730 
731  nmRequest.setFilterExpression( filters.join( QStringLiteral( " OR " ) ) );
732 
733  mDualView->init( mNmRelation.referencedLayer(), nullptr, nmRequest, mEditorContext );
734  }
735  else
736  {
737  mDualView->init( mRelation.referencingLayer(), nullptr, myRequest, mEditorContext );
738  }
739  }
740 }
741 
743 {
744  return mLinkFeatureButton->isVisible();
745 }
746 
748 {
749  mLinkFeatureButton->setVisible( showLinkButton );
750 }
751 
753 {
754  return mUnlinkFeatureButton->isVisible();
755 }
756 
758 {
759  mUnlinkFeatureButton->setVisible( showUnlinkButton );
760 }
761 
763 {
764  return mShowLabel;
765 }
766 
768 {
769  mShowLabel = showLabel;
770 
771  if ( mShowLabel && mRelation.isValid() )
772  setTitle( mRelation.name() );
773  else
774  setTitle( QString() );
775 }
776 
777 void QgsRelationEditorWidget::showContextMenu( QgsActionMenu *menu, const QgsFeatureId fid )
778 {
779  if ( mRelation.referencingLayer()->isEditable() )
780  {
781  QAction *qAction = nullptr;
782 
783  qAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteSelected.svg" ) ), tr( "Delete Feature" ) );
784  connect( qAction, &QAction::triggered, this, [this, fid]() { deleteFeature( fid ); } );
785 
786  qAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionUnlink.svg" ) ), tr( "Unlink Feature" ) );
787  connect( qAction, &QAction::triggered, this, [this, fid]() { unlinkFeature( fid ); } );
788  }
789 }
int lookupField(const QString &fieldName) const
Looks up field&#39;s index from the field name.
Definition: qgsfields.cpp:324
Methods in this class are used to handle basic operations on vector layers.
const QgsVectorLayerTools * vectorLayerTools() const
Returns the associated vector layer tools.
QString name
Definition: qgsrelation.h:48
QgsFeatureId id
Definition: qgsfeature.h:64
bool showUnlinkButton() const
Determines if the "unlink feature" button should be shown.
Wrapper for iterator of features from vector data provider or vector layer.
QgsVectorLayer * layer() const
Returns the layer this model uses as backend.
virtual bool saveEdits(QgsVectorLayer *layer) const =0
Should be called, when the features should be committed but the editing session is not ended...
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:34
void init(QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request=QgsFeatureRequest(), const QgsAttributeEditorContext &context=QgsAttributeEditorContext(), bool loadFeatures=true)
Has to be called to initialize the dual view.
Definition: qgsdualview.cpp:97
bool collapsed
The collapsed state of this group box.
virtual QgsVectorDataProvider::Capabilities capabilities() const
Returns flags containing the supported capabilities.
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
virtual bool startEditing(QgsVectorLayer *layer) const =0
This will be called, whenever a vector layer should be switched to edit mode.
QgsAttributeTableModel * masterModel() const
Returns the model which has the information about all features (not only filtered) ...
Definition: qgsdualview.h:172
void setFeatureSelectionManager(QgsIFeatureSelectionManager *featureSelectionManager)
Set the feature selection model.
A groupbox that collapses/expands when toggled and can save its collapsed and checked states...
int selectedFeatureCount() override
Returns the number of features that are selected in this layer.
Contains mainly the QMap with QgsVectorLayer and QgsFeatureIds do list all the duplicated features...
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
This class contains context information for attribute editor widgets.
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:571
QgsVectorLayer referencingLayer
Definition: qgsrelation.h:46
ViewMode
The view modes, in which this widget can present information.
Definition: qgsdualview.h:52
void collapsedStateChanged(bool collapsed)
Signal emitted when groupbox collapsed/expanded state is changed, and when first shown.
qint64 QgsFeatureId
Definition: qgsfeatureid.h:25
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
bool deleteFeatures(const QgsFeatureIds &fids)
Deletes a set of features from the layer (but does not commit it)
Container of fields for a vector layer.
Definition: qgsfields.h:42
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:111
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
void selectByIds(const QgsFeatureIds &ids, SelectBehavior behavior=SetSelection)
Selects matching features using a list of feature IDs.
bool showLinkButton() const
Determines if the "link feature" button should be shown.
virtual bool stopEditing(QgsVectorLayer *layer, bool allowCancel=true) const =0
Will be called, when an editing session is ended and the features should be committed.
void selectionChanged(const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect)
Emitted when selection was changed.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
void setViewMode(QgsDualView::ViewMode mode)
Define the view mode for the dual view.
bool isSpatial() const FINAL
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:73
void setView(ViewMode view)
Change the current view mode.
const QgsFeatureIds & selectedFeatures()
Gets the selected features.
QgsField at(int i) const
Gets field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:163
QgsExpression * filterExpression() const
Returns the filter expression if set.
Show a list of the features, where one can be chosen and the according attribute dialog will be prese...
Definition: qgsdualview.h:65
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
void setShowLinkButton(bool showLinkButton)
Determines if the "link feature" button should be shown.
QgsAttributeList referencedFields() const
Returns a list of attributes used to form the referenced fields (most likely primary key) on the refe...
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:202
QgsFeatureRequest getReferencedFeatureRequest(const QgsAttributes &attributes) const
Creates a request to return the feature on the referenced (parent) layer which is referenced by the p...
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Defines a relation between matching fields of the two involved tables of a relation.
Definition: qgsrelation.h:74
Shows the features and attributes in a table layout.
Definition: qgsdualview.h:58
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
void setShowUnlinkButton(bool showUnlinkButton)
Determines if the "unlink feature" button should be shown.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
This class is a menu that is populated automatically with the actions defined for a given layer...
Definition: qgsactionmenu.h:38
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
static QgsFeature createFeature(const QgsVectorLayer *layer, const QgsGeometry &geometry=QgsGeometry(), const QgsAttributeMap &attributes=QgsAttributeMap(), QgsExpressionContext *context=nullptr)
Creates a new feature ready for insertion into a layer.
QMap< int, QVariant > QgsAttributeMap
Definition: qgsattributes.h:38
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
bool addFeatures(QgsFeatureList &features, QgsFeatureSink::Flags flags=nullptr) FINAL
Adds a list of features to the sink.
QgsRelationEditorWidget(QWidget *parent=nullptr)
QgsVectorLayer referencedLayer
Definition: qgsrelation.h:47
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:48
QgsFeatureRequest getRelatedFeaturesRequest(const QgsFeature &feature) const
Creates a request to return all the features on the referencing (child) layer which have a foreign ke...
void editingStarted()
Emitted when editing on this layer has started.
QgsExpressionContext createExpressionContext() const FINAL
This method needs to be reimplemented in all classes which implement this interface and return an exp...
QList< QgsRelation::FieldPair > fieldPairs() const
Returns the field pairs which form this relation The first element of each pair are the field names o...
QString expression() const
Returns the original, unmodified expression string.
This selection manager synchronizes a local set of selected features with an attribute table...
void setRelations(const QgsRelation &relation, const QgsRelation &nmrelation)
Set the relation(s) for this widget If only one relation is set, it will act as a simple 1:N relation...
void setRelationFeature(const QgsRelation &relation, const QgsFeature &feature)
void showContextMenuExternally(QgsActionMenu *menu, QgsFeatureId fid)
Emitted when selecting context menu on the feature list to create the context menu individually...
void zoomToFeatureIds(QgsVectorLayer *layer, const QgsFeatureIds &ids)
Set canvas extent to the bounding box of a set of features.
bool isValid
Definition: qgsrelation.h:49
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets feature IDs that should be fetched.
#define FID_TO_STRING(fid)
Definition: qgsfeatureid.h:30
int indexOf(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:207
QMap< QPair< QString, QString >, QgsTransactionGroup * > transactionGroups()
Map of transaction groups.
QgsIFeatureSelectionManager * featureSelectionManager()
The feature selection manager is responsible for the selected features which are currently being edit...
static QgsFeature duplicateFeature(QgsVectorLayer *layer, const QgsFeature &feature, QgsProject *project, int depth, QgsDuplicateFeatureContext &duplicateFeatureContext)
Duplicates a feature and it&#39;s children (one level deep).
void setShowLabel(bool showLabel)
Defines if a title label should be shown for this widget.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:438
void setEditorContext(const QgsAttributeEditorContext &context)
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=nullptr) FINAL
Adds a single feature to the sink.
virtual bool addFeature(QgsVectorLayer *layer, const QgsAttributeMap &defaultValues=QgsAttributeMap(), const QgsGeometry &defaultGeometry=QgsGeometry(), QgsFeature *feature=nullptr) const =0
This method should/will be called, whenever a new feature will be added to the layer.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QString name
Definition: qgsmaplayer.h:82
bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue=QVariant(), bool skipDefaultValues=false)
Changes an attribute value for a feature (but does not immediately commit the changes).
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer&#39;s data provider, it may be nullptr.
QList< int > QgsAttributeList
Definition: qgsfield.h:27
bool nextFeature(QgsFeature &f)
static QString quotedValue(const QVariant &value)
Returns a string representation of a literal value, including appropriate quotations where required...
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Is an interface class to abstract feature selection handling.
Represents a vector layer which manages a vector based data sets.
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:262
QgsMapCanvas * mapCanvas() const
Returns the associated map canvas (e.g.
Allows modification of attribute values.
const QgsFeatureIds & selectedFeatureIds() const override
Returns reference to identifiers of selected features.
void setFeature(const QgsFeature &feature)
This widget is used to show the attributes of a set of features of a QgsVectorLayer.
Definition: qgsdualview.h:41
bool showLabel() const
Defines if a title label should be shown for this widget.
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.