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