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