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