diff --git a/python/core/layertree/qgslayertree.sip b/python/core/layertree/qgslayertree.sip index 6d269abc028..839591dbfe7 100644 --- a/python/core/layertree/qgslayertree.sip +++ b/python/core/layertree/qgslayertree.sip @@ -3,20 +3,22 @@ class QgsLayerTree : QgsLayerTreeGroup %TypeHeaderCode #include %End - static bool isGroup( QgsLayerTreeNode *node ); - static bool isLayer( const QgsLayerTreeNode *node ); - //! Cast node to a group. No type checking is done - use isGroup() to find out whether this operation is legal. - // PYTHON: automatic cast - //inline QgsLayerTreeGroup* toGroup( QgsLayerTreeNode* node ); + public: + static bool isGroup( QgsLayerTreeNode *node ); + static bool isLayer( const QgsLayerTreeNode *node ); - //! Cast node to a layer. No type checking is done - use isLayer() to find out whether this operation is legal. - // PYTHON: automatic cast - //inline QgsLayerTreeLayer* toLayer( QgsLayerTreeNode* node ); + //! Cast node to a group. No type checking is done - use isGroup() to find out whether this operation is legal. + // PYTHON: automatic cast + //inline QgsLayerTreeGroup* toGroup( QgsLayerTreeNode* node ); + + //! Cast node to a layer. No type checking is done - use isLayer() to find out whether this operation is legal. + // PYTHON: automatic cast + //inline QgsLayerTreeLayer* toLayer( QgsLayerTreeNode* node ); QList customLayerOrder() const; void setCustomLayerOrder( const QList &customLayerOrder ); - void setCustomLayerOrder( const QStringList &customLayerOrder ); + void setCustomLayerOrder( const QStringList &customLayerOrder ) /PyName=setCustomLayerOrderByIds/; QList layerOrder() const; bool hasCustomLayerOrder() const; void setHasCustomLayerOrder( bool hasCustomLayerOrder ); diff --git a/python/core/layertree/qgslayertreenode.sip b/python/core/layertree/qgslayertreenode.sip index b8dd206f478..a21efb21be4 100644 --- a/python/core/layertree/qgslayertreenode.sip +++ b/python/core/layertree/qgslayertreenode.sip @@ -51,6 +51,8 @@ class QgsLayerTreeNode : QObject QgsLayerTreeNode* node = qobject_cast(sipCpp); if (QgsLayerTree::isLayer(node)) sipType = sipType_QgsLayerTreeLayer; + else if (qobject_cast(sipCpp)) + sipType = sipType_QgsLayerTree; else if (QgsLayerTree::isGroup(node)) sipType = sipType_QgsLayerTreeGroup; } diff --git a/src/core/composer/qgscomposerlegend.cpp b/src/core/composer/qgscomposerlegend.cpp index 060fb64a3fd..400191c26c8 100644 --- a/src/core/composer/qgscomposerlegend.cpp +++ b/src/core/composer/qgscomposerlegend.cpp @@ -418,46 +418,6 @@ bool QgsComposerLegend::writeXml( QDomElement &elem, QDomDocument &doc ) const return _writeXml( composerLegendElem, doc ); } -static void _readOldLegendGroup( QDomElement &elem, QgsLayerTreeGroup *parentGroup, QgsProject *project ) -{ - QDomElement itemElem = elem.firstChildElement(); - - while ( !itemElem.isNull() ) - { - - if ( itemElem.tagName() == QLatin1String( "LayerItem" ) ) - { - QString layerId = itemElem.attribute( QStringLiteral( "layerId" ) ); - if ( QgsMapLayer *layer = project->mapLayer( layerId ) ) - { - QgsLayerTreeLayer *nodeLayer = parentGroup->addLayer( layer ); - QString userText = itemElem.attribute( QStringLiteral( "userText" ) ); - if ( !userText.isEmpty() ) - nodeLayer->setCustomProperty( QStringLiteral( "legend/title-label" ), userText ); - QString style = itemElem.attribute( QStringLiteral( "style" ) ); - if ( !style.isEmpty() ) - nodeLayer->setCustomProperty( QStringLiteral( "legend/title-style" ), style ); - QString showFeatureCount = itemElem.attribute( QStringLiteral( "showFeatureCount" ) ); - if ( showFeatureCount.toInt() ) - nodeLayer->setCustomProperty( QStringLiteral( "showFeatureCount" ), 1 ); - - // support for individual legend items (user text, order) not implemented yet - } - } - else if ( itemElem.tagName() == QLatin1String( "GroupItem" ) ) - { - QgsLayerTreeGroup *nodeGroup = parentGroup->addGroup( itemElem.attribute( QStringLiteral( "userText" ) ) ); - QString style = itemElem.attribute( QStringLiteral( "style" ) ); - if ( !style.isEmpty() ) - nodeGroup->setCustomProperty( QStringLiteral( "legend/title-style" ), style ); - - _readOldLegendGroup( itemElem, nodeGroup, project ); - } - - itemElem = itemElem.nextSiblingElement(); - } -} - bool QgsComposerLegend::readXml( const QDomElement &itemElem, const QDomDocument &doc ) { if ( itemElem.isNull() ) diff --git a/src/core/layertree/qgslayertree.h b/src/core/layertree/qgslayertree.h index 586b2fd6e9f..8da1d81e377 100644 --- a/src/core/layertree/qgslayertree.h +++ b/src/core/layertree/qgslayertree.h @@ -58,6 +58,7 @@ class CORE_EXPORT QgsLayerTree : public QgsLayerTreeGroup * Cast node to a group. No type checking is done - use isGroup() to find out whether this operation is legal. * * @note Added in QGIS 2.4 + * @note Not available in python bindings, because cast is automatic. */ static inline QgsLayerTreeGroup *toGroup( QgsLayerTreeNode *node ) { @@ -68,6 +69,7 @@ class CORE_EXPORT QgsLayerTree : public QgsLayerTreeGroup * Cast node to a layer. No type checking is done - use isLayer() to find out whether this operation is legal. * * @note Added in QGIS 2.4 + * @note Not available in python bindings, because cast is automatic. */ static inline QgsLayerTreeLayer *toLayer( QgsLayerTreeNode *node ) { @@ -78,14 +80,21 @@ class CORE_EXPORT QgsLayerTree : public QgsLayerTreeGroup * Cast node to a layer. No type checking is done - use isLayer() to find out whether this operation is legal. * * @note Added in QGIS 2.4 + * @note Not available in python bindings, because cast is automatic. */ static inline const QgsLayerTreeLayer *toLayer( const QgsLayerTreeNode *node ) { return static_cast< const QgsLayerTreeLayer *>( node ); } - //! Constructor + /** + * Create a new empty layer tree + */ QgsLayerTree(); + + /** + * Copy constructor + */ QgsLayerTree( const QgsLayerTree &other ); /** diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index d7defc25cad..0bc2a3ae9d0 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -64,6 +64,7 @@ ADD_PYTHON_TEST(PyQgsGraduatedSymbolRenderer test_qgsgraduatedsymbolrenderer.py) ADD_PYTHON_TEST(PyQgsInterval test_qgsinterval.py) ADD_PYTHON_TEST(PyQgsJSONUtils test_qgsjsonutils.py) ADD_PYTHON_TEST(PyQgsLayerTreeMapCanvasBridge test_qgslayertreemapcanvasbridge.py) +ADD_PYTHON_TEST(PyQgsLayerTree test_qgslayertree.py) ADD_PYTHON_TEST(PyQgsLayoutManager test_qgslayoutmanager.py) ADD_PYTHON_TEST(PyQgsLineSymbolLayers test_qgslinesymbollayers.py) ADD_PYTHON_TEST(PyQgsMapCanvas test_qgsmapcanvas.py) diff --git a/tests/src/python/test_qgslayertree.py b/tests/src/python/test_qgslayertree.py new file mode 100644 index 00000000000..00d07fc9cc4 --- /dev/null +++ b/tests/src/python/test_qgslayertree.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsLayerTree. + +.. 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__ = 'Matthias Kuhn' +__date__ = '22.3.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 + +import os + +from qgis.core import ( + QgsLayerTree, + QgsProject, + QgsVectorLayer +) +from qgis.testing import start_app, unittest +from utilities import (unitTestDataPath) +from qgis.PyQt.QtTest import QSignalSpy +from qgis.PyQt.QtCore import QDir + +app = start_app() +TEST_DATA_DIR = unitTestDataPath() + + +class TestQgsLayerTree(unittest.TestCase): + + def __init__(self, methodName): + """Run once on class initialization.""" + unittest.TestCase.__init__(self, methodName) + + def testCustomLayerOrder(self): + """ test project layer order""" + prj = QgsProject() + layer = QgsVectorLayer("Point?field=fldtxt:string", + "layer1", "memory") + layer2 = QgsVectorLayer("Point?field=fldtxt:string", + "layer2", "memory") + layer3 = QgsVectorLayer("Point?field=fldtxt:string", + "layer3", "memory") + prj.addMapLayers([layer, layer2, layer3]) + + layer_order_changed_spy = QSignalSpy(prj.layerTreeRoot().customLayerOrderChanged) + prj.layerTreeRoot().setCustomLayerOrder([layer2, layer]) + self.assertEqual(len(layer_order_changed_spy), 1) + prj.layerTreeRoot().setCustomLayerOrder([layer2, layer]) + self.assertEqual(len(layer_order_changed_spy), 1) # no signal, order not changed + + self.assertEqual(prj.layerTreeRoot().customLayerOrder(), [layer2, layer]) + prj.layerTreeRoot().setCustomLayerOrder([layer]) + self.assertEqual(prj.layerTreeRoot().customLayerOrder(), [layer]) + self.assertEqual(len(layer_order_changed_spy), 2) + + # remove a layer + prj.layerTreeRoot().setCustomLayerOrder([layer2, layer, layer3]) + self.assertEqual(len(layer_order_changed_spy), 3) + prj.removeMapLayer(layer) + self.assertEqual(prj.layerTreeRoot().customLayerOrder(), [layer2, layer3]) + self.assertEqual(len(layer_order_changed_spy), 4) + + # save and restore + file_name = os.path.join(QDir.tempPath(), 'proj.qgs') + prj.setFileName(file_name) + prj.write() + prj2 = QgsProject() + prj2.setFileName(file_name) + prj2.read() + self.assertEqual([l.id() for l in prj2.layerTreeRoot().customLayerOrder()], [layer2.id(), layer3.id()]) + + # clear project + prj.clear() + self.assertEqual(prj.layerTreeRoot().customLayerOrder(), []) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/src/python/test_qgslayertreemapcanvasbridge.py b/tests/src/python/test_qgslayertreemapcanvasbridge.py index 71fa0019834..3397656e995 100644 --- a/tests/src/python/test_qgslayertreemapcanvasbridge.py +++ b/tests/src/python/test_qgslayertreemapcanvasbridge.py @@ -54,32 +54,34 @@ class TestQgsLayerTreeMapCanvasBridge(unittest.TestCase): bridge = QgsLayerTreeMapCanvasBridge(prj.layerTreeRoot(), canvas) #custom layer order - bridge.setHasCustomLayerOrder(True) - bridge.setCustomLayerOrder([layer3.id(), layer.id(), layer2.id()]) + prj.layerTreeRoot().setHasCustomLayerOrder(True) + prj.layerTreeRoot().setCustomLayerOrder([layer3, layer, layer2]) app.processEvents() - self.assertEqual([l.id() for l in prj.layerOrder()], [layer3.id(), layer.id(), layer2.id()]) + self.assertEqual([l for l in prj.layerTreeRoot().customLayerOrder()], [layer3, layer, layer2]) + self.assertEqual([l for l in prj.layerTreeRoot().layerOrder()], [layer3, layer, layer2]) # no custom layer order - bridge.setHasCustomLayerOrder(False) + prj.layerTreeRoot().setHasCustomLayerOrder(False) app.processEvents() - self.assertEqual([l.id() for l in prj.layerOrder()], [layer.id(), layer2.id(), layer3.id()]) + self.assertEqual([l for l in prj.layerTreeRoot().customLayerOrder()], [layer3, layer, layer2]) + self.assertEqual([l for l in prj.layerTreeRoot().layerOrder()], [layer, layer2, layer3]) # mess around with the layer tree order root = prj.layerTreeRoot() - layer_node = root.findLayer(layer2.id()) + layer_node = root.findLayer(layer2) cloned_node = layer_node.clone() parent = layer_node.parent() parent.insertChildNode(0, cloned_node) parent.removeChildNode(layer_node) app.processEvents() # make sure project respects this - self.assertEqual([l.id() for l in prj.layerOrder()], [layer2.id(), layer.id(), layer3.id()]) + self.assertEqual([l for l in prj.layerTreeRoot().layerOrder()], [layer2, layer, layer3]) # make sure project order includes ALL layers, not just visible ones - layer_node = root.findLayer(layer.id()) + layer_node = root.findLayer(layer) layer_node.setItemVisibilityChecked(False) app.processEvents() - self.assertEqual([l.id() for l in prj.layerOrder()], [layer2.id(), layer.id(), layer3.id()]) + self.assertEqual([l for l in prj.layerTreeRoot().layerOrder()], [layer2, layer, layer3]) def testCustomLayerOrderUpdatedFromProject(self): """ test that setting project layer order is reflected in custom layer order panel """ @@ -99,37 +101,35 @@ class TestQgsLayerTreeMapCanvasBridge(unittest.TestCase): custom_order_widget = QgsCustomLayerOrderWidget(bridge) #custom layer order - bridge.setHasCustomLayerOrder(True) - bridge.setCustomLayerOrder([layer3.id(), layer.id(), layer2.id()]) + prj.layerTreeRoot().setHasCustomLayerOrder(True) + prj.layerTreeRoot().setCustomLayerOrder([layer3, layer, layer2]) app.processEvents() - self.assertEqual([l.id() for l in prj.layerOrder()], [layer3.id(), layer.id(), layer2.id()]) + self.assertEqual([l for l in prj.layerTreeRoot().customLayerOrder()], [layer3, layer, layer2]) # no custom layer order - bridge.setHasCustomLayerOrder(False) + prj.layerTreeRoot().setHasCustomLayerOrder(False) app.processEvents() - self.assertEqual([l.id() for l in prj.layerOrder()], [layer.id(), layer2.id(), layer3.id()]) + self.assertEqual([l for l in prj.layerTreeRoot().layerOrder()], [layer, layer2, layer3]) # mess around with the project layer order - prj.setLayerOrder([layer3, layer, layer2]) + prj.layerTreeRoot().setCustomLayerOrder([layer3, layer, layer2]) app.processEvents() - # make sure bridge respects this new order - self.assertTrue(bridge.hasCustomLayerOrder()) - self.assertEqual(bridge.customLayerOrder(), [layer3.id(), layer.id(), layer2.id()]) + self.assertEqual(prj.layerTreeRoot().layerOrder(), [layer, layer2, layer3]) # try reordering through bridge - bridge.setHasCustomLayerOrder(False) + prj.layerTreeRoot().setHasCustomLayerOrder(False) app.processEvents() - self.assertEqual([l.id() for l in prj.layerOrder()], [layer.id(), layer2.id(), layer3.id()]) + self.assertEqual([l for l in prj.layerTreeRoot().layerOrder()], [layer, layer2, layer3]) root = prj.layerTreeRoot() - layer_node = root.findLayer(layer2.id()) + layer_node = root.findLayer(layer2) cloned_node = layer_node.clone() parent = layer_node.parent() parent.insertChildNode(0, cloned_node) parent.removeChildNode(layer_node) app.processEvents() # make sure project respects this - self.assertEqual([l.id() for l in prj.layerOrder()], [layer2.id(), layer.id(), layer3.id()]) - self.assertFalse(bridge.hasCustomLayerOrder()) + self.assertEqual([l for l in prj.layerTreeRoot().layerOrder()], [layer2, layer, layer3]) + self.assertFalse(prj.layerTreeRoot().hasCustomLayerOrder()) if __name__ == '__main__': diff --git a/tests/src/python/test_qgsmapthemecollection.py b/tests/src/python/test_qgsmapthemecollection.py index 477b94ddefb..f57a939aff0 100644 --- a/tests/src/python/test_qgsmapthemecollection.py +++ b/tests/src/python/test_qgsmapthemecollection.py @@ -111,10 +111,11 @@ class TestQgsMapThemeCollection(unittest.TestCase): "layer3", "memory") prj.addMapLayers([layer, layer2, layer3]) - prj.setLayerOrder([layer2, layer]) + prj.layerTreeRoot().setHasCustomLayerOrder(True) + prj.layerTreeRoot().setCustomLayerOrder([layer2, layer]) self.assertEqual(prj.mapThemeCollection().masterLayerOrder(), [layer2, layer]) - prj.setLayerOrder([layer, layer2, layer3]) + prj.layerTreeRoot().setCustomLayerOrder([layer, layer2, layer3]) # make some themes... theme1 = QgsMapThemeCollection.MapThemeRecord() theme1.setLayerRecords([QgsMapThemeCollection.MapThemeLayerRecord(layer3), @@ -144,7 +145,7 @@ class TestQgsMapThemeCollection(unittest.TestCase): self.assertEqual(prj.mapThemeCollection().mapThemeVisibleLayerIds('theme3'), [layer.id(), layer2.id()]) # reset master order - prj.setLayerOrder([layer2, layer3, layer]) + prj.layerTreeRoot().setCustomLayerOrder([layer2, layer3, layer]) self.assertEqual(prj.mapThemeCollection().mapThemeVisibleLayers('theme1'), [layer3, layer]) self.assertEqual(prj.mapThemeCollection().mapThemeVisibleLayers('theme2'), [layer2, layer3, layer]) self.assertEqual(prj.mapThemeCollection().mapThemeVisibleLayers('theme3'), [layer2, layer]) @@ -159,6 +160,7 @@ class TestQgsMapThemeCollection(unittest.TestCase): layer_node = root.findLayer(layer2.id()) layer_node.setItemVisibilityChecked(False) app.processEvents() + prj.layerTreeRoot().setHasCustomLayerOrder(False) self.assertEqual(prj.mapThemeCollection().masterLayerOrder(), [layer, layer2, layer3]) self.assertEqual(prj.mapThemeCollection().mapThemeVisibleLayers('theme1'), [layer, layer3]) @@ -169,12 +171,6 @@ class TestQgsMapThemeCollection(unittest.TestCase): [layer.id(), layer2.id(), layer3.id()]) self.assertEqual(prj.mapThemeCollection().mapThemeVisibleLayerIds('theme3'), [layer.id(), layer2.id()]) - # no layer order - should use stored order as a last resort - prj.setLayerOrder([]) - self.assertEqual(prj.mapThemeCollection().mapThemeVisibleLayers('theme1'), [layer3, layer]) - self.assertEqual(prj.mapThemeCollection().mapThemeVisibleLayers('theme2'), [layer3, layer2, layer]) - self.assertEqual(prj.mapThemeCollection().mapThemeVisibleLayers('theme3'), [layer2, layer]) - def testMasterVisibleLayers(self): """ test master visible layers""" prj = QgsProject.instance() @@ -188,9 +184,10 @@ class TestQgsMapThemeCollection(unittest.TestCase): prj.addMapLayers([layer, layer2, layer3]) # general setup... - prj.setLayerOrder([layer2, layer]) + prj.layerTreeRoot().setHasCustomLayerOrder(True) + prj.layerTreeRoot().setCustomLayerOrder([layer2, layer]) self.assertEqual(prj.mapThemeCollection().masterVisibleLayers(), [layer2, layer]) - prj.setLayerOrder([layer3, layer, layer2]) + prj.layerTreeRoot().setCustomLayerOrder([layer3, layer, layer2]) self.assertEqual(prj.mapThemeCollection().masterVisibleLayers(), [layer3, layer, layer2]) #hide some layers @@ -201,11 +198,11 @@ class TestQgsMapThemeCollection(unittest.TestCase): layer_node.setItemVisibilityChecked(True) self.assertEqual(prj.mapThemeCollection().masterVisibleLayers(), [layer3, layer, layer2]) layer_node.setItemVisibilityChecked(False) - prj.setLayerOrder([layer, layer2, layer3]) + prj.layerTreeRoot().setCustomLayerOrder([layer, layer2, layer3]) self.assertEqual(prj.mapThemeCollection().masterVisibleLayers(), [layer, layer3]) # test with no project layer order set, should respect tree order - prj.setLayerOrder([]) + prj.layerTreeRoot().setCustomLayerOrder([]) self.assertEqual(prj.mapThemeCollection().masterVisibleLayers(), [layer, layer3]) layer_node.setItemVisibilityChecked(True) self.assertEqual(prj.mapThemeCollection().masterVisibleLayers(), [layer, layer2, layer3]) diff --git a/tests/src/python/test_qgsproject.py b/tests/src/python/test_qgsproject.py index dcbe13030fd..26f4fd16a83 100644 --- a/tests/src/python/test_qgsproject.py +++ b/tests/src/python/test_qgsproject.py @@ -27,8 +27,6 @@ from qgis.gui import (QgsLayerTreeMapCanvasBridge, QgsMapCanvas) from qgis.testing import start_app, unittest from utilities import (unitTestDataPath) -from qgis.PyQt.QtCore import QDir -from qgis.PyQt.QtTest import QSignalSpy app = start_app() TEST_DATA_DIR = unitTestDataPath() @@ -183,48 +181,6 @@ class TestQgsProject(unittest.TestCase): expected = ['polys', 'lines'] self.assertEqual(sorted(layers_names), sorted(expected)) - def testLayerOrder(self): - """ test project layer order""" - prj = QgsProject() - layer = QgsVectorLayer("Point?field=fldtxt:string", - "layer1", "memory") - layer2 = QgsVectorLayer("Point?field=fldtxt:string", - "layer2", "memory") - layer3 = QgsVectorLayer("Point?field=fldtxt:string", - "layer3", "memory") - prj.addMapLayers([layer, layer2, layer3]) - - layer_order_changed_spy = QSignalSpy(prj.layerOrderChanged) - prj.setLayerOrder([layer2, layer]) - self.assertEqual(len(layer_order_changed_spy), 1) - prj.setLayerOrder([layer2, layer]) - self.assertEqual(len(layer_order_changed_spy), 1) # no signal, order not changed - - self.assertEqual(prj.layerOrder(), [layer2, layer]) - prj.setLayerOrder([layer]) - self.assertEqual(prj.layerOrder(), [layer]) - self.assertEqual(len(layer_order_changed_spy), 2) - - # remove a layer - prj.setLayerOrder([layer2, layer, layer3]) - self.assertEqual(len(layer_order_changed_spy), 3) - prj.removeMapLayer(layer) - self.assertEqual(prj.layerOrder(), [layer2, layer3]) - self.assertEqual(len(layer_order_changed_spy), 3) # should be no signal - - # save and restore - file_name = os.path.join(str(QDir.tempPath()), 'proj.qgs') - prj.setFileName(file_name) - prj.write() - prj2 = QgsProject() - prj2.setFileName(file_name) - prj2.read() - self.assertEqual([l.id() for l in prj2.layerOrder()], [layer2.id(), layer3.id()]) - - # clear project - prj.clear() - self.assertEqual(prj.layerOrder(), []) - if __name__ == '__main__': unittest.main()