QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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  Q_FOREACH ( const QgsRelation::FieldPair &fieldPair, mRelation.fieldPairs() )
360  {
361  int index = fields.indexOf( fieldPair.first );
362  linkAttributes.insert( index, mFeature.attribute( fieldPair.second ) );
363  }
364 
365  Q_FOREACH ( const QgsRelation::FieldPair &fieldPair, mNmRelation.fieldPairs() )
366  {
367  int index = fields.indexOf( fieldPair.first );
368  linkAttributes.insert( index, f.attribute( fieldPair.second ) );
369  }
370  QgsFeature linkFeature = QgsVectorLayerUtils::createFeature( mRelation.referencingLayer(), QgsGeometry(), linkAttributes, &context );
371 
372  mRelation.referencingLayer()->addFeature( linkFeature );
373 
374  updateUi();
375  }
376  }
377  else
378  {
379  QgsFields fields = mRelation.referencingLayer()->fields();
380 
381  Q_FOREACH ( const QgsRelation::FieldPair &fieldPair, mRelation.fieldPairs() )
382  {
383  keyAttrs.insert( fields.indexFromName( fieldPair.referencingField() ), mFeature.attribute( fieldPair.referencedField() ) );
384  }
385 
386  vlTools->addFeature( mDualView->masterModel()->layer(), keyAttrs );
387  }
388 }
389 
390 void QgsRelationEditorWidget::linkFeature()
391 {
392  QgsVectorLayer *layer = nullptr;
393 
394  if ( mNmRelation.isValid() )
395  layer = mNmRelation.referencedLayer();
396  else
397  layer = mRelation.referencingLayer();
398 
399  QgsFeatureSelectionDlg selectionDlg( layer, mEditorContext, this );
400 
401  if ( selectionDlg.exec() )
402  {
403  if ( mNmRelation.isValid() )
404  {
405  QgsFeatureIterator it = mNmRelation.referencedLayer()->getFeatures(
407  .setFilterFids( selectionDlg.selectedFeatures() )
408  .setSubsetOfAttributes( mNmRelation.referencedFields() ) );
409 
410  QgsFeature relatedFeature;
411 
412  QgsFeatureList newFeatures;
413 
414  // Fields of the linking table
415  const QgsFields fields = mRelation.referencingLayer()->fields();
416 
417  // Expression context for the linking table
419 
420  QgsAttributeMap linkAttributes;
421  Q_FOREACH ( const QgsRelation::FieldPair &fieldPair, mRelation.fieldPairs() )
422  {
423  int index = fields.indexOf( fieldPair.first );
424  linkAttributes.insert( index, mFeature.attribute( fieldPair.second ) );
425  }
426 
427  while ( it.nextFeature( relatedFeature ) )
428  {
429  Q_FOREACH ( const QgsRelation::FieldPair &fieldPair, mNmRelation.fieldPairs() )
430  {
431  int index = fields.indexOf( fieldPair.first );
432  linkAttributes.insert( index, relatedFeature.attribute( fieldPair.second ) );
433  }
434  const QgsFeature linkFeature = QgsVectorLayerUtils::createFeature( mRelation.referencingLayer(), QgsGeometry(), linkAttributes, &context );
435 
436  newFeatures << linkFeature;
437  }
438 
439  mRelation.referencingLayer()->addFeatures( newFeatures );
440  QgsFeatureIds ids;
441  Q_FOREACH ( const QgsFeature &f, newFeatures )
442  ids << f.id();
443  mRelation.referencingLayer()->selectByIds( ids );
444 
445 
446  updateUi();
447  }
448  else
449  {
450  QMap<int, QVariant> keys;
451  Q_FOREACH ( const QgsRelation::FieldPair &fieldPair, mRelation.fieldPairs() )
452  {
453  int idx = mRelation.referencingLayer()->fields().lookupField( fieldPair.referencingField() );
454  QVariant val = mFeature.attribute( fieldPair.referencedField() );
455  keys.insert( idx, val );
456  }
457 
458  Q_FOREACH ( QgsFeatureId fid, selectionDlg.selectedFeatures() )
459  {
460  QMapIterator<int, QVariant> it( keys );
461  while ( it.hasNext() )
462  {
463  it.next();
464  mRelation.referencingLayer()->changeAttributeValue( fid, it.key(), it.value() );
465  }
466  }
467  }
468  }
469 }
470 
471 void QgsRelationEditorWidget::duplicateFeature()
472 {
473  QgsVectorLayer *layer = mRelation.referencingLayer();
474 
475  QgsFeatureIterator fit = layer->getFeatures( QgsFeatureRequest().setFilterFids( mFeatureSelectionMgr->selectedFeatureIds() ) );
476  QgsFeature f;
477  while ( fit.nextFeature( f ) )
478  {
479  QgsVectorLayerUtils::QgsDuplicateFeatureContext duplicatedFeatureContext;
480  QgsVectorLayerUtils::duplicateFeature( layer, f, QgsProject::instance(), 0, duplicatedFeatureContext );
481  }
482 }
483 
484 void QgsRelationEditorWidget::deleteFeature( const QgsFeatureId featureid )
485 {
486  deleteFeatures( QgsFeatureIds() << featureid );
487 }
488 
489 void QgsRelationEditorWidget::deleteSelectedFeatures()
490 {
491  deleteFeatures( mFeatureSelectionMgr->selectedFeatureIds() );
492 }
493 
494 void QgsRelationEditorWidget::deleteFeatures( const QgsFeatureIds &featureids )
495 {
496  bool deleteFeatures = true;
497 
498  QgsVectorLayer *layer;
499  if ( mNmRelation.isValid() )
500  {
501  layer = mNmRelation.referencedLayer();
502 
503  // When deleting a linked feature within an N:M relation,
504  // check if the feature is linked to more than just one feature.
505  // In case it is linked more than just once, ask the user for confirmation
506  // as it is likely he was not aware of the implications and might either
507  // leave the dataset in a corrupted state (referential integrity) or if
508  // the fk constraint is ON CASCADE DELETE, there may be several linking
509  // entries deleted along.
510 
511  QgsFeatureRequest deletedFeaturesRequest;
512  deletedFeaturesRequest.setFilterFids( featureids );
513  deletedFeaturesRequest.setFlags( QgsFeatureRequest::NoGeometry );
514  deletedFeaturesRequest.setSubsetOfAttributes( QgsAttributeList() << mNmRelation.referencedFields().first() );
515 
516  QgsFeatureIterator deletedFeatures = layer->getFeatures( deletedFeaturesRequest );
517  QStringList deletedFeaturesPks;
518  QgsFeature feature;
519  while ( deletedFeatures.nextFeature( feature ) )
520  {
521  deletedFeaturesPks.append( QgsExpression::quotedValue( feature.attribute( mNmRelation.referencedFields().first() ) ) );
522  }
523 
524  QgsFeatureRequest linkingFeaturesRequest;
525  linkingFeaturesRequest.setFlags( QgsFeatureRequest::NoGeometry );
526  linkingFeaturesRequest.setNoAttributes();
527 
528  QString linkingFeaturesRequestExpression;
529  if ( !deletedFeaturesPks.empty() )
530  {
531  linkingFeaturesRequestExpression = QStringLiteral( "%1 IN (%2)" ).arg( QgsExpression::quotedColumnRef( mNmRelation.fieldPairs().first().first ), deletedFeaturesPks.join( ',' ) );
532  linkingFeaturesRequest.setFilterExpression( linkingFeaturesRequestExpression );
533 
534  QgsFeatureIterator relatedLinkingFeatures = mNmRelation.referencingLayer()->getFeatures( linkingFeaturesRequest );
535 
536  int relatedLinkingFeaturesCount = 0;
537  while ( relatedLinkingFeatures.nextFeature( feature ) )
538  {
539  relatedLinkingFeaturesCount++;
540  }
541 
542  if ( deletedFeaturesPks.size() == 1 && relatedLinkingFeaturesCount > 1 )
543  {
544  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 );
545  messageBox.addButton( QMessageBox::Cancel );
546  QAbstractButton *deleteButton = messageBox.addButton( tr( "Delete" ), QMessageBox::AcceptRole );
547 
548  messageBox.exec();
549  if ( messageBox.clickedButton() != deleteButton )
550  deleteFeatures = false;
551  }
552  else if ( deletedFeaturesPks.size() > 1 && relatedLinkingFeaturesCount > deletedFeaturesPks.size() )
553  {
554  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 );
555  messageBox.addButton( QMessageBox::Cancel );
556  QAbstractButton *deleteButton = messageBox.addButton( tr( "Delete" ), QMessageBox::AcceptRole );
557 
558  messageBox.exec();
559  if ( messageBox.clickedButton() != deleteButton )
560  deleteFeatures = false;
561  }
562  }
563  }
564  else
565  {
566  layer = mRelation.referencingLayer();
567  }
568 
569  if ( deleteFeatures )
570  {
571  layer->deleteFeatures( featureids );
572  updateUi();
573  }
574 }
575 
576 void QgsRelationEditorWidget::unlinkFeature( const QgsFeatureId featureid )
577 {
578  unlinkFeatures( QgsFeatureIds() << featureid );
579 }
580 
581 void QgsRelationEditorWidget::unlinkSelectedFeatures()
582 {
583  unlinkFeatures( mFeatureSelectionMgr->selectedFeatureIds() );
584 }
585 
586 void QgsRelationEditorWidget::zoomToSelectedFeatures()
587 {
588  QgsMapCanvas *c = mEditorContext.mapCanvas();
589  if ( !c )
590  return;
591 
592  c->zoomToFeatureIds(
593  mNmRelation.isValid() ? mNmRelation.referencedLayer() : mRelation.referencingLayer(),
594  mFeatureSelectionMgr->selectedFeatureIds()
595  );
596 }
597 
598 void QgsRelationEditorWidget::unlinkFeatures( const QgsFeatureIds &featureids )
599 {
600  if ( mNmRelation.isValid() )
601  {
602  QgsFeatureIterator selectedIterator = mNmRelation.referencedLayer()->getFeatures(
604  .setFilterFids( featureids )
605  .setSubsetOfAttributes( mNmRelation.referencedFields() ) );
606 
607  QgsFeature f;
608 
609  QStringList filters;
610 
611  while ( selectedIterator.nextFeature( f ) )
612  {
613  filters << '(' + mNmRelation.getRelatedFeaturesRequest( f ).filterExpression()->expression() + ')';
614  }
615 
616  QString filter = QStringLiteral( "(%1) AND (%2)" ).arg(
617  mRelation.getRelatedFeaturesRequest( mFeature ).filterExpression()->expression(),
618  filters.join( QStringLiteral( " OR " ) ) );
619 
620  QgsFeatureIterator linkedIterator = mRelation.referencingLayer()->getFeatures( QgsFeatureRequest()
621  .setNoAttributes()
622  .setFilterExpression( filter ) );
623 
624  QgsFeatureIds fids;
625 
626  while ( linkedIterator.nextFeature( f ) )
627  {
628  fids << f.id();
629  QgsDebugMsgLevel( FID_TO_STRING( f.id() ), 4 );
630  }
631 
632  mRelation.referencingLayer()->deleteFeatures( fids );
633 
634  updateUi();
635  }
636  else
637  {
638  QMap<int, QgsField> keyFields;
639  Q_FOREACH ( const QgsRelation::FieldPair &fieldPair, mRelation.fieldPairs() )
640  {
641  int idx = mRelation.referencingLayer()->fields().lookupField( fieldPair.referencingField() );
642  if ( idx < 0 )
643  {
644  QgsDebugMsg( QStringLiteral( "referencing field %1 not found" ).arg( fieldPair.referencingField() ) );
645  return;
646  }
647  QgsField fld = mRelation.referencingLayer()->fields().at( idx );
648  keyFields.insert( idx, fld );
649  }
650 
651  Q_FOREACH ( QgsFeatureId fid, featureids )
652  {
653  QMapIterator<int, QgsField> it( keyFields );
654  while ( it.hasNext() )
655  {
656  it.next();
657  mRelation.referencingLayer()->changeAttributeValue( fid, it.key(), QVariant( it.value().type() ) );
658  }
659  }
660  }
661 }
662 
663 void QgsRelationEditorWidget::toggleEditing( bool state )
664 {
665  if ( state )
666  {
667  mEditorContext.vectorLayerTools()->startEditing( mRelation.referencingLayer() );
668  if ( mNmRelation.isValid() )
669  mEditorContext.vectorLayerTools()->startEditing( mNmRelation.referencedLayer() );
670  }
671  else
672  {
673  mEditorContext.vectorLayerTools()->stopEditing( mRelation.referencingLayer() );
674  if ( mNmRelation.isValid() )
675  mEditorContext.vectorLayerTools()->stopEditing( mNmRelation.referencedLayer() );
676  }
677 }
678 
679 void QgsRelationEditorWidget::saveEdits()
680 {
681  mEditorContext.vectorLayerTools()->saveEdits( mRelation.referencingLayer() );
682  if ( mNmRelation.isValid() )
683  mEditorContext.vectorLayerTools()->saveEdits( mNmRelation.referencedLayer() );
684 }
685 
686 void QgsRelationEditorWidget::onCollapsedStateChanged( bool collapsed )
687 {
688  if ( !collapsed )
689  {
690  mVisible = true;
691  updateUi();
692  }
693 }
694 
695 void QgsRelationEditorWidget::updateUi()
696 {
697  // If not yet initialized, it is not (yet) visible, so we don't load it to be faster (lazy loading)
698  // If it is already initialized, it has been set visible before and the currently shown feature is changing
699  // and the widget needs updating
700 
701  if ( mVisible )
702  {
703  QgsFeatureRequest myRequest = mRelation.getRelatedFeaturesRequest( mFeature );
704 
705  if ( mNmRelation.isValid() )
706  {
707  QgsFeatureIterator it = mRelation.referencingLayer()->getFeatures( myRequest );
708 
709  QgsFeature fet;
710 
711  QStringList filters;
712 
713  while ( it.nextFeature( fet ) )
714  {
715  QString filter = mNmRelation.getReferencedFeatureRequest( fet ).filterExpression()->expression();
716  filters << filter.prepend( '(' ).append( ')' );
717  }
718 
719  QgsFeatureRequest nmRequest;
720 
721  nmRequest.setFilterExpression( filters.join( QStringLiteral( " OR " ) ) );
722 
723  mDualView->init( mNmRelation.referencedLayer(), nullptr, nmRequest, mEditorContext );
724  }
725  else
726  {
727  mDualView->init( mRelation.referencingLayer(), nullptr, myRequest, mEditorContext );
728  }
729  }
730 }
731 
733 {
734  return mLinkFeatureButton->isVisible();
735 }
736 
738 {
739  mLinkFeatureButton->setVisible( showLinkButton );
740 }
741 
743 {
744  return mUnlinkFeatureButton->isVisible();
745 }
746 
748 {
749  mUnlinkFeatureButton->setVisible( showUnlinkButton );
750 }
751 
753 {
754  return mShowLabel;
755 }
756 
758 {
759  mShowLabel = showLabel;
760 
761  if ( mShowLabel && mRelation.isValid() )
762  setTitle( mRelation.name() );
763  else
764  setTitle( QString() );
765 }
766 
767 void QgsRelationEditorWidget::showContextMenu( QgsActionMenu *menu, const QgsFeatureId fid )
768 {
769  if ( mRelation.referencingLayer()->isEditable() )
770  {
771  QAction *qAction = nullptr;
772 
773  qAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteSelected.svg" ) ), tr( "Delete Feature" ) );
774  connect( qAction, &QAction::triggered, this, [this, fid]() { deleteFeature( fid ); } );
775 
776  qAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionUnlink.svg" ) ), tr( "Unlink Feature" ) );
777  connect( qAction, &QAction::triggered, this, [this, fid]() { unlinkFeature( fid ); } );
778  }
779 }
Methods in this class are used to handle basic operations on vector layers.
QString name
Definition: qgsrelation.h:48
QgsFeatureId id
Definition: qgsfeature.h:64
Wrapper for iterator of features from vector data provider or vector layer.
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:62
bool collapsed
The collapsed state of this group box.
QString referencedField() const
Gets the name of the referenced (parent) field.
Definition: qgsrelation.h:87
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.
void setFeatureSelectionManager(QgsIFeatureSelectionManager *featureSelectionManager)
Set the feature selection model.
bool showLinkButton() const
Determines if the "link feature" button should be shown.
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:53
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.
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:106
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
void selectByIds(const QgsFeatureIds &ids, SelectBehavior behavior=SetSelection)
Select matching features using a list of feature IDs.
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)
This signal is 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
QgsAttributeList referencedFields() const
Returns a list of attributes used to form the referenced fields (most likely primary key) on the refe...
void setViewMode(QgsDualView::ViewMode mode)
Define the view mode for the dual view.
QgsFeatureRequest getReferencedFeatureRequest(const QgsAttributes &attributes) const
Creates a request to return the feature on the referenced (parent) layer which is referenced by the p...
QgsField at(int i) const
Gets field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:163
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:74
void setView(ViewMode view)
Change the current view mode.
bool showUnlinkButton() const
Determines if the "unlink feature" button should be shown.
const QgsFeatureIds & selectedFeatures()
Gets the selected features.
Show a list of the features, where one can be chosen and the according attribute dialog will be prese...
Definition: qgsdualview.h:66
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.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
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:59
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
void setShowUnlinkButton(bool showUnlinkButton)
Determines if the "unlink feature" button should be shown.
QString referencingField() const
Gets the name of the referencing (child) field.
Definition: qgsrelation.h:85
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()
Is emitted, when edited changes successfully have been 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
int lookupField(const QString &fieldName) const
Looks up field&#39;s index from the field name.
Definition: qgsfields.cpp:320
int indexOf(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:207
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()
Is 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...
bool showLabel() const
Defines if a title label should be shown for this widget.
int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:202
This selection manager synchronizes a local set of selected features with an attribute table...
QgsAttributeTableModel * masterModel() const
Returns the model which has the information about all features (not only filtered) ...
Definition: qgsdualview.h:161
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
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:262
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets feature IDs that should be fetched.
#define FID_TO_STRING(fid)
Definition: qgsfeatureid.h:30
virtual QgsVectorDataProvider::Capabilities capabilities() const
Returns flags containing the supported capabilities.
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:411
QgsMapCanvas * mapCanvas() const
Returns the associated map canvas (e.g.
QgsVectorLayer * layer() const
Returns the layer this model uses as backend.
void setEditorContext(const QgsAttributeEditorContext &context)
const QgsVectorLayerTools * vectorLayerTools() const
Returns the associated vector layer tools.
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.
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.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Query the layer for features specified in request.
QString name
Definition: qgsmaplayer.h:67
QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
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.
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.
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:42
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
QgsExpression * filterExpression() const
Returns the filter expression if set.