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