QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 #ifdef QGISDEBUG
209  if ( index.model() != mModel->masterModel() )
210  qWarning() << "Index from wrong model passed in";
211 #endif
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  setEditSelection( mModel->mapToMaster( index ), QItemSelectionModel::ClearAndSelect );
240  }
241  else
242  {
243  selectRow( index, false );
244  }
245 }
246 
247 void QgsFeatureListView::mouseReleaseEvent( QMouseEvent *event )
248 {
249  Q_UNUSED( event )
250 
251  if ( mEditSelectionDrag )
252  {
253  mEditSelectionDrag = false;
254  }
255  else
256  {
257  if ( mFeatureSelectionModel )
258  mFeatureSelectionModel->enableSync( true );
259  }
260 }
261 
262 void QgsFeatureListView::keyPressEvent( QKeyEvent *event )
263 {
264  switch ( event->key() )
265  {
266  case Qt::Key_Up:
267  editOtherFeature( Previous );
268  break;
269 
270  case Qt::Key_Down:
271  editOtherFeature( Next );
272  break;
273 
274  default:
275  QListView::keyPressEvent( event );
276  }
277 }
278 
279 void QgsFeatureListView::editOtherFeature( QgsFeatureListView::PositionInList positionInList )
280 {
281  int currentRow = 0;
282  if ( 0 != mCurrentEditSelectionModel->selectedIndexes().count() )
283  {
284  QModelIndex localIndex = mModel->mapFromMaster( mCurrentEditSelectionModel->selectedIndexes().first() );
285  currentRow = localIndex.row();
286  }
287 
288  QModelIndex newLocalIndex;
289  QModelIndex newIndex;
290 
291  switch ( positionInList )
292  {
293  case First:
294  newLocalIndex = mModel->index( 0, 0 );
295  break;
296 
297  case Previous:
298  newLocalIndex = mModel->index( currentRow - 1, 0 );
299  break;
300 
301  case Next:
302  newLocalIndex = mModel->index( currentRow + 1, 0 );
303  break;
304 
305  case Last:
306  newLocalIndex = mModel->index( mModel->rowCount() - 1, 0 );
307  break;
308  }
309 
310  newIndex = mModel->mapToMaster( newLocalIndex );
311  if ( newIndex.isValid() )
312  {
313  setEditSelection( newIndex, QItemSelectionModel::ClearAndSelect );
314  scrollTo( newLocalIndex );
315  }
316 }
317 
318 void QgsFeatureListView::contextMenuEvent( QContextMenuEvent *event )
319 {
320  QModelIndex index = indexAt( event->pos() );
321 
322  if ( index.isValid() )
323  {
324  QgsFeature feature = mModel->data( index, QgsFeatureListModel::FeatureRole ).value<QgsFeature>();
325 
326  QgsActionMenu *menu = new QgsActionMenu( mModel->layerCache()->layer(), feature, QStringLiteral( "Feature" ), this );
327 
328  emit willShowContextMenu( menu, index );
329 
330  menu->exec( event->globalPos() );
331  }
332 }
333 
334 void QgsFeatureListView::selectRow( const QModelIndex &index, bool anchor )
335 {
336  QItemSelectionModel::SelectionFlags command = selectionCommand( index );
337  int row = index.row();
338 
339  if ( anchor )
340  mRowAnchor = row;
341 
342  if ( selectionMode() != QListView::SingleSelection
343  && command.testFlag( QItemSelectionModel::Toggle ) )
344  {
345  if ( anchor )
346  mCtrlDragSelectionFlag = mFeatureSelectionModel->isSelected( index )
347  ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
348  command &= ~QItemSelectionModel::Toggle;
349  command |= mCtrlDragSelectionFlag;
350  if ( !anchor )
351  command |= QItemSelectionModel::Current;
352  }
353 
354  QModelIndex tl = model()->index( std::min( mRowAnchor, row ), 0 );
355  QModelIndex br = model()->index( std::max( mRowAnchor, row ), model()->columnCount() - 1 );
356 
357  mFeatureSelectionModel->selectFeatures( QItemSelection( tl, br ), command );
358 }
359 
360 void QgsFeatureListView::ensureEditSelection( bool inSelection )
361 {
362  if ( !mModel->rowCount() )
363  return;
364 
365  const QModelIndexList selectedIndexes = mCurrentEditSelectionModel->selectedIndexes();
366 
367  // We potentially want a new edit selection
368  // If we it should be in the feature selection
369  // but we don't find a matching one we might
370  // still stick to the old edit selection
371  bool editSelectionUpdateRequested = false;
372  // There is a valid selection available which we
373  // could fall back to
374  bool validEditSelectionAvailable = false;
375 
376  if ( selectedIndexes.isEmpty() || mModel->mapFromMaster( selectedIndexes.first() ).row() == -1 )
377  {
378  validEditSelectionAvailable = false;
379  }
380  else
381  {
382  validEditSelectionAvailable = true;
383  }
384 
385  // If we want to force the edit selection to be within the feature selection
386  // let's do some additional checks
387  if ( inSelection )
388  {
389  // no valid edit selection, update anyway
390  if ( !validEditSelectionAvailable )
391  {
392  editSelectionUpdateRequested = true;
393  }
394  else
395  {
396  // valid selection: update only if it's not in the feature selection
397  const QgsFeatureIds selectedFids = layerCache()->layer()->selectedFeatureIds();
398 
399  if ( !selectedFids.contains( mModel->idxToFid( mModel->mapFromMaster( selectedIndexes.first() ) ) ) )
400  {
401  editSelectionUpdateRequested = true;
402  }
403  }
404  }
405  else
406  {
407  // we don't care if the edit selection is in the feature selection?
408  // well then, only update if there is no valid edit selection available
409  if ( !validEditSelectionAvailable )
410  editSelectionUpdateRequested = true;
411  }
412 
413  if ( editSelectionUpdateRequested )
414  {
415  if ( !mUpdateEditSelectionTimer.isSingleShot() )
416  {
417  mUpdateEditSelectionTimer.setSingleShot( true );
418  connect( &mUpdateEditSelectionTimer, &QTimer::timeout, this, [ this, inSelection, validEditSelectionAvailable ]()
419  {
420  // The layer might have been removed between timer start and timer triggered
421  // in this case there is nothing left for us to do.
422  if ( !layerCache() )
423  return;
424 
425  int rowToSelect = -1;
426 
427  if ( inSelection )
428  {
429  const QgsFeatureIds selectedFids = layerCache()->layer()->selectedFeatureIds();
430  const int rowCount = mModel->rowCount();
431 
432  for ( int i = 0; i < rowCount; i++ )
433  {
434  if ( selectedFids.contains( mModel->idxToFid( mModel->index( i, 0 ) ) ) )
435  {
436  rowToSelect = i;
437  break;
438  }
439 
440  if ( rowToSelect == -1 && !validEditSelectionAvailable )
441  rowToSelect = 0;
442  }
443  }
444  else
445  rowToSelect = 0;
446 
447  if ( rowToSelect != -1 )
448  {
449  setEditSelection( mModel->mapToMaster( mModel->index( rowToSelect, 0 ) ), QItemSelectionModel::ClearAndSelect );
450  }
451  } );
452  mUpdateEditSelectionTimer.setInterval( 0 );
453  }
454  mUpdateEditSelectionTimer.start();
455  }
456 }
457 
459 {
460  delete mFeatureSelectionManager;
461 
462  mFeatureSelectionManager = featureSelectionManager;
463 
464  if ( mFeatureSelectionModel )
465  mFeatureSelectionModel->setFeatureSelectionManager( mFeatureSelectionManager );
466 }
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.