QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
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 "qgis.h"
18#include "qgis_gui.h"
19#include "qgsapplication.h"
21#include "qgsfileutils.h"
22#include "qgslogger.h"
23#include "qgssettings.h"
24
25#include <QApplication>
26#include <QFontDialog>
27#include <QImageWriter>
28#include <QMessageBox>
29#include <QRegularExpression>
30#include <QString>
31
32using namespace Qt::StringLiterals;
33
34namespace QgsGuiUtils
35{
36
37 bool GUI_EXPORT openFilesRememberingFilter( QString const &filterName, QString const &filters, QStringList &selectedFiles, QString &enc, QString &title, bool cancelAll )
38 {
39 Q_UNUSED( enc )
40
41 QgsSettings settings;
42 QString lastUsedFilter = settings.value( "/UI/" + filterName, "" ).toString();
43 const QString lastUsedDir = settings.value( "/UI/" + filterName + "Dir", QDir::homePath() ).toString();
44
45 QgsDebugMsgLevel( "Opening file dialog with filters: " + filters, 3 );
46 if ( !cancelAll )
47 {
48 selectedFiles = QFileDialog::getOpenFileNames( nullptr, title, lastUsedDir, filters, &lastUsedFilter );
49 }
50 else //we have to use non-native dialog to add cancel all button
51 {
52 QgsEncodingFileDialog *openFileDialog = new QgsEncodingFileDialog( nullptr, title, lastUsedDir, filters, QString() );
53
54 // allow for selection of more than one file
55 openFileDialog->setFileMode( QFileDialog::ExistingFiles );
56
57 if ( !lastUsedFilter.isEmpty() )
58 {
59 openFileDialog->selectNameFilter( lastUsedFilter );
60 }
61 openFileDialog->addCancelAll();
62 if ( openFileDialog->exec() == QDialog::Accepted )
63 {
64 selectedFiles = openFileDialog->selectedFiles();
65 }
66 else
67 {
68 //cancel or cancel all?
69 if ( openFileDialog->cancelAll() )
70 {
71 return true;
72 }
73 }
74 }
75
76 if ( !selectedFiles.isEmpty() )
77 {
78 // Fix by Tim - getting the dirPath from the dialog
79 // directly truncates the last node in the dir path.
80 // This is a workaround for that
81 const QString firstFileName = selectedFiles.first();
82 const QFileInfo fi( firstFileName );
83 const QString path = fi.path();
84
85 QgsDebugMsgLevel( "Writing last used dir: " + path, 2 );
86
87 settings.setValue( "/UI/" + filterName, lastUsedFilter );
88 settings.setValue( "/UI/" + filterName + "Dir", path );
89 }
90 return false;
91 }
92
93 QPair<QString, QString> GUI_EXPORT getSaveAsImageName( QWidget *parent, const QString &message, const QString &defaultFilename )
94 {
95 // get a list of supported output image types
96 QMap<QString, QString> filterMap;
97 const auto supportedImageFormats { QImageWriter::supportedImageFormats() };
98 QStringList imageFormats;
99 // add PNG format first for certain file dialog to auto-fill from first listed extension
100 imageFormats << u"*.png *.PNG"_s;
101 for ( const QByteArray &format : supportedImageFormats )
102 {
103 // svg doesn't work so skip it
104 if ( format == "svg" )
105 {
106 continue;
107 }
108
109 filterMap.insert( createFileFilter_( format ), format );
110
111 if ( format != "png" )
112 {
113 imageFormats << u"*.%1 *.%2"_s.arg( format, QString( format ).toUpper() );
114 }
115 }
116 const QString formatByExtension = u"%1 (%2)"_s.arg( QObject::tr( "Format by Extension" ), imageFormats.join( ' '_L1 ) );
117
118#ifdef QGISDEBUG
119 QgsDebugMsgLevel( u"Available Filters Map: "_s, 2 );
120 for ( QMap<QString, QString>::iterator it = filterMap.begin(); it != filterMap.end(); ++it )
121 {
122 QgsDebugMsgLevel( it.key() + " : " + it.value(), 2 );
123 }
124#endif
125
126 QgsSettings settings; // where we keep last used filter in persistent state
127 const QString lastUsedDir = settings.value( u"UI/lastSaveAsImageDir"_s, QDir::homePath() ).toString();
128
129 QString selectedFilter = settings.value( u"UI/lastSaveAsImageFilter"_s, QString() ).toString();
130 if ( selectedFilter.isEmpty() )
131 {
132 selectedFilter = formatByExtension;
133 }
134
135 QString initialPath;
136 if ( defaultFilename.isNull() )
137 {
138 //no default filename provided, just use last directory
139 initialPath = lastUsedDir;
140 }
141 else
142 {
143 //a default filename was provided, so use it to build the initial path
144 initialPath = QDir( lastUsedDir ).filePath( defaultFilename );
145 }
146
147 QString outputFileName;
148 QString ext;
149#if defined( Q_OS_WIN ) || defined( Q_OS_MAC ) || defined( Q_OS_LINUX )
150 outputFileName = QFileDialog::getSaveFileName( parent, message, initialPath, formatByExtension + u";;"_s + qgsMapJoinKeys( filterMap, u";;"_s ), &selectedFilter );
151#else
152 //create a file dialog using the filter list generated above
153 auto fileDialog = std::make_unique<QFileDialog>( parent, message, initialPath, formatByExtension + u";;"_s + qgsMapJoinKeys( filterMap, u";;"_s ) );
154
155 // allow for selection of more than one file
156 fileDialog->setFileMode( QFileDialog::AnyFile );
157 fileDialog->setAcceptMode( QFileDialog::AcceptSave );
158 fileDialog->setOption( QFileDialog::DontConfirmOverwrite, false );
159
160 if ( !selectedFilter.isEmpty() ) // set the filter to the last one used
161 {
162 fileDialog->selectNameFilter( selectedFilter );
163 }
164
165 //prompt the user for a fileName
166 if ( fileDialog->exec() == QDialog::Accepted )
167 {
168 outputFileName = fileDialog->selectedFiles().first();
169 }
170#endif
171
172 if ( !outputFileName.isNull() )
173 {
174 if ( selectedFilter == formatByExtension )
175 {
176 settings.setValue( u"UI/lastSaveAsImageFilter"_s, QString() );
177 ext = QFileInfo( outputFileName ).suffix();
178
179 auto match = std::find_if( filterMap.begin(), filterMap.end(), [&ext]( const QString &filter ) { return filter == ext; } );
180 if ( match == filterMap.end() )
181 {
182 // Use "png" format when extension missing or not matching
183 ext = u"png"_s;
184 selectedFilter = createFileFilter_( ext );
185 outputFileName = QgsFileUtils::addExtensionFromFilter( outputFileName, selectedFilter );
186 }
187 }
188 else
189 {
190 ext = filterMap.value( selectedFilter, QString() );
191 if ( !ext.isEmpty() )
192 {
193 outputFileName = QgsFileUtils::addExtensionFromFilter( outputFileName, selectedFilter );
194 settings.setValue( u"UI/lastSaveAsImageFilter"_s, selectedFilter );
195 }
196 }
197 settings.setValue( u"UI/lastSaveAsImageDir"_s, QFileInfo( outputFileName ).absolutePath() );
198 }
199
200 return qMakePair( outputFileName, ext );
201 }
202
203 QString createFileFilter_( QString const &longName, QString const &glob )
204 {
205 return u"%1 (%2 %3)"_s.arg( longName, glob.toLower(), glob.toUpper() );
206 }
207
208 QString createFileFilter_( QString const &format )
209 {
210 const QString longName = format.toUpper() + " format";
211 const QString glob = "*." + format;
212 return createFileFilter_( longName, glob );
213 }
214
215 QFont getFont( bool &ok, const QFont &initial, const QString &title )
216 {
217 // parent is intentionally not set to 'this' as
218 // that would make it follow the style sheet font
219 // see also #12233 and #4937
220#if defined( Q_OS_MAC )
221 // Native dialog broken on macOS with Qt5
222 // probably only broken in Qt5.11.1 and .2
223 // (see https://successfulsoftware.net/2018/11/02/qt-is-broken-on-macos-right-now/ )
224 // possible upstream bug: https://bugreports.qt.io/browse/QTBUG-69878 (fixed in Qt 5.12 ?)
225 return QFontDialog::getFont( &ok, initial, nullptr, title, QFontDialog::DontUseNativeDialog );
226#else
227 return QFontDialog::getFont( &ok, initial, nullptr, title );
228#endif
229 }
230
231 void saveGeometry( QWidget *widget, const QString &keyName )
232 {
233 QgsSettings settings;
234 const QString key = createWidgetKey( widget, keyName );
235 settings.setValue( key, widget->saveGeometry() );
236 }
237
238 bool restoreGeometry( QWidget *widget, const QString &keyName )
239 {
240 const QgsSettings settings;
241 const QString key = createWidgetKey( widget, keyName );
242 return widget->restoreGeometry( settings.value( key ).toByteArray() );
243 }
244
245 QString createWidgetKey( QWidget *widget, const QString &keyName )
246 {
247 QString subKey;
248 if ( !keyName.isEmpty() )
249 {
250 subKey = keyName;
251 }
252 else if ( widget->objectName().isEmpty() )
253 {
254 subKey = QString( widget->metaObject()->className() );
255 }
256 else
257 {
258 subKey = widget->objectName();
259 }
260 QString key = u"Windows/%1/geometry"_s.arg( subKey );
261 return key;
262 }
263
264 int scaleIconSize( int standardSize )
265 {
266 return QgsApplication::scaleIconSize( standardSize );
267 }
268
269 QSize iconSize( bool dockableToolbar )
270 {
271 const QgsSettings s;
272 const int w = s.value( u"/qgis/toolbarIconSize"_s, 32 ).toInt();
273 QSize size( w, w );
274
275 if ( dockableToolbar )
276 {
277 size = panelIconSize( size );
278 }
279
280 return size;
281 }
282
283 QSize panelIconSize( QSize size )
284 {
285 int adjustedSize = 16;
286 if ( size.width() > 32 )
287 {
288 adjustedSize = size.width() - 16;
289 }
290 else if ( size.width() == 32 )
291 {
292 adjustedSize = 24;
293 }
294 return QSize( adjustedSize, adjustedSize );
295 }
296
297 QString displayValueWithMaximumDecimals( const Qgis::DataType dataType, const double value, bool displayTrailingZeroes )
298 {
299 const int precision { significantDigits( dataType ) };
300 QString result { QLocale().toString( value, 'f', precision ) };
301 if ( !displayTrailingZeroes )
302 {
303 const QRegularExpression zeroesRe { QStringLiteral( R"raw(\%1\d*?(0+$))raw" ).arg( QLocale().decimalPoint() ) };
304 if ( zeroesRe.match( result ).hasMatch() )
305 {
306 result.truncate( zeroesRe.match( result ).capturedStart( 1 ) );
307 if ( result.endsWith( QLocale().decimalPoint() ) )
308 {
309 result.chop( 1 );
310 }
311 }
312 }
313 return result;
314 }
315
316 int significantDigits( const Qgis::DataType rasterDataType )
317 {
318 switch ( rasterDataType )
319 {
330 {
331 return 0;
332 }
335 {
336 return std::numeric_limits<float>::digits10 + 1;
337 }
340 {
341 return std::numeric_limits<double>::digits10 + 1;
342 }
344 {
345 return std::numeric_limits<double>::digits10 + 1;
346 }
347 }
348 return 0;
349 }
350
352 {
353 const Qgis::WkbType flatType = QgsWkbTypes::flatType( wkbType );
354 return ( flatType == Qgis::WkbType::PolyhedralSurface || flatType == Qgis::WkbType::TIN || flatType == Qgis::WkbType::Triangle );
355 }
356
357 bool warnAboutNonStandardGeoPackageGeometryType( Qgis::WkbType wkbType, QWidget *parent, const QString &dialogTitle, bool showDialog, bool *isNonStandard )
358 {
359 const bool nonStandard = isNonStandardGeoPackageGeometryType( wkbType );
360
361 if ( isNonStandard )
362 {
363 *isNonStandard = nonStandard;
364 }
365
366 if ( !nonStandard )
367 {
368 return true;
369 }
370
371 if ( !showDialog )
372 {
373 return true;
374 }
375
376 return QMessageBox::question( parent, dialogTitle, QObject::tr( "PolyhedralSurface, TIN and Triangle are non-standard GeoPackage geometry types "
377 "and may not be recognized by other software.\n\n"
378 "Do you want to continue?" ),
379 QMessageBox::Yes | QMessageBox::No, QMessageBox::No )
380 == QMessageBox::Yes;
381 }
382} // namespace QgsGuiUtils
383
384//
385// QgsTemporaryCursorOverride
386//
387
389{
390 QApplication::setOverrideCursor( cursor );
391}
392
394{
395 if ( mHasOverride )
396 QApplication::restoreOverrideCursor();
397}
398
400{
401 if ( !mHasOverride )
402 return;
403
404 mHasOverride = false;
405 QApplication::restoreOverrideCursor();
406}
407
408
409//
410// QgsTemporaryCursorRestoreOverride
411//
412
414{
415 while ( QApplication::overrideCursor() )
416 {
417 mCursors.emplace_back( QCursor( *QApplication::overrideCursor() ) );
418 QApplication::restoreOverrideCursor();
419 }
420}
421
426
428{
429 for ( auto it = mCursors.rbegin(); it != mCursors.rend(); ++it )
430 {
431 QApplication::setOverrideCursor( *it );
432 }
433 mCursors.clear();
434}
435
436//
437// QWidgetUpdateBlocker
438//
439
441 : mWidget( widget )
442{
443 mWidget->setUpdatesEnabled( false );
444}
445
447{
448 if ( !mWidget )
449 return;
450
451 mWidget->setUpdatesEnabled( true );
452 mWidget = nullptr;
453}
454
QWidgetUpdateBlocker(QWidget *widget)
Constructor for QWidgetUpdateBlocker.
void release()
Releases the update block early (i.e.
DataType
Raster data types.
Definition qgis.h:379
@ CInt32
Complex Int32.
Definition qgis.h:390
@ Float32
Thirty two bit floating point (float).
Definition qgis.h:387
@ CFloat64
Complex Float64.
Definition qgis.h:392
@ Int16
Sixteen bit signed integer (qint16).
Definition qgis.h:384
@ ARGB32_Premultiplied
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
Definition qgis.h:394
@ Int8
Eight bit signed integer (qint8) (added in QGIS 3.30).
Definition qgis.h:382
@ UInt16
Sixteen bit unsigned integer (quint16).
Definition qgis.h:383
@ Byte
Eight bit unsigned integer (quint8).
Definition qgis.h:381
@ UnknownDataType
Unknown or unspecified type.
Definition qgis.h:380
@ ARGB32
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32.
Definition qgis.h:393
@ Int32
Thirty two bit signed integer (qint32).
Definition qgis.h:386
@ Float64
Sixty four bit floating point (double).
Definition qgis.h:388
@ CFloat32
Complex Float32.
Definition qgis.h:391
@ CInt16
Complex Int16.
Definition qgis.h:389
@ UInt32
Thirty two bit unsigned integer (quint32).
Definition qgis.h:385
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:280
@ TIN
TIN.
Definition qgis.h:296
@ Triangle
Triangle.
Definition qgis.h:285
@ PolyhedralSurface
PolyhedralSurface.
Definition qgis.h:295
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.
void addCancelAll()
Adds a 'Cancel All' button for the user to click.
bool cancelAll() const
Returns true if the user clicked 'Cancel All'.
static QString addExtensionFromFilter(const QString &fileName, const QString &filter)
Ensures that a fileName ends with an extension from the specified filter string.
Stores settings for use within QGIS.
Definition qgssettings.h:68
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.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
The QgsGuiUtils namespace contains constants and helper functions used throughout the QGIS GUI.
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.
bool isNonStandardGeoPackageGeometryType(Qgis::WkbType wkbType)
Returns true if the given wkbType is a non-standard GeoPackage geometry type (PolyhedralSurface,...
bool warnAboutNonStandardGeoPackageGeometryType(Qgis::WkbType wkbType, QWidget *parent, const QString &dialogTitle, bool showDialog, bool *isNonStandard)
Checks if the given wkbType is a non-standard GeoPackage geometry type (PolyhedralSurface,...
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,...
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...
QString qgsMapJoinKeys(const QMap< Key, Value > &map, const QString &separator)
Joins all the map keys into a single string with each element separated by the given separator.
Definition qgis.h:6957
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63