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 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + 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 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + 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 ) );