From a665d4858872e67582bfa39af6cd000d696b21cc Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 29 Jul 2025 13:17:17 +1000 Subject: [PATCH] Create elevation profile manager dialog --- src/app/CMakeLists.txt | 1 + .../qgselevationprofilemanagerdialog.cpp | 267 ++++++++++++++++++ .../qgselevationprofilemanagerdialog.h | 67 +++++ src/app/qgisapp.cpp | 10 +- src/app/qgisapp.h | 5 + src/app/qgsappwindowmanager.cpp | 20 +- src/app/qgsappwindowmanager.h | 7 +- src/ui/qgisapp.ui | 33 ++- src/ui/qgselevationprofilemanagerbase.ui | 160 +++++++++++ 9 files changed, 553 insertions(+), 17 deletions(-) create mode 100644 src/app/elevation/qgselevationprofilemanagerdialog.cpp create mode 100644 src/app/elevation/qgselevationprofilemanagerdialog.h create mode 100644 src/ui/qgselevationprofilemanagerbase.ui diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 18a5725d323..ecac7b9863b 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -187,6 +187,7 @@ set(QGIS_APP_SRCS elevation/qgselevationprofileexportsettingswidget.cpp elevation/qgselevationprofileimageexportdialog.cpp elevation/qgselevationprofilepdfexportdialog.cpp + elevation/qgselevationprofilemanagerdialog.cpp elevation/qgselevationprofiletoolidentify.cpp elevation/qgselevationprofiletoolmeasure.cpp elevation/qgselevationprofilewidget.cpp diff --git a/src/app/elevation/qgselevationprofilemanagerdialog.cpp b/src/app/elevation/qgselevationprofilemanagerdialog.cpp new file mode 100644 index 00000000000..67157233992 --- /dev/null +++ b/src/app/elevation/qgselevationprofilemanagerdialog.cpp @@ -0,0 +1,267 @@ +/*************************************************************************** + qgselevationprofilemanagerdialog.cpp + ----------------------- + begin : July 2025 + copyright : (C) 2025 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ***************************************************************************/ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgselevationprofilemanagerdialog.h" +#include "moc_qgselevationprofilemanagerdialog.cpp" +#include "qgisapp.h" +#include "qgselevationprofilemanager.h" +#include "qgselevationprofilemanagermodel.h" +#include "qgsproject.h" +#include "qgsgui.h" +#include "qgselevationprofile.h" +#include "qgsnewnamedialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +QgsElevationProfileManagerDialog::QgsElevationProfileManagerDialog( QWidget *parent, Qt::WindowFlags f ) + : QDialog( parent, f ) +{ + setupUi( this ); + + QgsGui::enableAutoGeometryRestore( this ); + + mModel = new QgsElevationProfileManagerModel( QgsProject::instance()->elevationProfileManager(), this ); + mProxyModel = new QgsElevationProfileManagerProxyModel( mProfileListView ); + mProxyModel->setSourceModel( mModel ); + mProfileListView->setModel( mProxyModel ); + + mSearchLineEdit->setShowSearchIcon( true ); + mSearchLineEdit->setShowClearButton( true ); + mSearchLineEdit->setFocus(); + connect( mSearchLineEdit, &QgsFilterLineEdit::textChanged, mProxyModel, &QgsElevationProfileManagerProxyModel::setFilterString ); + + connect( mButtonBox, &QDialogButtonBox::rejected, this, &QWidget::close ); + connect( mProfileListView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsElevationProfileManagerDialog::toggleButtons ); + connect( mProfileListView, &QListView::doubleClicked, this, &QgsElevationProfileManagerDialog::itemDoubleClicked ); + + connect( mShowButton, &QAbstractButton::clicked, this, &QgsElevationProfileManagerDialog::showClicked ); + connect( mRemoveButton, &QAbstractButton::clicked, this, &QgsElevationProfileManagerDialog::removeClicked ); + connect( mRenameButton, &QAbstractButton::clicked, this, &QgsElevationProfileManagerDialog::renameClicked ); + +#ifdef Q_OS_MAC + // Create action to select this window + mWindowAction = new QAction( windowTitle(), this ); + connect( mWindowAction, &QAction::triggered, this, &QgsElevationProfileManagerDialog::activate ); +#endif + + toggleButtons(); +} + +void QgsElevationProfileManagerDialog::toggleButtons() +{ + // Nothing selected: no button. + if ( mProfileListView->selectionModel()->selectedRows().isEmpty() ) + { + mShowButton->setEnabled( false ); + mRemoveButton->setEnabled( false ); + mRenameButton->setEnabled( false ); + } + // toggle everything if one profile is selected + else if ( mProfileListView->selectionModel()->selectedRows().count() == 1 ) + { + mShowButton->setEnabled( true ); + mRemoveButton->setEnabled( true ); + mRenameButton->setEnabled( true ); + } + // toggle only show and remove buttons in other cases + else + { + mShowButton->setEnabled( true ); + mRemoveButton->setEnabled( true ); + mRenameButton->setEnabled( false ); + } +} + +void QgsElevationProfileManagerDialog::activate() +{ + raise(); + setWindowState( windowState() & ~Qt::WindowMinimized ); + activateWindow(); +} + +bool QgsElevationProfileManagerDialog::uniqueProfileTitle( QWidget *parent, QString &title, const QString ¤tTitle ) +{ + if ( !parent ) + { + parent = this; + } + bool titleValid = false; + QString newTitle = QString( currentTitle ); + + QString chooseMsg = tr( "Enter a unique elevation profile title" ); + QString titleMsg = chooseMsg; + + QStringList profileNames; + const QList profiles = QgsProject::instance()->elevationProfileManager()->profiles(); + profileNames.reserve( profiles.size() + 1 ); + for ( QgsElevationProfile *l : profiles ) + { + profileNames << l->name(); + } + + const QString windowTitle = tr( "Rename Elevation Profile" ); + + while ( !titleValid ) + { + QgsNewNameDialog dlg( tr( "elevation profile" ), newTitle, QStringList(), profileNames, Qt::CaseSensitive, parent ); + dlg.setWindowTitle( windowTitle ); + dlg.setHintString( titleMsg ); + dlg.setOverwriteEnabled( false ); + dlg.setAllowEmptyName( true ); + dlg.setConflictingNameWarning( tr( "Title already exists!" ) ); + + if ( dlg.exec() != QDialog::Accepted ) + { + return false; + } + + newTitle = dlg.name(); + if ( newTitle.isEmpty() ) + { + titleMsg = chooseMsg + "\n\n" + tr( "Title can not be empty!" ); + } + else if ( profileNames.indexOf( newTitle, 1 ) >= 0 ) + { + profileNames[0] = QString(); // clear non-unique name + titleMsg = chooseMsg + "\n\n" + tr( "Title already exists!" ); + } + else + { + titleValid = true; + } + } + + title = newTitle; + + return true; +} + +#ifdef Q_OS_MAC +void QgsElevationProfileManagerDialog::showEvent( QShowEvent *event ) +{ + if ( !event->spontaneous() ) + { + QgisApp::instance()->addWindow( mWindowAction ); + } +} + +void QgsElevationProfileManagerDialog::changeEvent( QEvent *event ) +{ + QDialog::changeEvent( event ); + switch ( event->type() ) + { + case QEvent::ActivationChange: + if ( QApplication::activeWindow() == this ) + { + mWindowAction->setChecked( true ); + } + break; + + default: + break; + } +} +#endif + +void QgsElevationProfileManagerDialog::removeClicked() +{ + const QModelIndexList profileItems = mProfileListView->selectionModel()->selectedRows(); + if ( profileItems.isEmpty() ) + { + return; + } + + QString title; + QString message; + if ( profileItems.count() == 1 ) + { + title = tr( "Remove Elevation Profile" ); + message = tr( "Do you really want to remove the elevation profile “%1”?" ).arg( mProfileListView->model()->data( profileItems.at( 0 ), Qt::DisplayRole ).toString() ); + } + else + { + title = tr( "Remove Elevation Profiles" ); + message = tr( "Do you really want to remove all selected elevation profiles?" ); + } + + if ( QMessageBox::warning( this, title, message, QMessageBox::Ok | QMessageBox::Cancel ) != QMessageBox::Ok ) + { + return; + } + + QList profileList; + // Find the profiles that need to be deleted + for ( const QModelIndex &index : profileItems ) + { + if ( QgsElevationProfile *l = mModel->profileFromIndex( mProxyModel->mapToSource( index ) ) ) + { + profileList << l; + } + } + + // Once we have the profile list, we can delete all of them ! + for ( QgsElevationProfile *l : std::as_const( profileList ) ) + { + QgsProject::instance()->elevationProfileManager()->removeProfile( l ); + } +} + +void QgsElevationProfileManagerDialog::showClicked() +{ + const QModelIndexList profileItems = mProfileListView->selectionModel()->selectedRows(); + for ( const QModelIndex &index : profileItems ) + { + if ( QgsElevationProfile *l = mModel->profileFromIndex( mProxyModel->mapToSource( index ) ) ) + { + Q_UNUSED( l ); + } + } +} + +void QgsElevationProfileManagerDialog::renameClicked() +{ + if ( mProfileListView->selectionModel()->selectedRows().isEmpty() ) + { + return; + } + + QgsElevationProfile *currentProfile = mModel->profileFromIndex( mProxyModel->mapToSource( mProfileListView->selectionModel()->selectedRows().at( 0 ) ) ); + if ( !currentProfile ) + return; + + QString currentTitle = currentProfile->name(); + QString newTitle; + if ( !uniqueProfileTitle( this, newTitle, currentTitle ) ) + { + return; + } + currentProfile->setName( newTitle ); +} + +void QgsElevationProfileManagerDialog::itemDoubleClicked( const QModelIndex &index ) +{ + if ( QgsElevationProfile *l = mModel->profileFromIndex( mProxyModel->mapToSource( index ) ) ) + { + Q_UNUSED( l ); + } +} diff --git a/src/app/elevation/qgselevationprofilemanagerdialog.h b/src/app/elevation/qgselevationprofilemanagerdialog.h new file mode 100644 index 00000000000..f9f60400579 --- /dev/null +++ b/src/app/elevation/qgselevationprofilemanagerdialog.h @@ -0,0 +1,67 @@ +/*************************************************************************** + qgselevationprofilemanagerdialog.h + ----------------------- + begin : July 2025 + copyright : (C) 2025 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ***************************************************************************/ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSELEVATIONPROFILEMANAGERDIALOG_H +#define QGSELEVATIONPROFILEMANAGERDIALOG_H + +#include +#include + +#include "ui_qgselevationprofilemanagerbase.h" + +class QListWidgetItem; +class QgsElevationProfile; +class QgsElevationProfileManager; +class QgsElevationProfileManagerModel; +class QgsElevationProfileManagerProxyModel; + +/** + * A dialog that allows management of elevation profiles within a project. +*/ +class QgsElevationProfileManagerDialog : public QDialog, private Ui::QgsElevationProfileManagerBase +{ + Q_OBJECT + public: + QgsElevationProfileManagerDialog( QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags() ); + + public slots: + //! Raise, unminimize and activate this window + void activate(); + + private: + bool uniqueProfileTitle( QWidget *parent, QString &title, const QString ¤tTitle ); + + QgsElevationProfileManagerModel *mModel = nullptr; + QgsElevationProfileManagerProxyModel *mProxyModel = nullptr; + +#ifdef Q_OS_MAC + void showEvent( QShowEvent *event ); + void changeEvent( QEvent * ); + + QAction *mWindowAction = nullptr; +#endif + + private slots: + //! Slot to update buttons state when selecting layouts + void toggleButtons(); + + void removeClicked(); + void showClicked(); + void renameClicked(); + void itemDoubleClicked( const QModelIndex &index ); +}; + +#endif // QGSELEVATIONPROFILEMANAGERDIALOG_H diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 96cddd02052..8f552f385d4 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -2998,6 +2998,7 @@ void QgisApp::createActions() mStatisticalSummaryDockWidget->setToggleVisibilityAction( mActionStatisticalSummary ); connect( mActionManage3DMapViews, &QAction::triggered, this, &QgisApp::show3DMapViewsManager ); connect( mActionElevationProfile, &QAction::triggered, this, &QgisApp::createNewElevationProfile ); + connect( mActionManageElevationProfiles, &QAction::triggered, this, &QgisApp::showElevationProfileManager ); // Layer Menu Items @@ -13011,13 +13012,18 @@ void QgisApp::reloadConnections() void QgisApp::showLayoutManager() { - static_cast( QgsGui::windowManager() )->openApplicationDialog( QgsAppWindowManager::DialogLayoutManager ); + static_cast( QgsGui::windowManager() )->openApplicationDialog( QgsAppWindowManager::ApplicationDialog::LayoutManager ); +} + +void QgisApp::showElevationProfileManager() +{ + static_cast( QgsGui::windowManager() )->openApplicationDialog( QgsAppWindowManager::ApplicationDialog::ElevationProfileManager ); } void QgisApp::show3DMapViewsManager() { #ifdef HAVE_3D - static_cast( QgsGui::windowManager() )->openApplicationDialog( QgsAppWindowManager::Dialog3DMapViewsManager ); + static_cast( QgsGui::windowManager() )->openApplicationDialog( QgsAppWindowManager::ApplicationDialog::Dialog3DMapViewsManager ); #endif } diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index ae10633da7d..d0ac9ad1095 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -1351,6 +1351,11 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow */ void show3DMapViewsManager(); + /** + * Shows the elevation profile manager dialog. + */ + void showElevationProfileManager(); + //! shows the snapping Options void snappingOptions(); diff --git a/src/app/qgsappwindowmanager.cpp b/src/app/qgsappwindowmanager.cpp index 888b192f3ee..cd1da4aa33a 100644 --- a/src/app/qgsappwindowmanager.cpp +++ b/src/app/qgsappwindowmanager.cpp @@ -15,10 +15,9 @@ #include "qgsappwindowmanager.h" #include "qgsstylemanagerdialog.h" -#include "qgsstyle.h" #include "qgisapp.h" #include "qgslayoutmanagerdialog.h" -#include "qgsrasterlayer.h" +#include "elevation/qgselevationprofilemanagerdialog.h" #ifdef HAVE_3D #include "qgs3dviewsmanagerdialog.h" @@ -30,6 +29,8 @@ QgsAppWindowManager::~QgsAppWindowManager() delete mStyleManagerDialog; if ( mLayoutManagerDialog ) delete mLayoutManagerDialog; + if ( mElevationProfileManagerDialog ) + delete mElevationProfileManagerDialog; } QWidget *QgsAppWindowManager::openStandardDialog( QgsWindowManagerInterface::StandardDialog dialog ) @@ -55,7 +56,7 @@ QWidget *QgsAppWindowManager::openApplicationDialog( QgsAppWindowManager::Applic { switch ( dialog ) { - case DialogLayoutManager: + case ApplicationDialog::LayoutManager: { if ( !mLayoutManagerDialog ) { @@ -66,7 +67,7 @@ QWidget *QgsAppWindowManager::openApplicationDialog( QgsAppWindowManager::Applic mLayoutManagerDialog->activate(); return mLayoutManagerDialog; } - case Dialog3DMapViewsManager: + case ApplicationDialog::Dialog3DMapViewsManager: { #ifdef HAVE_3D if ( !m3DMapViewsManagerDialog ) @@ -82,6 +83,17 @@ QWidget *QgsAppWindowManager::openApplicationDialog( QgsAppWindowManager::Applic return m3DMapViewsManagerDialog; #endif } + case ApplicationDialog::ElevationProfileManager: + { + if ( !mElevationProfileManagerDialog ) + { + mElevationProfileManagerDialog = new QgsElevationProfileManagerDialog( QgisApp::instance(), Qt::Window ); + mElevationProfileManagerDialog->setAttribute( Qt::WA_DeleteOnClose ); + } + mElevationProfileManagerDialog->show(); + mElevationProfileManagerDialog->activate(); + return mElevationProfileManagerDialog; + } } return nullptr; } diff --git a/src/app/qgsappwindowmanager.h b/src/app/qgsappwindowmanager.h index d5f6888da51..edc7f9c0263 100644 --- a/src/app/qgsappwindowmanager.h +++ b/src/app/qgsappwindowmanager.h @@ -23,6 +23,7 @@ class QgsStyleManagerDialog; class QgsLayoutManagerDialog; class Qgs3DViewsManagerDialog; +class QgsElevationProfileManagerDialog; /** * \ingroup gui @@ -32,10 +33,11 @@ class QgsAppWindowManager : public QgsWindowManagerInterface { public: //! Application-only QGIS dialogs - enum ApplicationDialog + enum class ApplicationDialog : int { - DialogLayoutManager = 0, //!< Layout manager dialog + LayoutManager = 0, //!< Layout manager dialog Dialog3DMapViewsManager = 1, //!< 3D map views manager dialog + ElevationProfileManager = 2, //!< Elevation profile manager dialog }; QgsAppWindowManager() = default; @@ -57,6 +59,7 @@ class QgsAppWindowManager : public QgsWindowManagerInterface QPointer mStyleManagerDialog; QPointer mLayoutManagerDialog; QPointer m3DMapViewsManagerDialog; + QPointer mElevationProfileManagerDialog; }; diff --git a/src/ui/qgisapp.ui b/src/ui/qgisapp.ui index 4e6af6bd305..d5c9d0cdb6b 100644 --- a/src/ui/qgisapp.ui +++ b/src/ui/qgisapp.ui @@ -160,6 +160,13 @@ + + + Elevation Profiles + + + + @@ -171,7 +178,7 @@ - + @@ -912,7 +919,7 @@ Ctrl+Q - QAction::MenuRole::QuitRole + QAction::QuitRole @@ -1903,7 +1910,7 @@ Shift+O to turn segments into straight or curve lines. &Options… - QAction::MenuRole::NoRole + QAction::NoRole @@ -1924,7 +1931,7 @@ Shift+O to turn segments into straight or curve lines. Keyboard Shortcuts… - QAction::MenuRole::NoRole + QAction::NoRole @@ -1994,7 +2001,7 @@ Shift+O to turn segments into straight or curve lines. About - QAction::MenuRole::AboutRole + QAction::AboutRole @@ -3317,7 +3324,7 @@ Shows placeholders for labels which could not be placed, e.g. due to overlaps wi - Manage 3D Map Views + Manage 3D Map Views… @@ -3350,10 +3357,10 @@ Shows placeholders for labels which could not be placed, e.g. due to overlaps wi :/images/themes/default/mActionNewElevationProfile.svg:/images/themes/default/mActionNewElevationProfile.svg - Elevation Profile + New Elevation Profile - Open an interactive elevation profile chart + Create a new interactive elevation profile chart @@ -3409,7 +3416,15 @@ Shows placeholders for labels which could not be placed, e.g. due to overlaps wi Database Query History - QAction::MenuRole::NoRole + QAction::NoRole + + + + + Manage Elevation Profiles… + + + Manage Elevation Profiles diff --git a/src/ui/qgselevationprofilemanagerbase.ui b/src/ui/qgselevationprofilemanagerbase.ui new file mode 100644 index 00000000000..3c1492a2106 --- /dev/null +++ b/src/ui/qgselevationprofilemanagerbase.ui @@ -0,0 +1,160 @@ + + + QgsElevationProfileManagerBase + + + + 0 + 0 + 425 + 300 + + + + + 425 + 300 + + + + Elevation Profile Manager + + + + :/images/themes/default/mActionLayoutManager.svg:/images/themes/default/mActionLayoutManager.svg + + + + + + + + + 0 + 0 + + + + &Show + + + + + + + + 0 + 0 + + + + &Remove… + + + + + + + + 0 + 0 + + + + Re&name… + + + + + + + 0 + + + 0 + + + + + Search… + + + + + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + QgsFilterLineEdit + QLineEdit +
qgsfilterlineedit.h
+
+
+ + mSearchLineEdit + mProfileListView + mShowButton + mRemoveButton + mRenameButton + + + + + + + mButtonBox + accepted() + QgsElevationProfileManagerBase + accept() + + + 266 + 295 + + + 157 + 274 + + + + + mButtonBox + rejected() + QgsElevationProfileManagerBase + reject() + + + 266 + 295 + + + 286 + 274 + + + + +