QGIS API Documentation 4.1.0-Master (60fea48833c)
Loading...
Searching...
No Matches
qgsfontutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsfontutils.h
3 ---------------------
4 begin : June 5, 2013
5 copyright : (C) 2013 by Larry Shaffer
6 email : larrys at dakotacarto dot com
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
16#include "qgsfontutils.h"
17
18#include <memory>
19
20#include "qgis.h"
21#include "qgsapplication.h"
22#include "qgslogger.h"
23#include "qgssettings.h"
24
25#include <QApplication>
26#include <QFile>
27#include <QFont>
28#include <QFontDatabase>
29#include <QFontInfo>
30#include <QMimeData>
31#include <QString>
32#include <QStringList>
33
34using namespace Qt::StringLiterals;
35
36bool QgsFontUtils::fontMatchOnSystem( const QFont &f )
37{
38 const QFontInfo fi = QFontInfo( f );
39 return fi.exactMatch();
40}
41
42bool QgsFontUtils::fontFamilyOnSystem( const QString &family )
43{
44 const QFont tmpFont = QFont( family );
45 // compare just beginning of family string in case 'family [foundry]' differs
46 return tmpFont.family().startsWith( family, Qt::CaseInsensitive );
47}
48
49bool QgsFontUtils::fontFamilyHasStyle( const QString &family, const QString &style )
50{
51 const QFontDatabase fontDB;
52 if ( !fontFamilyOnSystem( family ) )
53 return false;
54
55 if ( fontDB.styles( family ).contains( style ) )
56 return true;
57
58 QString modified( style );
59#ifdef Q_OS_WIN
60 if ( style == "Roman" )
61 modified = "Normal";
62 else if ( style == "Oblique" )
63 modified = "Italic";
64 else if ( style == "Bold Oblique" )
65 modified = "Bold Italic";
66 if ( fontDB.styles( family ).contains( modified ) )
67 return true;
68#endif
69
70 // Deal with Qt6 Normal -> Regular style renaming
71 if ( modified == "Normal" )
72 modified = "Regular";
73 else if ( modified == QCoreApplication::translate( "QFontDatabase", "Normal" ) )
74 modified = QCoreApplication::translate( "QFontDatabase", "Regular" );
75 if ( fontDB.styles( family ).contains( modified ) )
76 return true;
77
78 return false;
79}
80
81QString QgsFontUtils::resolveFontStyleName( const QFont &font )
82{
83 auto styleNameIsMatch = [&font]( const QString &candidate ) -> bool {
84 // confirm that style name matches bold/italic flags
85 QFont testFont( font.family() );
86 testFont.setStyleName( candidate );
87 return testFont.italic() == font.italic() && testFont.weight() == font.weight();
88 };
89
90 // attempt 1
91 const QFontInfo fontInfo( font );
92 QString styleName = fontInfo.styleName();
93 if ( !styleName.isEmpty() )
94 {
95 if ( styleNameIsMatch( styleName ) )
96 return styleName;
97 }
98
99 // attempt 2
100 styleName = QFontDatabase().styleString( font );
101 if ( !styleName.isEmpty() )
102 {
103 if ( styleNameIsMatch( styleName ) )
104 return styleName;
105 }
106
107 // failed
108 return QString();
109}
110
111bool QgsFontUtils::fontFamilyMatchOnSystem( const QString &family, QString *chosen, bool *match )
112{
113 const QFontDatabase fontDB;
114 const QStringList fontFamilies = fontDB.families();
115 bool found = false;
116
117 QList<QString>::const_iterator it = fontFamilies.constBegin();
118 for ( ; it != fontFamilies.constEnd(); ++it )
119 {
120 // first compare just beginning of 'family [foundry]' string
121 if ( it->startsWith( family, Qt::CaseInsensitive ) )
122 {
123 found = true;
124 // keep looking if match info is requested
125 if ( match )
126 {
127 // full 'family [foundry]' strings have to match
128 *match = ( *it == family );
129 if ( *match )
130 break;
131 }
132 else
133 {
134 break;
135 }
136 }
137 }
138
139 if ( found )
140 {
141 if ( chosen )
142 {
143 // retrieve the family actually assigned by matching algorithm
144 const QFont f = QFont( family );
145 *chosen = f.family();
146 }
147 }
148 else
149 {
150 if ( chosen )
151 {
152 *chosen = QString();
153 }
154
155 if ( match )
156 {
157 *match = false;
158 }
159 }
160
161 return found;
162}
163
164bool QgsFontUtils::updateFontViaStyle( QFont &f, const QString &fontstyle, bool fallback )
165{
166 if ( fontstyle.isEmpty() )
167 {
168 return false;
169 }
170
171 QFontDatabase fontDB;
172 QString actualFontStyle = fontstyle;
173
174 if ( !fallback )
175 {
176 // does the font even have the requested style?
177 bool hasstyle = fontFamilyHasStyle( f.family(), actualFontStyle );
178 if ( !hasstyle )
179 {
180 actualFontStyle = untranslateNamedStyle( fontstyle );
181 hasstyle = fontFamilyHasStyle( f.family(), actualFontStyle );
182 }
183
184 if ( !hasstyle )
185 {
186 return false;
187 }
188 }
189
190 // is the font's style already the same as requested?
191 if ( actualFontStyle == fontDB.styleString( f ) )
192 {
193 if ( f.styleName().isEmpty() )
194 {
195 f.setStyleName( actualFontStyle );
196 return true;
197 }
198 return false;
199 }
200
201 const QFont appfont = QApplication::font();
202 const int defaultSize = appfont.pointSize(); // QFontDatabase::font() needs an integer for size
203
204 QFont styledfont;
205 bool foundmatch = false;
206
207 // if fontDB.font() fails, it returns the default app font; but, that may be the target style
208 styledfont = fontDB.font( f.family(), actualFontStyle, defaultSize );
209 if ( appfont != styledfont || actualFontStyle != fontDB.styleString( f ) )
210 {
211 foundmatch = true;
212 }
213
214 // default to first found style if requested style is unavailable
215 // this helps in the situations where the passed-in font has to have a named style applied
216 if ( fallback && !foundmatch )
217 {
218 QFont testFont = QFont( f );
219 testFont.setPointSize( defaultSize );
220
221 // prefer a style that mostly matches the passed-in font
222 const auto constFamily = fontDB.styles( f.family() );
223 for ( const QString &style : constFamily )
224 {
225 styledfont = fontDB.font( f.family(), style, defaultSize );
226 styledfont = styledfont.resolve( f );
227 if ( testFont.toString() == styledfont.toString() )
228 {
229 foundmatch = true;
230 break;
231 }
232 }
233
234 // fallback to first style found that works
235 if ( !foundmatch )
236 {
237 for ( const QString &style : constFamily )
238 {
239 styledfont = fontDB.font( f.family(), style, defaultSize );
240 if ( QApplication::font() != styledfont )
241 {
242 foundmatch = true;
243 break;
244 }
245 }
246 }
247 }
248
249 // similar to QFont::resolve, but font may already have pixel size set
250 // and we want to make sure that's preserved
251 if ( foundmatch )
252 {
253 if ( !qgsDoubleNear( f.pointSizeF(), -1 ) )
254 {
255 styledfont.setPointSizeF( f.pointSizeF() );
256 }
257 else if ( f.pixelSize() != -1 )
258 {
259 styledfont.setPixelSize( f.pixelSize() );
260 }
261 styledfont.setCapitalization( f.capitalization() );
262 styledfont.setUnderline( f.underline() );
263 styledfont.setStrikeOut( f.strikeOut() );
264 styledfont.setWordSpacing( f.wordSpacing() );
265 styledfont.setLetterSpacing( QFont::AbsoluteSpacing, f.letterSpacing() );
266 f = styledfont;
267
268 return true;
269 }
270
271 return false;
272}
273
275{
276 return u"QGIS Vera Sans"_s;
277}
278
279bool QgsFontUtils::loadStandardTestFonts( const QStringList &loadstyles )
280{
281 // load standard test font from filesystem or testdata.qrc (for unit tests and general testing)
282 bool fontsLoaded = false;
283
284 QMap<QString, QString> fontStyles;
285 fontStyles.insert( u"Roman"_s, u"QGIS-Vera/QGIS-Vera.ttf"_s );
286 fontStyles.insert( u"Oblique"_s, u"QGIS-Vera/QGIS-VeraIt.ttf"_s );
287 fontStyles.insert( u"Bold"_s, u"QGIS-Vera/QGIS-VeraBd.ttf"_s );
288 fontStyles.insert( u"Bold Oblique"_s, u"QGIS-Vera/QGIS-VeraBI.ttf"_s );
289 fontStyles.insert( u"Deja Bold"_s, u"QGIS-DejaVu/QGISDejaVuSans-Bold.ttf"_s );
290
291 QMap<QString, QString>::const_iterator f = fontStyles.constBegin();
292 for ( ; f != fontStyles.constEnd(); ++f )
293 {
294 const QString fontpath( f.value() );
295 if ( !( loadstyles.contains( f.key() ) || loadstyles.contains( u"All"_s ) ) )
296 {
297 continue;
298 }
299
300 const QString fontFamily = !f.key().startsWith( "Deja"_L1 ) ? standardTestFontFamily() : u"QGIS DejaVu Sans"_s;
301 const QString fontstyle = !f.key().startsWith( "Deja"_L1 ) ? f.key() : f.key().mid( 5 );
302
303 if ( fontFamilyHasStyle( fontFamily, fontstyle ) )
304 {
305 QgsDebugMsgLevel( u"Test font '%1 %2' already available"_s.arg( fontFamily, fontstyle ), 2 );
306 }
307 else
308 {
309 bool loaded = false;
311 {
312 // workaround for bugs with Qt 4.8.5 (other versions?) on Mac 10.9, where fonts
313 // from qrc resources load but fail to work and default font is substituted [LS]:
314 // https://bugreports.qt.io/browse/QTBUG-30917
315 // https://bugreports.qt.io/browse/QTBUG-32789
316 const QString fontPath( QgsApplication::buildSourcePath() + "/tests/testdata/font/" + fontpath );
317 const int fontID = QFontDatabase::addApplicationFont( fontPath );
318 loaded = ( fontID != -1 );
319 fontsLoaded = ( fontsLoaded || loaded );
320 QgsDebugMsgLevel( u"Test font '%1 %2' %3 from filesystem [%4]"_s.arg( fontFamily, fontstyle, loaded ? "loaded" : "FAILED to load", fontPath ), 2 );
321 QgsDebugMsgLevel( u"font families in %1: %2"_s.arg( fontID ).arg( QFontDatabase().applicationFontFamilies( fontID ).join( "," ) ), 2 );
322 }
323 else
324 {
325 QFile fontResource( ":/testdata/font/" + fontpath );
326 if ( fontResource.open( QIODevice::ReadOnly ) )
327 {
328 const int fontID = QFontDatabase::addApplicationFontFromData( fontResource.readAll() );
329 loaded = ( fontID != -1 );
330 fontsLoaded = ( fontsLoaded || loaded );
331 }
332 QgsDebugMsgLevel( u"Test font '%1' (%2) %3 from testdata.qrc"_s.arg( fontFamily, fontstyle, loaded ? "loaded" : "FAILED to load" ), 2 );
333 }
334 }
335 }
336
337 return fontsLoaded;
338}
339
340QFont QgsFontUtils::getStandardTestFont( const QString &style, int pointsize )
341{
342 const QString fontFamily = !style.startsWith( "Deja"_L1 ) ? standardTestFontFamily() : u"QGIS DejaVu Sans"_s;
343 const QString fontStyle = !style.startsWith( "Deja"_L1 ) ? style : style.mid( 5 );
344
345 if ( !fontFamilyHasStyle( fontFamily, fontStyle ) )
346 {
347 loadStandardTestFonts( QStringList() << style );
348 }
349
350 const QFontDatabase fontDB;
351 QFont f = fontDB.font( fontFamily, fontStyle, pointsize );
352#ifdef Q_OS_WIN
353 if ( !f.exactMatch() )
354 {
355 QString modified;
356 if ( fontStyle == "Roman" )
357 modified = "Normal";
358 else if ( fontStyle == "Oblique" )
359 modified = "Italic";
360 else if ( fontStyle == "Bold Oblique" )
361 modified = "Bold Italic";
362 if ( !modified.isEmpty() )
363 f = fontDB.font( fontFamily, modified, pointsize );
364 }
365 if ( !f.exactMatch() )
366 {
367 QgsDebugMsgLevel( u"Inexact font match - consider installing the %1 font."_s.arg( fontFamily ), 2 );
368 QgsDebugMsgLevel( u"Requested: %1"_s.arg( f.toString() ), 2 );
369 QFontInfo fi( f );
371 u"Replaced: %1,%2,%3,%4,%5,%6,%7,%8,%9"_s.arg( fi.family() )
372 .arg( fi.pointSizeF() )
373 .arg( fi.pixelSize() )
374 .arg( fi.styleHint() )
375 .arg( fi.weight() )
376 .arg( fi.style() )
377 .arg( fi.underline() )
378 .arg( fi.strikeOut() )
379 .arg( fi.fixedPitch() ),
380 2
381 );
382 }
383#endif
384 // in case above statement fails to set style
385 f.setBold( fontStyle.contains( "Bold"_L1 ) );
386 f.setItalic( fontStyle.contains( "Oblique"_L1 ) || fontStyle.contains( "Italic"_L1 ) );
387
388 return f;
389}
390
391QDomElement QgsFontUtils::toXmlElement( const QFont &font, QDomDocument &document, const QString &elementName )
392{
393 QDomElement fontElem = document.createElement( elementName );
394 fontElem.setAttribute( u"description"_s, font.toString() );
395 fontElem.setAttribute( u"style"_s, untranslateNamedStyle( font.styleName() ) );
396 fontElem.setAttribute( u"bold"_s, font.bold() ? QChar( '1' ) : QChar( '0' ) );
397 fontElem.setAttribute( u"italic"_s, font.italic() ? QChar( '1' ) : QChar( '0' ) );
398 fontElem.setAttribute( u"underline"_s, font.underline() ? QChar( '1' ) : QChar( '0' ) );
399 fontElem.setAttribute( u"strikethrough"_s, font.strikeOut() ? QChar( '1' ) : QChar( '0' ) );
400 return fontElem;
401}
402
403bool QgsFontUtils::setFromXmlElement( QFont &font, const QDomElement &element )
404{
405 if ( element.isNull() )
406 {
407 return false;
408 }
409
410 font.fromString( element.attribute( u"description"_s ) );
411
412 if ( element.hasAttribute( u"bold"_s ) && element.attribute( u"bold"_s ) == QChar( '1' ) )
413 {
414 font.setBold( true );
415 }
416 if ( element.hasAttribute( u"italic"_s ) )
417 {
418 font.setItalic( element.attribute( u"italic"_s ) == QChar( '1' ) );
419 }
420 if ( element.hasAttribute( u"underline"_s ) )
421 {
422 font.setUnderline( element.attribute( u"underline"_s ) == QChar( '1' ) );
423 }
424 if ( element.hasAttribute( u"strikethrough"_s ) )
425 {
426 font.setStrikeOut( element.attribute( u"strikethrough"_s ) == QChar( '1' ) );
427 }
428
429 if ( element.hasAttribute( u"style"_s ) )
430 {
431 ( void ) updateFontViaStyle( font, translateNamedStyle( element.attribute( u"style"_s ) ) );
432 }
433
434 return true;
435}
436
437bool QgsFontUtils::setFromXmlChildNode( QFont &font, const QDomElement &element, const QString &childNode )
438{
439 if ( element.isNull() )
440 {
441 return false;
442 }
443
444 const QDomNodeList nodeList = element.elementsByTagName( childNode );
445 if ( !nodeList.isEmpty() )
446 {
447 const QDomElement fontElem = nodeList.at( 0 ).toElement();
448 return setFromXmlElement( font, fontElem );
449 }
450 else
451 {
452 return false;
453 }
454}
455
456QMimeData *QgsFontUtils::toMimeData( const QFont &font )
457{
458 std::unique_ptr< QMimeData > mimeData( new QMimeData );
459
460 QDomDocument fontDoc;
461 const QDomElement fontElem = toXmlElement( font, fontDoc, u"font"_s );
462 fontDoc.appendChild( fontElem );
463 mimeData->setText( fontDoc.toString() );
464
465 return mimeData.release();
466}
467
468QFont QgsFontUtils::fromMimeData( const QMimeData *data, bool *ok )
469{
470 QFont font;
471 if ( ok )
472 *ok = false;
473
474 if ( !data )
475 return font;
476
477 const QString text = data->text();
478 if ( !text.isEmpty() )
479 {
480 QDomDocument doc;
481 QDomElement elem;
482
483 if ( doc.setContent( text ) )
484 {
485 elem = doc.documentElement();
486
487 if ( elem.nodeName() != "font"_L1 )
488 elem = elem.firstChildElement( u"font"_s );
489
490 if ( setFromXmlElement( font, elem ) )
491 {
492 if ( ok )
493 *ok = true;
494 }
495 return font;
496 }
497 }
498 return font;
499}
500
501static QMap<QString, QString> createTranslatedStyleMap()
502{
503 QMap<QString, QString> translatedStyleMap;
504 const QStringList words = QStringList() << u"Normal"_s << u"Regular"_s << u"Light"_s << u"Bold"_s << u"Black"_s << u"Demi"_s << u"Italic"_s << u"Oblique"_s;
505 const auto constWords = words;
506 for ( const QString &word : constWords )
507 {
508 translatedStyleMap.insert( QCoreApplication::translate( "QFontDatabase", qPrintable( word ) ), word );
509 }
510 return translatedStyleMap;
511}
512
513QString QgsFontUtils::translateNamedStyle( const QString &namedStyle )
514{
515 QStringList words = namedStyle.split( ' ', Qt::SkipEmptyParts );
516 for ( int i = 0, n = words.length(); i < n; ++i )
517 {
518 words[i] = QCoreApplication::translate( "QFontDatabase", words[i].toLocal8Bit().constData() );
519 }
520 return words.join( ' '_L1 );
521}
522
523QString QgsFontUtils::untranslateNamedStyle( const QString &namedStyle )
524{
525 static const QMap<QString, QString> translatedStyleMap = createTranslatedStyleMap();
526 QStringList words = namedStyle.split( ' ', Qt::SkipEmptyParts );
527
528 for ( int i = 0, n = words.length(); i < n; ++i )
529 {
530 if ( translatedStyleMap.contains( words[i] ) )
531 {
532 words[i] = translatedStyleMap.value( words[i] );
533 }
534 else
535 {
536 QgsDebugMsgLevel( u"Warning: style map does not contain %1"_s.arg( words[i] ), 2 );
537 }
538 }
539 return words.join( ' '_L1 );
540}
541
542QString QgsFontUtils::asCSS( const QFont &font, double pointToPixelScale )
543{
544 QString css = u"font-family: "_s + font.family() + ';';
545
546 //style
547 css += "font-style: "_L1;
548 switch ( font.style() )
549 {
550 case QFont::StyleNormal:
551 css += "normal"_L1;
552 break;
553 case QFont::StyleItalic:
554 css += "italic"_L1;
555 break;
556 case QFont::StyleOblique:
557 css += "oblique"_L1;
558 break;
559 }
560 css += ';';
561
562 //weight
563 int cssWeight = 400;
564 switch ( font.weight() )
565 {
566 case QFont::Light:
567 cssWeight = 300;
568 break;
569 case QFont::Normal:
570 cssWeight = 400;
571 break;
572 case QFont::DemiBold:
573 cssWeight = 600;
574 break;
575 case QFont::Bold:
576 cssWeight = 700;
577 break;
578 case QFont::Black:
579 cssWeight = 900;
580 break;
581 case QFont::Thin:
582 cssWeight = 100;
583 break;
584 case QFont::ExtraLight:
585 cssWeight = 200;
586 break;
587 case QFont::Medium:
588 cssWeight = 500;
589 break;
590 case QFont::ExtraBold:
591 cssWeight = 800;
592 break;
593 }
594 css += u"font-weight: %1;"_s.arg( cssWeight );
595
596 //size
597 css += u"font-size: %1px;"_s.arg( font.pointSizeF() >= 0 ? font.pointSizeF() * pointToPixelScale : font.pixelSize() );
598
599 return css;
600}
601
602void QgsFontUtils::addRecentFontFamily( const QString &family )
603{
604 if ( family.isEmpty() )
605 {
606 return;
607 }
608
609 QgsSettings settings;
610 QStringList recentFamilies = settings.value( u"fonts/recent"_s ).toStringList();
611
612 //remove matching families
613 recentFamilies.removeAll( family );
614
615 //then add to start of list
616 recentFamilies.prepend( family );
617
618 //trim to 10 fonts
619 recentFamilies = recentFamilies.mid( 0, 10 );
620
621 settings.setValue( u"fonts/recent"_s, recentFamilies );
622}
623
625{
626 const QgsSettings settings;
627 return settings.value( u"fonts/recent"_s ).toStringList();
628}
629
630void QgsFontUtils::setFontFamily( QFont &font, const QString &family )
631{
632 font.setFamily( family );
633 if ( !font.exactMatch() )
634 {
635 // some Qt versions struggle with fonts with certain unusual characters
636 // in their names, eg "ESRI Oil, Gas, & Water". Calling "setFamilies"
637 // can workaround these issues... (in some cases!)
638 font.setFamilies( { family } );
639 }
640}
641
642QFont QgsFontUtils::createFont( const QString &family, int pointSize, int weight, bool italic )
643{
644 QFont font( family, pointSize, weight, italic );
645 if ( !font.exactMatch() )
646 {
647 // some Qt versions struggle with fonts with certain unusual characters
648 // in their names, eg "ESRI Oil, Gas, & Water". Calling "setFamilies"
649 // can workaround these issues... (in some cases!)
650 font.setFamilies( { family } );
651 }
652 return font;
653}
static QString buildSourcePath()
Returns path to the source directory.
static bool isRunningFromBuildDir()
Indicates whether running from build directory (not installed).
static QString resolveFontStyleName(const QFont &font)
Attempts to resolve the style name corresponding to the specified font object.
static QString asCSS(const QFont &font, double pointToPixelMultiplier=1.0)
Returns a CSS string representing the specified font as closely as possible.
static QString translateNamedStyle(const QString &namedStyle)
Returns the localized named style of a font, if such a translation is available.
static QString untranslateNamedStyle(const QString &namedStyle)
Returns the english named style of a font, if possible.
static bool setFromXmlElement(QFont &font, const QDomElement &element)
Sets the properties of a font to match the properties stored in an XML element.
static bool setFromXmlChildNode(QFont &font, const QDomElement &element, const QString &childNode)
Sets the properties of a font to match the properties stored in an XML child node.
static QFont createFont(const QString &family, int pointSize=-1, int weight=-1, bool italic=false)
Creates a font with the specified family.
static QMimeData * toMimeData(const QFont &font)
Returns new mime data representing the specified font settings.
static bool fontFamilyMatchOnSystem(const QString &family, QString *chosen=nullptr, bool *match=nullptr)
Check whether font family is on system.
static bool fontFamilyOnSystem(const QString &family)
Check whether font family is on system in a quick manner, which does not compare [foundry].
static bool updateFontViaStyle(QFont &f, const QString &fontstyle, bool fallback=false)
Updates font with named style and retain all font properties.
static bool fontMatchOnSystem(const QFont &f)
Check whether exact font is on system.
static bool loadStandardTestFonts(const QStringList &loadstyles)
Loads standard test fonts from filesystem or qrc resource.
static QFont getStandardTestFont(const QString &style="Roman", int pointsize=12)
Gets standard test font with specific style.
static void setFontFamily(QFont &font, const QString &family)
Sets the family for a font object.
static QDomElement toXmlElement(const QFont &font, QDomDocument &document, const QString &elementName)
Returns a DOM element containing the properties of the font.
static void addRecentFontFamily(const QString &family)
Adds a font family to the list of recently used font families.
static QString standardTestFontFamily()
Gets standard test font family.
static QFont fromMimeData(const QMimeData *data, bool *ok=nullptr)
Attempts to parse the provided mime data as a QFont.
static bool fontFamilyHasStyle(const QString &family, const QString &style)
Check whether font family on system has specific style.
static QStringList recentFontFamilies()
Returns a list of recently used font families.
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.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6975
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63