QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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#include <QString>
34
35#include "moc_qgsfeaturelistview.cpp"
36
37using namespace Qt::StringLiterals;
38
40 : QListView( parent )
41{
42 setSelectionMode( QAbstractItemView::ExtendedSelection );
43
44 mUpdateEditSelectionTimerWithSelection.setSingleShot( true );
45 connect( &mUpdateEditSelectionTimerWithSelection, &QTimer::timeout, this, [this]() { updateEditSelection( true ); } );
46
47 mUpdateEditSelectionTimerWithSelection.setInterval( 0 );
48
49 mUpdateEditSelectionTimerWithoutSelection.setSingleShot( true );
50 connect( &mUpdateEditSelectionTimerWithoutSelection, &QTimer::timeout, this, [this]() { updateEditSelection( false ); } );
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]() { ensureEditSelection( true ); } );
78
79 if ( mItemDelegate && mItemDelegate->parent() == this )
80 {
81 delete mItemDelegate;
82 }
83
84 mItemDelegate = new QgsFeatureListViewDelegate( mModel, this );
85 mItemDelegate->setEditSelectionModel( mCurrentEditSelectionModel );
86 setItemDelegate( mItemDelegate );
87
88 mItemDelegate->setFeatureSelectionModel( mFeatureSelectionModel );
89 connect(
90 mFeatureSelectionModel,
91 static_cast<void ( QgsFeatureSelectionModel::* )( const QModelIndexList &indexes )>( &QgsFeatureSelectionModel::requestRepaint ),
92 this,
93 static_cast<void ( QgsFeatureListView::* )( const QModelIndexList &indexes )>( &QgsFeatureListView::repaintRequested )
94 );
95 connect( mFeatureSelectionModel, static_cast<void ( QgsFeatureSelectionModel::* )()>( &QgsFeatureSelectionModel::requestRepaint ), this, static_cast<void ( QgsFeatureListView::* )()>( &QgsFeatureListView::repaintRequested ) );
96 connect( mCurrentEditSelectionModel, &QItemSelectionModel::selectionChanged, this, &QgsFeatureListView::editSelectionChanged );
97 connect( mModel->layerCache()->layer(), &QgsVectorLayer::attributeValueChanged, this, [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( u"No model assigned to this view"_s );
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( u"No model assigned to this view"_s );
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::FeatureWithGeometryRole ).value<QgsFeature>();
397
398 QgsActionMenu *menu = new QgsActionMenu( mModel->layerCache()->layer(), feature, u"Feature"_s, 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 && command.testFlag( QItemSelectionModel::Toggle ) )
419 {
420 if ( anchor )
421 mCtrlDragSelectionFlag = mFeatureSelectionModel->isSelected( index ) ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
422 command &= ~QItemSelectionModel::Toggle;
423 command |= mCtrlDragSelectionFlag;
424 if ( !anchor )
425 command |= QItemSelectionModel::Current;
426 }
427
428 const QModelIndex tl = model()->index( std::min( mRowAnchor, row ), 0 );
429 const QModelIndex br = model()->index( std::max( mRowAnchor, row ), model()->columnCount() - 1 );
430
431 mFeatureSelectionModel->selectFeatures( QItemSelection( tl, br ), command );
432}
433
434void QgsFeatureListView::ensureEditSelection( bool inSelection )
435{
436 if ( inSelection )
437 {
438 mUpdateEditSelectionTimerWithSelection.start();
439 }
440 else
441 {
442 mUpdateEditSelectionTimerWithoutSelection.start();
443 }
444}
445
446void QgsFeatureListView::updateEditSelection( bool inSelection )
447{
448 if ( !mModel->rowCount() )
449 {
450 // not sure this is the best place to emit from
451 // this will allow setting the counter to zero in the browsing panel
453 return;
454 }
455
456 const QModelIndexList selectedIndexes = mCurrentEditSelectionModel->selectedIndexes();
457
458 // We potentially want a new edit selection
459 // If we it should be in the feature selection
460 // but we don't find a matching one we might
461 // still stick to the old edit selection
462 bool editSelectionUpdateRequested = false;
463 // There is a valid selection available which we
464 // could fall back to
465 bool validEditSelectionAvailable = false;
466
467 if ( selectedIndexes.isEmpty() || !selectedIndexes.first().isValid() || mModel->mapFromMaster( selectedIndexes.first() ).row() == -1 )
468 {
469 validEditSelectionAvailable = false;
470 }
471 else
472 {
473 validEditSelectionAvailable = true;
474 }
475
476 // If we want to force the edit selection to be within the feature selection
477 // let's do some additional checks
478 if ( inSelection )
479 {
480 // no valid edit selection, update anyway
481 if ( !validEditSelectionAvailable )
482 {
483 editSelectionUpdateRequested = true;
484 }
485 else
486 {
487 // valid selection: update only if it's not in the feature selection
488 const QgsFeatureIds selectedFids = layerCache()->layer()->selectedFeatureIds();
489
490 if ( !selectedFids.contains( mModel->idxToFid( mModel->mapFromMaster( selectedIndexes.first() ) ) ) )
491 {
492 editSelectionUpdateRequested = true;
493 }
494 }
495 }
496 else
497 {
498 // we don't care if the edit selection is in the feature selection?
499 // well then, only update if there is no valid edit selection available
500 if ( !validEditSelectionAvailable )
501 editSelectionUpdateRequested = true;
502 }
503
504 if ( editSelectionUpdateRequested )
505 {
506 // The layer might have been removed between timer start and timer triggered
507 // in this case there is nothing left for us to do.
508 if ( !layerCache() )
509 return;
510
511 int rowToSelect = -1;
512
513 QgsFeatureIds selectedFids;
514
515 if ( inSelection )
516 {
517 selectedFids = layerCache()->layer()->selectedFeatureIds();
518 }
519
520 //if the selectedFids are empty because of no selection or selection reset, the index should persist
521 if ( selectedFids.isEmpty() )
522 {
523 //if no index can be evaluated from the last position the index should go to 0
524 selectedFids = QgsFeatureIds() << mLastEditSelectionFid;
525 }
526
527 const int rowCount = mModel->rowCount();
528 for ( int i = 0; i < rowCount; i++ )
529 {
530 if ( selectedFids.contains( mModel->idxToFid( mModel->index( i, 0 ) ) ) )
531 {
532 rowToSelect = i;
533 break;
534 }
535 }
536
537 if ( rowToSelect == -1 && !validEditSelectionAvailable )
538 {
539 // if no index could have been evaluated but no validEditSelectionAvailable, then jump to zero
540 rowToSelect = 0;
541 }
542
543 if ( rowToSelect != -1 )
544 {
545 setEditSelection( mModel->mapToMaster( mModel->index( rowToSelect, 0 ) ), QItemSelectionModel::ClearAndSelect );
546 }
547 }
548}
549
551{
552 mFeatureSelectionManager = featureSelectionManager;
553
554 if ( mFeatureSelectionModel )
555 mFeatureSelectionModel->setFeatureSelectionManager( mFeatureSelectionManager );
556
557 // only delete the owned selection manager and not one created from outside
558 if ( mOwnedFeatureSelectionManager )
559 {
560 mOwnedFeatureSelectionManager->deleteLater();
561 mOwnedFeatureSelectionManager = nullptr;
562 }
563}
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:60
QgsFeatureId id
Definition qgsfeature.h:68
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:59