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