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