[api] Add QgsCombinedStyleModel class, for combining entities from several

QgsStyle models into a single combined view
This commit is contained in:
Nyall Dawson 2022-05-10 09:31:19 +10:00
parent 6c73cfcd45
commit 86f7d4e89e
9 changed files with 410 additions and 0 deletions

View File

@ -156,6 +156,10 @@ if((${PYQT5_VERSION_STR} VERSION_EQUAL 5.15) OR (${PYQT5_VERSION_STR} VERSION_GR
set(SIP_DISABLE_FEATURES ${SIP_DISABLE_FEATURES} VECTOR_MAPPED_TYPE)
endif()
if((${PYQT5_VERSION_STR} VERSION_LESS 5.13))
set(SIP_DISABLE_FEATURES ${SIP_DISABLE_FEATURES} CONCATENATED_TABLES_MODEL)
endif()
if(NOT WITH_GUI)
set(SIP_DISABLE_FEATURES ${SIP_DISABLE_FEATURES} HAVE_GUI)
endif()

View File

@ -0,0 +1,84 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/symbology/qgscombinedstylemodel.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsCombinedStyleModel: QConcatenateTablesProxyModel
{
%Docstring(signature="appended")
A model which contains entities from multiple :py:class:`QgsStyle` databases.
.. note::
Only available in builds based on Qt 5.13 or later
.. versionadded:: 3.26
%End
%TypeHeaderCode
#include "qgscombinedstylemodel.h"
%End
public:
enum Role
{
IsTitleRole,
};
explicit QgsCombinedStyleModel( QObject *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsCombinedStyleModel with the specified ``parent`` object.
%End
void addStyle( QgsStyle *style );
%Docstring
Adds a style to the model.
Ownership of ``style`` is not transferred.
.. seealso:: :py:func:`styles`
.. seealso:: :py:func:`addDefaultStyle`
%End
void addDefaultStyle();
%Docstring
Adds the default style (:py:func:`QgsStyle.defaultStyle()`) to the model.
.. seealso:: :py:func:`addStyle`
%End
QList< QgsStyle * > styles() const;
%Docstring
Returns a list of all styles shown in the model.
.. seealso:: :py:func:`addStyle`
%End
void addDesiredIconSize( QSize size );
%Docstring
Adds an additional icon ``size`` to generate for Qt.DecorationRole data.
This allows style icons to be generated at an icon size which
corresponds exactly to the view's icon size in which this model is used.
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/symbology/qgscombinedstylemodel.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -102,6 +102,7 @@ done:
%Feature HAVE_GUI
%Feature ANDROID
%Feature VECTOR_MAPPED_TYPE
%Feature CONCATENATED_TABLES_MODEL
%Include conversions.sip
%Include qgsexception.sip

View File

@ -628,6 +628,9 @@
%Include auto_generated/symbology/qgsarrowsymbollayer.sip
%Include auto_generated/symbology/qgscategorizedsymbolrenderer.sip
%Include auto_generated/symbology/qgscolorbrewerpalette.sip
%If ( CONCATENATED_TABLES_MODEL )
%Include auto_generated/symbology/qgscombinedstylemodel.sip
%End
%Include auto_generated/symbology/qgscptcityarchive.sip
%Include auto_generated/symbology/qgsellipsesymbollayer.sip
%Include auto_generated/symbology/qgsembeddedsymbolrenderer.sip

View File

@ -75,6 +75,7 @@ set(QGIS_CORE_SRCS
symbology/qgsarrowsymbollayer.cpp
symbology/qgscategorizedsymbolrenderer.cpp
symbology/qgscolorbrewerpalette.cpp
symbology/qgscombinedstylemodel.cpp
symbology/qgscptcityarchive.cpp
symbology/qgsellipsesymbollayer.cpp
symbology/qgsembeddedsymbolrenderer.cpp
@ -1748,6 +1749,7 @@ set(QGIS_CORE_HDRS
symbology/qgsarrowsymbollayer.h
symbology/qgscategorizedsymbolrenderer.h
symbology/qgscolorbrewerpalette.h
symbology/qgscombinedstylemodel.h
symbology/qgscptcityarchive.h
symbology/qgsellipsesymbollayer.h
symbology/qgsembeddedsymbolrenderer.h

View File

@ -0,0 +1,108 @@
/***************************************************************************
qgscombinedstylemodel.cpp
---------------
begin : May 2022
copyright : (C) 2022 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#include "qgscombinedstylemodel.h"
#include "qgsstyle.h"
#include "qgsstylemodel.h"
#include "qgssingleitemmodel.h"
#include "qgsapplication.h"
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
QgsCombinedStyleModel::QgsCombinedStyleModel( QObject *parent )
: QConcatenateTablesProxyModel( parent )
{
}
void QgsCombinedStyleModel::addStyle( QgsStyle *style )
{
connect( style, &QgsStyle::destroyed, this, [this, style]()
{
if ( QgsSingleItemModel *model = mTitleModels.value( style ) )
{
removeSourceModel( model );
mTitleModels.remove( style );
delete model;
}
if ( QgsStyleModel *model = mOwnedStyleModels.value( style ) )
{
removeSourceModel( model );
mOwnedStyleModels.remove( style );
delete model;
}
mStyles.removeAll( style );
} );
mStyles.append( style );
QgsSingleItemModel *titleModel = new QgsSingleItemModel( nullptr, style->name(), {{ IsTitleRole, true }} );
addSourceModel( titleModel );
mTitleModels.insert( style, titleModel );
QgsStyleModel *styleModel = new QgsStyleModel( style );
for ( QSize size : std::as_const( mAdditionalSizes ) )
{
styleModel->addDesiredIconSize( size );
}
addSourceModel( styleModel );
mOwnedStyleModels.insert( style, styleModel );
}
void QgsCombinedStyleModel::addDefaultStyle()
{
QgsStyle *defaultStyle = QgsStyle::defaultStyle();
mStyles.append( defaultStyle );
QgsSingleItemModel *titleModel = new QgsSingleItemModel( nullptr, defaultStyle->name(), {{ IsTitleRole, true }} );
addSourceModel( titleModel );
mTitleModels.insert( defaultStyle, titleModel );
QgsStyleModel *styleModel = QgsApplication::defaultStyleModel();
for ( QSize size : std::as_const( mAdditionalSizes ) )
{
styleModel->addDesiredIconSize( size );
}
addSourceModel( styleModel );
}
QList< QgsStyle * > QgsCombinedStyleModel::styles() const
{
return mStyles;
}
void QgsCombinedStyleModel::addDesiredIconSize( QSize size )
{
if ( !mAdditionalSizes.contains( size ) )
mAdditionalSizes.append( size );
for ( auto it = mOwnedStyleModels.constBegin(); it != mOwnedStyleModels.constEnd(); ++it )
{
it.value()->addDesiredIconSize( size );
}
if ( mStyles.contains( QgsStyle::defaultStyle() ) )
{
QgsApplication::defaultStyleModel()->addDesiredIconSize( size );
}
}
#endif

View File

@ -0,0 +1,107 @@
/***************************************************************************
qgscombinedstylemodel.h
---------------
begin : May 2022
copyright : (C) 2022 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef QGSCOMBINEDSTYLEMODEL_H
#define QGSCOMBINEDSTYLEMODEL_H
#include "qgis_core.h"
#include "qgis_sip.h"
#include <QtGlobal>
SIP_IF_MODULE( CONCATENATED_TABLES_MODEL )
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
#include <QConcatenateTablesProxyModel>
class QgsStyle;
class QgsStyleModel;
class QgsSingleItemModel;
/**
* \ingroup core
* \class QgsCombinedStyleModel
*
* \brief A model which contains entities from multiple QgsStyle databases.
*
* \note Only available in builds based on Qt 5.13 or later
* \since QGIS 3.26
*/
class CORE_EXPORT QgsCombinedStyleModel: public QConcatenateTablesProxyModel
{
Q_OBJECT
public:
/**
* Combined model roles.
*
* \note Roles from QgsStyleModel are also available.
*/
enum Role
{
IsTitleRole = Qt::UserRole + 3234, //!< True if the index corresponds to a title item
};
/**
* Constructor for QgsCombinedStyleModel with the specified \a parent object.
*/
explicit QgsCombinedStyleModel( QObject *parent SIP_TRANSFERTHIS = nullptr );
/**
* Adds a style to the model.
*
* Ownership of \a style is not transferred.
*
* \see styles()
* \see addDefaultStyle()
*/
void addStyle( QgsStyle *style );
/**
* Adds the default style (QgsStyle::defaultStyle()) to the model.
*
* \see addStyle()
*/
void addDefaultStyle();
/**
* Returns a list of all styles shown in the model.
*
* \see addStyle()
*/
QList< QgsStyle * > styles() const;
/**
* Adds an additional icon \a size to generate for Qt::DecorationRole data.
*
* This allows style icons to be generated at an icon size which
* corresponds exactly to the view's icon size in which this model is used.
*/
void addDesiredIconSize( QSize size );
private:
QList< QgsStyle * > mStyles;
QHash< QgsStyle *, QgsStyleModel * > mOwnedStyleModels;
QHash< QgsStyle *, QgsSingleItemModel * > mTitleModels;
QList< QSize > mAdditionalSizes;
};
#endif
#endif //QGSCOMBINEDSTYLEMODEL_H

View File

@ -56,6 +56,7 @@ ADD_PYTHON_TEST(PyQgsColorRamp test_qgscolorramp.py)
ADD_PYTHON_TEST(PyQgsColorRampLegendNode test_qgscolorramplegendnode.py)
ADD_PYTHON_TEST(PyQgsColorScheme test_qgscolorscheme.py)
ADD_PYTHON_TEST(PyQgsColorSchemeRegistry test_qgscolorschemeregistry.py)
ADD_PYTHON_TEST(PyQgsCombinedStyleModel test_qgscombinedstylemodel.py)
ADD_PYTHON_TEST(PyQgsCoordinateFormatter test_qgscoordinateformatter.py)
ADD_PYTHON_TEST(PyQgsCoordinateOperationWidget test_qgscoordinateoperationwidget.py)
ADD_PYTHON_TEST(PyQgsConditionalFormatWidgets test_qgsconditionalformatwidgets.py)

View File

@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsCombinedStyleModel
.. 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__ = '18/03/2022'
__copyright__ = 'Copyright 2022, The QGIS Project'
import os
import qgis # NOQA
from qgis.PyQt.QtCore import QCoreApplication, QEvent
from qgis.core import (
QgsStyle,
QgsTextFormat,
QgsProfileRequest,
QgsCoordinateReferenceSystem,
)
from qgis.PyQt.QtXml import QDomDocument
from qgis.testing import start_app, unittest
from utilities import unitTestDataPath
start_app()
try:
from qgis.core import QgsCombinedStyleModel
except ImportError:
QgsCombinedStyleModel = None
class TestQgsCombinedStyleModel(unittest.TestCase):
@unittest.skipIf(QgsCombinedStyleModel is None, "QgsCombinedStyleModel not available")
def test_model(self):
model = QgsCombinedStyleModel()
self.assertFalse(model.styles())
self.assertEqual(model.rowCount(), 0)
style1 = QgsStyle()
style1.createMemoryDatabase()
style1.setName('first style')
model.addStyle(style1)
self.assertEqual(model.styles(), [style1])
self.assertEqual(model.rowCount(), 1)
self.assertEqual(model.data(model.index(0, 0)), 'first style')
self.assertTrue(model.data(model.index(0, 0), QgsCombinedStyleModel.IsTitleRole))
style1.addTextFormat('format 1', QgsTextFormat(), True)
self.assertEqual(model.rowCount(), 2)
self.assertEqual(model.data(model.index(0, 0)), 'first style')
self.assertTrue(model.data(model.index(0, 0), QgsCombinedStyleModel.IsTitleRole))
self.assertEqual(model.data(model.index(1, 0)), 'format 1')
self.assertFalse(model.data(model.index(1, 0), QgsCombinedStyleModel.IsTitleRole))
style2 = QgsStyle()
style2.createMemoryDatabase()
style2.setName('second style')
style2.addTextFormat('format 2', QgsTextFormat(), True)
style2.addTextFormat('format 3', QgsTextFormat(), True)
model.addStyle(style2)
self.assertEqual(model.styles(), [style1, style2])
self.assertEqual(model.rowCount(), 5)
self.assertEqual(model.data(model.index(0, 0)), 'first style')
self.assertTrue(model.data(model.index(0, 0), QgsCombinedStyleModel.IsTitleRole))
self.assertEqual(model.data(model.index(1, 0)), 'format 1')
self.assertFalse(model.data(model.index(1, 0), QgsCombinedStyleModel.IsTitleRole))
self.assertEqual(model.data(model.index(2, 0)), 'second style')
self.assertTrue(model.data(model.index(2, 0), QgsCombinedStyleModel.IsTitleRole))
self.assertEqual(model.data(model.index(3, 0)), 'format 2')
self.assertFalse(model.data(model.index(3, 0), QgsCombinedStyleModel.IsTitleRole))
self.assertEqual(model.data(model.index(4, 0)), 'format 3')
self.assertFalse(model.data(model.index(4, 0), QgsCombinedStyleModel.IsTitleRole))
style1.deleteLater()
style1 = None
QCoreApplication.sendPostedEvents(None, QEvent.DeferredDelete)
self.assertEqual(model.rowCount(), 3)
self.assertEqual(model.data(model.index(0, 0)), 'second style')
self.assertTrue(model.data(model.index(0, 0), QgsCombinedStyleModel.IsTitleRole))
self.assertEqual(model.data(model.index(1, 0)), 'format 2')
self.assertFalse(model.data(model.index(1, 0), QgsCombinedStyleModel.IsTitleRole))
self.assertEqual(model.data(model.index(2, 0)), 'format 3')
self.assertFalse(model.data(model.index(2, 0), QgsCombinedStyleModel.IsTitleRole))
if __name__ == '__main__':
unittest.main()