QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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 
193  const auto constFids = fids;
194  for ( QgsFeatureId fid : constFids )
195  {
196  selection.append( QItemSelectionRange( mModel->mapToMaster( mModel->fidToIdx( fid ) ) ) );
197  }
198 
199  bool ok = true;
200  emit aboutToChangeEditSelection( ok );
201 
202  if ( ok )
203  mCurrentEditSelectionModel->select( selection, QItemSelectionModel::ClearAndSelect );
204 }
205 
206 void QgsFeatureListView::setEditSelection( const QModelIndex &index, QItemSelectionModel::SelectionFlags command )
207 {
208  bool ok = true;
209  emit aboutToChangeEditSelection( ok );
210 
211  Q_ASSERT( index.model() == mModel->masterModel() || !index.isValid() );
212 
213  if ( ok )
214  mCurrentEditSelectionModel->select( index, command );
215 }
216 
217 void QgsFeatureListView::repaintRequested( const QModelIndexList &indexes )
218 {
219  const auto constIndexes = indexes;
220  for ( const QModelIndex &index : constIndexes )
221  {
222  update( index );
223  }
224 }
225 
227 {
228  setDirtyRegion( viewport()->rect() );
229 }
230 
231 void QgsFeatureListView::mouseMoveEvent( QMouseEvent *event )
232 {
233  QPoint pos = event->pos();
234 
235  QModelIndex index = indexAt( pos );
236 
237  if ( mEditSelectionDrag )
238  {
239  if ( index.isValid() )
240  setEditSelection( mModel->mapToMaster( index ), QItemSelectionModel::ClearAndSelect );
241  }
242  else
243  {
244  selectRow( index, false );
245  }
246 }
247 
248 void QgsFeatureListView::mouseReleaseEvent( QMouseEvent *event )
249 {
250  Q_UNUSED( event )
251 
252  if ( mEditSelectionDrag )
253  {
254  mEditSelectionDrag = false;
255  }
256  else
257  {
258  if ( mFeatureSelectionModel )
259  mFeatureSelectionModel->enableSync( true );
260  }
261 }
262 
263 void QgsFeatureListView::keyPressEvent( QKeyEvent *event )
264 {
265  switch ( event->key() )
266  {
267  case Qt::Key_Up:
268  editOtherFeature( Previous );
269  break;
270 
271  case Qt::Key_Down:
272  editOtherFeature( Next );
273  break;
274 
275  default:
276  QListView::keyPressEvent( event );
277  }
278 }
279 
280 void QgsFeatureListView::editOtherFeature( QgsFeatureListView::PositionInList positionInList )
281 {
282  int currentRow = 0;
283  if ( 0 != mCurrentEditSelectionModel->selectedIndexes().count() )
284  {
285  QModelIndex localIndex = mModel->mapFromMaster( mCurrentEditSelectionModel->selectedIndexes().first() );
286  currentRow = localIndex.row();
287  }
288 
289  QModelIndex newLocalIndex;
290  QModelIndex newIndex;
291 
292  switch ( positionInList )
293  {
294  case First:
295  newLocalIndex = mModel->index( 0, 0 );
296  break;
297 
298  case Previous:
299  newLocalIndex = mModel->index( currentRow - 1, 0 );
300  break;
301 
302  case Next:
303  newLocalIndex = mModel->index( currentRow + 1, 0 );
304  break;
305 
306  case Last:
307  newLocalIndex = mModel->index( mModel->rowCount() - 1, 0 );
308  break;
309  }
310 
311  newIndex = mModel->mapToMaster( newLocalIndex );
312  if ( newIndex.isValid() )
313  {
314  setEditSelection( newIndex, QItemSelectionModel::ClearAndSelect );
315  scrollTo( newLocalIndex );
316  }
317 }
318 
319 void QgsFeatureListView::contextMenuEvent( QContextMenuEvent *event )
320 {
321  QModelIndex index = indexAt( event->pos() );
322 
323  if ( index.isValid() )
324  {
325  QgsFeature feature = mModel->data( index, QgsFeatureListModel::FeatureRole ).value<QgsFeature>();
326 
327  QgsActionMenu *menu = new QgsActionMenu( mModel->layerCache()->layer(), feature, QStringLiteral( "Feature" ), this );
328 
329  // Index is from feature list model, but we need an index from the
330  // filter model to be passed to listeners, using fid instead would
331  // have been much better in term of bugs (and headaches) but this
332  // belongs to the API unfortunately.
333  emit willShowContextMenu( menu, mModel->mapToSource( index ) );
334 
335  menu->exec( event->globalPos() );
336  }
337 }
338 
339 void QgsFeatureListView::selectRow( const QModelIndex &index, bool anchor )
340 {
341  QItemSelectionModel::SelectionFlags command = selectionCommand( index );
342  int row = index.row();
343 
344  if ( anchor )
345  mRowAnchor = row;
346 
347  if ( selectionMode() != QListView::SingleSelection
348  && command.testFlag( QItemSelectionModel::Toggle ) )
349  {
350  if ( anchor )
351  mCtrlDragSelectionFlag = mFeatureSelectionModel->isSelected( index )
352  ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
353  command &= ~QItemSelectionModel::Toggle;
354  command |= mCtrlDragSelectionFlag;
355  if ( !anchor )
356  command |= QItemSelectionModel::Current;
357  }
358 
359  QModelIndex tl = model()->index( std::min( mRowAnchor, row ), 0 );
360  QModelIndex br = model()->index( std::max( mRowAnchor, row ), model()->columnCount() - 1 );
361 
362  mFeatureSelectionModel->selectFeatures( QItemSelection( tl, br ), command );
363 }
364 
365 void QgsFeatureListView::ensureEditSelection( bool inSelection )
366 {
367  if ( !mModel->rowCount() )
368  return;
369 
370  const QModelIndexList selectedIndexes = mCurrentEditSelectionModel->selectedIndexes();
371 
372  // We potentially want a new edit selection
373  // If we it should be in the feature selection
374  // but we don't find a matching one we might
375  // still stick to the old edit selection
376  bool editSelectionUpdateRequested = false;
377  // There is a valid selection available which we
378  // could fall back to
379  bool validEditSelectionAvailable = false;
380 
381  if ( selectedIndexes.isEmpty() || !selectedIndexes.first().isValid() || mModel->mapFromMaster( selectedIndexes.first() ).row() == -1 )
382  {
383  validEditSelectionAvailable = false;
384  }
385  else
386  {
387  validEditSelectionAvailable = true;
388  }
389 
390  // If we want to force the edit selection to be within the feature selection
391  // let's do some additional checks
392  if ( inSelection )
393  {
394  // no valid edit selection, update anyway
395  if ( !validEditSelectionAvailable )
396  {
397  editSelectionUpdateRequested = true;
398  }
399  else
400  {
401  // valid selection: update only if it's not in the feature selection
402  const QgsFeatureIds selectedFids = layerCache()->layer()->selectedFeatureIds();
403 
404  if ( !selectedFids.contains( mModel->idxToFid( mModel->mapFromMaster( selectedIndexes.first() ) ) ) )
405  {
406  editSelectionUpdateRequested = true;
407  }
408  }
409  }
410  else
411  {
412  // we don't care if the edit selection is in the feature selection?
413  // well then, only update if there is no valid edit selection available
414  if ( !validEditSelectionAvailable )
415  editSelectionUpdateRequested = true;
416  }
417 
418  if ( editSelectionUpdateRequested )
419  {
420  if ( !mUpdateEditSelectionTimer.isSingleShot() )
421  {
422  mUpdateEditSelectionTimer.setSingleShot( true );
423  connect( &mUpdateEditSelectionTimer, &QTimer::timeout, this, [ this, inSelection, validEditSelectionAvailable ]()
424  {
425  // The layer might have been removed between timer start and timer triggered
426  // in this case there is nothing left for us to do.
427  if ( !layerCache() )
428  return;
429 
430  int rowToSelect = -1;
431 
432  if ( inSelection )
433  {
434  const QgsFeatureIds selectedFids = layerCache()->layer()->selectedFeatureIds();
435  const int rowCount = mModel->rowCount();
436 
437  for ( int i = 0; i < rowCount; i++ )
438  {
439  if ( selectedFids.contains( mModel->idxToFid( mModel->index( i, 0 ) ) ) )
440  {
441  rowToSelect = i;
442  break;
443  }
444 
445  if ( rowToSelect == -1 && !validEditSelectionAvailable )
446  rowToSelect = 0;
447  }
448  }
449  else
450  rowToSelect = 0;
451 
452  if ( rowToSelect != -1 )
453  {
454  setEditSelection( mModel->mapToMaster( mModel->index( rowToSelect, 0 ) ), QItemSelectionModel::ClearAndSelect );
455  }
456  } );
457  mUpdateEditSelectionTimer.setInterval( 0 );
458  }
459  mUpdateEditSelectionTimer.start();
460  }
461 }
462 
464 {
465  mFeatureSelectionManager = featureSelectionManager;
466 
467  if ( mFeatureSelectionModel )
468  mFeatureSelectionModel->setFeatureSelectionManager( mFeatureSelectionManager );
469 
470  // only delete the owner selection manager and not one created from outside
471  if ( mOwnedFeatureSelectionManager )
472  {
473  mOwnedFeatureSelectionManager->deleteLater();
474  mOwnedFeatureSelectionManager = nullptr;
475  }
476 }
virtual bool isSelected(QgsFeatureId fid)
Returns the selection status of a given feature id.
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:34
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
Definition: qgsfeatureid.h:25
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.