Skip to content

Commit 69ac677

Browse files
committed
[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.
1 parent 5537e23 commit 69ac677

7 files changed

+304
-150
lines changed

python/core/composer/qgsatlascomposition.sip

+59-34
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,6 @@ public:
2828
*/
2929
void setEnabled( bool enabled );
3030

31-
/** Returns the map used by the atlas
32-
* @deprecated Use QgsComposerMap::atlasDriven() instead
33-
*/
34-
QgsComposerMap* composerMap() const /Deprecated/;
35-
36-
/** Sets the map used by the atlas
37-
* @deprecated Use QgsComposerMap::setAtlasDriven( true ) instead
38-
*/
39-
void setComposerMap( QgsComposerMap* map ) /Deprecated/;
40-
4131
/** Returns true if the atlas is set to hide the coverage layer
4232
* @returns true if coverage layer is hidden
4333
* @see setHideCoverage
@@ -48,27 +38,7 @@ public:
4838
* @param hide set to true to hide the coverage layer
4939
* @see hideCoverage
5040
*/
51-
void setHideCoverage( bool hide );
52-
53-
/** Returns whether the atlas map uses a fixed scale
54-
* @deprecated since 2.4 Use QgsComposerMap::atlasScalingMode() instead
55-
*/
56-
bool fixedScale() const /Deprecated/;
57-
58-
/** Sets whether the atlas map should use a fixed scale
59-
* @deprecated since 2.4 Use QgsComposerMap::setAtlasScalingMode() instead
60-
*/
61-
void setFixedScale( bool fixed ) /Deprecated/;
62-
63-
/** Returns the margin for the atlas map
64-
* @deprecated Use QgsComposerMap::atlasMargin() instead
65-
*/
66-
float margin() const /Deprecated/;
67-
68-
/** Sets the margin for the atlas map
69-
* @deprecated Use QgsComposerMap::setAtlasMargin( double ) instead
70-
*/
71-
void setMargin( float margin ) /Deprecated/;
41+
void setHideCoverage( bool hide );
7242

7343
/** Returns the filename expression used for generating output filenames for each
7444
* atlas page.
@@ -107,6 +77,29 @@ public:
10777
* @see coverageLayer
10878
*/
10979
void setCoverageLayer( QgsVectorLayer* layer );
80+
81+
/** Returns the expression used for calculating the page name.
82+
* @returns expression string, or field name from coverage layer
83+
* @see setPageNameExpression
84+
* @see nameForPage
85+
* @note added in QGIS 2.12
86+
*/
87+
QString pageNameExpression() const;
88+
89+
/** Sets the expression used for calculating the page name.
90+
* @param pageNameExpression expression string, or field name from coverage layer
91+
* @see pageNameExpression
92+
* @note added in QGIS 2.12
93+
*/
94+
void setPageNameExpression( const QString& pageNameExpression );
95+
96+
/** Returns the calculated name for a specified atlas page number.
97+
* @param pageNumber number of page, where 0 = first page
98+
* @returns page name
99+
* @see pageNameExpression
100+
* @note added in QGIS 2.12
101+
*/
102+
QString nameForPage( int pageNumber ) const;
110103

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

149-
int sortKeyAttributeIndex() const /Deprecated/;
150-
void setSortKeyAttributeIndex( int idx ) /Deprecated/;
151-
152142
/** Returns the current list of predefined scales for the atlas. This is used
153143
* for maps which are set to the predefined atlas scaling mode.
154144
* @returns a vector of doubles representing predefined scales
@@ -224,6 +214,41 @@ public:
224214

225215
/** Recalculates the bounds of an atlas driven map */
226216
void prepareMap( QgsComposerMap* map );
217+
218+
//Deprecated methods
219+
220+
/** Returns the map used by the atlas
221+
* @deprecated Use QgsComposerMap::atlasDriven() instead
222+
*/
223+
QgsComposerMap* composerMap() const /Deprecated/;
224+
225+
/** Sets the map used by the atlas
226+
* @deprecated Use QgsComposerMap::setAtlasDriven( true ) instead
227+
*/
228+
void setComposerMap( QgsComposerMap* map ) /Deprecated/;
229+
230+
/** Returns whether the atlas map uses a fixed scale
231+
* @deprecated since 2.4 Use QgsComposerMap::atlasScalingMode() instead
232+
*/
233+
bool fixedScale() const /Deprecated/;
234+
235+
/** Sets whether the atlas map should use a fixed scale
236+
* @deprecated since 2.4 Use QgsComposerMap::setAtlasScalingMode() instead
237+
*/
238+
void setFixedScale( bool fixed ) /Deprecated/;
239+
240+
/** Returns the margin for the atlas map
241+
* @deprecated Use QgsComposerMap::atlasMargin() instead
242+
*/
243+
float margin() const /Deprecated/;
244+
245+
/** Sets the margin for the atlas map
246+
* @deprecated Use QgsComposerMap::setAtlasMargin( double ) instead
247+
*/
248+
void setMargin( float margin ) /Deprecated/;
249+
250+
int sortKeyAttributeIndex() const /Deprecated/;
251+
void setSortKeyAttributeIndex( int idx ) /Deprecated/;
227252

228253
public slots:
229254

src/app/composer/qgsatlascompositionwidget.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ QgsAtlasCompositionWidget::QgsAtlasCompositionWidget( QWidget* parent, QgsCompos
3535
connect( mAtlasCoverageLayerComboBox, SIGNAL( layerChanged( QgsMapLayer* ) ), mAtlasSortFeatureKeyComboBox, SLOT( setLayer( QgsMapLayer* ) ) );
3636
connect( mAtlasCoverageLayerComboBox, SIGNAL( layerChanged( QgsMapLayer* ) ), this, SLOT( changeCoverageLayer( QgsMapLayer* ) ) );
3737
connect( mAtlasSortFeatureKeyComboBox, SIGNAL( fieldChanged( QString ) ), this, SLOT( changesSortFeatureField( QString ) ) );
38+
connect( mPageNameWidget, SIGNAL( fieldChanged( QString, bool ) ), this, SLOT( pageNameExpressionChanged( QString, bool ) ) );
3839

3940
// Sort direction
4041
mAtlasSortFeatureDirectionButton->setEnabled( false );
@@ -253,6 +254,17 @@ void QgsAtlasCompositionWidget::on_mAtlasFeatureFilterCheckBox_stateChanged( int
253254
updateAtlasFeatures();
254255
}
255256

257+
void QgsAtlasCompositionWidget::pageNameExpressionChanged( QString expression, bool valid )
258+
{
259+
QgsAtlasComposition* atlasMap = &mComposition->atlasComposition();
260+
if ( !atlasMap || ( !valid && !expression.isEmpty() ) )
261+
{
262+
return;
263+
}
264+
265+
atlasMap->setPageNameExpression( expression );
266+
}
267+
256268
void QgsAtlasCompositionWidget::on_mAtlasFeatureFilterEdit_editingFinished()
257269
{
258270
QgsAtlasComposition* atlasMap = &mComposition->atlasComposition();
@@ -313,6 +325,8 @@ void QgsAtlasCompositionWidget::updateGuiElements()
313325
mOutputGroup->setEnabled( atlasMap->enabled() );
314326

315327
mAtlasCoverageLayerComboBox->setLayer( atlasMap->coverageLayer() );
328+
mPageNameWidget->setLayer( atlasMap->coverageLayer() );
329+
mPageNameWidget->setField( atlasMap->pageNameExpression() );
316330

317331
mAtlasSortFeatureKeyComboBox->setLayer( atlasMap->coverageLayer() );
318332
mAtlasSortFeatureKeyComboBox->setField( atlasMap->sortKeyAttributeName() );
@@ -344,6 +358,7 @@ void QgsAtlasCompositionWidget::blockAllSignals( bool b )
344358
mConfigurationGroup->blockSignals( b );
345359
mOutputGroup->blockSignals( b );
346360
mAtlasCoverageLayerComboBox->blockSignals( b );
361+
mPageNameWidget->blockSignals( b );
347362
mAtlasSortFeatureKeyComboBox->blockSignals( b );
348363
mAtlasFilenamePatternEdit->blockSignals( b );
349364
mAtlasHideCoverageCheckBox->blockSignals( b );

src/app/composer/qgsatlascompositionwidget.h

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class QgsAtlasCompositionWidget:
4848
void on_mAtlasFeatureFilterEdit_editingFinished();
4949
void on_mAtlasFeatureFilterButton_clicked();
5050
void on_mAtlasFeatureFilterCheckBox_stateChanged( int state );
51+
void pageNameExpressionChanged( QString expression, bool valid );
5152

5253
private slots:
5354
void updateGuiElements();

src/app/composer/qgscomposer.cpp

+25-3
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,7 @@ QgsComposer::QgsComposer( QgisApp *qgis, const QString& title )
415415
mAtlasPageComboBox->setMinimumHeight( mAtlasToolbar->height() );
416416
mAtlasPageComboBox->setMinimumContentsLength( 6 );
417417
mAtlasPageComboBox->setMaxVisibleItems( 20 );
418+
mAtlasPageComboBox->setSizeAdjustPolicy( QComboBox::AdjustToContents );
418419
mAtlasPageComboBox->setInsertPolicy( QComboBox::NoInsert );
419420
connect( mAtlasPageComboBox->lineEdit(), SIGNAL( editingFinished() ), this, SLOT( atlasPageComboEditingFinished() ) );
420421
connect( mAtlasPageComboBox, SIGNAL( currentIndexChanged( QString ) ), this, SLOT( atlasPageComboEditingFinished() ) );
@@ -999,11 +1000,19 @@ void QgsComposer::updateAtlasPageComboBox( int pageCount )
9991000
if ( pageCount == mAtlasPageComboBox->count() )
10001001
return;
10011002

1003+
if ( !mComposition )
1004+
return;
1005+
10021006
mAtlasPageComboBox->blockSignals( true );
10031007
mAtlasPageComboBox->clear();
10041008
for ( int i = 1; i <= pageCount && i < 500; ++i )
10051009
{
1006-
mAtlasPageComboBox->addItem( QString::number( i ), i );
1010+
QString name = mComposition->atlasComposition().nameForPage( i - 1 );
1011+
QString fullName = ( !name.isEmpty() ? QString( "%1: %2" ).arg( i ).arg( name ) : QString::number( i ) );
1012+
1013+
mAtlasPageComboBox->addItem( fullName, i );
1014+
mAtlasPageComboBox->setItemData( i - 1, name, Qt::UserRole + 1 );
1015+
mAtlasPageComboBox->setItemData( i - 1, fullName, Qt::UserRole + 2 );
10071016
}
10081017
mAtlasPageComboBox->blockSignals( false );
10091018
}
@@ -1154,8 +1163,21 @@ void QgsComposer::on_mActionAtlasLast_triggered()
11541163
void QgsComposer::atlasPageComboEditingFinished()
11551164
{
11561165
QString text = mAtlasPageComboBox->lineEdit()->text();
1157-
bool ok = false;
1158-
int page = text.toInt( &ok );
1166+
1167+
//find matching record in combo box
1168+
int page = -1;
1169+
for ( int i = 0; i < mAtlasPageComboBox->count(); ++i )
1170+
{
1171+
if ( text.compare( mAtlasPageComboBox->itemData( i, Qt::UserRole + 1 ).toString(), Qt::CaseInsensitive ) == 0
1172+
|| text.compare( mAtlasPageComboBox->itemData( i, Qt::UserRole + 2 ).toString(), Qt::CaseInsensitive ) == 0
1173+
|| QString::number( i + 1 ) == text )
1174+
{
1175+
page = i + 1;
1176+
break;
1177+
}
1178+
}
1179+
bool ok = ( page > 0 );
1180+
11591181
if ( !ok || page >= mComposition->atlasComposition().numFeatures() || page < 1 )
11601182
{
11611183
mAtlasPageComboBox->blockSignals( true );

src/core/composer/qgsatlascomposition.cpp

+66-19
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,17 @@
2828
#include "qgsproject.h"
2929
#include "qgsmessagelog.h"
3030

31-
QgsAtlasComposition::QgsAtlasComposition( QgsComposition* composition ) :
32-
mComposition( composition ),
33-
mEnabled( false ),
34-
mHideCoverage( false ), mFilenamePattern( "'output_'||$feature" ),
35-
mCoverageLayer( 0 ), mSingleFile( false ),
36-
mSortFeatures( false ), mSortAscending( true ), mCurrentFeatureNo( 0 ),
37-
mFilterFeatures( false ), mFeatureFilter( "" ),
38-
mFilenameParserError( QString() ),
39-
mFilterParserError( QString() )
31+
QgsAtlasComposition::QgsAtlasComposition( QgsComposition* composition )
32+
: mComposition( composition )
33+
, mEnabled( false )
34+
, mHideCoverage( false )
35+
, mFilenamePattern( "'output_'||$feature" )
36+
, mCoverageLayer( 0 )
37+
, mSingleFile( false )
38+
, mSortFeatures( false )
39+
, mSortAscending( true )
40+
, mCurrentFeatureNo( 0 )
41+
, mFilterFeatures( false )
4042
{
4143

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

118+
QString QgsAtlasComposition::nameForPage( int pageNumber ) const
119+
{
120+
if ( pageNumber < 0 || pageNumber >= mFeatureIds.count() )
121+
return QString();
122+
123+
return mFeatureIds.at( pageNumber ).second;
124+
}
125+
116126
QgsComposerMap* QgsAtlasComposition::composerMap() const
117127
{
118128
//deprecated method. Until removed just return the first atlas-enabled composer map
@@ -175,21 +185,21 @@ class FieldSorter
175185
public:
176186
FieldSorter( QgsAtlasComposition::SorterKeys& keys, bool ascending = true ) : mKeys( keys ), mAscending( ascending ) {}
177187

178-
bool operator()( const QgsFeatureId& id1, const QgsFeatureId& id2 )
188+
bool operator()( const QPair< QgsFeatureId, QString > & id1, const QPair< QgsFeatureId, QString >& id2 )
179189
{
180190
bool result = true;
181191

182-
if ( mKeys[ id1 ].type() == QVariant::Int )
192+
if ( mKeys[ id1.first ].type() == QVariant::Int )
183193
{
184-
result = mKeys[ id1 ].toInt() < mKeys[ id2 ].toInt();
194+
result = mKeys[ id1.first ].toInt() < mKeys[ id2.first ].toInt();
185195
}
186-
else if ( mKeys[ id1 ].type() == QVariant::Double )
196+
else if ( mKeys[ id1.first ].type() == QVariant::Double )
187197
{
188-
result = mKeys[ id1 ].toDouble() < mKeys[ id2 ].toDouble();
198+
result = mKeys[ id1.first ].toDouble() < mKeys[ id2.first ].toDouble();
189199
}
190-
else if ( mKeys[ id1 ].type() == QVariant::String )
200+
else if ( mKeys[ id1.first ].type() == QVariant::String )
191201
{
192-
result = ( QString::localeAwareCompare( mKeys[ id1 ].toString(), mKeys[ id2 ].toString() ) < 0 );
202+
result = ( QString::localeAwareCompare( mKeys[ id1.first ].toString(), mKeys[ id2.first ].toString() ) < 0 );
193203
}
194204

195205
return mAscending ? result : !result;
@@ -225,14 +235,37 @@ int QgsAtlasComposition::updateFeatures()
225235
}
226236
mFilterParserError = QString();
227237

238+
QScopedPointer<QgsExpression> nameExpression;
239+
if ( !mPageNameExpression.isEmpty() )
240+
{
241+
nameExpression.reset( new QgsExpression( mPageNameExpression ) );
242+
if ( nameExpression->hasParserError() )
243+
{
244+
nameExpression.reset( 0 );
245+
}
246+
nameExpression->prepare( mCoverageLayer->pendingFields() );
247+
}
248+
228249
// We cannot use nextFeature() directly since the feature pointer is rewinded by the rendering process
229250
// We thus store the feature ids for future extraction
230251
QgsFeature feat;
231252
mFeatureIds.clear();
232253
mFeatureKeys.clear();
233254
int sortIdx = mCoverageLayer->fieldNameIndex( mSortKeyAttributeName );
255+
234256
while ( fit.nextFeature( feat ) )
235257
{
258+
QString pageName;
259+
if ( !nameExpression.isNull() )
260+
{
261+
QVariant result = nameExpression->evaluate( &feat, mCoverageLayer->pendingFields() );
262+
if ( nameExpression->hasEvalError() )
263+
{
264+
QgsMessageLog::logMessage( tr( "Atlas name eval error: %1" ).arg( nameExpression->evalErrorString() ), tr( "Composer" ) );
265+
}
266+
pageName = result.toString();
267+
}
268+
236269
if ( !filterExpression.isNull() )
237270
{
238271
QVariant result = filterExpression->evaluate( &feat, mCoverageLayer->pendingFields() );
@@ -247,7 +280,8 @@ int QgsAtlasComposition::updateFeatures()
247280
continue;
248281
}
249282
}
250-
mFeatureIds.push_back( feat.id() );
283+
284+
mFeatureIds.push_back( qMakePair( feat.id(), pageName ) );
251285

252286
if ( mSortFeatures && sortIdx != -1 )
253287
{
@@ -369,7 +403,18 @@ void QgsAtlasComposition::lastFeature()
369403

370404
bool QgsAtlasComposition::prepareForFeature( const QgsFeature * feat )
371405
{
372-
int featureI = mFeatureIds.indexOf( feat->id() );
406+
int featureI = -1;
407+
QVector< QPair<QgsFeatureId, QString> >::const_iterator it = mFeatureIds.constBegin();
408+
int currentIdx = 0;
409+
for ( ; it != mFeatureIds.constEnd(); ++it, ++currentIdx )
410+
{
411+
if (( *it ).first == feat->id() )
412+
{
413+
featureI = currentIdx;
414+
break;
415+
}
416+
}
417+
373418
if ( featureI < 0 )
374419
{
375420
//feature not found
@@ -405,7 +450,7 @@ bool QgsAtlasComposition::prepareForFeature( const int featureI, const bool upda
405450
mCurrentFeatureNo = featureI;
406451

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

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

659705
atlasElem.setAttribute( "sortFeatures", mSortFeatures ? "true" : "false" );
660706
if ( mSortFeatures )
@@ -693,6 +739,7 @@ void QgsAtlasComposition::readXML( const QDomElement& atlasElem, const QDomDocum
693739
}
694740
}
695741

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

0 commit comments

Comments
 (0)