Skip to content

Commit

Permalink
fix project migration of old reference to new ones
Browse files Browse the repository at this point in the history
  • Loading branch information
troopa81 committed Feb 27, 2023
1 parent 1bc1dd3 commit 388c6e3
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 7 deletions.
11 changes: 10 additions & 1 deletion python/core/auto_generated/symbology/qgssymbollayerutils.sip.in
Expand Up @@ -978,7 +978,7 @@ Encodes a reference to a parametric SVG into a path with parameters according to
.. versionadded:: 3.0
%End

static QSet<const QgsSymbolLayer *> toSymbolLayerPointers( QgsFeatureRenderer *renderer, const QSet<QgsSymbolLayerId> &symbolLayerIds );
static QSet<const QgsSymbolLayer *> toSymbolLayerPointers( const QgsFeatureRenderer *renderer, const QSet<QgsSymbolLayerId> &symbolLayerIds );
%Docstring
Converts a set of symbol layer id to a set of pointers to actual symbol layers carried by the feature renderer.

Expand Down Expand Up @@ -1030,6 +1030,15 @@ The method makes approximations and can modify ``angle`` in order to generate th

:return: the size of the tile

.. versionadded:: 3.30
%End

static void fixOldSymbolLayerReferences( const QMap<QString, QgsMapLayer *> &mapLayers );
%Docstring
:py:class:`QgsSymbolLayerReference` uses :py:class:`QgsSymbolLayer` unique uuid identifier since QGIS 3.30, instead of the symbol
key (rule for :py:class:`QgsRuleBasedRenderer` for instance) and index path, so this method migrates ``mapLayers`` old references
to new ones.

.. versionadded:: 3.30
%End

Expand Down
8 changes: 8 additions & 0 deletions src/core/project/qgsproject.cpp
Expand Up @@ -1946,6 +1946,14 @@ bool QgsProject::readProjectFile( const QString &filename, Qgis::ProjectReadFlag
profile.switchTask( tr( "Resolving references" ) );
mRootGroup->resolveReferences( this );

// we need to migrate old fashion designed QgsSymbolLayerReference to new ones
if ( QgsProjectVersion( 3, 28, 0 ) > mSaveVersion )
{
Q_NOWARN_DEPRECATED_PUSH
QgsSymbolLayerUtils::fixOldSymbolLayerReferences( mapLayers() );
Q_NOWARN_DEPRECATED_POP
}

if ( !layerTreeElem.isNull() )
{
mRootGroup->readLayerOrderFromXml( layerTreeElem );
Expand Down
2 changes: 1 addition & 1 deletion src/core/symbology/qgssymbollayerreference.cpp
Expand Up @@ -40,7 +40,7 @@ QgsSymbolLayerReferenceList stringToSymbolLayerReferenceList( const QString &str
// TODO QGIS 4 : remove this if branch, keep only else part
Q_NOWARN_DEPRECATED_PUSH

// old masked symbol layer format (before 3.28), we use unique id now!
// old masked symbol layer format (before 3.30), we use unique id now!
// we load it the old fashion way and we will update the new one later when
// the whole project is loaded

Expand Down
90 changes: 89 additions & 1 deletion src/core/symbology/qgssymbollayerutils.cpp
Expand Up @@ -43,6 +43,7 @@
#include "qgssymbollayerreference.h"
#include "qgsmarkersymbollayer.h"
#include "qmath.h"
#include "qgsmasksymbollayer.h"

#include <QColor>
#include <QFont>
Expand Down Expand Up @@ -4911,7 +4912,7 @@ double QgsSymbolLayerUtils::sizeInPixelsFromSldUom( const QString &uom, double s
return size * scale;
}

QSet<const QgsSymbolLayer *> QgsSymbolLayerUtils::toSymbolLayerPointers( QgsFeatureRenderer *renderer, const QSet<QgsSymbolLayerId> &symbolLayerIds )
QSet<const QgsSymbolLayer *> QgsSymbolLayerUtils::toSymbolLayerPointers( const QgsFeatureRenderer *renderer, const QSet<QgsSymbolLayerId> &symbolLayerIds )
{
class SymbolLayerVisitor : public QgsStyleEntityVisitorInterface
{
Expand Down Expand Up @@ -5355,5 +5356,92 @@ QSize QgsSymbolLayerUtils::tileSize( int width, int height, double &angleRad )
}

return tileSize;
}

void QgsSymbolLayerUtils::fixOldSymbolLayerReferences( const QMap<QString, QgsMapLayer *> &mapLayers )
{
for ( QgsMapLayer *ml : mapLayers )
{
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
if ( !vl )
continue;

auto migrateOldReferences = [&mapLayers]( const QList<QgsSymbolLayerReference> &slRefs )
{
QList<QgsSymbolLayerReference> newRefs;
for ( QgsSymbolLayerReference slRef : slRefs )
{
const QgsVectorLayer *vlRef = qobject_cast<QgsVectorLayer *>( mapLayers[ slRef.layerId() ] );
const QgsFeatureRenderer *renderer = vlRef ? vlRef->renderer() : nullptr;
QSet<const QgsSymbolLayer *> symbolLayers = renderer ? QgsSymbolLayerUtils::toSymbolLayerPointers(
renderer, QSet<QgsSymbolLayerId>() << slRef.symbolLayerId() ) : QSet<const QgsSymbolLayer *>();
const QString slId = symbolLayers.isEmpty() ? QString() : ( *symbolLayers.constBegin() )->id();
newRefs << QgsSymbolLayerReference( slRef.layerId(), slId );
}

return newRefs;
};

if ( QgsAbstractVectorLayerLabeling *labeling = vl->labeling() )
{
for ( QString provider : labeling->subProviders() )
{
QgsPalLayerSettings settings = labeling->settings( provider );
QgsTextFormat format = settings.format();
QList<QgsSymbolLayerReference> newMaskedSymbolLayers = migrateOldReferences( format.mask().maskedSymbolLayers() );
format.mask().setMaskedSymbolLayers( newMaskedSymbolLayers );
settings.setFormat( format );
labeling->setSettings( new QgsPalLayerSettings( settings ), provider );
}
}

if ( QgsFeatureRenderer *renderer = vl->renderer() )
{

class SymbolLayerVisitor : public QgsStyleEntityVisitorInterface
{
public:
bool visitEnter( const QgsStyleEntityVisitorInterface::Node &node ) override
{
return ( node.type == QgsStyleEntityVisitorInterface::NodeType::SymbolRule );
}

void visitSymbol( const QgsSymbol *symbol )
{
for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
{
const QgsSymbolLayer *sl = symbol->symbolLayer( idx );

// recurse over sub symbols
const QgsSymbol *subSymbol = const_cast<QgsSymbolLayer *>( sl )->subSymbol();
if ( subSymbol )
visitSymbol( subSymbol );

if ( const QgsMaskMarkerSymbolLayer *maskLayer = dynamic_cast<const QgsMaskMarkerSymbolLayer *>( sl ) )
maskSymbolLayers << maskLayer;
}
}

bool visit( const QgsStyleEntityVisitorInterface::StyleLeaf &leaf ) override
{
if ( leaf.entity && leaf.entity->type() == QgsStyle::SymbolEntity )
{
auto symbolEntity = static_cast<const QgsStyleSymbolEntity *>( leaf.entity );
if ( symbolEntity->symbol() )
visitSymbol( symbolEntity->symbol() );
}
return true;
}

QList<const QgsMaskMarkerSymbolLayer *> maskSymbolLayers;
};

SymbolLayerVisitor visitor;
renderer->accept( &visitor );

for ( const QgsMaskMarkerSymbolLayer *maskSymbolLayer : visitor.maskSymbolLayers )
// Ugly but there is no other proper way to get those layer in order to modify them
const_cast<QgsMaskMarkerSymbolLayer *>( maskSymbolLayer )->setMasks( migrateOldReferences( maskSymbolLayer->masks() ) );
}
}
}
10 changes: 9 additions & 1 deletion src/core/symbology/qgssymbollayerutils.h
Expand Up @@ -884,7 +884,7 @@ class CORE_EXPORT QgsSymbolLayerUtils
* Converts a set of symbol layer id to a set of pointers to actual symbol layers carried by the feature renderer.
* \since QGIS 3.12
*/
static QSet<const QgsSymbolLayer *> toSymbolLayerPointers( QgsFeatureRenderer *renderer, const QSet<QgsSymbolLayerId> &symbolLayerIds );
static QSet<const QgsSymbolLayer *> toSymbolLayerPointers( const QgsFeatureRenderer *renderer, const QSet<QgsSymbolLayerId> &symbolLayerIds );

/**
* Calculates the frame rate (in frames per second) at which the given \a renderer must be redrawn.
Expand Down Expand Up @@ -928,6 +928,14 @@ class CORE_EXPORT QgsSymbolLayerUtils
*/
static QSize tileSize( int width, int height, double &angleRad SIP_INOUT );

/**
* QgsSymbolLayerReference uses QgsSymbolLayer unique uuid identifier since QGIS 3.30, instead of the symbol
* key (rule for QgsRuleBasedRenderer for instance) and index path, so this method migrates \a mapLayers old references
* to new ones.
* \since QGIS 3.30
*/
Q_DECL_DEPRECATED static void fixOldSymbolLayerReferences( const QMap<QString, QgsMapLayer *> &mapLayers );

///@cond PRIVATE
#ifndef SIP_RUN
static QgsProperty rotateWholeSymbol( double additionalRotation, const QgsProperty &property )
Expand Down
87 changes: 84 additions & 3 deletions tests/src/python/test_selective_masking.py
Expand Up @@ -145,7 +145,7 @@ def tearDownClass(cls):
cls.report != REPORT_TITLE):
QDesktopServices.openUrl(QUrl("file:///{}".format(report_file_path)))

def get_symbollayer_ref(self, layer, ruleId, symbollayer_ids):
def get_symbollayer(self, layer, ruleId, symbollayer_ids):
"""
Returns the symbol layer according to given layer, ruleId (None if no rule) and the path
to symbol layer id (for instance [0, 1])
Expand All @@ -164,6 +164,14 @@ def get_symbollayer_ref(self, layer, ruleId, symbollayer_ids):
symbol = symbollayer.subSymbol()
symbollayer = symbol.symbolLayer(symbollayer_ids[i])

return symbollayer

def get_symbollayer_ref(self, layer, ruleId, symbollayer_ids):
"""
Returns the symbol layer according to given layer, ruleId (None if no rule) and the path
to symbol layer id (for instance [0, 1])
"""
symbollayer = self.get_symbollayer(layer, rule, symbollayer_ids)
return QgsSymbolLayerReference(layer.id(), symbollayer.id())

def check_renderings(self, map_settings, control_name):
Expand Down Expand Up @@ -265,16 +273,22 @@ def test_save_restore_references(self):
# simple ids
mask_layer = QgsMaskMarkerSymbolLayer()
mask_layer.setMasks([
self.get_symbollayer_ref(self.lines_layer, "", [0]),
QgsSymbolLayerReference(self.lines_layer.id(), QgsSymbolLayerId("", [0])),
QgsSymbolLayerReference(self.lines_layer2.id(), QgsSymbolLayerId("some_id", [1, 3, 5, 19])),
QgsSymbolLayerReference(self.polys_layer.id(), QgsSymbolLayerId("some_other_id", [4, 5])),
])

props = mask_layer.properties()

mask_layer2 = QgsMaskMarkerSymbolLayer.create(props)
print(f"mask2={mask_layer2.masks()}")
print("mask={}".format([
QgsSymbolLayerReference(self.lines_layer.id(), QgsSymbolLayerId("", [0])),
QgsSymbolLayerReference(self.lines_layer2.id(), QgsSymbolLayerId("some_id", [1, 3, 5, 19])),
QgsSymbolLayerReference(self.polys_layer.id(), QgsSymbolLayerId("some_other_id", [4, 5])),
]))
self.assertEqual(mask_layer2.masks(), [
self.get_symbollayer_ref(self.lines_layer, "", [0]),
QgsSymbolLayerReference(self.lines_layer.id(), QgsSymbolLayerId("", [0])),
QgsSymbolLayerReference(self.lines_layer2.id(), QgsSymbolLayerId("some_id", [1, 3, 5, 19])),
QgsSymbolLayerReference(self.polys_layer.id(), QgsSymbolLayerId("some_other_id", [4, 5])),
])
Expand Down Expand Up @@ -313,6 +327,73 @@ def test_save_restore_references(self):
QgsSymbolLayerReference(self.polys_layer.id(), QgsSymbolLayerId("some other; id, lik;e, this", [4, 5])),
])

def test_migrate_old_references(self):
"""
Since QGIS 3.30, QgsSymbolLayerReference has change its definition, so we test we can migrate
old reference to new ones
"""

# test label mask
label_settings = self.polys_layer.labeling().settings()
fmt = label_settings.format()
# enable a mask
fmt.mask().setEnabled(True)
fmt.mask().setSize(4.0)
# and mask other symbol layers underneath
oldMaskRefs = [
# the black part of roads
QgsSymbolLayerReference(self.lines_layer2.id(), QgsSymbolLayerId("", [1, 0])),
# the black jets
QgsSymbolLayerReference(self.points_layer.id(), QgsSymbolLayerId("B52", [0])),
QgsSymbolLayerReference(self.points_layer.id(), QgsSymbolLayerId("Jet", [0]))]
fmt.mask().setMaskedSymbolLayers(oldMaskRefs)

label_settings.setFormat(fmt)
self.polys_layer.labeling().setSettings(label_settings)

self.assertEqual([slRef.symbolLayerIdV2() for slRef in self.polys_layer.labeling().settings().format().mask().maskedSymbolLayers()],
["", "", ""])
self.assertEqual([slRef.symbolLayerId() for slRef in self.polys_layer.labeling().settings().format().mask().maskedSymbolLayers()],
[slRef.symbolLayerId() for slRef in oldMaskRefs])

QgsSymbolLayerUtils.fixOldSymbolLayerReferences(QgsProject.instance().mapLayers())

self.assertEqual([QUuid(slRef.symbolLayerIdV2()).isNull() for slRef in self.polys_layer.labeling().settings().format().mask().maskedSymbolLayers()],
[False, False, False])
self.assertEqual([slRef.symbolLayerIdV2() for slRef in self.polys_layer.labeling().settings().format().mask().maskedSymbolLayers()],
[self.get_symbollayer(self.lines_layer2, "", [1, 0]).id(),
self.get_symbollayer(self.points_layer, "B52", [0]).id(),
self.get_symbollayer(self.points_layer, "Jet", [0]).id()])
self.assertEqual([slRef.symbolLayerId() for slRef in self.polys_layer.labeling().settings().format().mask().maskedSymbolLayers()],
[QgsSymbolLayerId(), QgsSymbolLayerId(), QgsSymbolLayerId()])

# test symbol layer masks
p = QgsMarkerSymbol.createSimple({'color': '#fdbf6f', 'size': "7"})
self.points_layer.setRenderer(QgsSingleSymbolRenderer(p))

circle_symbol = QgsMarkerSymbol.createSimple({'size': '10'})
mask_layer = QgsMaskMarkerSymbolLayer()
mask_layer.setSubSymbol(circle_symbol)
oldMaskRefs = [QgsSymbolLayerReference(self.lines_layer2.id(), QgsSymbolLayerId("", [1, 0]))]
mask_layer.setMasks(oldMaskRefs)

# add this mask layer to the point layer
self.points_layer.renderer().symbol().appendSymbolLayer(mask_layer)

self.assertEqual([slRef.symbolLayerIdV2() for slRef in self.points_layer.renderer().symbol().symbolLayers()[1].masks()],
[""])
self.assertEqual([slRef.symbolLayerId() for slRef in self.points_layer.renderer().symbol().symbolLayers()[1].masks()],
[slRef.symbolLayerId() for slRef in oldMaskRefs])

QgsSymbolLayerUtils.fixOldSymbolLayerReferences(QgsProject.instance().mapLayers())

self.assertEqual([QUuid(slRef.symbolLayerIdV2()).isNull() for slRef in self.points_layer.renderer().symbol().symbolLayers()[1].masks()],
[False])
self.assertEqual([slRef.symbolLayerIdV2() for slRef in self.points_layer.renderer().symbol().symbolLayers()[1].masks()],
[self.get_symbollayer(self.lines_layer2, "", [1, 0]).id()])
self.assertEqual([slRef.symbolLayerId() for slRef in self.points_layer.renderer().symbol().symbolLayers()[1].masks()],
[QgsSymbolLayerId()])

def test_label_mask(self):
# modify labeling settings
label_settings = self.polys_layer.labeling().settings()
Expand Down

0 comments on commit 388c6e3

Please sign in to comment.