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