QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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  mFeatureSelectionManager = new QgsVectorLayerSelectionManager( mModel->layerCache()->layer(), mModel );
58  }
59 
60  mFeatureSelectionModel = new QgsFeatureSelectionModel( featureListModel, featureListModel, mFeatureSelectionManager, this );
61  setSelectionModel( mFeatureSelectionModel );
62  connect( featureListModel->layerCache()->layer(), &QgsVectorLayer::selectionChanged, this, [ this ]()
63  {
64  ensureEditSelection( true );
65  } );
66 
67  if ( mItemDelegate && mItemDelegate->parent() == this )
68  {
69  delete mItemDelegate;
70  }
71 
72  mItemDelegate = new QgsFeatureListViewDelegate( mModel, this );
73  mItemDelegate->setEditSelectionModel( mCurrentEditSelectionModel );
74  setItemDelegate( mItemDelegate );
75 
76  mItemDelegate->setFeatureSelectionModel( mFeatureSelectionModel );
77  connect( mFeatureSelectionModel, static_cast<void ( QgsFeatureSelectionModel::* )( const QModelIndexList &indexes )>( &QgsFeatureSelectionModel::requestRepaint ),
78  this, static_cast<void ( QgsFeatureListView::* )( const QModelIndexList &indexes )>( &QgsFeatureListView::repaintRequested ) );
79  connect( mFeatureSelectionModel, static_cast<void ( QgsFeatureSelectionModel::* )()>( &QgsFeatureSelectionModel::requestRepaint ),
80  this, static_cast<void ( QgsFeatureListView::* )()>( &QgsFeatureListView::repaintRequested ) );
81  connect( mCurrentEditSelectionModel, &QItemSelectionModel::selectionChanged, this, &QgsFeatureListView::editSelectionChanged );
82  connect( mModel->layerCache()->layer(), &QgsVectorLayer::attributeValueChanged, this, [ = ] { repaintRequested(); } );
83  connect( featureListModel, &QgsFeatureListModel::rowsRemoved, this, [ this ]() { ensureEditSelection(); } );
84  connect( featureListModel, &QgsFeatureListModel::rowsInserted, this, [ this ]() { ensureEditSelection(); } );
85  connect( featureListModel, &QgsFeatureListModel::modelReset, this, [ this ]() { ensureEditSelection(); } );
86 }
87 
88 bool QgsFeatureListView::setDisplayExpression( const QString &expression )
89 {
90  if ( mModel->setDisplayExpression( expression ) )
91  {
92  emit displayExpressionChanged( expression );
93  return true;
94  }
95  else
96  {
97  return false;
98  }
99 }
100 
102 {
103  return mModel->displayExpression();
104 }
105 
107 {
108  return mModel->parserErrorString();
109 }
110 
112 {
113  QgsFeatureIds selection;
114  const QModelIndexList selectedIndexes = mCurrentEditSelectionModel->selectedIndexes();
115  for ( const QModelIndex &idx : selectedIndexes )
116  {
117  selection << idx.data( QgsAttributeTableModel::FeatureIdRole ).value<QgsFeatureId>();
118  }
119  return selection;
120 }
121 
123 {
124  mItemDelegate->setCurrentFeatureEdited( state );
125  viewport()->update( visualRegionForSelection( mCurrentEditSelectionModel->selection() ) );
126 }
127 
128 void QgsFeatureListView::mousePressEvent( QMouseEvent *event )
129 {
130  if ( mModel )
131  {
132  QPoint pos = event->pos();
133 
134  QModelIndex index = indexAt( pos );
135 
136  if ( QgsFeatureListViewDelegate::EditElement == mItemDelegate->positionToElement( event->pos() ) )
137  {
138  mEditSelectionDrag = true;
139  setEditSelection( mModel->mapToMaster( index ), QItemSelectionModel::ClearAndSelect );
140  }
141  else
142  {
143  mFeatureSelectionModel->enableSync( false );
144  selectRow( index, true );
146  }
147  }
148  else
149  {
150  QgsDebugMsg( QStringLiteral( "No model assigned to this view" ) );
151  }
152 }
153 
154 void QgsFeatureListView::editSelectionChanged( const QItemSelection &deselected, const QItemSelection &selected )
155 {
156  if ( isVisible() && updatesEnabled() )
157  {
158  QItemSelection localDeselected = mModel->mapSelectionFromMaster( deselected );
159  QItemSelection localSelected = mModel->mapSelectionFromMaster( selected );
160  viewport()->update( visualRegionForSelection( localDeselected ) | visualRegionForSelection( localSelected ) );
161  }
162 
163  QItemSelection currentSelection = mCurrentEditSelectionModel->selection();
164  if ( currentSelection.size() == 1 )
165  {
166  QModelIndexList indexList = currentSelection.indexes();
167  if ( !indexList.isEmpty() )
168  {
169  QgsFeature feat;
170  mModel->featureByIndex( mModel->mapFromMaster( indexList.first() ), feat );
171 
172  emit currentEditSelectionChanged( feat );
173  emit currentEditSelectionProgressChanged( mModel->mapFromMaster( indexList.first() ).row(), mModel->rowCount() );
174  }
175  }
176 }
177 
179 {
180  QItemSelection selection;
181  selection.append( QItemSelectionRange( mModel->index( 0, 0 ), mModel->index( mModel->rowCount() - 1, 0 ) ) );
182 
183  mFeatureSelectionModel->selectFeatures( selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
184 }
185 
187 {
188  QItemSelection selection;
189 
190  const auto constFids = fids;
191  for ( QgsFeatureId fid : constFids )
192  {
193  selection.append( QItemSelectionRange( mModel->mapToMaster( mModel->fidToIdx( fid ) ) ) );
194  }
195 
196  bool ok = true;
197  emit aboutToChangeEditSelection( ok );
198 
199  if ( ok )
200  mCurrentEditSelectionModel->select( selection, QItemSelectionModel::ClearAndSelect );
201 }
202 
203 void QgsFeatureListView::setEditSelection( const QModelIndex &index, QItemSelectionModel::SelectionFlags command )
204 {
205  bool ok = true;
206  emit aboutToChangeEditSelection( ok );
207 
208  Q_ASSERT( index.model() == mModel->masterModel() || !index.isValid() );
209 
210  if ( ok )
211  mCurrentEditSelectionModel->select( index, command );
212 }
213 
214 void QgsFeatureListView::repaintRequested( const QModelIndexList &indexes )
215 {
216  const auto constIndexes = indexes;
217  for ( const QModelIndex &index : constIndexes )
218  {
219  update( index );
220  }
221 }
222 
224 {
225  setDirtyRegion( viewport()->rect() );
226 }
227 
228 void QgsFeatureListView::mouseMoveEvent( QMouseEvent *event )
229 {
230  QPoint pos = event->pos();
231 
232  QModelIndex index = indexAt( pos );
233 
234  if ( mEditSelectionDrag )
235  {
236  setEditSelection( mModel->mapToMaster( index ), QItemSelectionModel::ClearAndSelect );
237  }
238  else
239  {
240  selectRow( index, false );
241  }
242 }
243 
244 void QgsFeatureListView::mouseReleaseEvent( QMouseEvent *event )
245 {
246  Q_UNUSED( event )
247 
248  if ( mEditSelectionDrag )
249  {
250  mEditSelectionDrag = false;
251  }
252  else
253  {
254  if ( mFeatureSelectionModel )
255  mFeatureSelectionModel->enableSync( true );
256  }
257 }
258 
259 void QgsFeatureListView::keyPressEvent( QKeyEvent *event )
260 {
261  switch ( event->key() )
262  {
263  case Qt::Key_Up:
264  editOtherFeature( Previous );
265  break;
266 
267  case Qt::Key_Down:
268  editOtherFeature( Next );
269  break;
270 
271  default:
272  QListView::keyPressEvent( event );
273  }
274 }
275 
276 void QgsFeatureListView::editOtherFeature( QgsFeatureListView::PositionInList positionInList )
277 {
278  int currentRow = 0;
279  if ( 0 != mCurrentEditSelectionModel->selectedIndexes().count() )
280  {
281  QModelIndex localIndex = mModel->mapFromMaster( mCurrentEditSelectionModel->selectedIndexes().first() );
282  currentRow = localIndex.row();
283  }
284 
285  QModelIndex newLocalIndex;
286  QModelIndex newIndex;
287 
288  switch ( positionInList )
289  {
290  case First:
291  newLocalIndex = mModel->index( 0, 0 );
292  break;
293 
294  case Previous:
295  newLocalIndex = mModel->index( currentRow - 1, 0 );
296  break;
297 
298  case Next:
299  newLocalIndex = mModel->index( currentRow + 1, 0 );
300  break;
301 
302  case Last:
303  newLocalIndex = mModel->index( mModel->rowCount() - 1, 0 );
304  break;
305  }
306 
307  newIndex = mModel->mapToMaster( newLocalIndex );
308  if ( newIndex.isValid() )
309  {
310  setEditSelection( newIndex, QItemSelectionModel::ClearAndSelect );
311  scrollTo( newLocalIndex );
312  }
313 }
314 
315 void QgsFeatureListView::contextMenuEvent( QContextMenuEvent *event )
316 {
317  QModelIndex index = indexAt( event->pos() );
318 
319  if ( index.isValid() )
320  {
321  QgsFeature feature = mModel->data( index, QgsFeatureListModel::FeatureRole ).value<QgsFeature>();
322 
323  QgsActionMenu *menu = new QgsActionMenu( mModel->layerCache()->layer(), feature, QStringLiteral( "Feature" ), this );
324 
325  emit willShowContextMenu( menu, index );
326 
327  menu->exec( event->globalPos() );
328  }
329 }
330 
331 void QgsFeatureListView::selectRow( const QModelIndex &index, bool anchor )
332 {
333  QItemSelectionModel::SelectionFlags command = selectionCommand( index );
334  int row = index.row();
335 
336  if ( anchor )
337  mRowAnchor = row;
338 
339  if ( selectionMode() != QListView::SingleSelection
340  && command.testFlag( QItemSelectionModel::Toggle ) )
341  {
342  if ( anchor )
343  mCtrlDragSelectionFlag = mFeatureSelectionModel->isSelected( index )
344  ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
345  command &= ~QItemSelectionModel::Toggle;
346  command |= mCtrlDragSelectionFlag;
347  if ( !anchor )
348  command |= QItemSelectionModel::Current;
349  }
350 
351  QModelIndex tl = model()->index( std::min( mRowAnchor, row ), 0 );
352  QModelIndex br = model()->index( std::max( mRowAnchor, row ), model()->columnCount() - 1 );
353 
354  mFeatureSelectionModel->selectFeatures( QItemSelection( tl, br ), command );
355 }
356 
357 void QgsFeatureListView::ensureEditSelection( bool inSelection )
358 {
359  if ( !mModel->rowCount() )
360  return;
361 
362  const QModelIndexList selectedIndexes = mCurrentEditSelectionModel->selectedIndexes();
363 
364  // We potentially want a new edit selection
365  // If we it should be in the feature selection
366  // but we don't find a matching one we might
367  // still stick to the old edit selection
368  bool editSelectionUpdateRequested = false;
369  // There is a valid selection available which we
370  // could fall back to
371  bool validEditSelectionAvailable = false;
372 
373  if ( selectedIndexes.isEmpty() || !selectedIndexes.first().isValid() || mModel->mapFromMaster( selectedIndexes.first() ).row() == -1 )
374  {
375  validEditSelectionAvailable = false;
376  }
377  else
378  {
379  validEditSelectionAvailable = true;
380  }
381 
382  // If we want to force the edit selection to be within the feature selection
383  // let's do some additional checks
384  if ( inSelection )
385  {
386  // no valid edit selection, update anyway
387  if ( !validEditSelectionAvailable )
388  {
389  editSelectionUpdateRequested = true;
390  }
391  else
392  {
393  // valid selection: update only if it's not in the feature selection
394  const QgsFeatureIds selectedFids = layerCache()->layer()->selectedFeatureIds();
395 
396  if ( !selectedFids.contains( mModel->idxToFid( mModel->mapFromMaster( selectedIndexes.first() ) ) ) )
397  {
398  editSelectionUpdateRequested = true;
399  }
400  }
401  }
402  else
403  {
404  // we don't care if the edit selection is in the feature selection?
405  // well then, only update if there is no valid edit selection available
406  if ( !validEditSelectionAvailable )
407  editSelectionUpdateRequested = true;
408  }
409 
410  if ( editSelectionUpdateRequested )
411  {
412  if ( !mUpdateEditSelectionTimer.isSingleShot() )
413  {
414  mUpdateEditSelectionTimer.setSingleShot( true );
415  connect( &mUpdateEditSelectionTimer, &QTimer::timeout, this, [ this, inSelection, validEditSelectionAvailable ]()
416  {
417  // The layer might have been removed between timer start and timer triggered
418  // in this case there is nothing left for us to do.
419  if ( !layerCache() )
420  return;
421 
422  int rowToSelect = -1;
423 
424  if ( inSelection )
425  {
426  const QgsFeatureIds selectedFids = layerCache()->layer()->selectedFeatureIds();
427  const int rowCount = mModel->rowCount();
428 
429  for ( int i = 0; i < rowCount; i++ )
430  {
431  if ( selectedFids.contains( mModel->idxToFid( mModel->index( i, 0 ) ) ) )
432  {
433  rowToSelect = i;
434  break;
435  }
436 
437  if ( rowToSelect == -1 && !validEditSelectionAvailable )
438  rowToSelect = 0;
439  }
440  }
441  else
442  rowToSelect = 0;
443 
444  if ( rowToSelect != -1 )
445  {
446  setEditSelection( mModel->mapToMaster( mModel->index( rowToSelect, 0 ) ), QItemSelectionModel::ClearAndSelect );
447  }
448  } );
449  mUpdateEditSelectionTimer.setInterval( 0 );
450  }
451  mUpdateEditSelectionTimer.start();
452  }
453 }
454 
456 {
457  delete mFeatureSelectionManager;
458 
459  mFeatureSelectionManager = featureSelectionManager;
460 
461  if ( mFeatureSelectionModel )
462  mFeatureSelectionModel->setFeatureSelectionManager( mFeatureSelectionManager );
463 }
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.
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.
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:38
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.