QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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( static_cast< int >( QgsAttributeTableModel::CustomRole::FeatureId ) ).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 QgsDebugError( QStringLiteral( "No model assigned to this view" ) );
174 }
175}
176
177void QgsFeatureListView::editSelectionChanged( const QItemSelection &selected, const QItemSelection &deselected )
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 mLastEditSelectionFid = QgsFeatureId();
187 if ( !selected.isEmpty() )
188 {
189 const QModelIndexList indexList = selected.indexes();
190 if ( !indexList.isEmpty() )
191 {
192 QgsFeature selectedFeature;
193 mModel->featureByIndex( mModel->mapFromMaster( indexList.first() ), selectedFeature );
194 mLastEditSelectionFid = selectedFeature.id();
195 }
196 }
197
198 const QItemSelection currentSelection = mCurrentEditSelectionModel->selection();
199 if ( currentSelection.size() == 1 )
200 {
201 QModelIndexList indexList = currentSelection.indexes();
202 if ( !indexList.isEmpty() )
203 {
204 QgsFeature feat;
205 mModel->featureByIndex( mModel->mapFromMaster( indexList.first() ), feat );
206
207 emit currentEditSelectionChanged( feat );
208 emit currentEditSelectionProgressChanged( mModel->mapFromMaster( indexList.first() ).row(), mModel->rowCount() );
209 }
210 }
211 else if ( mModel->rowCount() == 0 )
212 {
214 }
215}
216
218{
219 QItemSelection selection;
220 selection.append( QItemSelectionRange( mModel->index( 0, 0 ), mModel->index( mModel->rowCount() - 1, 0 ) ) );
221
222 mFeatureSelectionModel->selectFeatures( selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
223}
224
226{
227 QItemSelection selection;
228 QModelIndex firstModelIdx;
229
230 const auto constFids = fids;
231 for ( const QgsFeatureId fid : constFids )
232 {
233 const QModelIndex modelIdx = mModel->fidToIdx( fid );
234
235 if ( ! firstModelIdx.isValid() )
236 firstModelIdx = modelIdx;
237
238 selection.append( QItemSelectionRange( mModel->mapToMaster( modelIdx ) ) );
239 }
240
241 bool ok = true;
243
244 if ( ok )
245 {
246 mCurrentEditSelectionModel->select( selection, QItemSelectionModel::ClearAndSelect );
247 scrollTo( firstModelIdx );
248 }
249}
250
251void QgsFeatureListView::setEditSelection( const QModelIndex &index, QItemSelectionModel::SelectionFlags command )
252{
253 bool ok = true;
255
256 // cppcheck-suppress assertWithSideEffect
257 Q_ASSERT( index.model() == mModel->masterModel() || !index.isValid() );
258
259 if ( ok )
260 {
261 mCurrentEditSelectionModel->select( index, command );
262 scrollTo( index );
263 }
264}
265
266void QgsFeatureListView::repaintRequested( const QModelIndexList &indexes )
267{
268 const auto constIndexes = indexes;
269 for ( const QModelIndex &index : constIndexes )
270 {
271 update( index );
272 }
273}
274
276{
277 setDirtyRegion( viewport()->rect() );
278}
279
280void QgsFeatureListView::mouseMoveEvent( QMouseEvent *event )
281{
282 if ( mModel )
283 {
284 const QPoint pos = event->pos();
285 const QModelIndex index = indexAt( pos );
286
287 switch ( mDragMode )
288 {
289 case QgsFeatureListView::DragMode::Inactive:
290 break;
291
292 case QgsFeatureListView::DragMode::ExpandSelection:
293 {
294 selectRow( index, false );
295 break;
296 }
297
298 case QgsFeatureListView::DragMode::MoveSelection:
299 {
300 if ( index.isValid() )
301 setEditSelection( mModel->mapToMaster( index ), QItemSelectionModel::ClearAndSelect );
302 break;
303 }
304 }
305 }
306 else
307 {
308 QgsDebugError( QStringLiteral( "No model assigned to this view" ) );
309 }
310}
311
313{
314 if ( event->button() != Qt::LeftButton )
315 {
316 QListView::mouseReleaseEvent( event );
317 return;
318 }
319
320 switch ( mDragMode )
321 {
322 case QgsFeatureListView::DragMode::ExpandSelection:
323 if ( mFeatureSelectionModel )
324 mFeatureSelectionModel->enableSync( true );
325 break;
326 case QgsFeatureListView::DragMode::Inactive:
327 case QgsFeatureListView::DragMode::MoveSelection:
328 break;
329 }
330
331 mDragMode = DragMode::Inactive;
332}
333
334void QgsFeatureListView::keyPressEvent( QKeyEvent *event )
335{
336 switch ( event->key() )
337 {
338 case Qt::Key_Up:
339 editOtherFeature( Previous );
340 break;
341
342 case Qt::Key_Down:
343 editOtherFeature( Next );
344 break;
345
346 default:
347 QListView::keyPressEvent( event );
348 }
349}
350
351void QgsFeatureListView::editOtherFeature( QgsFeatureListView::PositionInList positionInList )
352{
353 int currentRow = 0;
354 if ( 0 != mCurrentEditSelectionModel->selectedIndexes().count() )
355 {
356 const QModelIndex localIndex = mModel->mapFromMaster( mCurrentEditSelectionModel->selectedIndexes().first() );
357 currentRow = localIndex.row();
358 }
359
360 QModelIndex newLocalIndex;
361 QModelIndex newIndex;
362
363 switch ( positionInList )
364 {
365 case First:
366 newLocalIndex = mModel->index( 0, 0 );
367 break;
368
369 case Previous:
370 newLocalIndex = mModel->index( currentRow - 1, 0 );
371 break;
372
373 case Next:
374 newLocalIndex = mModel->index( currentRow + 1, 0 );
375 break;
376
377 case Last:
378 newLocalIndex = mModel->index( mModel->rowCount() - 1, 0 );
379 break;
380 }
381
382 newIndex = mModel->mapToMaster( newLocalIndex );
383 if ( newIndex.isValid() )
384 {
385 setEditSelection( newIndex, QItemSelectionModel::ClearAndSelect );
386 scrollTo( newLocalIndex );
387 }
388}
389
390void QgsFeatureListView::contextMenuEvent( QContextMenuEvent *event )
391{
392 const QModelIndex index = indexAt( event->pos() );
393
394 if ( index.isValid() )
395 {
396 const QgsFeature feature = mModel->data( index, QgsFeatureListModel::FeatureRole ).value<QgsFeature>();
397
398 QgsActionMenu *menu = new QgsActionMenu( mModel->layerCache()->layer(), feature, QStringLiteral( "Feature" ), this );
399
400 // Index is from feature list model, but we need an index from the
401 // filter model to be passed to listeners, using fid instead would
402 // have been much better in term of bugs (and headaches) but this
403 // belongs to the API unfortunately.
404 emit willShowContextMenu( menu, mModel->mapToSource( index ) );
405
406 menu->exec( event->globalPos() );
407 }
408}
409
410void QgsFeatureListView::selectRow( const QModelIndex &index, bool anchor )
411{
412 QItemSelectionModel::SelectionFlags command = selectionCommand( index );
413 const int row = index.row();
414
415 if ( anchor )
416 mRowAnchor = row;
417
418 if ( selectionMode() != QListView::SingleSelection
419 && command.testFlag( QItemSelectionModel::Toggle ) )
420 {
421 if ( anchor )
422 mCtrlDragSelectionFlag = mFeatureSelectionModel->isSelected( index )
423 ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
424 command &= ~QItemSelectionModel::Toggle;
425 command |= mCtrlDragSelectionFlag;
426 if ( !anchor )
427 command |= QItemSelectionModel::Current;
428 }
429
430 const QModelIndex tl = model()->index( std::min( mRowAnchor, row ), 0 );
431 const QModelIndex br = model()->index( std::max( mRowAnchor, row ), model()->columnCount() - 1 );
432
433 mFeatureSelectionModel->selectFeatures( QItemSelection( tl, br ), command );
434}
435
436void QgsFeatureListView::ensureEditSelection( bool inSelection )
437{
438
439 if ( inSelection )
440 {
441 mUpdateEditSelectionTimerWithSelection.start();
442 }
443 else
444 {
445 mUpdateEditSelectionTimerWithoutSelection.start();
446 }
447}
448
449void QgsFeatureListView::updateEditSelection( bool inSelection )
450{
451 if ( !mModel->rowCount() )
452 {
453 // not sure this is the best place to emit from
454 // this will allow setting the counter to zero in the browsing panel
456 return;
457 }
458
459 const QModelIndexList selectedIndexes = mCurrentEditSelectionModel->selectedIndexes();
460
461 // We potentially want a new edit selection
462 // If we it should be in the feature selection
463 // but we don't find a matching one we might
464 // still stick to the old edit selection
465 bool editSelectionUpdateRequested = false;
466 // There is a valid selection available which we
467 // could fall back to
468 bool validEditSelectionAvailable = false;
469
470 if ( selectedIndexes.isEmpty() || !selectedIndexes.first().isValid() || mModel->mapFromMaster( selectedIndexes.first() ).row() == -1 )
471 {
472 validEditSelectionAvailable = false;
473 }
474 else
475 {
476 validEditSelectionAvailable = true;
477 }
478
479 // If we want to force the edit selection to be within the feature selection
480 // let's do some additional checks
481 if ( inSelection )
482 {
483 // no valid edit selection, update anyway
484 if ( !validEditSelectionAvailable )
485 {
486 editSelectionUpdateRequested = true;
487 }
488 else
489 {
490 // valid selection: update only if it's not in the feature selection
491 const QgsFeatureIds selectedFids = layerCache()->layer()->selectedFeatureIds();
492
493 if ( !selectedFids.contains( mModel->idxToFid( mModel->mapFromMaster( selectedIndexes.first() ) ) ) )
494 {
495 editSelectionUpdateRequested = true;
496 }
497 }
498 }
499 else
500 {
501 // we don't care if the edit selection is in the feature selection?
502 // well then, only update if there is no valid edit selection available
503 if ( !validEditSelectionAvailable )
504 editSelectionUpdateRequested = true;
505 }
506
507 if ( editSelectionUpdateRequested )
508 {
509 // The layer might have been removed between timer start and timer triggered
510 // in this case there is nothing left for us to do.
511 if ( !layerCache() )
512 return;
513
514 int rowToSelect = -1;
515
516 QgsFeatureIds selectedFids;
517
518 if ( inSelection )
519 {
520 selectedFids = layerCache()->layer()->selectedFeatureIds();
521 }
522
523 //if the selectedFids are empty because of no selection or selection reset, the index should persist
524 if ( selectedFids.isEmpty() )
525 {
526 //if no index can be evaluated from the last position the index should go to 0
527 selectedFids = QgsFeatureIds() << mLastEditSelectionFid;
528 }
529
530 const int rowCount = mModel->rowCount();
531 for ( int i = 0; i < rowCount; i++ )
532 {
533 if ( selectedFids.contains( mModel->idxToFid( mModel->index( i, 0 ) ) ) )
534 {
535 rowToSelect = i;
536 break;
537 }
538 }
539
540 if ( rowToSelect == -1 && !validEditSelectionAvailable )
541 {
542 // if no index could have been evaluated but no validEditSelectionAvailable, then jump to zero
543 rowToSelect = 0;
544 }
545
546 if ( rowToSelect != -1 )
547 {
548 setEditSelection( mModel->mapToMaster( mModel->index( rowToSelect, 0 ) ), QItemSelectionModel::ClearAndSelect );
549 }
550 }
551}
552
554{
555 mFeatureSelectionManager = featureSelectionManager;
556
557 if ( mFeatureSelectionModel )
558 mFeatureSelectionModel->setFeatureSelectionManager( mFeatureSelectionManager );
559
560 // only delete the owned selection manager and not one created from outside
561 if ( mOwnedFeatureSelectionManager )
562 {
563 mOwnedFeatureSelectionManager->deleteLater();
564 mOwnedFeatureSelectionManager = nullptr;
565 }
566}
This class is a menu that is populated automatically with the actions defined for a given layer.
Definition: qgsactionmenu.h:39
@ FeatureId
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
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
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 QgsDebugError(str)
Definition: qgslogger.h:38