Skip to content

Commit

Permalink
Serialize atlas settings
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Jan 5, 2018
1 parent 83af352 commit f86c298
Show file tree
Hide file tree
Showing 10 changed files with 220 additions and 10 deletions.
4 changes: 2 additions & 2 deletions python/core/layout/qgslayout.sip
Expand Up @@ -500,14 +500,14 @@ If ``ok`` is specified, it will be set to true if the load was successful.
Returns a list of loaded items.
%End

QDomElement writeXml( QDomDocument &document, const QgsReadWriteContext &context ) const;
virtual QDomElement writeXml( QDomDocument &document, const QgsReadWriteContext &context ) const;
%Docstring
Returns the layout's state encapsulated in a DOM element.

.. seealso:: :py:func:`readXml()`
%End

bool readXml( const QDomElement &layoutElement, const QDomDocument &document, const QgsReadWriteContext &context );
virtual bool readXml( const QDomElement &layoutElement, const QDomDocument &document, const QgsReadWriteContext &context );
%Docstring
Sets the collection's state from a DOM element. ``layoutElement`` is the DOM node corresponding to the layout.

Expand Down
15 changes: 12 additions & 3 deletions python/core/layout/qgslayoutatlas.sip
Expand Up @@ -8,7 +8,7 @@



class QgsLayoutAtlas : QObject
class QgsLayoutAtlas : QObject, QgsLayoutSerializableObject
{
%Docstring
Class used to render an Atlas, iterating over geometry features.
Expand All @@ -34,6 +34,15 @@ QgsLayoutAtlas which is automatically created and attached to the composition.
Constructor for new QgsLayoutAtlas.
%End

virtual QString stringType() const;

virtual QgsLayout *layout();

virtual bool writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const;

virtual bool readXml( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context );


bool enabled() const;
%Docstring
Returns whether the atlas generation is enabled
Expand Down Expand Up @@ -72,7 +81,7 @@ atlas page.
.. seealso:: :py:func:`filenameExpressionErrorString()`
%End

bool setFilenameExpression( const QString &expression, QString &errorString );
bool setFilenameExpression( const QString &expression, QString &errorString /Out/ );
%Docstring
Sets the filename ``expression`` used for generating output filenames for each
atlas page.
Expand Down Expand Up @@ -222,7 +231,7 @@ This property has no effect is filterFeatures() is false.
.. seealso:: :py:func:`filterFeatures()`
%End

bool setFilterExpression( const QString &expression, QString &errorString );
bool setFilterExpression( const QString &expression, QString &errorString /Out/ );
%Docstring
Sets the ``expression`` used for filtering features in the coverage layer.

Expand Down
5 changes: 5 additions & 0 deletions python/core/layout/qgsprintlayout.sip
Expand Up @@ -31,6 +31,11 @@ Constructor for QgsPrintLayout.
Returns the print layout's atlas.
%End

virtual QDomElement writeXml( QDomDocument &document, const QgsReadWriteContext &context ) const;

virtual bool readXml( const QDomElement &layoutElement, const QDomDocument &document, const QgsReadWriteContext &context );


};

/************************************************************************
Expand Down
4 changes: 2 additions & 2 deletions src/core/layout/qgslayout.h
Expand Up @@ -514,13 +514,13 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
* Returns the layout's state encapsulated in a DOM element.
* \see readXml()
*/
QDomElement writeXml( QDomDocument &document, const QgsReadWriteContext &context ) const;
virtual QDomElement writeXml( QDomDocument &document, const QgsReadWriteContext &context ) const;

/**
* Sets the collection's state from a DOM element. \a layoutElement is the DOM node corresponding to the layout.
* \see writeXml()
*/
bool readXml( const QDomElement &layoutElement, const QDomDocument &document, const QgsReadWriteContext &context );
virtual bool readXml( const QDomElement &layoutElement, const QDomDocument &document, const QgsReadWriteContext &context );

/**
* Add items from an XML representation to the layout. Used for project file reading and pasting items from clipboard.
Expand Down
78 changes: 78 additions & 0 deletions src/core/layout/qgslayoutatlas.cpp
Expand Up @@ -31,6 +31,84 @@ QgsLayoutAtlas::QgsLayoutAtlas( QgsLayout *layout )
connect( mLayout->project(), static_cast < void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ), this, &QgsLayoutAtlas::removeLayers );
}

QString QgsLayoutAtlas::stringType() const
{
return QStringLiteral( "atlas" );
}

QgsLayout *QgsLayoutAtlas::layout()
{
return mLayout;
}

bool QgsLayoutAtlas::writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext & ) const
{
QDomElement atlasElem = document.createElement( QStringLiteral( "Atlas" ) );
atlasElem.setAttribute( QStringLiteral( "enabled" ), mEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );

if ( mCoverageLayer )
{
atlasElem.setAttribute( QStringLiteral( "coverageLayer" ), mCoverageLayer.layerId );
atlasElem.setAttribute( QStringLiteral( "coverageLayerName" ), mCoverageLayer.name );
atlasElem.setAttribute( QStringLiteral( "coverageLayerSource" ), mCoverageLayer.source );
atlasElem.setAttribute( QStringLiteral( "coverageLayerProvider" ), mCoverageLayer.provider );
}
else
{
atlasElem.setAttribute( QStringLiteral( "coverageLayer" ), QString() );
}

atlasElem.setAttribute( QStringLiteral( "hideCoverage" ), mHideCoverage ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
atlasElem.setAttribute( QStringLiteral( "filenamePattern" ), mFilenameExpressionString );
atlasElem.setAttribute( QStringLiteral( "pageNameExpression" ), mPageNameExpression );

atlasElem.setAttribute( QStringLiteral( "sortFeatures" ), mSortFeatures ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
if ( mSortFeatures )
{
atlasElem.setAttribute( QStringLiteral( "sortKey" ), mSortExpression );
atlasElem.setAttribute( QStringLiteral( "sortAscending" ), mSortAscending ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
}
atlasElem.setAttribute( QStringLiteral( "filterFeatures" ), mFilterFeatures ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
if ( mFilterFeatures )
{
atlasElem.setAttribute( QStringLiteral( "featureFilter" ), mFilterExpression );
}

parentElement.appendChild( atlasElem );

return true;
}

bool QgsLayoutAtlas::readXml( const QDomElement &atlasElem, const QDomDocument &, const QgsReadWriteContext & )
{
mEnabled = atlasElem.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ).toInt();

// look for stored layer name
QString layerId = atlasElem.attribute( QStringLiteral( "coverageLayer" ) );
QString layerName = atlasElem.attribute( QStringLiteral( "coverageLayerName" ) );
QString layerSource = atlasElem.attribute( QStringLiteral( "coverageLayerSource" ) );
QString layerProvider = atlasElem.attribute( QStringLiteral( "coverageLayerProvider" ) );

mCoverageLayer = QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
mCoverageLayer.resolveWeakly( mLayout->project() );

mPageNameExpression = atlasElem.attribute( QStringLiteral( "pageNameExpression" ), QString() );
QString error;
setFilenameExpression( atlasElem.attribute( QStringLiteral( "filenamePattern" ), QString() ), error );

mSortFeatures = atlasElem.attribute( QStringLiteral( "sortFeatures" ), QStringLiteral( "0" ) ).toInt();
mSortExpression = atlasElem.attribute( QStringLiteral( "sortKey" ) );
mSortAscending = atlasElem.attribute( QStringLiteral( "sortAscending" ), QStringLiteral( "1" ) ).toInt();
mFilterFeatures = atlasElem.attribute( QStringLiteral( "filterFeatures" ), QStringLiteral( "0" ) ).toInt();
mFilterExpression = atlasElem.attribute( QStringLiteral( "featureFilter" ) );

mHideCoverage = atlasElem.attribute( QStringLiteral( "hideCoverage" ), QStringLiteral( "0" ) ).toInt();

emit toggled( mEnabled );
emit changed();
return true;
}

void QgsLayoutAtlas::setEnabled( bool enabled )
{
if ( enabled == mEnabled )
Expand Down
12 changes: 9 additions & 3 deletions src/core/layout/qgslayoutatlas.h
Expand Up @@ -18,6 +18,7 @@

#include "qgis_core.h"
#include "qgsvectorlayerref.h"
#include "qgslayoutserializableobject.h"
#include <QObject>

class QgsLayout;
Expand All @@ -32,7 +33,7 @@ class QgsLayout;
* QgsLayoutAtlas which is automatically created and attached to the composition.
* \since QGIS 3.0
*/
class CORE_EXPORT QgsLayoutAtlas : public QObject
class CORE_EXPORT QgsLayoutAtlas : public QObject, public QgsLayoutSerializableObject
{
Q_OBJECT
public:
Expand All @@ -42,6 +43,11 @@ class CORE_EXPORT QgsLayoutAtlas : public QObject
*/
QgsLayoutAtlas( QgsLayout *layout SIP_TRANSFERTHIS );

QString stringType() const override;
QgsLayout *layout() override;
bool writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const override;
bool readXml( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context ) override;

/**
* Returns whether the atlas generation is enabled
* \see setEnabled()
Expand Down Expand Up @@ -81,7 +87,7 @@ class CORE_EXPORT QgsLayoutAtlas : public QObject
* will be set to the expression error.
* \see filenameExpression()
*/
bool setFilenameExpression( const QString &expression, QString &errorString );
bool setFilenameExpression( const QString &expression, QString &errorString SIP_OUT );

/**
* Returns the coverage layer used for the atlas features.
Expand Down Expand Up @@ -209,7 +215,7 @@ class CORE_EXPORT QgsLayoutAtlas : public QObject
* \see filterExpression()
* \see setFilterFeatures()
*/
bool setFilterExpression( const QString &expression, QString &errorString );
bool setFilterExpression( const QString &expression, QString &errorString SIP_OUT );

public slots:

Expand Down
17 changes: 17 additions & 0 deletions src/core/layout/qgsprintlayout.cpp
Expand Up @@ -27,3 +27,20 @@ QgsLayoutAtlas *QgsPrintLayout::atlas()
{
return mAtlas;
}

QDomElement QgsPrintLayout::writeXml( QDomDocument &document, const QgsReadWriteContext &context ) const
{
QDomElement layoutElem = QgsLayout::writeXml( document, context );
mAtlas->writeXml( layoutElem, document, context );
return layoutElem;
}

bool QgsPrintLayout::readXml( const QDomElement &layoutElement, const QDomDocument &document, const QgsReadWriteContext &context )
{
if ( !QgsLayout::readXml( layoutElement, document, context ) )
return false;

QDomElement atlasElem = layoutElement.firstChildElement( QStringLiteral( "Atlas" ) );
mAtlas->readXml( atlasElem, document, context );
return true;
}
3 changes: 3 additions & 0 deletions src/core/layout/qgsprintlayout.h
Expand Up @@ -43,6 +43,9 @@ class CORE_EXPORT QgsPrintLayout : public QgsLayout
*/
QgsLayoutAtlas *atlas();

QDomElement writeXml( QDomDocument &document, const QgsReadWriteContext &context ) const override;
bool readXml( const QDomElement &layoutElement, const QDomDocument &document, const QgsReadWriteContext &context ) override;

private:

QgsLayoutAtlas *mAtlas = nullptr;
Expand Down
1 change: 1 addition & 0 deletions tests/src/python/CMakeLists.txt
Expand Up @@ -84,6 +84,7 @@ ADD_PYTHON_TEST(PyQgsLayerTreeMapCanvasBridge test_qgslayertreemapcanvasbridge.p
ADD_PYTHON_TEST(PyQgsLayerTree test_qgslayertree.py)
ADD_PYTHON_TEST(PyQgsLayout test_qgslayout.py)
ADD_PYTHON_TEST(PyQgsLayoutAlign test_qgslayoutaligner.py)
ADD_PYTHON_TEST(PyQgsLayoutAtlas test_qgslayoutatlas.py)
ADD_PYTHON_TEST(PyQgsLayoutExporter test_qgslayoutexporter.py)
ADD_PYTHON_TEST(PyQgsLayoutFrame test_qgslayoutframe.py)
ADD_PYTHON_TEST(PyQgsLayoutManager test_qgslayoutmanager.py)
Expand Down
91 changes: 91 additions & 0 deletions tests/src/python/test_qgslayoutatlas.py
@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsLayoutAtlas
.. 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__ = '19/12/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 sip
import tempfile
import shutil
import os

from qgis.core import (QgsUnitTypes,
QgsLayout,
QgsPrintLayout,
QgsLayoutAtlas,
QgsLayoutItemPage,
QgsLayoutGuide,
QgsLayoutObject,
QgsProject,
QgsLayoutItemGroup,
QgsLayoutItem,
QgsProperty,
QgsLayoutPageCollection,
QgsLayoutMeasurement,
QgsFillSymbol,
QgsReadWriteContext,
QgsLayoutItemMap,
QgsLayoutItemLabel,
QgsLayoutSize,
QgsLayoutPoint,
QgsVectorLayer)
from qgis.PyQt.QtCore import QFileInfo
from qgis.PyQt.QtTest import QSignalSpy
from qgis.PyQt.QtXml import QDomDocument
from utilities import unitTestDataPath
from qgis.testing import start_app, unittest

start_app()


class TestQgsLayoutAtlas(unittest.TestCase):

def testReadWriteXml(self):
p = QgsProject()
vectorFileInfo = QFileInfo(unitTestDataPath() + "/france_parts.shp")
vector_layer = QgsVectorLayer(vectorFileInfo.filePath(), vectorFileInfo.completeBaseName(), "ogr")
self.assertTrue(vector_layer.isValid())
p.addMapLayer(vector_layer)

l = QgsPrintLayout(p)
atlas = l.atlas()
atlas.setEnabled(True)
atlas.setHideCoverage(True)
atlas.setFilenameExpression('filename exp')
atlas.setCoverageLayer(vector_layer)
atlas.setPageNameExpression('page name')
atlas.setSortFeatures(True)
atlas.setSortAscending(False)
atlas.setSortExpression('sort exp')
atlas.setFilterFeatures(True)
atlas.setFilterExpression('filter exp')

doc = QDomDocument("testdoc")
elem = l.writeXml(doc, QgsReadWriteContext())

l2 = QgsPrintLayout(p)
self.assertTrue(l2.readXml(elem, doc, QgsReadWriteContext()))
atlas2 = l2.atlas()
self.assertTrue(atlas2.enabled())
self.assertTrue(atlas2.hideCoverage())
self.assertEqual(atlas2.filenameExpression(), 'filename exp')
self.assertEqual(atlas2.coverageLayer(), vector_layer)
self.assertEqual(atlas2.pageNameExpression(), 'page name')
self.assertTrue(atlas2.sortFeatures())
self.assertFalse(atlas2.sortAscending())
self.assertEqual(atlas2.sortExpression(), 'sort exp')
self.assertTrue(atlas2.filterFeatures())
self.assertEqual(atlas2.filterExpression(), 'filter exp')


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

0 comments on commit f86c298

Please sign in to comment.