QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
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 
17 #include "qgsoptionsdialogbase.h"
18 
19 #include <QDialog>
20 #include <QDialogButtonBox>
21 #include <QLayout>
22 #include <QListWidget>
23 #include <QListWidgetItem>
24 #include <QMessageBox>
25 #include <QPainter>
26 #include <QScrollBar>
27 #include <QSplitter>
28 #include <QStackedWidget>
29 #include <QTimer>
30 #include <QStandardItem>
31 #include <QTreeView>
32 #include <QHeaderView>
33 #include <functional>
34 
35 #include "qgsfilterlineedit.h"
36 #include "qgsmessagebaritem.h"
37 #include "qgslogger.h"
40 #include "qgsguiutils.h"
41 #include "qgsapplication.h"
42 
43 QgsOptionsDialogBase::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 
67 void 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 
206 void QgsOptionsDialogBase::restoreOptionsBaseUi( const QString &title )
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  mSettings->value( QStringLiteral( "/Windows/%1/splitState" ).arg( mOptsKey ) ).isNull() ? 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 
238  restoreLastPage();
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 
276 void 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 
312 void 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 
327 void QgsOptionsDialogBase::addPage( const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget, const QStringList &path )
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 
344  QModelIndex parent;
345  QStandardItem *parentItem = nullptr;
346  if ( !path.empty() )
347  {
348  QStringList parents = path;
349  while ( !parents.empty() )
350  {
351  const QString parentPath = parents.takeFirst();
352 
353  QModelIndex thisParent;
354  for ( int row = 0; row < mOptTreeModel->rowCount( parent ); ++row )
355  {
356  const QModelIndex index = mOptTreeModel->index( row, 0, parent );
357  if ( index.data().toString().compare( parentPath, Qt::CaseInsensitive ) == 0
358  || index.data( Qt::UserRole + 1 ).toString().compare( parentPath, Qt::CaseInsensitive ) == 0 )
359  {
360  thisParent = index;
361  break;
362  }
363  }
364 
365  // add new child if required
366  if ( !thisParent.isValid() )
367  {
368  QStandardItem *newParentItem = new QStandardItem( parentPath );
369  newParentItem->setToolTip( parentPath );
370  newParentItem->setSelectable( false );
371  if ( parentItem )
372  parentItem->appendRow( newParentItem );
373  else
374  mOptTreeModel->appendRow( newParentItem );
375  parentItem = newParentItem;
376  }
377  else
378  {
379  parentItem = mOptTreeModel->itemFromIndex( thisParent );
380  }
381  parent = mOptTreeModel->indexFromItem( parentItem );
382  }
383  }
384 
385  if ( parentItem )
386  {
387  parentItem->appendRow( item );
388  const QModelIndex newIndex = mOptTreeModel->indexFromItem( item );
389  newPage = mTreeProxyModel->sourceIndexToPageNumber( newIndex );
390  }
391  else
392  mOptTreeModel->appendRow( item );
393  }
394 
395  if ( newPage < 0 )
396  mOptStackedWidget->addWidget( widget );
397  else
398  mOptStackedWidget->insertWidget( newPage, widget );
399 }
400 
401 void QgsOptionsDialogBase::insertPage( const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget, const QString &before, const QStringList &path )
402 {
403  //find the page with a matching widget name
404  for ( int page = 0; page < mOptStackedWidget->count(); ++page )
405  {
406  QWidget *currentPage = mOptStackedWidget->widget( page );
407  if ( currentPage->objectName() == before )
408  {
409  //found the "before" page
410 
411  if ( mOptListWidget )
412  {
413  QListWidgetItem *item = new QListWidgetItem();
414  item->setIcon( icon );
415  item->setText( title );
416  item->setToolTip( tooltip );
417  mOptListWidget->insertItem( page, item );
418  }
419  else if ( mOptTreeModel )
420  {
421  QModelIndex sourceIndexBefore = mTreeProxyModel->pageNumberToSourceIndex( page );
422  QList< QModelIndex > sourceBeforeIndices;
423  while ( sourceIndexBefore.parent().isValid() )
424  {
425  sourceBeforeIndices.insert( 0, sourceIndexBefore );
426  sourceIndexBefore = sourceIndexBefore.parent();
427  }
428  sourceBeforeIndices.insert( 0, sourceIndexBefore );
429 
430  QStringList parentPaths = path;
431 
432  QModelIndex parentIndex;
433  QStandardItem *parentItem = nullptr;
434  while ( !parentPaths.empty() )
435  {
436  QString thisPath = parentPaths.takeFirst();
437  QModelIndex sourceIndex = !sourceBeforeIndices.isEmpty() ? sourceBeforeIndices.takeFirst() : QModelIndex();
438 
439  if ( sourceIndex.data().toString().compare( thisPath, Qt::CaseInsensitive ) == 0
440  || sourceIndex.data( Qt::UserRole + 1 ).toString().compare( thisPath, Qt::CaseInsensitive ) == 0 )
441  {
442  parentIndex = sourceIndex;
443  parentItem = mOptTreeModel->itemFromIndex( parentIndex );
444  }
445  else
446  {
447  QStandardItem *newParentItem = new QStandardItem( thisPath );
448  newParentItem->setToolTip( thisPath );
449  newParentItem->setSelectable( false );
450  if ( sourceIndex.isValid() )
451  {
452  // insert in model before sourceIndex
453  if ( parentItem )
454  parentItem->insertRow( sourceIndex.row(), newParentItem );
455  else
456  mOptTreeModel->insertRow( sourceIndex.row(), newParentItem );
457  }
458  else
459  {
460  // append to end
461  if ( parentItem )
462  parentItem->appendRow( newParentItem );
463  else
464  mOptTreeModel->appendRow( newParentItem );
465  }
466  parentItem = newParentItem;
467  }
468  }
469 
470  QStandardItem *item = new QStandardItem( icon, title );
471  item->setToolTip( tooltip );
472  if ( parentItem )
473  {
474  if ( sourceBeforeIndices.empty() )
475  parentItem->appendRow( item );
476  else
477  {
478  parentItem->insertRow( sourceBeforeIndices.at( 0 ).row(), item );
479  }
480  }
481  else
482  {
483  mOptTreeModel->insertRow( sourceIndexBefore.row(), item );
484  }
485  }
486 
487  mOptStackedWidget->insertWidget( page, widget );
488  return;
489  }
490  }
491 
492  // no matching pages, so just add the page
493  addPage( title, tooltip, icon, widget, path );
494 }
495 
496 void QgsOptionsDialogBase::searchText( const QString &text )
497 {
498  const int minimumTextLength = 3;
499 
500  mSearchLineEdit->setMinimumWidth( text.isEmpty() ? 0 : 70 );
501 
502  if ( !mOptStackedWidget )
503  return;
504 
505  if ( mOptStackedWidget->isHidden() )
506  mOptStackedWidget->show();
507  if ( mOptButtonBox && mOptButtonBox->isHidden() )
508  mOptButtonBox->show();
509 
510  // hide all pages if text has to be search, show them all otherwise
511  if ( mOptListWidget )
512  {
513  for ( int r = 0; r < mOptStackedWidget->count(); ++r )
514  {
515  if ( mOptListWidget->item( r )->text().contains( text, Qt::CaseInsensitive ) )
516  {
517  mOptListWidget->setRowHidden( r, false );
518  }
519  else
520  {
521  mOptListWidget->setRowHidden( r, text.length() >= minimumTextLength );
522  }
523  }
524 
525  for ( const QPair< QgsOptionsDialogHighlightWidget *, int > &rsw : std::as_const( mRegisteredSearchWidgets ) )
526  {
527  if ( rsw.first->searchHighlight( text.length() >= minimumTextLength ? text : QString() ) )
528  {
529  mOptListWidget->setRowHidden( rsw.second, false );
530  }
531  }
532  }
533  else if ( mTreeProxyModel )
534  {
535  QMap< int, bool > hiddenPages;
536  for ( int r = 0; r < mOptStackedWidget->count(); ++r )
537  {
538  hiddenPages.insert( r, text.length() >= minimumTextLength );
539  }
540 
541  std::function<void( const QModelIndex & )> traverseModel;
542  // traverse through the model, showing pages which match by page name
543  traverseModel = [&]( const QModelIndex & parent )
544  {
545  for ( int row = 0; row < mOptTreeModel->rowCount( parent ); ++row )
546  {
547  const QModelIndex currentIndex = mOptTreeModel->index( row, 0, parent );
548  if ( currentIndex.data().toString().contains( text, Qt::CaseInsensitive ) )
549  {
550  hiddenPages.insert( mTreeProxyModel->sourceIndexToPageNumber( currentIndex ), false );
551  }
552  traverseModel( currentIndex );
553  }
554  };
555  traverseModel( QModelIndex() );
556 
557  for ( const QPair< QgsOptionsDialogHighlightWidget *, int > &rsw : std::as_const( mRegisteredSearchWidgets ) )
558  {
559  if ( rsw.first->searchHighlight( text.length() >= minimumTextLength ? text : QString() ) )
560  {
561  hiddenPages.insert( rsw.second, false );
562  }
563  }
564  for ( auto it = hiddenPages.constBegin(); it != hiddenPages.constEnd(); ++it )
565  {
566  mTreeProxyModel->setPageHidden( it.key(), it.value() );
567  }
568  }
569  if ( mOptTreeView && text.length() >= minimumTextLength )
570  {
571  // auto expand out any group with children matching the search term
572  mOptTreeView->expandAll();
573  }
574 
575  // if current item is hidden, move to first available...
576  if ( mOptListWidget && mOptListWidget->isRowHidden( mOptStackedWidget->currentIndex() ) )
577  {
578  for ( int r = 0; r < mOptListWidget->count(); ++r )
579  {
580  if ( !mOptListWidget->isRowHidden( r ) )
581  {
582  mOptListWidget->setCurrentRow( r );
583  return;
584  }
585  }
586 
587  // if no page can be shown, hide stack widget
588  mOptStackedWidget->hide();
589  if ( mOptButtonBox )
590  mOptButtonBox->hide();
591  }
592  else if ( mOptTreeView )
593  {
594  const QModelIndex currentSourceIndex = mTreeProxyModel->pageNumberToSourceIndex( mOptStackedWidget->currentIndex() );
595  if ( !mTreeProxyModel->filterAcceptsRow( currentSourceIndex.row(), currentSourceIndex.parent() ) )
596  {
597  std::function<QModelIndex( const QModelIndex & )> traverseModel;
598  traverseModel = [&]( const QModelIndex & parent ) -> QModelIndex
599  {
600  for ( int row = 0; row < mTreeProxyModel->rowCount(); ++row )
601  {
602  const QModelIndex proxyIndex = mTreeProxyModel->index( row, 0, parent );
603  const QModelIndex sourceIndex = mTreeProxyModel->mapToSource( proxyIndex );
604  if ( mOptTreeModel->itemFromIndex( sourceIndex )->isSelectable() )
605  {
606  return sourceIndex;
607  }
608  else
609  {
610  QModelIndex res = traverseModel( proxyIndex );
611  if ( res.isValid() )
612  return res;
613  }
614  }
615  return QModelIndex();
616  };
617 
618  const QModelIndex firstVisibleSourceIndex = traverseModel( QModelIndex() );
619 
620  if ( firstVisibleSourceIndex.isValid() )
621  {
622  mOptTreeView->setCurrentIndex( mTreeProxyModel->mapFromSource( firstVisibleSourceIndex ) );
623  }
624  else
625  {
626  // if no page can be shown, hide stack widget
627  mOptStackedWidget->hide();
628  if ( mOptButtonBox )
629  mOptButtonBox->hide();
630  }
631  }
632  else
633  {
634  // make sure item stays current
635  mOptTreeView->setCurrentIndex( mTreeProxyModel->mapFromSource( currentSourceIndex ) );
636  }
637  }
638 }
639 
641 {
642  mRegisteredSearchWidgets.clear();
643 
644  for ( int i = 0; i < mOptStackedWidget->count(); i++ )
645  {
646 
647  const QList< QWidget * > widgets = mOptStackedWidget->widget( i )->findChildren<QWidget *>();
648  for ( QWidget *w : widgets )
649  {
650  // get custom highlight widget in user added pages
651  QHash<QWidget *, QgsOptionsDialogHighlightWidget *> customHighlightWidgets;
652  QgsOptionsPageWidget *opw = qobject_cast<QgsOptionsPageWidget *>( mOptStackedWidget->widget( i ) );
653  if ( opw )
654  {
655  customHighlightWidgets = opw->registeredHighlightWidgets();
656  }
657  QgsOptionsDialogHighlightWidget *shw = nullptr;
658  // take custom if exists
659  if ( customHighlightWidgets.contains( w ) )
660  {
661  shw = customHighlightWidgets.value( w );
662  }
663  // try to construct one otherwise
664  if ( !shw || !shw->isValid() )
665  {
667  }
668  if ( shw && shw->isValid() )
669  {
670  QgsDebugMsgLevel( QStringLiteral( "Registering: %1" ).arg( w->objectName() ), 4 );
671  mRegisteredSearchWidgets.append( qMakePair( shw, i ) );
672  }
673  else
674  {
675  delete shw;
676  }
677  }
678  }
679 }
680 
681 QStandardItem *QgsOptionsDialogBase::createItem( const QString &name, const QString &tooltip, const QString &icon )
682 {
683  QStandardItem *res = new QStandardItem( QgsApplication::getThemeIcon( icon ), name );
684  res->setToolTip( tooltip );
685  return res;
686 }
687 
688 void QgsOptionsDialogBase::showEvent( QShowEvent *e )
689 {
690  if ( mInit )
691  {
693  if ( mOptListWidget )
694  {
696  }
697  else if ( mOptTreeView )
698  {
699  optionsStackedWidget_CurrentChanged( mTreeProxyModel->sourceIndexToPageNumber( mTreeProxyModel->mapToSource( mOptTreeView->currentIndex() ) ) );
700  }
701  }
702  else
703  {
704  QTimer::singleShot( 0, this, &QgsOptionsDialogBase::warnAboutMissingObjects );
705  }
706 
707  if ( mSearchLineEdit )
708  {
710  }
711 
712  QDialog::showEvent( e );
713 }
714 
715 void QgsOptionsDialogBase::paintEvent( QPaintEvent *e )
716 {
717  if ( mInit )
718  QTimer::singleShot( 0, this, &QgsOptionsDialogBase::updateOptionsListVerticalTabs );
719 
720  QDialog::paintEvent( e );
721 }
722 
724 {
725  const QString itemText = mOptListWidget && mOptListWidget->currentItem() ? mOptListWidget->currentItem()->text()
726  : mOptTreeView && mOptTreeView->currentIndex().isValid() ? mOptTreeView->currentIndex().data( Qt::DisplayRole ).toString() : QString();
727  if ( !itemText.isEmpty() )
728  {
729  setWindowTitle( QStringLiteral( "%1 %2 %3" )
730  .arg( mDialogTitle )
731  .arg( QChar( 0x2014 ) ) // em-dash unicode
732  .arg( itemText ) );
733  }
734  else
735  {
736  setWindowTitle( mDialogTitle );
737  }
738 }
739 
741 {
742  if ( !mInit )
743  return;
744 
745  QAbstractItemView *optView = mOptListWidget ? static_cast< QAbstractItemView * >( mOptListWidget ) : static_cast< QAbstractItemView * >( mOptTreeView );
746  if ( optView )
747  {
748  if ( optView->maximumWidth() != 16777215 )
749  optView->setMaximumWidth( 16777215 );
750  // auto-resize splitter for vert scrollbar without covering icons in icon-only mode
751  // TODO: mOptListWidget has fixed 32px wide icons for now, allow user-defined
752  // Note: called on splitter resize and dialog paint event, so only update when necessary
753  int iconWidth = optView->iconSize().width();
754  int snapToIconWidth = iconWidth + 32;
755 
756  QList<int> splitSizes = mOptSplitter->sizes();
757  mIconOnly = ( splitSizes.at( 0 ) <= snapToIconWidth );
758 
759  // iconBuffer (above) may need adjusted if you adjust iconWidth here
760  int newWidth = optView->verticalScrollBar()->isVisible() ? iconWidth + 22 : iconWidth + 9;
761  bool diffWidth = optView->minimumWidth() != newWidth;
762 
763  if ( diffWidth )
764  optView->setMinimumWidth( newWidth );
765 
766  if ( mIconOnly && ( diffWidth || optView->width() != newWidth ) )
767  {
768  splitSizes[1] = splitSizes.at( 1 ) - ( splitSizes.at( 0 ) - newWidth );
769  splitSizes[0] = newWidth;
770  mOptSplitter->setSizes( splitSizes );
771  }
772 
773  if ( mOptListWidget )
774  {
775  if ( mOptListWidget->wordWrap() && mIconOnly )
776  mOptListWidget->setWordWrap( false );
777  if ( !mOptListWidget->wordWrap() && !mIconOnly )
778  mOptListWidget->setWordWrap( true );
779  }
780  }
781 }
782 
784 {
785  if ( mOptListWidget )
786  {
787  mOptListWidget->blockSignals( true );
788  mOptListWidget->setCurrentRow( index );
789  mOptListWidget->blockSignals( false );
790  }
791  else if ( mOptTreeView )
792  {
793  mOptTreeView->blockSignals( true );
794  mOptTreeView->setCurrentIndex( mTreeProxyModel->mapFromSource( mTreeProxyModel->pageNumberToSourceIndex( index ) ) );
795  mOptTreeView->blockSignals( false );
796  }
797 
799 }
800 
802 {
803  // will need to take item first, if widgets are set for item in future
804  if ( mOptListWidget )
805  {
806  delete mOptListWidget->item( index );
807  }
808  else if ( mOptTreeModel )
809  {
810  mOptTreeModel->removeRow( index );
811  }
812 
813  QList<QPair< QgsOptionsDialogHighlightWidget *, int > >::iterator it = mRegisteredSearchWidgets.begin();
814  while ( it != mRegisteredSearchWidgets.end() )
815  {
816  if ( ( *it ).second == index )
817  it = mRegisteredSearchWidgets.erase( it );
818  else
819  ++it;
820  }
821 }
822 
824 {
825  QMessageBox::warning( nullptr, tr( "Missing Objects" ),
826  tr( "Base options dialog could not be initialized.\n\n"
827  "Missing some of the .ui template objects:\n" )
828  + " mOptionsListWidget,\n mOptionsStackedWidget,\n mOptionsSplitter,\n mOptionsListFrame",
829  QMessageBox::Ok,
830  QMessageBox::Ok );
831 }
832 
833 
835 QgsOptionsProxyModel::QgsOptionsProxyModel( QObject *parent )
836  : QSortFilterProxyModel( parent )
837 {
838  setDynamicSortFilter( true );
839 }
840 
841 void QgsOptionsProxyModel::setPageHidden( int page, bool hidden )
842 {
843  mHiddenPages[ page ] = hidden;
844  invalidateFilter();
845 }
846 
847 QModelIndex QgsOptionsProxyModel::pageNumberToSourceIndex( int page ) const
848 {
849  QStandardItemModel *itemModel = qobject_cast< QStandardItemModel * >( sourceModel() );
850  if ( !itemModel )
851  return QModelIndex();
852 
853  int pagesRemaining = page;
854  std::function<QModelIndex( const QModelIndex & )> traversePages;
855 
856  // traverse through the model, counting all selectable items until we hit the desired page number
857  traversePages = [&]( const QModelIndex & parent ) -> QModelIndex
858  {
859  for ( int row = 0; row < itemModel->rowCount( parent ); ++row )
860  {
861  const QModelIndex currentIndex = itemModel->index( row, 0, parent );
862  if ( itemModel->itemFromIndex( currentIndex )->isSelectable() )
863  {
864  if ( pagesRemaining == 0 )
865  return currentIndex;
866 
867  else pagesRemaining--;
868  }
869 
870  const QModelIndex res = traversePages( currentIndex );
871  if ( res.isValid() )
872  return res;
873  }
874  return QModelIndex();
875  };
876 
877  return traversePages( QModelIndex() );
878 }
879 
880 int QgsOptionsProxyModel::sourceIndexToPageNumber( const QModelIndex &index ) const
881 {
882  QStandardItemModel *itemModel = qobject_cast< QStandardItemModel * >( sourceModel() );
883  if ( !itemModel )
884  return 0;
885 
886  int page = 0;
887 
888  std::function<int( const QModelIndex & )> traverseModel;
889 
890  // traverse through the model, counting all which correspond to pages till we hit the desired index
891  traverseModel = [&]( const QModelIndex & parent ) -> int
892  {
893  for ( int row = 0; row < itemModel->rowCount( parent ); ++row )
894  {
895  const QModelIndex currentIndex = itemModel->index( row, 0, parent );
896  if ( currentIndex == index )
897  return page;
898 
899  if ( itemModel->itemFromIndex( currentIndex )->isSelectable() )
900  page++;
901 
902  const int res = traverseModel( currentIndex );
903  if ( res >= 0 )
904  return res;
905  }
906  return -1;
907  };
908 
909  return traverseModel( QModelIndex() );
910 }
911 
912 bool QgsOptionsProxyModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
913 {
914  QStandardItemModel *itemModel = qobject_cast< QStandardItemModel * >( sourceModel() );
915  if ( !itemModel )
916  return true;
917 
918  const QModelIndex sourceIndex = sourceModel()->index( source_row, 0, source_parent );
919 
920  const int pageNumber = sourceIndexToPageNumber( sourceIndex );
921  if ( !mHiddenPages.value( pageNumber, false ) )
922  return true;
923 
924  if ( sourceModel()->hasChildren( sourceIndex ) )
925  {
926  // this is a group -- show if any children are visible
927  for ( int row = 0; row < sourceModel()->rowCount( sourceIndex ); ++row )
928  {
929  if ( filterAcceptsRow( row, sourceIndex ) )
930  return true;
931  }
932  }
933  return false;
934 }
qgsmessagebaritem.h
QgsOptionsDialogBase::insertPage
void insertPage(const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget, const QString &before, const QStringList &path=QStringList())
Inserts a new page into the dialog pages.
Definition: qgsoptionsdialogbase.cpp:401
QgsOptionsDialogBase::addPage
void addPage(const QString &title, const QString &tooltip, const QIcon &icon, QWidget *widget, const QStringList &path=QStringList())
Adds a new page to the dialog pages.
Definition: qgsoptionsdialogbase.cpp:327
QgsDebugMsgLevel
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QgsOptionsDialogHighlightWidget
Container for a widget to be used to search text in the option dialog If the widget type is handled,...
Definition: qgsoptionsdialoghighlightwidget.h:35
QgsOptionsDialogBase::paintEvent
void paintEvent(QPaintEvent *e) override
Definition: qgsoptionsdialogbase.cpp:715
qgsfilterlineedit.h
QgsOptionsDialogHighlightWidget::createWidget
static QgsOptionsDialogHighlightWidget * createWidget(QWidget *widget)
create a highlight widget implementation for the proper widget type.
Definition: qgsoptionsdialoghighlightwidget.cpp:40
QgsOptionsDialogBase::registerTextSearchWidgets
void registerTextSearchWidgets()
register widgets in the dialog to search for text in it it is automatically called if a line edit has...
Definition: qgsoptionsdialogbase.cpp:640
QgsSettings
This class is a composition of two QSettings instances:
Definition: qgssettings.h:61
QgsGuiUtils::saveGeometry
void saveGeometry(QWidget *widget, const QString &keyName)
Save the wigget geometry into settings.
Definition: qgsguiutils.cpp:226
QgsGuiUtils::restoreGeometry
bool restoreGeometry(QWidget *widget, const QString &keyName)
Restore the wigget geometry from settings.
Definition: qgsguiutils.cpp:233
QgsOptionsDialogBase::setCurrentPage
void setCurrentPage(const QString &page)
Sets the dialog page (by object name) to show.
Definition: qgsoptionsdialogbase.cpp:312
QgsOptionsDialogBase::setSettings
void setSettings(QgsSettings *settings)
Definition: qgsoptionsdialogbase.cpp:195
QgsOptionsDialogBase::updateWindowTitle
virtual void updateWindowTitle()
Definition: qgsoptionsdialogbase.cpp:723
QgsGuiUtils::iconSize
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
Definition: qgsguiutils.cpp:264
qgsapplication.h
QgsOptionsPageWidget::registeredHighlightWidgets
QHash< QWidget *, QgsOptionsDialogHighlightWidget * > registeredHighlightWidgets()
Returns the registered highlight widgets used to search and highlight text in options dialogs.
Definition: qgsoptionswidgetfactory.h:59
qgsoptionsdialogbase.h
QgsOptionsDialogBase::mDelSettings
bool mDelSettings
Definition: qgsoptionsdialogbase.h:235
QgsFilterLineEdit::setShowSearchIcon
void setShowSearchIcon(bool visible)
Define if a search icon shall be shown on the left of the image when no text is entered.
Definition: qgsfilterlineedit.cpp:49
QgsOptionsDialogBase::mSearchLineEdit
QgsFilterLineEdit * mSearchLineEdit
Definition: qgsoptionsdialogbase.h:229
QgsOptionsDialogBase::mOptTreeModel
QStandardItemModel * mOptTreeModel
Definition: qgsoptionsdialogbase.h:223
QgsOptionsDialogBase::mIconOnly
bool mIconOnly
Definition: qgsoptionsdialogbase.h:231
QgsOptionsDialogBase::mSettings
QPointer< QgsSettings > mSettings
Definition: qgsoptionsdialogbase.h:234
QgsOptionsDialogBase::warnAboutMissingObjects
void warnAboutMissingObjects()
Definition: qgsoptionsdialogbase.cpp:823
QgsOptionsDialogBase::restoreLastPage
void restoreLastPage()
Refocus the active tab from the last time the dialog was shown.
Definition: qgsoptionsdialogbase.cpp:250
QgsOptionsDialogBase::initOptionsBase
void initOptionsBase(bool restoreUi=true, const QString &title=QString())
Set up the base ui connections for vertical tabs.
Definition: qgsoptionsdialogbase.cpp:67
qgsoptionswidgetfactory.h
qgsoptionsdialoghighlightwidget.h
QgsOptionsDialogBase::mDialogTitle
QString mDialogTitle
Definition: qgsoptionsdialogbase.h:230
QgsOptionsDialogBase::mTreeProxyModel
QgsOptionsProxyModel * mTreeProxyModel
Definition: qgsoptionsdialogbase.h:224
QgsOptionsDialogBase::resizeAlltabs
void resizeAlltabs(int index)
Resizes all tabs when the dialog is resized.
Definition: qgsoptionsdialogbase.cpp:288
QgsOptionsDialogBase::QgsOptionsDialogBase
QgsOptionsDialogBase(const QString &settingsKey, QWidget *parent=nullptr, Qt::WindowFlags fl=Qt::WindowFlags(), QgsSettings *settings=nullptr)
Constructor.
Definition: qgsoptionsdialogbase.cpp:43
QgsOptionsDialogBase::mOptListWidget
QListWidget * mOptListWidget
Definition: qgsoptionsdialogbase.h:221
QgsOptionsDialogBase::optionsStackedWidget_CurrentChanged
virtual void optionsStackedWidget_CurrentChanged(int index)
Select relevant tab on current page change.
Definition: qgsoptionsdialogbase.cpp:783
QgsOptionsDialogBase::mOptsKey
QString mOptsKey
Definition: qgsoptionsdialogbase.h:219
QgsOptionsDialogBase::showEvent
void showEvent(QShowEvent *e) override
Definition: qgsoptionsdialogbase.cpp:688
QgsOptionsDialogBase::mOptSplitter
QSplitter * mOptSplitter
Definition: qgsoptionsdialogbase.h:227
QgsOptionsDialogBase::optionsStackedWidget_WidgetRemoved
virtual void optionsStackedWidget_WidgetRemoved(int index)
Remove tab and unregister widgets on page remove.
Definition: qgsoptionsdialogbase.cpp:801
QgsOptionsDialogBase::mInit
bool mInit
Definition: qgsoptionsdialogbase.h:220
QgsFilterLineEdit::cleared
void cleared()
Emitted when the widget is cleared.
QgsOptionsDialogBase::mOptStackedWidget
QStackedWidget * mOptStackedWidget
Definition: qgsoptionsdialogbase.h:226
QgsApplication::getThemeIcon
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
Definition: qgsapplication.cpp:693
QgsOptionsDialogBase::mOptButtonBox
QDialogButtonBox * mOptButtonBox
Definition: qgsoptionsdialogbase.h:228
QgsOptionsDialogBase::mRegisteredSearchWidgets
QList< QPair< QgsOptionsDialogHighlightWidget *, int > > mRegisteredSearchWidgets
Definition: qgsoptionsdialogbase.h:217
QgsOptionsDialogBase::mOptTreeView
QTreeView * mOptTreeView
Definition: qgsoptionsdialogbase.h:222
qgslogger.h
QgsOptionsDialogHighlightWidget::isValid
bool isValid()
Returns if it valid: if the widget type is handled and if the widget is not still available.
Definition: qgsoptionsdialoghighlightwidget.h:52
QgsOptionsDialogBase::~QgsOptionsDialogBase
~QgsOptionsDialogBase() override
Definition: qgsoptionsdialogbase.cpp:50
qgsguiutils.h
QgsOptionsDialogBase::createItem
QStandardItem * createItem(const QString &name, const QString &tooltip, const QString &icon)
Creates a new QStandardItem with the specified name, tooltip and icon.
Definition: qgsoptionsdialogbase.cpp:681
QgsGuiUtils::scaleIconSize
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...
Definition: qgsguiutils.cpp:259
QgsOptionsPageWidget
Base class for widgets for pages included in the options dialog.
Definition: qgsoptionswidgetfactory.h:30
QgsOptionsDialogBase::restoreOptionsBaseUi
void restoreOptionsBaseUi(const QString &title=QString())
Restore the base ui.
Definition: qgsoptionsdialogbase.cpp:206
QgsOptionsDialogBase::searchText
void searchText(const QString &text)
searchText searches for a text in all the pages of the stacked widget and highlight the results
Definition: qgsoptionsdialogbase.cpp:496
QgsOptionsDialogBase::updateOptionsListVerticalTabs
virtual void updateOptionsListVerticalTabs()
Update tabs on the splitter move.
Definition: qgsoptionsdialogbase.cpp:740