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