Factories can specify a path to add their pages to the options tree

This commit is contained in:
Nyall Dawson 2021-07-29 13:55:35 +10:00
parent 518efd0850
commit 550a1d3a52
9 changed files with 186 additions and 17 deletions

View File

@ -38,6 +38,9 @@ class ConsoleOptionsFactory(QgsOptionsWidgetFactory):
def icon(self): def icon(self):
return QgsApplication.getThemeIcon('/console/mIconRunConsole.svg') return QgsApplication.getThemeIcon('/console/mIconRunConsole.svg')
def path(self):
return ['ide']
def createWidget(self, parent): def createWidget(self, parent):
return ConsoleOptionsPage(parent) return ConsoleOptionsPage(parent)

View File

@ -96,7 +96,7 @@ Sets the dialog ``page`` (by object name) to show.
.. versionadded:: 3.14 .. versionadded:: 3.14
%End %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 %Docstring
Adds a new page to the dialog pages. 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. 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` .. seealso:: :py:func:`insertPage`
.. versionadded:: 3.14 .. versionadded:: 3.14
%End %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 %Docstring
Inserts a new page into the dialog pages. 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 The ``before`` argument specifies the object name of an existing page. The new page will be inserted directly
before the matching page. 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` .. seealso:: :py:func:`addPage`
.. versionadded:: 3.14 .. versionadded:: 3.14

View File

@ -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. to be placed at the end of the dialog page list.
.. versionadded:: 3.18 .. 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 %End
virtual QgsOptionsPageWidget *createWidget( QWidget *parent = 0 ) const = 0 /Factory/; virtual QgsOptionsPageWidget *createWidget( QWidget *parent = 0 ) const = 0 /Factory/;

View File

@ -356,3 +356,13 @@ QgsOptionsPageWidget *QgsCodeEditorOptionsFactory::createWidget( QWidget *parent
{ {
return new QgsCodeEditorOptionsWidget( parent ); return new QgsCodeEditorOptionsWidget( parent );
} }
QStringList QgsCodeEditorOptionsFactory::path() const
{
return {QStringLiteral( "ide" ) };
}
QString QgsCodeEditorOptionsFactory::pagePositionHint() const
{
return QStringLiteral( "consoleOptions" );
}

View File

@ -63,6 +63,8 @@ class QgsCodeEditorOptionsFactory : public QgsOptionsWidgetFactory
QIcon icon() const override; QIcon icon() const override;
QgsOptionsPageWidget *createWidget( QWidget *parent = nullptr ) const override; QgsOptionsPageWidget *createWidget( QWidget *parent = nullptr ) const override;
QStringList path() const override;
QString pagePositionHint() const override;
}; };

View File

@ -110,6 +110,7 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl, const QList<QgsOpti
mTreeModel->appendRow( createItem( tr( "System" ), tr( "System" ), QStringLiteral( "propertyicons/system.svg" ) ) ); mTreeModel->appendRow( createItem( tr( "System" ), tr( "System" ), QStringLiteral( "propertyicons/system.svg" ) ) );
QStandardItem *crsGroup = new QStandardItem( tr( "CRS and Transforms" ) ); QStandardItem *crsGroup = new QStandardItem( tr( "CRS and Transforms" ) );
crsGroup->setToolTip( tr( "CRS and Transforms" ) );
crsGroup->setSelectable( false ); crsGroup->setSelectable( false );
crsGroup->appendRow( createItem( tr( "CRS" ), tr( "CRS" ), QStringLiteral( "propertyicons/CRS.svg" ) ) ); crsGroup->appendRow( createItem( tr( "CRS" ), tr( "CRS" ), QStringLiteral( "propertyicons/CRS.svg" ) ) );
crsGroup->appendRow( createItem( tr( "Transformations" ), tr( "Coordinate transformations and operations" ), QStringLiteral( "transformation.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 QList<QgsOpti
mTreeModel->appendRow( createItem( tr( "Locator" ), tr( "Locator" ), QStringLiteral( "search.svg" ) ) ); mTreeModel->appendRow( createItem( tr( "Locator" ), tr( "Locator" ), QStringLiteral( "search.svg" ) ) );
mTreeModel->appendRow( createItem( tr( "Acceleration" ), tr( "GPU acceleration" ), QStringLiteral( "mIconGPU.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 ); mOptionsTreeView->setModel( mTreeModel );
connect( cbxProjectDefaultNew, &QCheckBox::toggled, this, &QgsOptions::cbxProjectDefaultNew_toggled ); connect( cbxProjectDefaultNew, &QCheckBox::toggled, this, &QgsOptions::cbxProjectDefaultNew_toggled );
@ -1259,9 +1266,9 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl, const QList<QgsOpti
mAdditionalOptionWidgets << page; mAdditionalOptionWidgets << page;
const QString beforePage = factory->pagePositionHint(); const QString beforePage = factory->pagePositionHint();
if ( beforePage.isEmpty() ) if ( beforePage.isEmpty() )
addPage( factory->title(), factory->title(), factory->icon(), page ); addPage( factory->title(), factory->title(), factory->icon(), page, factory->path() );
else 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 ) ) if ( QgsAdvancedSettingsWidget *advancedPage = qobject_cast< QgsAdvancedSettingsWidget * >( page ) )
{ {
@ -1361,6 +1368,8 @@ QgsOptions::~QgsOptions()
void QgsOptions::checkPageWidgetNameMap() void QgsOptions::checkPageWidgetNameMap()
{ {
return;
const QMap< QString, int > pageNames = QgisApp::instance()->optionsPagesMap(); const QMap< QString, int > pageNames = QgisApp::instance()->optionsPagesMap();
int pageCount = 0; int pageCount = 0;

View File

@ -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(); QListWidgetItem *item = new QListWidgetItem();
item->setIcon( icon ); item->setIcon( icon );
item->setText( title ); item->setText( title );
item->setToolTip( tooltip ); item->setToolTip( tooltip );
int newPage = -1;
if ( mOptListWidget ) if ( mOptListWidget )
{ {
mOptListWidget->addItem( item ); mOptListWidget->addItem( item );
@ -335,18 +337,70 @@ void QgsOptionsDialogBase::addPage( const QString &title, const QString &tooltip
{ {
QStandardItem *item = new QStandardItem( icon, title ); QStandardItem *item = new QStandardItem( icon, title );
item->setToolTip( tooltip ); 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 //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 ) if ( currentPage->objectName() == before )
{ {
//found the "before" page //found the "before" page
@ -358,26 +412,83 @@ void QgsOptionsDialogBase::insertPage( const QString &title, const QString &tool
if ( mOptListWidget ) if ( mOptListWidget )
{ {
mOptListWidget->insertItem( idx, item ); mOptListWidget->insertItem( page, item );
} }
else if ( mOptTreeModel ) else if ( mOptTreeModel )
{ {
QModelIndex sourceIndexBefore = mTreeProxyModel->pageNumberToSourceIndex( idx ); QModelIndex sourceIndexBefore = mTreeProxyModel->pageNumberToSourceIndex( page );
QList< QModelIndex > sourceBeforeIndices;
while ( sourceIndexBefore.parent().isValid() ) while ( sourceIndexBefore.parent().isValid() )
{
sourceBeforeIndices.insert( 0, sourceIndexBefore );
sourceIndexBefore = sourceIndexBefore.parent(); 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 ); QStandardItem *item = new QStandardItem( icon, title );
item->setToolTip( tooltip ); 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; return;
} }
} }
// no matching pages, so just add the page // 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 ) void QgsOptionsDialogBase::searchText( const QString &text )

View File

@ -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. * 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() * \see insertPage()
* \since QGIS 3.14 * \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. * 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 * The \a before argument specifies the object name of an existing page. The new page will be inserted directly
* before the matching page. * 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() * \see addPage()
* \since QGIS 3.14 * \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: public slots:

View File

@ -148,6 +148,17 @@ class GUI_EXPORT QgsOptionsWidgetFactory : public QObject
*/ */
virtual QString pagePositionHint() const { return QString(); } 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. * \brief Factory function to create the widget on demand as needed by the options dialog.
* \param parent The parent of the widget. * \param parent The parent of the widget.