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