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