From f680466bf7519857c2ae526fcfafcee2ee1d1b68 Mon Sep 17 00:00:00 2001 From: Denis Rouzaud Date: Tue, 7 May 2019 15:06:00 -0500 Subject: [PATCH] [FEATURE] add toolbar in attribute table to browse feature list arrows allow browsing the feature list in the attribute table in form view the current edited feature can be highlighted and the map canvas automatically panned or zoomed --- python/gui/auto_additions/qgsdualview.py | 1 + .../attributetable/qgsdualview.sip.in | 13 +- .../attributetable/qgsfeaturelistview.sip.in | 24 +++ python/gui/auto_generated/qgsmapcanvas.sip.in | 5 +- src/gui/attributetable/qgsdualview.cpp | 150 ++++++++++++++---- src/gui/attributetable/qgsdualview.h | 27 +++- src/gui/attributetable/qgsfeaturelistview.cpp | 80 ++++++---- src/gui/attributetable/qgsfeaturelistview.h | 29 +++- src/gui/qgsmapcanvas.cpp | 5 +- src/gui/qgsmapcanvas.h | 8 +- src/ui/qgsdualviewbase.ui | 148 ++++++++++++++++- tests/src/app/testqgsattributetable.cpp | 4 +- 12 files changed, 408 insertions(+), 86 deletions(-) diff --git a/python/gui/auto_additions/qgsdualview.py b/python/gui/auto_additions/qgsdualview.py index f394b3081788..e10d39dd6340 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 87b67fefd7c1..f7a54f707b93 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 dc9fc483b245..e2d00c5001d8 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,20 @@ Select all currently visible features void repaintRequested( const QModelIndexList &indexes ); void repaintRequested(); + void editNextFeature(); +%Docstring +editNextFeature will try to edit next feature in form + +.. versionadded:: 3.8 +%End + + void editPreviousFeature(); +%Docstring +editPreviousFeature will try to edit previous feature in form + +.. versionadded:: 3.8 +%End + }; /************************************************************************ diff --git a/python/gui/auto_generated/qgsmapcanvas.sip.in b/python/gui/auto_generated/qgsmapcanvas.sip.in index fb3ca85f7e74..0d9238634b6b 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 bc8a57442444..eebd38be2746 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,37 @@ 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 ); + + 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 +98,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 +121,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 +185,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 +319,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 +418,74 @@ 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 ); +} + +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 +494,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 +508,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 +549,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 +580,7 @@ void QgsDualView::previewColumnChanged( QAction *previewAction, const QString &e mFeatureListPreviewButton->setPopupMode( QToolButton::InstantPopup ); } - setDisplayExpression( mFeatureList->displayExpression() ); + setDisplayExpression( mFeatureListView->displayExpression() ); } int QgsDualView::featureCount() @@ -841,11 +923,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 +960,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 +976,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 66eebb8749a1..a9605cfbe0aa 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 bbf0547c303d..64083d69ba3e 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,48 +261,56 @@ 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(); - } - - QModelIndex newLocalIndex; - QModelIndex newIndex; + case Qt::Key_Up: + editNextOrPreviousFeature( Previous ); + 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: + editNextOrPreviousFeature( Next ); + 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: + QListView::keyPressEvent( event ); + } +} - default: - break; - } +void QgsFeatureListView::editNextOrPreviousFeature( QgsFeatureListView::NextOrPrevious nextOrPrevious ) +{ + int currentRow = 0; + if ( 0 != mCurrentEditSelectionModel->selectedIndexes().count() ) + { + QModelIndex localIndex = mModel->mapFromMaster( mCurrentEditSelectionModel->selectedIndexes().first() ); + currentRow = localIndex.row(); } - else + + QModelIndex newLocalIndex; + QModelIndex newIndex; + + switch ( nextOrPrevious ) { - QListView::keyPressEvent( event ); + case Previous: + newLocalIndex = mModel->index( currentRow - 1, 0 ); + newIndex = mModel->mapToMaster( newLocalIndex ); + if ( newIndex.isValid() ) + { + setEditSelection( newIndex, QItemSelectionModel::ClearAndSelect ); + scrollTo( newLocalIndex ); + } + break; + + case Next: + newLocalIndex = mModel->index( currentRow + 1, 0 ); + newIndex = mModel->mapToMaster( newLocalIndex ); + if ( newIndex.isValid() ) + { + setEditSelection( newIndex, QItemSelectionModel::ClearAndSelect ); + scrollTo( newLocalIndex ); + } + break; } + } void QgsFeatureListView::contextMenuEvent( QContextMenuEvent *event ) diff --git a/src/gui/attributetable/qgsfeaturelistview.h b/src/gui/attributetable/qgsfeaturelistview.h index 7c0e3835380a..6e1616a586b5 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,18 @@ class GUI_EXPORT QgsFeatureListView : public QListView void repaintRequested( const QModelIndexList &indexes ); void repaintRequested(); + /** + * editNextFeature will try to edit next feature in form + * \since QGIS 3.8 + */ + void editNextFeature() {editNextOrPreviousFeature( Next );} + + /** + * editPreviousFeature will try to edit previous feature in form + * \since QGIS 3.8 + */ + void editPreviousFeature() {editNextOrPreviousFeature( Previous );} + private slots: void editSelectionChanged( const QItemSelection &deselected, const QItemSelection &selected ); @@ -192,6 +211,14 @@ class GUI_EXPORT QgsFeatureListView : public QListView private: void selectRow( const QModelIndex &index, bool anchor ); + enum NextOrPrevious + { + Next, + Previous + }; + + void editNextOrPreviousFeature( NextOrPrevious nextOrPrevious ); + QgsFeatureListModel *mModel = nullptr; QItemSelectionModel *mCurrentEditSelectionModel = nullptr; diff --git a/src/gui/qgsmapcanvas.cpp b/src/gui/qgsmapcanvas.cpp index fb5edb1e9c1f..08af8f3947e7 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 86d81611613c..9972d76fe10d 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/qgsdualviewbase.ui b/src/ui/qgsdualviewbase.ui index b74ae977940d..d9cc076f6fac 100644 --- a/src/ui/qgsdualviewbase.ui +++ b/src/ui/qgsdualviewbase.ui @@ -73,6 +73,9 @@ + + 2 + 0 @@ -111,7 +114,150 @@ - + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + :/images/themes/default/mActionArrowLeft.svg:/images/themes/default/mActionArrowLeft.svg + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 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 + + + + + + + + + + + + + :/images/themes/default/mActionArrowRight.svg:/images/themes/default/mActionArrowRight.svg + + + true + + + + + + + + 0 diff --git a/tests/src/app/testqgsattributetable.cpp b/tests/src/app/testqgsattributetable.cpp index 6969559a3d77..e0496215f67f 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 ) );