diff --git a/images/images.qrc b/images/images.qrc
index 73d86eb2b70..bd3dd6d68be 100644
--- a/images/images.qrc
+++ b/images/images.qrc
@@ -751,6 +751,8 @@
themes/default/mIconDataDefineColor.svg
themes/default/mIconDataDefineColorOn.svg
themes/default/mActionNewVirtualLayer.svg
+ themes/default/mActionDoubleArrowRight.svg
+ themes/default/mActionDoubleArrowLeft.svg
qgis_tips/symbol_levels.png
diff --git a/images/themes/default/mActionDoubleArrowLeft.svg b/images/themes/default/mActionDoubleArrowLeft.svg
new file mode 100644
index 00000000000..c7ae720b545
--- /dev/null
+++ b/images/themes/default/mActionDoubleArrowLeft.svg
@@ -0,0 +1,81 @@
+
+
diff --git a/images/themes/default/mActionDoubleArrowRight.svg b/images/themes/default/mActionDoubleArrowRight.svg
new file mode 100644
index 00000000000..55667f23ca4
--- /dev/null
+++ b/images/themes/default/mActionDoubleArrowRight.svg
@@ -0,0 +1,81 @@
+
+
diff --git a/python/gui/auto_additions/qgsdualview.py b/python/gui/auto_additions/qgsdualview.py
index f394b308178..e10d39dd634 100644
--- a/python/gui/auto_additions/qgsdualview.py
+++ b/python/gui/auto_additions/qgsdualview.py
@@ -1,2 +1,3 @@
# The following has been generated automatically from src/gui/attributetable/qgsdualview.h
QgsDualView.ViewMode.baseClass = QgsDualView
+QgsDualView.FeatureListBrowsingAction.baseClass = QgsDualView
diff --git a/python/gui/auto_generated/attributetable/qgsdualview.sip.in b/python/gui/auto_generated/attributetable/qgsdualview.sip.in
index 87b67fefd7c..f7a54f707b9 100644
--- a/python/gui/auto_generated/attributetable/qgsdualview.sip.in
+++ b/python/gui/auto_generated/attributetable/qgsdualview.sip.in
@@ -35,6 +35,14 @@ and the attributes for the currently selected feature are shown in a form.
};
+ enum FeatureListBrowsingAction
+ {
+ NoAction,
+ FlashFeature,
+ PanToFeature,
+ ZoomToFeature,
+ };
+
explicit QgsDualView( QWidget *parent /TransferThis/ = 0 );
%Docstring
Constructor
@@ -42,7 +50,10 @@ Constructor
:param parent: The parent widget
%End
- void init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request = QgsFeatureRequest(), const QgsAttributeEditorContext &context = QgsAttributeEditorContext(),
+ void init( QgsVectorLayer *layer,
+ QgsMapCanvas *mapCanvas,
+ const QgsFeatureRequest &request = QgsFeatureRequest(),
+ const QgsAttributeEditorContext &context = QgsAttributeEditorContext(),
bool loadFeatures = true );
%Docstring
Has to be called to initialize the dual view.
diff --git a/python/gui/auto_generated/attributetable/qgsfeaturelistview.sip.in b/python/gui/auto_generated/attributetable/qgsfeaturelistview.sip.in
index dc9fc483b24..0fe6eb39ed9 100644
--- a/python/gui/auto_generated/attributetable/qgsfeaturelistview.sip.in
+++ b/python/gui/auto_generated/attributetable/qgsfeaturelistview.sip.in
@@ -118,6 +118,16 @@ setFeatureSelectionManager
Emitted whenever the current edit selection has been changed.
:param feat: the feature, which will be edited.
+%End
+
+ void currentEditSelectionProgressChanged( int progress, int count );
+%Docstring
+Emitted whenever the current edit selection has been changed.
+
+:param progress: the position of the feature in the list
+:param count: the number of features in the list
+
+.. versionadded:: 3.8
%End
void displayExpressionChanged( const QString &expression );
@@ -162,6 +172,36 @@ Select all currently visible features
void repaintRequested( const QModelIndexList &indexes );
void repaintRequested();
+ void editFirstFeature();
+%Docstring
+editFirstFeature will try to edit the first feature of the list
+
+.. versionadded:: 3.8
+%End
+
+ void editNextFeature();
+%Docstring
+editNextFeature will try to edit next feature of the list
+
+.. versionadded:: 3.8
+%End
+
+ void editPreviousFeature();
+%Docstring
+editPreviousFeature will try to edit previous feature of the list
+
+.. versionadded:: 3.8
+%End
+
+ void editLastFeature();
+%Docstring
+editLastFeature will try to edit the last feature of the list
+
+.. versionadded:: 3.8
+%End
+
+
+
};
/************************************************************************
diff --git a/python/gui/auto_generated/qgsmapcanvas.sip.in b/python/gui/auto_generated/qgsmapcanvas.sip.in
index fb3ca85f7e7..0d9238634b6 100644
--- a/python/gui/auto_generated/qgsmapcanvas.sip.in
+++ b/python/gui/auto_generated/qgsmapcanvas.sip.in
@@ -232,12 +232,13 @@ Set canvas extent to the bounding box of a set of features
:param ids: the feature ids*
%End
- void panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids );
+ void panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter = true );
%Docstring
Centers canvas extent to feature ids
:param layer: the vector layer
-:param ids: the feature ids*
+:param ids: the feature ids
+:param alwaysRecenter: if false, the canvas is recentered only if the bounding box is not contained within the current extent
%End
void panToSelected( QgsVectorLayer *layer = 0 );
diff --git a/src/gui/attributetable/qgsdualview.cpp b/src/gui/attributetable/qgsdualview.cpp
index f8423bca2c4..9e4059aa2f6 100644
--- a/src/gui/attributetable/qgsdualview.cpp
+++ b/src/gui/attributetable/qgsdualview.cpp
@@ -13,6 +13,15 @@
* *
***************************************************************************/
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
#include "qgsapplication.h"
#include "qgsactionmanager.h"
#include "qgsattributetablemodel.h"
@@ -32,20 +41,14 @@
#include "qgsgui.h"
#include "qgsexpressioncontextutils.h"
-#include
-#include
-#include
-#include
-#include
-#include
-#include
QgsDualView::QgsDualView( QWidget *parent )
: QStackedWidget( parent )
{
setupUi( this );
- connect( mFeatureList, &QgsFeatureListView::aboutToChangeEditSelection, this, &QgsDualView::mFeatureList_aboutToChangeEditSelection );
- connect( mFeatureList, &QgsFeatureListView::currentEditSelectionChanged, this, &QgsDualView::mFeatureList_currentEditSelectionChanged );
+ connect( mFeatureListView, &QgsFeatureListView::aboutToChangeEditSelection, this, &QgsDualView::featureListAboutToChangeEditSelection );
+ connect( mFeatureListView, &QgsFeatureListView::currentEditSelectionChanged, this, &QgsDualView::featureListCurrentEditSelectionChanged );
+ connect( mFeatureListView, &QgsFeatureListView::currentEditSelectionProgressChanged, this, &QgsDualView::updateEditSelectionProgress );
mConditionalFormatWidget->hide();
@@ -57,23 +60,39 @@ QgsDualView::QgsDualView( QWidget *parent )
// Connect layer list preview signals
connect( mActionExpressionPreview, &QAction::triggered, this, &QgsDualView::previewExpressionBuilder );
- connect( mFeatureList, &QgsFeatureListView::displayExpressionChanged, this, &QgsDualView::previewExpressionChanged );
+ connect( mFeatureListView, &QgsFeatureListView::displayExpressionChanged, this, &QgsDualView::previewExpressionChanged );
+
+ connect( mNextFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editNextFeature );
+ connect( mPreviousFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editPreviousFeature );
+ connect( mFirstFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editFirstFeature );
+ connect( mLastFeatureButton, &QToolButton::clicked, mFeatureListView, &QgsFeatureListView::editLastFeature );
+
+ QButtonGroup *buttonGroup = new QButtonGroup( this );
+ buttonGroup->setExclusive( false );
+ buttonGroup->addButton( mFlashButton, FlashFeature );
+ buttonGroup->addButton( mAutoPanButton, PanToFeature );
+ buttonGroup->addButton( mAutoZoomButton, ZoomToFeature );
+ FeatureListBrowsingAction action = QgsSettings().enumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), FlashFeature );
+ QAbstractButton *bt = buttonGroup->button( static_cast( action ) );
+ if ( bt )
+ bt->setChecked( true );
+ connect( buttonGroup, qgis::overload< QAbstractButton *, bool >::of( &QButtonGroup::buttonToggled ), this, &QgsDualView::panZoomGroupButtonToggled );
}
-void QgsDualView::init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request, const QgsAttributeEditorContext &context, bool loadFeatures )
+void QgsDualView::init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request,
+ const QgsAttributeEditorContext &context, bool loadFeatures )
{
if ( !layer )
return;
mLayer = layer;
-
mEditorContext = context;
connect( mTableView, &QgsAttributeTableView::willShowContextMenu, this, &QgsDualView::viewWillShowContextMenu );
mTableView->horizontalHeader()->setContextMenuPolicy( Qt::CustomContextMenu );
connect( mTableView->horizontalHeader(), &QHeaderView::customContextMenuRequested, this, &QgsDualView::showViewHeaderMenu );
connect( mTableView, &QgsAttributeTableView::columnResized, this, &QgsDualView::tableColumnResized );
- connect( mFeatureList, &QgsFeatureListView::willShowContextMenu, this, &QgsDualView::widgetWillShowContextMenu );
+ connect( mFeatureListView, &QgsFeatureListView::willShowContextMenu, this, &QgsDualView::widgetWillShowContextMenu );
initLayerCache( !( request.flags() & QgsFeatureRequest::NoGeometry ) || !request.filterRect().isNull() );
initModels( mapCanvas, request, loadFeatures );
@@ -81,7 +100,7 @@ void QgsDualView::init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const Qg
mConditionalFormatWidget->setLayer( mLayer );
mTableView->setModel( mFilterModel );
- mFeatureList->setModel( mFeatureListModel );
+ mFeatureListView->setModel( mFeatureListModel );
delete mAttributeForm;
mAttributeForm = new QgsAttributeForm( mLayer, mTempAttributeFormFeature, mEditorContext );
mTempAttributeFormFeature = QgsFeature();
@@ -104,7 +123,7 @@ void QgsDualView::init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const Qg
connect( mFilterModel, &QgsAttributeTableFilterModel::sortColumnChanged, this, &QgsDualView::onSortColumnChanged );
if ( mFeatureListPreviewButton->defaultAction() )
- mFeatureList->setDisplayExpression( mDisplayExpression );
+ mFeatureListView->setDisplayExpression( mDisplayExpression );
else
columnBoxInit();
@@ -168,9 +187,9 @@ void QgsDualView::columnBoxInit()
// If there is no single field found as preview
if ( !mFeatureListPreviewButton->defaultAction() )
{
- mFeatureList->setDisplayExpression( displayExpression );
+ mFeatureListView->setDisplayExpression( displayExpression );
mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
- setDisplayExpression( mFeatureList->displayExpression() );
+ setDisplayExpression( mFeatureListView->displayExpression() );
}
else
{
@@ -302,7 +321,7 @@ void QgsDualView::initModels( QgsMapCanvas *mapCanvas, const QgsFeatureRequest &
connect( mMasterModel, &QgsAttributeTableModel::rowsRemoved, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
connect( mMasterModel, &QgsAttributeTableModel::rowsInserted, mFilterModel, &QgsAttributeTableFilterModel::invalidate );
- connect( mFeatureList, &QgsFeatureListView::displayExpressionChanged, this, &QgsDualView::displayExpressionChanged );
+ connect( mFeatureListView, &QgsFeatureListView::displayExpressionChanged, this, &QgsDualView::displayExpressionChanged );
mFeatureListModel = new QgsFeatureListModel( mFilterModel, mFilterModel );
mFeatureListModel->setSortByDisplayExpression( true );
@@ -401,13 +420,76 @@ void QgsDualView::insertRecentlyUsedDisplayExpression( const QString &expression
mLastDisplayExpressionAction = previewAction;
}
-void QgsDualView::mFeatureList_aboutToChangeEditSelection( bool &ok )
+void QgsDualView::updateEditSelectionProgress( int progress, int count )
+{
+ mProgressCount->setText( QStringLiteral( "%1 / %2" ).arg( progress + 1 ).arg( count ) );
+ mPreviousFeatureButton->setEnabled( progress > 0 );
+ mNextFeatureButton->setEnabled( progress + 1 < count );
+ mFirstFeatureButton->setEnabled( progress > 0 );
+ mLastFeatureButton->setEnabled( progress + 1 < count );
+}
+
+void QgsDualView::panOrZoomToFeature( const QgsFeatureIds &featureset )
+{
+ QgsMapCanvas *canvas = mFilterModel->mapCanvas();
+ if ( canvas )
+ {
+ if ( mAutoPanButton->isChecked() )
+ QTimer::singleShot( 0, this, [ = ]()
+ {
+ canvas->panToFeatureIds( mLayer, featureset, false );
+ canvas->flashFeatureIds( mLayer, featureset );
+ } );
+ else if ( mAutoZoomButton->isChecked() )
+ QTimer::singleShot( 0, this, [ = ]()
+ {
+ canvas->zoomToFeatureIds( mLayer, featureset );
+ canvas->flashFeatureIds( mLayer, featureset );
+ } );
+ else if ( mFlashButton->isChecked() )
+ QTimer::singleShot( 0, this, [ = ]()
+ {
+ canvas->flashFeatureIds( mLayer, featureset );
+ } );
+ }
+}
+
+void QgsDualView::panZoomGroupButtonToggled( QAbstractButton *button, bool checked )
+{
+ if ( button == mAutoPanButton && checked )
+ {
+ QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), PanToFeature );
+ mAutoZoomButton->setChecked( false );
+ mFlashButton->setChecked( false );
+ }
+ else if ( button == mAutoZoomButton && checked )
+ {
+ QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), ZoomToFeature );
+ mAutoPanButton->setChecked( false );
+ mFlashButton->setChecked( false );
+ }
+ else if ( button == mFlashButton && checked )
+ {
+ QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), FlashFeature );
+ mAutoZoomButton->setChecked( false );
+ mAutoPanButton->setChecked( false );
+ }
+ else
+ {
+ QgsSettings().setEnumValue( QStringLiteral( "/qgis/attributeTable/featureListBrowsingAction" ), NoAction );
+ }
+
+ if ( checked )
+ panOrZoomToFeature( mFeatureListView->currentEditSelection() );
+}
+
+void QgsDualView::featureListAboutToChangeEditSelection( bool &ok )
{
if ( mLayer->isEditable() && !mAttributeForm->save() )
ok = false;
}
-void QgsDualView::mFeatureList_currentEditSelectionChanged( const QgsFeature &feat )
+void QgsDualView::featureListCurrentEditSelectionChanged( const QgsFeature &feat )
{
if ( !mAttributeForm )
{
@@ -416,7 +498,11 @@ void QgsDualView::mFeatureList_currentEditSelectionChanged( const QgsFeature &fe
else if ( !mLayer->isEditable() || mAttributeForm->save() )
{
mAttributeForm->setFeature( feat );
- setCurrentEditSelection( QgsFeatureIds() << feat.id() );
+ QgsFeatureIds featureset;
+ featureset << feat.id();
+ setCurrentEditSelection( featureset );
+
+ panOrZoomToFeature( featureset );
}
else
{
@@ -426,8 +512,8 @@ void QgsDualView::mFeatureList_currentEditSelectionChanged( const QgsFeature &fe
void QgsDualView::setCurrentEditSelection( const QgsFeatureIds &fids )
{
- mFeatureList->setCurrentFeatureEdited( false );
- mFeatureList->setEditSelection( fids );
+ mFeatureListView->setCurrentFeatureEdited( false );
+ mFeatureListView->setEditSelection( fids );
}
bool QgsDualView::saveEditChanges()
@@ -467,28 +553,28 @@ void QgsDualView::previewExpressionBuilder()
// Show expression builder
QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) );
- QgsExpressionBuilderDialog dlg( mLayer, mFeatureList->displayExpression(), this, QStringLiteral( "generic" ), context );
+ QgsExpressionBuilderDialog dlg( mLayer, mFeatureListView->displayExpression(), this, QStringLiteral( "generic" ), context );
dlg.setWindowTitle( tr( "Expression Based Preview" ) );
- dlg.setExpressionText( mFeatureList->displayExpression() );
+ dlg.setExpressionText( mFeatureListView->displayExpression() );
if ( dlg.exec() == QDialog::Accepted )
{
- mFeatureList->setDisplayExpression( dlg.expressionText() );
+ mFeatureListView->setDisplayExpression( dlg.expressionText() );
mFeatureListPreviewButton->setDefaultAction( mActionExpressionPreview );
mFeatureListPreviewButton->setPopupMode( QToolButton::MenuButtonPopup );
}
- setDisplayExpression( mFeatureList->displayExpression() );
+ setDisplayExpression( mFeatureListView->displayExpression() );
}
void QgsDualView::previewColumnChanged( QAction *previewAction, const QString &expression )
{
- if ( !mFeatureList->setDisplayExpression( QStringLiteral( "COALESCE( \"%1\", '' )" ).arg( expression ) ) )
+ if ( !mFeatureListView->setDisplayExpression( QStringLiteral( "COALESCE( \"%1\", '' )" ).arg( expression ) ) )
{
QMessageBox::warning( this,
tr( "Column Preview" ),
tr( "Could not set column '%1' as preview column.\nParser error:\n%2" )
- .arg( previewAction->text(), mFeatureList->parserErrorString() )
+ .arg( previewAction->text(), mFeatureListView->parserErrorString() )
);
}
else
@@ -498,7 +584,7 @@ void QgsDualView::previewColumnChanged( QAction *previewAction, const QString &e
mFeatureListPreviewButton->setPopupMode( QToolButton::InstantPopup );
}
- setDisplayExpression( mFeatureList->displayExpression() );
+ setDisplayExpression( mFeatureListView->displayExpression() );
}
int QgsDualView::featureCount()
@@ -841,11 +927,11 @@ void QgsDualView::onSortColumnChanged()
void QgsDualView::sortByPreviewExpression()
{
Qt::SortOrder sortOrder = Qt::AscendingOrder;
- if ( mFeatureList->displayExpression() == sortExpression() )
+ if ( mFeatureListView->displayExpression() == sortExpression() )
{
sortOrder = mConfig.sortOrder() == Qt::AscendingOrder ? Qt::DescendingOrder : Qt::AscendingOrder;
}
- setSortExpression( mFeatureList->displayExpression(), sortOrder );
+ setSortExpression( mFeatureListView->displayExpression(), sortOrder );
}
void QgsDualView::updateSelectedFeatures()
@@ -878,7 +964,7 @@ void QgsDualView::featureFormAttributeChanged( const QString &attribute, const Q
Q_UNUSED( attribute )
Q_UNUSED( value )
if ( attributeChanged )
- mFeatureList->setCurrentFeatureEdited( true );
+ mFeatureListView->setCurrentFeatureEdited( true );
}
void QgsDualView::setFilteredFeatures( const QgsFeatureIds &filteredFeatures )
@@ -894,7 +980,7 @@ void QgsDualView::setRequest( const QgsFeatureRequest &request )
void QgsDualView::setFeatureSelectionManager( QgsIFeatureSelectionManager *featureSelectionManager )
{
mTableView->setFeatureSelectionManager( featureSelectionManager );
- mFeatureList->setFeatureSelectionManager( featureSelectionManager );
+ mFeatureListView->setFeatureSelectionManager( featureSelectionManager );
if ( mFeatureSelectionManager && mFeatureSelectionManager->parent() == this )
delete mFeatureSelectionManager;
diff --git a/src/gui/attributetable/qgsdualview.h b/src/gui/attributetable/qgsdualview.h
index 66eebb8749a..a9605cfbe0a 100644
--- a/src/gui/attributetable/qgsdualview.h
+++ b/src/gui/attributetable/qgsdualview.h
@@ -65,9 +65,19 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
*/
AttributeEditor = 1
};
-
Q_ENUM( ViewMode )
+
+ //! Action on the map canvas when browsing the list of features
+ enum FeatureListBrowsingAction
+ {
+ NoAction = 0, //!< No action is done
+ FlashFeature, //!< The feature is highlighted with a flash
+ PanToFeature, //!< The map is panned to the center of the feature bounding-box
+ ZoomToFeature, //!< The map is zoomed to contained the feature bounding-box
+ };
+ Q_ENUM( FeatureListBrowsingAction )
+
/**
* \brief Constructor
* \param parent The parent widget
@@ -83,9 +93,12 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
* \param request Use a modified request to limit the shown features
* \param context The context in which this view is shown
* \param loadFeatures whether to initially load all features into the view. If set to
- * FALSE, limited features can later be loaded using setFilterMode()
+ * FALSE, limited features can later be loaded using setFilterMode()
*/
- void init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsFeatureRequest &request = QgsFeatureRequest(), const QgsAttributeEditorContext &context = QgsAttributeEditorContext(),
+ void init( QgsVectorLayer *layer,
+ QgsMapCanvas *mapCanvas,
+ const QgsFeatureRequest &request = QgsFeatureRequest(),
+ const QgsAttributeEditorContext &context = QgsAttributeEditorContext(),
bool loadFeatures = true );
/**
@@ -286,14 +299,14 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
private slots:
- void mFeatureList_aboutToChangeEditSelection( bool &ok );
+ void featureListAboutToChangeEditSelection( bool &ok );
/**
* Changes the currently visible feature within the attribute editor
*
* \param feat The newly visible feature
*/
- void mFeatureList_currentEditSelectionChanged( const QgsFeature &feat );
+ void featureListCurrentEditSelectionChanged( const QgsFeature &feat );
void previewExpressionBuilder();
@@ -357,6 +370,8 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
void rebuildFullLayerCache();
+ void panZoomGroupButtonToggled( QAbstractButton *button, bool checked );
+
private:
/**
@@ -369,6 +384,8 @@ class GUI_EXPORT QgsDualView : public QStackedWidget, private Ui::QgsDualViewBas
void saveRecentDisplayExpressions() const;
void setDisplayExpression( const QString &expression );
void insertRecentlyUsedDisplayExpression( const QString &expression );
+ void updateEditSelectionProgress( int progress, int count );
+ void panOrZoomToFeature( const QgsFeatureIds &featureset );
QgsAttributeEditorContext mEditorContext;
QgsAttributeTableModel *mMasterModel = nullptr;
diff --git a/src/gui/attributetable/qgsfeaturelistview.cpp b/src/gui/attributetable/qgsfeaturelistview.cpp
index cd830479c12..49f4ffe8f50 100644
--- a/src/gui/attributetable/qgsfeaturelistview.cpp
+++ b/src/gui/attributetable/qgsfeaturelistview.cpp
@@ -49,6 +49,7 @@ void QgsFeatureListView::setModel( QgsFeatureListModel *featureListModel )
mModel = featureListModel;
delete mFeatureSelectionModel;
+ delete mCurrentEditSelectionModel;
mCurrentEditSelectionModel = new QItemSelectionModel( mModel->masterModel(), this );
if ( !mFeatureSelectionManager )
@@ -169,6 +170,7 @@ void QgsFeatureListView::editSelectionChanged( const QItemSelection &deselected,
mModel->featureByIndex( mModel->mapFromMaster( indexList.first() ), feat );
emit currentEditSelectionChanged( feat );
+ emit currentEditSelectionProgressChanged( mModel->mapFromMaster( indexList.first() ).row(), mModel->rowCount() );
}
}
}
@@ -259,47 +261,57 @@ void QgsFeatureListView::mouseReleaseEvent( QMouseEvent *event )
void QgsFeatureListView::keyPressEvent( QKeyEvent *event )
{
- if ( Qt::Key_Up == event->key() || Qt::Key_Down == event->key() )
+ switch ( event->key() )
{
- int currentRow = 0;
- if ( 0 != mCurrentEditSelectionModel->selectedIndexes().count() )
- {
- QModelIndex localIndex = mModel->mapFromMaster( mCurrentEditSelectionModel->selectedIndexes().first() );
- currentRow = localIndex.row();
- }
+ case Qt::Key_Up:
+ editOtherFeature( Previous );
+ break;
- QModelIndex newLocalIndex;
- QModelIndex newIndex;
+ case Qt::Key_Down:
+ editOtherFeature( Next );
+ break;
- switch ( event->key() )
- {
- case Qt::Key_Up:
- newLocalIndex = mModel->index( currentRow - 1, 0 );
- newIndex = mModel->mapToMaster( newLocalIndex );
- if ( newIndex.isValid() )
- {
- setEditSelection( newIndex, QItemSelectionModel::ClearAndSelect );
- scrollTo( newLocalIndex );
- }
- break;
-
- case Qt::Key_Down:
- newLocalIndex = mModel->index( currentRow + 1, 0 );
- newIndex = mModel->mapToMaster( newLocalIndex );
- if ( newIndex.isValid() )
- {
- setEditSelection( newIndex, QItemSelectionModel::ClearAndSelect );
- scrollTo( newLocalIndex );
- }
- break;
-
- default:
- break;
- }
+ default:
+ QListView::keyPressEvent( event );
}
- else
+}
+
+void QgsFeatureListView::editOtherFeature( QgsFeatureListView::PositionInList positionInList )
+{
+ int currentRow = 0;
+ if ( 0 != mCurrentEditSelectionModel->selectedIndexes().count() )
{
- QListView::keyPressEvent( event );
+ QModelIndex localIndex = mModel->mapFromMaster( mCurrentEditSelectionModel->selectedIndexes().first() );
+ currentRow = localIndex.row();
+ }
+
+ QModelIndex newLocalIndex;
+ QModelIndex newIndex;
+
+ switch ( positionInList )
+ {
+ case First:
+ newLocalIndex = mModel->index( 0, 0 );
+ break;
+
+ case Previous:
+ newLocalIndex = mModel->index( currentRow - 1, 0 );
+ break;
+
+ case Next:
+ newLocalIndex = mModel->index( currentRow + 1, 0 );
+ break;
+
+ case Last:
+ newLocalIndex = mModel->index( mModel->rowCount() - 1, 0 );
+ break;
+ }
+
+ newIndex = mModel->mapToMaster( newLocalIndex );
+ if ( newIndex.isValid() )
+ {
+ setEditSelection( newIndex, QItemSelectionModel::ClearAndSelect );
+ scrollTo( newLocalIndex );
}
}
diff --git a/src/gui/attributetable/qgsfeaturelistview.h b/src/gui/attributetable/qgsfeaturelistview.h
index 7c0e3835380..81faccd75f0 100644
--- a/src/gui/attributetable/qgsfeaturelistview.h
+++ b/src/gui/attributetable/qgsfeaturelistview.h
@@ -132,11 +132,18 @@ class GUI_EXPORT QgsFeatureListView : public QListView
/**
* Emitted whenever the current edit selection has been changed.
- *
* \param feat the feature, which will be edited.
*/
void currentEditSelectionChanged( QgsFeature &feat );
+ /**
+ * Emitted whenever the current edit selection has been changed.
+ * \param progress the position of the feature in the list
+ * \param count the number of features in the list
+ * \since QGIS 3.8
+ */
+ void currentEditSelectionProgressChanged( int progress, int count );
+
/**
* Emitted whenever the display expression is successfully changed
* \param expression The expression that was applied
@@ -178,6 +185,32 @@ class GUI_EXPORT QgsFeatureListView : public QListView
void repaintRequested( const QModelIndexList &indexes );
void repaintRequested();
+ /**
+ * editFirstFeature will try to edit the first feature of the list
+ * \since QGIS 3.8
+ */
+ void editFirstFeature() {editOtherFeature( First );}
+
+ /**
+ * editNextFeature will try to edit next feature of the list
+ * \since QGIS 3.8
+ */
+ void editNextFeature() {editOtherFeature( Next );}
+
+ /**
+ * editPreviousFeature will try to edit previous feature of the list
+ * \since QGIS 3.8
+ */
+ void editPreviousFeature() {editOtherFeature( Previous );}
+
+ /**
+ * editLastFeature will try to edit the last feature of the list
+ * \since QGIS 3.8
+ */
+ void editLastFeature() {editOtherFeature( Last );}
+
+
+
private slots:
void editSelectionChanged( const QItemSelection &deselected, const QItemSelection &selected );
@@ -192,6 +225,16 @@ class GUI_EXPORT QgsFeatureListView : public QListView
private:
void selectRow( const QModelIndex &index, bool anchor );
+ enum PositionInList
+ {
+ First,
+ Next,
+ Previous,
+ Last
+ };
+
+ void editOtherFeature( PositionInList positionInList );
+
QgsFeatureListModel *mModel = nullptr;
QItemSelectionModel *mCurrentEditSelectionModel = nullptr;
diff --git a/src/gui/qgsmapcanvas.cpp b/src/gui/qgsmapcanvas.cpp
index b500d40847d..ac78c99d0e5 100644
--- a/src/gui/qgsmapcanvas.cpp
+++ b/src/gui/qgsmapcanvas.cpp
@@ -1074,7 +1074,7 @@ void QgsMapCanvas::zoomToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds
}
-void QgsMapCanvas::panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids )
+void QgsMapCanvas::panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter )
{
if ( !layer )
{
@@ -1085,7 +1085,8 @@ void QgsMapCanvas::panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &
QString errorMsg;
if ( boundingBoxOfFeatureIds( ids, layer, bbox, errorMsg ) )
{
- setCenter( bbox.center() );
+ if ( alwaysRecenter || !mapSettings().extent().contains( bbox ) )
+ setCenter( bbox.center() );
refresh();
}
else
diff --git a/src/gui/qgsmapcanvas.h b/src/gui/qgsmapcanvas.h
index 86d81611613..9972d76fe10 100644
--- a/src/gui/qgsmapcanvas.h
+++ b/src/gui/qgsmapcanvas.h
@@ -253,9 +253,11 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView
/**
* Centers canvas extent to feature ids
- \param layer the vector layer
- \param ids the feature ids*/
- void panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids );
+ * \param layer the vector layer
+ * \param ids the feature ids
+ * \param alwaysRecenter if false, the canvas is recentered only if the bounding box is not contained within the current extent
+ */
+ void panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter = true );
//! Pan to the selected features of current (vector) layer keeping same extent.
void panToSelected( QgsVectorLayer *layer = nullptr );
diff --git a/src/ui/qgsattributetabledialog.ui b/src/ui/qgsattributetabledialog.ui
index 54901fd8c2b..a99ea6a71a4 100644
--- a/src/ui/qgsattributetabledialog.ui
+++ b/src/ui/qgsattributetabledialog.ui
@@ -214,10 +214,10 @@
true
-
+
true
-
+
true
@@ -693,35 +693,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/ui/qgsdualviewbase.ui b/src/ui/qgsdualviewbase.ui
index b74ae977940..43122234bee 100644
--- a/src/ui/qgsdualviewbase.ui
+++ b/src/ui/qgsdualviewbase.ui
@@ -73,6 +73,9 @@
+
+ 2
+
0
@@ -111,7 +114,7 @@
-
-
+
0
@@ -120,6 +123,199 @@
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 24
+ 24
+
+
+
+
+
+
+
+ :/images/themes/default/mActionDoubleArrowLeft.svg:/images/themes/default/mActionDoubleArrowLeft.svg
+
+
+ true
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+
+
+
+
+ :/images/themes/default/mActionArrowLeft.svg:/images/themes/default/mActionArrowLeft.svg
+
+
+ true
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 24
+ 24
+
+
+
+
+
+
+
+ :/images/themes/default/mActionArrowRight.svg:/images/themes/default/mActionArrowRight.svg
+
+
+ true
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+
+
+
+
+ :/images/themes/default/mActionDoubleArrowRight.svg:/images/themes/default/mActionDoubleArrowRight.svg
+
+
+ true
+
+
+
+ -
+
+
+
+ 11
+
+
+
+ TextLabel
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ Highlight currently edited feature on map
+
+
+
+
+
+
+ :/images/themes/default/mActionHighlightFeature.svg:/images/themes/default/mActionHighlightFeature.svg
+
+
+ true
+
+
+
+ -
+
+
+ automatically zoom to currently edited feature
+
+
+
+
+
+
+ :/images/themes/default/mActionPanToSelected.svg:/images/themes/default/mActionPanToSelected.svg
+
+
+ true
+
+
+
+ -
+
+
+ automatically pan to currently edited feature
+
+
+
+
+
+
+ :/images/themes/default/mActionZoomToSelected.svg:/images/themes/default/mActionZoomToSelected.svg
+
+
+ true
+
+
+
+
+
+
+
+
diff --git a/tests/src/app/testqgsattributetable.cpp b/tests/src/app/testqgsattributetable.cpp
index 6969559a3d7..e0496215f67 100644
--- a/tests/src/app/testqgsattributetable.cpp
+++ b/tests/src/app/testqgsattributetable.cpp
@@ -260,7 +260,7 @@ void TestQgsAttributeTable::testSortByDisplayExpression()
std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.get() ) );
- dlg->mMainView->mFeatureList->setDisplayExpression( "pk" );
+ dlg->mMainView->mFeatureListView->setDisplayExpression( "pk" );
QgsFeatureListModel *listModel = dlg->mMainView->mFeatureListModel;
QCOMPARE( listModel->rowCount(), 3 );
@@ -268,7 +268,7 @@ void TestQgsAttributeTable::testSortByDisplayExpression()
QCOMPARE( listModel->index( 1, 0 ).data( Qt::DisplayRole ), QVariant( 2 ) );
QCOMPARE( listModel->index( 2, 0 ).data( Qt::DisplayRole ), QVariant( 3 ) );
- dlg->mMainView->mFeatureList->setDisplayExpression( "col1" );
+ dlg->mMainView->mFeatureListView->setDisplayExpression( "col1" );
QCOMPARE( listModel->index( 0, 0 ).data( Qt::DisplayRole ), QVariant( 1.8 ) );
QCOMPARE( listModel->index( 1, 0 ).data( Qt::DisplayRole ), QVariant( 3.2 ) );
QCOMPARE( listModel->index( 2, 0 ).data( Qt::DisplayRole ), QVariant( 5.0 ) );