QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
qgsguiutils.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsguiutils.cpp - Constants used throughout the QGIS GUI.
3  --------------------------------------
4  Date : 11-Jan-2006
5  Copyright : (C) 2006 by Tom Elwertowski
6  Email : telwertowski at users dot sourceforge dot net
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 #include "qgsguiutils.h"
16 
17 #include "qgsapplication.h"
18 #include "qgssettings.h"
19 #include "qgsencodingfiledialog.h"
20 #include "qgslogger.h"
21 #include "qgis_gui.h"
22 #include "qgis.h"
23 
24 #include <QImageWriter>
25 #include <QFontDialog>
26 #include <QApplication>
27 #include <QRegularExpression>
28 
29 
30 namespace QgsGuiUtils
31 {
32 
33  bool GUI_EXPORT openFilesRememberingFilter( QString const &filterName,
34  QString const &filters, QStringList &selectedFiles, QString &enc, QString &title,
35  bool cancelAll )
36  {
37  Q_UNUSED( enc )
38 
39  QgsSettings settings;
40  QString lastUsedFilter = settings.value( "/UI/" + filterName, "" ).toString();
41  const QString lastUsedDir = settings.value( "/UI/" + filterName + "Dir", QDir::homePath() ).toString();
42 
43  QgsDebugMsg( "Opening file dialog with filters: " + filters );
44  if ( !cancelAll )
45  {
46  selectedFiles = QFileDialog::getOpenFileNames( nullptr, title, lastUsedDir, filters, &lastUsedFilter );
47  }
48  else //we have to use non-native dialog to add cancel all button
49  {
50  QgsEncodingFileDialog *openFileDialog = new QgsEncodingFileDialog( nullptr, title, lastUsedDir, filters, QString() );
51 
52  // allow for selection of more than one file
53  openFileDialog->setFileMode( QFileDialog::ExistingFiles );
54 
55  if ( !lastUsedFilter.isEmpty() )
56  {
57  openFileDialog->selectNameFilter( lastUsedFilter );
58  }
59  openFileDialog->addCancelAll();
60  if ( openFileDialog->exec() == QDialog::Accepted )
61  {
62  selectedFiles = openFileDialog->selectedFiles();
63  }
64  else
65  {
66  //cancel or cancel all?
67  if ( openFileDialog->cancelAll() )
68  {
69  return true;
70  }
71  }
72  }
73 
74  if ( !selectedFiles.isEmpty() )
75  {
76  // Fix by Tim - getting the dirPath from the dialog
77  // directly truncates the last node in the dir path.
78  // This is a workaround for that
79  const QString firstFileName = selectedFiles.first();
80  const QFileInfo fi( firstFileName );
81  const QString path = fi.path();
82 
83  QgsDebugMsg( "Writing last used dir: " + path );
84 
85  settings.setValue( "/UI/" + filterName, lastUsedFilter );
86  settings.setValue( "/UI/" + filterName + "Dir", path );
87  }
88  return false;
89  }
90 
91  QPair<QString, QString> GUI_EXPORT getSaveAsImageName( QWidget *parent, const QString &message, const QString &defaultFilename )
92  {
93  // get a list of supported output image types
94  QMap<QString, QString> filterMap;
95  const auto supportedImageFormats { QImageWriter::supportedImageFormats() };
96  for ( const QByteArray &format : supportedImageFormats )
97  {
98  //svg doesn't work so skip it
99  if ( format == "svg" )
100  continue;
101 
102  filterMap.insert( createFileFilter_( format ), format );
103  }
104 
105 #ifdef QGISDEBUG
106  QgsDebugMsgLevel( QStringLiteral( "Available Filters Map: " ), 2 );
107  for ( QMap<QString, QString>::iterator it = filterMap.begin(); it != filterMap.end(); ++it )
108  {
109  QgsDebugMsgLevel( it.key() + " : " + it.value(), 2 );
110  }
111 #endif
112 
113  QgsSettings settings; // where we keep last used filter in persistent state
114  const QString lastUsedDir = settings.value( QStringLiteral( "UI/lastSaveAsImageDir" ), QDir::homePath() ).toString();
115 
116  // Prefer "png" format unless the user previously chose a different format
117  const QString pngExtension = QStringLiteral( "png" );
118  const QString pngFilter = createFileFilter_( pngExtension );
119  QString selectedFilter = settings.value( QStringLiteral( "UI/lastSaveAsImageFilter" ), pngFilter ).toString();
120 
121  QString initialPath;
122  if ( defaultFilename.isNull() )
123  {
124  //no default filename provided, just use last directory
125  initialPath = lastUsedDir;
126  }
127  else
128  {
129  //a default filename was provided, so use it to build the initial path
130  initialPath = QDir( lastUsedDir ).filePath( defaultFilename );
131  }
132 
133  QString outputFileName;
134  QString ext;
135 #if defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(Q_OS_LINUX)
136  outputFileName = QFileDialog::getSaveFileName( parent, message, initialPath, QStringList( filterMap.keys() ).join( QLatin1String( ";;" ) ), &selectedFilter );
137 
138  if ( !outputFileName.isNull() )
139  {
140  ext = filterMap.value( selectedFilter, QString() );
141  if ( !ext.isNull() )
142  settings.setValue( QStringLiteral( "UI/lastSaveAsImageFilter" ), selectedFilter );
143  settings.setValue( QStringLiteral( "UI/lastSaveAsImageDir" ), QFileInfo( outputFileName ).absolutePath() );
144  }
145 #else
146 
147  //create a file dialog using the filter list generated above
148  std::unique_ptr<QFileDialog> fileDialog( new QFileDialog( parent, message, initialPath, QStringList( filterMap.keys() ).join( ";;" ) ) );
149 
150  // allow for selection of more than one file
151  fileDialog->setFileMode( QFileDialog::AnyFile );
152  fileDialog->setAcceptMode( QFileDialog::AcceptSave );
153  fileDialog->setOption( QFileDialog::DontConfirmOverwrite, false );
154 
155  if ( !selectedFilter.isEmpty() ) // set the filter to the last one used
156  {
157  fileDialog->selectNameFilter( selectedFilter );
158  }
159 
160  //prompt the user for a fileName
161  if ( fileDialog->exec() == QDialog::Accepted )
162  {
163  outputFileName = fileDialog->selectedFiles().first();
164  }
165 
166  selectedFilter = fileDialog->selectedNameFilter();
167  QgsDebugMsg( "Selected filter: " + selectedFilter );
168  ext = filterMap.value( selectedFilter, QString() );
169 
170  if ( !ext.isNull() )
171  settings.setValue( "/UI/lastSaveAsImageFilter", selectedFilter );
172 
173  settings.setValue( "/UI/lastSaveAsImageDir", fileDialog->directory().absolutePath() );
174 #endif
175 
176  // Add the file type suffix to the fileName if required
177  if ( !ext.isNull() && !outputFileName.endsWith( '.' + ext.toLower(), Qt::CaseInsensitive ) )
178  {
179  outputFileName += '.' + ext;
180  }
181 
182  return qMakePair( outputFileName, ext );
183  }
184 
185  QString createFileFilter_( QString const &longName, QString const &glob )
186  {
187  return QStringLiteral( "%1 (%2 %3)" ).arg( longName, glob.toLower(), glob.toUpper() );
188  }
189 
190  QString createFileFilter_( QString const &format )
191  {
192  const QString longName = format.toUpper() + " format";
193  const QString glob = "*." + format;
194  return createFileFilter_( longName, glob );
195  }
196 
197  QFont getFont( bool &ok, const QFont &initial, const QString &title )
198  {
199  // parent is intentionally not set to 'this' as
200  // that would make it follow the style sheet font
201  // see also #12233 and #4937
202 #if defined(Q_OS_MAC)
203  // Native dialog broken on macOS with Qt5
204  // probably only broken in Qt5.11.1 and .2
205  // (see https://successfulsoftware.net/2018/11/02/qt-is-broken-on-macos-right-now/ )
206  // possible upstream bug: https://bugreports.qt.io/browse/QTBUG-69878 (fixed in Qt 5.12 ?)
207  return QFontDialog::getFont( &ok, initial, nullptr, title, QFontDialog::DontUseNativeDialog );
208 #else
209  return QFontDialog::getFont( &ok, initial, nullptr, title );
210 #endif
211  }
212 
213  void saveGeometry( QWidget *widget, const QString &keyName )
214  {
215  QgsSettings settings;
216  const QString key = createWidgetKey( widget, keyName );
217  settings.setValue( key, widget->saveGeometry() );
218  }
219 
220  bool restoreGeometry( QWidget *widget, const QString &keyName )
221  {
222  const QgsSettings settings;
223  const QString key = createWidgetKey( widget, keyName );
224  return widget->restoreGeometry( settings.value( key ).toByteArray() );
225  }
226 
227  QString createWidgetKey( QWidget *widget, const QString &keyName )
228  {
229  QString subKey;
230  if ( !keyName.isEmpty() )
231  {
232  subKey = keyName;
233  }
234  else if ( widget->objectName().isEmpty() )
235  {
236  subKey = QString( widget->metaObject()->className() );
237  }
238  else
239  {
240  subKey = widget->objectName();
241  }
242  QString key = QStringLiteral( "Windows/%1/geometry" ).arg( subKey );
243  return key;
244  }
245 
246  int scaleIconSize( int standardSize )
247  {
248  return QgsApplication::scaleIconSize( standardSize );
249  }
250 
251  QSize iconSize( bool dockableToolbar )
252  {
253  const QgsSettings s;
254  const int w = s.value( QStringLiteral( "/qgis/iconSize" ), 32 ).toInt();
255  QSize size( w, w );
256 
257  if ( dockableToolbar )
258  {
259  size = panelIconSize( size );
260  }
261 
262  return size;
263  }
264 
265  QSize panelIconSize( QSize size )
266  {
267  int adjustedSize = 16;
268  if ( size.width() > 32 )
269  {
270  adjustedSize = size.width() - 16;
271  }
272  else if ( size.width() == 32 )
273  {
274  adjustedSize = 24;
275  }
276  return QSize( adjustedSize, adjustedSize );
277  }
278 
279  QString displayValueWithMaximumDecimals( const Qgis::DataType dataType, const double value, bool displayTrailingZeroes )
280  {
281  const int precision { significantDigits( dataType ) };
282  QString result { QLocale().toString( value, 'f', precision ) };
283  if ( ! displayTrailingZeroes )
284  {
285  const QRegularExpression zeroesRe { QStringLiteral( R"raw(\%1\d*?(0+$))raw" ).arg( QLocale().decimalPoint() ) };
286  if ( zeroesRe.match( result ).hasMatch() )
287  {
288  result.truncate( zeroesRe.match( result ).capturedStart( 1 ) );
289  if ( result.endsWith( QLocale().decimalPoint( ) ) )
290  {
291  result.chop( 1 );
292  }
293  }
294  }
295  return result;
296  }
297 
298  int significantDigits( const Qgis::DataType rasterDataType )
299  {
300  switch ( rasterDataType )
301  {
311  {
312  return 0;
313  }
316  {
317  return std::numeric_limits<float>::digits10 + 1;
318  }
321  {
322  return std::numeric_limits<double>::digits10 + 1;
323  }
325  {
326  return std::numeric_limits<double>::digits10 + 1;
327  }
328  }
329  return 0;
330  }
331 }
332 
333 //
334 // QgsTemporaryCursorOverride
335 //
336 
338 {
339  QApplication::setOverrideCursor( cursor );
340 }
341 
343 {
344  if ( mHasOverride )
345  QApplication::restoreOverrideCursor();
346 }
347 
349 {
350  if ( !mHasOverride )
351  return;
352 
353  mHasOverride = false;
354  QApplication::restoreOverrideCursor();
355 }
356 
357 
358 //
359 // QgsTemporaryCursorRestoreOverride
360 //
361 
363 {
364  while ( QApplication::overrideCursor() )
365  {
366  mCursors.emplace_back( QCursor( *QApplication::overrideCursor() ) );
367  QApplication::restoreOverrideCursor();
368  }
369 }
370 
372 {
373  restore();
374 }
375 
377 {
378  for ( auto it = mCursors.rbegin(); it != mCursors.rend(); ++it )
379  {
380  QApplication::setOverrideCursor( *it );
381  }
382  mCursors.clear();
383 }
DataType
Raster data types.
Definition: qgis.h:120
@ CInt32
Complex Int32.
@ Float32
Thirty two bit floating point (float)
@ CFloat64
Complex Float64.
@ Int16
Sixteen bit signed integer (qint16)
@ ARGB32_Premultiplied
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
@ UInt16
Sixteen bit unsigned integer (quint16)
@ Byte
Eight bit unsigned integer (quint8)
@ UnknownDataType
Unknown or unspecified type.
@ ARGB32
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32.
@ Int32
Thirty two bit signed integer (qint32)
@ Float64
Sixty four bit floating point (double)
@ CFloat32
Complex Float32.
@ CInt16
Complex Int16.
@ UInt32
Thirty two bit unsigned integer (quint32)
static int scaleIconSize(int standardSize, bool applyDevicePixelRatio=false)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...
A file dialog which lets the user select the preferred encoding type for a data provider.
bool cancelAll()
Returns true if the user clicked 'Cancel All'.
void addCancelAll()
Adds a 'Cancel All' button for the user to click.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
QgsTemporaryCursorOverride(const QCursor &cursor)
Constructor for QgsTemporaryCursorOverride.
void release()
Releases the cursor override early (i.e.
QgsTemporaryCursorRestoreOverride()
Constructor for QgsTemporaryCursorRestoreOverride.
void restore()
Restores the cursor override early (i.e.
The QgsGuiUtils namespace contains constants and helper functions used throughout the QGIS GUI.
Definition: qgsguiutils.cpp:31
bool restoreGeometry(QWidget *widget, const QString &keyName)
Restore the wigget geometry from settings.
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
QString createWidgetKey(QWidget *widget, const QString &keyName)
Creates a key for the given widget that can be used to store related data in settings.
QPair< QString, QString > GUI_EXPORT getSaveAsImageName(QWidget *parent, const QString &message, const QString &defaultFilename)
A helper function to get an image name from the user.
Definition: qgsguiutils.cpp:91
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...
int significantDigits(const Qgis::DataType rasterDataType)
Returns the maximum number of significant digits a for the given rasterDataType.
void saveGeometry(QWidget *widget, const QString &keyName)
Save the wigget geometry into settings.
QFont getFont(bool &ok, const QFont &initial, const QString &title)
Show font selection dialog.
bool GUI_EXPORT openFilesRememberingFilter(QString const &filterName, QString const &filters, QStringList &selectedFiles, QString &enc, QString &title, bool cancelAll)
Open files, preferring to have the default file selector be the last one used, if any; also,...
Definition: qgsguiutils.cpp:33
QString createFileFilter_(QString const &longName, QString const &glob)
Convenience function for readily creating file filters.
QSize panelIconSize(QSize size)
Returns dockable panel toolbar icon width based on the provided window toolbar width.
QString displayValueWithMaximumDecimals(const Qgis::DataType dataType, const double value, bool displayTrailingZeroes)
Returns a localized string representation of the value with the appropriate number of decimals suppor...
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
int precision