diff --git a/python/core/auto_generated/proj/qgscoordinatereferencesystem.sip.in b/python/core/auto_generated/proj/qgscoordinatereferencesystem.sip.in index 0938af9c08a..9108f1f3130 100644 --- a/python/core/auto_generated/proj/qgscoordinatereferencesystem.sip.in +++ b/python/core/auto_generated/proj/qgscoordinatereferencesystem.sip.in @@ -1123,6 +1123,20 @@ Pushes a recently used CRS to the top of the recent CRS list. .. versionadded:: 3.10.3 %End + static void removeRecentCoordinateReferenceSystem( const QgsCoordinateReferenceSystem &crs ); +%Docstring +Removes a CRS from the list of recently used CRS. + +.. versionadded:: 3.32 +%End + + static void clearRecentCoordinateReferenceSystems(); +%Docstring +Cleans the list of recently used CRS. + +.. versionadded:: 3.32 +%End + static void invalidateCache(); %Docstring diff --git a/python/gui/auto_generated/qgsprojectionselectiontreewidget.sip.in b/python/gui/auto_generated/qgsprojectionselectiontreewidget.sip.in index 50bb7cbb579..0807a7a8941 100644 --- a/python/gui/auto_generated/qgsprojectionselectiontreewidget.sip.in +++ b/python/gui/auto_generated/qgsprojectionselectiontreewidget.sip.in @@ -155,6 +155,14 @@ Marks the current selected projection for push to front of recent projections li Has no effect since QGIS 3.20 %End + + void clearRecentCrs(); +%Docstring +Clear the list of recent projections. + +.. versionadded:: 3.32 +%End + signals: void crsSelected(); @@ -190,6 +198,9 @@ Emitted when the selection in the tree is changed from a valid selection to an i virtual void resizeEvent( QResizeEvent *event ); + virtual bool eventFilter( QObject *obj, QEvent *ev ); + + }; /************************************************************************ diff --git a/src/core/proj/qgscoordinatereferencesystem.cpp b/src/core/proj/qgscoordinatereferencesystem.cpp index 80e92071ceb..351e2fce9d8 100644 --- a/src/core/proj/qgscoordinatereferencesystem.cpp +++ b/src/core/proj/qgscoordinatereferencesystem.cpp @@ -2942,6 +2942,40 @@ void QgsCoordinateReferenceSystem::pushRecentCoordinateReferenceSystem( const Qg settings.setValue( QStringLiteral( "UI/recentProjectionsProj4" ), proj ); } + +void QgsCoordinateReferenceSystem::removeRecentCoordinateReferenceSystem( const QgsCoordinateReferenceSystem &crs ) +{ + if ( crs.srsid() == 0 || !crs.isValid() ) + return; + + QList recent = recentCoordinateReferenceSystems(); + recent.removeAll( crs ); + QStringList authids; + authids.reserve( recent.size() ); + QStringList proj; + proj.reserve( recent.size() ); + QStringList wkt; + wkt.reserve( recent.size() ); + for ( const QgsCoordinateReferenceSystem &c : std::as_const( recent ) ) + { + authids << c.authid(); + proj << c.toProj(); + wkt << c.toWkt( WKT_PREFERRED ); + } + QgsSettings settings; + settings.setValue( QStringLiteral( "UI/recentProjectionsAuthId" ), authids ); + settings.setValue( QStringLiteral( "UI/recentProjectionsWkt" ), wkt ); + settings.setValue( QStringLiteral( "UI/recentProjectionsProj4" ), proj ); +} + +void QgsCoordinateReferenceSystem::clearRecentCoordinateReferenceSystems() +{ + QgsSettings settings; + settings.remove( QStringLiteral( "UI/recentProjectionsAuthId" ) ); + settings.remove( QStringLiteral( "UI/recentProjectionsWkt" ) ); + settings.remove( QStringLiteral( "UI/recentProjectionsProj4" ) ); +} + void QgsCoordinateReferenceSystem::invalidateCache( bool disableCache ) { sSrIdCacheLock()->lockForWrite(); diff --git a/src/core/proj/qgscoordinatereferencesystem.h b/src/core/proj/qgscoordinatereferencesystem.h index a912ee885a9..5f4764b5fc9 100644 --- a/src/core/proj/qgscoordinatereferencesystem.h +++ b/src/core/proj/qgscoordinatereferencesystem.h @@ -1063,6 +1063,18 @@ class CORE_EXPORT QgsCoordinateReferenceSystem */ static void pushRecentCoordinateReferenceSystem( const QgsCoordinateReferenceSystem &crs ); + /** + * Removes a CRS from the list of recently used CRS. + * \since QGIS 3.32 + */ + static void removeRecentCoordinateReferenceSystem( const QgsCoordinateReferenceSystem &crs ); + + /** + * Cleans the list of recently used CRS. + * \since QGIS 3.32 + */ + static void clearRecentCoordinateReferenceSystems(); + #ifndef SIP_RUN /** diff --git a/src/gui/qgsprojectionselectiontreewidget.cpp b/src/gui/qgsprojectionselectiontreewidget.cpp index 1ab6e7d0e2f..5bdd2314931 100644 --- a/src/gui/qgsprojectionselectiontreewidget.cpp +++ b/src/gui/qgsprojectionselectiontreewidget.cpp @@ -28,6 +28,9 @@ #include "qgsunittypes.h" //qt includes +#include +#include +#include #include #include #include @@ -63,14 +66,41 @@ QgsProjectionSelectionTreeWidget::QgsProjectionSelectionTreeWidget( QWidget *par // Hide (internal) ID column lstCoordinateSystems->setColumnHidden( QgisCrsIdColumn, true ); - lstRecent->header()->setSectionResizeMode( AuthidColumn, QHeaderView::Stretch ); lstRecent->header()->resizeSection( QgisCrsIdColumn, 0 ); lstRecent->header()->setSectionResizeMode( QgisCrsIdColumn, QHeaderView::Fixed ); - - // Hide (internal) ID column lstRecent->setColumnHidden( QgisCrsIdColumn, true ); + // Clear Crs Column + lstRecent->header()->setMinimumSectionSize( 10 ); + lstRecent->header()->setStretchLastSection( false ); + lstRecent->header()->resizeSection( ClearColumn, 20 ); + + // Clear recent crs context menu + lstRecent->setContextMenuPolicy( Qt::CustomContextMenu ); + connect( lstRecent, &QTreeWidget::customContextMenuRequested, this, [this]( const QPoint & pos ) + { + // If list is empty, do nothing + if ( lstRecent->topLevelItemCount() == 0 ) + return; + QMenu menu; + // Clear selected + QTreeWidgetItem *currentItem = lstRecent->itemAt( pos ); + if ( currentItem ) + { + QAction *clearSelected = menu.addAction( QgsApplication::getThemeIcon( "/mIconClearItem.svg" ), tr( "Remove selected CRS from recently used CRS" ) ); + connect( clearSelected, &QAction::triggered, this, [this, currentItem ] { removeRecentCrsItem( currentItem ); } ); + menu.addSeparator(); + } + // Clear all + QAction *clearAll = menu.addAction( QgsApplication::getThemeIcon( "/console/iconClearConsole.svg" ), tr( "Clear all recently used CRS" ) ); + connect( clearAll, &QAction::triggered, this, &QgsProjectionSelectionTreeWidget::clearRecentCrs ); + menu.exec( lstRecent->viewport()->mapToGlobal( pos ) ); + } ); + + // Install event fiter to catch delete key press on the recent crs list + lstRecent->installEventFilter( this ); + mRecentProjections = QgsCoordinateReferenceSystem::recentCoordinateReferenceSystems(); mCheckBoxNoProjection->setHidden( true ); @@ -112,9 +142,10 @@ void QgsProjectionSelectionTreeWidget::resizeEvent( QResizeEvent *event ) lstCoordinateSystems->header()->resizeSection( AuthidColumn, 240 ); lstCoordinateSystems->header()->resizeSection( QgisCrsIdColumn, 0 ); - lstRecent->header()->resizeSection( NameColumn, event->size().width() - 240 ); + lstRecent->header()->resizeSection( NameColumn, event->size().width() - 260 ); lstRecent->header()->resizeSection( AuthidColumn, 240 ); lstRecent->header()->resizeSection( QgisCrsIdColumn, 0 ); + lstRecent->header()->resizeSection( ClearColumn, 20 ); } void QgsProjectionSelectionTreeWidget::showEvent( QShowEvent *event ) @@ -146,6 +177,25 @@ void QgsProjectionSelectionTreeWidget::showEvent( QShowEvent *event ) mInitialized = true; } + +bool QgsProjectionSelectionTreeWidget::eventFilter( QObject *obj, QEvent *ev ) +{ + if ( obj != lstRecent ) + return false; + + if ( ev->type() != QEvent::KeyPress ) + return false; + + QKeyEvent *keyEvent = static_cast( ev ); + if ( keyEvent->matches( QKeySequence::Delete ) ) + { + removeRecentCrsItem( lstRecent->currentItem() ); + return true; + } + + return false; +} + QString QgsProjectionSelectionTreeWidget::ogcWmsCrsFilterAsSqlExpression( QSet *crsFilter ) { QString sqlExpression = QStringLiteral( "1" ); // it's "SQL" for "true" @@ -253,13 +303,25 @@ void QgsProjectionSelectionTreeWidget::insertRecent( const QgsCoordinateReferenc if ( nodes.isEmpty() ) return; - lstRecent->insertTopLevelItem( 0, new QTreeWidgetItem( lstRecent, QStringList() - << nodes.first()->text( NameColumn ) - << nodes.first()->text( AuthidColumn ) - << nodes.first()->text( QgisCrsIdColumn ) ) ); + QTreeWidgetItem *item = new QTreeWidgetItem( lstRecent, QStringList() + << nodes.first()->text( NameColumn ) + << nodes.first()->text( AuthidColumn ) + << nodes.first()->text( QgisCrsIdColumn ) ); + + // Insert clear button in the last column + QToolButton *clearButton = new QToolButton(); + clearButton->setIcon( QgsApplication::getThemeIcon( "/mIconClearItem.svg" ) ); + clearButton->setAutoRaise( true ); + clearButton->setToolTip( tr( "Remove from recently used CRS" ) ); + connect( clearButton, &QToolButton::clicked, this, [this, item] { removeRecentCrsItem( item ); } ); + lstRecent->setItemWidget( item, ClearColumn, clearButton ); + + + lstRecent->insertTopLevelItem( 0, item ); + } -//note this line just returns the projection name! +// note this line just returns the projection name! QString QgsProjectionSelectionTreeWidget::selectedName() { // return the selected wkt name from the list view @@ -307,25 +369,19 @@ QgsRectangle QgsProjectionSelectionTreeWidget::previewRect() const return mAreaCanvas->canvasRect(); } -QString QgsProjectionSelectionTreeWidget::getSelectedExpression( const QString &expression ) const +QString QgsProjectionSelectionTreeWidget::expressionForItem( QTreeWidgetItem *item, const QString &expression ) const { - // Only return the attribute if there is a node in the tree - // selected that has an srs_id. This prevents error if the user - // selects a top-level node rather than an actual coordinate - // system - // - // Get the selected node and make sure it is a srs andx - // not a top-level projection node - QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem(); - if ( !lvi || lvi->text( QgisCrsIdColumn ).isEmpty() ) + // Only return the attribute if the selected item that has an srs_id. + // This prevents error if the user selects a top-level node rather + // than an actual coordinate system + if ( !item || item->text( QgisCrsIdColumn ).isEmpty() ) return QString(); - // + // Determine if this is a user projection or a system on // user projection defs all have srs_id >= 100000 - // QString databaseFileName; - if ( lvi->text( QgisCrsIdColumn ).toLong() >= USER_CRS_START_ID ) + if ( item->text( QgisCrsIdColumn ).toLong() >= USER_CRS_START_ID ) { databaseFileName = QgsApplication::qgisUserDatabaseFilePath(); if ( !QFileInfo::exists( databaseFileName ) ) @@ -338,7 +394,6 @@ QString QgsProjectionSelectionTreeWidget::getSelectedExpression( const QString & databaseFileName = mSrsDatabaseFileName; } - // // set up the database // XXX We could probably hold the database open for the life of this object, // assuming that it will never be used anywhere else. Given the low overhead, @@ -358,7 +413,7 @@ QString QgsProjectionSelectionTreeWidget::getSelectedExpression( const QString & sqlite3_stmt *stmt = nullptr; QString sql = QStringLiteral( "select %1 from tbl_srs where srs_id=%2" ) .arg( expression, - lvi->text( QgisCrsIdColumn ) ); + item->text( QgisCrsIdColumn ) ); QgsDebugMsgLevel( QStringLiteral( "Finding selected attribute using : %1" ).arg( sql ), 4 ); rc = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail ); @@ -379,7 +434,9 @@ QString QgsProjectionSelectionTreeWidget::getSelectedExpression( const QString & return attributeValue; } -QgsCoordinateReferenceSystem QgsProjectionSelectionTreeWidget::crs() const + + +QgsCoordinateReferenceSystem QgsProjectionSelectionTreeWidget::crsForItem( QTreeWidgetItem *item ) const { if ( mCheckBoxNoProjection->isEnabled() && mCheckBoxNoProjection->isChecked() ) return QgsCoordinateReferenceSystem(); @@ -387,28 +444,32 @@ QgsCoordinateReferenceSystem QgsProjectionSelectionTreeWidget::crs() const if ( !mInitialized && mDeferredLoadCrs.isValid() ) return mDeferredLoadCrs; - const QString srsIdString = getSelectedExpression( QStringLiteral( "srs_id" ) ); + const QString srsIdString = expressionForItem( item, QStringLiteral( "srs_id" ) ); if ( !srsIdString.isEmpty() ) { int srid = srsIdString.toLong(); if ( srid >= USER_CRS_START_ID ) return QgsCoordinateReferenceSystem::fromOgcWmsCrs( QStringLiteral( "USER:%1" ).arg( srid ) ); else - return QgsCoordinateReferenceSystem::fromOgcWmsCrs( getSelectedExpression( QStringLiteral( "upper(auth_name||':'||auth_id)" ) ) ); + return QgsCoordinateReferenceSystem::fromOgcWmsCrs( expressionForItem( item, QStringLiteral( "upper(auth_name||':'||auth_id)" ) ) ); } else { // custom CRS - QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem(); - if ( lvi && lvi->data( 0, RoleWkt ).isValid() ) - return QgsCoordinateReferenceSystem::fromWkt( lvi->data( 0, RoleWkt ).toString() ); - else if ( lvi && lvi->data( 0, RoleProj ).isValid() ) - return QgsCoordinateReferenceSystem::fromProj( lvi->data( 0, RoleProj ).toString() ); + if ( item && item->data( 0, RoleWkt ).isValid() ) + return QgsCoordinateReferenceSystem::fromWkt( item->data( 0, RoleWkt ).toString() ); + else if ( item && item->data( 0, RoleProj ).isValid() ) + return QgsCoordinateReferenceSystem::fromProj( item->data( 0, RoleProj ).toString() ); else return QgsCoordinateReferenceSystem(); } } +QgsCoordinateReferenceSystem QgsProjectionSelectionTreeWidget::crs() const +{ + return crsForItem( lstCoordinateSystems->currentItem() ); +} + void QgsProjectionSelectionTreeWidget::setShowNoProjection( bool show ) { mCheckBoxNoProjection->setVisible( show ); @@ -1053,3 +1114,34 @@ void QgsProjectionSelectionTreeWidget::showDBMissingWarning( const QString &file "Because of this the projection selector will not work…" ) .arg( fileName ) ); } + +void QgsProjectionSelectionTreeWidget::clearRecentCrs() +{ + // If the list is empty, there is nothing to do + if ( lstRecent->topLevelItemCount() == 0 ) + { + return; + } + + // Ask for confirmation + if ( QMessageBox::question( this, tr( "Clear Recent CRS" ), + tr( "Are you sure you want to clear the list of recently used coordinate reference system?" ), + QMessageBox::Yes | QMessageBox::No ) != QMessageBox::Yes ) + { + return; + } + QgsCoordinateReferenceSystem::clearRecentCoordinateReferenceSystems(); + lstRecent->clear(); +} + + +void QgsProjectionSelectionTreeWidget::removeRecentCrsItem( QTreeWidgetItem *item ) +{ + int index = lstRecent->indexOfTopLevelItem( item ); + if ( index == -1 ) + return; + QgsCoordinateReferenceSystem crs = crsForItem( item ); + QgsCoordinateReferenceSystem::removeRecentCoordinateReferenceSystem( crs ); + lstRecent->takeTopLevelItem( index ); + delete item; +} diff --git a/src/gui/qgsprojectionselectiontreewidget.h b/src/gui/qgsprojectionselectiontreewidget.h index 5acf5d4f959..b8bd274d658 100644 --- a/src/gui/qgsprojectionselectiontreewidget.h +++ b/src/gui/qgsprojectionselectiontreewidget.h @@ -145,6 +145,14 @@ class GUI_EXPORT QgsProjectionSelectionTreeWidget : public QWidget, private Ui:: */ Q_DECL_DEPRECATED void pushProjectionToFront() SIP_DEPRECATED; + + /** + * Clear the list of recent projections. + * + * \since QGIS 3.32 + */ + void clearRecentCrs(); + signals: /** @@ -178,6 +186,9 @@ class GUI_EXPORT QgsProjectionSelectionTreeWidget : public QWidget, private Ui:: // Used to manage column sizes void resizeEvent( QResizeEvent *event ) override; + // Used to catch key presses on the recent projections list + bool eventFilter( QObject *obj, QEvent *ev ) override; + private: /** @@ -236,12 +247,11 @@ class GUI_EXPORT QgsProjectionSelectionTreeWidget : public QWidget, private Ui:: */ void applySelection( int column = QgsProjectionSelectionTreeWidget::None, QString value = QString() ); - /** - * \brief gets an arbitrary sqlite3 expression from the selection - * - * \param e The sqlite3 expression (typically "srid" or "sridid") - */ - QString getSelectedExpression( const QString &e ) const; + //! gets an arbitrary sqlite3 expression from the given item + QString expressionForItem( QTreeWidgetItem *item, const QString &expression ) const; + + //! Returns the CRS of the given item + QgsCoordinateReferenceSystem crsForItem( QTreeWidgetItem *item ) const; QString selectedName(); @@ -290,7 +300,7 @@ class GUI_EXPORT QgsProjectionSelectionTreeWidget : public QWidget, private Ui:: //! Has the Recent Projection List been populated? bool mRecentProjListDone = false; - enum Columns { NameColumn, AuthidColumn, QgisCrsIdColumn, None }; + enum Columns { NameColumn, AuthidColumn, QgisCrsIdColumn, ClearColumn, None }; int mSearchColumn = QgsProjectionSelectionTreeWidget::None; QString mSearchValue; @@ -320,6 +330,8 @@ class GUI_EXPORT QgsProjectionSelectionTreeWidget : public QWidget, private Ui:: void lstCoordinateSystems_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem *prev ); void lstRecent_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem *prev ); void updateFilter(); + + void removeRecentCrsItem( QTreeWidgetItem *item ); }; #endif diff --git a/src/ui/qgsprojectionselectorbase.ui b/src/ui/qgsprojectionselectorbase.ui index e35d6e643ef..d3087cd53af 100644 --- a/src/ui/qgsprojectionselectorbase.ui +++ b/src/ui/qgsprojectionselectorbase.ui @@ -7,7 +7,7 @@ 0 0 578 - 654 + 650 @@ -82,17 +82,21 @@ - - - - 75 - true - - - - Recently Used Coordinate Reference Systems - - + + + + + + 75 + true + + + + Recently Used Coordinate Reference Systems + + + + @@ -125,7 +129,7 @@ true - 3 + 4 @@ -142,8 +146,13 @@ ID + + + + + - + 0 @@ -223,7 +232,7 @@ - + 0 @@ -314,6 +323,8 @@ cbxHideDeprecated lstCoordinateSystems - + + +