QGIS API Documentation 3.99.0-Master (8e76e220402)
Loading...
Searching...
No Matches
qgsoptionsdialogbase.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsoptionsdialogbase.cpp - base vertical tabs option dialog
3
4 ---------------------
5 begin : March 24, 2013
6 copyright : (C) 2013 by Larry Shaffer
7 email : larrys at dakcarto dot com
8 ***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
18
19#include <functional>
20
21#include "qgsapplication.h"
22#include "qgsfilterlineedit.h"
23#include "qgsguiutils.h"
24#include "qgslogger.h"
27#include "qgsscrollarea.h"
28#include "qgsvariantutils.h"
29
30#include <QDialog>
31#include <QDialogButtonBox>
32#include <QHeaderView>
33#include <QLayout>
34#include <QListWidget>
35#include <QListWidgetItem>
36#include <QMessageBox>
37#include <QPainter>
38#include <QScrollBar>
39#include <QSplitter>
40#include <QStackedWidget>
41#include <QStandardItem>
42#include <QString>
43#include <QTimer>
44#include <QTreeView>
45
46#include "moc_qgsoptionsdialogbase.cpp"
47
48using namespace Qt::StringLiterals;
49
50QgsOptionsDialogBase::QgsOptionsDialogBase( const QString &settingsKey, QWidget *parent, Qt::WindowFlags fl, QgsSettings *settings )
51 : QDialog( parent, fl )
52 , mOptsKey( settingsKey )
53 , mSettings( settings )
54{
55}
56
58{
59 if ( mInit )
60 {
61 mSettings->setValue( u"/Windows/%1/geometry"_s.arg( mOptsKey ), saveGeometry() );
62 mSettings->setValue( u"/Windows/%1/splitState"_s.arg( mOptsKey ), mOptSplitter->saveState() );
63 mSettings->setValue( u"/Windows/%1/tab"_s.arg( mOptsKey ), mOptStackedWidget->currentIndex() );
64 }
65
66 if ( mDelSettings ) // local settings obj to delete
67 {
68 delete mSettings;
69 }
70
71 mSettings = nullptr; // null the pointer (in case of outside settings obj)
72}
73
74void QgsOptionsDialogBase::initOptionsBase( bool restoreUi, const QString &title )
75{
76 // use pointer to app QgsSettings if no custom QgsSettings specified
77 // custom QgsSettings object may be from Python plugin
78 mDelSettings = false;
79
80 if ( !mSettings )
81 {
82 mSettings = new QgsSettings();
83 mDelSettings = true; // only delete obj created by class
84 }
85
86 // save dialog title so it can be used to be concatenated
87 // with category title in icon-only mode
88 if ( title.isEmpty() )
89 mDialogTitle = windowTitle();
90 else
91 mDialogTitle = title;
92
93 // don't add to dialog margins
94 // redefine now, or those in inherited .ui file will be added
95 if ( auto *lLayout = layout() )
96 {
97 lLayout->setContentsMargins( 0, 0, 0, 0 ); // Qt default spacing
98 }
99
100 // start with copy of qgsoptionsdialog_template.ui to ensure existence of these objects
101 mOptListWidget = findChild<QListWidget *>( u"mOptionsListWidget"_s );
102 mOptTreeView = findChild<QTreeView *>( u"mOptionsTreeView"_s );
103 if ( mOptTreeView )
104 {
105 mOptTreeModel = qobject_cast<QStandardItemModel *>( mOptTreeView->model() );
106 mTreeProxyModel = new QgsOptionsProxyModel( this );
107 mTreeProxyModel->setSourceModel( mOptTreeModel );
108 mOptTreeView->setModel( mTreeProxyModel );
109 mOptTreeView->expandAll();
110 }
111
112 QFrame *optionsFrame = findChild<QFrame *>( u"mOptionsFrame"_s );
113 mOptStackedWidget = findChild<QStackedWidget *>( u"mOptionsStackedWidget"_s );
114 mOptSplitter = findChild<QSplitter *>( u"mOptionsSplitter"_s );
115 mOptButtonBox = findChild<QDialogButtonBox *>( u"buttonBox"_s );
116 QFrame *buttonBoxFrame = findChild<QFrame *>( u"mButtonBoxFrame"_s );
117 mSearchLineEdit = findChild<QgsFilterLineEdit *>( u"mSearchLineEdit"_s );
118
119 if ( ( !mOptListWidget && !mOptTreeView ) || !mOptStackedWidget || !mOptSplitter || !optionsFrame )
120 {
121 return;
122 }
123
124 QAbstractItemView *optView = mOptListWidget ? static_cast<QAbstractItemView *>( mOptListWidget ) : static_cast<QAbstractItemView *>( mOptTreeView );
125 int iconSize = 16;
126 if ( mOptListWidget )
127 {
128 int size = QgsGuiUtils::scaleIconSize( mSettings->value( u"/IconSize"_s, 24 ).toInt() );
129 // buffer size to match displayed icon size in toolbars, and expected geometry restore
130 // newWidth (above) may need adjusted if you adjust iconBuffer here
131 const int iconBuffer = QgsGuiUtils::scaleIconSize( 4 );
132 iconSize = size + iconBuffer;
133 }
134 else if ( mOptTreeView )
135 {
136 iconSize = QgsGuiUtils::scaleIconSize( mSettings->value( u"/IconSize"_s, 16 ).toInt() );
137 mOptTreeView->header()->setVisible( false );
138 }
139 optView->setIconSize( QSize( iconSize, iconSize ) );
140 optView->setFrameStyle( QFrame::NoFrame );
141
142 const int frameMargin = QgsGuiUtils::scaleIconSize( 3 );
143 optionsFrame->layout()->setContentsMargins( 0, frameMargin, frameMargin, frameMargin );
144 QVBoxLayout *layout = static_cast<QVBoxLayout *>( optionsFrame->layout() );
145
146 if ( buttonBoxFrame )
147 {
148 buttonBoxFrame->layout()->setContentsMargins( 0, 0, 0, 0 );
149 layout->insertWidget( layout->count(), buttonBoxFrame );
150 }
151 else if ( mOptButtonBox )
152 {
153 layout->insertWidget( layout->count(), mOptButtonBox );
154 }
155
156 if ( mOptButtonBox )
157 {
158 // enforce only one connection per signal, in case added in Qt Designer
159 disconnect( mOptButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
160 connect( mOptButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
161 disconnect( mOptButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
162 connect( mOptButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
163 }
164 connect( mOptSplitter, &QSplitter::splitterMoved, this, &QgsOptionsDialogBase::updateOptionsListVerticalTabs );
165 connect( mOptStackedWidget, &QStackedWidget::currentChanged, this, &QgsOptionsDialogBase::optionsStackedWidget_CurrentChanged );
166 connect( mOptStackedWidget, &QStackedWidget::widgetRemoved, this, &QgsOptionsDialogBase::optionsStackedWidget_WidgetRemoved );
167
168 if ( mOptTreeView )
169 {
170 // sync selection in tree view with current stacked widget index
171 connect( mOptTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, mOptStackedWidget, [this]( const QItemSelection &, const QItemSelection & ) {
172 const QModelIndexList selected = mOptTreeView->selectionModel()->selectedIndexes();
173 if ( selected.isEmpty() )
174 return;
175
176 const QModelIndex index = mTreeProxyModel->mapToSource( selected.at( 0 ) );
177
178 if ( !mOptTreeModel || !mOptTreeModel->itemFromIndex( index )->isSelectable() )
179 return;
180
181 mOptStackedWidget->setCurrentIndex( mTreeProxyModel->sourceIndexToPageNumber( index ) );
182 } );
183 }
184
185 if ( mSearchLineEdit )
186 {
187 mSearchLineEdit->setShowSearchIcon( true );
188 connect( mSearchLineEdit, &QgsFilterLineEdit::textChanged, this, &QgsOptionsDialogBase::searchText );
189 if ( mOptTreeView )
190 {
191 connect( mSearchLineEdit, &QgsFilterLineEdit::cleared, mOptTreeView, &QTreeView::expandAll );
192 }
193 }
194
195 mInit = true;
196
197 if ( restoreUi )
199}
200
202{
203 if ( mDelSettings ) // local settings obj to delete
204 {
205 delete mSettings;
206 }
207
208 mSettings = settings;
209 mDelSettings = false; // don't delete outside obj
210}
211
213{
214 if ( !mInit )
215 {
216 return;
217 }
218
219 if ( !title.isEmpty() )
220 {
221 mDialogTitle = title;
222 }
223 else
224 {
225 // re-save original dialog title in case it was changed after dialog initialization
226 mDialogTitle = windowTitle();
227 }
229
230 restoreGeometry( mSettings->value( u"/Windows/%1/geometry"_s.arg( mOptsKey ) ).toByteArray() );
231 // mOptListWidget width is fixed to take up less space in QtDesigner
232 // revert it now unless the splitter's state hasn't been saved yet
233 QAbstractItemView *optView = mOptListWidget ? static_cast<QAbstractItemView *>( mOptListWidget ) : static_cast<QAbstractItemView *>( mOptTreeView );
234 if ( optView )
235 {
236 optView->setMaximumWidth(
237 QgsVariantUtils::isNull( mSettings->value( u"/Windows/%1/splitState"_s.arg( mOptsKey ) ) ) ? 150 : 16777215
238 );
239 // get rid of annoying outer focus rect on Mac
240 optView->setAttribute( Qt::WA_MacShowFocusRect, false );
241 }
242
243 mOptSplitter->restoreState( mSettings->value( u"/Windows/%1/splitState"_s.arg( mOptsKey ) ).toByteArray() );
244
246
247 // brute force approach to try to standardize page margins!
248 for ( int i = 0; i < mOptStackedWidget->count(); ++i )
249 {
250 if ( QLayout *l = mOptStackedWidget->widget( i )->layout() )
251 {
252 l->setContentsMargins( 0, 0, 0, 0 );
253 }
254 }
255}
256
258{
259 int curIndx = mSettings->value( u"/Windows/%1/tab"_s.arg( mOptsKey ), 0 ).toInt();
260
261 // if the last used tab is out of range or not enabled display the first enabled one
262 if ( mOptStackedWidget->count() < curIndx + 1
263 || !mOptStackedWidget->widget( curIndx )->isEnabled() )
264 {
265 curIndx = 0;
266 for ( int i = 0; i < mOptStackedWidget->count(); i++ )
267 {
268 if ( mOptStackedWidget->widget( i )->isEnabled() )
269 {
270 curIndx = i;
271 break;
272 }
273 }
274 }
275
276 if ( mOptStackedWidget->count() == 0 )
277 return;
278
279 mOptStackedWidget->setCurrentIndex( curIndx );
280 setListToItemAtIndex( curIndx );
281}
282
283void QgsOptionsDialogBase::setListToItemAtIndex( int index )
284{
285 if ( mOptListWidget && mOptListWidget->count() > index )
286 {
287 mOptListWidget->setCurrentRow( index );
288 }
289 else if ( mOptTreeView && mOptTreeModel )
290 {
291 mOptTreeView->setCurrentIndex( mTreeProxyModel->mapFromSource( mTreeProxyModel->pageNumberToSourceIndex( index ) ) );
292 }
293}
294
296{
297 // Adjust size (GH issue #31449 and #32615)
298 // make the stacked widget size to the current page only
299 for ( int i = 0; i < mOptStackedWidget->count(); ++i )
300 {
301 // Set the size policy
302 QSizePolicy::Policy policy = QSizePolicy::Ignored;
303 if ( i == index )
304 {
305 policy = QSizePolicy::MinimumExpanding;
306 }
307
308 // update the size policy
309 mOptStackedWidget->widget( i )->setSizePolicy( policy, policy );
310
311 if ( i == index )
312 {
313 mOptStackedWidget->layout()->update();
314 }
315 }
316 mOptStackedWidget->adjustSize();
317}
318
319void QgsOptionsDialogBase::setCurrentPage( const QString &page )
320{
321 //find the page with a matching widget name
322 for ( int idx = 0; idx < mOptStackedWidget->count(); ++idx )
323 {
324 QWidget *currentPage = mOptStackedWidget->widget( idx );
325 if ( currentPage->objectName() == page )
326 {
327 //found the page, set it as current
328 mOptStackedWidget->setCurrentIndex( idx );
329 return;
330 }
331 }
332}
333
334void QgsOptionsDialogBase::addPage( const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget, const QStringList &path, const QString &key )
335{
336 int newPage = -1;
337
338 if ( mOptListWidget )
339 {
340 QListWidgetItem *item = new QListWidgetItem();
341 item->setIcon( icon );
342 item->setText( title );
343 item->setToolTip( tooltip );
344 mOptListWidget->addItem( item );
345 }
346 else if ( mOptTreeModel )
347 {
348 QStandardItem *item = new QStandardItem( icon, title );
349 item->setToolTip( tooltip );
350 if ( !key.isEmpty() )
351 {
352 item->setData( key );
353 }
354
355 QModelIndex parent;
356 QStandardItem *parentItem = nullptr;
357 if ( !path.empty() )
358 {
359 QStringList parents = path;
360 while ( !parents.empty() )
361 {
362 const QString parentPath = parents.takeFirst();
363
364 QModelIndex thisParent;
365 for ( int row = 0; row < mOptTreeModel->rowCount( parent ); ++row )
366 {
367 const QModelIndex index = mOptTreeModel->index( row, 0, parent );
368 if ( index.data().toString().compare( parentPath, Qt::CaseInsensitive ) == 0
369 || index.data( Qt::UserRole + 1 ).toString().compare( parentPath, Qt::CaseInsensitive ) == 0 )
370 {
371 thisParent = index;
372 break;
373 }
374 }
375
376 // add new child if required
377 if ( !thisParent.isValid() )
378 {
379 QStandardItem *newParentItem = new QStandardItem( parentPath );
380 newParentItem->setToolTip( parentPath );
381 newParentItem->setSelectable( false );
382 if ( parentItem )
383 parentItem->appendRow( newParentItem );
384 else
385 mOptTreeModel->appendRow( newParentItem );
386 parentItem = newParentItem;
387 }
388 else
389 {
390 parentItem = mOptTreeModel->itemFromIndex( thisParent );
391 }
392 parent = mOptTreeModel->indexFromItem( parentItem );
393 }
394 }
395
396 if ( parentItem )
397 {
398 parentItem->appendRow( item );
399 const QModelIndex newIndex = mOptTreeModel->indexFromItem( item );
400 newPage = mTreeProxyModel->sourceIndexToPageNumber( newIndex );
401 }
402 else
403 mOptTreeModel->appendRow( item );
404 }
405
406 QgsScrollArea *scrollArea = new QgsScrollArea();
407 scrollArea->setWidgetResizable( true );
408 scrollArea->setFrameShape( QFrame::NoFrame );
409 scrollArea->setObjectName( widget->objectName() );
410 scrollArea->setWidget( widget );
411
412 if ( newPage < 0 )
413 mOptStackedWidget->addWidget( scrollArea );
414 else
415 mOptStackedWidget->insertWidget( newPage, scrollArea );
416}
417
418void QgsOptionsDialogBase::insertPage( const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget, const QString &before, const QStringList &path, const QString &key )
419{
420 //find the page with a matching widget name
421 for ( int page = 0; page < mOptStackedWidget->count(); ++page )
422 {
423 QWidget *currentPage = mOptStackedWidget->widget( page );
424 if ( currentPage->objectName() == before )
425 {
426 //found the "before" page
427
428 if ( mOptListWidget )
429 {
430 QListWidgetItem *item = new QListWidgetItem();
431 item->setIcon( icon );
432 item->setText( title );
433 item->setToolTip( tooltip );
434 mOptListWidget->insertItem( page, item );
435 }
436 else if ( mOptTreeModel )
437 {
438 QModelIndex sourceIndexBefore = mTreeProxyModel->pageNumberToSourceIndex( page );
439 QList<QModelIndex> sourceBeforeIndices;
440 while ( sourceIndexBefore.parent().isValid() )
441 {
442 sourceBeforeIndices.insert( 0, sourceIndexBefore );
443 sourceIndexBefore = sourceIndexBefore.parent();
444 }
445 sourceBeforeIndices.insert( 0, sourceIndexBefore );
446
447 QStringList parentPaths = path;
448
449 QModelIndex parentIndex;
450 QStandardItem *parentItem = nullptr;
451 while ( !parentPaths.empty() )
452 {
453 QString thisPath = parentPaths.takeFirst();
454 QModelIndex sourceIndex = !sourceBeforeIndices.isEmpty() ? sourceBeforeIndices.takeFirst() : QModelIndex();
455
456 if ( sourceIndex.data().toString().compare( thisPath, Qt::CaseInsensitive ) == 0
457 || sourceIndex.data( Qt::UserRole + 1 ).toString().compare( thisPath, Qt::CaseInsensitive ) == 0 )
458 {
459 parentIndex = sourceIndex;
460 parentItem = mOptTreeModel->itemFromIndex( parentIndex );
461 }
462 else
463 {
464 QStandardItem *newParentItem = new QStandardItem( thisPath );
465 newParentItem->setToolTip( thisPath );
466 newParentItem->setSelectable( false );
467 if ( sourceIndex.isValid() )
468 {
469 // insert in model before sourceIndex
470 if ( parentItem )
471 parentItem->insertRow( sourceIndex.row(), newParentItem );
472 else
473 mOptTreeModel->insertRow( sourceIndex.row(), newParentItem );
474 }
475 else
476 {
477 // append to end
478 if ( parentItem )
479 parentItem->appendRow( newParentItem );
480 else
481 mOptTreeModel->appendRow( newParentItem );
482 }
483 parentItem = newParentItem;
484 }
485 }
486
487 QStandardItem *item = new QStandardItem( icon, title );
488 item->setToolTip( tooltip );
489 if ( !key.isEmpty() )
490 {
491 item->setData( key );
492 }
493 if ( parentItem )
494 {
495 if ( sourceBeforeIndices.empty() )
496 parentItem->appendRow( item );
497 else
498 {
499 parentItem->insertRow( sourceBeforeIndices.at( 0 ).row(), item );
500 }
501 }
502 else
503 {
504 mOptTreeModel->insertRow( sourceIndexBefore.row(), item );
505 }
506 }
507
508 QgsScrollArea *scrollArea = new QgsScrollArea();
509 scrollArea->setWidgetResizable( true );
510 scrollArea->setFrameShape( QFrame::NoFrame );
511 scrollArea->setWidget( widget );
512 scrollArea->setObjectName( widget->objectName() );
513 mOptStackedWidget->insertWidget( page, scrollArea );
514 return;
515 }
516 }
517
518 // no matching pages, so just add the page
519 addPage( title, tooltip, icon, widget, path );
520}
521
522void QgsOptionsDialogBase::searchText( const QString &text )
523{
524 const int minimumTextLength = 3;
525
526 mSearchLineEdit->setMinimumWidth( text.isEmpty() ? 0 : 70 );
527
528 if ( !mOptStackedWidget )
529 return;
530
531 if ( mOptStackedWidget->isHidden() )
532 mOptStackedWidget->show();
533 if ( mOptButtonBox && mOptButtonBox->isHidden() )
534 mOptButtonBox->show();
535
536 // hide all pages if text has to be search, show them all otherwise
537 if ( mOptListWidget )
538 {
539 for ( int r = 0; r < mOptStackedWidget->count(); ++r )
540 {
541 if ( mOptListWidget->item( r )->text().contains( text, Qt::CaseInsensitive ) )
542 {
543 mOptListWidget->setRowHidden( r, false );
544 }
545 else
546 {
547 mOptListWidget->setRowHidden( r, text.length() >= minimumTextLength );
548 }
549 }
550
551 for ( const QPair<QgsOptionsDialogHighlightWidget *, int> &rsw : std::as_const( mRegisteredSearchWidgets ) )
552 {
553 if ( rsw.first->searchHighlight( text.length() >= minimumTextLength ? text : QString() ) )
554 {
555 mOptListWidget->setRowHidden( rsw.second, false );
556 }
557 }
558 }
559 else if ( mTreeProxyModel )
560 {
561 QMap<int, bool> hiddenPages;
562 for ( int r = 0; r < mOptStackedWidget->count(); ++r )
563 {
564 hiddenPages.insert( r, text.length() >= minimumTextLength );
565 }
566
567 std::function<void( const QModelIndex & )> traverseModel;
568 // traverse through the model, showing pages which match by page name
569 traverseModel = [&]( const QModelIndex &parent ) {
570 for ( int row = 0; row < mOptTreeModel->rowCount( parent ); ++row )
571 {
572 const QModelIndex currentIndex = mOptTreeModel->index( row, 0, parent );
573 if ( currentIndex.data().toString().contains( text, Qt::CaseInsensitive ) )
574 {
575 hiddenPages.insert( mTreeProxyModel->sourceIndexToPageNumber( currentIndex ), false );
576 }
577 traverseModel( currentIndex );
578 }
579 };
580 traverseModel( QModelIndex() );
581
582 for ( const QPair<QgsOptionsDialogHighlightWidget *, int> &rsw : std::as_const( mRegisteredSearchWidgets ) )
583 {
584 if ( rsw.first->searchHighlight( text.length() >= minimumTextLength ? text : QString() ) )
585 {
586 hiddenPages.insert( rsw.second, false );
587 }
588 }
589 for ( auto it = hiddenPages.constBegin(); it != hiddenPages.constEnd(); ++it )
590 {
591 mTreeProxyModel->setPageHidden( it.key(), it.value() );
592 }
593 }
594 if ( mOptTreeView && text.length() >= minimumTextLength )
595 {
596 // auto expand out any group with children matching the search term
597 mOptTreeView->expandAll();
598 }
599
600 // if current item is hidden, move to first available...
601 if ( mOptListWidget && mOptListWidget->isRowHidden( mOptStackedWidget->currentIndex() ) )
602 {
603 for ( int r = 0; r < mOptListWidget->count(); ++r )
604 {
605 if ( !mOptListWidget->isRowHidden( r ) )
606 {
607 mOptListWidget->setCurrentRow( r );
608 return;
609 }
610 }
611
612 // if no page can be shown, hide stack widget
613 mOptStackedWidget->hide();
614 if ( mOptButtonBox )
615 mOptButtonBox->hide();
616 }
617 else if ( mOptTreeView && mTreeProxyModel )
618 {
619 const QModelIndex currentSourceIndex = mTreeProxyModel->pageNumberToSourceIndex( mOptStackedWidget->currentIndex() );
620 if ( !mTreeProxyModel->filterAcceptsRow( currentSourceIndex.row(), currentSourceIndex.parent() ) )
621 {
622 std::function<QModelIndex( const QModelIndex & )> traverseModel;
623 traverseModel = [&]( const QModelIndex &parent ) -> QModelIndex {
624 for ( int row = 0; row < mTreeProxyModel->rowCount(); ++row )
625 {
626 const QModelIndex proxyIndex = mTreeProxyModel->index( row, 0, parent );
627 const QModelIndex sourceIndex = mTreeProxyModel->mapToSource( proxyIndex );
628 if ( mOptTreeModel->itemFromIndex( sourceIndex )->isSelectable() )
629 {
630 return sourceIndex;
631 }
632 else
633 {
634 QModelIndex res = traverseModel( proxyIndex );
635 if ( res.isValid() )
636 return res;
637 }
638 }
639 return QModelIndex();
640 };
641
642 const QModelIndex firstVisibleSourceIndex = traverseModel( QModelIndex() );
643
644 if ( firstVisibleSourceIndex.isValid() )
645 {
646 mOptTreeView->setCurrentIndex( mTreeProxyModel->mapFromSource( firstVisibleSourceIndex ) );
647 }
648 else
649 {
650 // if no page can be shown, hide stack widget
651 mOptStackedWidget->hide();
652 if ( mOptButtonBox )
653 mOptButtonBox->hide();
654 }
655 }
656 else
657 {
658 // make sure item stays current
659 mOptTreeView->setCurrentIndex( mTreeProxyModel->mapFromSource( currentSourceIndex ) );
660 }
661 }
662}
663
665{
667
668 for ( int i = 0; i < mOptStackedWidget->count(); i++ )
669 {
670 const QList<QWidget *> widgets = mOptStackedWidget->widget( i )->findChildren<QWidget *>();
671 for ( QWidget *widget : widgets )
672 {
673 // see if the widget also inherits QgsOptionsDialogHighlightWidget
675 if ( !shw )
676 {
677 // get custom highlight widget in user added pages
678 QHash<QWidget *, QgsOptionsDialogHighlightWidget *> customHighlightWidgets;
679 QgsOptionsPageWidget *opw = qobject_cast<QgsOptionsPageWidget *>( mOptStackedWidget->widget( i ) );
680 if ( opw )
681 {
682 customHighlightWidgets = opw->registeredHighlightWidgets();
683 }
684 // take custom if exists
685 if ( customHighlightWidgets.contains( widget ) )
686 {
687 shw = customHighlightWidgets.value( widget );
688 }
689 }
690 // try to construct one otherwise
691 if ( !shw || !shw->isValid() )
692 {
694 }
695 if ( shw && shw->isValid() )
696 {
697 QgsDebugMsgLevel( u"Registering: %1"_s.arg( widget->objectName() ), 4 );
698 mRegisteredSearchWidgets.append( qMakePair( shw, i ) );
699 }
700 else
701 {
702 delete shw;
703 }
704 }
705 }
706}
707
708QStandardItem *QgsOptionsDialogBase::createItem( const QString &name, const QString &tooltip, const QString &icon )
709{
710 QStandardItem *res = new QStandardItem( QgsApplication::getThemeIcon( icon ), name );
711 res->setToolTip( tooltip );
712 return res;
713}
714
716{
717 if ( mInit )
718 {
720 if ( mOptListWidget )
721 {
723 }
724 else if ( mOptTreeView )
725 {
726 optionsStackedWidget_CurrentChanged( mTreeProxyModel->sourceIndexToPageNumber( mTreeProxyModel->mapToSource( mOptTreeView->currentIndex() ) ) );
727 }
728 }
729 else
730 {
731 QTimer::singleShot( 0, this, &QgsOptionsDialogBase::warnAboutMissingObjects );
732 }
733
734 if ( mSearchLineEdit )
735 {
737 }
738
739 QDialog::showEvent( e );
740}
741
743{
744 if ( mInit )
745 QTimer::singleShot( 0, this, &QgsOptionsDialogBase::updateOptionsListVerticalTabs );
746
747 QDialog::paintEvent( e );
748}
749
751{
752 const QString itemText = mOptListWidget && mOptListWidget->currentItem() ? mOptListWidget->currentItem()->text()
753 : mOptTreeView && mOptTreeView->currentIndex().isValid() ? mOptTreeView->currentIndex().data( Qt::DisplayRole ).toString()
754 : QString();
755 if ( !itemText.isEmpty() )
756 {
757 setWindowTitle( u"%1 %2 %3"_s
758 .arg( mDialogTitle )
759 .arg( QChar( 0x2014 ) ) // em-dash unicode
760 .arg( itemText ) );
761 }
762 else
763 {
764 setWindowTitle( mDialogTitle );
765 }
766}
767
769{
770 if ( !mInit )
771 return;
772
773 QAbstractItemView *optView = mOptListWidget ? static_cast<QAbstractItemView *>( mOptListWidget ) : static_cast<QAbstractItemView *>( mOptTreeView );
774 if ( optView )
775 {
776 if ( optView->maximumWidth() != 16777215 )
777 optView->setMaximumWidth( 16777215 );
778 // auto-resize splitter for vert scrollbar without covering icons in icon-only mode
779 // TODO: mOptListWidget has fixed 32px wide icons for now, allow user-defined
780 // Note: called on splitter resize and dialog paint event, so only update when necessary
781 int iconWidth = optView->iconSize().width();
782 int snapToIconWidth = iconWidth + 32;
783
784 QList<int> splitSizes = mOptSplitter->sizes();
785 mIconOnly = ( splitSizes.at( 0 ) <= snapToIconWidth );
786
787 // iconBuffer (above) may need adjusted if you adjust iconWidth here
788 int newWidth = optView->verticalScrollBar()->isVisible() ? iconWidth + 22 : iconWidth + 9;
789 bool diffWidth = optView->minimumWidth() != newWidth;
790
791 if ( diffWidth )
792 optView->setMinimumWidth( newWidth );
793
794 if ( mIconOnly && ( diffWidth || optView->width() != newWidth ) )
795 {
796 splitSizes[1] = splitSizes.at( 1 ) - ( splitSizes.at( 0 ) - newWidth );
797 splitSizes[0] = newWidth;
798 mOptSplitter->setSizes( splitSizes );
799 }
800
801 if ( mOptListWidget )
802 {
803 if ( mOptListWidget->wordWrap() && mIconOnly )
804 mOptListWidget->setWordWrap( false );
805 if ( !mOptListWidget->wordWrap() && !mIconOnly )
806 mOptListWidget->setWordWrap( true );
807 }
808 }
809}
810
812{
813 if ( mOptListWidget )
814 {
815 mOptListWidget->blockSignals( true );
816 mOptListWidget->setCurrentRow( index );
817 mOptListWidget->blockSignals( false );
818 }
819 else if ( mOptTreeView )
820 {
821 mOptTreeView->blockSignals( true );
822 mOptTreeView->setCurrentIndex( mTreeProxyModel->mapFromSource( mTreeProxyModel->pageNumberToSourceIndex( index ) ) );
823 mOptTreeView->blockSignals( false );
824 }
825
827}
828
830{
831 // will need to take item first, if widgets are set for item in future
832 if ( mOptListWidget )
833 {
834 delete mOptListWidget->item( index );
835 }
836 else if ( mOptTreeModel )
837 {
838 mOptTreeModel->removeRow( index );
839 }
840
841 QList<QPair<QgsOptionsDialogHighlightWidget *, int>>::iterator it = mRegisteredSearchWidgets.begin();
842 while ( it != mRegisteredSearchWidgets.end() )
843 {
844 if ( ( *it ).second == index )
845 it = mRegisteredSearchWidgets.erase( it );
846 else
847 ++it;
848 }
849}
850
852{
853 QMessageBox::warning( nullptr, tr( "Missing Objects" ), tr( "Base options dialog could not be initialized.\n\n"
854 "Missing some of the .ui template objects:\n" )
855 + " mOptionsListWidget,\n mOptionsStackedWidget,\n mOptionsSplitter,\n mOptionsListFrame",
856 QMessageBox::Ok, QMessageBox::Ok );
857}
858
859
861QgsOptionsProxyModel::QgsOptionsProxyModel( QObject *parent )
862 : QSortFilterProxyModel( parent )
863{
864 setDynamicSortFilter( true );
865}
866
867void QgsOptionsProxyModel::setPageHidden( int page, bool hidden )
868{
869 mHiddenPages[page] = hidden;
870 invalidateFilter();
871}
872
873QModelIndex QgsOptionsProxyModel::pageNumberToSourceIndex( int page ) const
874{
875 QStandardItemModel *itemModel = qobject_cast<QStandardItemModel *>( sourceModel() );
876 if ( !itemModel )
877 return QModelIndex();
878
879 int pagesRemaining = page;
880 std::function<QModelIndex( const QModelIndex & )> traversePages;
881
882 // traverse through the model, counting all selectable items until we hit the desired page number
883 traversePages = [&]( const QModelIndex &parent ) -> QModelIndex {
884 for ( int row = 0; row < itemModel->rowCount( parent ); ++row )
885 {
886 const QModelIndex currentIndex = itemModel->index( row, 0, parent );
887 if ( itemModel->itemFromIndex( currentIndex )->isSelectable() )
888 {
889 if ( pagesRemaining == 0 )
890 return currentIndex;
891
892 else
893 pagesRemaining--;
894 }
895
896 const QModelIndex res = traversePages( currentIndex );
897 if ( res.isValid() )
898 return res;
899 }
900 return QModelIndex();
901 };
902
903 return traversePages( QModelIndex() );
904}
905
906int QgsOptionsProxyModel::sourceIndexToPageNumber( const QModelIndex &index ) const
907{
908 QStandardItemModel *itemModel = qobject_cast<QStandardItemModel *>( sourceModel() );
909 if ( !itemModel )
910 return 0;
911
912 int page = 0;
913
914 std::function<int( const QModelIndex & )> traverseModel;
915
916 // traverse through the model, counting all which correspond to pages till we hit the desired index
917 traverseModel = [&]( const QModelIndex &parent ) -> int {
918 for ( int row = 0; row < itemModel->rowCount( parent ); ++row )
919 {
920 const QModelIndex currentIndex = itemModel->index( row, 0, parent );
921 if ( currentIndex == index )
922 return page;
923
924 if ( itemModel->itemFromIndex( currentIndex )->isSelectable() )
925 page++;
926
927 const int res = traverseModel( currentIndex );
928 if ( res >= 0 )
929 return res;
930 }
931 return -1;
932 };
933
934 return traverseModel( QModelIndex() );
935}
936
937bool QgsOptionsProxyModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
938{
939 QStandardItemModel *itemModel = qobject_cast<QStandardItemModel *>( sourceModel() );
940 if ( !itemModel )
941 return true;
942
943 const QModelIndex sourceIndex = sourceModel()->index( source_row, 0, source_parent );
944
945 const int pageNumber = sourceIndexToPageNumber( sourceIndex );
946 if ( !mHiddenPages.value( pageNumber, false ) )
947 return true;
948
949 if ( sourceModel()->hasChildren( sourceIndex ) )
950 {
951 // this is a group -- show if any children are visible
952 for ( int row = 0; row < sourceModel()->rowCount( sourceIndex ); ++row )
953 {
954 if ( filterAcceptsRow( row, sourceIndex ) )
955 return true;
956 }
957 }
958 return false;
959}
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
void cleared()
Emitted when the widget is cleared.
QPointer< QgsSettings > mSettings
void resizeAlltabs(int index)
Resizes all tabs when the dialog is resized.
void paintEvent(QPaintEvent *e) override
void restoreLastPage()
Refocus the active tab from the last time the dialog was shown.
QList< QPair< QgsOptionsDialogHighlightWidget *, int > > mRegisteredSearchWidgets
void searchText(const QString &text)
searchText searches for a text in all the pages of the stacked widget and highlight the results
void registerTextSearchWidgets()
register widgets in the dialog to search for text in it it is automatically called if a line edit has...
virtual void optionsStackedWidget_CurrentChanged(int index)
Select relevant tab on current page change.
QgsOptionsDialogBase(const QString &settingsKey, QWidget *parent=nullptr, Qt::WindowFlags fl=Qt::WindowFlags(), QgsSettings *settings=nullptr)
Constructor.
QgsFilterLineEdit * mSearchLineEdit
void setSettings(QgsSettings *settings)
virtual void optionsStackedWidget_WidgetRemoved(int index)
Remove tab and unregister widgets on page remove.
QDialogButtonBox * mOptButtonBox
QgsOptionsProxyModel * mTreeProxyModel
void addPage(const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget, const QStringList &path=QStringList(), const QString &key=QString())
Adds a new page to the dialog pages.
QStandardItemModel * mOptTreeModel
QStandardItem * createItem(const QString &name, const QString &tooltip, const QString &icon)
Creates a new QStandardItem with the specified name, tooltip and icon.
virtual void updateOptionsListVerticalTabs()
Update tabs on the splitter move.
void restoreOptionsBaseUi(const QString &title=QString())
Restore the base ui.
QStackedWidget * mOptStackedWidget
void initOptionsBase(bool restoreUi=true, const QString &title=QString())
Set up the base ui connections for vertical tabs.
void showEvent(QShowEvent *e) override
void insertPage(const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget, const QString &before, const QStringList &path=QStringList(), const QString &key=QString())
Inserts a new page into the dialog pages.
void setCurrentPage(const QString &page)
Sets the dialog page (by object name) to show.
Container for a widget to be used to search text in the option dialog.
static QgsOptionsDialogHighlightWidget * createWidget(QWidget *widget)
create a highlight widget implementation for the proper widget type.
bool isValid()
Returns if it valid: if the widget type is handled and if the widget is not still available.
Base class for widgets for pages included in the options dialog.
QHash< QWidget *, QgsOptionsDialogHighlightWidget * > registeredHighlightWidgets()
Returns the registered highlight widgets used to search and highlight text in options dialogs.
A QScrollArea subclass with improved scrolling behavior.
Stores settings for use within QGIS.
Definition qgssettings.h:68
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63