fix project migration of old reference to new ones

This commit is contained in:
Julien Cabieces 2022-11-03 11:51:29 +01:00
parent 1bc1dd3de1
commit 388c6e3ee6
6 changed files with 202 additions and 8 deletions

View File

@ -978,7 +978,7 @@ Encodes a reference to a parametric SVG into a path with parameters according to
.. versionadded:: 3.0 .. versionadded:: 3.0
%End %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 %Docstring
Converts a set of symbol layer id to a set of pointers to actual symbol layers carried by the feature renderer. Converts a set of symbol layer id to a set of pointers to actual symbol layers carried by the feature renderer.
@ -1030,6 +1030,15 @@ The method makes approximations and can modify ``angle`` in order to generate th
:return: the size of the tile :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 .. versionadded:: 3.30
%End %End

View File

@ -1946,6 +1946,14 @@ bool QgsProject::readProjectFile( const QString &filename, Qgis::ProjectReadFlag
profile.switchTask( tr( "Resolving references" ) ); profile.switchTask( tr( "Resolving references" ) );
mRootGroup->resolveReferences( this ); 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() ) if ( !layerTreeElem.isNull() )
{ {
mRootGroup->readLayerOrderFromXml( layerTreeElem ); mRootGroup->readLayerOrderFromXml( layerTreeElem );

View File

@ -40,7 +40,7 @@ QgsSymbolLayerReferenceList stringToSymbolLayerReferenceList( const QString &str
// TODO QGIS 4 : remove this if branch, keep only else part // TODO QGIS 4 : remove this if branch, keep only else part
Q_NOWARN_DEPRECATED_PUSH 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 // we load it the old fashion way and we will update the new one later when
// the whole project is loaded // the whole project is loaded

View File

@ -43,6 +43,7 @@
#include "qgssymbollayerreference.h" #include "qgssymbollayerreference.h"
#include "qgsmarkersymbollayer.h" #include "qgsmarkersymbollayer.h"
#include "qmath.h" #include "qmath.h"
#include "qgsmasksymbollayer.h"
#include <QColor> #include <QColor>
#include <QFont> #include <QFont>
@ -4911,7 +4912,7 @@ double QgsSymbolLayerUtils::sizeInPixelsFromSldUom( const QString &uom, double s
return size * scale; 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 class SymbolLayerVisitor : public QgsStyleEntityVisitorInterface
{ {
@ -5355,5 +5356,92 @@ QSize QgsSymbolLayerUtils::tileSize( int width, int height, double &angleRad )
} }
return tileSize; 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() ) );
}
}
} }

View File

@ -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. * 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 * \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. * Calculates the frame rate (in frames per second) at which the given \a renderer must be redrawn.
@ -928,6 +928,14 @@ class CORE_EXPORT QgsSymbolLayerUtils
*/ */
static QSize tileSize( int width, int height, double &angleRad SIP_INOUT ); 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 ///@cond PRIVATE
#ifndef SIP_RUN #ifndef SIP_RUN
static QgsProperty rotateWholeSymbol( double additionalRotation, const QgsProperty &property ) static QgsProperty rotateWholeSymbol( double additionalRotation, const QgsProperty &property )

View File

@ -145,7 +145,7 @@ class TestSelectiveMasking(unittest.TestCase):
cls.report != REPORT_TITLE): cls.report != REPORT_TITLE):
QDesktopServices.openUrl(QUrl("file:///{}".format(report_file_path))) 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 Returns the symbol layer according to given layer, ruleId (None if no rule) and the path
to symbol layer id (for instance [0, 1]) to symbol layer id (for instance [0, 1])
@ -164,6 +164,14 @@ class TestSelectiveMasking(unittest.TestCase):
symbol = symbollayer.subSymbol() symbol = symbollayer.subSymbol()
symbollayer = symbol.symbolLayer(symbollayer_ids[i]) 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()) return QgsSymbolLayerReference(layer.id(), symbollayer.id())
def check_renderings(self, map_settings, control_name): def check_renderings(self, map_settings, control_name):
@ -265,7 +273,7 @@ class TestSelectiveMasking(unittest.TestCase):
# simple ids # simple ids
mask_layer = QgsMaskMarkerSymbolLayer() mask_layer = QgsMaskMarkerSymbolLayer()
mask_layer.setMasks([ 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.lines_layer2.id(), QgsSymbolLayerId("some_id", [1, 3, 5, 19])),
QgsSymbolLayerReference(self.polys_layer.id(), QgsSymbolLayerId("some_other_id", [4, 5])), QgsSymbolLayerReference(self.polys_layer.id(), QgsSymbolLayerId("some_other_id", [4, 5])),
]) ])
@ -273,8 +281,14 @@ class TestSelectiveMasking(unittest.TestCase):
props = mask_layer.properties() props = mask_layer.properties()
mask_layer2 = QgsMaskMarkerSymbolLayer.create(props) 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.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.lines_layer2.id(), QgsSymbolLayerId("some_id", [1, 3, 5, 19])),
QgsSymbolLayerReference(self.polys_layer.id(), QgsSymbolLayerId("some_other_id", [4, 5])), QgsSymbolLayerReference(self.polys_layer.id(), QgsSymbolLayerId("some_other_id", [4, 5])),
]) ])
@ -313,6 +327,73 @@ class TestSelectiveMasking(unittest.TestCase):
QgsSymbolLayerReference(self.polys_layer.id(), QgsSymbolLayerId("some other; id, lik;e, this", [4, 5])), 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): def test_label_mask(self):
# modify labeling settings # modify labeling settings
label_settings = self.polys_layer.labeling().settings() label_settings = self.polys_layer.labeling().settings()