QGIS API Documentation  3.2.0-Bonn (bc43194)
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 
38  : QgsCollapsibleGroupBox( parent )
39 {
40  QVBoxLayout *topLayout = new QVBoxLayout( this );
41  topLayout->setContentsMargins( 0, 9, 0, 0 );
42  setLayout( topLayout );
43 
44  // buttons
45  QHBoxLayout *buttonLayout = new QHBoxLayout();
46  buttonLayout->setContentsMargins( 0, 0, 0, 0 );
47  // toogle editing
48  mToggleEditingButton = new QToolButton( this );
49  mToggleEditingButton->setObjectName( QStringLiteral( "mToggleEditingButton" ) );
50  mToggleEditingButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) );
51  mToggleEditingButton->setText( tr( "Toggle editing" ) );
52  mToggleEditingButton->setEnabled( false );
53  mToggleEditingButton->setCheckable( true );
54  mToggleEditingButton->setToolTip( tr( "Toggle editing mode for child layer" ) );
55  buttonLayout->addWidget( mToggleEditingButton );
56  // save Edits
57  mSaveEditsButton = new QToolButton( this );
58  mSaveEditsButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveEdits.svg" ) ) );
59  mSaveEditsButton->setText( tr( "Save child layer edits" ) );
60  mSaveEditsButton->setToolTip( tr( "Save child layer edits" ) );
61  mSaveEditsButton->setEnabled( true );
62  buttonLayout->addWidget( mSaveEditsButton );
63  // add feature
64  mAddFeatureButton = new QToolButton( this );
65  mAddFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewTableRow.svg" ) ) );
66  mAddFeatureButton->setText( tr( "Add child feature" ) );
67  mAddFeatureButton->setToolTip( tr( "Add child feature" ) );
68  mAddFeatureButton->setObjectName( QStringLiteral( "mAddFeatureButton" ) );
69  buttonLayout->addWidget( mAddFeatureButton );
70  // duplicate feature
71  mDuplicateFeatureButton = new QToolButton( this );
72  mDuplicateFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDuplicateFeature.svg" ) ) );
73  mDuplicateFeatureButton->setText( tr( "Duplicate child feature" ) );
74  mDuplicateFeatureButton->setToolTip( tr( "Duplicate child feature" ) );
75  mDuplicateFeatureButton->setObjectName( QStringLiteral( "mDuplicateFeatureButton" ) );
76  buttonLayout->addWidget( mDuplicateFeatureButton );
77  // delete feature
78  mDeleteFeatureButton = new QToolButton( this );
79  mDeleteFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteSelected.svg" ) ) );
80  mDeleteFeatureButton->setText( tr( "Delete child feature" ) );
81  mDeleteFeatureButton->setToolTip( tr( "Delete child feature" ) );
82  mDeleteFeatureButton->setObjectName( QStringLiteral( "mDeleteFeatureButton" ) );
83  buttonLayout->addWidget( mDeleteFeatureButton );
84  // link feature
85  mLinkFeatureButton = new QToolButton( this );
86  mLinkFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionLink.svg" ) ) );
87  mLinkFeatureButton->setText( tr( "Link existing features" ) );
88  mLinkFeatureButton->setToolTip( tr( "Link existing child features" ) );
89  mLinkFeatureButton->setObjectName( QStringLiteral( "mLinkFeatureButton" ) );
90  buttonLayout->addWidget( mLinkFeatureButton );
91  // unlink feature
92  mUnlinkFeatureButton = new QToolButton( this );
93  mUnlinkFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionUnlink.svg" ) ) );
94  mUnlinkFeatureButton->setText( tr( "Unlink feature" ) );
95  mUnlinkFeatureButton->setToolTip( tr( "Unlink child feature" ) );
96  mUnlinkFeatureButton->setObjectName( QStringLiteral( "mUnlinkFeatureButton" ) );
97  buttonLayout->addWidget( mUnlinkFeatureButton );
98  // zoom to linked feature
99  mZoomToFeatureButton = new QToolButton( this );
100  mZoomToFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomToSelected.svg" ) ) );
101  mZoomToFeatureButton->setText( tr( "Zoom To Feature" ) );
102  mZoomToFeatureButton->setToolTip( tr( "Zoom to child feature" ) );
103  mZoomToFeatureButton->setObjectName( QStringLiteral( "mZoomToFeatureButton" ) );
104  buttonLayout->addWidget( mZoomToFeatureButton );
105  // spacer
106  buttonLayout->addItem( new QSpacerItem( 0, 0, QSizePolicy::Expanding ) );
107  // form view
108  mFormViewButton = new QToolButton( this );
109  mFormViewButton->setText( tr( "Form view" ) );
110  mFormViewButton->setToolTip( tr( "Switch to form view" ) );
111  mFormViewButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPropertyItem.svg" ) ) );
112  mFormViewButton->setCheckable( true );
113  mFormViewButton->setChecked( mViewMode == QgsDualView::AttributeEditor );
114  buttonLayout->addWidget( mFormViewButton );
115  // table view
116  mTableViewButton = new QToolButton( this );
117  mTableViewButton->setText( tr( "Table view" ) );
118  mTableViewButton->setToolTip( tr( "Switch to table view" ) );
119  mTableViewButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOpenTable.svg" ) ) );
120  mTableViewButton->setCheckable( true );
121  mTableViewButton->setChecked( mViewMode == QgsDualView::AttributeTable );
122  buttonLayout->addWidget( mTableViewButton );
123  // button group
124  mViewModeButtonGroup = new QButtonGroup( this );
125  mViewModeButtonGroup->addButton( mFormViewButton, QgsDualView::AttributeEditor );
126  mViewModeButtonGroup->addButton( mTableViewButton, QgsDualView::AttributeTable );
127 
128  // add buttons layout
129  topLayout->addLayout( buttonLayout );
130 
131  mRelationLayout = new QGridLayout();
132  mRelationLayout->setContentsMargins( 0, 0, 0, 0 );
133  topLayout->addLayout( mRelationLayout );
134 
135  mDualView = new QgsDualView( this );
136  mDualView->setView( mViewMode );
137  mFeatureSelectionMgr = new QgsGenericFeatureSelectionManager( mDualView );
138  mDualView->setFeatureSelectionManager( mFeatureSelectionMgr );
139 
140  mRelationLayout->addWidget( mDualView );
141 
142  connect( this, &QgsCollapsibleGroupBoxBasic::collapsedStateChanged, this, &QgsRelationEditorWidget::onCollapsedStateChanged );
143  connect( mViewModeButtonGroup, static_cast<void ( QButtonGroup::* )( int )>( &QButtonGroup::buttonClicked ),
144  this, static_cast<void ( QgsRelationEditorWidget::* )( int )>( &QgsRelationEditorWidget::setViewMode ) );
145  connect( mToggleEditingButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::toggleEditing );
146  connect( mSaveEditsButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::saveEdits );
147  connect( mAddFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::addFeature );
148  connect( mDuplicateFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::duplicateFeature );
149  connect( mDeleteFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::deleteSelectedFeatures );
150  connect( mLinkFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::linkFeature );
151  connect( mUnlinkFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::unlinkSelectedFeatures );
152  connect( mZoomToFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::zoomToSelectedFeatures );
153  connect( mFeatureSelectionMgr, &QgsIFeatureSelectionManager::selectionChanged, this, &QgsRelationEditorWidget::updateButtons );
154 
155  connect( mDualView, &QgsDualView::showContextMenuExternally, this, &QgsRelationEditorWidget::showContextMenu );
156 
157  // Set initial state for add/remove etc. buttons
158  updateButtons();
159 }
160 
162 {
163  if ( mRelation.isValid() )
164  {
165  disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
166  disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
167  }
168 
169  mRelation = relation;
170  mFeature = feature;
171 
172  connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
173  connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
174 
175  if ( mShowLabel )
176  setTitle( relation.name() );
177 
178  QgsVectorLayer *lyr = relation.referencingLayer();
179 
180  bool canChangeAttributes = lyr->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeAttributeValues;
181  if ( canChangeAttributes && !lyr->readOnly() )
182  {
183  mToggleEditingButton->setEnabled( true );
184  updateButtons();
185  }
186  else
187  {
188  mToggleEditingButton->setEnabled( false );
189  }
190 
191  setObjectName( QStringLiteral( "referenced/" ) + mRelation.name() );
192 
193  // If not yet initialized, it is not (yet) visible, so we don't load it to be faster (lazy loading)
194  // If it is already initialized, it has been set visible before and the currently shown feature is changing
195  // and the widget needs updating
196 
197  if ( mVisible )
198  {
199  QgsFeatureRequest myRequest = mRelation.getRelatedFeaturesRequest( mFeature );
200  mDualView->init( mRelation.referencingLayer(), nullptr, myRequest, mEditorContext );
201  }
202 }
203 
204 void QgsRelationEditorWidget::setRelations( const QgsRelation &relation, const QgsRelation &nmrelation )
205 {
206  if ( mRelation.isValid() )
207  {
208  disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
209  disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
210  }
211 
212  if ( mNmRelation.isValid() )
213  {
214  disconnect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
215  disconnect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
216  }
217 
218  mRelation = relation;
219  mNmRelation = nmrelation;
220 
221  if ( !mRelation.isValid() )
222  return;
223 
224  mToggleEditingButton->setVisible( true );
225 
226  const auto transactionGroups = QgsProject::instance()->transactionGroups();
227  for ( auto it = transactionGroups.constBegin(); it != transactionGroups.constEnd(); ++it )
228  {
229  if ( it.value()->layers().contains( mRelation.referencingLayer() ) )
230  {
231  mToggleEditingButton->setVisible( false );
232  mSaveEditsButton->setVisible( false );
233  }
234  }
235 
236  connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
237  connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
238 
239  if ( mNmRelation.isValid() )
240  {
241  connect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons );
242  connect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons );
243  }
244 
245  setTitle( relation.name() );
246 
247  QgsVectorLayer *lyr = relation.referencingLayer();
248 
249  bool canChangeAttributes = lyr->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeAttributeValues;
250  if ( canChangeAttributes && !lyr->readOnly() )
251  {
252  mToggleEditingButton->setEnabled( true );
253  updateButtons();
254  }
255  else
256  {
257  mToggleEditingButton->setEnabled( false );
258  }
259 
260  setObjectName( QStringLiteral( "referenced/" ) + mRelation.name() );
261 
262  updateUi();
263 }
264 
266 {
267  mEditorContext = context;
268 }
269 
271 {
272  return mFeatureSelectionMgr;
273 }
274 
276 {
277  mDualView->setView( mode );
278  mViewMode = mode;
279 }
280 
282 {
283  mFeature = feature;
284 
285  updateUi();
286 }
287 
288 void QgsRelationEditorWidget::updateButtons()
289 {
290  bool editable = false;
291  bool linkable = false;
292  bool selectionNotEmpty = mFeatureSelectionMgr->selectedFeatureCount();
293 
294  if ( mRelation.isValid() )
295  {
296  editable = mRelation.referencingLayer()->isEditable();
297  linkable = mRelation.referencingLayer()->isEditable();
298  }
299 
300  if ( mNmRelation.isValid() )
301  {
302  editable = mNmRelation.referencedLayer()->isEditable();
303  }
304 
305  mAddFeatureButton->setEnabled( editable );
306  mDuplicateFeatureButton->setEnabled( editable && selectionNotEmpty );
307  mLinkFeatureButton->setEnabled( linkable );
308  mDeleteFeatureButton->setEnabled( editable && selectionNotEmpty );
309  mUnlinkFeatureButton->setEnabled( linkable && selectionNotEmpty );
310 
311  mZoomToFeatureButton->setVisible(
312  mEditorContext.mapCanvas() && (
313  (
314  mNmRelation.isValid() &&
317  )
318  ||
319  (
320  mRelation.isValid() &&
323  )
324  )
325  );
326 
327  mZoomToFeatureButton->setEnabled( selectionNotEmpty );
328 
329  mToggleEditingButton->setChecked( editable );
330  mSaveEditsButton->setEnabled( editable );
331 }
332 
333 void QgsRelationEditorWidget::addFeature()
334 {
335  QgsAttributeMap keyAttrs;
336 
337  const QgsVectorLayerTools *vlTools = mEditorContext.vectorLayerTools();
338 
339  if ( mNmRelation.isValid() )
340  {
341  // n:m Relation: first let the user create a new feature on the other table
342  // and autocreate a new linking feature.
343  QgsFeature f;
344  if ( vlTools->addFeature( mNmRelation.referencedLayer(), QgsAttributeMap(), QgsGeometry(), &f ) )
345  {
346  // Fields of the linking table
347  const QgsFields fields = mRelation.referencingLayer()->fields();
348 
349  // Expression context for the linking table
351 
352  QgsAttributeMap linkAttributes;
353  Q_FOREACH ( const QgsRelation::FieldPair &fieldPair, mRelation.fieldPairs() )
354  {
355  int index = fields.indexOf( fieldPair.first );
356  linkAttributes.insert( index, mFeature.attribute( fieldPair.second ) );
357  }
358 
359  Q_FOREACH ( const QgsRelation::FieldPair &fieldPair, mNmRelation.fieldPairs() )
360  {
361  int index = fields.indexOf( fieldPair.first );
362  linkAttributes.insert( index, f.attribute( fieldPair.second ) );
363  }
364  QgsFeature linkFeature = QgsVectorLayerUtils::createFeature( mRelation.referencingLayer(), QgsGeometry(), linkAttributes, &context );
365 
366  mRelation.referencingLayer()->addFeature( linkFeature );
367 
368  updateUi();
369  }
370  }
371  else
372  {
373  QgsFields fields = mRelation.referencingLayer()->fields();
374 
375  Q_FOREACH ( const QgsRelation::FieldPair &fieldPair, mRelation.fieldPairs() )
376  {
377  keyAttrs.insert( fields.indexFromName( fieldPair.referencingField() ), mFeature.attribute( fieldPair.referencedField() ) );
378  }
379 
380  vlTools->addFeature( mDualView->masterModel()->layer(), keyAttrs );
381  }
382 }
383 
384 void QgsRelationEditorWidget::linkFeature()
385 {
386  QgsVectorLayer *layer = nullptr;
387 
388  if ( mNmRelation.isValid() )
389  layer = mNmRelation.referencedLayer();
390  else
391  layer = mRelation.referencingLayer();
392 
393  QgsFeatureSelectionDlg selectionDlg( layer, mEditorContext, this );
394 
395  if ( selectionDlg.exec() )
396  {
397  if ( mNmRelation.isValid() )
398  {
399  QgsFeatureIterator it = mNmRelation.referencedLayer()->getFeatures(
401  .setFilterFids( selectionDlg.selectedFeatures() )
402  .setSubsetOfAttributes( mNmRelation.referencedFields() ) );
403 
404  QgsFeature relatedFeature;
405 
406  QgsFeatureList newFeatures;
407 
408  // Fields of the linking table
409  const QgsFields fields = mRelation.referencingLayer()->fields();
410 
411  // Expression context for the linking table
413 
414  QgsAttributeMap linkAttributes;
415  Q_FOREACH ( const QgsRelation::FieldPair &fieldPair, mRelation.fieldPairs() )
416  {
417  int index = fields.indexOf( fieldPair.first );
418  linkAttributes.insert( index, mFeature.attribute( fieldPair.second ) );
419  }
420 
421  while ( it.nextFeature( relatedFeature ) )
422  {
423  Q_FOREACH ( const QgsRelation::FieldPair &fieldPair, mNmRelation.fieldPairs() )
424  {
425  int index = fields.indexOf( fieldPair.first );
426  linkAttributes.insert( index, relatedFeature.attribute( fieldPair.second ) );
427  }
428  const QgsFeature linkFeature = QgsVectorLayerUtils::createFeature( mRelation.referencingLayer(), QgsGeometry(), linkAttributes, &context );
429 
430  newFeatures << linkFeature;
431  }
432 
433  mRelation.referencingLayer()->addFeatures( newFeatures );
434  QgsFeatureIds ids;
435  Q_FOREACH ( const QgsFeature &f, newFeatures )
436  ids << f.id();
437  mRelation.referencingLayer()->selectByIds( ids );
438 
439 
440  updateUi();
441  }
442  else
443  {
444  QMap<int, QVariant> keys;
445  Q_FOREACH ( const QgsRelation::FieldPair &fieldPair, mRelation.fieldPairs() )
446  {
447  int idx = mRelation.referencingLayer()->fields().lookupField( fieldPair.referencingField() );
448  QVariant val = mFeature.attribute( fieldPair.referencedField() );
449  keys.insert( idx, val );
450  }
451 
452  Q_FOREACH ( QgsFeatureId fid, selectionDlg.selectedFeatures() )
453  {
454  QMapIterator<int, QVariant> it( keys );
455  while ( it.hasNext() )
456  {
457  it.next();
458  mRelation.referencingLayer()->changeAttributeValue( fid, it.key(), it.value() );
459  }
460  }
461  }
462  }
463 }
464 
465 void QgsRelationEditorWidget::duplicateFeature()
466 {
467  QgsVectorLayer *layer = mRelation.referencingLayer();
468 
469  QgsFeatureIterator fit = layer->getFeatures( QgsFeatureRequest().setFilterFids( mFeatureSelectionMgr->selectedFeatureIds() ) );
470  QgsFeature f;
471  while ( fit.nextFeature( f ) )
472  {
473  QgsVectorLayerUtils::QgsDuplicateFeatureContext duplicatedFeatureContext;
474  QgsVectorLayerUtils::duplicateFeature( layer, f, QgsProject::instance(), 0, duplicatedFeatureContext );
475  }
476 }
477 
478 void QgsRelationEditorWidget::deleteFeature( const QgsFeatureId featureid )
479 {
480  deleteFeatures( QgsFeatureIds() << featureid );
481 }
482 
483 void QgsRelationEditorWidget::deleteSelectedFeatures()
484 {
485  deleteFeatures( mFeatureSelectionMgr->selectedFeatureIds() );
486 }
487 
488 void QgsRelationEditorWidget::deleteFeatures( const QgsFeatureIds &featureids )
489 {
490  QgsVectorLayer *layer = mNmRelation.isValid() ? mNmRelation.referencedLayer() : mRelation.referencingLayer();
491  layer->deleteFeatures( featureids );
492  updateUi();
493 }
494 
495 void QgsRelationEditorWidget::unlinkFeature( const QgsFeatureId featureid )
496 {
497  unlinkFeatures( QgsFeatureIds() << featureid );
498 }
499 
500 void QgsRelationEditorWidget::unlinkSelectedFeatures()
501 {
502  unlinkFeatures( mFeatureSelectionMgr->selectedFeatureIds() );
503 }
504 
505 void QgsRelationEditorWidget::zoomToSelectedFeatures()
506 {
507  QgsMapCanvas *c = mEditorContext.mapCanvas();
508  if ( !c )
509  return;
510 
511  c->zoomToFeatureIds(
512  mNmRelation.isValid() ? mNmRelation.referencedLayer() : mRelation.referencingLayer(),
513  mFeatureSelectionMgr->selectedFeatureIds()
514  );
515 }
516 
517 void QgsRelationEditorWidget::unlinkFeatures( const QgsFeatureIds &featureids )
518 {
519  if ( mNmRelation.isValid() )
520  {
521  QgsFeatureIterator selectedIterator = mNmRelation.referencedLayer()->getFeatures(
523  .setFilterFids( featureids )
524  .setSubsetOfAttributes( mNmRelation.referencedFields() ) );
525 
526  QgsFeature f;
527 
528  QStringList filters;
529 
530  while ( selectedIterator.nextFeature( f ) )
531  {
532  filters << '(' + mNmRelation.getRelatedFeaturesRequest( f ).filterExpression()->expression() + ')';
533  }
534 
535  QString filter = QStringLiteral( "(%1) AND (%2)" ).arg(
536  mRelation.getRelatedFeaturesRequest( mFeature ).filterExpression()->expression(),
537  filters.join( QStringLiteral( " OR " ) ) );
538 
539  QgsFeatureIterator linkedIterator = mRelation.referencingLayer()->getFeatures( QgsFeatureRequest()
540  .setSubsetOfAttributes( QgsAttributeList() )
541  .setFilterExpression( filter ) );
542 
543  QgsFeatureIds fids;
544 
545  while ( linkedIterator.nextFeature( f ) )
546  {
547  fids << f.id();
548  QgsDebugMsgLevel( FID_TO_STRING( f.id() ), 4 );
549  }
550 
551  mRelation.referencingLayer()->deleteFeatures( fids );
552 
553  updateUi();
554  }
555  else
556  {
557  QMap<int, QgsField> keyFields;
558  Q_FOREACH ( const QgsRelation::FieldPair &fieldPair, mRelation.fieldPairs() )
559  {
560  int idx = mRelation.referencingLayer()->fields().lookupField( fieldPair.referencingField() );
561  if ( idx < 0 )
562  {
563  QgsDebugMsg( QString( "referencing field %1 not found" ).arg( fieldPair.referencingField() ) );
564  return;
565  }
566  QgsField fld = mRelation.referencingLayer()->fields().at( idx );
567  keyFields.insert( idx, fld );
568  }
569 
570  Q_FOREACH ( QgsFeatureId fid, featureids )
571  {
572  QMapIterator<int, QgsField> it( keyFields );
573  while ( it.hasNext() )
574  {
575  it.next();
576  mRelation.referencingLayer()->changeAttributeValue( fid, it.key(), QVariant( it.value().type() ) );
577  }
578  }
579  }
580 }
581 
582 void QgsRelationEditorWidget::toggleEditing( bool state )
583 {
584  if ( state )
585  {
586  mEditorContext.vectorLayerTools()->startEditing( mRelation.referencingLayer() );
587  if ( mNmRelation.isValid() )
588  mEditorContext.vectorLayerTools()->startEditing( mNmRelation.referencedLayer() );
589  }
590  else
591  {
592  mEditorContext.vectorLayerTools()->stopEditing( mRelation.referencingLayer() );
593  if ( mNmRelation.isValid() )
594  mEditorContext.vectorLayerTools()->stopEditing( mNmRelation.referencedLayer() );
595  }
596 }
597 
598 void QgsRelationEditorWidget::saveEdits()
599 {
600  mEditorContext.vectorLayerTools()->saveEdits( mRelation.referencingLayer() );
601  if ( mNmRelation.isValid() )
602  mEditorContext.vectorLayerTools()->saveEdits( mNmRelation.referencedLayer() );
603 }
604 
605 void QgsRelationEditorWidget::onCollapsedStateChanged( bool collapsed )
606 {
607  if ( !collapsed )
608  {
609  mVisible = true;
610  updateUi();
611  }
612 }
613 
614 void QgsRelationEditorWidget::updateUi()
615 {
616  // If not yet initialized, it is not (yet) visible, so we don't load it to be faster (lazy loading)
617  // If it is already initialized, it has been set visible before and the currently shown feature is changing
618  // and the widget needs updating
619 
620  if ( mVisible )
621  {
622  QgsFeatureRequest myRequest = mRelation.getRelatedFeaturesRequest( mFeature );
623 
624  if ( mNmRelation.isValid() )
625  {
626  QgsFeatureIterator it = mRelation.referencingLayer()->getFeatures( myRequest );
627 
628  QgsFeature fet;
629 
630  QStringList filters;
631 
632  while ( it.nextFeature( fet ) )
633  {
634  QString filter = mNmRelation.getReferencedFeatureRequest( fet ).filterExpression()->expression();
635  filters << filter.prepend( '(' ).append( ')' );
636  }
637 
638  QgsFeatureRequest nmRequest;
639 
640  nmRequest.setFilterExpression( filters.join( QStringLiteral( " OR " ) ) );
641 
642  mDualView->init( mNmRelation.referencedLayer(), nullptr, nmRequest, mEditorContext );
643  }
644  else
645  {
646  mDualView->init( mRelation.referencingLayer(), nullptr, myRequest, mEditorContext );
647  }
648  }
649 }
650 
652 {
653  return mLinkFeatureButton->isVisible();
654 }
655 
657 {
658  mLinkFeatureButton->setVisible( showLinkButton );
659 }
660 
662 {
663  return mUnlinkFeatureButton->isVisible();
664 }
665 
667 {
668  mUnlinkFeatureButton->setVisible( showUnlinkButton );
669 }
670 
672 {
673  return mShowLabel;
674 }
675 
677 {
678  mShowLabel = showLabel;
679 
680  if ( mShowLabel && mRelation.isValid() )
681  setTitle( mRelation.name() );
682  else
683  setTitle( QString() );
684 }
685 
686 void QgsRelationEditorWidget::showContextMenu( QgsActionMenu *menu, const QgsFeatureId fid )
687 {
688  if ( mRelation.referencingLayer()->isEditable() )
689  {
690  QAction *qAction = nullptr;
691 
692  qAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteSelected.svg" ) ), tr( "Delete Feature" ) );
693  connect( qAction, &QAction::triggered, this, [this, fid]() { deleteFeature( fid ); } );
694 
695  qAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionUnlink.svg" ) ), tr( "Unlink Feature" ) );
696  connect( qAction, &QAction::triggered, this, [this, fid]() { unlinkFeature( fid ); } );
697  }
698 }
int lookupField(const QString &fieldName) const
Look up field&#39;s index from the field name.
Definition: qgsfields.cpp:299
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:45
QgsFeatureId id
Definition: qgsfeature.h:71
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...
static QgsFeature createFeature(QgsVectorLayer *layer, const QgsGeometry &geometry=QgsGeometry(), const QgsAttributeMap &attributes=QgsAttributeMap(), QgsExpressionContext *context=nullptr)
Creates a new feature ready for insertion into a layer.
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.
virtual QgsVectorDataProvider::Capabilities capabilities() const
Returns flags containing the supported capabilities.
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:162
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
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeature.h:544
This class contains context information for attribute editor widgets.
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:549
QgsVectorLayer referencingLayer
Definition: qgsrelation.h:43
ViewMode
The view modes, in which this widget can present information.
Definition: qgsdualview.h:54
void collapsedStateChanged(bool collapsed)
Signal emitted when groupbox collapsed/expanded state is changed, and when first shown.
#define FID_TO_STRING(fid)
Definition: qgsfeature.h:52
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:104
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.
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)
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:62
void setViewMode(QgsDualView::ViewMode mode)
Define the view mode for the dual view.
bool isEditable() const override
Returns true if the provider is in editing mode.
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.
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:145
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:67
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
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:184
QgsFeatureRequest getReferencedFeatureRequest(const QgsAttributes &attributes) const
Creates a request to return the feature on the referenced (parent) layer which is referenced by the p...
Defines a relation between matching fields of the two involved tables of a relation.
Definition: qgsrelation.h:72
QgsFields fields() const override
Returns the list of fields of this layer.
Shows the features and attributes in a table layout.
Definition: qgsdualview.h:60
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
void setShowUnlinkButton(bool showUnlinkButton)
Determines if the "unlink feature" button should be shown.
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...
QMap< int, QVariant > QgsAttributeMap
Definition: qgsattributes.h:39
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)...
QgsRelationEditorWidget(QWidget *parent=nullptr)
QgsVectorLayer referencedLayer
Definition: qgsrelation.h:44
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()
Is emitted, when editing on this layer has started.
QList< QgsRelation::FieldPair > fieldPairs() const
Returns the field pairs which form this relation The first element of each pair are the field names o...
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const override
Query the layer for features specified in request.
QString expression() const
Returns the original, unmodified expression string.
This selection manager synchronizes a local set of selected features with an attribute table...
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
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:46
int indexOf(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:189
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:391
void setEditorContext(const QgsAttributeEditorContext &context)
QString referencedField() const
Gets the name of the referenced (parent) field.
Definition: qgsrelation.h:85
QgsVectorDataProvider * dataProvider() override
Returns the layer&#39;s data provider.
bool addFeatures(QgsFeatureList &features, QgsFeatureSink::Flags flags=nullptr) override
Adds a list of features 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.
qint64 QgsFeatureId
Definition: qgsfeature.h:37
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).
QList< int > QgsAttributeList
Definition: qgsfield.h:27
bool nextFeature(QgsFeature &f)
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:255
QString referencingField() const
Gets the name of the referencing (child) field.
Definition: qgsrelation.h:83
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)
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=nullptr) override
Adds a single feature to the sink.
This widget is used to show the attributes of a set of features of a QgsVectorLayer.
Definition: qgsdualview.h:43
bool showLabel() const
Defines if a title label should be shown for this widget.