Skip to content

Commit

Permalink
Factories can specify a path to add their pages to the options tree
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Jul 29, 2021
1 parent 518efd0 commit 550a1d3
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 17 deletions.
3 changes: 3 additions & 0 deletions python/console/console_settings.py
Expand Up @@ -38,6 +38,9 @@ def __init__(self):
def icon(self):
return QgsApplication.getThemeIcon('/console/mIconRunConsole.svg')

def path(self):
return ['ide']

def createWidget(self, parent):
return ConsoleOptionsPage(parent)

Expand Down
10 changes: 8 additions & 2 deletions python/gui/auto_generated/qgsoptionsdialogbase.sip.in
Expand Up @@ -96,20 +96,23 @@ 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.

The ``title``, ``tooltip`` and ``icon`` arguments dictate the page list item title, tooltip and icon respectively.

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.

Expand All @@ -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
Expand Down
11 changes: 11 additions & 0 deletions python/gui/auto_generated/qgsoptionswidgetfactory.sip.in
Expand Up @@ -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/;
Expand Down
10 changes: 10 additions & 0 deletions src/app/options/qgscodeeditoroptions.cpp
Expand Up @@ -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" );
}
2 changes: 2 additions & 0 deletions src/app/options/qgscodeeditoroptions.h
Expand Up @@ -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;

};

Expand Down
13 changes: 11 additions & 2 deletions src/app/options/qgsoptions.cpp
Expand Up @@ -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" ) ) );

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" ) ) );
Expand All @@ -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( "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 );
Expand Down Expand Up @@ -1259,9 +1266,9 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl, const QList<QgsOpti
mAdditionalOptionWidgets << page;
const QString beforePage = factory->pagePositionHint();
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 ) )
{
Expand Down Expand Up @@ -1361,6 +1368,8 @@ QgsOptions::~QgsOptions()

void QgsOptions::checkPageWidgetNameMap()
{
return;

const QMap< QString, int > pageNames = QgisApp::instance()->optionsPagesMap();

int pageCount = 0;
Expand Down
133 changes: 122 additions & 11 deletions src/gui/qgsoptionsdialogbase.cpp
Expand Up @@ -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 );
Expand All @@ -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
Expand All @@ -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 )
Expand Down
10 changes: 8 additions & 2 deletions src/gui/qgsoptionsdialogbase.h
Expand Up @@ -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.
Expand All @@ -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:

Expand Down
11 changes: 11 additions & 0 deletions src/gui/qgsoptionswidgetfactory.h
Expand Up @@ -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.
Expand Down

0 comments on commit 550a1d3

Please sign in to comment.