QGIS API Documentation  3.13.0-Master (740be229cb)
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 
31 #include "qgsfilterlineedit.h"
32 #include "qgsmessagebaritem.h"
33 #include "qgslogger.h"
36 
37 QgsOptionsDialogBase::QgsOptionsDialogBase( const QString &settingsKey, QWidget *parent, Qt::WindowFlags fl, QgsSettings *settings )
38  : QDialog( parent, fl )
39  , mOptsKey( settingsKey )
40  , mInit( false )
41  , mIconOnly( false )
42  , mSettings( settings )
43  , mDelSettings( false )
44 {
45 }
46 
48 {
49  if ( mInit )
50  {
51  mSettings->setValue( QStringLiteral( "/Windows/%1/geometry" ).arg( mOptsKey ), saveGeometry() );
52  mSettings->setValue( QStringLiteral( "/Windows/%1/splitState" ).arg( mOptsKey ), mOptSplitter->saveState() );
53  mSettings->setValue( QStringLiteral( "/Windows/%1/tab" ).arg( mOptsKey ), mOptStackedWidget->currentIndex() );
54  }
55 
56  if ( mDelSettings ) // local settings obj to delete
57  {
58  delete mSettings;
59  }
60 
61  mSettings = nullptr; // null the pointer (in case of outside settings obj)
62 }
63 
64 void QgsOptionsDialogBase::initOptionsBase( bool restoreUi, const QString &title )
65 {
66  // use pointer to app QgsSettings if no custom QgsSettings specified
67  // custom QgsSettings object may be from Python plugin
68  mDelSettings = false;
69 
70  if ( !mSettings )
71  {
72  mSettings = new QgsSettings();
73  mDelSettings = true; // only delete obj created by class
74  }
75 
76  // save dialog title so it can be used to be concatenated
77  // with category title in icon-only mode
78  if ( title.isEmpty() )
79  mDialogTitle = windowTitle();
80  else
81  mDialogTitle = title;
82 
83  // don't add to dialog margins
84  // redefine now, or those in inherited .ui file will be added
85  if ( layout() )
86  {
87  layout()->setContentsMargins( 0, 0, 0, 0 ); // Qt default spacing
88  }
89 
90  // start with copy of qgsoptionsdialog_template.ui to ensure existence of these objects
91  mOptListWidget = findChild<QListWidget *>( QStringLiteral( "mOptionsListWidget" ) );
92  QFrame *optionsFrame = findChild<QFrame *>( QStringLiteral( "mOptionsFrame" ) );
93  mOptStackedWidget = findChild<QStackedWidget *>( QStringLiteral( "mOptionsStackedWidget" ) );
94  mOptSplitter = findChild<QSplitter *>( QStringLiteral( "mOptionsSplitter" ) );
95  mOptButtonBox = findChild<QDialogButtonBox *>( QStringLiteral( "buttonBox" ) );
96  QFrame *buttonBoxFrame = findChild<QFrame *>( QStringLiteral( "mButtonBoxFrame" ) );
97  mSearchLineEdit = findChild<QgsFilterLineEdit *>( QStringLiteral( "mSearchLineEdit" ) );
98 
99  if ( !mOptListWidget || !mOptStackedWidget || !mOptSplitter || !optionsFrame )
100  {
101  return;
102  }
103 
104  int size = mSettings->value( QStringLiteral( "/IconSize" ), 24 ).toInt();
105  // buffer size to match displayed icon size in toolbars, and expected geometry restore
106  // newWidth (above) may need adjusted if you adjust iconBuffer here
107  int iconBuffer = 4;
108  mOptListWidget->setIconSize( QSize( size + iconBuffer, size + iconBuffer ) );
109  mOptListWidget->setFrameStyle( QFrame::NoFrame );
110 
111  optionsFrame->layout()->setContentsMargins( 0, 3, 3, 3 );
112  QVBoxLayout *layout = static_cast<QVBoxLayout *>( optionsFrame->layout() );
113 
114  if ( buttonBoxFrame )
115  {
116  buttonBoxFrame->layout()->setContentsMargins( 0, 0, 0, 0 );
117  layout->insertWidget( layout->count() + 1, buttonBoxFrame );
118  }
119  else if ( mOptButtonBox )
120  {
121  layout->insertWidget( layout->count() + 1, mOptButtonBox );
122  }
123 
124  if ( mOptButtonBox )
125  {
126  // enforce only one connection per signal, in case added in Qt Designer
127  disconnect( mOptButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
128  connect( mOptButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
129  disconnect( mOptButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
130  connect( mOptButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
131  }
132  connect( mOptSplitter, &QSplitter::splitterMoved, this, &QgsOptionsDialogBase::updateOptionsListVerticalTabs );
133  connect( mOptStackedWidget, &QStackedWidget::currentChanged, this, &QgsOptionsDialogBase::optionsStackedWidget_CurrentChanged );
134  connect( mOptStackedWidget, &QStackedWidget::widgetRemoved, this, &QgsOptionsDialogBase::optionsStackedWidget_WidgetRemoved );
135 
136  if ( mSearchLineEdit )
137  {
138  mSearchLineEdit->setShowSearchIcon( true );
139  connect( mSearchLineEdit, &QgsFilterLineEdit::textChanged, this, &QgsOptionsDialogBase::searchText );
140  }
141 
142  mInit = true;
143 
144  if ( restoreUi )
146 }
147 
149 {
150  if ( mDelSettings ) // local settings obj to delete
151  {
152  delete mSettings;
153  }
154 
155  mSettings = settings;
156  mDelSettings = false; // don't delete outside obj
157 }
158 
159 void QgsOptionsDialogBase::restoreOptionsBaseUi( const QString &title )
160 {
161  if ( !mInit )
162  {
163  return;
164  }
165 
166  if ( !title.isEmpty() )
167  {
168  mDialogTitle = title;
170  }
171 
172  // re-save original dialog title in case it was changed after dialog initialization
173  mDialogTitle = windowTitle();
174 
175  restoreGeometry( mSettings->value( QStringLiteral( "/Windows/%1/geometry" ).arg( mOptsKey ) ).toByteArray() );
176  // mOptListWidget width is fixed to take up less space in QtDesigner
177  // revert it now unless the splitter's state hasn't been saved yet
178  mOptListWidget->setMaximumWidth(
179  mSettings->value( QStringLiteral( "/Windows/%1/splitState" ).arg( mOptsKey ) ).isNull() ? 150 : 16777215 );
180  mOptSplitter->restoreState( mSettings->value( QStringLiteral( "/Windows/%1/splitState" ).arg( mOptsKey ) ).toByteArray() );
181  int curIndx = mSettings->value( QStringLiteral( "/Windows/%1/tab" ).arg( mOptsKey ), 0 ).toInt();
182 
183  // if the last used tab is out of range or not enabled display the first enabled one
184  if ( mOptStackedWidget->count() < curIndx + 1
185  || !mOptStackedWidget->widget( curIndx )->isEnabled() )
186  {
187  curIndx = 0;
188  for ( int i = 0; i < mOptStackedWidget->count(); i++ )
189  {
190  if ( mOptStackedWidget->widget( i )->isEnabled() )
191  {
192  curIndx = i;
193  break;
194  }
195  }
196  }
197 
198  if ( mOptStackedWidget->count() != 0 && mOptListWidget->count() != 0 )
199  {
200  mOptStackedWidget->setCurrentIndex( curIndx );
201  mOptListWidget->setCurrentRow( curIndx );
202  }
203 
204  // get rid of annoying outer focus rect on Mac
205  mOptListWidget->setAttribute( Qt::WA_MacShowFocusRect, false );
206 }
207 
209 {
210  // Adjust size (GH issue #31449 and #32615)
211  // make the stacked widget size to the current page only
212  for ( int i = 0; i < mOptStackedWidget->count(); ++i )
213  {
214  // Set the size policy
215  QSizePolicy::Policy policy = QSizePolicy::Ignored;
216  if ( i == index )
217  {
218  policy = QSizePolicy::MinimumExpanding;
219  }
220 
221  // update the size policy
222  mOptStackedWidget->widget( i )->setSizePolicy( policy, policy );
223 
224  if ( i == index )
225  {
226  mOptStackedWidget->layout()->update();
227  }
228  }
229  mOptStackedWidget->adjustSize();
230 }
231 
232 void QgsOptionsDialogBase::searchText( const QString &text )
233 {
234  const int minimumTextLength = 3;
235 
236  mSearchLineEdit->setMinimumWidth( text.isEmpty() ? 0 : 70 );
237 
238  if ( !mOptStackedWidget )
239  return;
240 
241  if ( mOptStackedWidget->isHidden() )
242  mOptStackedWidget->show();
243  if ( mOptButtonBox && mOptButtonBox->isHidden() )
244  mOptButtonBox->show();
245  // hide all page if text has to be search, show them all otherwise
246  for ( int r = 0; r < mOptListWidget->count(); ++r )
247  {
248  mOptListWidget->setRowHidden( r, text.length() >= minimumTextLength );
249  }
250 
251  for ( const QPair< QgsOptionsDialogHighlightWidget *, int > &rsw : qgis::as_const( mRegisteredSearchWidgets ) )
252  {
253  if ( rsw.first->searchHighlight( text.length() >= minimumTextLength ? text : QString() ) )
254  {
255  mOptListWidget->setRowHidden( rsw.second, false );
256  }
257  }
258 
259  if ( mOptListWidget->isRowHidden( mOptStackedWidget->currentIndex() ) )
260  {
261  for ( int r = 0; r < mOptListWidget->count(); ++r )
262  {
263  if ( !mOptListWidget->isRowHidden( r ) )
264  {
265  mOptListWidget->setCurrentRow( r );
266  return;
267  }
268  }
269 
270  // if no page can be shown, hide stack widget
271  mOptStackedWidget->hide();
272  if ( mOptButtonBox )
273  mOptButtonBox->hide();
274  }
275 }
276 
278 {
279  mRegisteredSearchWidgets.clear();
280 
281  for ( int i = 0; i < mOptStackedWidget->count(); i++ )
282  {
283  const auto constWidget = mOptStackedWidget->widget( i )->findChildren<QWidget *>();
284  for ( QWidget *w : constWidget )
285  {
286 
287  // get custom highlight widget in user added pages
288  QHash<QWidget *, QgsOptionsDialogHighlightWidget *> customHighlightWidgets;
289  QgsOptionsPageWidget *opw = qobject_cast<QgsOptionsPageWidget *>( mOptStackedWidget->widget( i ) );
290  if ( opw )
291  {
292  customHighlightWidgets = opw->registeredHighlightWidgets();
293  }
294  QgsOptionsDialogHighlightWidget *shw = nullptr;
295  // take custom if exists
296  if ( customHighlightWidgets.contains( w ) )
297  {
298  shw = customHighlightWidgets.value( w );
299  }
300  // try to construct one otherwise
301  if ( !shw || !shw->isValid() )
302  {
304  }
305  if ( shw && shw->isValid() )
306  {
307  QgsDebugMsgLevel( QStringLiteral( "Registering: %1" ).arg( w->objectName() ), 4 );
308  mRegisteredSearchWidgets.append( qMakePair( shw, i ) );
309  }
310  else
311  {
312  delete shw;
313  }
314  }
315  }
316 }
317 
318 void QgsOptionsDialogBase::showEvent( QShowEvent *e )
319 {
320  if ( mInit )
321  {
324  }
325  else
326  {
327  QTimer::singleShot( 0, this, SLOT( warnAboutMissingObjects() ) );
328  }
329 
330  if ( mSearchLineEdit )
331  {
333  }
334 
335  QDialog::showEvent( e );
336 }
337 
338 void QgsOptionsDialogBase::paintEvent( QPaintEvent *e )
339 {
340  if ( mInit )
341  QTimer::singleShot( 0, this, SLOT( updateOptionsListVerticalTabs() ) );
342 
343  QDialog::paintEvent( e );
344 }
345 
347 {
348  QListWidgetItem *curitem = mOptListWidget->currentItem();
349  if ( curitem )
350  {
351  setWindowTitle( QStringLiteral( "%1 | %2" ).arg( mDialogTitle, curitem->text() ) );
352  }
353  else
354  {
355  setWindowTitle( mDialogTitle );
356  }
357 }
358 
360 {
361  if ( !mInit )
362  return;
363 
364  if ( mOptListWidget->maximumWidth() != 16777215 )
365  mOptListWidget->setMaximumWidth( 16777215 );
366  // auto-resize splitter for vert scrollbar without covering icons in icon-only mode
367  // TODO: mOptListWidget has fixed 32px wide icons for now, allow user-defined
368  // Note: called on splitter resize and dialog paint event, so only update when necessary
369  int iconWidth = mOptListWidget->iconSize().width();
370  int snapToIconWidth = iconWidth + 32;
371 
372  QList<int> splitSizes = mOptSplitter->sizes();
373  mIconOnly = ( splitSizes.at( 0 ) <= snapToIconWidth );
374 
375  // iconBuffer (above) may need adjusted if you adjust iconWidth here
376  int newWidth = mOptListWidget->verticalScrollBar()->isVisible() ? iconWidth + 22 : iconWidth + 9;
377  bool diffWidth = mOptListWidget->minimumWidth() != newWidth;
378 
379  if ( diffWidth )
380  mOptListWidget->setMinimumWidth( newWidth );
381 
382  if ( mIconOnly && ( diffWidth || mOptListWidget->width() != newWidth ) )
383  {
384  splitSizes[1] = splitSizes.at( 1 ) - ( splitSizes.at( 0 ) - newWidth );
385  splitSizes[0] = newWidth;
386  mOptSplitter->setSizes( splitSizes );
387  }
388 
389  if ( mOptListWidget->wordWrap() && mIconOnly )
390  mOptListWidget->setWordWrap( false );
391  if ( !mOptListWidget->wordWrap() && !mIconOnly )
392  mOptListWidget->setWordWrap( true );
393 }
394 
396 {
397  mOptListWidget->blockSignals( true );
398  mOptListWidget->setCurrentRow( index );
399  mOptListWidget->blockSignals( false );
400 
402 }
403 
405 {
406  // will need to take item first, if widgets are set for item in future
407  delete mOptListWidget->item( index );
408 
409  QList<QPair< QgsOptionsDialogHighlightWidget *, int > >::iterator it = mRegisteredSearchWidgets.begin();
410  while ( it != mRegisteredSearchWidgets.end() )
411  {
412  if ( ( *it ).second == index )
413  it = mRegisteredSearchWidgets.erase( it );
414  else
415  ++it;
416  }
417 }
418 
420 {
421  QMessageBox::warning( nullptr, tr( "Missing Objects" ),
422  tr( "Base options dialog could not be initialized.\n\n"
423  "Missing some of the .ui template objects:\n" )
424  + " mOptionsListWidget,\n mOptionsStackedWidget,\n mOptionsSplitter,\n mOptionsListFrame",
425  QMessageBox::Ok,
426  QMessageBox::Ok );
427 }
428 
Base class for widgets for pages included in the options dialog.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:61
QList< QPair< QgsOptionsDialogHighlightWidget *, int > > mRegisteredSearchWidgets
void initOptionsBase(bool restoreUi=true, const QString &title=QString())
Set up the base ui connections for vertical tabs.
void paintEvent(QPaintEvent *e) override
void restoreOptionsBaseUi(const QString &title=QString())
Restore the base ui.
void saveGeometry(QWidget *widget, const QString &keyName)
Save the wigget geometry into settings.
QgsFilterLineEdit * mSearchLineEdit
bool restoreGeometry(QWidget *widget, const QString &keyName)
Restore the wigget geometry from settings.
void resizeAlltabs(int index)
Resizes all tabs when the dialog is resized.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
static QgsOptionsDialogHighlightWidget * createWidget(QWidget *widget)
create a highlight widget implementation for the proper widget type.
void searchText(const QString &text)
searchText searches for a text in all the pages of the stacked widget and highlight the results ...
virtual void optionsStackedWidget_WidgetRemoved(int index)
Remove tab and unregister widgets on page remove.
bool isValid()
Returns if it valid: if the widget type is handled and if the widget is not still available...
void showEvent(QShowEvent *e) override
void registerTextSearchWidgets()
register widgets in the dialog to search for text in it it is automatically called if a line edit has...
QDialogButtonBox * mOptButtonBox
Container for a widget to be used to search text in the option dialog If the widget type is handled...
void setSettings(QgsSettings *settings)
QPointer< QgsSettings > mSettings
QgsOptionsDialogBase(const QString &settingsKey, QWidget *parent=nullptr, Qt::WindowFlags fl=nullptr, QgsSettings *settings=nullptr)
Constructor.
virtual void updateOptionsListVerticalTabs()
Update tabs on the splitter move.
QStackedWidget * mOptStackedWidget
QHash< QWidget *, QgsOptionsDialogHighlightWidget * > registeredHighlightWidgets()
Returns the registered highlight widgets used to search and highlight text in options dialogs...
virtual void optionsStackedWidget_CurrentChanged(int index)
Select relevant tab on current page change.