Skip to content

Commit 053f54c

Browse files
authoredMay 15, 2019
Merge pull request #9992 from 3nids/itembrowser
allow browsing feature list in feature mode
2 parents 7df3163 + 3b875a7 commit 053f54c

16 files changed

+661
-116
lines changed
 

‎images/images.qrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,8 @@
751751
<file>themes/default/mIconDataDefineColor.svg</file>
752752
<file>themes/default/mIconDataDefineColorOn.svg</file>
753753
<file>themes/default/mActionNewVirtualLayer.svg</file>
754+
<file>themes/default/mActionDoubleArrowRight.svg</file>
755+
<file>themes/default/mActionDoubleArrowLeft.svg</file>
754756
</qresource>
755757
<qresource prefix="/images/tips">
756758
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>
Lines changed: 81 additions & 0 deletions

Error rendering embedded code

Invalid image source.

Lines changed: 81 additions & 0 deletions

Error rendering embedded code

Invalid image source.

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
# The following has been generated automatically from src/gui/attributetable/qgsdualview.h
22
QgsDualView.ViewMode.baseClass = QgsDualView
3+
QgsDualView.FeatureListBrowsingAction.baseClass = QgsDualView

‎python/gui/auto_generated/attributetable/qgsdualview.sip.in

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,25 @@ and the attributes for the currently selected feature are shown in a form.
3535
};
3636

3737

38+
enum FeatureListBrowsingAction
39+
{
40+
NoAction,
41+
FlashFeature,
42+
PanToFeature,
43+
ZoomToFeature,
44+
};
45+
3846
explicit QgsDualView( QWidget *parent /TransferThis/ = 0 );
3947
%Docstring
4048
Constructor
4149

4250
:param parent: The parent widget
4351
%End
4452

45-
void init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request = QgsFeatureRequest(), const QgsAttributeEditorContext &context = QgsAttributeEditorContext(),
53+
void init( QgsVectorLayer *layer,
54+
QgsMapCanvas *mapCanvas,
55+
const QgsFeatureRequest &request = QgsFeatureRequest(),
56+
const QgsAttributeEditorContext &context = QgsAttributeEditorContext(),
4657
bool loadFeatures = true );
4758
%Docstring
4859
Has to be called to initialize the dual view.

‎python/gui/auto_generated/attributetable/qgsfeaturelistview.sip.in

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,16 @@ setFeatureSelectionManager
118118
Emitted whenever the current edit selection has been changed.
119119

120120
:param feat: the feature, which will be edited.
121+
%End
122+
123+
void currentEditSelectionProgressChanged( int progress, int count );
124+
%Docstring
125+
Emitted whenever the current edit selection has been changed.
126+
127+
:param progress: the position of the feature in the list
128+
:param count: the number of features in the list
129+
130+
.. versionadded:: 3.8
121131
%End
122132

123133
void displayExpressionChanged( const QString &expression );
@@ -162,6 +172,36 @@ Select all currently visible features
162172
void repaintRequested( const QModelIndexList &indexes );
163173
void repaintRequested();
164174

175+
void editFirstFeature();
176+
%Docstring
177+
editFirstFeature will try to edit the first feature of the list
178+
179+
.. versionadded:: 3.8
180+
%End
181+
182+
void editNextFeature();
183+
%Docstring
184+
editNextFeature will try to edit next feature of the list
185+
186+
.. versionadded:: 3.8
187+
%End
188+
189+
void editPreviousFeature();
190+
%Docstring
191+
editPreviousFeature will try to edit previous feature of the list
192+
193+
.. versionadded:: 3.8
194+
%End
195+
196+
void editLastFeature();
197+
%Docstring
198+
editLastFeature will try to edit the last feature of the list
199+
200+
.. versionadded:: 3.8
201+
%End
202+
203+
204+
165205
};
166206

167207
/************************************************************************

‎python/gui/auto_generated/qgsmapcanvas.sip.in

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,12 +232,13 @@ Set canvas extent to the bounding box of a set of features
232232
:param ids: the feature ids*
233233
%End
234234

235-
void panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids );
235+
void panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter = true );
236236
%Docstring
237237
Centers canvas extent to feature ids
238238

239239
:param layer: the vector layer
240-
:param ids: the feature ids*
240+
:param ids: the feature ids
241+
:param alwaysRecenter: if false, the canvas is recentered only if the bounding box is not contained within the current extent
241242
%End
242243

243244
void panToSelected( QgsVectorLayer *layer = 0 );

‎src/gui/attributetable/qgsdualview.cpp

Lines changed: 120 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@
1313
* *
1414
***************************************************************************/
1515

16+
#include <QClipboard>
17+
#include <QDialog>
18+
#include <QMenu>
19+
#include <QMessageBox>
20+
#include <QProgressDialog>
21+
#include <QGroupBox>
22+
#include <QInputDialog>
23+
#include <QTimer>
24+
1625
#include "qgsapplication.h"
1726
#include "qgsactionmanager.h"
1827
#include "qgsattributetablemodel.h"
@@ -32,20 +41,14 @@
3241
#include "qgsgui.h"
3342
#include "qgsexpressioncontextutils.h"
3443

35-
#include <QClipboard>
36-
#include <QDialog>
37-
#include <QMenu>
38-
#include <QMessageBox>
39-
#include <QProgressDialog>
40-
#include <QGroupBox>
41-
#include <QInputDialog>
4244

4345
QgsDualView::QgsDualView( QWidget *parent )
4446
: QStackedWidget( parent )
4547
{
4648
setupUi( this );
47-
connect( mFeatureList, &QgsFeatureListView::aboutToChangeEditSelection, this, &QgsDualView::mFeatureList_aboutToChangeEditSelection );
48-
connect( mFeatureList, &QgsFeatureListView::currentEditSelectionChanged, this, &QgsDualView::mFeatureList_currentEditSelectionChanged );
49+
connect( mFeatureListView, &QgsFeatureListView::aboutToChangeEditSelection, this, &QgsDualView::featureListAboutToChangeEditSelection );
50+
connect( mFeatureListView, &QgsFeatureListView::currentEditSelectionChanged, this, &QgsDualView::featureListCurrentEditSelectionChanged );
51+
connect( mFeatureListView, &QgsFeatureListView::currentEditSelectionProgressChanged, this, &QgsDualView::updateEditSelectionProgress );
4952

5053
mConditionalFormatWidget->hide();
5154

@@ -57,31 +60,47 @@ QgsDualView::QgsDualView( QWidget *parent )
5760

5861
// Connect layer list preview signals
5962
connect( mActionExpressionPreview, &QAction::triggered, this, &QgsDualView::previewExpressionBuilder );
60-
connect( mFeatureList, &QgsFeatureListView::displayExpressionChanged, this, &QgsDualView::previewExpressionChanged );
63+
connect( mFeatureListView, &QgsFeatureListView::displayExpressionChanged, this, &QgsDualView::previewExpressionChanged );
64+
65+
connect( mNextFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editNextFeature );
66+
connect( mPreviousFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editPreviousFeature );
67+
connect( mFirstFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editFirstFeature );
68+
connect( mLastFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editLastFeature );
69+
70+
QButtonGroup *buttonGroup = new QButtonGroup( this );
71+
buttonGroup->setExclusive( false );
72+
buttonGroup->addButton( mFlashButton, FlashFeature );
73+
buttonGroup->addButton( mAutoPanButton, PanToFeature );
74+
buttonGroup->addButton( mAutoZoomButton, ZoomToFeature );
75+
FeatureListBrowsingAction action = QgsSettings().enumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), FlashFeature );
76+
QAbstractButton *bt = buttonGroup->button( static_cast<int>( action ) );
77+
if ( bt )
78+
bt->setChecked( true );
79+
connect( buttonGroup, qgis::overload< QAbstractButton *, bool >::of( &QButtonGroup::buttonToggled ), this, &QgsDualView::panZoomGroupButtonToggled );
6180
}
6281

63-
void QgsDualView::init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request, const QgsAttributeEditorContext &context, bool loadFeatures )
82+
void QgsDualView::init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request,
83+
const QgsAttributeEditorContext &context, bool loadFeatures )
6484
{
6585
if ( !layer )
6686
return;
6787

6888
mLayer = layer;
69-
7089
mEditorContext = context;
7190

7291
connect( mTableView, &QgsAttributeTableView::willShowContextMenu, this, &QgsDualView::viewWillShowContextMenu );
7392
mTableView->horizontalHeader()->setContextMenuPolicy( Qt::CustomContextMenu );
7493
connect( mTableView->horizontalHeader(), &QHeaderView::customContextMenuRequested, this, &QgsDualView::showViewHeaderMenu );
7594
connect( mTableView, &QgsAttributeTableView::columnResized, this, &QgsDualView::tableColumnResized );
76-
connect( mFeatureList, &QgsFeatureListView::willShowContextMenu, this, &QgsDualView::widgetWillShowContextMenu );
95+
connect( mFeatureListView, &QgsFeatureListView::willShowContextMenu, this, &QgsDualView::widgetWillShowContextMenu );
7796

7897
initLayerCache( !( request.flags() & QgsFeatureRequest::NoGeometry ) || !request.filterRect().isNull() );
7998
initModels( mapCanvas, request, loadFeatures );
8099

81100
mConditionalFormatWidget->setLayer( mLayer );
82101

83102
mTableView->setModel( mFilterModel );
84-
mFeatureList->setModel( mFeatureListModel );
103+
mFeatureListView->setModel( mFeatureListModel );
85104
delete mAttributeForm;
86105
mAttributeForm = new QgsAttributeForm( mLayer, mTempAttributeFormFeature, mEditorContext );
87106
mTempAttributeFormFeature = QgsFeature();
@@ -104,7 +123,7 @@ void QgsDualView::init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const Qg
104123
connect( mFilterModel, &QgsAttributeTableFilterModel::sortColumnChanged, this, &QgsDualView::onSortColumnChanged );
105124

106125
if ( mFeatureListPreviewButton->defaultAction() )
107-
mFeatureList->setDisplayExpression( mDisplayExpression );
126+
mFeatureListView->setDisplayExpression( mDisplayExpression );
108127
else
109128
columnBoxInit();
110129

@@ -168,9 +187,9 @@ void QgsDualView::columnBoxInit()
168187
// If there is no single field found as preview
169188
if ( !mFeatureListPreviewButton->defaultAction() )
170189
{
171-
mFeatureList->setDisplayExpression( displayExpression );
190+
mFeatureListView->setDisplayExpression( displayExpression );
172191
mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
173-
setDisplayExpression( mFeatureList->displayExpression() );
192+
setDisplayExpression( mFeatureListView->displayExpression() );
174193
}
175194
else
176195
{
@@ -302,7 +321,7 @@ void QgsDualView::initModels( QgsMapCanvas *mapCanvas, const QgsFeatureRequest &
302321
connect( mMasterModel, &QgsAttributeTableModel::rowsRemoved, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
303322
connect( mMasterModel, &QgsAttributeTableModel::rowsInserted, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
304323

305-
connect( mFeatureList, &QgsFeatureListView::displayExpressionChanged, this, &QgsDualView::displayExpressionChanged );
324+
connect( mFeatureListView, &QgsFeatureListView::displayExpressionChanged, this, &QgsDualView::displayExpressionChanged );
306325

307326
mFeatureListModel = new QgsFeatureListModel( mFilterModel, mFilterModel );
308327
mFeatureListModel->setSortByDisplayExpression( true );
@@ -401,13 +420,76 @@ void QgsDualView::insertRecentlyUsedDisplayExpression( const QString &expression
401420
mLastDisplayExpressionAction = previewAction;
402421
}
403422

404-
void QgsDualView::mFeatureList_aboutToChangeEditSelection( bool &ok )
423+
void QgsDualView::updateEditSelectionProgress( int progress, int count )
424+
{
425+
mProgressCount->setText( QStringLiteral( "%1 / %2" ).arg( progress + 1 ).arg( count ) );
426+
mPreviousFeatureButton->setEnabled( progress > 0 );
427+
mNextFeatureButton->setEnabled( progress + 1 < count );
428+
mFirstFeatureButton->setEnabled( progress > 0 );
429+
mLastFeatureButton->setEnabled( progress + 1 < count );
430+
}
431+
432+
void QgsDualView::panOrZoomToFeature( const QgsFeatureIds &featureset )
433+
{
434+
QgsMapCanvas *canvas = mFilterModel->mapCanvas();
435+
if ( canvas )
436+
{
437+
if ( mAutoPanButton->isChecked() )
438+
QTimer::singleShot( 0, this, [ = ]()
439+
{
440+
canvas->panToFeatureIds( mLayer, featureset, false );
441+
canvas->flashFeatureIds( mLayer, featureset );
442+
} );
443+
else if ( mAutoZoomButton->isChecked() )
444+
QTimer::singleShot( 0, this, [ = ]()
445+
{
446+
canvas->zoomToFeatureIds( mLayer, featureset );
447+
canvas->flashFeatureIds( mLayer, featureset );
448+
} );
449+
else if ( mFlashButton->isChecked() )
450+
QTimer::singleShot( 0, this, [ = ]()
451+
{
452+
canvas->flashFeatureIds( mLayer, featureset );
453+
} );
454+
}
455+
}
456+
457+
void QgsDualView::panZoomGroupButtonToggled( QAbstractButton *button, bool checked )
458+
{
459+
if ( button == mAutoPanButton && checked )
460+
{
461+
QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), PanToFeature );
462+
mAutoZoomButton->setChecked( false );
463+
mFlashButton->setChecked( false );
464+
}
465+
else if ( button == mAutoZoomButton && checked )
466+
{
467+
QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), ZoomToFeature );
468+
mAutoPanButton->setChecked( false );
469+
mFlashButton->setChecked( false );
470+
}
471+
else if ( button == mFlashButton && checked )
472+
{
473+
QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), FlashFeature );
474+
mAutoZoomButton->setChecked( false );
475+
mAutoPanButton->setChecked( false );
476+
}
477+
else
478+
{
479+
QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), NoAction );
480+
}
481+
482+
if ( checked )
483+
panOrZoomToFeature( mFeatureListView->currentEditSelection() );
484+
}
485+
486+
void QgsDualView::featureListAboutToChangeEditSelection( bool &ok )
405487
{
406488
if ( mLayer->isEditable() && !mAttributeForm->save() )
407489
ok = false;
408490
}
409491

410-
void QgsDualView::mFeatureList_currentEditSelectionChanged( const QgsFeature &feat )
492+
void QgsDualView::featureListCurrentEditSelectionChanged( const QgsFeature &feat )
411493
{
412494
if ( !mAttributeForm )
413495
{
@@ -416,7 +498,11 @@ void QgsDualView::mFeatureList_currentEditSelectionChanged( const QgsFeature &fe
416498
else if ( !mLayer->isEditable() || mAttributeForm->save() )
417499
{
418500
mAttributeForm->setFeature( feat );
419-
setCurrentEditSelection( QgsFeatureIds() << feat.id() );
501+
QgsFeatureIds featureset;
502+
featureset << feat.id();
503+
setCurrentEditSelection( featureset );
504+
505+
panOrZoomToFeature( featureset );
420506
}
421507
else
422508
{
@@ -426,8 +512,8 @@ void QgsDualView::mFeatureList_currentEditSelectionChanged( const QgsFeature &fe
426512

427513
void QgsDualView::setCurrentEditSelection( const QgsFeatureIds &fids )
428514
{
429-
mFeatureList->setCurrentFeatureEdited( false );
430-
mFeatureList->setEditSelection( fids );
515+
mFeatureListView->setCurrentFeatureEdited( false );
516+
mFeatureListView->setEditSelection( fids );
431517
}
432518

433519
bool QgsDualView::saveEditChanges()
@@ -467,28 +553,28 @@ void QgsDualView::previewExpressionBuilder()
467553
// Show expression builder
468554
QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) );
469555

470-
QgsExpressionBuilderDialog dlg( mLayer, mFeatureList->displayExpression(), this, QStringLiteral( "generic" ), context );
556+
QgsExpressionBuilderDialog dlg( mLayer, mFeatureListView->displayExpression(), this, QStringLiteral( "generic" ), context );
471557
dlg.setWindowTitle( tr( "Expression Based Preview" ) );
472-
dlg.setExpressionText( mFeatureList->displayExpression() );
558+
dlg.setExpressionText( mFeatureListView->displayExpression() );
473559

474560
if ( dlg.exec() == QDialog::Accepted )
475561
{
476-
mFeatureList->setDisplayExpression( dlg.expressionText() );
562+
mFeatureListView->setDisplayExpression( dlg.expressionText() );
477563
mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
478564
mFeatureListPreviewButton->setPopupMode( QToolButton::MenuButtonPopup );
479565
}
480566

481-
setDisplayExpression( mFeatureList->displayExpression() );
567+
setDisplayExpression( mFeatureListView->displayExpression() );
482568
}
483569

484570
void QgsDualView::previewColumnChanged( QAction *previewAction, const QString &expression )
485571
{
486-
if ( !mFeatureList->setDisplayExpression( QStringLiteral( "COALESCE( \"%1\", '<NULL>' )" ).arg( expression ) ) )
572+
if ( !mFeatureListView->setDisplayExpression( QStringLiteral( "COALESCE( \"%1\", '<NULL>' )" ).arg( expression ) ) )
487573
{
488574
QMessageBox::warning( this,
489575
tr( "Column Preview" ),
490576
tr( "Could not set column '%1' as preview column.\nParser error:\n%2" )
491-
.arg( previewAction->text(), mFeatureList->parserErrorString() )
577+
.arg( previewAction->text(), mFeatureListView->parserErrorString() )
492578
);
493579
}
494580
else
@@ -498,7 +584,7 @@ void QgsDualView::previewColumnChanged( QAction *previewAction, const QString &e
498584
mFeatureListPreviewButton->setPopupMode( QToolButton::InstantPopup );
499585
}
500586

501-
setDisplayExpression( mFeatureList->displayExpression() );
587+
setDisplayExpression( mFeatureListView->displayExpression() );
502588
}
503589

504590
int QgsDualView::featureCount()
@@ -841,11 +927,11 @@ void QgsDualView::onSortColumnChanged()
841927
void QgsDualView::sortByPreviewExpression()
842928
{
843929
Qt::SortOrder sortOrder = Qt::AscendingOrder;
844-
if ( mFeatureList->displayExpression() == sortExpression() )
930+
if ( mFeatureListView->displayExpression() == sortExpression() )
845931
{
846932
sortOrder = mConfig.sortOrder() == Qt::AscendingOrder ? Qt::DescendingOrder : Qt::AscendingOrder;
847933
}
848-
setSortExpression( mFeatureList->displayExpression(), sortOrder );
934+
setSortExpression( mFeatureListView->displayExpression(), sortOrder );
849935
}
850936

851937
void QgsDualView::updateSelectedFeatures()
@@ -878,7 +964,7 @@ void QgsDualView::featureFormAttributeChanged( const QString &attribute, const Q
878964
Q_UNUSED( attribute )
879965
Q_UNUSED( value )
880966
if ( attributeChanged )
881-
mFeatureList->setCurrentFeatureEdited( true );
967+
mFeatureListView->setCurrentFeatureEdited( true );
882968
}
883969

884970
void QgsDualView::setFilteredFeatures( const QgsFeatureIds &filteredFeatures )
@@ -894,7 +980,7 @@ void QgsDualView::setRequest( const QgsFeatureRequest &request )
894980
void QgsDualView::setFeatureSelectionManager( QgsIFeatureSelectionManager *featureSelectionManager )
895981
{
896982
mTableView->setFeatureSelectionManager( featureSelectionManager );
897-
mFeatureList->setFeatureSelectionManager( featureSelectionManager );
983+
mFeatureListView->setFeatureSelectionManager( featureSelectionManager );
898984

899985
if ( mFeatureSelectionManager && mFeatureSelectionManager->parent() == this )
900986
delete mFeatureSelectionManager;

‎src/gui/attributetable/qgsdualview.h

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,19 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
6565
*/
6666
AttributeEditor = 1
6767
};
68-
6968
Q_ENUM( ViewMode )
7069

70+
71+
//! Action on the map canvas when browsing the list of features
72+
enum FeatureListBrowsingAction
73+
{
74+
NoAction = 0, //!< No action is done
75+
FlashFeature, //!< The feature is highlighted with a flash
76+
PanToFeature, //!< The map is panned to the center of the feature bounding-box
77+
ZoomToFeature, //!< The map is zoomed to contained the feature bounding-box
78+
};
79+
Q_ENUM( FeatureListBrowsingAction )
80+
7181
/**
7282
* \brief Constructor
7383
* \param parent The parent widget
@@ -83,9 +93,12 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
8393
* \param request Use a modified request to limit the shown features
8494
* \param context The context in which this view is shown
8595
* \param loadFeatures whether to initially load all features into the view. If set to
86-
* FALSE, limited features can later be loaded using setFilterMode()
96+
* FALSE, limited features can later be loaded using setFilterMode()
8797
*/
88-
void init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request = QgsFeatureRequest(), const QgsAttributeEditorContext &context = QgsAttributeEditorContext(),
98+
void init( QgsVectorLayer *layer,
99+
QgsMapCanvas *mapCanvas,
100+
const QgsFeatureRequest &request = QgsFeatureRequest(),
101+
const QgsAttributeEditorContext &context = QgsAttributeEditorContext(),
89102
bool loadFeatures = true );
90103

91104
/**
@@ -286,14 +299,14 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
286299

287300
private slots:
288301

289-
void mFeatureList_aboutToChangeEditSelection( bool &ok );
302+
void featureListAboutToChangeEditSelection( bool &ok );
290303

291304
/**
292305
* Changes the currently visible feature within the attribute editor
293306
*
294307
* \param feat The newly visible feature
295308
*/
296-
void mFeatureList_currentEditSelectionChanged( const QgsFeature &feat );
309+
void featureListCurrentEditSelectionChanged( const QgsFeature &feat );
297310

298311
void previewExpressionBuilder();
299312

@@ -357,6 +370,8 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
357370

358371
void rebuildFullLayerCache();
359372

373+
void panZoomGroupButtonToggled( QAbstractButton *button, bool checked );
374+
360375
private:
361376

362377
/**
@@ -369,6 +384,8 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
369384
void saveRecentDisplayExpressions() const;
370385
void setDisplayExpression( const QString &expression );
371386
void insertRecentlyUsedDisplayExpression( const QString &expression );
387+
void updateEditSelectionProgress( int progress, int count );
388+
void panOrZoomToFeature( const QgsFeatureIds &featureset );
372389

373390
QgsAttributeEditorContext mEditorContext;
374391
QgsAttributeTableModel *mMasterModel = nullptr;

‎src/gui/attributetable/qgsfeaturelistview.cpp

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ void QgsFeatureListView::setModel( QgsFeatureListModel *featureListModel )
4949
mModel = featureListModel;
5050

5151
delete mFeatureSelectionModel;
52+
delete mCurrentEditSelectionModel;
5253

5354
mCurrentEditSelectionModel = new QItemSelectionModel( mModel->masterModel(), this );
5455
if ( !mFeatureSelectionManager )
@@ -169,6 +170,7 @@ void QgsFeatureListView::editSelectionChanged( const QItemSelection &deselected,
169170
mModel->featureByIndex( mModel->mapFromMaster( indexList.first() ), feat );
170171

171172
emit currentEditSelectionChanged( feat );
173+
emit currentEditSelectionProgressChanged( mModel->mapFromMaster( indexList.first() ).row(), mModel->rowCount() );
172174
}
173175
}
174176
}
@@ -259,47 +261,57 @@ void QgsFeatureListView::mouseReleaseEvent( QMouseEvent *event )
259261

260262
void QgsFeatureListView::keyPressEvent( QKeyEvent *event )
261263
{
262-
if ( Qt::Key_Up == event->key() || Qt::Key_Down == event->key() )
264+
switch ( event->key() )
263265
{
264-
int currentRow = 0;
265-
if ( 0 != mCurrentEditSelectionModel->selectedIndexes().count() )
266-
{
267-
QModelIndex localIndex = mModel->mapFromMaster( mCurrentEditSelectionModel->selectedIndexes().first() );
268-
currentRow = localIndex.row();
269-
}
266+
case Qt::Key_Up:
267+
editOtherFeature( Previous );
268+
break;
270269

271-
QModelIndex newLocalIndex;
272-
QModelIndex newIndex;
270+
case Qt::Key_Down:
271+
editOtherFeature( Next );
272+
break;
273273

274-
switch ( event->key() )
275-
{
276-
case Qt::Key_Up:
277-
newLocalIndex = mModel->index( currentRow - 1, 0 );
278-
newIndex = mModel->mapToMaster( newLocalIndex );
279-
if ( newIndex.isValid() )
280-
{
281-
setEditSelection( newIndex, QItemSelectionModel::ClearAndSelect );
282-
scrollTo( newLocalIndex );
283-
}
284-
break;
274+
default:
275+
QListView::keyPressEvent( event );
276+
}
277+
}
285278

286-
case Qt::Key_Down:
287-
newLocalIndex = mModel->index( currentRow + 1, 0 );
288-
newIndex = mModel->mapToMaster( newLocalIndex );
289-
if ( newIndex.isValid() )
290-
{
291-
setEditSelection( newIndex, QItemSelectionModel::ClearAndSelect );
292-
scrollTo( newLocalIndex );
293-
}
294-
break;
279+
void QgsFeatureListView::editOtherFeature( QgsFeatureListView::PositionInList positionInList )
280+
{
281+
int currentRow = 0;
282+
if ( 0 != mCurrentEditSelectionModel->selectedIndexes().count() )
283+
{
284+
QModelIndex localIndex = mModel->mapFromMaster( mCurrentEditSelectionModel->selectedIndexes().first() );
285+
currentRow = localIndex.row();
286+
}
295287

296-
default:
297-
break;
298-
}
288+
QModelIndex newLocalIndex;
289+
QModelIndex newIndex;
290+
291+
switch ( positionInList )
292+
{
293+
case First:
294+
newLocalIndex = mModel->index( 0, 0 );
295+
break;
296+
297+
case Previous:
298+
newLocalIndex = mModel->index( currentRow - 1, 0 );
299+
break;
300+
301+
case Next:
302+
newLocalIndex = mModel->index( currentRow + 1, 0 );
303+
break;
304+
305+
case Last:
306+
newLocalIndex = mModel->index( mModel->rowCount() - 1, 0 );
307+
break;
299308
}
300-
else
309+
310+
newIndex = mModel->mapToMaster( newLocalIndex );
311+
if ( newIndex.isValid() )
301312
{
302-
QListView::keyPressEvent( event );
313+
setEditSelection( newIndex, QItemSelectionModel::ClearAndSelect );
314+
scrollTo( newLocalIndex );
303315
}
304316
}
305317

‎src/gui/attributetable/qgsfeaturelistview.h

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,18 @@ class GUI_EXPORT QgsFeatureListView : public QListView
132132

133133
/**
134134
* Emitted whenever the current edit selection has been changed.
135-
*
136135
* \param feat the feature, which will be edited.
137136
*/
138137
void currentEditSelectionChanged( QgsFeature &feat );
139138

139+
/**
140+
* Emitted whenever the current edit selection has been changed.
141+
* \param progress the position of the feature in the list
142+
* \param count the number of features in the list
143+
* \since QGIS 3.8
144+
*/
145+
void currentEditSelectionProgressChanged( int progress, int count );
146+
140147
/**
141148
* Emitted whenever the display expression is successfully changed
142149
* \param expression The expression that was applied
@@ -178,6 +185,32 @@ class GUI_EXPORT QgsFeatureListView : public QListView
178185
void repaintRequested( const QModelIndexList &indexes );
179186
void repaintRequested();
180187

188+
/**
189+
* editFirstFeature will try to edit the first feature of the list
190+
* \since QGIS 3.8
191+
*/
192+
void editFirstFeature() {editOtherFeature( First );}
193+
194+
/**
195+
* editNextFeature will try to edit next feature of the list
196+
* \since QGIS 3.8
197+
*/
198+
void editNextFeature() {editOtherFeature( Next );}
199+
200+
/**
201+
* editPreviousFeature will try to edit previous feature of the list
202+
* \since QGIS 3.8
203+
*/
204+
void editPreviousFeature() {editOtherFeature( Previous );}
205+
206+
/**
207+
* editLastFeature will try to edit the last feature of the list
208+
* \since QGIS 3.8
209+
*/
210+
void editLastFeature() {editOtherFeature( Last );}
211+
212+
213+
181214
private slots:
182215
void editSelectionChanged( const QItemSelection &deselected, const QItemSelection &selected );
183216

@@ -192,6 +225,16 @@ class GUI_EXPORT QgsFeatureListView : public QListView
192225
private:
193226
void selectRow( const QModelIndex &index, bool anchor );
194227

228+
enum PositionInList
229+
{
230+
First,
231+
Next,
232+
Previous,
233+
Last
234+
};
235+
236+
void editOtherFeature( PositionInList positionInList );
237+
195238

196239
QgsFeatureListModel *mModel = nullptr;
197240
QItemSelectionModel *mCurrentEditSelectionModel = nullptr;

‎src/gui/qgsmapcanvas.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,7 +1074,7 @@ void QgsMapCanvas::zoomToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds
10741074

10751075
}
10761076

1077-
void QgsMapCanvas::panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids )
1077+
void QgsMapCanvas::panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter )
10781078
{
10791079
if ( !layer )
10801080
{
@@ -1085,7 +1085,8 @@ void QgsMapCanvas::panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &
10851085
QString errorMsg;
10861086
if ( boundingBoxOfFeatureIds( ids, layer, bbox, errorMsg ) )
10871087
{
1088-
setCenter( bbox.center() );
1088+
if ( alwaysRecenter || !mapSettings().extent().contains( bbox ) )
1089+
setCenter( bbox.center() );
10891090
refresh();
10901091
}
10911092
else

‎src/gui/qgsmapcanvas.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,11 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView
253253

254254
/**
255255
* Centers canvas extent to feature ids
256-
\param layer the vector layer
257-
\param ids the feature ids*/
258-
void panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids );
256+
* \param layer the vector layer
257+
* \param ids the feature ids
258+
* \param alwaysRecenter if false, the canvas is recentered only if the bounding box is not contained within the current extent
259+
*/
260+
void panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter = true );
259261

260262
//! Pan to the selected features of current (vector) layer keeping same extent.
261263
void panToSelected( QgsVectorLayer *layer = nullptr );

‎src/ui/qgsattributetabledialog.ui

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,10 @@
214214
<property name="checkable">
215215
<bool>true</bool>
216216
</property>
217-
<property name="autoRaise">
217+
<property name="checked">
218218
<bool>true</bool>
219219
</property>
220-
<property name="checked">
220+
<property name="autoRaise">
221221
<bool>true</bool>
222222
</property>
223223
<attribute name="buttonGroup">
@@ -693,35 +693,6 @@
693693
</tabstops>
694694
<resources>
695695
<include location="../../images/images.qrc"/>
696-
<include location="../../images/images.qrc"/>
697-
<include location="../../images/images.qrc"/>
698-
<include location="../../images/images.qrc"/>
699-
<include location="../../images/images.qrc"/>
700-
<include location="../../images/images.qrc"/>
701-
<include location="../../images/images.qrc"/>
702-
<include location="../../images/images.qrc"/>
703-
<include location="../../images/images.qrc"/>
704-
<include location="../../images/images.qrc"/>
705-
<include location="../../images/images.qrc"/>
706-
<include location="../../images/images.qrc"/>
707-
<include location="../../images/images.qrc"/>
708-
<include location="../../images/images.qrc"/>
709-
<include location="../../images/images.qrc"/>
710-
<include location="../../images/images.qrc"/>
711-
<include location="../../images/images.qrc"/>
712-
<include location="../../images/images.qrc"/>
713-
<include location="../../images/images.qrc"/>
714-
<include location="../../images/images.qrc"/>
715-
<include location="../../images/images.qrc"/>
716-
<include location="../../images/images.qrc"/>
717-
<include location="../../images/images.qrc"/>
718-
<include location="../../images/images.qrc"/>
719-
<include location="../../images/images.qrc"/>
720-
<include location="../../images/images.qrc"/>
721-
<include location="../../images/images.qrc"/>
722-
<include location="../../images/images.qrc"/>
723-
<include location="../../images/images.qrc"/>
724-
<include location="../../images/images.qrc"/>
725696
</resources>
726697
<connections>
727698
<connection>

‎src/ui/qgsdualviewbase.ui

Lines changed: 197 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@
7373
</property>
7474
<widget class="QWidget" name="listViewWdg" native="true">
7575
<layout class="QVBoxLayout" name="verticalLayout_2">
76+
<property name="spacing">
77+
<number>2</number>
78+
</property>
7679
<property name="leftMargin">
7780
<number>0</number>
7881
</property>
@@ -111,7 +114,7 @@
111114
</widget>
112115
</item>
113116
<item>
114-
<widget class="QgsFeatureListView" name="mFeatureList">
117+
<widget class="QgsFeatureListView" name="mFeatureListView">
115118
<property name="sizePolicy">
116119
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
117120
<horstretch>0</horstretch>
@@ -120,6 +123,199 @@
120123
</property>
121124
</widget>
122125
</item>
126+
<item>
127+
<widget class="QWidget" name="widget" native="true">
128+
<property name="sizePolicy">
129+
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
130+
<horstretch>0</horstretch>
131+
<verstretch>0</verstretch>
132+
</sizepolicy>
133+
</property>
134+
<layout class="QHBoxLayout" name="horizontalLayout">
135+
<property name="leftMargin">
136+
<number>0</number>
137+
</property>
138+
<property name="topMargin">
139+
<number>0</number>
140+
</property>
141+
<property name="rightMargin">
142+
<number>0</number>
143+
</property>
144+
<property name="bottomMargin">
145+
<number>0</number>
146+
</property>
147+
<item>
148+
<widget class="QPushButton" name="mFirstFeatureButton">
149+
<property name="maximumSize">
150+
<size>
151+
<width>24</width>
152+
<height>24</height>
153+
</size>
154+
</property>
155+
<property name="text">
156+
<string/>
157+
</property>
158+
<property name="icon">
159+
<iconset resource="../../images/images.qrc">
160+
<normaloff>:/images/themes/default/mActionDoubleArrowLeft.svg</normaloff>:/images/themes/default/mActionDoubleArrowLeft.svg</iconset>
161+
</property>
162+
<property name="flat">
163+
<bool>true</bool>
164+
</property>
165+
</widget>
166+
</item>
167+
<item>
168+
<widget class="QPushButton" name="mPreviousFeatureButton">
169+
<property name="maximumSize">
170+
<size>
171+
<width>24</width>
172+
<height>24</height>
173+
</size>
174+
</property>
175+
<property name="text">
176+
<string/>
177+
</property>
178+
<property name="icon">
179+
<iconset resource="../../images/images.qrc">
180+
<normaloff>:/images/themes/default/mActionArrowLeft.svg</normaloff>:/images/themes/default/mActionArrowLeft.svg</iconset>
181+
</property>
182+
<property name="flat">
183+
<bool>true</bool>
184+
</property>
185+
</widget>
186+
</item>
187+
<item>
188+
<widget class="QPushButton" name="mNextFeatureButton">
189+
<property name="sizePolicy">
190+
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
191+
<horstretch>0</horstretch>
192+
<verstretch>0</verstretch>
193+
</sizepolicy>
194+
</property>
195+
<property name="maximumSize">
196+
<size>
197+
<width>24</width>
198+
<height>24</height>
199+
</size>
200+
</property>
201+
<property name="text">
202+
<string/>
203+
</property>
204+
<property name="icon">
205+
<iconset resource="../../images/images.qrc">
206+
<normaloff>:/images/themes/default/mActionArrowRight.svg</normaloff>:/images/themes/default/mActionArrowRight.svg</iconset>
207+
</property>
208+
<property name="flat">
209+
<bool>true</bool>
210+
</property>
211+
</widget>
212+
</item>
213+
<item>
214+
<widget class="QPushButton" name="mLastFeatureButton">
215+
<property name="maximumSize">
216+
<size>
217+
<width>24</width>
218+
<height>24</height>
219+
</size>
220+
</property>
221+
<property name="text">
222+
<string/>
223+
</property>
224+
<property name="icon">
225+
<iconset resource="../../images/images.qrc">
226+
<normaloff>:/images/themes/default/mActionDoubleArrowRight.svg</normaloff>:/images/themes/default/mActionDoubleArrowRight.svg</iconset>
227+
</property>
228+
<property name="flat">
229+
<bool>true</bool>
230+
</property>
231+
</widget>
232+
</item>
233+
<item>
234+
<widget class="QLabel" name="mProgressCount">
235+
<property name="font">
236+
<font>
237+
<pointsize>11</pointsize>
238+
</font>
239+
</property>
240+
<property name="text">
241+
<string>TextLabel</string>
242+
</property>
243+
</widget>
244+
</item>
245+
<item>
246+
<spacer name="horizontalSpacer">
247+
<property name="orientation">
248+
<enum>Qt::Horizontal</enum>
249+
</property>
250+
<property name="sizeHint" stdset="0">
251+
<size>
252+
<width>40</width>
253+
<height>20</height>
254+
</size>
255+
</property>
256+
</spacer>
257+
</item>
258+
<item>
259+
<layout class="QHBoxLayout" name="horizontalLayout_2">
260+
<property name="spacing">
261+
<number>0</number>
262+
</property>
263+
<item>
264+
<widget class="QToolButton" name="mFlashButton">
265+
<property name="toolTip">
266+
<string>Highlight currently edited feature on map</string>
267+
</property>
268+
<property name="text">
269+
<string/>
270+
</property>
271+
<property name="icon">
272+
<iconset resource="../../images/images.qrc">
273+
<normaloff>:/images/themes/default/mActionHighlightFeature.svg</normaloff>:/images/themes/default/mActionHighlightFeature.svg</iconset>
274+
</property>
275+
<property name="checkable">
276+
<bool>true</bool>
277+
</property>
278+
</widget>
279+
</item>
280+
<item>
281+
<widget class="QToolButton" name="mAutoZoomButton">
282+
<property name="toolTip">
283+
<string>automatically zoom to currently edited feature</string>
284+
</property>
285+
<property name="text">
286+
<string/>
287+
</property>
288+
<property name="icon">
289+
<iconset resource="../../images/images.qrc">
290+
<normaloff>:/images/themes/default/mActionPanToSelected.svg</normaloff>:/images/themes/default/mActionPanToSelected.svg</iconset>
291+
</property>
292+
<property name="checkable">
293+
<bool>true</bool>
294+
</property>
295+
</widget>
296+
</item>
297+
<item>
298+
<widget class="QToolButton" name="mAutoPanButton">
299+
<property name="toolTip">
300+
<string>automatically pan to currently edited feature</string>
301+
</property>
302+
<property name="text">
303+
<string/>
304+
</property>
305+
<property name="icon">
306+
<iconset resource="../../images/images.qrc">
307+
<normaloff>:/images/themes/default/mActionZoomToSelected.svg</normaloff>:/images/themes/default/mActionZoomToSelected.svg</iconset>
308+
</property>
309+
<property name="checkable">
310+
<bool>true</bool>
311+
</property>
312+
</widget>
313+
</item>
314+
</layout>
315+
</item>
316+
</layout>
317+
</widget>
318+
</item>
123319
</layout>
124320
</widget>
125321
<widget class="QFrame" name="mAttributeEditor">

‎tests/src/app/testqgsattributetable.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,15 +260,15 @@ void TestQgsAttributeTable::testSortByDisplayExpression()
260260

261261
std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.get() ) );
262262

263-
dlg->mMainView->mFeatureList->setDisplayExpression( "pk" );
263+
dlg->mMainView->mFeatureListView->setDisplayExpression( "pk" );
264264
QgsFeatureListModel *listModel = dlg->mMainView->mFeatureListModel;
265265
QCOMPARE( listModel->rowCount(), 3 );
266266

267267
QCOMPARE( listModel->index( 0, 0 ).data( Qt::DisplayRole ), QVariant( 1 ) );
268268
QCOMPARE( listModel->index( 1, 0 ).data( Qt::DisplayRole ), QVariant( 2 ) );
269269
QCOMPARE( listModel->index( 2, 0 ).data( Qt::DisplayRole ), QVariant( 3 ) );
270270

271-
dlg->mMainView->mFeatureList->setDisplayExpression( "col1" );
271+
dlg->mMainView->mFeatureListView->setDisplayExpression( "col1" );
272272
QCOMPARE( listModel->index( 0, 0 ).data( Qt::DisplayRole ), QVariant( 1.8 ) );
273273
QCOMPARE( listModel->index( 1, 0 ).data( Qt::DisplayRole ), QVariant( 3.2 ) );
274274
QCOMPARE( listModel->index( 2, 0 ).data( Qt::DisplayRole ), QVariant( 5.0 ) );

0 commit comments

Comments
 (0)
Please sign in to comment.