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