Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FEATURE][composer] Add page name option for atlas
Page name can be set to either a field or expression derived from
the coverage layer, and is shown in the new atlas page combobox.
  • Loading branch information
nyalldawson committed Aug 3, 2015
1 parent 5537e23 commit 69ac677
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 150 deletions.
93 changes: 59 additions & 34 deletions python/core/composer/qgsatlascomposition.sip
Expand Up @@ -28,16 +28,6 @@ public:
*/
void setEnabled( bool enabled );

/** Returns the map used by the atlas
* @deprecated Use QgsComposerMap::atlasDriven() instead
*/
QgsComposerMap* composerMap() const /Deprecated/;

/** Sets the map used by the atlas
* @deprecated Use QgsComposerMap::setAtlasDriven( true ) instead
*/
void setComposerMap( QgsComposerMap* map ) /Deprecated/;

/** Returns true if the atlas is set to hide the coverage layer
* @returns true if coverage layer is hidden
* @see setHideCoverage
Expand All @@ -48,27 +38,7 @@ public:
* @param hide set to true to hide the coverage layer
* @see hideCoverage
*/
void setHideCoverage( bool hide );

/** Returns whether the atlas map uses a fixed scale
* @deprecated since 2.4 Use QgsComposerMap::atlasScalingMode() instead
*/
bool fixedScale() const /Deprecated/;

/** Sets whether the atlas map should use a fixed scale
* @deprecated since 2.4 Use QgsComposerMap::setAtlasScalingMode() instead
*/
void setFixedScale( bool fixed ) /Deprecated/;

/** Returns the margin for the atlas map
* @deprecated Use QgsComposerMap::atlasMargin() instead
*/
float margin() const /Deprecated/;

/** Sets the margin for the atlas map
* @deprecated Use QgsComposerMap::setAtlasMargin( double ) instead
*/
void setMargin( float margin ) /Deprecated/;
void setHideCoverage( bool hide );

/** Returns the filename expression used for generating output filenames for each
* atlas page.
Expand Down Expand Up @@ -107,6 +77,29 @@ public:
* @see coverageLayer
*/
void setCoverageLayer( QgsVectorLayer* layer );

/** Returns the expression used for calculating the page name.
* @returns expression string, or field name from coverage layer
* @see setPageNameExpression
* @see nameForPage
* @note added in QGIS 2.12
*/
QString pageNameExpression() const;

/** Sets the expression used for calculating the page name.
* @param pageNameExpression expression string, or field name from coverage layer
* @see pageNameExpression
* @note added in QGIS 2.12
*/
void setPageNameExpression( const QString& pageNameExpression );

/** Returns the calculated name for a specified atlas page number.
* @param pageNumber number of page, where 0 = first page
* @returns page name
* @see pageNameExpression
* @note added in QGIS 2.12
*/
QString nameForPage( int pageNumber ) const;

/** Returns whether the atlas will be exported to a single file. This is only
* applicable for PDF exports.
Expand Down Expand Up @@ -146,9 +139,6 @@ public:
QString sortKeyAttributeName() const;
void setSortKeyAttributeName( QString fieldName );

int sortKeyAttributeIndex() const /Deprecated/;
void setSortKeyAttributeIndex( int idx ) /Deprecated/;

/** Returns the current list of predefined scales for the atlas. This is used
* for maps which are set to the predefined atlas scaling mode.
* @returns a vector of doubles representing predefined scales
Expand Down Expand Up @@ -224,6 +214,41 @@ public:

/** Recalculates the bounds of an atlas driven map */
void prepareMap( QgsComposerMap* map );

//Deprecated methods

/** Returns the map used by the atlas
* @deprecated Use QgsComposerMap::atlasDriven() instead
*/
QgsComposerMap* composerMap() const /Deprecated/;

/** Sets the map used by the atlas
* @deprecated Use QgsComposerMap::setAtlasDriven( true ) instead
*/
void setComposerMap( QgsComposerMap* map ) /Deprecated/;

/** Returns whether the atlas map uses a fixed scale
* @deprecated since 2.4 Use QgsComposerMap::atlasScalingMode() instead
*/
bool fixedScale() const /Deprecated/;

/** Sets whether the atlas map should use a fixed scale
* @deprecated since 2.4 Use QgsComposerMap::setAtlasScalingMode() instead
*/
void setFixedScale( bool fixed ) /Deprecated/;

/** Returns the margin for the atlas map
* @deprecated Use QgsComposerMap::atlasMargin() instead
*/
float margin() const /Deprecated/;

/** Sets the margin for the atlas map
* @deprecated Use QgsComposerMap::setAtlasMargin( double ) instead
*/
void setMargin( float margin ) /Deprecated/;

int sortKeyAttributeIndex() const /Deprecated/;
void setSortKeyAttributeIndex( int idx ) /Deprecated/;

public slots:

Expand Down
15 changes: 15 additions & 0 deletions src/app/composer/qgsatlascompositionwidget.cpp
Expand Up @@ -35,6 +35,7 @@ QgsAtlasCompositionWidget::QgsAtlasCompositionWidget( QWidget* parent, QgsCompos
connect( mAtlasCoverageLayerComboBox, SIGNAL( layerChanged( QgsMapLayer* ) ), mAtlasSortFeatureKeyComboBox, SLOT( setLayer( QgsMapLayer* ) ) );
connect( mAtlasCoverageLayerComboBox, SIGNAL( layerChanged( QgsMapLayer* ) ), this, SLOT( changeCoverageLayer( QgsMapLayer* ) ) );
connect( mAtlasSortFeatureKeyComboBox, SIGNAL( fieldChanged( QString ) ), this, SLOT( changesSortFeatureField( QString ) ) );
connect( mPageNameWidget, SIGNAL( fieldChanged( QString, bool ) ), this, SLOT( pageNameExpressionChanged( QString, bool ) ) );

// Sort direction
mAtlasSortFeatureDirectionButton->setEnabled( false );
Expand Down Expand Up @@ -253,6 +254,17 @@ void QgsAtlasCompositionWidget::on_mAtlasFeatureFilterCheckBox_stateChanged( int
updateAtlasFeatures();
}

void QgsAtlasCompositionWidget::pageNameExpressionChanged( QString expression, bool valid )
{
QgsAtlasComposition* atlasMap = &mComposition->atlasComposition();
if ( !atlasMap || ( !valid && !expression.isEmpty() ) )
{
return;
}

atlasMap->setPageNameExpression( expression );
}

void QgsAtlasCompositionWidget::on_mAtlasFeatureFilterEdit_editingFinished()
{
QgsAtlasComposition* atlasMap = &mComposition->atlasComposition();
Expand Down Expand Up @@ -313,6 +325,8 @@ void QgsAtlasCompositionWidget::updateGuiElements()
mOutputGroup->setEnabled( atlasMap->enabled() );

mAtlasCoverageLayerComboBox->setLayer( atlasMap->coverageLayer() );
mPageNameWidget->setLayer( atlasMap->coverageLayer() );
mPageNameWidget->setField( atlasMap->pageNameExpression() );

mAtlasSortFeatureKeyComboBox->setLayer( atlasMap->coverageLayer() );
mAtlasSortFeatureKeyComboBox->setField( atlasMap->sortKeyAttributeName() );
Expand Down Expand Up @@ -344,6 +358,7 @@ void QgsAtlasCompositionWidget::blockAllSignals( bool b )
mConfigurationGroup->blockSignals( b );
mOutputGroup->blockSignals( b );
mAtlasCoverageLayerComboBox->blockSignals( b );
mPageNameWidget->blockSignals( b );
mAtlasSortFeatureKeyComboBox->blockSignals( b );
mAtlasFilenamePatternEdit->blockSignals( b );
mAtlasHideCoverageCheckBox->blockSignals( b );
Expand Down
1 change: 1 addition & 0 deletions src/app/composer/qgsatlascompositionwidget.h
Expand Up @@ -48,6 +48,7 @@ class QgsAtlasCompositionWidget:
void on_mAtlasFeatureFilterEdit_editingFinished();
void on_mAtlasFeatureFilterButton_clicked();
void on_mAtlasFeatureFilterCheckBox_stateChanged( int state );
void pageNameExpressionChanged( QString expression, bool valid );

private slots:
void updateGuiElements();
Expand Down
28 changes: 25 additions & 3 deletions src/app/composer/qgscomposer.cpp
Expand Up @@ -415,6 +415,7 @@ QgsComposer::QgsComposer( QgisApp *qgis, const QString& title )
mAtlasPageComboBox->setMinimumHeight( mAtlasToolbar->height() );
mAtlasPageComboBox->setMinimumContentsLength( 6 );
mAtlasPageComboBox->setMaxVisibleItems( 20 );
mAtlasPageComboBox->setSizeAdjustPolicy( QComboBox::AdjustToContents );
mAtlasPageComboBox->setInsertPolicy( QComboBox::NoInsert );
connect( mAtlasPageComboBox->lineEdit(), SIGNAL( editingFinished() ), this, SLOT( atlasPageComboEditingFinished() ) );
connect( mAtlasPageComboBox, SIGNAL( currentIndexChanged( QString ) ), this, SLOT( atlasPageComboEditingFinished() ) );
Expand Down Expand Up @@ -999,11 +1000,19 @@ void QgsComposer::updateAtlasPageComboBox( int pageCount )
if ( pageCount == mAtlasPageComboBox->count() )
return;

if ( !mComposition )
return;

mAtlasPageComboBox->blockSignals( true );
mAtlasPageComboBox->clear();
for ( int i = 1; i <= pageCount && i < 500; ++i )
{
mAtlasPageComboBox->addItem( QString::number( i ), i );
QString name = mComposition->atlasComposition().nameForPage( i - 1 );
QString fullName = ( !name.isEmpty() ? QString( "%1: %2" ).arg( i ).arg( name ) : QString::number( i ) );

mAtlasPageComboBox->addItem( fullName, i );
mAtlasPageComboBox->setItemData( i - 1, name, Qt::UserRole + 1 );
mAtlasPageComboBox->setItemData( i - 1, fullName, Qt::UserRole + 2 );
}
mAtlasPageComboBox->blockSignals( false );
}
Expand Down Expand Up @@ -1154,8 +1163,21 @@ void QgsComposer::on_mActionAtlasLast_triggered()
void QgsComposer::atlasPageComboEditingFinished()
{
QString text = mAtlasPageComboBox->lineEdit()->text();
bool ok = false;
int page = text.toInt( &ok );

//find matching record in combo box
int page = -1;
for ( int i = 0; i < mAtlasPageComboBox->count(); ++i )
{
if ( text.compare( mAtlasPageComboBox->itemData( i, Qt::UserRole + 1 ).toString(), Qt::CaseInsensitive ) == 0
|| text.compare( mAtlasPageComboBox->itemData( i, Qt::UserRole + 2 ).toString(), Qt::CaseInsensitive ) == 0
|| QString::number( i + 1 ) == text )
{
page = i + 1;
break;
}
}
bool ok = ( page > 0 );

if ( !ok || page >= mComposition->atlasComposition().numFeatures() || page < 1 )
{
mAtlasPageComboBox->blockSignals( true );
Expand Down
85 changes: 66 additions & 19 deletions src/core/composer/qgsatlascomposition.cpp
Expand Up @@ -28,15 +28,17 @@
#include "qgsproject.h"
#include "qgsmessagelog.h"

QgsAtlasComposition::QgsAtlasComposition( QgsComposition* composition ) :
mComposition( composition ),
mEnabled( false ),
mHideCoverage( false ), mFilenamePattern( "'output_'||$feature" ),
mCoverageLayer( 0 ), mSingleFile( false ),
mSortFeatures( false ), mSortAscending( true ), mCurrentFeatureNo( 0 ),
mFilterFeatures( false ), mFeatureFilter( "" ),
mFilenameParserError( QString() ),
mFilterParserError( QString() )
QgsAtlasComposition::QgsAtlasComposition( QgsComposition* composition )
: mComposition( composition )
, mEnabled( false )
, mHideCoverage( false )
, mFilenamePattern( "'output_'||$feature" )
, mCoverageLayer( 0 )
, mSingleFile( false )
, mSortFeatures( false )
, mSortAscending( true )
, mCurrentFeatureNo( 0 )
, mFilterFeatures( false )
{

// declare special columns with a default value
Expand Down Expand Up @@ -113,6 +115,14 @@ void QgsAtlasComposition::setCoverageLayer( QgsVectorLayer* layer )
emit coverageLayerChanged( layer );
}

QString QgsAtlasComposition::nameForPage( int pageNumber ) const
{
if ( pageNumber < 0 || pageNumber >= mFeatureIds.count() )
return QString();

return mFeatureIds.at( pageNumber ).second;
}

QgsComposerMap* QgsAtlasComposition::composerMap() const
{
//deprecated method. Until removed just return the first atlas-enabled composer map
Expand Down Expand Up @@ -175,21 +185,21 @@ class FieldSorter
public:
FieldSorter( QgsAtlasComposition::SorterKeys& keys, bool ascending = true ) : mKeys( keys ), mAscending( ascending ) {}

bool operator()( const QgsFeatureId& id1, const QgsFeatureId& id2 )
bool operator()( const QPair< QgsFeatureId, QString > & id1, const QPair< QgsFeatureId, QString >& id2 )
{
bool result = true;

if ( mKeys[ id1 ].type() == QVariant::Int )
if ( mKeys[ id1.first ].type() == QVariant::Int )
{
result = mKeys[ id1 ].toInt() < mKeys[ id2 ].toInt();
result = mKeys[ id1.first ].toInt() < mKeys[ id2.first ].toInt();
}
else if ( mKeys[ id1 ].type() == QVariant::Double )
else if ( mKeys[ id1.first ].type() == QVariant::Double )
{
result = mKeys[ id1 ].toDouble() < mKeys[ id2 ].toDouble();
result = mKeys[ id1.first ].toDouble() < mKeys[ id2.first ].toDouble();
}
else if ( mKeys[ id1 ].type() == QVariant::String )
else if ( mKeys[ id1.first ].type() == QVariant::String )
{
result = ( QString::localeAwareCompare( mKeys[ id1 ].toString(), mKeys[ id2 ].toString() ) < 0 );
result = ( QString::localeAwareCompare( mKeys[ id1.first ].toString(), mKeys[ id2.first ].toString() ) < 0 );
}

return mAscending ? result : !result;
Expand Down Expand Up @@ -225,14 +235,37 @@ int QgsAtlasComposition::updateFeatures()
}
mFilterParserError = QString();

QScopedPointer<QgsExpression> nameExpression;
if ( !mPageNameExpression.isEmpty() )
{
nameExpression.reset( new QgsExpression( mPageNameExpression ) );
if ( nameExpression->hasParserError() )
{
nameExpression.reset( 0 );
}
nameExpression->prepare( mCoverageLayer->pendingFields() );
}

// We cannot use nextFeature() directly since the feature pointer is rewinded by the rendering process
// We thus store the feature ids for future extraction
QgsFeature feat;
mFeatureIds.clear();
mFeatureKeys.clear();
int sortIdx = mCoverageLayer->fieldNameIndex( mSortKeyAttributeName );

while ( fit.nextFeature( feat ) )
{
QString pageName;
if ( !nameExpression.isNull() )
{
QVariant result = nameExpression->evaluate( &feat, mCoverageLayer->pendingFields() );
if ( nameExpression->hasEvalError() )
{
QgsMessageLog::logMessage( tr( "Atlas name eval error: %1" ).arg( nameExpression->evalErrorString() ), tr( "Composer" ) );
}
pageName = result.toString();
}

if ( !filterExpression.isNull() )
{
QVariant result = filterExpression->evaluate( &feat, mCoverageLayer->pendingFields() );
Expand All @@ -247,7 +280,8 @@ int QgsAtlasComposition::updateFeatures()
continue;
}
}
mFeatureIds.push_back( feat.id() );

mFeatureIds.push_back( qMakePair( feat.id(), pageName ) );

if ( mSortFeatures && sortIdx != -1 )
{
Expand Down Expand Up @@ -369,7 +403,18 @@ void QgsAtlasComposition::lastFeature()

bool QgsAtlasComposition::prepareForFeature( const QgsFeature * feat )
{
int featureI = mFeatureIds.indexOf( feat->id() );
int featureI = -1;
QVector< QPair<QgsFeatureId, QString> >::const_iterator it = mFeatureIds.constBegin();
int currentIdx = 0;
for ( ; it != mFeatureIds.constEnd(); ++it, ++currentIdx )
{
if (( *it ).first == feat->id() )
{
featureI = currentIdx;
break;
}
}

if ( featureI < 0 )
{
//feature not found
Expand Down Expand Up @@ -405,7 +450,7 @@ bool QgsAtlasComposition::prepareForFeature( const int featureI, const bool upda
mCurrentFeatureNo = featureI;

// retrieve the next feature, based on its id
mCoverageLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeatureIds[ featureI ] ) ).nextFeature( mCurrentFeature );
mCoverageLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeatureIds[ featureI ].first ) ).nextFeature( mCurrentFeature );

QgsExpression::setSpecialColumn( "$atlasfeatureid", mCurrentFeature.id() );
QgsExpression::setSpecialColumn( "$atlasgeometry", QVariant::fromValue( *mCurrentFeature.constGeometry() ) );
Expand Down Expand Up @@ -655,6 +700,7 @@ void QgsAtlasComposition::writeXML( QDomElement& elem, QDomDocument& doc ) const
atlasElem.setAttribute( "hideCoverage", mHideCoverage ? "true" : "false" );
atlasElem.setAttribute( "singleFile", mSingleFile ? "true" : "false" );
atlasElem.setAttribute( "filenamePattern", mFilenamePattern );
atlasElem.setAttribute( "pageNameExpression", mPageNameExpression );

atlasElem.setAttribute( "sortFeatures", mSortFeatures ? "true" : "false" );
if ( mSortFeatures )
Expand Down Expand Up @@ -693,6 +739,7 @@ void QgsAtlasComposition::readXML( const QDomElement& atlasElem, const QDomDocum
}
}

mPageNameExpression = atlasElem.attribute( "pageNameExpression", QString() );
mSingleFile = atlasElem.attribute( "singleFile", "false" ) == "true" ? true : false;
mFilenamePattern = atlasElem.attribute( "filenamePattern", "" );

Expand Down

1 comment on commit 69ac677

@nirvn
Copy link
Contributor

@nirvn nirvn commented on 69ac677 Aug 4, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yummy stuff, thanks.

Please sign in to comment.