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