QGIS API Documentation 4.1.0-Master (d6fb7a379fb)
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 <QDockWidget>
27#include <QFontDialog>
28#include <QImageWriter>
29#include <QMainWindow>
30#include <QMenu>
31#include <QMessageBox>
32#include <QRegularExpression>
33#include <QString>
34#include <QTabBar>
35
36using namespace Qt::StringLiterals;
37
38namespace QgsGuiUtils
39{
40
41 bool GUI_EXPORT openFilesRememberingFilter( QString const &filterName, QString const &filters, QStringList &selectedFiles, QString &enc, QString &title, bool cancelAll )
42 {
43 Q_UNUSED( enc )
44
45 QgsSettings settings;
46 QString lastUsedFilter = settings.value( "/UI/" + filterName, "" ).toString();
47 const QString lastUsedDir = settings.value( "/UI/" + filterName + "Dir", QDir::homePath() ).toString();
48
49 QgsDebugMsgLevel( "Opening file dialog with filters: " + filters, 3 );
50 if ( !cancelAll )
51 {
52 selectedFiles = QFileDialog::getOpenFileNames( nullptr, title, lastUsedDir, filters, &lastUsedFilter );
53 }
54 else //we have to use non-native dialog to add cancel all button
55 {
56 QgsEncodingFileDialog *openFileDialog = new QgsEncodingFileDialog( nullptr, title, lastUsedDir, filters, QString() );
57
58 // allow for selection of more than one file
59 openFileDialog->setFileMode( QFileDialog::ExistingFiles );
60
61 if ( !lastUsedFilter.isEmpty() )
62 {
63 openFileDialog->selectNameFilter( lastUsedFilter );
64 }
65 openFileDialog->addCancelAll();
66 if ( openFileDialog->exec() == QDialog::Accepted )
67 {
68 selectedFiles = openFileDialog->selectedFiles();
69 }
70 else
71 {
72 //cancel or cancel all?
73 if ( openFileDialog->cancelAll() )
74 {
75 return true;
76 }
77 }
78 }
79
80 if ( !selectedFiles.isEmpty() )
81 {
82 // Fix by Tim - getting the dirPath from the dialog
83 // directly truncates the last node in the dir path.
84 // This is a workaround for that
85 const QString firstFileName = selectedFiles.first();
86 const QFileInfo fi( firstFileName );
87 const QString path = fi.path();
88
89 QgsDebugMsgLevel( "Writing last used dir: " + path, 2 );
90
91 settings.setValue( "/UI/" + filterName, lastUsedFilter );
92 settings.setValue( "/UI/" + filterName + "Dir", path );
93 }
94 return false;
95 }
96
97 QPair<QString, QString> GUI_EXPORT getSaveAsImageName( QWidget *parent, const QString &message, const QString &defaultFilename )
98 {
99 // get a list of supported output image types
100 QMap<QString, QString> filterMap;
101 const auto supportedImageFormats { QImageWriter::supportedImageFormats() };
102 QStringList imageFormats;
103 // add PNG format first for certain file dialog to auto-fill from first listed extension
104 imageFormats << u"*.png *.PNG"_s;
105 for ( const QByteArray &format : supportedImageFormats )
106 {
107 // svg doesn't work so skip it
108 if ( format == "svg" )
109 {
110 continue;
111 }
112
113 filterMap.insert( createFileFilter_( format ), format );
114
115 if ( format != "png" )
116 {
117 imageFormats << u"*.%1 *.%2"_s.arg( format, QString( format ).toUpper() );
118 }
119 }
120 const QString formatByExtension = u"%1 (%2)"_s.arg( QObject::tr( "Format by Extension" ), imageFormats.join( ' '_L1 ) );
121
122#ifdef QGISDEBUG
123 QgsDebugMsgLevel( u"Available Filters Map: "_s, 2 );
124 for ( QMap<QString, QString>::iterator it = filterMap.begin(); it != filterMap.end(); ++it )
125 {
126 QgsDebugMsgLevel( it.key() + " : " + it.value(), 2 );
127 }
128#endif
129
130 QgsSettings settings; // where we keep last used filter in persistent state
131 const QString lastUsedDir = settings.value( u"UI/lastSaveAsImageDir"_s, QDir::homePath() ).toString();
132
133 QString selectedFilter = settings.value( u"UI/lastSaveAsImageFilter"_s, QString() ).toString();
134 if ( selectedFilter.isEmpty() )
135 {
136 selectedFilter = formatByExtension;
137 }
138
139 QString initialPath;
140 if ( defaultFilename.isNull() )
141 {
142 //no default filename provided, just use last directory
143 initialPath = lastUsedDir;
144 }
145 else
146 {
147 //a default filename was provided, so use it to build the initial path
148 initialPath = QDir( lastUsedDir ).filePath( defaultFilename );
149 }
150
151 QString outputFileName;
152 QString ext;
153#if defined( Q_OS_WIN ) || defined( Q_OS_MAC ) || defined( Q_OS_LINUX )
154 outputFileName = QFileDialog::getSaveFileName( parent, message, initialPath, formatByExtension + u";;"_s + qgsMapJoinKeys( filterMap, u";;"_s ), &selectedFilter );
155#else
156 //create a file dialog using the filter list generated above
157 auto fileDialog = std::make_unique<QFileDialog>( parent, message, initialPath, formatByExtension + u";;"_s + qgsMapJoinKeys( filterMap, u";;"_s ) );
158
159 // allow for selection of more than one file
160 fileDialog->setFileMode( QFileDialog::AnyFile );
161 fileDialog->setAcceptMode( QFileDialog::AcceptSave );
162 fileDialog->setOption( QFileDialog::DontConfirmOverwrite, false );
163
164 if ( !selectedFilter.isEmpty() ) // set the filter to the last one used
165 {
166 fileDialog->selectNameFilter( selectedFilter );
167 }
168
169 //prompt the user for a fileName
170 if ( fileDialog->exec() == QDialog::Accepted )
171 {
172 outputFileName = fileDialog->selectedFiles().first();
173 }
174#endif
175
176 if ( !outputFileName.isNull() )
177 {
178 if ( selectedFilter == formatByExtension )
179 {
180 settings.setValue( u"UI/lastSaveAsImageFilter"_s, QString() );
181 ext = QFileInfo( outputFileName ).suffix();
182
183 auto match = std::find_if( filterMap.begin(), filterMap.end(), [&ext]( const QString &filter ) { return filter == ext; } );
184 if ( match == filterMap.end() )
185 {
186 // Use "png" format when extension missing or not matching
187 ext = u"png"_s;
188 selectedFilter = createFileFilter_( ext );
189 outputFileName = QgsFileUtils::addExtensionFromFilter( outputFileName, selectedFilter );
190 }
191 }
192 else
193 {
194 ext = filterMap.value( selectedFilter, QString() );
195 if ( !ext.isEmpty() )
196 {
197 outputFileName = QgsFileUtils::addExtensionFromFilter( outputFileName, selectedFilter );
198 settings.setValue( u"UI/lastSaveAsImageFilter"_s, selectedFilter );
199 }
200 }
201 settings.setValue( u"UI/lastSaveAsImageDir"_s, QFileInfo( outputFileName ).absolutePath() );
202 }
203
204 return qMakePair( outputFileName, ext );
205 }
206
207 QString createFileFilter_( QString const &longName, QString const &glob )
208 {
209 return u"%1 (%2 %3)"_s.arg( longName, glob.toLower(), glob.toUpper() );
210 }
211
212 QString createFileFilter_( QString const &format )
213 {
214 const QString longName = format.toUpper() + " format";
215 const QString glob = "*." + format;
216 return createFileFilter_( longName, glob );
217 }
218
219 QFont getFont( bool &ok, const QFont &initial, const QString &title )
220 {
221 // parent is intentionally not set to 'this' as
222 // that would make it follow the style sheet font
223 // see also #12233 and #4937
224#if defined( Q_OS_MAC )
225 // Native dialog broken on macOS with Qt5
226 // probably only broken in Qt5.11.1 and .2
227 // (see https://successfulsoftware.net/2018/11/02/qt-is-broken-on-macos-right-now/ )
228 // possible upstream bug: https://bugreports.qt.io/browse/QTBUG-69878 (fixed in Qt 5.12 ?)
229 return QFontDialog::getFont( &ok, initial, nullptr, title, QFontDialog::DontUseNativeDialog );
230#else
231 return QFontDialog::getFont( &ok, initial, nullptr, title );
232#endif
233 }
234
235 void saveGeometry( QWidget *widget, const QString &keyName )
236 {
237 QgsSettings settings;
238 const QString key = createWidgetKey( widget, keyName );
239 settings.setValue( key, widget->saveGeometry() );
240 }
241
242 bool restoreGeometry( QWidget *widget, const QString &keyName )
243 {
244 const QgsSettings settings;
245 const QString key = createWidgetKey( widget, keyName );
246 return widget->restoreGeometry( settings.value( key ).toByteArray() );
247 }
248
249 QString createWidgetKey( QWidget *widget, const QString &keyName )
250 {
251 QString subKey;
252 if ( !keyName.isEmpty() )
253 {
254 subKey = keyName;
255 }
256 else if ( widget->objectName().isEmpty() )
257 {
258 subKey = QString( widget->metaObject()->className() );
259 }
260 else
261 {
262 subKey = widget->objectName();
263 }
264 QString key = u"Windows/%1/geometry"_s.arg( subKey );
265 return key;
266 }
267
268 int scaleIconSize( int standardSize )
269 {
270 return QgsApplication::scaleIconSize( standardSize );
271 }
272
273 QSize iconSize( bool dockableToolbar )
274 {
275 const QgsSettings s;
276 const int w = s.value( u"/qgis/toolbarIconSize"_s, 32 ).toInt();
277 QSize size( w, w );
278
279 if ( dockableToolbar )
280 {
281 size = panelIconSize( size );
282 }
283
284 return size;
285 }
286
287 QSize panelIconSize( QSize size )
288 {
289 int adjustedSize = 16;
290 if ( size.width() > 32 )
291 {
292 adjustedSize = size.width() - 16;
293 }
294 else if ( size.width() == 32 )
295 {
296 adjustedSize = 24;
297 }
298 return QSize( adjustedSize, adjustedSize );
299 }
300
301 QString displayValueWithMaximumDecimals( const Qgis::DataType dataType, const double value, bool displayTrailingZeroes )
302 {
303 const int precision { significantDigits( dataType ) };
304 QString result { QLocale().toString( value, 'f', precision ) };
305 if ( !displayTrailingZeroes )
306 {
307 const QRegularExpression zeroesRe { QStringLiteral( R"raw(\%1\d*?(0+$))raw" ).arg( QLocale().decimalPoint() ) };
308 if ( zeroesRe.match( result ).hasMatch() )
309 {
310 result.truncate( zeroesRe.match( result ).capturedStart( 1 ) );
311 if ( result.endsWith( QLocale().decimalPoint() ) )
312 {
313 result.chop( 1 );
314 }
315 }
316 }
317 return result;
318 }
319
320 int significantDigits( const Qgis::DataType rasterDataType )
321 {
322 switch ( rasterDataType )
323 {
334 {
335 return 0;
336 }
339 {
340 return std::numeric_limits<float>::digits10 + 1;
341 }
344 {
345 return std::numeric_limits<double>::digits10 + 1;
346 }
348 {
349 return std::numeric_limits<double>::digits10 + 1;
350 }
351 }
352 return 0;
353 }
354
356 {
357 const Qgis::WkbType flatType = QgsWkbTypes::flatType( wkbType );
358 return ( flatType == Qgis::WkbType::PolyhedralSurface || flatType == Qgis::WkbType::TIN || flatType == Qgis::WkbType::Triangle );
359 }
360
361 bool warnAboutNonStandardGeoPackageGeometryType( Qgis::WkbType wkbType, QWidget *parent, const QString &dialogTitle, bool showDialog, bool *isNonStandard )
362 {
363 const bool nonStandard = isNonStandardGeoPackageGeometryType( wkbType );
364
365 if ( isNonStandard )
366 {
367 *isNonStandard = nonStandard;
368 }
369
370 if ( !nonStandard )
371 {
372 return true;
373 }
374
375 if ( !showDialog )
376 {
377 return true;
378 }
379
380 return QMessageBox::question(
381 parent,
382 dialogTitle,
383 QObject::tr(
384 "PolyhedralSurface, TIN and Triangle are non-standard GeoPackage geometry types "
385 "and may not be recognized by other software.\n\n"
386 "Do you want to continue?"
387 ),
388 QMessageBox::Yes | QMessageBox::No,
389 QMessageBox::No
390 )
391 == QMessageBox::Yes;
392 }
393
394 void addDockWidget( QMainWindow *window, Qt::DockWidgetArea area, QDockWidget *dockwidget )
395 {
396 window->addDockWidget( area, dockwidget );
397 // Make the right and left docks consume all vertical space and top
398 // and bottom docks nest between them
399 window->setCorner( Qt::TopLeftCorner, Qt::LeftDockWidgetArea );
400 window->setCorner( Qt::BottomLeftCorner, Qt::LeftDockWidgetArea );
401 window->setCorner( Qt::TopRightCorner, Qt::RightDockWidgetArea );
402 window->setCorner( Qt::BottomRightCorner, Qt::RightDockWidgetArea );
403 // add to the Panel submenu, if it exists
404 if ( auto menu = window->findChild< QMenu * >( u"mPanelMenu"_s ) )
405 {
406 menu->addAction( dockwidget->toggleViewAction() );
407 }
408
409 dockwidget->show();
410 }
411
412 void addTabifiedDockWidget( QMainWindow *window, Qt::DockWidgetArea area, QDockWidget *dockWidget, const QStringList &tabifyWith, bool raiseTab )
413 {
414 QList<QDockWidget *> dockWidgetsInArea;
415 const QList<QDockWidget *> allDockWidgets = window->findChildren<QDockWidget *>();
416 for ( QDockWidget *w : allDockWidgets )
417 {
418 if ( w->isVisible() && window->dockWidgetArea( w ) == area )
419 {
420 dockWidgetsInArea << w;
421 }
422 }
423
424 addDockWidget( window, area, dockWidget ); // First add the dock widget, then attempt to tabify
425 if ( dockWidgetsInArea.empty() )
426 return;
427
428 // Get the base dock widget that we'll use to tabify our new dockWidget
429 QDockWidget *tabifyWithDockWidget = nullptr;
430 for ( const QString &targetName : tabifyWith )
431 {
432 auto it = std::find_if( dockWidgetsInArea.begin(), dockWidgetsInArea.end(), [&targetName]( QDockWidget *cw ) {
433 return cw->objectName() == targetName || cw->property( "dock_uuid" ).toString() == targetName;
434 } );
435
436 if ( it != dockWidgetsInArea.end() )
437 {
438 tabifyWithDockWidget = *it;
439 break;
440 }
441 }
442
443 if ( !tabifyWithDockWidget )
444 {
445 // fallback to the first available dock widget if no matches were found, or if no tabifyWith names were specified
446 tabifyWithDockWidget = dockWidgetsInArea.at( 0 );
447 }
448 if ( tabifyWithDockWidget == dockWidget )
449 return;
450
451 // find the currently active dock widget so that we can restore that if we're not raising the new tab
452 QTabBar *existingTabBar = nullptr;
453 int currentTabIndex = -1;
454 if ( !raiseTab && dockWidgetsInArea.length() > 1 )
455 {
456 // Chances are we've already got a tabBar, if so, get
457 // currentTabIndex to restore status after inserting our new tab
458 const QList<QTabBar *> tabBars = window->findChildren<QTabBar *>( QString(), Qt::FindDirectChildrenOnly );
459 bool tabBarFound = false;
460 for ( QTabBar *tabBar : tabBars )
461 {
462 for ( int i = 0; i < tabBar->count(); i++ )
463 {
464 if ( tabBar->tabText( i ) == tabifyWithDockWidget->windowTitle() )
465 {
466 existingTabBar = tabBar;
467 currentTabIndex = tabBar->currentIndex();
468 tabBarFound = true;
469 break;
470 }
471 }
472 if ( tabBarFound )
473 {
474 break;
475 }
476 }
477 }
478
479 // Now we can put the new dockWidget on top of tabifyWith
480 window->tabifyDockWidget( tabifyWithDockWidget, dockWidget );
481
482 // Should we restore dock widgets status?
483 if ( !raiseTab )
484 {
485 if ( existingTabBar )
486 {
487 existingTabBar->setCurrentIndex( currentTabIndex );
488 }
489 else
490 {
491 tabifyWithDockWidget->raise(); // Single base dock widget, we can just raise it
492 }
493 }
494 }
495
496} // namespace QgsGuiUtils
497
498//
499// QgsTemporaryCursorOverride
500//
501
503{
504 QApplication::setOverrideCursor( cursor );
505}
506
508{
509 if ( mHasOverride )
510 QApplication::restoreOverrideCursor();
511}
512
514{
515 if ( !mHasOverride )
516 return;
517
518 mHasOverride = false;
519 QApplication::restoreOverrideCursor();
520}
521
522
523//
524// QgsTemporaryCursorRestoreOverride
525//
526
528{
529 while ( QApplication::overrideCursor() )
530 {
531 mCursors.emplace_back( QCursor( *QApplication::overrideCursor() ) );
532 QApplication::restoreOverrideCursor();
533 }
534}
535
540
542{
543 for ( auto it = mCursors.rbegin(); it != mCursors.rend(); ++it )
544 {
545 QApplication::setOverrideCursor( *it );
546 }
547 mCursors.clear();
548}
549
550//
551// QWidgetUpdateBlocker
552//
553
555 : mWidget( widget )
556{
557 mWidget->setUpdatesEnabled( false );
558}
559
561{
562 if ( !mWidget )
563 return;
564
565 mWidget->setUpdatesEnabled( true );
566 mWidget = nullptr;
567}
568
QWidgetUpdateBlocker(QWidget *widget)
Constructor for QWidgetUpdateBlocker.
void release()
Releases the update block early (i.e.
DataType
Raster data types.
Definition qgis.h:393
@ CInt32
Complex Int32.
Definition qgis.h:404
@ Float32
Thirty two bit floating point (float).
Definition qgis.h:401
@ CFloat64
Complex Float64.
Definition qgis.h:406
@ Int16
Sixteen bit signed integer (qint16).
Definition qgis.h:398
@ ARGB32_Premultiplied
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
Definition qgis.h:408
@ Int8
Eight bit signed integer (qint8) (added in QGIS 3.30).
Definition qgis.h:396
@ UInt16
Sixteen bit unsigned integer (quint16).
Definition qgis.h:397
@ Byte
Eight bit unsigned integer (quint8).
Definition qgis.h:395
@ UnknownDataType
Unknown or unspecified type.
Definition qgis.h:394
@ ARGB32
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32.
Definition qgis.h:407
@ Int32
Thirty two bit signed integer (qint32).
Definition qgis.h:400
@ Float64
Sixty four bit floating point (double).
Definition qgis.h:402
@ CFloat32
Complex Float32.
Definition qgis.h:405
@ CInt16
Complex Int16.
Definition qgis.h:403
@ UInt32
Thirty two bit unsigned integer (quint32).
Definition qgis.h:399
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:294
@ TIN
TIN.
Definition qgis.h:310
@ Triangle
Triangle.
Definition qgis.h:299
@ PolyhedralSurface
PolyhedralSurface.
Definition qgis.h:309
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.
void addDockWidget(QMainWindow *window, Qt::DockWidgetArea area, QDockWidget *dockwidget)
Add a dock widget to a main window.
void addTabifiedDockWidget(QMainWindow *window, Qt::DockWidgetArea area, QDockWidget *dockWidget, const QStringList &tabifyWith, bool raiseTab)
Add a dock widget to the given area and tabify it (if other dock widgets exist in the same area).
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:7277
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63