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