Skip to content

Commit

Permalink
Allow retrieval of project layer order through QgsProject
Browse files Browse the repository at this point in the history
Previously this was only accessible through app
  • Loading branch information
nyalldawson committed Mar 13, 2017
1 parent 9842fcb commit 6cfc6a1
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 7 deletions.
11 changes: 7 additions & 4 deletions python/core/qgsproject.sip
Expand Up @@ -487,6 +487,9 @@ class QgsProject : QObject, QgsExpressionContextGenerator
*/
void reloadAllLayers();

QList< QgsMapLayer * > layerOrder() const;
void setLayerOrder( const QList< QgsMapLayer * > &order );

signals:
//! emitted when project is being read
void readProject( const QDomDocument& );
Expand Down Expand Up @@ -623,12 +626,12 @@ class QgsProject : QObject, QgsExpressionContextGenerator
*/
//TODO QGIS 3.0 - rename to past tense
void removeAll();
void layersAdded( const QList<QgsMapLayer *> &layers );

void layersAdded( const QList<QgsMapLayer *>& layers );

void layerWasAdded( QgsMapLayer* layer );
void layerWasAdded( QgsMapLayer *layer );
void legendLayersAdded( const QList<QgsMapLayer *> &layers );

void legendLayersAdded( const QList<QgsMapLayer*>& layers );
void layerOrderChanged();

public slots:
/**
Expand Down
45 changes: 45 additions & 0 deletions src/core/qgsproject.cpp
Expand Up @@ -44,6 +44,7 @@
#include "qgsvectordataprovider.h"
#include "qgsprojectbadlayerhandler.h"
#include "qgssettings.h"
#include "qgsmaplayerlistutils.h"

#include <QApplication>
#include <QFileInfo>
Expand Down Expand Up @@ -460,6 +461,7 @@ void QgsProject::clear()
mEvaluateDefaultValues = false;
mDirty = false;
mCustomVariables.clear();
mLayerOrder.clear();

mEmbeddedLayers.clear();
mRelationManager->clear();
Expand Down Expand Up @@ -881,6 +883,20 @@ bool QgsProject::read()
// load embedded groups and layers
loadEmbeddedNodes( mRootGroup );

// load layer order
QList< QgsMapLayer * > layerOrder;
QDomNodeList layerOrderNodes = doc->elementsByTagName( QStringLiteral( "layerorder" ) );
if ( layerOrderNodes.count() )
{
QDomElement layerOrderElem = layerOrderNodes.at( 0 ).toElement();
for ( int i = 0; i < layerOrderElem.childNodes().count(); ++i )
{
QDomElement layerElem = layerOrderElem.childNodes().at( i ).toElement();
layerOrder << mMapLayers.value( layerElem.attribute( QStringLiteral( "id" ) ) );
}
}
setLayerOrder( layerOrder );

// now that layers are loaded, we can resolve layer tree's references to the layers
mRootGroup->resolveReferences( this );

Expand Down Expand Up @@ -1244,6 +1260,16 @@ bool QgsProject::write()

qgisNode.appendChild( projectLayersNode );

QDomElement layerOrderNode = doc->createElement( QStringLiteral( "layerorder" ) );
Q_FOREACH ( QgsMapLayer *layer, layerOrder() )
{
QDomElement mapLayerElem = doc->createElement( QStringLiteral( "layer" ) );
mapLayerElem.setAttribute( QStringLiteral( "id" ), layer->id() );
layerOrderNode.appendChild( mapLayerElem );
}
qgisNode.appendChild( layerOrderNode );


// now add the optional extra properties

dump_( mProperties );
Expand Down Expand Up @@ -2091,13 +2117,16 @@ void QgsProject::removeMapLayers( const QList<QgsMapLayer *> &layers )
QStringList layerIds;
QList<QgsMapLayer *> layerList;

bool layerOrderHasChanged = false;
QList< QgsMapLayer * > currentOrder = layerOrder();
Q_FOREACH ( QgsMapLayer *layer, layers )
{
// check layer and the registry contains it
if ( layer && mMapLayers.contains( layer->id() ) )
{
layerIds << layer->id();
layerList << layer;
layerOrderHasChanged = layerOrderHasChanged || currentOrder.contains( layer );
}
}

Expand All @@ -2121,6 +2150,8 @@ void QgsProject::removeMapLayers( const QList<QgsMapLayer *> &layers )
}

emit layersRemoved( layerIds );
if ( layerOrderHasChanged )
emit layerOrderChanged();
}

void QgsProject::removeMapLayer( const QString &layerId )
Expand Down Expand Up @@ -2151,6 +2182,20 @@ void QgsProject::reloadAllLayers()
}
}

QList<QgsMapLayer *> QgsProject::layerOrder() const
{
return _qgis_listQPointerToRaw( mLayerOrder );
}

void QgsProject::setLayerOrder( const QList<QgsMapLayer *> &order )
{
if ( order == layerOrder() )
return;

mLayerOrder = _qgis_listRawToQPointer( order );
emit layerOrderChanged();
}

void QgsProject::onMapLayerDeleted( QObject *obj )
{
QString id = mMapLayers.key( static_cast<QgsMapLayer *>( obj ) );
Expand Down
26 changes: 26 additions & 0 deletions src/core/qgsproject.h
Expand Up @@ -38,6 +38,7 @@
#include "qgsexpressioncontextgenerator.h"
#include "qgscoordinatereferencesystem.h"
#include "qgsprojectproperty.h"
#include "qgsmaplayer.h"

class QFileInfo;
class QDomDocument;
Expand Down Expand Up @@ -690,6 +691,22 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
*/
void reloadAllLayers();

/**
* Returns an ordered list of layers. This list reflects the order of layers as
* drawn in the main map canvas for the project.
* @note added in QGIS 3.0
* @see setLayerOrder()
*/
QList< QgsMapLayer * > layerOrder() const;

/**
* Sets the \a order for layers in the project. This list reflects the order of layers shown in
* the layer tree for the project.
* @note added in QGIS 3.0
* @see layerOrder()
*/
void setLayerOrder( const QList< QgsMapLayer * > &order );

signals:
//! emitted when project is being read
void readProject( const QDomDocument & );
Expand Down Expand Up @@ -892,6 +909,13 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
*/
void legendLayersAdded( const QList<QgsMapLayer *> &layers );

/**
* Emitted when the order of layers in the project is changed.
* @note added in QGIS 3.0
* @see setLayerOrder()
*/
void layerOrderChanged();

public slots:

/**
Expand Down Expand Up @@ -951,6 +975,8 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera

QMap<QString, QgsMapLayer *> mMapLayers;

QgsWeakMapLayerPointerList mLayerOrder;

QString mErrorMessage;

QgsProjectBadLayerHandler *mBadLayerHandler = nullptr;
Expand Down
1 change: 1 addition & 0 deletions src/gui/layertree/qgslayertreemapcanvasbridge.cpp
Expand Up @@ -140,6 +140,7 @@ void QgsLayerTreeMapCanvasBridge::setCanvasLayers()
int currentLayerCount = layerNodes.count();
bool firstLayers = mAutoSetupOnFirstLayer && mLastLayerCount == 0 && currentLayerCount != 0;

QgsProject::instance()->setLayerOrder( canvasLayers );
mCanvas->setLayers( canvasLayers );
if ( mOverviewCanvas )
mOverviewCanvas->setLayers( overviewLayers );
Expand Down
92 changes: 89 additions & 3 deletions tests/src/python/test_qgsproject.py
Expand Up @@ -18,12 +18,19 @@

import qgis # NOQA

from qgis.core import QgsProject, QgsApplication, QgsUnitTypes, QgsCoordinateReferenceSystem

from qgis.core import (QgsProject,
QgsApplication,
QgsUnitTypes,
QgsCoordinateReferenceSystem,
QgsVectorLayer)
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

start_app()
app = start_app()
TEST_DATA_DIR = unitTestDataPath()


Expand Down Expand Up @@ -176,6 +183,85 @@ def testEmbeddedGroup(self):
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), 4)

# 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(), [])

def testLayerOrderUpdatedThroughBridge(self):
""" test that project layer order is updated when layer tree changes """

prj = QgsProject.instance()
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])

canvas = QgsMapCanvas()
bridge = QgsLayerTreeMapCanvasBridge(prj.layerTreeRoot(), canvas)

#custom layer order
bridge.setHasCustomLayerOrder(True)
bridge.setCustomLayerOrder([layer3.id(), layer.id(), layer2.id()])
app.processEvents()
self.assertEqual([l.id() for l in prj.layerOrder()], [layer3.id(), layer.id(), layer2.id()])

# no custom layer order
bridge.setHasCustomLayerOrder(False)
app.processEvents()
self.assertEqual([l.id() for l in prj.layerOrder()], [layer.id(), layer2.id(), layer3.id()])

# mess around with the layer tree order
root = prj.layerTreeRoot()
layer_node = root.findLayer(layer2.id())
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()])


if __name__ == '__main__':
unittest.main()

0 comments on commit 6cfc6a1

Please sign in to comment.