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