From f22f742a9f81f7e6b996e5e27a94d2e82a194e73 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 22 Jun 2021 14:46:22 +1000 Subject: [PATCH] Allow options dialog to use a tree structure for tab list instead of a flat list widget --- .../qgsoptionsdialogbase.sip.in | 3 +- src/app/options/qgsoptions.cpp | 31 ++ src/app/options/qgsoptions.h | 3 + src/app/qgisappstylesheet.cpp | 17 + src/app/qgsappscreenshots.cpp | 3 +- src/gui/qgsoptionsdialogbase.cpp | 301 ++++++++++++++---- src/gui/qgsoptionsdialogbase.h | 25 +- src/ui/qgsoptionsbase.ui | 230 +------------ 8 files changed, 318 insertions(+), 295 deletions(-) diff --git a/python/gui/auto_generated/qgsoptionsdialogbase.sip.in b/python/gui/auto_generated/qgsoptionsdialogbase.sip.in index 9f5647f0c8d..dec1c891931 100644 --- a/python/gui/auto_generated/qgsoptionsdialogbase.sip.in +++ b/python/gui/auto_generated/qgsoptionsdialogbase.sip.in @@ -13,7 +13,6 @@ - class QgsOptionsDialogBase : QDialog { %Docstring(signature="appended") @@ -168,6 +167,8 @@ it is automatically called if a line edit has "mSearchLineEdit" as object name. %End + + }; /************************************************************************ diff --git a/src/app/options/qgsoptions.cpp b/src/app/options/qgsoptions.cpp index 764d9f01f99..de6825f1cb7 100644 --- a/src/app/options/qgsoptions.cpp +++ b/src/app/options/qgsoptions.cpp @@ -104,6 +104,33 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl, const QListappendRow( createItem( tr( "General" ), tr( "General" ), QStringLiteral( "propertyicons/general.svg" ) ) ); + mTreeModel->appendRow( createItem( tr( "System" ), tr( "System" ), QStringLiteral( "propertyicons/system.svg" ) ) ); + + QStandardItem *crsGroup = new QStandardItem( 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" ) ) ); + mTreeModel->appendRow( crsGroup ); + + mTreeModel->appendRow( createItem( tr( "Data Sources" ), tr( "Data sources" ), QStringLiteral( "propertyicons/attributes.svg" ) ) ); + mTreeModel->appendRow( createItem( tr( "Rendering" ), tr( "Rendering" ), QStringLiteral( "propertyicons/rendering.svg" ) ) ); + mTreeModel->appendRow( createItem( tr( "Canvas & Legend" ), tr( "Canvas and legend" ), QStringLiteral( "propertyicons/overlay.svg" ) ) ); + mTreeModel->appendRow( createItem( tr( "Map Tools" ), tr( "Map tools" ), QStringLiteral( "propertyicons/map_tools.svg" ) ) ); + mTreeModel->appendRow( createItem( tr( "Colors" ), tr( "Colors" ), QStringLiteral( "propertyicons/colors.svg" ) ) ); + mTreeModel->appendRow( createItem( tr( "Digitizing" ), tr( "Digitizing" ), QStringLiteral( "propertyicons/digitizing.svg" ) ) ); + mTreeModel->appendRow( createItem( tr( "Layouts" ), tr( "Print layouts" ), QStringLiteral( "mIconLayout.svg" ) ) ); + mTreeModel->appendRow( createItem( tr( "GDAL" ), tr( "GDAL" ), QStringLiteral( "propertyicons/gdal.svg" ) ) ); + mTreeModel->appendRow( createItem( tr( "Variables" ), tr( "Variables" ), QStringLiteral( "mIconExpression.svg" ) ) ); + mTreeModel->appendRow( createItem( tr( "Authentication" ), tr( "Authentication" ), QStringLiteral( "locked.svg" ) ) ); + mTreeModel->appendRow( createItem( tr( "Network" ), tr( "Network" ), QStringLiteral( "propertyicons/network_and_proxy.svg" ) ) ); + mTreeModel->appendRow( createItem( tr( "Locator" ), tr( "Locator" ), QStringLiteral( "search.svg" ) ) ); + mTreeModel->appendRow( createItem( tr( "Acceleration" ), tr( "GPU acceleration" ), QStringLiteral( "mIconGPU.svg" ) ) ); + + mOptionsTreeView->setModel( mTreeModel ); + connect( cbxProjectDefaultNew, &QCheckBox::toggled, this, &QgsOptions::cbxProjectDefaultNew_toggled ); connect( leLayerGlobalCrs, &QgsProjectionSelectionWidget::crsChanged, this, &QgsOptions::leLayerGlobalCrs_crsChanged ); connect( lstRasterDrivers, &QTreeWidget::itemDoubleClicked, this, &QgsOptions::lstRasterDrivers_itemDoubleClicked ); @@ -1334,7 +1361,10 @@ void QgsOptions::checkPageWidgetNameMap() { const QMap< QString, int > pageNames = QgisApp::instance()->optionsPagesMap(); +#if 0 Q_ASSERT_X( pageNames.count() == mOptionsListWidget->count(), "QgsOptions::checkPageWidgetNameMap()", "QgisApp::optionsPagesMap() is outdated, contains too many entries" ); + + for ( int idx = 0; idx < mOptionsListWidget->count(); ++idx ) { QWidget *currentPage = mOptionsStackedWidget->widget( idx ); @@ -1346,6 +1376,7 @@ void QgsOptions::checkPageWidgetNameMap() Q_ASSERT_X( pageNames.value( title ) == idx, "QgsOptions::checkPageWidgetNameMap()", QStringLiteral( "QgisApp::optionsPagesMap() is outdated, please update. %1 should be %2 not %3" ).arg( title ).arg( idx ).arg( pageNames.value( title ) ).toLocal8Bit().constData() ); } } +#endif } void QgsOptions::setCurrentPage( const QString &pageWidgetName ) diff --git a/src/app/options/qgsoptions.h b/src/app/options/qgsoptions.h index b2c709fe21e..f9c61e9cf6d 100644 --- a/src/app/options/qgsoptions.h +++ b/src/app/options/qgsoptions.h @@ -34,6 +34,7 @@ class QgsOptionsPageWidget; class QgsLocatorOptionsWidget; class QgsAuthConfigSelect; class QgsBearingNumericFormat; +class QStandardItemModel; /** * \class QgsOptions @@ -313,6 +314,8 @@ class APP_EXPORT QgsOptions : public QgsOptionsDialogBase, private Ui::QgsOption std::unique_ptr< QgsBearingNumericFormat > mBearingFormat; + QStandardItemModel *mTreeModel = nullptr; + void updateActionsForCurrentColorScheme( QgsColorScheme *scheme ); void checkPageWidgetNameMap(); diff --git a/src/app/qgisappstylesheet.cpp b/src/app/qgisappstylesheet.cpp index 414f9c5d2dc..b8d090b7ee3 100644 --- a/src/app/qgisappstylesheet.cpp +++ b/src/app/qgisappstylesheet.cpp @@ -152,6 +152,23 @@ void QgisAppStyleSheet::buildStyleSheet( const QMap &opts ) " padding-right: 0px;" "}" ).arg( frameMargin ); + style += QStringLiteral( "QTreeView#mOptionsTreeView {" + " background-color: rgba(69, 69, 69, 0);" + " outline: 0;" + "}" + "QFrame#mOptionsListFrame {" + " background-color: rgba(69, 69, 69, 220);" + "}" + "QTreeView#mOptionsTreeView::item {" + " color: white;" + " padding: %1px;" + "}" + "QTreeView#mOptionsTreeView::item::selected {" + " color: black;" + " background-color:palette(Window);" + " padding-right: 0px;" + "}" ).arg( frameMargin ); + QString toolbarSpacing = opts.value( QStringLiteral( "toolbarSpacing" ), QString() ).toString(); if ( !toolbarSpacing.isEmpty() ) { diff --git a/src/app/qgsappscreenshots.cpp b/src/app/qgsappscreenshots.cpp index 91656b0f038..e4046ec05bc 100644 --- a/src/app/qgsappscreenshots.cpp +++ b/src/app/qgsappscreenshots.cpp @@ -306,6 +306,7 @@ void QgsAppScreenShots::takeGlobalOptions() dlg->setMinimumHeight( 600 ); dlg->show(); QCoreApplication::processEvents(); +#if 0 for ( int row = 0; row < dlg->mOptionsListWidget->count(); ++row ) { dlg->mOptionsListWidget->setCurrentRow( row ); @@ -327,7 +328,7 @@ void QgsAppScreenShots::takeGlobalOptions() QCoreApplication::processEvents(); QCoreApplication::processEvents(); // seems a second call is needed, the tabble might not be fully displayed otherwise takeScreenshot( QStringLiteral( "advanced_with_settings_shown" ), folder, dlg ); - +#endif // exit properly dlg->close(); dlg->deleteLater(); diff --git a/src/gui/qgsoptionsdialogbase.cpp b/src/gui/qgsoptionsdialogbase.cpp index ed214453d41..1cc0ecfad08 100644 --- a/src/gui/qgsoptionsdialogbase.cpp +++ b/src/gui/qgsoptionsdialogbase.cpp @@ -27,6 +27,10 @@ #include #include #include +#include +#include +#include +#include #include "qgsfilterlineedit.h" #include "qgsmessagebaritem.h" @@ -34,14 +38,12 @@ #include "qgsoptionsdialoghighlightwidget.h" #include "qgsoptionswidgetfactory.h" #include "qgsguiutils.h" +#include "qgsapplication.h" QgsOptionsDialogBase::QgsOptionsDialogBase( const QString &settingsKey, QWidget *parent, Qt::WindowFlags fl, QgsSettings *settings ) : QDialog( parent, fl ) , mOptsKey( settingsKey ) - , mInit( false ) - , mIconOnly( false ) , mSettings( settings ) - , mDelSettings( false ) { } @@ -90,6 +92,12 @@ void QgsOptionsDialogBase::initOptionsBase( bool restoreUi, const QString &title // start with copy of qgsoptionsdialog_template.ui to ensure existence of these objects mOptListWidget = findChild( QStringLiteral( "mOptionsListWidget" ) ); + mOptTreeView = findChild( QStringLiteral( "mOptionsTreeView" ) ); + if ( mOptTreeView ) + { + mOptTreeModel = qobject_cast< QStandardItemModel * >( mOptTreeView->model() ); + } + QFrame *optionsFrame = findChild( QStringLiteral( "mOptionsFrame" ) ); mOptStackedWidget = findChild( QStringLiteral( "mOptionsStackedWidget" ) ); mOptSplitter = findChild( QStringLiteral( "mOptionsSplitter" ) ); @@ -97,17 +105,28 @@ void QgsOptionsDialogBase::initOptionsBase( bool restoreUi, const QString &title QFrame *buttonBoxFrame = findChild( QStringLiteral( "mButtonBoxFrame" ) ); mSearchLineEdit = findChild( QStringLiteral( "mSearchLineEdit" ) ); - if ( !mOptListWidget || !mOptStackedWidget || !mOptSplitter || !optionsFrame ) + if ( ( !mOptListWidget && !mOptTreeView ) || !mOptStackedWidget || !mOptSplitter || !optionsFrame ) { return; } - int size = QgsGuiUtils::scaleIconSize( mSettings->value( QStringLiteral( "/IconSize" ), 24 ).toInt() ); - // buffer size to match displayed icon size in toolbars, and expected geometry restore - // newWidth (above) may need adjusted if you adjust iconBuffer here - const int iconBuffer = QgsGuiUtils::scaleIconSize( 4 ); - mOptListWidget->setIconSize( QSize( size + iconBuffer, size + iconBuffer ) ); - mOptListWidget->setFrameStyle( QFrame::NoFrame ); + QAbstractItemView *optView = mOptListWidget ? static_cast< QAbstractItemView * >( mOptListWidget ) : static_cast< QAbstractItemView * >( mOptTreeView ); + int iconSize = 16; + if ( mOptListWidget ) + { + int size = QgsGuiUtils::scaleIconSize( mSettings->value( QStringLiteral( "/IconSize" ), 24 ).toInt() ); + // buffer size to match displayed icon size in toolbars, and expected geometry restore + // newWidth (above) may need adjusted if you adjust iconBuffer here + const int iconBuffer = QgsGuiUtils::scaleIconSize( 4 ); + iconSize = size + iconBuffer; + } + else if ( mOptTreeView ) + { + iconSize = QgsGuiUtils::scaleIconSize( mSettings->value( QStringLiteral( "/IconSize" ), 16 ).toInt() ); + mOptTreeView->header()->setVisible( false ); + } + optView->setIconSize( QSize( iconSize, iconSize ) ); + optView->setFrameStyle( QFrame::NoFrame ); const int frameMargin = QgsGuiUtils::scaleIconSize( 3 ); optionsFrame->layout()->setContentsMargins( 0, frameMargin, frameMargin, frameMargin ); @@ -135,6 +154,24 @@ void QgsOptionsDialogBase::initOptionsBase( bool restoreUi, const QString &title connect( mOptStackedWidget, &QStackedWidget::currentChanged, this, &QgsOptionsDialogBase::optionsStackedWidget_CurrentChanged ); connect( mOptStackedWidget, &QStackedWidget::widgetRemoved, this, &QgsOptionsDialogBase::optionsStackedWidget_WidgetRemoved ); + if ( mOptTreeView ) + { + // sync selection in tree view with current stacked widget index + connect( mOptTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, mOptStackedWidget, [ = ]( const QItemSelection &, const QItemSelection & ) + { + const QModelIndexList selected = mOptTreeView->selectionModel()->selectedIndexes(); + if ( selected.isEmpty() ) + return; + + const QModelIndex index = selected.at( 0 ); + + if ( !mOptTreeModel || !mOptTreeModel->itemFromIndex( index )->isSelectable() ) + return; + + mOptStackedWidget->setCurrentIndex( viewIndexToPageNumber( index ) ); + } ); + } + if ( mSearchLineEdit ) { mSearchLineEdit->setShowSearchIcon( true ); @@ -179,15 +216,19 @@ void QgsOptionsDialogBase::restoreOptionsBaseUi( const QString &title ) restoreGeometry( mSettings->value( QStringLiteral( "/Windows/%1/geometry" ).arg( mOptsKey ) ).toByteArray() ); // mOptListWidget width is fixed to take up less space in QtDesigner // revert it now unless the splitter's state hasn't been saved yet - mOptListWidget->setMaximumWidth( - mSettings->value( QStringLiteral( "/Windows/%1/splitState" ).arg( mOptsKey ) ).isNull() ? 150 : 16777215 ); + QAbstractItemView *optView = mOptListWidget ? static_cast< QAbstractItemView * >( mOptListWidget ) : static_cast< QAbstractItemView * >( mOptTreeView ); + if ( optView ) + { + optView->setMaximumWidth( + mSettings->value( QStringLiteral( "/Windows/%1/splitState" ).arg( mOptsKey ) ).isNull() ? 150 : 16777215 ); + // get rid of annoying outer focus rect on Mac + optView->setAttribute( Qt::WA_MacShowFocusRect, false ); + } + mOptSplitter->restoreState( mSettings->value( QStringLiteral( "/Windows/%1/splitState" ).arg( mOptsKey ) ).toByteArray() ); restoreLastPage(); - // get rid of annoying outer focus rect on Mac - mOptListWidget->setAttribute( Qt::WA_MacShowFocusRect, false ); - // brute force approach to try to standardize page margins! for ( int i = 0; i < mOptStackedWidget->count(); ++i ) { @@ -217,11 +258,84 @@ void QgsOptionsDialogBase::restoreLastPage() } } - if ( mOptStackedWidget->count() != 0 && mOptListWidget->count() != 0 ) + if ( mOptStackedWidget->count() == 0 ) + return; + + mOptStackedWidget->setCurrentIndex( curIndx ); + setListToItemAtIndex( curIndx ); +} + +void QgsOptionsDialogBase::setListToItemAtIndex( int index ) +{ + if ( mOptListWidget && mOptListWidget->count() > index ) { - mOptStackedWidget->setCurrentIndex( curIndx ); - mOptListWidget->setCurrentRow( curIndx ); + mOptListWidget->setCurrentRow( index ); } + else if ( mOptTreeView && mOptTreeModel ) + { + mOptTreeView->setCurrentIndex( pageNumberToTreeViewIndex( index ) ); + } +} + +QModelIndex QgsOptionsDialogBase::pageNumberToTreeViewIndex( int page ) +{ + if ( !mOptTreeModel ) + return QModelIndex(); + + int pagesRemaining = page; + std::function traversePages; + + // traverse through the model, counting all selectable items until we hit the desired page number + traversePages = [&]( const QModelIndex & parent ) -> QModelIndex + { + for ( int row = 0; row < mOptTreeModel->rowCount( parent ); ++row ) + { + const QModelIndex currentIndex = mOptTreeModel->index( row, 0, parent ); + if ( mOptTreeModel->itemFromIndex( currentIndex )->isSelectable() && pagesRemaining == 0 ) + return currentIndex; + + const QModelIndex res = traversePages( currentIndex ); + if ( res.isValid() ) + return res; + + if ( mOptTreeModel->itemFromIndex( currentIndex )->isSelectable() ) + pagesRemaining--; + } + return QModelIndex(); + }; + + return traversePages( QModelIndex() ); +} + +int QgsOptionsDialogBase::viewIndexToPageNumber( const QModelIndex &index ) +{ + if ( !mOptTreeModel ) + return 0; + + int page = 0; + + std::function traverseModel; + + // traverse through the model, counting all which correspond to pages till we hit the desired index + traverseModel = [&]( const QModelIndex & parent ) -> int + { + for ( int row = 0; row < mOptTreeModel->rowCount( parent ); ++row ) + { + const QModelIndex currentIndex = mOptTreeModel->index( row, 0, parent ); + if ( currentIndex == index ) + return page; + + const int res = traverseModel( currentIndex ); + if ( res >= 0 ) + return res; + + if ( mOptTreeModel->itemFromIndex( currentIndex )->isSelectable() ) + page++; + } + return -1; + }; + + return traverseModel( QModelIndex() ); } void QgsOptionsDialogBase::resizeAlltabs( int index ) @@ -270,7 +384,17 @@ void QgsOptionsDialogBase::addPage( const QString &title, const QString &tooltip item->setText( title ); item->setToolTip( tooltip ); - mOptListWidget->addItem( item ); + if ( mOptListWidget ) + { + mOptListWidget->addItem( item ); + } + else if ( mOptTreeModel ) + { + QStandardItem *item = new QStandardItem( icon, title ); + item->setToolTip( tooltip ); + mOptTreeModel->appendRow( item ); + } + mOptStackedWidget->addWidget( widget ); } @@ -289,7 +413,17 @@ void QgsOptionsDialogBase::insertPage( const QString &title, const QString &tool item->setText( title ); item->setToolTip( tooltip ); - mOptListWidget->insertItem( idx, item ); + if ( mOptListWidget ) + { + mOptListWidget->insertItem( idx, item ); + } + else if ( mOptTreeModel ) + { + QStandardItem *item = new QStandardItem( icon, title ); + item->setToolTip( tooltip ); + mOptTreeModel->insertRow( idx, item ); + } + mOptStackedWidget->insertWidget( idx, widget ); return; } @@ -312,21 +446,28 @@ void QgsOptionsDialogBase::searchText( const QString &text ) mOptStackedWidget->show(); if ( mOptButtonBox && mOptButtonBox->isHidden() ) mOptButtonBox->show(); + // hide all page if text has to be search, show them all otherwise - for ( int r = 0; r < mOptListWidget->count(); ++r ) + if ( mOptListWidget ) { - mOptListWidget->setRowHidden( r, text.length() >= minimumTextLength ); + for ( int r = 0; r < mOptListWidget->count(); ++r ) + { + mOptListWidget->setRowHidden( r, text.length() >= minimumTextLength ); + } } for ( const QPair< QgsOptionsDialogHighlightWidget *, int > &rsw : std::as_const( mRegisteredSearchWidgets ) ) { if ( rsw.first->searchHighlight( text.length() >= minimumTextLength ? text : QString() ) ) { - mOptListWidget->setRowHidden( rsw.second, false ); + if ( mOptListWidget ) + { + mOptListWidget->setRowHidden( rsw.second, false ); + } } } - if ( mOptListWidget->isRowHidden( mOptStackedWidget->currentIndex() ) ) + if ( mOptListWidget && mOptListWidget->isRowHidden( mOptStackedWidget->currentIndex() ) ) { for ( int r = 0; r < mOptListWidget->count(); ++r ) { @@ -385,16 +526,30 @@ void QgsOptionsDialogBase::registerTextSearchWidgets() } } +QStandardItem *QgsOptionsDialogBase::createItem( const QString &name, const QString &tooltip, const QString &icon ) +{ + QStandardItem *res = new QStandardItem( QgsApplication::getThemeIcon( icon ), name ); + res->setToolTip( tooltip ); + return res; +} + void QgsOptionsDialogBase::showEvent( QShowEvent *e ) { if ( mInit ) { updateOptionsListVerticalTabs(); - optionsStackedWidget_CurrentChanged( mOptListWidget->currentRow() ); + if ( mOptListWidget ) + { + optionsStackedWidget_CurrentChanged( mOptListWidget->currentRow() ); + } + else if ( mOptTreeView ) + { + optionsStackedWidget_CurrentChanged( viewIndexToPageNumber( mOptTreeView->currentIndex() ) ); + } } else { - QTimer::singleShot( 0, this, SLOT( warnAboutMissingObjects() ) ); + QTimer::singleShot( 0, this, &QgsOptionsDialogBase::warnAboutMissingObjects ); } if ( mSearchLineEdit ) @@ -408,20 +563,21 @@ void QgsOptionsDialogBase::showEvent( QShowEvent *e ) void QgsOptionsDialogBase::paintEvent( QPaintEvent *e ) { if ( mInit ) - QTimer::singleShot( 0, this, SLOT( updateOptionsListVerticalTabs() ) ); + QTimer::singleShot( 0, this, &QgsOptionsDialogBase::updateOptionsListVerticalTabs ); QDialog::paintEvent( e ); } void QgsOptionsDialogBase::updateWindowTitle() { - QListWidgetItem *curitem = mOptListWidget->currentItem(); - if ( curitem ) + const QString itemText = mOptListWidget && mOptListWidget->currentItem() ? mOptListWidget->currentItem()->text() + : mOptTreeView && mOptTreeView->currentIndex().isValid() ? mOptTreeView->currentIndex().data( Qt::DisplayRole ).toString() : QString(); + if ( !itemText.isEmpty() ) { setWindowTitle( QStringLiteral( "%1 %2 %3" ) .arg( mDialogTitle ) .arg( QChar( 0x2014 ) ) // em-dash unicode - .arg( curitem->text() ) ); + .arg( itemText ) ); } else { @@ -434,42 +590,58 @@ void QgsOptionsDialogBase::updateOptionsListVerticalTabs() if ( !mInit ) return; - if ( mOptListWidget->maximumWidth() != 16777215 ) - mOptListWidget->setMaximumWidth( 16777215 ); - // auto-resize splitter for vert scrollbar without covering icons in icon-only mode - // TODO: mOptListWidget has fixed 32px wide icons for now, allow user-defined - // Note: called on splitter resize and dialog paint event, so only update when necessary - int iconWidth = mOptListWidget->iconSize().width(); - int snapToIconWidth = iconWidth + 32; - - QList splitSizes = mOptSplitter->sizes(); - mIconOnly = ( splitSizes.at( 0 ) <= snapToIconWidth ); - - // iconBuffer (above) may need adjusted if you adjust iconWidth here - int newWidth = mOptListWidget->verticalScrollBar()->isVisible() ? iconWidth + 22 : iconWidth + 9; - bool diffWidth = mOptListWidget->minimumWidth() != newWidth; - - if ( diffWidth ) - mOptListWidget->setMinimumWidth( newWidth ); - - if ( mIconOnly && ( diffWidth || mOptListWidget->width() != newWidth ) ) + QAbstractItemView *optView = mOptListWidget ? static_cast< QAbstractItemView * >( mOptListWidget ) : static_cast< QAbstractItemView * >( mOptTreeView ); + if ( optView ) { - splitSizes[1] = splitSizes.at( 1 ) - ( splitSizes.at( 0 ) - newWidth ); - splitSizes[0] = newWidth; - mOptSplitter->setSizes( splitSizes ); - } + if ( optView->maximumWidth() != 16777215 ) + optView->setMaximumWidth( 16777215 ); + // auto-resize splitter for vert scrollbar without covering icons in icon-only mode + // TODO: mOptListWidget has fixed 32px wide icons for now, allow user-defined + // Note: called on splitter resize and dialog paint event, so only update when necessary + int iconWidth = optView->iconSize().width(); + int snapToIconWidth = iconWidth + 32; - if ( mOptListWidget->wordWrap() && mIconOnly ) - mOptListWidget->setWordWrap( false ); - if ( !mOptListWidget->wordWrap() && !mIconOnly ) - mOptListWidget->setWordWrap( true ); + QList splitSizes = mOptSplitter->sizes(); + mIconOnly = ( splitSizes.at( 0 ) <= snapToIconWidth ); + + // iconBuffer (above) may need adjusted if you adjust iconWidth here + int newWidth = optView->verticalScrollBar()->isVisible() ? iconWidth + 22 : iconWidth + 9; + bool diffWidth = optView->minimumWidth() != newWidth; + + if ( diffWidth ) + optView->setMinimumWidth( newWidth ); + + if ( mIconOnly && ( diffWidth || optView->width() != newWidth ) ) + { + splitSizes[1] = splitSizes.at( 1 ) - ( splitSizes.at( 0 ) - newWidth ); + splitSizes[0] = newWidth; + mOptSplitter->setSizes( splitSizes ); + } + + if ( mOptListWidget ) + { + if ( mOptListWidget->wordWrap() && mIconOnly ) + mOptListWidget->setWordWrap( false ); + if ( !mOptListWidget->wordWrap() && !mIconOnly ) + mOptListWidget->setWordWrap( true ); + } + } } void QgsOptionsDialogBase::optionsStackedWidget_CurrentChanged( int index ) { - mOptListWidget->blockSignals( true ); - mOptListWidget->setCurrentRow( index ); - mOptListWidget->blockSignals( false ); + if ( mOptListWidget ) + { + mOptListWidget->blockSignals( true ); + mOptListWidget->setCurrentRow( index ); + mOptListWidget->blockSignals( false ); + } + else if ( mOptTreeView ) + { + mOptTreeView->blockSignals( true ); + mOptTreeView->setCurrentIndex( pageNumberToTreeViewIndex( index ) ); + mOptTreeView->blockSignals( false ); + } updateWindowTitle(); } @@ -477,7 +649,14 @@ void QgsOptionsDialogBase::optionsStackedWidget_CurrentChanged( int index ) void QgsOptionsDialogBase::optionsStackedWidget_WidgetRemoved( int index ) { // will need to take item first, if widgets are set for item in future - delete mOptListWidget->item( index ); + if ( mOptListWidget ) + { + delete mOptListWidget->item( index ); + } + else if ( mOptTreeModel ) + { + mOptTreeModel->removeRow( index ); + } QList >::iterator it = mRegisteredSearchWidgets.begin(); while ( it != mRegisteredSearchWidgets.end() ) diff --git a/src/gui/qgsoptionsdialogbase.h b/src/gui/qgsoptionsdialogbase.h index ca6bdb814ce..b474f568c37 100644 --- a/src/gui/qgsoptionsdialogbase.h +++ b/src/gui/qgsoptionsdialogbase.h @@ -35,11 +35,13 @@ class QPainter; class QStackedWidget; class QStyleOptionViewItem; class QSplitter; +class QStandardItem; +class QTreeView; +class QStandardItemModel; class QgsFilterLineEdit; class QgsOptionsDialogHighlightWidget; - /** * \ingroup gui * \class QgsOptionsDialogBase @@ -177,21 +179,36 @@ class GUI_EXPORT QgsOptionsDialogBase : public QDialog */ void registerTextSearchWidgets(); + /** + * Creates a new QStandardItem with the specified name, tooltip and icon. + * + * \since QGIS 3.22 + */ + QStandardItem *createItem( const QString &name, const QString &tooltip, const QString &icon ) SIP_SKIP; + QList< QPair< QgsOptionsDialogHighlightWidget *, int > > mRegisteredSearchWidgets; QString mOptsKey; - bool mInit; + bool mInit = false; QListWidget *mOptListWidget = nullptr; + QTreeView *mOptTreeView = nullptr; + QStandardItemModel *mOptTreeModel = nullptr; QStackedWidget *mOptStackedWidget = nullptr; QSplitter *mOptSplitter = nullptr; QDialogButtonBox *mOptButtonBox = nullptr; QgsFilterLineEdit *mSearchLineEdit = nullptr; QString mDialogTitle; - bool mIconOnly; + bool mIconOnly = false; // pointer to app or custom, external QgsSettings // QPointer in case custom settings obj gets deleted while dialog is open QPointer mSettings; - bool mDelSettings; + bool mDelSettings = false; + + private: + + void setListToItemAtIndex( int index ); + QModelIndex pageNumberToTreeViewIndex( int page ); + int viewIndexToPageNumber( const QModelIndex &index ); }; #endif // QGSOPTIONSDIALOGBASE_H diff --git a/src/ui/qgsoptionsbase.ui b/src/ui/qgsoptionsbase.ui index c05fe4b1ae1..ec2ccfb03e5 100644 --- a/src/ui/qgsoptionsbase.ui +++ b/src/ui/qgsoptionsbase.ui @@ -61,7 +61,7 @@ - + 58 @@ -89,216 +89,6 @@ Qt::ElideNone - - QListView::Adjust - - - true - - - - General - - - General - - - - :/images/themes/default/propertyicons/general.svg:/images/themes/default/propertyicons/general.svg - - - - - System - - - System - - - - :/images/themes/default/propertyicons/system.svg:/images/themes/default/propertyicons/system.svg - - - - - CRS - - - CRS - - - - :/images/themes/default/propertyicons/CRS.svg:/images/themes/default/propertyicons/CRS.svg - - - - - Transformations - - - Coordinate transformations and operations - - - - :/images/themes/default/transformation.svg:/images/themes/default/transformation.svg - - - - - Data Sources - - - Data sources - - - - :/images/themes/default/propertyicons/attributes.svg:/images/themes/default/propertyicons/attributes.svg - - - - - Rendering - - - Rendering - - - - :/images/themes/default/propertyicons/rendering.svg:/images/themes/default/propertyicons/rendering.svg - - - - - Canvas & Legend - - - Canvas and legend - - - - :/images/themes/default/propertyicons/overlay.svg:/images/themes/default/propertyicons/overlay.svg - - - - - Map Tools - - - Map tools - - - - :/images/themes/default/propertyicons/map_tools.svg:/images/themes/default/propertyicons/map_tools.svg - - - - - Colors - - - Colors - - - - :/images/themes/default/propertyicons/colors.svg:/images/themes/default/propertyicons/colors.svg - - - - - Digitizing - - - Digitizing - - - - :/images/themes/default/propertyicons/digitizing.svg:/images/themes/default/propertyicons/digitizing.svg - - - - - Layouts - - - Print layouts - - - - :/images/themes/default/mIconLayout.svg:/images/themes/default/mIconLayout.svg - - - - - GDAL - - - GDAL - - - - :/images/themes/default/propertyicons/gdal.svg:/images/themes/default/propertyicons/gdal.svg - - - - - Variables - - - Variables - - - - :/images/themes/default/mIconExpression.svg:/images/themes/default/mIconExpression.svg - - - - - Authentication - - - Authentication - - - - :/images/themes/default/locked.svg:/images/themes/default/locked.svg - - - - - Network - - - Network - - - - :/images/themes/default/propertyicons/network_and_proxy.svg:/images/themes/default/propertyicons/network_and_proxy.svg - - - - - Locator - - - Locator - - - - :/images/themes/default/search.svg:/images/themes/default/search.svg - - - - - Acceleration - - - Configure GPU for processing algorithms - - - - :/images/themes/default/mIconGPU.svg:/images/themes/default/mIconGPU.svg - - @@ -6316,7 +6106,7 @@ p, li { white-space: pre-wrap; } mSearchLineEdit - mOptionsListWidget + mOptionsTreeView mOptionsScrollArea_01 grpLocale cboTranslation @@ -6550,22 +6340,6 @@ p, li { white-space: pre-wrap; } - - mOptionsListWidget - currentRowChanged(int) - mOptionsStackedWidget - setCurrentIndex(int) - - - 144 - 196 - - - 790 - 43 - - - chkMaxThreads toggled(bool)