QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
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
23#include "qgsfeaturelistmodel.h"
25#include "qgsfeaturelistview.h"
27#include "qgslogger.h"
28#include "qgsvectorlayer.h"
30#include "qgsvectorlayercache.h"
31#include "qgsactionmenu.h"
32
34 : QListView( parent )
35{
36 setSelectionMode( QAbstractItemView::ExtendedSelection );
37
38 mUpdateEditSelectionTimerWithSelection.setSingleShot( true );
39 connect( &mUpdateEditSelectionTimerWithSelection, &QTimer::timeout, this, [ this ]()
40 {
41 updateEditSelection( true );
42 } );
43
44 mUpdateEditSelectionTimerWithSelection.setInterval( 0 );
45
46 mUpdateEditSelectionTimerWithoutSelection.setSingleShot( true );
47 connect( &mUpdateEditSelectionTimerWithoutSelection, &QTimer::timeout, this, [ this ]()
48 {
49 updateEditSelection( false );
50 } );
51
52 mUpdateEditSelectionTimerWithoutSelection.setInterval( 0 );
53}
54
56{
57 return mModel->layerCache();
58}
59
61{
62 QListView::setModel( featureListModel );
63 mModel = featureListModel;
64
65 delete mFeatureSelectionModel;
66 delete mCurrentEditSelectionModel;
67
68 mCurrentEditSelectionModel = new QItemSelectionModel( mModel->masterModel(), this );
69 if ( !mFeatureSelectionManager )
70 {
71 mOwnedFeatureSelectionManager = new QgsVectorLayerSelectionManager( mModel->layerCache()->layer(), mModel );
72 mFeatureSelectionManager = mOwnedFeatureSelectionManager;
73 }
74
75 mFeatureSelectionModel = new QgsFeatureSelectionModel( featureListModel, featureListModel, mFeatureSelectionManager, this );
76 setSelectionModel( mFeatureSelectionModel );
77 connect( featureListModel->layerCache()->layer(), &QgsVectorLayer::selectionChanged, this, [ this ]()
78 {
79 ensureEditSelection( true );
80 } );
81
82 if ( mItemDelegate && mItemDelegate->parent() == this )
83 {
84 delete mItemDelegate;
85 }
86
87 mItemDelegate = new QgsFeatureListViewDelegate( mModel, this );
88 mItemDelegate->setEditSelectionModel( mCurrentEditSelectionModel );
89 setItemDelegate( mItemDelegate );
90
91 mItemDelegate->setFeatureSelectionModel( mFeatureSelectionModel );
92 connect( mFeatureSelectionModel, static_cast<void ( QgsFeatureSelectionModel::* )( const QModelIndexList &indexes )>( &QgsFeatureSelectionModel::requestRepaint ),
93 this, static_cast<void ( QgsFeatureListView::* )( const QModelIndexList &indexes )>( &QgsFeatureListView::repaintRequested ) );
94 connect( mFeatureSelectionModel, static_cast<void ( QgsFeatureSelectionModel::* )()>( &QgsFeatureSelectionModel::requestRepaint ),
95 this, static_cast<void ( QgsFeatureListView::* )()>( &QgsFeatureListView::repaintRequested ) );
96 connect( mCurrentEditSelectionModel, &QItemSelectionModel::selectionChanged, this, &QgsFeatureListView::editSelectionChanged );
97 connect( mModel->layerCache()->layer(), &QgsVectorLayer::attributeValueChanged, this, [ = ] { repaintRequested(); } );
98 connect( featureListModel, &QgsFeatureListModel::rowsRemoved, this, [ this ]() { ensureEditSelection(); } );
99 connect( featureListModel, &QgsFeatureListModel::rowsInserted, this, [ this ]() { ensureEditSelection(); } );
100 connect( featureListModel, &QgsFeatureListModel::modelReset, this, [ this ]() { ensureEditSelection(); } );
101}
102
103bool QgsFeatureListView::setDisplayExpression( const QString &expression )
104{
105 if ( mModel->setDisplayExpression( expression ) )
106 {
107 emit displayExpressionChanged( expression );
108 return true;
109 }
110 else
111 {
112 return false;
113 }
114}
115
117{
118 return mModel->displayExpression();
119}
120
122{
123 return mModel->parserErrorString();
124}
125
127{
128 QgsFeatureIds selection;
129 const QModelIndexList selectedIndexes = mCurrentEditSelectionModel->selectedIndexes();
130 for ( const QModelIndex &idx : selectedIndexes )
131 {
132 selection << idx.data( QgsAttributeTableModel::FeatureIdRole ).value<QgsFeatureId>();
133 }
134 return selection;
135}
136
138{
139 mItemDelegate->setCurrentFeatureEdited( state );
140 viewport()->update( visualRegionForSelection( mCurrentEditSelectionModel->selection() ) );
141}
142
143void QgsFeatureListView::mousePressEvent( QMouseEvent *event )
144{
145 if ( event->button() != Qt::LeftButton )
146 {
147 QListView::mousePressEvent( event );
148 return;
149 }
150
151 if ( mModel )
152 {
153 const QPoint pos = event->pos();
154
155 const QModelIndex index = indexAt( pos );
156
157 if ( QgsFeatureListViewDelegate::EditElement == mItemDelegate->positionToElement( event->pos() ) )
158 {
159 mDragMode = DragMode::MoveSelection;
160 if ( index.isValid() )
161 setEditSelection( mModel->mapToMaster( index ), QItemSelectionModel::ClearAndSelect );
162 }
163 else
164 {
165 mDragMode = DragMode::ExpandSelection;
166 mFeatureSelectionModel->enableSync( false );
167 selectRow( index, true );
169 }
170 }
171 else
172 {
173 QgsDebugMsg( QStringLiteral( "No model assigned to this view" ) );
174 }
175}
176
177void QgsFeatureListView::editSelectionChanged( const QItemSelection &deselected, const QItemSelection &selected )
178{
179 if ( isVisible() && updatesEnabled() )
180 {
181 const QItemSelection localDeselected = mModel->mapSelectionFromMaster( deselected );
182 const QItemSelection localSelected = mModel->mapSelectionFromMaster( selected );
183 viewport()->update( visualRegionForSelection( localDeselected ) | visualRegionForSelection( localSelected ) );
184 }
185
186 const QItemSelection currentSelection = mCurrentEditSelectionModel->selection();
187 if ( currentSelection.size() == 1 )
188 {
189 QModelIndexList indexList = currentSelection.indexes();
190 if ( !indexList.isEmpty() )
191 {
192 QgsFeature feat;
193 mModel->featureByIndex( mModel->mapFromMaster( indexList.first() ), feat );
194
195 emit currentEditSelectionChanged( feat );
196 emit currentEditSelectionProgressChanged( mModel->mapFromMaster( indexList.first() ).row(), mModel->rowCount() );
197 }
198 }
199 else if ( mModel->rowCount() == 0 )
200 {
202 }
203}
204
206{
207 QItemSelection selection;
208 selection.append( QItemSelectionRange( mModel->index( 0, 0 ), mModel->index( mModel->rowCount() - 1, 0 ) ) );
209
210 mFeatureSelectionModel->selectFeatures( selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
211}
212
214{
215 QItemSelection selection;
216 QModelIndex firstModelIdx;
217
218 const auto constFids = fids;
219 for ( const QgsFeatureId fid : constFids )
220 {
221 const QModelIndex modelIdx = mModel->fidToIdx( fid );
222
223 if ( ! firstModelIdx.isValid() )
224 firstModelIdx = modelIdx;
225
226 selection.append( QItemSelectionRange( mModel->mapToMaster( modelIdx ) ) );
227 }
228
229 bool ok = true;
231
232 if ( ok )
233 {
234 mCurrentEditSelectionModel->select( selection, QItemSelectionModel::ClearAndSelect );
235 scrollTo( firstModelIdx );
236 }
237}
238
239void QgsFeatureListView::setEditSelection( const QModelIndex &index, QItemSelectionModel::SelectionFlags command )
240{
241 bool ok = true;
243
244 // cppcheck-suppress assertWithSideEffect
245 Q_ASSERT( index.model() == mModel->masterModel() || !index.isValid() );
246
247 if ( ok )
248 {
249 mCurrentEditSelectionModel->select( index, command );
250 scrollTo( index );
251 }
252}
253
254void QgsFeatureListView::repaintRequested( const QModelIndexList &indexes )
255{
256 const auto constIndexes = indexes;
257 for ( const QModelIndex &index : constIndexes )
258 {
259 update( index );
260 }
261}
262
264{
265 setDirtyRegion( viewport()->rect() );
266}
267
268void QgsFeatureListView::mouseMoveEvent( QMouseEvent *event )
269{
270 if ( mModel )
271 {
272 const QPoint pos = event->pos();
273 const QModelIndex index = indexAt( pos );
274
275 switch ( mDragMode )
276 {
277 case QgsFeatureListView::DragMode::Inactive:
278 break;
279
280 case QgsFeatureListView::DragMode::ExpandSelection:
281 {
282 selectRow( index, false );
283 break;
284 }
285
286 case QgsFeatureListView::DragMode::MoveSelection:
287 {
288 if ( index.isValid() )
289 setEditSelection( mModel->mapToMaster( index ), QItemSelectionModel::ClearAndSelect );
290 break;
291 }
292 }
293 }
294 else
295 {
296 QgsDebugMsg( QStringLiteral( "No model assigned to this view" ) );
297 }
298}
299
301{
302 if ( event->button() != Qt::LeftButton )
303 {
304 QListView::mouseReleaseEvent( event );
305 return;
306 }
307
308 switch ( mDragMode )
309 {
310 case QgsFeatureListView::DragMode::ExpandSelection:
311 if ( mFeatureSelectionModel )
312 mFeatureSelectionModel->enableSync( true );
313 break;
314 case QgsFeatureListView::DragMode::Inactive:
315 case QgsFeatureListView::DragMode::MoveSelection:
316 break;
317 }
318
319 mDragMode = DragMode::Inactive;
320}
321
322void QgsFeatureListView::keyPressEvent( QKeyEvent *event )
323{
324 switch ( event->key() )
325 {
326 case Qt::Key_Up:
327 editOtherFeature( Previous );
328 break;
329
330 case Qt::Key_Down:
331 editOtherFeature( Next );
332 break;
333
334 default:
335 QListView::keyPressEvent( event );
336 }
337}
338
339void QgsFeatureListView::editOtherFeature( QgsFeatureListView::PositionInList positionInList )
340{
341 int currentRow = 0;
342 if ( 0 != mCurrentEditSelectionModel->selectedIndexes().count() )
343 {
344 const QModelIndex localIndex = mModel->mapFromMaster( mCurrentEditSelectionModel->selectedIndexes().first() );
345 currentRow = localIndex.row();
346 }
347
348 QModelIndex newLocalIndex;
349 QModelIndex newIndex;
350
351 switch ( positionInList )
352 {
353 case First:
354 newLocalIndex = mModel->index( 0, 0 );
355 break;
356
357 case Previous:
358 newLocalIndex = mModel->index( currentRow - 1, 0 );
359 break;
360
361 case Next:
362 newLocalIndex = mModel->index( currentRow + 1, 0 );
363 break;
364
365 case Last:
366 newLocalIndex = mModel->index( mModel->rowCount() - 1, 0 );
367 break;
368 }
369
370 newIndex = mModel->mapToMaster( newLocalIndex );
371 if ( newIndex.isValid() )
372 {
373 setEditSelection( newIndex, QItemSelectionModel::ClearAndSelect );
374 scrollTo( newLocalIndex );
375 }
376}
377
378void QgsFeatureListView::contextMenuEvent( QContextMenuEvent *event )
379{
380 const QModelIndex index = indexAt( event->pos() );
381
382 if ( index.isValid() )
383 {
384 const QgsFeature feature = mModel->data( index, QgsFeatureListModel::FeatureRole ).value<QgsFeature>();
385
386 QgsActionMenu *menu = new QgsActionMenu( mModel->layerCache()->layer(), feature, QStringLiteral( "Feature" ), this );
387
388 // Index is from feature list model, but we need an index from the
389 // filter model to be passed to listeners, using fid instead would
390 // have been much better in term of bugs (and headaches) but this
391 // belongs to the API unfortunately.
392 emit willShowContextMenu( menu, mModel->mapToSource( index ) );
393
394 menu->exec( event->globalPos() );
395 }
396}
397
398void QgsFeatureListView::selectRow( const QModelIndex &index, bool anchor )
399{
400 QItemSelectionModel::SelectionFlags command = selectionCommand( index );
401 const int row = index.row();
402
403 if ( anchor )
404 mRowAnchor = row;
405
406 if ( selectionMode() != QListView::SingleSelection
407 && command.testFlag( QItemSelectionModel::Toggle ) )
408 {
409 if ( anchor )
410 mCtrlDragSelectionFlag = mFeatureSelectionModel->isSelected( index )
411 ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
412 command &= ~QItemSelectionModel::Toggle;
413 command |= mCtrlDragSelectionFlag;
414 if ( !anchor )
415 command |= QItemSelectionModel::Current;
416 }
417
418 const QModelIndex tl = model()->index( std::min( mRowAnchor, row ), 0 );
419 const QModelIndex br = model()->index( std::max( mRowAnchor, row ), model()->columnCount() - 1 );
420
421 mFeatureSelectionModel->selectFeatures( QItemSelection( tl, br ), command );
422}
423
424void QgsFeatureListView::ensureEditSelection( bool inSelection )
425{
426
427 if ( inSelection )
428 {
429 mUpdateEditSelectionTimerWithSelection.start();
430 }
431 else
432 {
433 mUpdateEditSelectionTimerWithoutSelection.start();
434 }
435}
436
437void QgsFeatureListView::updateEditSelection( bool inSelection )
438{
439 if ( !mModel->rowCount() )
440 {
441 // not sure this is the best place to emit from
442 // this will allow setting the counter to zero in the browsing panel
444 return;
445 }
446
447 const QModelIndexList selectedIndexes = mCurrentEditSelectionModel->selectedIndexes();
448
449 // We potentially want a new edit selection
450 // If we it should be in the feature selection
451 // but we don't find a matching one we might
452 // still stick to the old edit selection
453 bool editSelectionUpdateRequested = false;
454 // There is a valid selection available which we
455 // could fall back to
456 bool validEditSelectionAvailable = false;
457
458 if ( selectedIndexes.isEmpty() || !selectedIndexes.first().isValid() || mModel->mapFromMaster( selectedIndexes.first() ).row() == -1 )
459 {
460 validEditSelectionAvailable = false;
461 }
462 else
463 {
464 validEditSelectionAvailable = true;
465 }
466
467 // If we want to force the edit selection to be within the feature selection
468 // let's do some additional checks
469 if ( inSelection )
470 {
471 // no valid edit selection, update anyway
472 if ( !validEditSelectionAvailable )
473 {
474 editSelectionUpdateRequested = true;
475 }
476 else
477 {
478 // valid selection: update only if it's not in the feature selection
479 const QgsFeatureIds selectedFids = layerCache()->layer()->selectedFeatureIds();
480
481 if ( !selectedFids.contains( mModel->idxToFid( mModel->mapFromMaster( selectedIndexes.first() ) ) ) )
482 {
483 editSelectionUpdateRequested = true;
484 }
485 }
486 }
487 else
488 {
489 // we don't care if the edit selection is in the feature selection?
490 // well then, only update if there is no valid edit selection available
491 if ( !validEditSelectionAvailable )
492 editSelectionUpdateRequested = true;
493 }
494
495 if ( editSelectionUpdateRequested )
496 {
497 // The layer might have been removed between timer start and timer triggered
498 // in this case there is nothing left for us to do.
499 if ( !layerCache() )
500 return;
501
502 int rowToSelect = -1;
503
504 if ( inSelection )
505 {
506 const QgsFeatureIds selectedFids = layerCache()->layer()->selectedFeatureIds();
507 const int rowCount = mModel->rowCount();
508
509 for ( int i = 0; i < rowCount; i++ )
510 {
511 if ( selectedFids.contains( mModel->idxToFid( mModel->index( i, 0 ) ) ) )
512 {
513 rowToSelect = i;
514 break;
515 }
516
517 if ( rowToSelect == -1 && !validEditSelectionAvailable )
518 rowToSelect = 0;
519 }
520 }
521 else
522 rowToSelect = 0;
523
524 if ( rowToSelect != -1 )
525 {
526 setEditSelection( mModel->mapToMaster( mModel->index( rowToSelect, 0 ) ), QItemSelectionModel::ClearAndSelect );
527 }
528 }
529}
530
532{
533 mFeatureSelectionManager = featureSelectionManager;
534
535 if ( mFeatureSelectionModel )
536 mFeatureSelectionModel->setFeatureSelectionManager( mFeatureSelectionManager );
537
538 // only delete the owned selection manager and not one created from outside
539 if ( mOwnedFeatureSelectionManager )
540 {
541 mOwnedFeatureSelectionManager->deleteLater();
542 mOwnedFeatureSelectionManager = nullptr;
543 }
544}
This class is a menu that is populated automatically with the actions defined for a given layer.
Definition: qgsactionmenu.h:39
@ 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.
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.
QgsFeatureListModel * featureListModel()
Gets the featureListModel used by this view.
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