QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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)
211  // make the stacked widget size to the current page only
212  for ( int i = 0; i < mOptStackedWidget->count(); ++i )
213  {
214  // determine the vertical size policy
215  QSizePolicy::Policy policy = QSizePolicy::Ignored;
216  if ( i == index )
217  policy = QSizePolicy::MinimumExpanding;
218 
219  // update the size policy
220  mOptStackedWidget->widget( i )->setSizePolicy( policy, policy );
221  }
222  mOptStackedWidget->adjustSize();
223 }
224 
225 void QgsOptionsDialogBase::searchText( const QString &text )
226 {
227  const int minimumTextLength = 3;
228 
229  mSearchLineEdit->setMinimumWidth( text.isEmpty() ? 0 : 70 );
230 
231  if ( !mOptStackedWidget )
232  return;
233 
234  if ( mOptStackedWidget->isHidden() )
235  mOptStackedWidget->show();
236  if ( mOptButtonBox && mOptButtonBox->isHidden() )
237  mOptButtonBox->show();
238  // hide all page if text has to be search, show them all otherwise
239  for ( int r = 0; r < mOptListWidget->count(); ++r )
240  {
241  mOptListWidget->setRowHidden( r, text.length() >= minimumTextLength );
242  }
243 
244  for ( const QPair< QgsOptionsDialogHighlightWidget *, int > &rsw : qgis::as_const( mRegisteredSearchWidgets ) )
245  {
246  if ( rsw.first->searchHighlight( text.length() >= minimumTextLength ? text : QString() ) )
247  {
248  mOptListWidget->setRowHidden( rsw.second, false );
249  }
250  }
251 
252  if ( mOptListWidget->isRowHidden( mOptStackedWidget->currentIndex() ) )
253  {
254  for ( int r = 0; r < mOptListWidget->count(); ++r )
255  {
256  if ( !mOptListWidget->isRowHidden( r ) )
257  {
258  mOptListWidget->setCurrentRow( r );
259  return;
260  }
261  }
262 
263  // if no page can be shown, hide stack widget
264  mOptStackedWidget->hide();
265  if ( mOptButtonBox )
266  mOptButtonBox->hide();
267  }
268 }
269 
271 {
272  mRegisteredSearchWidgets.clear();
273 
274  for ( int i = 0; i < mOptStackedWidget->count(); i++ )
275  {
276  const auto constWidget = mOptStackedWidget->widget( i )->findChildren<QWidget *>();
277  for ( QWidget *w : constWidget )
278  {
279 
280  // get custom highlight widget in user added pages
281  QMap<QWidget *, QgsOptionsDialogHighlightWidget *> customHighlightWidgets;
282  QgsOptionsPageWidget *opw = qobject_cast<QgsOptionsPageWidget *>( mOptStackedWidget->widget( i ) );
283  if ( opw )
284  {
285  customHighlightWidgets = opw->registeredHighlightWidgets();
286  }
287  QgsOptionsDialogHighlightWidget *shw = nullptr;
288  // take custom if exists
289  if ( customHighlightWidgets.contains( w ) )
290  {
291  shw = customHighlightWidgets.value( w );
292  }
293  // try to construct one otherwise
294  if ( !shw || !shw->isValid() )
295  {
297  }
298  if ( shw && shw->isValid() )
299  {
300  QgsDebugMsgLevel( QStringLiteral( "Registering: %1" ).arg( w->objectName() ), 4 );
301  mRegisteredSearchWidgets.append( qMakePair( shw, i ) );
302  }
303  else
304  {
305  delete shw;
306  }
307  }
308  }
309 }
310 
311 void QgsOptionsDialogBase::showEvent( QShowEvent *e )
312 {
313  if ( mInit )
314  {
317  }
318  else
319  {
320  QTimer::singleShot( 0, this, SLOT( warnAboutMissingObjects() ) );
321  }
322 
323  if ( mSearchLineEdit )
324  {
326  }
327 
328  QDialog::showEvent( e );
329 }
330 
331 void QgsOptionsDialogBase::paintEvent( QPaintEvent *e )
332 {
333  if ( mInit )
334  QTimer::singleShot( 0, this, SLOT( updateOptionsListVerticalTabs() ) );
335 
336  QDialog::paintEvent( e );
337 }
338 
340 {
341  QListWidgetItem *curitem = mOptListWidget->currentItem();
342  if ( curitem )
343  {
344  setWindowTitle( QStringLiteral( "%1 | %2" ).arg( mDialogTitle, curitem->text() ) );
345  }
346  else
347  {
348  setWindowTitle( mDialogTitle );
349  }
350 }
351 
353 {
354  if ( !mInit )
355  return;
356 
357  if ( mOptListWidget->maximumWidth() != 16777215 )
358  mOptListWidget->setMaximumWidth( 16777215 );
359  // auto-resize splitter for vert scrollbar without covering icons in icon-only mode
360  // TODO: mOptListWidget has fixed 32px wide icons for now, allow user-defined
361  // Note: called on splitter resize and dialog paint event, so only update when necessary
362  int iconWidth = mOptListWidget->iconSize().width();
363  int snapToIconWidth = iconWidth + 32;
364 
365  QList<int> splitSizes = mOptSplitter->sizes();
366  mIconOnly = ( splitSizes.at( 0 ) <= snapToIconWidth );
367 
368  // iconBuffer (above) may need adjusted if you adjust iconWidth here
369  int newWidth = mOptListWidget->verticalScrollBar()->isVisible() ? iconWidth + 22 : iconWidth + 9;
370  bool diffWidth = mOptListWidget->minimumWidth() != newWidth;
371 
372  if ( diffWidth )
373  mOptListWidget->setMinimumWidth( newWidth );
374 
375  if ( mIconOnly && ( diffWidth || mOptListWidget->width() != newWidth ) )
376  {
377  splitSizes[1] = splitSizes.at( 1 ) - ( splitSizes.at( 0 ) - newWidth );
378  splitSizes[0] = newWidth;
379  mOptSplitter->setSizes( splitSizes );
380  }
381 
382  if ( mOptListWidget->wordWrap() && mIconOnly )
383  mOptListWidget->setWordWrap( false );
384  if ( !mOptListWidget->wordWrap() && !mIconOnly )
385  mOptListWidget->setWordWrap( true );
386 }
387 
389 {
390  mOptListWidget->blockSignals( true );
391  mOptListWidget->setCurrentRow( index );
392  mOptListWidget->blockSignals( false );
393 
395 }
396 
398 {
399  // will need to take item first, if widgets are set for item in future
400  delete mOptListWidget->item( index );
401 
402  QList<QPair< QgsOptionsDialogHighlightWidget *, int > >::iterator it = mRegisteredSearchWidgets.begin();
403  while ( it != mRegisteredSearchWidgets.end() )
404  {
405  if ( ( *it ).second == index )
406  it = mRegisteredSearchWidgets.erase( it );
407  else
408  ++it;
409  }
410 }
411 
413 {
414  QMessageBox::warning( nullptr, tr( "Missing Objects" ),
415  tr( "Base options dialog could not be initialized.\n\n"
416  "Missing some of the .ui template objects:\n" )
417  + " mOptionsListWidget,\n mOptionsStackedWidget,\n mOptionsSplitter,\n mOptionsListFrame",
418  QMessageBox::Ok,
419  QMessageBox::Ok );
420 }
421 
Base class for widgets for pages included in the options dialog.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
QList< QPair< QgsOptionsDialogHighlightWidget *, int > > mRegisteredSearchWidgets
QMap< QWidget *, QgsOptionsDialogHighlightWidget * > registeredHighlightWidgets()
Returns the registered highlight widgets used to search and highlight text in options dialogs...
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
virtual void optionsStackedWidget_CurrentChanged(int index)
Select relevant tab on current page change.