QGIS API Documentation  3.17.0-Master (df2c9ff931)
qgsfeaturelistview.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  QgsAttributeTableView.cpp
3  --------------------------------------
4  Date : Feb 2009
5  Copyright : (C) 2009 Vita Cizek
6  Email : weetya (at) gmail.com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include <QHeaderView>
17 #include <QKeyEvent>
18 #include <QMenu>
19 #include <QSet>
20 #include <QSettings>
21 
24 #include "qgsattributetablemodel.h"
25 #include "qgsfeaturelistmodel.h"
27 #include "qgsfeaturelistview.h"
29 #include "qgslogger.h"
30 #include "qgsmapcanvas.h"
31 #include "qgsvectordataprovider.h"
32 #include "qgsvectorlayer.h"
34 
36  : QListView( parent )
37 {
38  setSelectionMode( QAbstractItemView::ExtendedSelection );
39 }
40 
42 {
43  return mModel->layerCache();
44 }
45 
47 {
48  QListView::setModel( featureListModel );
49  mModel = featureListModel;
50 
51  delete mFeatureSelectionModel;
52  delete mCurrentEditSelectionModel;
53 
54  mCurrentEditSelectionModel = new QItemSelectionModel( mModel->masterModel(), this );
55  if ( !mFeatureSelectionManager )
56  {
57  mOwnedFeatureSelectionManager = new QgsVectorLayerSelectionManager( mModel->layerCache()->layer(), mModel );
58  mFeatureSelectionManager = mOwnedFeatureSelectionManager;
59  }
60 
61  mFeatureSelectionModel = new QgsFeatureSelectionModel( featureListModel, featureListModel, mFeatureSelectionManager, this );
62  setSelectionModel( mFeatureSelectionModel );
63  connect( featureListModel->layerCache()->layer(), &QgsVectorLayer::selectionChanged, this, [ this ]()
64  {
65  ensureEditSelection( true );
66  } );
67 
68  if ( mItemDelegate && mItemDelegate->parent() == this )
69  {
70  delete mItemDelegate;
71  }
72 
73  mItemDelegate = new QgsFeatureListViewDelegate( mModel, this );
74  mItemDelegate->setEditSelectionModel( mCurrentEditSelectionModel );
75  setItemDelegate( mItemDelegate );
76 
77  mItemDelegate->setFeatureSelectionModel( mFeatureSelectionModel );
78  connect( mFeatureSelectionModel, static_cast<void ( QgsFeatureSelectionModel::* )( const QModelIndexList &indexes )>( &QgsFeatureSelectionModel::requestRepaint ),
79  this, static_cast<void ( QgsFeatureListView::* )( const QModelIndexList &indexes )>( &QgsFeatureListView::repaintRequested ) );
80  connect( mFeatureSelectionModel, static_cast<void ( QgsFeatureSelectionModel::* )()>( &QgsFeatureSelectionModel::requestRepaint ),
81  this, static_cast<void ( QgsFeatureListView::* )()>( &QgsFeatureListView::repaintRequested ) );
82  connect( mCurrentEditSelectionModel, &QItemSelectionModel::selectionChanged, this, &QgsFeatureListView::editSelectionChanged );
83  connect( mModel->layerCache()->layer(), &QgsVectorLayer::attributeValueChanged, this, [ = ] { repaintRequested(); } );
84  connect( featureListModel, &QgsFeatureListModel::rowsRemoved, this, [ this ]() { ensureEditSelection(); } );
85  connect( featureListModel, &QgsFeatureListModel::rowsInserted, this, [ this ]() { ensureEditSelection(); } );
86  connect( featureListModel, &QgsFeatureListModel::modelReset, this, [ this ]() { ensureEditSelection(); } );
87 }
88 
89 bool QgsFeatureListView::setDisplayExpression( const QString &expression )
90 {
91  if ( mModel->setDisplayExpression( expression ) )
92  {
93  emit displayExpressionChanged( expression );
94  return true;
95  }
96  else
97  {
98  return false;
99  }
100 }
101 
103 {
104  return mModel->displayExpression();
105 }
106 
108 {
109  return mModel->parserErrorString();
110 }
111 
113 {
114  QgsFeatureIds selection;
115  const QModelIndexList selectedIndexes = mCurrentEditSelectionModel->selectedIndexes();
116  for ( const QModelIndex &idx : selectedIndexes )
117  {
118  selection << idx.data( QgsAttributeTableModel::FeatureIdRole ).value<QgsFeatureId>();
119  }
120  return selection;
121 }
122 
124 {
125  mItemDelegate->setCurrentFeatureEdited( state );
126  viewport()->update( visualRegionForSelection( mCurrentEditSelectionModel->selection() ) );
127 }
128 
129 void QgsFeatureListView::mousePressEvent( QMouseEvent *event )
130 {
131  if ( mModel )
132  {
133  QPoint pos = event->pos();
134 
135  QModelIndex index = indexAt( pos );
136 
137  if ( QgsFeatureListViewDelegate::EditElement == mItemDelegate->positionToElement( event->pos() ) )
138  {
139 
140  mEditSelectionDrag = true;
141  if ( index.isValid() )
142  setEditSelection( mModel->mapToMaster( index ), QItemSelectionModel::ClearAndSelect );
143  }
144  else
145  {
146  mFeatureSelectionModel->enableSync( false );
147  selectRow( index, true );
149  }
150  }
151  else
152  {
153  QgsDebugMsg( QStringLiteral( "No model assigned to this view" ) );
154  }
155 }
156 
157 void QgsFeatureListView::editSelectionChanged( const QItemSelection &deselected, const QItemSelection &selected )
158 {
159  if ( isVisible() && updatesEnabled() )
160  {
161  QItemSelection localDeselected = mModel->mapSelectionFromMaster( deselected );
162  QItemSelection localSelected = mModel->mapSelectionFromMaster( selected );
163  viewport()->update( visualRegionForSelection( localDeselected ) | visualRegionForSelection( localSelected ) );
164  }
165 
166  QItemSelection currentSelection = mCurrentEditSelectionModel->selection();
167  if ( currentSelection.size() == 1 )
168  {
169  QModelIndexList indexList = currentSelection.indexes();
170  if ( !indexList.isEmpty() )
171  {
172  QgsFeature feat;
173  mModel->featureByIndex( mModel->mapFromMaster( indexList.first() ), feat );
174 
175  emit currentEditSelectionChanged( feat );
176  emit currentEditSelectionProgressChanged( mModel->mapFromMaster( indexList.first() ).row(), mModel->rowCount() );
177  }
178  }
179 }
180 
182 {
183  QItemSelection selection;
184  selection.append( QItemSelectionRange( mModel->index( 0, 0 ), mModel->index( mModel->rowCount() - 1, 0 ) ) );
185 
186  mFeatureSelectionModel->selectFeatures( selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
187 }
188 
190 {
191  QItemSelection selection;
192  QModelIndex firstModelIdx;
193 
194  const auto constFids = fids;
195  for ( QgsFeatureId fid : constFids )
196  {
197  QModelIndex modelIdx = mModel->fidToIdx( fid );
198 
199  if ( ! firstModelIdx.isValid() )
200  firstModelIdx = modelIdx;
201 
202  selection.append( QItemSelectionRange( mModel->mapToMaster( modelIdx ) ) );
203  }
204 
205  bool ok = true;
206  emit aboutToChangeEditSelection( ok );
207 
208  if ( ok )
209  {
210  mCurrentEditSelectionModel->select( selection, QItemSelectionModel::ClearAndSelect );
211  scrollTo( firstModelIdx );
212  }
213 }
214 
215 void QgsFeatureListView::setEditSelection( const QModelIndex &index, QItemSelectionModel::SelectionFlags command )
216 {
217  bool ok = true;
218  emit aboutToChangeEditSelection( ok );
219 
220  // cppcheck-suppress assertWithSideEffect
221  Q_ASSERT( index.model() == mModel->masterModel() || !index.isValid() );
222 
223  if ( ok )
224  {
225  mCurrentEditSelectionModel->select( index, command );
226  scrollTo( index );
227  }
228 }
229 
230 void QgsFeatureListView::repaintRequested( const QModelIndexList &indexes )
231 {
232  const auto constIndexes = indexes;
233  for ( const QModelIndex &index : constIndexes )
234  {
235  update( index );
236  }
237 }
238 
240 {
241  setDirtyRegion( viewport()->rect() );
242 }
243 
244 void QgsFeatureListView::mouseMoveEvent( QMouseEvent *event )
245 {
246  if ( mModel )
247  {
248  QPoint pos = event->pos();
249 
250  QModelIndex index = indexAt( pos );
251 
252  if ( mEditSelectionDrag )
253  {
254  if ( index.isValid() )
255  setEditSelection( mModel->mapToMaster( index ), QItemSelectionModel::ClearAndSelect );
256  }
257  else
258  {
259  selectRow( index, false );
260  }
261  }
262  else
263  {
264  QgsDebugMsg( QStringLiteral( "No model assigned to this view" ) );
265  }
266 }
267 
268 void QgsFeatureListView::mouseReleaseEvent( QMouseEvent *event )
269 {
270  Q_UNUSED( event )
271 
272  if ( mEditSelectionDrag )
273  {
274  mEditSelectionDrag = false;
275  }
276  else
277  {
278  if ( mFeatureSelectionModel )
279  mFeatureSelectionModel->enableSync( true );
280  }
281 }
282 
283 void QgsFeatureListView::keyPressEvent( QKeyEvent *event )
284 {
285  switch ( event->key() )
286  {
287  case Qt::Key_Up:
288  editOtherFeature( Previous );
289  break;
290 
291  case Qt::Key_Down:
292  editOtherFeature( Next );
293  break;
294 
295  default:
296  QListView::keyPressEvent( event );
297  }
298 }
299 
300 void QgsFeatureListView::editOtherFeature( QgsFeatureListView::PositionInList positionInList )
301 {
302  int currentRow = 0;
303  if ( 0 != mCurrentEditSelectionModel->selectedIndexes().count() )
304  {
305  QModelIndex localIndex = mModel->mapFromMaster( mCurrentEditSelectionModel->selectedIndexes().first() );
306  currentRow = localIndex.row();
307  }
308 
309  QModelIndex newLocalIndex;
310  QModelIndex newIndex;
311 
312  switch ( positionInList )
313  {
314  case First:
315  newLocalIndex = mModel->index( 0, 0 );
316  break;
317 
318  case Previous:
319  newLocalIndex = mModel->index( currentRow - 1, 0 );
320  break;
321 
322  case Next:
323  newLocalIndex = mModel->index( currentRow + 1, 0 );
324  break;
325 
326  case Last:
327  newLocalIndex = mModel->index( mModel->rowCount() - 1, 0 );
328  break;
329  }
330 
331  newIndex = mModel->mapToMaster( newLocalIndex );
332  if ( newIndex.isValid() )
333  {
334  setEditSelection( newIndex, QItemSelectionModel::ClearAndSelect );
335  scrollTo( newLocalIndex );
336  }
337 }
338 
339 void QgsFeatureListView::contextMenuEvent( QContextMenuEvent *event )
340 {
341  QModelIndex index = indexAt( event->pos() );
342 
343  if ( index.isValid() )
344  {
345  QgsFeature feature = mModel->data( index, QgsFeatureListModel::FeatureRole ).value<QgsFeature>();
346 
347  QgsActionMenu *menu = new QgsActionMenu( mModel->layerCache()->layer(), feature, QStringLiteral( "Feature" ), this );
348 
349  // Index is from feature list model, but we need an index from the
350  // filter model to be passed to listeners, using fid instead would
351  // have been much better in term of bugs (and headaches) but this
352  // belongs to the API unfortunately.
353  emit willShowContextMenu( menu, mModel->mapToSource( index ) );
354 
355  menu->exec( event->globalPos() );
356  }
357 }
358 
359 void QgsFeatureListView::selectRow( const QModelIndex &index, bool anchor )
360 {
361  QItemSelectionModel::SelectionFlags command = selectionCommand( index );
362  int row = index.row();
363 
364  if ( anchor )
365  mRowAnchor = row;
366 
367  if ( selectionMode() != QListView::SingleSelection
368  && command.testFlag( QItemSelectionModel::Toggle ) )
369  {
370  if ( anchor )
371  mCtrlDragSelectionFlag = mFeatureSelectionModel->isSelected( index )
372  ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
373  command &= ~QItemSelectionModel::Toggle;
374  command |= mCtrlDragSelectionFlag;
375  if ( !anchor )
376  command |= QItemSelectionModel::Current;
377  }
378 
379  QModelIndex tl = model()->index( std::min( mRowAnchor, row ), 0 );
380  QModelIndex br = model()->index( std::max( mRowAnchor, row ), model()->columnCount() - 1 );
381 
382  mFeatureSelectionModel->selectFeatures( QItemSelection( tl, br ), command );
383 }
384 
385 void QgsFeatureListView::ensureEditSelection( bool inSelection )
386 {
387  if ( !mModel->rowCount() )
388  {
389  // not sure this is the best place to emit from
390  // this will allow setting the counter to zero in the browsing panel
392  return;
393  }
394 
395  const QModelIndexList selectedIndexes = mCurrentEditSelectionModel->selectedIndexes();
396 
397  // We potentially want a new edit selection
398  // If we it should be in the feature selection
399  // but we don't find a matching one we might
400  // still stick to the old edit selection
401  bool editSelectionUpdateRequested = false;
402  // There is a valid selection available which we
403  // could fall back to
404  bool validEditSelectionAvailable = false;
405 
406  if ( selectedIndexes.isEmpty() || !selectedIndexes.first().isValid() || mModel->mapFromMaster( selectedIndexes.first() ).row() == -1 )
407  {
408  validEditSelectionAvailable = false;
409  }
410  else
411  {
412  validEditSelectionAvailable = true;
413  }
414 
415  // If we want to force the edit selection to be within the feature selection
416  // let's do some additional checks
417  if ( inSelection )
418  {
419  // no valid edit selection, update anyway
420  if ( !validEditSelectionAvailable )
421  {
422  editSelectionUpdateRequested = true;
423  }
424  else
425  {
426  // valid selection: update only if it's not in the feature selection
427  const QgsFeatureIds selectedFids = layerCache()->layer()->selectedFeatureIds();
428 
429  if ( !selectedFids.contains( mModel->idxToFid( mModel->mapFromMaster( selectedIndexes.first() ) ) ) )
430  {
431  editSelectionUpdateRequested = true;
432  }
433  }
434  }
435  else
436  {
437  // we don't care if the edit selection is in the feature selection?
438  // well then, only update if there is no valid edit selection available
439  if ( !validEditSelectionAvailable )
440  editSelectionUpdateRequested = true;
441  }
442 
443  if ( editSelectionUpdateRequested )
444  {
445  if ( !mUpdateEditSelectionTimer.isSingleShot() )
446  {
447  mUpdateEditSelectionTimer.setSingleShot( true );
448  connect( &mUpdateEditSelectionTimer, &QTimer::timeout, this, [ this, inSelection, validEditSelectionAvailable ]()
449  {
450  // The layer might have been removed between timer start and timer triggered
451  // in this case there is nothing left for us to do.
452  if ( !layerCache() )
453  return;
454 
455  int rowToSelect = -1;
456 
457  if ( inSelection )
458  {
459  const QgsFeatureIds selectedFids = layerCache()->layer()->selectedFeatureIds();
460  const int rowCount = mModel->rowCount();
461 
462  for ( int i = 0; i < rowCount; i++ )
463  {
464  if ( selectedFids.contains( mModel->idxToFid( mModel->index( i, 0 ) ) ) )
465  {
466  rowToSelect = i;
467  break;
468  }
469 
470  if ( rowToSelect == -1 && !validEditSelectionAvailable )
471  rowToSelect = 0;
472  }
473  }
474  else
475  rowToSelect = 0;
476 
477  if ( rowToSelect != -1 )
478  {
479  setEditSelection( mModel->mapToMaster( mModel->index( rowToSelect, 0 ) ), QItemSelectionModel::ClearAndSelect );
480  }
481  } );
482  mUpdateEditSelectionTimer.setInterval( 0 );
483  }
484  mUpdateEditSelectionTimer.start();
485  }
486 }
487 
489 {
490  mFeatureSelectionManager = featureSelectionManager;
491 
492  if ( mFeatureSelectionModel )
493  mFeatureSelectionModel->setFeatureSelectionManager( mFeatureSelectionManager );
494 
495  // only delete the owned selection manager and not one created from outside
496  if ( mOwnedFeatureSelectionManager )
497  {
498  mOwnedFeatureSelectionManager->deleteLater();
499  mOwnedFeatureSelectionManager = nullptr;
500  }
501 }
virtual bool isSelected(QgsFeatureId fid)
Returns the selection status of a given feature id.
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
void setCurrentFeatureEdited(bool state)
Sets if the currently shown form has received any edit events so far.
QgsFeatureId idxToFid(const QModelIndex &index) const
Returns the feature ID corresponding to an index from the model.
void mouseReleaseEvent(QMouseEvent *event) override
bool setDisplayExpression(const QString &expression)
bool setDisplayExpression(const QString &displayExpression)
The display expression is an expression used to render the fields into a single string which is displ...
void currentEditSelectionProgressChanged(int progress, int count)
Emitted whenever the current edit selection has been changed.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
int rowCount(const QModelIndex &parent=QModelIndex()) const override
void willShowContextMenu(QgsActionMenu *menu, const QModelIndex &atIndex)
Emitted when the context menu is created to add the specific actions to it.
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features ...
Definition: qgsfeatureid.h:28
bool featureByIndex(const QModelIndex &index, QgsFeature &feat)
virtual void selectFeatures(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command)
Select features on this table.
QgsVectorLayer * layer()
Returns the layer to which this cache belongs.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
void enableSync(bool enable)
Enables or disables synchronisation to the QgsVectorLayer When synchronisation is disabled...
QString parserErrorString()
Returns a detailed message about errors while parsing a QgsExpression.
virtual QModelIndex mapToMaster(const QModelIndex &proxyIndex) const
Get the feature id of the feature in this row.
Shows a list of features and renders a edit button next to each feature.
QgsFeatureListModel * featureListModel()
Gets the featureListModel used by this view.
Q_INVOKABLE const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
void requestRepaint()
Request a repaint of the visible items of connected views.
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override
void setFeatureSelectionModel(QgsFeatureSelectionModel *featureSelectionModel)
QVariant data(const QModelIndex &index, int role) const override
void aboutToChangeEditSelection(bool &ok)
This class is a menu that is populated automatically with the actions defined for a given layer...
Definition: qgsactionmenu.h:37
QString parserErrorString()
Returns a detailed message about errors while parsing a QgsExpression.
virtual void setFeatureSelectionManager(QgsIFeatureSelectionManager *featureSelectionManager)
void displayExpressionChanged(const QString &expression)
Emitted whenever the display expression is successfully changed.
QString displayExpression() const
void selectionChanged(const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect)
Emitted when selection was changed.
void attributeValueChanged(QgsFeatureId fid, int idx, const QVariant &value)
Emitted whenever an attribute value change is done in the edit buffer.
QgsFeatureIds currentEditSelection()
Gets the currentEditSelection.
void mouseMoveEvent(QMouseEvent *event) override
QgsAttributeTableModel * masterModel()
virtual QModelIndex mapFromMaster(const QModelIndex &sourceIndex) const
This class caches features of a given QgsVectorLayer.
void setEditSelection(const QgsFeatureIds &fids)
Set the feature(s) to be edited.
const QString displayExpression() const
Returns the expression which is currently used to render the features.
void selectAll() override
Select all currently visible features.
void mousePressEvent(QMouseEvent *event) override
void setFeatureSelectionManager(QgsIFeatureSelectionManager *featureSelectionManager)
setFeatureSelectionManager
void contextMenuEvent(QContextMenuEvent *event) override
void keyPressEvent(QKeyEvent *event) override
virtual QItemSelection mapSelectionFromMaster(const QItemSelection &selection) const
QgsFeatureListView(QWidget *parent=nullptr)
Creates a feature list view.
QgsVectorLayerCache * layerCache()
Returns the vector layer cache which is being used to populate the model.
QModelIndex fidToIdx(QgsFeatureId fid) const
Returns the model index corresponding to a feature ID.
void setEditSelectionModel(QItemSelectionModel *editSelectionModel)
Is an interface class to abstract feature selection handling.
void currentEditSelectionChanged(QgsFeature &feat)
Emitted whenever the current edit selection has been changed.
virtual void setModel(QgsFeatureListModel *featureListModel)
Set the QgsFeatureListModel which is used to retrieve information.
QgsVectorLayerCache * layerCache()
Returns the layer cache.