mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-07 00:15:48 -04:00
[api] Add QgsCombinedStyleModel class, for combining entities from several
QgsStyle models into a single combined view
This commit is contained in:
parent
6c73cfcd45
commit
86f7d4e89e
@ -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()
|
||||
|
@ -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 *
|
||||
************************************************************************/
|
@ -102,6 +102,7 @@ done:
|
||||
%Feature HAVE_GUI
|
||||
%Feature ANDROID
|
||||
%Feature VECTOR_MAPPED_TYPE
|
||||
%Feature CONCATENATED_TABLES_MODEL
|
||||
|
||||
%Include conversions.sip
|
||||
%Include qgsexception.sip
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
108
src/core/symbology/qgscombinedstylemodel.cpp
Normal file
108
src/core/symbology/qgscombinedstylemodel.cpp
Normal 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
|
107
src/core/symbology/qgscombinedstylemodel.h
Normal file
107
src/core/symbology/qgscombinedstylemodel.h
Normal 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
|
@ -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)
|
||||
|
100
tests/src/python/test_qgscombinedstylemodel.py
Normal file
100
tests/src/python/test_qgscombinedstylemodel.py
Normal 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()
|
Loading…
x
Reference in New Issue
Block a user