From 550a1d3a52020410c3996d0ba0c43f098e7d9a46 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 29 Jul 2021 13:55:35 +1000 Subject: [PATCH] Factories can specify a path to add their pages to the options tree --- python/console/console_settings.py | 3 + .../qgsoptionsdialogbase.sip.in | 10 +- .../qgsoptionswidgetfactory.sip.in | 11 ++ src/app/options/qgscodeeditoroptions.cpp | 10 ++ src/app/options/qgscodeeditoroptions.h | 2 + src/app/options/qgsoptions.cpp | 13 +- src/gui/qgsoptionsdialogbase.cpp | 133 ++++++++++++++++-- src/gui/qgsoptionsdialogbase.h | 10 +- src/gui/qgsoptionswidgetfactory.h | 11 ++ 9 files changed, 186 insertions(+), 17 deletions(-) diff --git a/python/console/console_settings.py b/python/console/console_settings.py index 470d7df6983..8abefbedf07 100644 --- a/python/console/console_settings.py +++ b/python/console/console_settings.py @@ -38,6 +38,9 @@ class ConsoleOptionsFactory(QgsOptionsWidgetFactory): def icon(self): return QgsApplication.getThemeIcon('/console/mIconRunConsole.svg') + def path(self): + return ['ide'] + def createWidget(self, parent): return ConsoleOptionsPage(parent) diff --git a/python/gui/auto_generated/qgsoptionsdialogbase.sip.in b/python/gui/auto_generated/qgsoptionsdialogbase.sip.in index af487382f84..257e91e5355 100644 --- a/python/gui/auto_generated/qgsoptionsdialogbase.sip.in +++ b/python/gui/auto_generated/qgsoptionsdialogbase.sip.in @@ -96,7 +96,7 @@ Sets the dialog ``page`` (by object name) to show. .. versionadded:: 3.14 %End - void addPage( const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget /Transfer/ ); + void addPage( const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget /Transfer/, const QStringList &path = QStringList() ); %Docstring Adds a new page to the dialog pages. @@ -104,12 +104,15 @@ The ``title``, ``tooltip`` and ``icon`` arguments dictate the page list item tit The page content is specified via the ``widget`` argument. Ownership of ``widget`` is transferred to the dialog. +Since QGIS 3.22, the optional ``path`` argument can be used to set the path of the item's entry in the tree view +(for dialogs which show a tree view of options pages only). + .. seealso:: :py:func:`insertPage` .. versionadded:: 3.14 %End - void insertPage( const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget /Transfer/, const QString &before ); + void insertPage( const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget /Transfer/, const QString &before, const QStringList &path = QStringList() ); %Docstring Inserts a new page into the dialog pages. @@ -120,6 +123,9 @@ The page content is specified via the ``widget`` argument. Ownership of ``widget The ``before`` argument specifies the object name of an existing page. The new page will be inserted directly before the matching page. +Since QGIS 3.22, the optional ``path`` argument can be used to set the path of the item's entry in the tree view +(for dialogs which show a tree view of options pages only). + .. seealso:: :py:func:`addPage` .. versionadded:: 3.14 diff --git a/python/gui/auto_generated/qgsoptionswidgetfactory.sip.in b/python/gui/auto_generated/qgsoptionswidgetfactory.sip.in index 0b1113c473a..ec124fec53c 100644 --- a/python/gui/auto_generated/qgsoptionswidgetfactory.sip.in +++ b/python/gui/auto_generated/qgsoptionswidgetfactory.sip.in @@ -120,6 +120,17 @@ The default implementation returns an empty string, which causes the widget to be placed at the end of the dialog page list. .. versionadded:: 3.18 +%End + + virtual QStringList path() const; +%Docstring +Returns the path to place the widget page at, for options dialogs +which are structured using a tree view. + +A factory which returns "Code", "Javascript" would have its widget placed +in a group named "Javascript", contained in a parent group named "Code". + +.. versionadded:: 3.22 %End virtual QgsOptionsPageWidget *createWidget( QWidget *parent = 0 ) const = 0 /Factory/; diff --git a/src/app/options/qgscodeeditoroptions.cpp b/src/app/options/qgscodeeditoroptions.cpp index 64b285a4dfc..bcb56b619cd 100644 --- a/src/app/options/qgscodeeditoroptions.cpp +++ b/src/app/options/qgscodeeditoroptions.cpp @@ -356,3 +356,13 @@ QgsOptionsPageWidget *QgsCodeEditorOptionsFactory::createWidget( QWidget *parent { return new QgsCodeEditorOptionsWidget( parent ); } + +QStringList QgsCodeEditorOptionsFactory::path() const +{ + return {QStringLiteral( "ide" ) }; +} + +QString QgsCodeEditorOptionsFactory::pagePositionHint() const +{ + return QStringLiteral( "consoleOptions" ); +} diff --git a/src/app/options/qgscodeeditoroptions.h b/src/app/options/qgscodeeditoroptions.h index e2e024639ff..06342b3d584 100644 --- a/src/app/options/qgscodeeditoroptions.h +++ b/src/app/options/qgscodeeditoroptions.h @@ -63,6 +63,8 @@ class QgsCodeEditorOptionsFactory : public QgsOptionsWidgetFactory QIcon icon() const override; QgsOptionsPageWidget *createWidget( QWidget *parent = nullptr ) const override; + QStringList path() const override; + QString pagePositionHint() const override; }; diff --git a/src/app/options/qgsoptions.cpp b/src/app/options/qgsoptions.cpp index 4cd84c87994..eddf767abe9 100644 --- a/src/app/options/qgsoptions.cpp +++ b/src/app/options/qgsoptions.cpp @@ -110,6 +110,7 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl, const QListappendRow( createItem( tr( "System" ), tr( "System" ), QStringLiteral( "propertyicons/system.svg" ) ) ); QStandardItem *crsGroup = new QStandardItem( tr( "CRS and Transforms" ) ); + crsGroup->setToolTip( tr( "CRS and Transforms" ) ); crsGroup->setSelectable( false ); crsGroup->appendRow( createItem( tr( "CRS" ), tr( "CRS" ), QStringLiteral( "propertyicons/CRS.svg" ) ) ); crsGroup->appendRow( createItem( tr( "Transformations" ), tr( "Coordinate transformations and operations" ), QStringLiteral( "transformation.svg" ) ) ); @@ -131,6 +132,12 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl, const QListappendRow( createItem( tr( "Locator" ), tr( "Locator" ), QStringLiteral( "search.svg" ) ) ); mTreeModel->appendRow( createItem( tr( "Acceleration" ), tr( "GPU acceleration" ), QStringLiteral( "mIconGPU.svg" ) ) ); + QStandardItem *ideGroup = new QStandardItem( tr( "IDE" ) ); + ideGroup->setData( QStringLiteral( "ide" ) ); + ideGroup->setToolTip( tr( "Development and Scripting Settings" ) ); + ideGroup->setSelectable( false ); + mTreeModel->appendRow( ideGroup ); + mOptionsTreeView->setModel( mTreeModel ); connect( cbxProjectDefaultNew, &QCheckBox::toggled, this, &QgsOptions::cbxProjectDefaultNew_toggled ); @@ -1259,9 +1266,9 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl, const QListpagePositionHint(); if ( beforePage.isEmpty() ) - addPage( factory->title(), factory->title(), factory->icon(), page ); + addPage( factory->title(), factory->title(), factory->icon(), page, factory->path() ); else - insertPage( factory->title(), factory->title(), factory->icon(), page, beforePage ); + insertPage( factory->title(), factory->title(), factory->icon(), page, beforePage, factory->path() ); if ( QgsAdvancedSettingsWidget *advancedPage = qobject_cast< QgsAdvancedSettingsWidget * >( page ) ) { @@ -1361,6 +1368,8 @@ QgsOptions::~QgsOptions() void QgsOptions::checkPageWidgetNameMap() { + return; + const QMap< QString, int > pageNames = QgisApp::instance()->optionsPagesMap(); int pageCount = 0; diff --git a/src/gui/qgsoptionsdialogbase.cpp b/src/gui/qgsoptionsdialogbase.cpp index fe5097b9c23..ccf2b12b394 100644 --- a/src/gui/qgsoptionsdialogbase.cpp +++ b/src/gui/qgsoptionsdialogbase.cpp @@ -320,13 +320,15 @@ void QgsOptionsDialogBase::setCurrentPage( const QString &page ) } } -void QgsOptionsDialogBase::addPage( const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget ) +void QgsOptionsDialogBase::addPage( const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget, const QStringList &path ) { QListWidgetItem *item = new QListWidgetItem(); item->setIcon( icon ); item->setText( title ); item->setToolTip( tooltip ); + int newPage = -1; + if ( mOptListWidget ) { mOptListWidget->addItem( item ); @@ -335,18 +337,70 @@ void QgsOptionsDialogBase::addPage( const QString &title, const QString &tooltip { QStandardItem *item = new QStandardItem( icon, title ); item->setToolTip( tooltip ); - mOptTreeModel->appendRow( item ); + + QModelIndex parent; + QStandardItem *parentItem = nullptr; + if ( !path.empty() ) + { + QStringList parents = path; + while ( !parents.empty() ) + { + const QString parentPath = parents.takeFirst(); + + QModelIndex thisParent; + for ( int row = 0; row < mOptTreeModel->rowCount( parent ); ++row ) + { + const QModelIndex index = mOptTreeModel->index( row, 0, parent ); + if ( index.data().toString().compare( parentPath, Qt::CaseInsensitive ) == 0 + || index.data( Qt::UserRole + 1 ).toString().compare( parentPath, Qt::CaseInsensitive ) == 0 ) + { + thisParent = index; + break; + } + } + + // add new child if required + if ( !thisParent.isValid() ) + { + QStandardItem *newParentItem = new QStandardItem( parentPath ); + newParentItem->setToolTip( parentPath ); + newParentItem->setSelectable( false ); + if ( parentItem ) + parentItem->appendRow( newParentItem ); + else + mOptTreeModel->appendRow( newParentItem ); + parentItem = newParentItem; + } + else + { + parentItem = mOptTreeModel->itemFromIndex( thisParent ); + } + parent = mOptTreeModel->indexFromItem( parentItem ); + } + } + + if ( parentItem ) + { + parentItem->appendRow( item ); + const QModelIndex newIndex = mOptTreeModel->indexFromItem( item ); + newPage = mTreeProxyModel->sourceIndexToPageNumber( newIndex ); + } + else + mOptTreeModel->appendRow( item ); } - mOptStackedWidget->addWidget( widget ); + if ( newPage < 0 ) + mOptStackedWidget->addWidget( widget ); + else + mOptStackedWidget->insertWidget( newPage, widget ); } -void QgsOptionsDialogBase::insertPage( const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget, const QString &before ) +void QgsOptionsDialogBase::insertPage( const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget, const QString &before, const QStringList &path ) { //find the page with a matching widget name - for ( int idx = 0; idx < mOptStackedWidget->count(); ++idx ) + for ( int page = 0; page < mOptStackedWidget->count(); ++page ) { - QWidget *currentPage = mOptStackedWidget->widget( idx ); + QWidget *currentPage = mOptStackedWidget->widget( page ); if ( currentPage->objectName() == before ) { //found the "before" page @@ -358,26 +412,83 @@ void QgsOptionsDialogBase::insertPage( const QString &title, const QString &tool if ( mOptListWidget ) { - mOptListWidget->insertItem( idx, item ); + mOptListWidget->insertItem( page, item ); } else if ( mOptTreeModel ) { - QModelIndex sourceIndexBefore = mTreeProxyModel->pageNumberToSourceIndex( idx ); + QModelIndex sourceIndexBefore = mTreeProxyModel->pageNumberToSourceIndex( page ); + QList< QModelIndex > sourceBeforeIndices; while ( sourceIndexBefore.parent().isValid() ) + { + sourceBeforeIndices.insert( 0, sourceIndexBefore ); sourceIndexBefore = sourceIndexBefore.parent(); + } + sourceBeforeIndices.insert( 0, sourceIndexBefore ); + + QStringList parentPaths = path; + + QModelIndex parentIndex; + QStandardItem *parentItem = nullptr; + while ( !parentPaths.empty() ) + { + QString thisPath = parentPaths.takeFirst(); + QModelIndex sourceIndex = !sourceBeforeIndices.isEmpty() ? sourceBeforeIndices.takeFirst() : QModelIndex(); + + if ( sourceIndex.data().toString().compare( thisPath, Qt::CaseInsensitive ) == 0 + || sourceIndex.data( Qt::UserRole + 1 ).toString().compare( thisPath, Qt::CaseInsensitive ) == 0 ) + { + parentIndex = sourceIndex; + parentItem = mOptTreeModel->itemFromIndex( parentIndex ); + } + else + { + QStandardItem *newParentItem = new QStandardItem( thisPath ); + newParentItem->setToolTip( thisPath ); + newParentItem->setSelectable( false ); + if ( sourceIndex.isValid() ) + { + // insert in model before sourceIndex + if ( parentItem ) + parentItem->insertRow( sourceIndex.row(), newParentItem ); + else + mOptTreeModel->insertRow( sourceIndex.row(), newParentItem ); + } + else + { + // append to end + if ( parentItem ) + parentItem->appendRow( newParentItem ); + else + mOptTreeModel->appendRow( newParentItem ); + } + parentItem = newParentItem; + } + } QStandardItem *item = new QStandardItem( icon, title ); item->setToolTip( tooltip ); - mOptTreeModel->insertRow( sourceIndexBefore.row(), item ); + if ( parentItem ) + { + if ( sourceBeforeIndices.empty() ) + parentItem->appendRow( item ); + else + { + parentItem->insertRow( sourceBeforeIndices.at( 0 ).row(), item ); + } + } + else + { + mOptTreeModel->insertRow( sourceIndexBefore.row(), item ); + } } - mOptStackedWidget->insertWidget( idx, widget ); + mOptStackedWidget->insertWidget( page, widget ); return; } } // no matching pages, so just add the page - addPage( title, tooltip, icon, widget ); + addPage( title, tooltip, icon, widget, path ); } void QgsOptionsDialogBase::searchText( const QString &text ) diff --git a/src/gui/qgsoptionsdialogbase.h b/src/gui/qgsoptionsdialogbase.h index 82ce4765105..b6e6a90d5b1 100644 --- a/src/gui/qgsoptionsdialogbase.h +++ b/src/gui/qgsoptionsdialogbase.h @@ -149,10 +149,13 @@ class GUI_EXPORT QgsOptionsDialogBase : public QDialog * * The page content is specified via the \a widget argument. Ownership of \a widget is transferred to the dialog. * + * Since QGIS 3.22, the optional \a path argument can be used to set the path of the item's entry in the tree view + * (for dialogs which show a tree view of options pages only). + * * \see insertPage() * \since QGIS 3.14 */ - void addPage( const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget SIP_TRANSFER ); + void addPage( const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget SIP_TRANSFER, const QStringList &path = QStringList() ); /** * Inserts a new page into the dialog pages. @@ -164,10 +167,13 @@ class GUI_EXPORT QgsOptionsDialogBase : public QDialog * The \a before argument specifies the object name of an existing page. The new page will be inserted directly * before the matching page. * + * Since QGIS 3.22, the optional \a path argument can be used to set the path of the item's entry in the tree view + * (for dialogs which show a tree view of options pages only). + * * \see addPage() * \since QGIS 3.14 */ - void insertPage( const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget SIP_TRANSFER, const QString &before ); + void insertPage( const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget SIP_TRANSFER, const QString &before, const QStringList &path = QStringList() ); public slots: diff --git a/src/gui/qgsoptionswidgetfactory.h b/src/gui/qgsoptionswidgetfactory.h index 2823c286760..dc2b20c93d2 100644 --- a/src/gui/qgsoptionswidgetfactory.h +++ b/src/gui/qgsoptionswidgetfactory.h @@ -148,6 +148,17 @@ class GUI_EXPORT QgsOptionsWidgetFactory : public QObject */ virtual QString pagePositionHint() const { return QString(); } + /** + * Returns the path to place the widget page at, for options dialogs + * which are structured using a tree view. + * + * A factory which returns "Code", "Javascript" would have its widget placed + * in a group named "Javascript", contained in a parent group named "Code". + * + * \since QGIS 3.22 + */ + virtual QStringList path() const { return QStringList(); } + /** * \brief Factory function to create the widget on demand as needed by the options dialog. * \param parent The parent of the widget.