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