mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
[FEATURE] Reporting framework
Reports are based on the new layouts engine. They consist of multiple nested sections. Each individual section (and the report itself) can have an optional header and footer (which are themselves layouts, and can consist of multiple pages!). Two different types of sections are implemented so far: - a standard section, which has a single, static body layout. This can be used to embed static layouts mid way through a report - a "field group" section, which repeats its body layout for every feature in a layer. The features are sorted by the selected grouping feature (with an option for ascending/descending sort). If a field group section has child sections (e.g. another field group section with a different field, then only features with unique values for the group feature are iterated over. This allows nested reports, e.g. Report - Country: Australia - State: NSW - Town: Sydney - Town: Woolongong - State: QLD - Town: Beerburrum - Town: Brisbane - Town: Emerald - Country: NZ - State: ... etc In this example country, state or town groups can have their own headers and footers which will be inserted in the report. Reports are configured through a new panel in the layout designer dialog, which is shown when editing a report (created through the Layout Manager Dialog). The organizer allows for adding (and removing) sections to the report, and for selecting which layout (e.g. headers, footers, bodies) to edit within the layout designer.
This commit is contained in:
parent
811145eb96
commit
1ea5a5fb98
@ -162,6 +162,7 @@
|
||||
%Include composer/qgscomposertexttable.sip
|
||||
%Include composer/qgspaperitem.sip
|
||||
%Include layout/qgsabstractlayoutiterator.sip
|
||||
%Include layout/qgsabstractreportsection.sip
|
||||
%Include layout/qgslayoutaligner.sip
|
||||
%Include layout/qgslayoutexporter.sip
|
||||
%Include layout/qgslayoutgridsettings.sip
|
||||
|
270
python/core/layout/qgsabstractreportsection.sip
Normal file
270
python/core/layout/qgsabstractreportsection.sip
Normal file
@ -0,0 +1,270 @@
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/layout/qgsabstractreportsection.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
|
||||
|
||||
class QgsAbstractReportSection : QgsAbstractLayoutIterator
|
||||
{
|
||||
%Docstring
|
||||
An abstract base class for QgsReport subsections.
|
||||
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsabstractreportsection.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsAbstractReportSection();
|
||||
%Docstring
|
||||
Constructor for QgsAbstractReportSection
|
||||
%End
|
||||
|
||||
~QgsAbstractReportSection();
|
||||
|
||||
|
||||
|
||||
virtual QgsAbstractReportSection *clone() const = 0 /Factory/;
|
||||
%Docstring
|
||||
Clones the report section. Ownership of the returned section is
|
||||
transferred to the caller.
|
||||
|
||||
Subclasses should call copyCommonProperties() in their clone()
|
||||
implementations.
|
||||
%End
|
||||
|
||||
|
||||
virtual QgsLayout *layout();
|
||||
|
||||
virtual bool beginRender();
|
||||
|
||||
virtual bool next();
|
||||
|
||||
virtual bool endRender();
|
||||
|
||||
|
||||
bool headerEnabled() const;
|
||||
%Docstring
|
||||
Returns true if the header for the section is enabled.
|
||||
|
||||
.. seealso:: :py:func:`setHeaderEnabled()`
|
||||
|
||||
.. seealso:: :py:func:`header()`
|
||||
|
||||
.. seealso:: :py:func:`setHeader()`
|
||||
%End
|
||||
|
||||
void setHeaderEnabled( bool enabled );
|
||||
%Docstring
|
||||
Sets whether the header for the section is ``enabled``.
|
||||
|
||||
.. seealso:: :py:func:`headerEnabled()`
|
||||
|
||||
.. seealso:: :py:func:`header()`
|
||||
|
||||
.. seealso:: :py:func:`setHeader()`
|
||||
%End
|
||||
|
||||
QgsLayout *header();
|
||||
%Docstring
|
||||
Returns the header for the section. Note that the header is only
|
||||
included if headerEnabled() is true.
|
||||
|
||||
.. seealso:: :py:func:`setHeaderEnabled()`
|
||||
|
||||
.. seealso:: :py:func:`headerEnabled()`
|
||||
|
||||
.. seealso:: :py:func:`setHeader()`
|
||||
%End
|
||||
|
||||
void setHeader( QgsLayout *header /Transfer/ );
|
||||
%Docstring
|
||||
Sets the ``header`` for the section. Note that the header is only
|
||||
included if headerEnabled() is true. Ownership of ``header``
|
||||
is transferred to the report section.
|
||||
|
||||
.. seealso:: :py:func:`setHeaderEnabled()`
|
||||
|
||||
.. seealso:: :py:func:`headerEnabled()`
|
||||
|
||||
.. seealso:: :py:func:`header()`
|
||||
%End
|
||||
|
||||
bool footerEnabled() const;
|
||||
%Docstring
|
||||
Returns true if the footer for the section is enabled.
|
||||
|
||||
.. seealso:: :py:func:`setFooterEnabled()`
|
||||
|
||||
.. seealso:: :py:func:`footer()`
|
||||
|
||||
.. seealso:: :py:func:`setFooter()`
|
||||
%End
|
||||
|
||||
void setFooterEnabled( bool enabled );
|
||||
%Docstring
|
||||
Sets whether the footer for the section is ``enabled``.
|
||||
|
||||
.. seealso:: :py:func:`footerEnabled()`
|
||||
|
||||
.. seealso:: :py:func:`footer()`
|
||||
|
||||
.. seealso:: :py:func:`setFooter()`
|
||||
%End
|
||||
|
||||
QgsLayout *footer();
|
||||
%Docstring
|
||||
Returns the footer for the section. Note that the footer is only
|
||||
included if footerEnabled() is true.
|
||||
|
||||
.. seealso:: :py:func:`setFooterEnabled()`
|
||||
|
||||
.. seealso:: :py:func:`footerEnabled()`
|
||||
|
||||
.. seealso:: :py:func:`setFooter()`
|
||||
%End
|
||||
|
||||
void setFooter( QgsLayout *footer /Transfer/ );
|
||||
%Docstring
|
||||
Sets the ``footer`` for the section. Note that the footer is only
|
||||
included if footerEnabled() is true. Ownership of ``footer``
|
||||
is transferred to the report section.
|
||||
|
||||
.. seealso:: :py:func:`setFooterEnabled()`
|
||||
|
||||
.. seealso:: :py:func:`footerEnabled()`
|
||||
|
||||
.. seealso:: :py:func:`footer()`
|
||||
%End
|
||||
|
||||
int childCount() const;
|
||||
%Docstring
|
||||
Return the number of child sections for this report section. The child
|
||||
sections form the body of the report section.
|
||||
|
||||
.. seealso:: :py:func:`children()`
|
||||
%End
|
||||
|
||||
QList< QgsAbstractReportSection * > children();
|
||||
%Docstring
|
||||
Return all child sections for this report section. The child
|
||||
sections form the body of the report section.
|
||||
|
||||
.. seealso:: :py:func:`childCount()`
|
||||
|
||||
.. seealso:: :py:func:`child()`
|
||||
|
||||
.. seealso:: :py:func:`appendChild()`
|
||||
|
||||
.. seealso:: :py:func:`insertChild()`
|
||||
|
||||
.. seealso:: :py:func:`removeChild()`
|
||||
%End
|
||||
|
||||
QgsAbstractReportSection *child( int index );
|
||||
%Docstring
|
||||
Returns the child section at the specified ``index``.
|
||||
|
||||
.. seealso:: :py:func:`children()`
|
||||
%End
|
||||
|
||||
void appendChild( QgsAbstractReportSection *section /Transfer/ );
|
||||
%Docstring
|
||||
Adds a child ``section``, transferring ownership of the section to this section.
|
||||
|
||||
.. seealso:: :py:func:`children()`
|
||||
|
||||
.. seealso:: :py:func:`insertChild()`
|
||||
%End
|
||||
|
||||
void insertChild( int index, QgsAbstractReportSection *section /Transfer/ );
|
||||
%Docstring
|
||||
Inserts a child ``section`` at the specified ``index``, transferring ownership of the section to this section.
|
||||
|
||||
.. seealso:: :py:func:`children()`
|
||||
|
||||
.. seealso:: :py:func:`appendChild()`
|
||||
%End
|
||||
|
||||
void removeChild( QgsAbstractReportSection *section );
|
||||
%Docstring
|
||||
Removes a child ``section``, deleting it.
|
||||
|
||||
.. seealso:: :py:func:`children()`
|
||||
%End
|
||||
|
||||
void removeChildAt( int index );
|
||||
%Docstring
|
||||
Removes the child section at the specified ``index``, deleting it.
|
||||
|
||||
.. seealso:: :py:func:`children()`
|
||||
%End
|
||||
|
||||
protected:
|
||||
|
||||
enum SubSection
|
||||
{
|
||||
Header,
|
||||
Body,
|
||||
Footer,
|
||||
End,
|
||||
};
|
||||
|
||||
void copyCommonProperties( QgsAbstractReportSection *destination ) const;
|
||||
%Docstring
|
||||
Copies the common properties of a report section to a ``destination`` section.
|
||||
This method should be called from clone() implementations.
|
||||
%End
|
||||
|
||||
private:
|
||||
QgsAbstractReportSection( const QgsAbstractReportSection &other );
|
||||
};
|
||||
|
||||
|
||||
class QgsReport : QgsAbstractReportSection
|
||||
{
|
||||
%Docstring
|
||||
Represents a report for use with the QgsLayout engine.
|
||||
|
||||
Reports consist of multiple sections, represented by QgsAbstractReportSection
|
||||
subclasses.
|
||||
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsabstractreportsection.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsReport();
|
||||
%Docstring
|
||||
Constructor for QgsReport.
|
||||
%End
|
||||
|
||||
virtual QgsReport *clone() const;
|
||||
|
||||
|
||||
virtual int count();
|
||||
virtual bool beginRender();
|
||||
|
||||
virtual bool next();
|
||||
|
||||
|
||||
virtual QString filePath( const QString &baseFilePath, const QString &extension );
|
||||
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/layout/qgsabstractreportsection.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
@ -364,6 +364,7 @@ SET(QGIS_CORE_SRCS
|
||||
dxf/qgsdxfpaintengine.cpp
|
||||
dxf/qgsdxfpallabeling.cpp
|
||||
|
||||
layout/qgsabstractreportsection.cpp
|
||||
layout/qgslayout.cpp
|
||||
layout/qgslayoutaligner.cpp
|
||||
layout/qgslayoutatlas.cpp
|
||||
@ -1029,6 +1030,7 @@ SET(QGIS_CORE_HDRS
|
||||
composer/qgspaperitem.h
|
||||
|
||||
layout/qgsabstractlayoutiterator.h
|
||||
layout/qgsabstractreportsection.h
|
||||
layout/qgslayoutaligner.h
|
||||
layout/qgslayoutexporter.h
|
||||
layout/qgslayoutgridsettings.h
|
||||
|
215
src/core/layout/qgsabstractreportsection.cpp
Normal file
215
src/core/layout/qgsabstractreportsection.cpp
Normal file
@ -0,0 +1,215 @@
|
||||
/***************************************************************************
|
||||
qgsabstractreportsection.cpp
|
||||
--------------------
|
||||
begin : December 2017
|
||||
copyright : (C) 2017 by Nyall Dawson
|
||||
email : nyall dot dawson at gmail dot com
|
||||
***************************************************************************/
|
||||
/***************************************************************************
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgsabstractreportsection.h"
|
||||
#include "qgslayout.h"
|
||||
|
||||
QgsAbstractReportSection::~QgsAbstractReportSection()
|
||||
{
|
||||
qDeleteAll( mChildren );
|
||||
}
|
||||
|
||||
QgsLayout *QgsAbstractReportSection::layout()
|
||||
{
|
||||
return mCurrentLayout;
|
||||
}
|
||||
|
||||
bool QgsAbstractReportSection::beginRender()
|
||||
{
|
||||
// reset this section
|
||||
mCurrentLayout = nullptr;
|
||||
mNextChild = 0;
|
||||
mNextSection = Header;
|
||||
|
||||
// and all children too
|
||||
bool result = true;
|
||||
for ( QgsAbstractReportSection *child : qgis::as_const( mChildren ) )
|
||||
{
|
||||
result = result && child->beginRender();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool QgsAbstractReportSection::next()
|
||||
{
|
||||
switch ( mNextSection )
|
||||
{
|
||||
case Header:
|
||||
{
|
||||
// regardless of whether we have a header or not, the next section will be the body
|
||||
mNextSection = Body;
|
||||
|
||||
// if we have a header, then the current section will be the header
|
||||
if ( mHeaderEnabled && mHeader )
|
||||
{
|
||||
mCurrentLayout = mHeader.get();
|
||||
return true;
|
||||
}
|
||||
|
||||
// but if not, then the current section is the body
|
||||
FALLTHROUGH;
|
||||
}
|
||||
|
||||
case Body:
|
||||
{
|
||||
|
||||
// we iterate through all the section's children...
|
||||
while ( mNextChild < mChildren.count() )
|
||||
{
|
||||
// ... staying on the current child only while it still has content for us
|
||||
if ( mChildren.at( mNextChild )->next() )
|
||||
{
|
||||
mCurrentLayout = mChildren.at( mNextChild )->layout();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// no more content for this child, so move to next child
|
||||
mNextChild++;
|
||||
}
|
||||
}
|
||||
|
||||
// all children have spent their content, so move to the footer
|
||||
mNextSection = Footer;
|
||||
FALLTHROUGH;
|
||||
}
|
||||
|
||||
case Footer:
|
||||
{
|
||||
// regardless of whether we have a footer or not, this is the last section
|
||||
mNextSection = End;
|
||||
|
||||
// if we have a footer, then the current section will be the footer
|
||||
if ( mFooterEnabled && mFooter )
|
||||
{
|
||||
mCurrentLayout = mFooter.get();
|
||||
return true;
|
||||
}
|
||||
|
||||
// if not, then we're all done
|
||||
FALLTHROUGH;
|
||||
}
|
||||
|
||||
case End:
|
||||
break;
|
||||
}
|
||||
|
||||
mCurrentLayout = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QgsAbstractReportSection::endRender()
|
||||
{
|
||||
// reset this section
|
||||
mCurrentLayout = nullptr;
|
||||
mNextChild = 0;
|
||||
mNextSection = Header;
|
||||
|
||||
// and all children too
|
||||
bool result = true;
|
||||
for ( QgsAbstractReportSection *child : qgis::as_const( mChildren ) )
|
||||
{
|
||||
result = result && child->endRender();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QgsAbstractReportSection *QgsAbstractReportSection::child( int index )
|
||||
{
|
||||
return mChildren.value( index );
|
||||
}
|
||||
|
||||
void QgsAbstractReportSection::appendChild( QgsAbstractReportSection *section )
|
||||
{
|
||||
mChildren.append( section );
|
||||
}
|
||||
|
||||
void QgsAbstractReportSection::insertChild( int index, QgsAbstractReportSection *section )
|
||||
{
|
||||
index = std::max( 0, index );
|
||||
index = std::min( index, mChildren.count() );
|
||||
mChildren.insert( index, section );
|
||||
}
|
||||
|
||||
void QgsAbstractReportSection::removeChild( QgsAbstractReportSection *section )
|
||||
{
|
||||
mChildren.removeAll( section );
|
||||
delete section;
|
||||
}
|
||||
|
||||
void QgsAbstractReportSection::removeChildAt( int index )
|
||||
{
|
||||
if ( index < 0 || index >= mChildren.count() )
|
||||
return;
|
||||
|
||||
QgsAbstractReportSection *section = mChildren.at( index );
|
||||
removeChild( section );
|
||||
}
|
||||
|
||||
void QgsAbstractReportSection::copyCommonProperties( QgsAbstractReportSection *destination ) const
|
||||
{
|
||||
destination->mHeaderEnabled = mHeaderEnabled;
|
||||
if ( mHeader )
|
||||
destination->mHeader.reset( mHeader->clone() );
|
||||
else
|
||||
destination->mHeader.reset();
|
||||
|
||||
destination->mFooterEnabled = mFooterEnabled;
|
||||
if ( mFooter )
|
||||
destination->mFooter.reset( mFooter->clone() );
|
||||
else
|
||||
destination->mFooter.reset();
|
||||
|
||||
qDeleteAll( destination->mChildren );
|
||||
destination->mChildren.clear();
|
||||
|
||||
for ( QgsAbstractReportSection *child : qgis::as_const( mChildren ) )
|
||||
{
|
||||
destination->mChildren.append( child->clone() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// QgsReport
|
||||
|
||||
QgsReport *QgsReport::clone() const
|
||||
{
|
||||
std::unique_ptr< QgsReport > copy = qgis::make_unique< QgsReport >();
|
||||
copyCommonProperties( copy.get() );
|
||||
return copy.release();
|
||||
}
|
||||
|
||||
bool QgsReport::beginRender()
|
||||
{
|
||||
mSectionNumber = 0;
|
||||
return QgsAbstractReportSection::beginRender();
|
||||
}
|
||||
|
||||
bool QgsReport::next()
|
||||
{
|
||||
mSectionNumber++;
|
||||
return QgsAbstractReportSection::next();
|
||||
}
|
||||
|
||||
QString QgsReport::filePath( const QString &baseFilePath, const QString &extension )
|
||||
{
|
||||
QString base = QDir( baseFilePath ).filePath( "report_" ) + QString::number( mSectionNumber );
|
||||
if ( !extension.startsWith( '.' ) )
|
||||
base += '.';
|
||||
base += extension;
|
||||
return base;
|
||||
|
||||
}
|
253
src/core/layout/qgsabstractreportsection.h
Normal file
253
src/core/layout/qgsabstractreportsection.h
Normal file
@ -0,0 +1,253 @@
|
||||
/***************************************************************************
|
||||
qgsabstractreportsection.h
|
||||
---------------------------
|
||||
begin : December 2017
|
||||
copyright : (C) 2017 by Nyall Dawson
|
||||
email : nyall dot dawson at gmail dot com
|
||||
***************************************************************************/
|
||||
/***************************************************************************
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
#ifndef QGSABSTRACTREPORTSECTION_H
|
||||
#define QGSABSTRACTREPORTSECTION_H
|
||||
|
||||
#include "qgis_core.h"
|
||||
#include "qgsabstractlayoutiterator.h"
|
||||
#include "qgslayoutreportcontext.h"
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* \class QgsAbstractReportSection
|
||||
* \brief An abstract base class for QgsReport subsections.
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
class CORE_EXPORT QgsAbstractReportSection : public QgsAbstractLayoutIterator
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
//! Constructor for QgsAbstractReportSection
|
||||
QgsAbstractReportSection() = default;
|
||||
|
||||
~QgsAbstractReportSection() override;
|
||||
|
||||
//! QgsAbstractReportSection cannot be copied
|
||||
QgsAbstractReportSection( const QgsAbstractReportSection &other ) = delete;
|
||||
|
||||
//! QgsAbstractReportSection cannot be copied
|
||||
QgsAbstractReportSection &operator=( const QgsAbstractReportSection &other ) = delete;
|
||||
|
||||
/**
|
||||
* Clones the report section. Ownership of the returned section is
|
||||
* transferred to the caller.
|
||||
*
|
||||
* Subclasses should call copyCommonProperties() in their clone()
|
||||
* implementations.
|
||||
*/
|
||||
virtual QgsAbstractReportSection *clone() const = 0 SIP_FACTORY;
|
||||
|
||||
#if 0 //TODO
|
||||
virtual void setContext( const QgsLayoutReportContext &context ) = 0;
|
||||
#endif
|
||||
|
||||
QgsLayout *layout() override;
|
||||
bool beginRender() override;
|
||||
bool next() override;
|
||||
bool endRender() override;
|
||||
|
||||
/**
|
||||
* Returns true if the header for the section is enabled.
|
||||
* \see setHeaderEnabled()
|
||||
* \see header()
|
||||
* \see setHeader()
|
||||
*/
|
||||
bool headerEnabled() const { return mHeaderEnabled; }
|
||||
|
||||
/**
|
||||
* Sets whether the header for the section is \a enabled.
|
||||
* \see headerEnabled()
|
||||
* \see header()
|
||||
* \see setHeader()
|
||||
*/
|
||||
void setHeaderEnabled( bool enabled ) { mHeaderEnabled = enabled; }
|
||||
|
||||
/**
|
||||
* Returns the header for the section. Note that the header is only
|
||||
* included if headerEnabled() is true.
|
||||
* \see setHeaderEnabled()
|
||||
* \see headerEnabled()
|
||||
* \see setHeader()
|
||||
*/
|
||||
QgsLayout *header() { return mHeader.get(); }
|
||||
|
||||
/**
|
||||
* Sets the \a header for the section. Note that the header is only
|
||||
* included if headerEnabled() is true. Ownership of \a header
|
||||
* is transferred to the report section.
|
||||
* \see setHeaderEnabled()
|
||||
* \see headerEnabled()
|
||||
* \see header()
|
||||
*/
|
||||
void setHeader( QgsLayout *header SIP_TRANSFER ) { mHeader.reset( header ); }
|
||||
|
||||
/**
|
||||
* Returns true if the footer for the section is enabled.
|
||||
* \see setFooterEnabled()
|
||||
* \see footer()
|
||||
* \see setFooter()
|
||||
*/
|
||||
bool footerEnabled() const { return mFooterEnabled; }
|
||||
|
||||
/**
|
||||
* Sets whether the footer for the section is \a enabled.
|
||||
* \see footerEnabled()
|
||||
* \see footer()
|
||||
* \see setFooter()
|
||||
*/
|
||||
void setFooterEnabled( bool enabled ) { mFooterEnabled = enabled; }
|
||||
|
||||
/**
|
||||
* Returns the footer for the section. Note that the footer is only
|
||||
* included if footerEnabled() is true.
|
||||
* \see setFooterEnabled()
|
||||
* \see footerEnabled()
|
||||
* \see setFooter()
|
||||
*/
|
||||
QgsLayout *footer() { return mFooter.get(); }
|
||||
|
||||
/**
|
||||
* Sets the \a footer for the section. Note that the footer is only
|
||||
* included if footerEnabled() is true. Ownership of \a footer
|
||||
* is transferred to the report section.
|
||||
* \see setFooterEnabled()
|
||||
* \see footerEnabled()
|
||||
* \see footer()
|
||||
*/
|
||||
void setFooter( QgsLayout *footer SIP_TRANSFER ) { mFooter.reset( footer ); }
|
||||
|
||||
/**
|
||||
* Return the number of child sections for this report section. The child
|
||||
* sections form the body of the report section.
|
||||
* \see children()
|
||||
*/
|
||||
int childCount() const { return mChildren.count(); }
|
||||
|
||||
/**
|
||||
* Return all child sections for this report section. The child
|
||||
* sections form the body of the report section.
|
||||
* \see childCount()
|
||||
* \see child()
|
||||
* \see appendChild()
|
||||
* \see insertChild()
|
||||
* \see removeChild()
|
||||
*/
|
||||
QList< QgsAbstractReportSection * > children() { return mChildren; }
|
||||
|
||||
/**
|
||||
* Returns the child section at the specified \a index.
|
||||
* \see children()
|
||||
*/
|
||||
QgsAbstractReportSection *child( int index );
|
||||
|
||||
/**
|
||||
* Adds a child \a section, transferring ownership of the section to this section.
|
||||
* \see children()
|
||||
* \see insertChild()
|
||||
*/
|
||||
void appendChild( QgsAbstractReportSection *section SIP_TRANSFER );
|
||||
|
||||
/**
|
||||
* Inserts a child \a section at the specified \a index, transferring ownership of the section to this section.
|
||||
* \see children()
|
||||
* \see appendChild()
|
||||
*/
|
||||
void insertChild( int index, QgsAbstractReportSection *section SIP_TRANSFER );
|
||||
|
||||
/**
|
||||
* Removes a child \a section, deleting it.
|
||||
* \see children()
|
||||
*/
|
||||
void removeChild( QgsAbstractReportSection *section );
|
||||
|
||||
/**
|
||||
* Removes the child section at the specified \a index, deleting it.
|
||||
* \see children()
|
||||
*/
|
||||
void removeChildAt( int index );
|
||||
|
||||
protected:
|
||||
|
||||
//! Report sub-sections
|
||||
enum SubSection
|
||||
{
|
||||
Header, //!< Header for section
|
||||
Body, //!< Body of section
|
||||
Footer, //!< Footer for section
|
||||
End, //!< End of section (i.e. past all available content)
|
||||
};
|
||||
|
||||
/**
|
||||
* Copies the common properties of a report section to a \a destination section.
|
||||
* This method should be called from clone() implementations.
|
||||
*/
|
||||
void copyCommonProperties( QgsAbstractReportSection *destination ) const;
|
||||
|
||||
private:
|
||||
|
||||
SubSection mNextSection = Header;
|
||||
int mNextChild = 0;
|
||||
QgsLayout *mCurrentLayout = nullptr;
|
||||
|
||||
bool mHeaderEnabled = false;
|
||||
bool mFooterEnabled = false;
|
||||
std::unique_ptr< QgsLayout > mHeader;
|
||||
std::unique_ptr< QgsLayout > mFooter;
|
||||
|
||||
QList< QgsAbstractReportSection * > mChildren;
|
||||
|
||||
#ifdef SIP_RUN
|
||||
QgsAbstractReportSection( const QgsAbstractReportSection &other );
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* \class QgsReport
|
||||
* \brief Represents a report for use with the QgsLayout engine.
|
||||
*
|
||||
* Reports consist of multiple sections, represented by QgsAbstractReportSection
|
||||
* subclasses.
|
||||
*
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
class CORE_EXPORT QgsReport : public QgsAbstractReportSection
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
//! Constructor for QgsReport.
|
||||
QgsReport() = default;
|
||||
|
||||
QgsReport *clone() const override;
|
||||
|
||||
// TODO - how to handle this?
|
||||
int count() override { return -1; }
|
||||
bool beginRender() override;
|
||||
bool next() override;
|
||||
|
||||
//TODO - baseFilePath should be a filename, not directory
|
||||
QString filePath( const QString &baseFilePath, const QString &extension ) override;
|
||||
|
||||
private:
|
||||
|
||||
int mSectionNumber = 0;
|
||||
|
||||
};
|
||||
|
||||
#endif //QGSABSTRACTREPORTSECTION_H
|
@ -154,6 +154,7 @@ ADD_PYTHON_TEST(PyQgsRelation test_qgsrelation.py)
|
||||
ADD_PYTHON_TEST(PyQgsRelationManager test_qgsrelationmanager.py)
|
||||
ADD_PYTHON_TEST(PyQgsRenderContext test_qgsrendercontext.py)
|
||||
ADD_PYTHON_TEST(PyQgsRenderer test_qgsrenderer.py)
|
||||
ADD_PYTHON_TEST(PyQgsReport test_qgsreport.py)
|
||||
ADD_PYTHON_TEST(PyQgsRulebasedRenderer test_qgsrulebasedrenderer.py)
|
||||
ADD_PYTHON_TEST(PyQgsSingleSymbolRenderer test_qgssinglesymbolrenderer.py)
|
||||
ADD_PYTHON_TEST(PyQgsShapefileProvider test_provider_shapefile.py)
|
||||
|
128
tests/src/python/test_qgsreport.py
Normal file
128
tests/src/python/test_qgsreport.py
Normal file
@ -0,0 +1,128 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""QGIS Unit tests for QgsReport
|
||||
|
||||
.. 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__ = '29/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
|
||||
|
||||
from qgis.core import (QgsProject,
|
||||
QgsLayout,
|
||||
QgsReport)
|
||||
from qgis.testing import start_app, unittest
|
||||
|
||||
start_app()
|
||||
|
||||
|
||||
class TestQgsReport(unittest.TestCase):
|
||||
|
||||
def testGettersSetters(self):
|
||||
p = QgsProject()
|
||||
r = QgsReport()
|
||||
|
||||
r.setHeaderEnabled(True)
|
||||
self.assertTrue(r.headerEnabled())
|
||||
|
||||
header = QgsLayout(p)
|
||||
r.setHeader(header)
|
||||
self.assertEqual(r.header(), header)
|
||||
|
||||
r.setFooterEnabled(True)
|
||||
self.assertTrue(r.footerEnabled())
|
||||
|
||||
footer = QgsLayout(p)
|
||||
r.setFooter(footer)
|
||||
self.assertEqual(r.footer(), footer)
|
||||
|
||||
def testChildren(self):
|
||||
p = QgsProject()
|
||||
r = QgsReport()
|
||||
self.assertEqual(r.childCount(), 0)
|
||||
self.assertEqual(r.children(), [])
|
||||
self.assertIsNone(r.child(-1))
|
||||
self.assertIsNone(r.child(1))
|
||||
self.assertIsNone(r.child(0))
|
||||
|
||||
# try deleting non-existant children
|
||||
r.removeChildAt(-1)
|
||||
r.removeChildAt(0)
|
||||
r.removeChildAt(100)
|
||||
r.removeChild(None)
|
||||
|
||||
# append child
|
||||
child1 = QgsReport()
|
||||
r.appendChild(child1)
|
||||
self.assertEqual(r.childCount(), 1)
|
||||
self.assertEqual(r.children(), [child1])
|
||||
self.assertEqual(r.child(0), child1)
|
||||
child2 = QgsReport()
|
||||
r.appendChild(child2)
|
||||
self.assertEqual(r.childCount(), 2)
|
||||
self.assertEqual(r.children(), [child1, child2])
|
||||
self.assertEqual(r.child(1), child2)
|
||||
|
||||
def testInsertChild(self):
|
||||
p = QgsProject()
|
||||
r = QgsReport()
|
||||
|
||||
child1 = QgsReport()
|
||||
r.insertChild(11, child1)
|
||||
self.assertEqual(r.childCount(), 1)
|
||||
self.assertEqual(r.children(), [child1])
|
||||
child2 = QgsReport()
|
||||
r.insertChild(-1, child2)
|
||||
self.assertEqual(r.childCount(), 2)
|
||||
self.assertEqual(r.children(), [child2, child1])
|
||||
|
||||
def testRemoveChild(self):
|
||||
p = QgsProject()
|
||||
r = QgsReport()
|
||||
|
||||
child1 = QgsReport()
|
||||
r.appendChild(child1)
|
||||
child2 = QgsReport()
|
||||
r.appendChild(child2)
|
||||
|
||||
r.removeChildAt(-1)
|
||||
r.removeChildAt(100)
|
||||
r.removeChild(None)
|
||||
self.assertEqual(r.childCount(), 2)
|
||||
self.assertEqual(r.children(), [child1, child2])
|
||||
|
||||
r.removeChildAt(1)
|
||||
self.assertEqual(r.childCount(), 1)
|
||||
self.assertEqual(r.children(), [child1])
|
||||
|
||||
r.removeChild(child1)
|
||||
self.assertEqual(r.childCount(), 0)
|
||||
self.assertEqual(r.children(), [])
|
||||
|
||||
def testClone(self):
|
||||
p = QgsProject()
|
||||
r = QgsReport()
|
||||
|
||||
child1 = QgsReport()
|
||||
child1.setHeaderEnabled(True)
|
||||
r.appendChild(child1)
|
||||
child2 = QgsReport()
|
||||
child2.setFooterEnabled(True)
|
||||
r.appendChild(child2)
|
||||
|
||||
cloned = r.clone()
|
||||
self.assertEqual(cloned.childCount(), 2)
|
||||
self.assertTrue(cloned.child(0).headerEnabled())
|
||||
self.assertFalse(cloned.child(0).footerEnabled())
|
||||
self.assertFalse(cloned.child(1).headerEnabled())
|
||||
self.assertTrue(cloned.child(1).footerEnabled())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
x
Reference in New Issue
Block a user