QGIS API Documentation  2.99.0-Master (6c64c5a)
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  Q_FOREACH ( QWidget *w, mOptStackedWidget->widget( i )->findChildren<QWidget *>() )
260  {
261 
262  // get custom highlight widget in user added pages
263  QMap<QWidget *, QgsOptionsDialogHighlightWidget *> customHighlightWidgets = QMap<QWidget *, QgsOptionsDialogHighlightWidget *>();
264  QgsOptionsPageWidget *opw = qobject_cast<QgsOptionsPageWidget *>( mOptStackedWidget->widget( i ) );
265  if ( opw )
266  {
267  customHighlightWidgets = opw->registeredHighlightWidgets();
268  }
269  QgsOptionsDialogHighlightWidget *shw = nullptr;
270  // take custom if exists
271  if ( customHighlightWidgets.contains( w ) )
272  {
273  shw = customHighlightWidgets.value( w );
274  }
275  // try to construct one otherwise
276  if ( !shw || !shw->isValid() )
277  {
279  }
280  if ( shw && shw->isValid() )
281  {
282  QgsDebugMsgLevel( QString( "Registering: %1" ).arg( w->objectName() ), 4 );
283  mRegisteredSearchWidgets.append( qMakePair( shw, i ) );
284  }
285  else
286  {
287  delete shw;
288  }
289  }
290  }
291 }
292 
293 void QgsOptionsDialogBase::showEvent( QShowEvent *e )
294 {
295  if ( mInit )
296  {
299  }
300  else
301  {
302  QTimer::singleShot( 0, this, SLOT( warnAboutMissingObjects() ) );
303  }
304 
305  if ( mSearchLineEdit )
306  {
308  }
309 
310  QDialog::showEvent( e );
311 }
312 
313 void QgsOptionsDialogBase::paintEvent( QPaintEvent *e )
314 {
315  if ( mInit )
316  QTimer::singleShot( 0, this, SLOT( updateOptionsListVerticalTabs() ) );
317 
318  QDialog::paintEvent( e );
319 }
320 
322 {
323  QListWidgetItem *curitem = mOptListWidget->currentItem();
324  if ( curitem )
325  {
326  setWindowTitle( QStringLiteral( "%1 | %2" ).arg( mDialogTitle, curitem->text() ) );
327  }
328  else
329  {
330  setWindowTitle( mDialogTitle );
331  }
332 }
333 
335 {
336  if ( !mInit )
337  return;
338 
339  if ( mOptListWidget->maximumWidth() != 16777215 )
340  mOptListWidget->setMaximumWidth( 16777215 );
341  // auto-resize splitter for vert scrollbar without covering icons in icon-only mode
342  // TODO: mOptListWidget has fixed 32px wide icons for now, allow user-defined
343  // Note: called on splitter resize and dialog paint event, so only update when necessary
344  int iconWidth = mOptListWidget->iconSize().width();
345  int snapToIconWidth = iconWidth + 32;
346 
347  QList<int> splitSizes = mOptSplitter->sizes();
348  mIconOnly = ( splitSizes.at( 0 ) <= snapToIconWidth );
349 
350  // iconBuffer (above) may need adjusted if you adjust iconWidth here
351  int newWidth = mOptListWidget->verticalScrollBar()->isVisible() ? iconWidth + 22 : iconWidth + 9;
352  bool diffWidth = mOptListWidget->minimumWidth() != newWidth;
353 
354  if ( diffWidth )
355  mOptListWidget->setMinimumWidth( newWidth );
356 
357  if ( mIconOnly && ( diffWidth || mOptListWidget->width() != newWidth ) )
358  {
359  splitSizes[1] = splitSizes.at( 1 ) - ( splitSizes.at( 0 ) - newWidth );
360  splitSizes[0] = newWidth;
361  mOptSplitter->setSizes( splitSizes );
362  }
363 
364  if ( mOptListWidget->wordWrap() && mIconOnly )
365  mOptListWidget->setWordWrap( false );
366  if ( !mOptListWidget->wordWrap() && !mIconOnly )
367  mOptListWidget->setWordWrap( true );
368 }
369 
371 {
372  mOptListWidget->blockSignals( true );
373  mOptListWidget->setCurrentRow( index );
374  mOptListWidget->blockSignals( false );
375 
377 }
378 
380 {
381  // will need to take item first, if widgets are set for item in future
382  delete mOptListWidget->item( index );
383 
384  QList<QPair< QgsOptionsDialogHighlightWidget *, int > >::iterator it = mRegisteredSearchWidgets.begin();
385  while ( it != mRegisteredSearchWidgets.end() )
386  {
387  if ( ( *it ).second == index )
388  it = mRegisteredSearchWidgets.erase( it );
389  else
390  ++it;
391  }
392 }
393 
395 {
396  QMessageBox::warning( nullptr, tr( "Missing objects" ),
397  tr( "Base options dialog could not be initialized.\n\n"
398  "Missing some of the .ui template objects:\n" )
399  + " mOptionsListWidget,\n mOptionsStackedWidget,\n mOptionsSplitter,\n mOptionsListFrame",
400  QMessageBox::Ok,
401  QMessageBox::Ok );
402 }
403 
Base class for widgets for pages included in the options dialog.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:57
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.