QGIS API Documentation  2.99.0-Master (90ae728)
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 <QCheckBox>
20 #include <QDialog>
21 #include <QDialogButtonBox>
22 #include <QEvent>
23 #include <QGroupBox>
24 #include <QLabel>
25 #include <QLayout>
26 #include <QListWidget>
27 #include <QListWidgetItem>
28 #include <QMessageBox>
29 #include <QPainter>
30 #include <QScrollBar>
31 #include <QSplitter>
32 #include <QStackedWidget>
33 #include <QTimer>
34 
35 
36 #include "qgsfilterlineedit.h"
37 
38 #include "qgslogger.h"
39 
40 QgsOptionsDialogBase::QgsOptionsDialogBase( const QString& settingsKey, QWidget* parent, Qt::WindowFlags fl, QSettings* settings )
41  : QDialog( parent, fl )
42  , mOptsKey( settingsKey )
43  , mInit( false )
44  , mOptListWidget( nullptr )
45  , mOptStackedWidget( nullptr )
46  , mOptSplitter( nullptr )
47  , mOptButtonBox( nullptr )
48  , mSearchLineEdit( nullptr )
49  , mDialogTitle( QLatin1String( "" ) )
50  , mIconOnly( false )
51  , mSettings( settings )
52  , mDelSettings( false )
53 {
54 }
55 
57 {
58  if ( mInit )
59  {
60  mSettings->setValue( QStringLiteral( "/Windows/%1/geometry" ).arg( mOptsKey ), saveGeometry() );
61  mSettings->setValue( QStringLiteral( "/Windows/%1/splitState" ).arg( mOptsKey ), mOptSplitter->saveState() );
62  mSettings->setValue( QStringLiteral( "/Windows/%1/tab" ).arg( mOptsKey ), mOptStackedWidget->currentIndex() );
63  }
64 
65  if ( mDelSettings ) // local settings obj to delete
66  {
67  delete mSettings;
68  }
69 
70  mSettings = nullptr; // null the pointer (in case of outside settings obj)
71 }
72 
73 void QgsOptionsDialogBase::initOptionsBase( bool restoreUi, const QString& title )
74 {
75  // use pointer to app QSettings if no custom QSettings specified
76  // custom QSettings object may be from Python plugin
77  mDelSettings = false;
78 
79  if ( !mSettings )
80  {
81  mSettings = new QSettings();
82  mDelSettings = true; // only delete obj created by class
83  }
84 
85  // save dialog title so it can be used to be concatenated
86  // with category title in icon-only mode
87  if ( title.isEmpty() )
88  mDialogTitle = windowTitle();
89  else
90  mDialogTitle = title;
91 
92  // don't add to dialog margins
93  // redefine now, or those in inherited .ui file will be added
94  if ( layout() )
95  {
96  layout()->setContentsMargins( 0, 0, 0, 0 ); // Qt default spacing
97  }
98 
99  // start with copy of qgsoptionsdialog_template.ui to ensure existence of these objects
100  mOptListWidget = findChild<QListWidget*>( QStringLiteral( "mOptionsListWidget" ) );
101  QFrame* optionsFrame = findChild<QFrame*>( QStringLiteral( "mOptionsFrame" ) );
102  mOptStackedWidget = findChild<QStackedWidget*>( QStringLiteral( "mOptionsStackedWidget" ) );
103  mOptSplitter = findChild<QSplitter*>( QStringLiteral( "mOptionsSplitter" ) );
104  mOptButtonBox = findChild<QDialogButtonBox*>( QStringLiteral( "buttonBox" ) );
105  QFrame* buttonBoxFrame = findChild<QFrame*>( QStringLiteral( "mButtonBoxFrame" ) );
106  mSearchLineEdit = findChild<QgsFilterLineEdit*>( "mSearchLineEdit" );
107 
108  if ( !mOptListWidget || !mOptStackedWidget || !mOptSplitter || !optionsFrame )
109  {
110  return;
111  }
112 
113  int size = mSettings->value( QStringLiteral( "/IconSize" ), 24 ).toInt();
114  // buffer size to match displayed icon size in toolbars, and expected geometry restore
115  // newWidth (above) may need adjusted if you adjust iconBuffer here
116  int iconBuffer = 4;
117  mOptListWidget->setIconSize( QSize( size + iconBuffer, size + iconBuffer ) );
118  mOptListWidget->setFrameStyle( QFrame::NoFrame );
119 
120  optionsFrame->layout()->setContentsMargins( 0, 3, 3, 3 );
121  QVBoxLayout* layout = static_cast<QVBoxLayout*>( optionsFrame->layout() );
122 
123  if ( buttonBoxFrame )
124  {
125  buttonBoxFrame->layout()->setContentsMargins( 0, 0, 0, 0 );
126  layout->insertWidget( layout->count() + 1, buttonBoxFrame );
127  }
128  else
129  {
130  layout->insertWidget( layout->count() + 1, mOptButtonBox );
131  }
132 
133  if ( mOptButtonBox )
134  {
135  // enforce only one connection per signal, in case added in Qt Designer
136  disconnect( mOptButtonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
137  connect( mOptButtonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
138  disconnect( mOptButtonBox, SIGNAL( rejected() ), this, SLOT( reject() ) );
139  connect( mOptButtonBox, SIGNAL( rejected() ), this, SLOT( reject() ) );
140  }
141  connect( mOptSplitter, SIGNAL( splitterMoved( int, int ) ), this, SLOT( updateOptionsListVerticalTabs() ) );
142  connect( mOptStackedWidget, SIGNAL( currentChanged( int ) ), this, SLOT( optionsStackedWidget_CurrentChanged( int ) ) );
143  connect( mOptStackedWidget, SIGNAL( widgetRemoved( int ) ), this, SLOT( optionsStackedWidget_WidgetRemoved( int ) ) );
144 
145  if ( mSearchLineEdit )
146  {
147  mSearchLineEdit->setShowSearchIcon( true );
148  connect( mSearchLineEdit, &QgsFilterLineEdit::textChanged, this, &QgsOptionsDialogBase::searchText );
149  }
150 
151  mInit = true;
152 
153  if ( restoreUi )
155 }
156 
157 void QgsOptionsDialogBase::setSettings( QSettings* settings )
158 {
159  if ( mDelSettings ) // local settings obj to delete
160  {
161  delete mSettings;
162  }
163 
164  mSettings = settings;
165  mDelSettings = false; // don't delete outside obj
166 }
167 
168 void QgsOptionsDialogBase::restoreOptionsBaseUi( const QString& title )
169 {
170  if ( !mInit )
171  {
172  return;
173  }
174 
175  if ( !title.isEmpty() )
176  {
177  mDialogTitle = title;
179  }
180 
181  // re-save original dialog title in case it was changed after dialog initialization
182  mDialogTitle = windowTitle();
183 
184  restoreGeometry( mSettings->value( QStringLiteral( "/Windows/%1/geometry" ).arg( mOptsKey ) ).toByteArray() );
185  // mOptListWidget width is fixed to take up less space in QtDesigner
186  // revert it now unless the splitter's state hasn't been saved yet
187  mOptListWidget->setMaximumWidth(
188  mSettings->value( QStringLiteral( "/Windows/%1/splitState" ).arg( mOptsKey ) ).isNull() ? 150 : 16777215 );
189  mOptSplitter->restoreState( mSettings->value( QStringLiteral( "/Windows/%1/splitState" ).arg( mOptsKey ) ).toByteArray() );
190  int curIndx = mSettings->value( QStringLiteral( "/Windows/%1/tab" ).arg( mOptsKey ), 0 ).toInt();
191 
192  // if the last used tab is out of range or not enabled display the first enabled one
193  if ( mOptStackedWidget->count() < ( curIndx + 1 )
194  || !mOptStackedWidget->widget( curIndx )->isEnabled() )
195  {
196  curIndx = 0;
197  for ( int i = 0; i < mOptStackedWidget->count(); i++ )
198  {
199  if ( mOptStackedWidget->widget( i )->isEnabled() )
200  {
201  curIndx = i;
202  break;
203  }
204  }
205  }
206 
207  if ( mOptStackedWidget->count() != 0 && mOptListWidget->count() != 0 )
208  {
209  mOptStackedWidget->setCurrentIndex( curIndx );
210  mOptListWidget->setCurrentRow( curIndx );
211  }
212 
213  // get rid of annoying outer focus rect on Mac
214  mOptListWidget->setAttribute( Qt::WA_MacShowFocusRect, false );
215 }
216 
217 void QgsOptionsDialogBase::searchText( const QString& text )
218 {
219  mSearchLineEdit->setMinimumWidth( text.isEmpty() ? 0 : 70 );
220 
221  if ( !mOptStackedWidget )
222  return;
223 
224  if ( mOptStackedWidget->isHidden() )
225  mOptStackedWidget->show();
226  if ( mOptButtonBox && mOptButtonBox->isHidden() )
227  mOptButtonBox->show();
228  // hide all page if text has to be search, show them all otherwise
229  for ( int r = 0; r < mOptListWidget->count(); ++r )
230  {
231  mOptListWidget->setRowHidden( r, !text.isEmpty() );
232  }
233 
234 for ( QPair< QgsSearchHighlightOptionWidget*, int > rsw : mRegisteredSearchWidgets )
235  {
236  rsw.first->reset();
237  if ( !text.isEmpty() && rsw.first->searchHighlight( text ) )
238  {
239  QgsDebugMsg( QString( "Found %1 in %2 (tab: %3)" )
240  .arg( text )
241  .arg( rsw.first->isValid() ? rsw.first->widget()->objectName() : "no widget" )
242  .arg( mOptListWidget->item( rsw.second )->text() ) );
243  mOptListWidget->setRowHidden( rsw.second, false );
244  }
245  }
246 
247  if ( mOptListWidget->isRowHidden( mOptStackedWidget->currentIndex() ) )
248  {
249  for ( int r = 0; r < mOptListWidget->count(); ++r )
250  {
251  if ( !mOptListWidget->isRowHidden( r ) )
252  {
253  mOptListWidget->setCurrentRow( r );
254  return;
255  }
256  }
257 
258  // if no page can be shown, hide stack widget
259  mOptStackedWidget->hide();
260  if ( mOptButtonBox )
261  mOptButtonBox->hide();
262  }
263 }
264 
266 {
267  mRegisteredSearchWidgets.clear();
268 
269  for ( int i = 0; i < mOptStackedWidget->count(); i++ )
270  {
271  Q_FOREACH ( QWidget* w, mOptStackedWidget->widget( i )->findChildren<QWidget*>() )
272  {
274  if ( shw->isValid() )
275  {
276  QgsDebugMsg( QString( "Registering: %1" ).arg( w->objectName() ) );
277  mRegisteredSearchWidgets.append( qMakePair( shw, i ) );
278  }
279  }
280  }
281 }
282 
283 void QgsOptionsDialogBase::showEvent( QShowEvent* e )
284 {
285  if ( mInit )
286  {
289  }
290  else
291  {
292  QTimer::singleShot( 0, this, SLOT( warnAboutMissingObjects() ) );
293  }
294 
295  if ( mSearchLineEdit )
296  {
298  }
299 
300  QDialog::showEvent( e );
301 }
302 
303 void QgsOptionsDialogBase::paintEvent( QPaintEvent* e )
304 {
305  if ( mInit )
306  QTimer::singleShot( 0, this, SLOT( updateOptionsListVerticalTabs() ) );
307 
308  QDialog::paintEvent( e );
309 }
310 
312 {
313  QListWidgetItem *curitem = mOptListWidget->currentItem();
314  if ( curitem )
315  {
316  setWindowTitle( QStringLiteral( "%1 | %2" ).arg( mDialogTitle, curitem->text() ) );
317  }
318  else
319  {
320  setWindowTitle( mDialogTitle );
321  }
322 }
323 
325 {
326  if ( !mInit )
327  return;
328 
329  if ( mOptListWidget->maximumWidth() != 16777215 )
330  mOptListWidget->setMaximumWidth( 16777215 );
331  // auto-resize splitter for vert scrollbar without covering icons in icon-only mode
332  // TODO: mOptListWidget has fixed 32px wide icons for now, allow user-defined
333  // Note: called on splitter resize and dialog paint event, so only update when necessary
334  int iconWidth = mOptListWidget->iconSize().width();
335  int snapToIconWidth = iconWidth + 32;
336 
337  QList<int> splitSizes = mOptSplitter->sizes();
338  mIconOnly = ( splitSizes.at( 0 ) <= snapToIconWidth );
339 
340  // iconBuffer (above) may need adjusted if you adjust iconWidth here
341  int newWidth = mOptListWidget->verticalScrollBar()->isVisible() ? iconWidth + 22 : iconWidth + 9;
342  bool diffWidth = mOptListWidget->minimumWidth() != newWidth;
343 
344  if ( diffWidth )
345  mOptListWidget->setMinimumWidth( newWidth );
346 
347  if ( mIconOnly && ( diffWidth || mOptListWidget->width() != newWidth ) )
348  {
349  splitSizes[1] = splitSizes.at( 1 ) - ( splitSizes.at( 0 ) - newWidth );
350  splitSizes[0] = newWidth;
351  mOptSplitter->setSizes( splitSizes );
352  }
353 
354  if ( mOptListWidget->wordWrap() && mIconOnly )
355  mOptListWidget->setWordWrap( false );
356  if ( !mOptListWidget->wordWrap() && !mIconOnly )
357  mOptListWidget->setWordWrap( true );
358 }
359 
361 {
362  mOptListWidget->blockSignals( true );
363  mOptListWidget->setCurrentRow( indx );
364  mOptListWidget->blockSignals( false );
365 
367 }
368 
370 {
371  // will need to take item first, if widgets are set for item in future
372  delete mOptListWidget->item( indx );
373 
374  QList<QPair< QgsSearchHighlightOptionWidget*, int > >::iterator it = mRegisteredSearchWidgets.begin();
375  while ( it != mRegisteredSearchWidgets.end() )
376  {
377  if (( *it ).second == indx )
378  it = mRegisteredSearchWidgets.erase( it );
379  else
380  ++it;
381  }
382 }
383 
385 {
386  QMessageBox::warning( nullptr, tr( "Missing objects" ),
387  tr( "Base options dialog could not be initialized.\n\n"
388  "Missing some of the .ui template objects:\n" )
389  + " mOptionsListWidget,\n mOptionsStackedWidget,\n mOptionsSplitter",
390  QMessageBox::Ok,
391  QMessageBox::Ok );
392 }
393 
394 
396  : mWidget( widget )
397  , mStyleSheet( QString() )
398  , mValid( true )
399  , mChangedStyle( false )
400  , mText( [=]() {return QString();} )
401 {
402  if ( qobject_cast<QLabel*>( widget ) )
403  {
404  mStyleSheet = "QLabel { background-color: yellow; color: blue;}";
405  mText = [=]() {return qobject_cast<QLabel*>( mWidget )->text();};
406  }
407  else if ( qobject_cast<QCheckBox*>( widget ) )
408  {
409  mStyleSheet = "QCheckBox { background-color: yellow; color: blue;}";
410  mText = [=]() {return qobject_cast<QCheckBox*>( mWidget )->text();};
411  }
412  else if ( qobject_cast<QAbstractButton*>( widget ) )
413  {
414  mStyleSheet = "QAbstractButton { background-color: yellow; color: blue;}";
415  mText = [=]() {return qobject_cast<QAbstractButton*>( mWidget )->text();};
416  }
417  else if ( qobject_cast<QGroupBox*>( widget ) )
418  {
419  mStyleSheet = "QGroupBox::title { background-color: yellow; color: blue;}";
420  mText = [=]() {return qobject_cast<QGroupBox*>( mWidget )->title();};
421  }
422  else
423  {
424  mValid = false;
425  }
426  if ( mValid )
427  {
428  mStyleSheet.prepend( "/*!search!*/" ).append( "/*!search!*/" );
429  QgsDebugMsg( mStyleSheet );
430  connect( mWidget, &QWidget::destroyed, this, &QgsSearchHighlightOptionWidget::widgetDestroyed );
431  }
432 }
433 
434 bool QgsSearchHighlightOptionWidget::searchHighlight( const QString& searchText )
435 {
436  bool found = false;
437  if ( !mWidget )
438  return found;
439 
440  if ( !searchText.isEmpty() )
441  {
442  QString origText = mText();
443  if ( origText.contains( searchText, Qt::CaseInsensitive ) )
444  {
445  found = true;
446  }
447  }
448 
449  if ( found && !mChangedStyle )
450  {
451  if ( !mWidget->isVisible() )
452  {
453  // show the widget to get initial stylesheet in case it's modified
454  mWidget->show();
455  }
456  mWidget->setStyleSheet( mWidget->styleSheet() + mStyleSheet );
457  mChangedStyle = true;
458  }
459 
460  return found;
461 }
462 
464 {
465  if ( mValid && mChangedStyle )
466  {
467  QString ss = mWidget->styleSheet();
468  ss.remove( mStyleSheet );
469  mWidget->setStyleSheet( ss );
470  mChangedStyle = false;
471  }
472 }
473 
474 void QgsSearchHighlightOptionWidget::widgetDestroyed()
475 {
476  mValid = false;
477 }
bool isValid()
Returns if it valid: if the widget type is handled and if the widget is not still available...
#define QgsDebugMsg(str)
Definition: qgslogger.h:36
void setSettings(QSettings *settings)
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 reset()
reset the style to the original state
QgsFilterLineEdit * mSearchLineEdit
QgsSearchHighlightOptionWidget(QWidget *widget=0)
Constructor.
QList< QPair< QgsSearchHighlightOptionWidget *, int > > mRegisteredSearchWidgets
void searchText(const QString &text)
searchText searches for a text in all the pages of the stacked widget and highlight the results ...
QWidget * widget()
return the widget
void optionsStackedWidget_WidgetRemoved(int indx)
bool searchHighlight(const QString &searchText)
search for a text pattern and highlight the widget if the text is found
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
void optionsStackedWidget_CurrentChanged(int indx)
Container for a widget to be used to search text in the option dialog If the widget type is handled...
QStackedWidget * mOptStackedWidget
QgsOptionsDialogBase(const QString &settingsKey, QWidget *parent=nullptr, Qt::WindowFlags fl=0, QSettings *settings=nullptr)
Constructor.
QPointer< QSettings > mSettings