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