Skip to content
Permalink
Browse files

Ensure that QgsApplication members are usable even when no

QgsApplication instance is available

Fixes crash when using custom widgets with Qt Designer
  • Loading branch information
3nids committed Feb 20, 2017
1 parent e53ad47 commit 4505ba7a013d8e82ab1dd691004d33b920ec73b7
Showing with 183 additions and 76 deletions.
  1. +93 −60 src/core/qgsapplication.cpp
  2. +32 −16 src/core/qgsapplication.h
  3. +1 −0 tests/src/python/CMakeLists.txt
  4. +57 −0 tests/src/python/test_qgsnoapplication.py
@@ -101,6 +101,8 @@ const char* QgsApplication::QGIS_ORGANIZATION_NAME = "QGIS";
const char* QgsApplication::QGIS_ORGANIZATION_DOMAIN = "qgis.org";
const char* QgsApplication::QGIS_APPLICATION_NAME = "QGIS3";

QgsApplication::ApplicationMembers* QgsApplication::sApplicationMembers = nullptr;

/*!
\class QgsApplication
\brief The QgsApplication class manages application-wide information.
@@ -119,24 +121,7 @@ QgsApplication::QgsApplication( int & argc, char ** argv, bool GUIenabled, const
{
sPlatformName = platformName;

// don't use initializer lists or scoped pointers - as more objects are added here we
// will need to be careful with the order of creation/destruction
mMessageLog = new QgsMessageLog();
mProfiler = new QgsRuntimeProfiler();
mTaskManager = new QgsTaskManager();
mActionScopeRegistry = new QgsActionScopeRegistry();
mFieldFormatterRegistry = new QgsFieldFormatterRegistry();
mSvgCache = new QgsSvgCache();
mColorSchemeRegistry = new QgsColorSchemeRegistry();
mColorSchemeRegistry->addDefaultSchemes();
mPaintEffectRegistry = new QgsPaintEffectRegistry();
mSymbolLayerRegistry = new QgsSymbolLayerRegistry();
mRendererRegistry = new QgsRendererRegistry();
mRasterRendererRegistry = new QgsRasterRendererRegistry();
mGpsConnectionRegistry = new QgsGPSConnectionRegistry();
mPluginLayerRegistry = new QgsPluginLayerRegistry();
mProcessingRegistry = new QgsProcessingRegistry();
mAnnotationRegistry = new QgsAnnotationRegistry();
mApplicationMembers = new ApplicationMembers();

init( customConfigPath ); // init can also be called directly by e.g. unit tests that don't inherit QApplication.
}
@@ -268,22 +253,8 @@ void QgsApplication::init( QString customConfigPath )

QgsApplication::~QgsApplication()
{
delete mAnnotationRegistry;
delete mProcessingRegistry;
delete mActionScopeRegistry;
delete mTaskManager;
delete mFieldFormatterRegistry;
delete mRasterRendererRegistry;
delete mRendererRegistry;
delete mSymbolLayerRegistry;
delete mPaintEffectRegistry;
delete mColorSchemeRegistry;
delete mSvgCache;
delete mGpsConnectionRegistry;
delete mPluginLayerRegistry;
delete mDataItemProviderRegistry;
delete mProfiler;
delete mMessageLog;
delete mApplicationMembers;
}

QgsApplication* QgsApplication::instance()
@@ -359,7 +330,7 @@ bool QgsApplication::notify( QObject * receiver, QEvent * event )

QgsRuntimeProfiler *QgsApplication::profiler()
{
return instance()->mProfiler;
return members()->mProfiler;
}

void QgsApplication::setFileOpenEventReceiver( QObject * receiver )
@@ -1373,26 +1344,31 @@ void QgsApplication::setCustomVariable( const QString& name, const QVariant& val

QString QgsApplication::nullRepresentation()
{
QgsApplication* app = instance();
if ( app->mNullRepresentation.isNull() )
app->mNullRepresentation = QSettings().value( QStringLiteral( "qgis/nullValue" ), QStringLiteral( "NULL" ) ).toString();
return app->mNullRepresentation;
ApplicationMembers* appMembers = members();
if ( appMembers->mNullRepresentation.isNull() )
{
appMembers->mNullRepresentation = QSettings().value( QStringLiteral( "qgis/nullValue" ), QStringLiteral( "NULL" ) ).toString();
}
return appMembers->mNullRepresentation;
}

void QgsApplication::setNullRepresentation( const QString& nullRepresentation )
{
QgsApplication* app = instance();
if ( app->mNullRepresentation == nullRepresentation )
ApplicationMembers* appMembers = members();
if ( !appMembers || appMembers->mNullRepresentation == nullRepresentation )
return;

app->mNullRepresentation = nullRepresentation;
appMembers->mNullRepresentation = nullRepresentation;
QSettings().setValue( QStringLiteral( "qgis/nullValue" ), nullRepresentation );
emit app->nullRepresentationChanged();

QgsApplication* app = instance();
if ( app )
emit app->nullRepresentationChanged();
}

QgsActionScopeRegistry* QgsApplication::actionScopeRegistry()
{
return instance()->mActionScopeRegistry;
return members()->mActionScopeRegistry;
}

bool QgsApplication::createDatabase( QString *errorMessage )
@@ -1549,70 +1525,127 @@ void QgsApplication::setMaxThreads( int maxThreads )

QgsTaskManager* QgsApplication::taskManager()
{
return instance()->mTaskManager;
return members()->mTaskManager;
}

QgsColorSchemeRegistry* QgsApplication::colorSchemeRegistry()
{
return instance()->mColorSchemeRegistry;
return members()->mColorSchemeRegistry;
}

QgsPaintEffectRegistry* QgsApplication::paintEffectRegistry()
{
return instance()->mPaintEffectRegistry;
return members()->mPaintEffectRegistry;
}

QgsRendererRegistry*QgsApplication::rendererRegistry()
QgsRendererRegistry* QgsApplication::rendererRegistry()
{
return instance()->mRendererRegistry;
return members()->mRendererRegistry;
}

QgsRasterRendererRegistry* QgsApplication::rasterRendererRegistry()
{
return instance()->mRasterRendererRegistry;
return members()->mRasterRendererRegistry;
}

QgsDataItemProviderRegistry*QgsApplication::dataItemProviderRegistry()
QgsDataItemProviderRegistry* QgsApplication::dataItemProviderRegistry()
{
return instance()->mDataItemProviderRegistry;
}

QgsSvgCache* QgsApplication::svgCache()
{
return instance()->mSvgCache;
return members()->mSvgCache;
}

QgsSymbolLayerRegistry* QgsApplication::symbolLayerRegistry()
{
return instance()->mSymbolLayerRegistry;
return members()->mSymbolLayerRegistry;
}

QgsGPSConnectionRegistry* QgsApplication::gpsConnectionRegistry()
{
return instance()->mGpsConnectionRegistry;
return members()->mGpsConnectionRegistry;
}

QgsPluginLayerRegistry*QgsApplication::pluginLayerRegistry()
{
return instance()->mPluginLayerRegistry;
return members()->mPluginLayerRegistry;
}

QgsMessageLog* QgsApplication::messageLog()
{
return instance()->mMessageLog;
return members()->mMessageLog;
}

QgsProcessingRegistry*QgsApplication::processingRegistry()
QgsProcessingRegistry* QgsApplication::processingRegistry()
{
return instance()->mProcessingRegistry;
return members()->mProcessingRegistry;
}

QgsAnnotationRegistry*QgsApplication::annotationRegistry()
QgsAnnotationRegistry* QgsApplication::annotationRegistry()
{
return instance()->mAnnotationRegistry;
return members()->mAnnotationRegistry;
}

QgsFieldFormatterRegistry* QgsApplication::fieldFormatterRegistry()
{
return instance()->mFieldFormatterRegistry;
return members()->mFieldFormatterRegistry;
}

QgsApplication::ApplicationMembers::ApplicationMembers()
{
// don't use initializer lists or scoped pointers - as more objects are added here we
// will need to be careful with the order of creation/destruction
mMessageLog = new QgsMessageLog();
mProfiler = new QgsRuntimeProfiler();
mTaskManager = new QgsTaskManager();
mActionScopeRegistry = new QgsActionScopeRegistry();
mFieldFormatterRegistry = new QgsFieldFormatterRegistry();
mSvgCache = new QgsSvgCache();
mColorSchemeRegistry = new QgsColorSchemeRegistry();
mColorSchemeRegistry->addDefaultSchemes();
mPaintEffectRegistry = new QgsPaintEffectRegistry();
mSymbolLayerRegistry = new QgsSymbolLayerRegistry();
mRendererRegistry = new QgsRendererRegistry();
mRasterRendererRegistry = new QgsRasterRendererRegistry();
mGpsConnectionRegistry = new QgsGPSConnectionRegistry();
mPluginLayerRegistry = new QgsPluginLayerRegistry();
mProcessingRegistry = new QgsProcessingRegistry();
mAnnotationRegistry = new QgsAnnotationRegistry();
}

QgsApplication::ApplicationMembers::~ApplicationMembers()
{
delete mActionScopeRegistry;
delete mAnnotationRegistry;
delete mColorSchemeRegistry;
delete mFieldFormatterRegistry;
delete mGpsConnectionRegistry;
delete mMessageLog;
delete mPaintEffectRegistry;
delete mPluginLayerRegistry;
delete mProcessingRegistry;
delete mProfiler;
delete mRasterRendererRegistry;
delete mRendererRegistry;
delete mSvgCache;
delete mSymbolLayerRegistry;
delete mTaskManager;
}

QgsApplication::ApplicationMembers* QgsApplication::members()
{
if ( instance() )
{
return instance()->mApplicationMembers;
}
else
{
static QMutex sMemberMutex( QMutex::Recursive );
QMutexLocker lock( &sMemberMutex );
if ( !sApplicationMembers )
sApplicationMembers = new ApplicationMembers();
return sApplicationMembers;
}
}
@@ -54,6 +54,7 @@ class CORE_EXPORT QgsApplication : public QApplication
Q_OBJECT

public:

static const char* QGIS_ORGANIZATION_NAME;
static const char* QGIS_ORGANIZATION_DOMAIN;
static const char* QGIS_APPLICATION_NAME;
@@ -555,6 +556,7 @@ class CORE_EXPORT QgsApplication : public QApplication
void nullRepresentationChanged();

private:

static void copyPath( const QString& src, const QString& dst );
static QObject* ABISYM( mFileOpenEventReceiver );
static QStringList ABISYM( mFileOpenEventList );
@@ -600,23 +602,37 @@ class CORE_EXPORT QgsApplication : public QApplication

QMap<QString, QIcon> mIconCache;

QgsActionScopeRegistry* mActionScopeRegistry = nullptr;
QgsRuntimeProfiler* mProfiler = nullptr;
QgsTaskManager* mTaskManager = nullptr;
QgsFieldFormatterRegistry* mFieldFormatterRegistry = nullptr;
QgsColorSchemeRegistry* mColorSchemeRegistry = nullptr;
QgsPaintEffectRegistry* mPaintEffectRegistry = nullptr;
QgsRendererRegistry* mRendererRegistry = nullptr;
QgsSvgCache* mSvgCache = nullptr;
QgsSymbolLayerRegistry* mSymbolLayerRegistry = nullptr;
QgsRasterRendererRegistry* mRasterRendererRegistry = nullptr;
QgsGPSConnectionRegistry* mGpsConnectionRegistry = nullptr;
QgsDataItemProviderRegistry* mDataItemProviderRegistry = nullptr;
QgsPluginLayerRegistry* mPluginLayerRegistry = nullptr;
QgsMessageLog* mMessageLog = nullptr;
QgsProcessingRegistry* mProcessingRegistry = nullptr;
QgsAnnotationRegistry* mAnnotationRegistry = nullptr;
QString mNullRepresentation;

struct ApplicationMembers
{
QgsActionScopeRegistry* mActionScopeRegistry = nullptr;
QgsAnnotationRegistry* mAnnotationRegistry = nullptr;
QgsColorSchemeRegistry* mColorSchemeRegistry = nullptr;
QgsFieldFormatterRegistry* mFieldFormatterRegistry = nullptr;
QgsGPSConnectionRegistry* mGpsConnectionRegistry = nullptr;
QgsMessageLog* mMessageLog = nullptr;
QgsPaintEffectRegistry* mPaintEffectRegistry = nullptr;
QgsPluginLayerRegistry* mPluginLayerRegistry = nullptr;
QgsProcessingRegistry* mProcessingRegistry = nullptr;
QgsRasterRendererRegistry* mRasterRendererRegistry = nullptr;
QgsRendererRegistry* mRendererRegistry = nullptr;
QgsRuntimeProfiler* mProfiler = nullptr;
QgsSvgCache* mSvgCache = nullptr;
QgsSymbolLayerRegistry* mSymbolLayerRegistry = nullptr;
QgsTaskManager* mTaskManager = nullptr;
QString mNullRepresentation;

ApplicationMembers();
~ApplicationMembers();
};

// Applications members which belong to an instance of QgsApplication
ApplicationMembers* mApplicationMembers = nullptr;
// ... but in case QgsApplication is never instantiated (eg with custom designer widgets), we fall back to static members
static ApplicationMembers* sApplicationMembers;

static ApplicationMembers* members();
};

#endif
@@ -77,6 +77,7 @@ ADD_PYTHON_TEST(PyQgsMultiEditToolButton test_qgsmultiedittoolbutton.py)
ADD_PYTHON_TEST(PyQgsNetworkContentFetcher test_qgsnetworkcontentfetcher.py)
ADD_PYTHON_TEST(PyQgsNullSymbolRenderer test_qgsnullsymbolrenderer.py)
ADD_PYTHON_TEST(PyQgsNewGeoPackageLayerDialog test_qgsnewgeopackagelayerdialog.py)
ADD_PYTHON_TEST(PyQgsNoApplication test_qgsnoapplication.py)
ADD_PYTHON_TEST(PyQgsOGRProviderGpkg test_provider_ogr_gpkg.py)
ADD_PYTHON_TEST(PyQgsOGRProviderSqlite test_provider_ogr_sqlite.py)
ADD_PYTHON_TEST(PyQgsOptional test_qgsoptional.py)
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for accessing QgsApplication members with a QgsApplication instance.
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = 'Nyall Dawson'
__date__ = '1/02/2017'
__copyright__ = 'Copyright 2017, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'

import qgis # NOQA

from qgis.core import (QgsApplication)
from qgis.testing import unittest

"""
Really important!! This test is designed to ensure that the members
which usually belong to a QgsApplication instance are still usable
when no QgsApplication instance is available (eg when using
custom editor widgets in QtDesigner). In this case QgsApplication
falls back to static members, which this test is designed to check.
So don't add start_app here or anything else which creates a
QgsApplication instance!
"""


class TestQgsNoApplication(unittest.TestCase):
def testMembers(self):
self.assertTrue(QgsApplication.actionScopeRegistry())
# self.assertTrue(QgsApplication.annotationRegistry()) NOT AVAILABLE IN BINDINGS
self.assertTrue(QgsApplication.colorSchemeRegistry())
self.assertTrue(QgsApplication.fieldFormatterRegistry())
self.assertTrue(QgsApplication.gpsConnectionRegistry())
self.assertTrue(QgsApplication.messageLog())
self.assertTrue(QgsApplication.paintEffectRegistry())
self.assertTrue(QgsApplication.pluginLayerRegistry())
self.assertTrue(QgsApplication.processingRegistry())
self.assertTrue(QgsApplication.profiler())
# self.assertTrue(QgsApplication.rasterRendererRegistry()) NOT AVAILABLE IN BINDINGS
self.assertTrue(QgsApplication.rendererRegistry())
self.assertTrue(QgsApplication.svgCache())
self.assertTrue(QgsApplication.symbolLayerRegistry())
self.assertTrue(QgsApplication.taskManager())

def testNullRepresentation(self):
nr = 'my_null_value'
QgsApplication.setNullRepresentation(nr)
self.assertEqual(QgsApplication.nullRepresentation(), nr)


if __name__ == '__main__':
unittest.main()

0 comments on commit 4505ba7

Please sign in to comment.
You can’t perform that action at this time.