Skip to content

Commit fe8385e

Browse files
committedSep 28, 2012
Merge pull request #245 from Oslandia/atlas_integration
[FEATURE] Support for creation of map atlasses in print composer - Atlas integration
2 parents 23352ce + 80eb345 commit fe8385e

36 files changed

+2045
-200
lines changed
 

‎python/core/qgsexpression.sip

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ class QgsExpression
4343
//! Return the number used for $rownum special column
4444
int currentRowNumber();
4545

46+
//! Assign a special column
47+
static void setSpecialColumn( const QString& name, QVariant value );
48+
//! Unset a special column
49+
static void unsetSpecialColumn( const QString& name );
50+
//! Return the value of the given special column or a null QVariant if undefined
51+
static QVariant specialColumn( const QString& name );
52+
4653
void setScale( double scale );
4754

4855
int scale();
@@ -64,7 +71,6 @@ class QgsExpression
6471
static QString replaceExpressionText( QString action, QgsFeature &feat,
6572
QgsVectorLayer* layer,
6673
const QMap<QString, QVariant> *substitutionMap = 0 );
67-
6874
//
6975

7076
enum UnaryOperator
@@ -141,6 +147,11 @@ class QgsExpression
141147
*/
142148
static int functionCount();
143149

150+
/**
151+
* Returns a list of special Column definitions
152+
*/
153+
static QList<QgsExpression::FunctionDef> specialColumns();
154+
144155
//! return quoted column reference (in double quotes)
145156
static QString quotedColumnRef( QString name );
146157
//! return quoted string (in single quotes)

‎src/app/composer/qgscomposer.cpp

Lines changed: 466 additions & 66 deletions
Large diffs are not rendered by default.

‎src/app/composer/qgscomposerlabelwidget.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include "qgscomposerlabelwidget.h"
1919
#include "qgscomposerlabel.h"
2020
#include "qgscomposeritemwidget.h"
21+
#include "qgsexpressionbuilderdialog.h"
22+
2123
#include <QColorDialog>
2224
#include <QFontDialog>
2325
#include <QWidget>
@@ -98,6 +100,33 @@ void QgsComposerLabelWidget::on_mFontColorButton_clicked()
98100
mComposerLabel->endCommand();
99101
}
100102

103+
void QgsComposerLabelWidget::on_mInsertExpressionButton_clicked()
104+
{
105+
if ( !mComposerLabel)
106+
{
107+
return;
108+
}
109+
110+
QString selText = mTextEdit->textCursor().selectedText();
111+
112+
// edit the selected expression if there's one
113+
if ( selText.startsWith( "[%" ) && selText.endsWith( "%]" ) )
114+
selText = selText.mid( 2, selText.size() - 4 );
115+
116+
QgsExpressionBuilderDialog exprDlg( /* layer = */ 0, selText, this );
117+
exprDlg.setWindowTitle( tr( "Insert expression" ) );
118+
if ( exprDlg.exec() == QDialog::Accepted )
119+
{
120+
QString expression = exprDlg.expressionText();
121+
if ( !expression.isEmpty() )
122+
{
123+
mComposerLabel->beginCommand( tr( "Insert expression" ) );
124+
mTextEdit->insertPlainText( "[%" + expression + "%]" );
125+
mComposerLabel->endCommand();
126+
}
127+
}
128+
}
129+
101130
void QgsComposerLabelWidget::on_mCenterRadioButton_clicked()
102131
{
103132
if ( mComposerLabel )

‎src/app/composer/qgscomposerlabelwidget.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class QgsComposerLabelWidget: public QWidget, private Ui::QgsComposerLabelWidget
3434
public slots:
3535
void on_mTextEdit_textChanged();
3636
void on_mFontButton_clicked();
37+
void on_mInsertExpressionButton_clicked();
3738
void on_mMarginDoubleSpinBox_valueChanged( double d );
3839
void on_mFontColorButton_clicked();
3940
void on_mCenterRadioButton_clicked();

‎src/app/composer/qgscomposermapwidget.cpp

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,15 @@
2323
//#include "qgssymbolv2propertiesdialog.h"
2424
#include "qgssymbolv2selectordialog.h"
2525
#include "qgssymbollayerv2utils.h"
26+
#include "qgsvectorlayer.h"
27+
#include "qgsvectordataprovider.h"
28+
#include "qgsmaplayerregistry.h"
29+
#include "qgscomposershape.h"
30+
#include "qgspaperitem.h"
31+
#include "qgsexpressionbuilderdialog.h"
2632
#include <QColorDialog>
2733
#include <QFontDialog>
34+
#include <QMessageBox>
2835

2936
QgsComposerMapWidget::QgsComposerMapWidget( QgsComposerMap* composerMap ): QWidget(), mComposerMap( composerMap )
3037
{
@@ -409,6 +416,29 @@ void QgsComposerMapWidget::updateGuiElements()
409416
mLineWidthSpinBox->setValue( gridPen.widthF() );
410417
mLineColorButton->setColor( gridPen.color() );
411418

419+
// special processing for atlas
420+
QgsComposition* composition = mComposerMap->composition();
421+
if ( composition->atlasMap() && composition->atlasMap() == mComposerMap )
422+
{
423+
mIsAtlasCheckBox->setCheckState( Qt::Checked );
424+
425+
int idx = mAtlasCoverageLayerComboBox->findData( qVariantFromValue( (void*)mComposerMap->atlasCoverageLayer() ));
426+
if ( idx != -1 )
427+
{
428+
mAtlasCoverageLayerComboBox->setCurrentIndex( idx );
429+
}
430+
431+
mAtlasMarginSpinBox->setValue( static_cast<int>(mComposerMap->atlasMargin() * 100) );
432+
mAtlasFilenamePatternEdit->setText( mComposerMap->atlasFilenamePattern() );
433+
mAtlasFixedScaleCheckBox->setCheckState( mComposerMap->atlasFixedScale() ? Qt::Checked : Qt::Unchecked );
434+
mAtlasHideCoverageCheckBox->setCheckState( mComposerMap->atlasHideCoverage() ? Qt::Checked : Qt::Unchecked );
435+
mAtlasSingleFileCheckBox->setCheckState( mComposerMap->atlasSingleFile() ? Qt::Checked : Qt::Unchecked );
436+
}
437+
else
438+
{
439+
mIsAtlasCheckBox->setCheckState( Qt::Unchecked );
440+
}
441+
412442
blockAllSignals( false );
413443
}
414444
}
@@ -897,12 +927,151 @@ void QgsComposerMapWidget::on_mFrameWidthSpinBox_valueChanged( double d )
897927
}
898928
}
899929

930+
void QgsComposerMapWidget::on_mIsAtlasCheckBox_stateChanged( int state )
931+
{
932+
if ( !mComposerMap )
933+
{
934+
return;
935+
}
936+
937+
QgsComposition* composition = mComposerMap->composition();
938+
if ( state == Qt::Checked )
939+
{
940+
if ( composition->atlasMap() != 0 && composition->atlasMap() != mComposerMap )
941+
{
942+
QMessageBox msgBox;
943+
msgBox.setText(tr("An atlas map already exists."));
944+
msgBox.setInformativeText("Are you sure to define this map as the new atlas map ?");
945+
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No );
946+
msgBox.setDefaultButton(QMessageBox::No);
947+
if ( msgBox.exec() != QMessageBox::Yes )
948+
{
949+
mIsAtlasCheckBox->setCheckState( Qt::Unchecked );
950+
return;
951+
}
952+
}
953+
composition->setAtlasMap( mComposerMap );
954+
955+
// repopulate the layer list
956+
mAtlasCoverageLayerComboBox->clear();
957+
QMap< QString, QgsMapLayer * >& layers = QgsMapLayerRegistry::instance()->mapLayers();
958+
int idx = 0;
959+
for ( QMap<QString, QgsMapLayer*>::const_iterator it = layers.begin(); it != layers.end(); ++it )
960+
{
961+
// Only consider vector layers
962+
if ( dynamic_cast<QgsVectorLayer*>(it.value()) )
963+
{
964+
mAtlasCoverageLayerComboBox->insertItem( idx++, it.key(), /* userdata */ qVariantFromValue( (void*)it.value() ) );
965+
}
966+
}
967+
968+
mAtlasFrame->setEnabled( true );
969+
updateGuiElements();
970+
}
971+
else
972+
{
973+
mAtlasFrame->setEnabled( false );
974+
975+
// If the current atlas map was this one and a uncheck is requested, set the atlas map to null
976+
if ( composition->atlasMap() == mComposerMap )
977+
{
978+
composition->setAtlasMap( 0 );
979+
}
980+
}
981+
}
982+
983+
void QgsComposerMapWidget::on_mAtlasCoverageLayerComboBox_currentIndexChanged( int index )
984+
{
985+
if ( !mComposerMap )
986+
{
987+
return;
988+
}
989+
990+
QgsVectorLayer* layer = reinterpret_cast<QgsVectorLayer*>(mAtlasCoverageLayerComboBox->itemData( index ).value<void*>());
991+
mComposerMap->setAtlasCoverageLayer( layer );
992+
}
993+
994+
void QgsComposerMapWidget::on_mAtlasFilenamePatternEdit_textChanged( const QString& text )
995+
{
996+
if ( !mComposerMap )
997+
{
998+
return;
999+
}
1000+
1001+
mComposerMap->setAtlasFilenamePattern( text );
1002+
}
1003+
1004+
void QgsComposerMapWidget::on_mAtlasFilenameExpressionButton_clicked()
1005+
{
1006+
if ( !mComposerMap )
1007+
{
1008+
return;
1009+
}
1010+
if ( !mComposerMap->atlasCoverageLayer() )
1011+
{
1012+
return;
1013+
}
1014+
QgsExpressionBuilderDialog exprDlg( mComposerMap->atlasCoverageLayer(), mAtlasFilenamePatternEdit->text(), this );
1015+
exprDlg.setWindowTitle( tr( "Expression based filename" ) );
1016+
if ( exprDlg.exec() == QDialog::Accepted )
1017+
{
1018+
QString expression = exprDlg.expressionText();
1019+
if ( !expression.isEmpty() )
1020+
{
1021+
// will emit a textChanged signal
1022+
mAtlasFilenamePatternEdit->setText( expression );
1023+
}
1024+
}
1025+
}
1026+
1027+
void QgsComposerMapWidget::on_mAtlasHideCoverageCheckBox_stateChanged( int state )
1028+
{
1029+
if (!mComposerMap)
1030+
{
1031+
return;
1032+
}
1033+
mComposerMap->setAtlasHideCoverage( state == Qt::Checked );
1034+
}
1035+
1036+
void QgsComposerMapWidget::on_mAtlasFixedScaleCheckBox_stateChanged( int state )
1037+
{
1038+
if (!mComposerMap)
1039+
{
1040+
return;
1041+
}
1042+
mComposerMap->setAtlasFixedScale( state == Qt::Checked );
1043+
1044+
// in fixed scale mode, the margin is meaningless
1045+
if ( state == Qt::Checked )
1046+
{
1047+
mAtlasMarginSpinBox->setEnabled( false );
1048+
}
1049+
else
1050+
{
1051+
mAtlasMarginSpinBox->setEnabled( true );
1052+
}
1053+
}
1054+
1055+
void QgsComposerMapWidget::on_mAtlasSingleFileCheckBox_stateChanged( int state )
1056+
{
1057+
if (!mComposerMap)
1058+
{
1059+
return;
1060+
}
1061+
mComposerMap->setAtlasSingleFile( state == Qt::Checked );
1062+
}
1063+
9001064
void QgsComposerMapWidget::showEvent( QShowEvent * event )
9011065
{
9021066
refreshMapComboBox();
9031067
QWidget::showEvent( event );
9041068
}
9051069

1070+
void QgsComposerMapWidget::addPageToToolbox( QWidget* widget, const QString& name )
1071+
{
1072+
toolBox->addItem( widget, name );
1073+
}
1074+
9061075
void QgsComposerMapWidget::insertAnnotationPositionEntries( QComboBox* c )
9071076
{
9081077
c->insertItem( 0, tr( "Inside frame" ) );

‎src/app/composer/qgscomposermapwidget.h

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class QgsComposerMapWidget: public QWidget, private Ui::QgsComposerMapWidgetBase
3131
public:
3232

3333
QgsComposerMapWidget( QgsComposerMap* composerMap );
34-
~QgsComposerMapWidget();
34+
virtual ~QgsComposerMapWidget();
3535

3636
public slots:
3737
void on_mWidthLineEdit_editingFinished();
@@ -83,9 +83,22 @@ class QgsComposerMapWidget: public QWidget, private Ui::QgsComposerMapWidgetBase
8383
void on_mFrameStyleComboBox_currentIndexChanged( const QString& text );
8484
void on_mFrameWidthSpinBox_valueChanged( double d );
8585

86+
void on_mIsAtlasCheckBox_stateChanged( int state );
87+
void on_mAtlasCoverageLayerComboBox_currentIndexChanged( int index );
88+
void on_mAtlasFilenamePatternEdit_textChanged( const QString& );
89+
void on_mAtlasFilenameExpressionButton_clicked();
90+
void on_mAtlasHideCoverageCheckBox_stateChanged( int state );
91+
void on_mAtlasFixedScaleCheckBox_stateChanged( int state );
92+
void on_mAtlasSingleFileCheckBox_stateChanged( int state );
93+
8694
protected:
8795
void showEvent( QShowEvent * event );
8896

97+
void addPageToToolbox( QWidget * widget, const QString& name );
98+
99+
/**Sets the current composer map values to the GUI elements*/
100+
virtual void updateGuiElements();
101+
89102
private slots:
90103

91104
/**Sets the GUI elements to the values of mPicture*/
@@ -94,9 +107,6 @@ class QgsComposerMapWidget: public QWidget, private Ui::QgsComposerMapWidgetBase
94107
private:
95108
QgsComposerMap* mComposerMap;
96109

97-
/**Sets the current composer map values to the GUI elements*/
98-
void updateGuiElements();
99-
100110
/**Sets extent of composer map from line edits*/
101111
void updateComposerExtentFromGui();
102112

‎src/core/composer/qgscomposeritem.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem
190190
virtual void removeItems() {}
191191

192192
const QgsComposition* composition() const {return mComposition;}
193+
QgsComposition* composition() {return mComposition;}
193194

194195
virtual void beginItemCommand( const QString& text ) { beginCommand( text ); }
195196

‎src/core/composer/qgscomposerlabel.cpp

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616
***************************************************************************/
1717

1818
#include "qgscomposerlabel.h"
19+
#include "qgsexpression.h"
1920
#include <QDate>
2021
#include <QDomElement>
2122
#include <QPainter>
2223

23-
QgsComposerLabel::QgsComposerLabel( QgsComposition *composition ): QgsComposerItem( composition ), mMargin( 1.0 ), mFontColor( QColor( 0, 0, 0 ) ),
24-
mHAlignment( Qt::AlignLeft ), mVAlignment( Qt::AlignTop )
24+
QgsComposerLabel::QgsComposerLabel( QgsComposition *composition ):
25+
QgsComposerItem( composition ), mMargin( 1.0 ), mFontColor( QColor( 0, 0, 0 ) ),
26+
mHAlignment( Qt::AlignLeft ), mVAlignment( Qt::AlignTop ),
27+
mExpressionFeature( 0 ), mExpressionLayer( 0 )
2528
{
2629
//default font size is 10 point
2730
mFont.setPointSizeF( 10 );
@@ -75,23 +78,36 @@ void QgsComposerLabel::setText( const QString& text )
7578
emit itemChanged();
7679
}
7780

81+
void QgsComposerLabel::setExpressionContext( QgsFeature* feature, QgsVectorLayer* layer, QMap<QString, QVariant> substitutions )
82+
{
83+
mExpressionFeature = feature;
84+
mExpressionLayer = layer;
85+
mSubstitutions = substitutions;
86+
}
87+
7888
QString QgsComposerLabel::displayText() const
7989
{
8090
QString displayText = mText;
8191
replaceDateText( displayText );
82-
return displayText;
92+
QMap<QString, QVariant> subs = mSubstitutions;
93+
subs[ "$page" ] = QVariant((int)mComposition->itemPageNumber( this ) + 1);
94+
return QgsExpression::replaceExpressionText( displayText, mExpressionFeature, mExpressionLayer, &subs );
8395
}
8496

8597
void QgsComposerLabel::replaceDateText( QString& text ) const
8698
{
87-
int currentDatePos = text.indexOf( "$CURRENT_DATE" );
99+
QString constant = "$CURRENT_DATE";
100+
int currentDatePos = text.indexOf( constant );
88101
if ( currentDatePos != -1 )
89102
{
90103
//check if there is a bracket just after $CURRENT_DATE
91104
QString formatText;
92105
int openingBracketPos = text.indexOf( "(", currentDatePos );
93106
int closingBracketPos = text.indexOf( ")", openingBracketPos + 1 );
94-
if ( openingBracketPos != -1 && closingBracketPos != -1 && ( closingBracketPos - openingBracketPos ) > 1 )
107+
if ( openingBracketPos != -1 &&
108+
closingBracketPos != -1 &&
109+
( closingBracketPos - openingBracketPos ) > 1 &&
110+
openingBracketPos == currentDatePos + constant.size() )
95111
{
96112
formatText = text.mid( openingBracketPos + 1, closingBracketPos - openingBracketPos - 1 );
97113
text.replace( currentDatePos, closingBracketPos - currentDatePos + 1, QDate::currentDate().toString( formatText ) );

‎src/core/composer/qgscomposerlabel.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919

2020
#include "qgscomposeritem.h"
2121

22+
class QgsVectorLayer;
23+
class QgsFeature;
24+
2225
/** \ingroup MapComposer
2326
* A label that can be placed onto a map composition.
2427
*/
@@ -45,6 +48,9 @@ class CORE_EXPORT QgsComposerLabel: public QgsComposerItem
4548
@note this function was added in version 1.2*/
4649
QString displayText() const;
4750

51+
/** Sets the current feature, the current layer and a list of local variable substitutions for evaluating expressions */
52+
void setExpressionContext( QgsFeature* feature, QgsVectorLayer* layer, QMap<QString, QVariant> substitutions = QMap<QString, QVariant>() );
53+
4854
QFont font() const;
4955
void setFont( const QFont& f );
5056
/** Accessor for the vertical alignment of the label
@@ -120,6 +126,10 @@ class CORE_EXPORT QgsComposerLabel: public QgsComposerItem
120126
double mTextBoxWidth;
121127
/**Height of the text box. This is different to rectangle().height() in case there is rotation*/
122128
double mTextBoxHeight;
129+
130+
QgsFeature* mExpressionFeature;
131+
QgsVectorLayer* mExpressionLayer;
132+
QMap<QString, QVariant> mSubstitutions;
123133
};
124134

125135
#endif

‎src/core/composer/qgscomposermap.cpp

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,14 @@
3838
#include <cmath>
3939

4040
QgsComposerMap::QgsComposerMap( QgsComposition *composition, int x, int y, int width, int height )
41-
: QgsComposerItem( x, y, width, height, composition ), mKeepLayerSet( false ), mOverviewFrameMapId( -1 ), mGridEnabled( false ), mGridStyle( Solid ),
41+
: QgsComposerItem( x, y, width, height, composition ), mKeepLayerSet( false ),
42+
mOverviewFrameMapId( -1 ), mGridEnabled( false ), mGridStyle( Solid ),
4243
mGridIntervalX( 0.0 ), mGridIntervalY( 0.0 ), mGridOffsetX( 0.0 ), mGridOffsetY( 0.0 ), mGridAnnotationPrecision( 3 ), mShowGridAnnotation( false ),
4344
mLeftGridAnnotationPosition( OutsideMapFrame ), mRightGridAnnotationPosition( OutsideMapFrame ), mTopGridAnnotationPosition( OutsideMapFrame ),
4445
mBottomGridAnnotationPosition( OutsideMapFrame ), mAnnotationFrameDistance( 1.0 ), mLeftGridAnnotationDirection( Horizontal ), mRightGridAnnotationDirection( Horizontal ),
4546
mTopGridAnnotationDirection( Horizontal ), mBottomGridAnnotationDirection( Horizontal ), mGridFrameStyle( NoGridFrame ), mGridFrameWidth( 2.0 ),
46-
mCrossLength( 3 ), mMapCanvas( 0 ), mDrawCanvasItems( true )
47+
mCrossLength( 3 ), mMapCanvas( 0 ), mDrawCanvasItems( true ),
48+
mAtlasHideCoverage( false ), mAtlasFixedScale( false ), mAtlasMargin( 0.10 ), mAtlasFilenamePattern("'output_'||$feature"), mAtlasCoverageLayer(0), mAtlasSingleFile( false )
4749
{
4850
mComposition = composition;
4951
mOverviewFrameMapSymbol = 0;
@@ -84,7 +86,8 @@ QgsComposerMap::QgsComposerMap( QgsComposition *composition )
8486
mLeftGridAnnotationPosition( OutsideMapFrame ), mRightGridAnnotationPosition( OutsideMapFrame ), mTopGridAnnotationPosition( OutsideMapFrame ),
8587
mBottomGridAnnotationPosition( OutsideMapFrame ), mAnnotationFrameDistance( 1.0 ), mLeftGridAnnotationDirection( Horizontal ), mRightGridAnnotationDirection( Horizontal ),
8688
mTopGridAnnotationDirection( Horizontal ), mBottomGridAnnotationDirection( Horizontal ), mGridFrameStyle( NoGridFrame ), mGridFrameWidth( 2.0 ), mCrossLength( 3 ),
87-
mMapCanvas( 0 ), mDrawCanvasItems( true )
89+
mMapCanvas( 0 ), mDrawCanvasItems( true ),
90+
mAtlasHideCoverage( false ), mAtlasFixedScale( false ), mAtlasMargin( 0.10 ), mAtlasFilenamePattern("'output_'||$feature"), mAtlasCoverageLayer(0), mAtlasSingleFile( false )
8891
{
8992
mOverviewFrameMapSymbol = 0;
9093
createDefaultOverviewFrameSymbol();
@@ -617,10 +620,26 @@ void QgsComposerMap::connectUpdateSlot()
617620
if ( layerRegistry )
618621
{
619622
connect( layerRegistry, SIGNAL( layerWillBeRemoved( QString ) ), this, SLOT( updateCachedImage() ) );
623+
connect( layerRegistry, SIGNAL( layerWillBeRemoved( QString ) ), this, SLOT( syncAtlasCoverageLayer( QString ) ) );
620624
connect( layerRegistry, SIGNAL( layerWasAdded( QgsMapLayer* ) ), this, SLOT( updateCachedImage() ) );
621625
}
622626
}
623627

628+
void QgsComposerMap::syncAtlasCoverageLayer( QString lname )
629+
{
630+
if ( mAtlasCoverageLayer && mAtlasCoverageLayer->id() == lname )
631+
{
632+
mAtlasCoverageLayer = 0;
633+
}
634+
}
635+
636+
void QgsComposerMap::setAtlasCoverageLayer( QgsVectorLayer* map )
637+
{
638+
mAtlasCoverageLayer = map;
639+
640+
emit atlasCoverageLayerChanged( map );
641+
}
642+
624643
bool QgsComposerMap::writeXML( QDomElement& elem, QDomDocument & doc ) const
625644
{
626645
if ( elem.isNull() )
@@ -728,6 +747,27 @@ bool QgsComposerMap::writeXML( QDomElement& elem, QDomDocument & doc ) const
728747
gridElem.appendChild( annotationElem );
729748
composerMapElem.appendChild( gridElem );
730749

750+
// atlas
751+
if ( mComposition->atlasMap() == this )
752+
{
753+
QDomElement atlasElem = doc.createElement( "Atlas" );
754+
if ( mAtlasCoverageLayer )
755+
{
756+
atlasElem.setAttribute( "coverageLayer", mAtlasCoverageLayer->id() );
757+
}
758+
else
759+
{
760+
atlasElem.setAttribute( "coverageLayer", "" );
761+
}
762+
atlasElem.setAttribute( "hideCoverage", mAtlasHideCoverage ? "true" : "false" );
763+
atlasElem.setAttribute( "fixedScale", mAtlasFixedScale ? "true" : "false" );
764+
atlasElem.setAttribute( "singleFile", mAtlasSingleFile ? "true" : "false" );
765+
atlasElem.setAttribute( "margin", QString::number(mAtlasMargin) );
766+
atlasElem.setAttribute( "filenamePattern", mAtlasFilenamePattern );
767+
768+
composerMapElem.appendChild( atlasElem );
769+
}
770+
731771
elem.appendChild( composerMapElem );
732772
return _writeXML( composerMapElem, doc );
733773
}
@@ -865,6 +905,32 @@ bool QgsComposerMap::readXML( const QDomElement& itemElem, const QDomDocument& d
865905
}
866906
}
867907

908+
// atlas
909+
QDomNodeList atlasNodeList = itemElem.elementsByTagName( "Atlas" );
910+
if ( atlasNodeList.size() > 0 )
911+
{
912+
mComposition->setAtlasMap( this );
913+
914+
QDomElement atlasElem = atlasNodeList.at( 0 ).toElement();
915+
916+
// look for stored layer name
917+
mAtlasCoverageLayer = 0;
918+
QMap<QString, QgsMapLayer*> layers = QgsMapLayerRegistry::instance()->mapLayers();
919+
for ( QMap<QString, QgsMapLayer*>::const_iterator it = layers.begin(); it != layers.end(); ++it )
920+
{
921+
if ( it.key() == atlasElem.attribute("coverageLayer") )
922+
{
923+
mAtlasCoverageLayer = dynamic_cast<QgsVectorLayer*>(it.value());
924+
break;
925+
}
926+
}
927+
mAtlasMargin = atlasElem.attribute( "margin", "0.0" ).toDouble();
928+
mAtlasHideCoverage = atlasElem.attribute( "hideCoverage", "false" ) == "true" ? true : false;
929+
mAtlasFixedScale = atlasElem.attribute( "fixedScale", "false" ) == "true" ? true : false;
930+
mAtlasSingleFile = atlasElem.attribute( "singleFile", "false" ) == "true" ? true : false;
931+
mAtlasFilenamePattern = atlasElem.attribute( "filenamePattern", "" );
932+
}
933+
868934
//restore general composer item properties
869935
QDomNodeList composerItemList = itemElem.elementsByTagName( "ComposerItem" );
870936
if ( composerItemList.size() > 0 )

‎src/core/composer/qgscomposermap.h

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class QDomDocument;
3030
class QGraphicsView;
3131
class QPainter;
3232
class QgsFillSymbolV2;
33+
class QgsVectorLayer;
3334

3435
/** \ingroup MapComposer
3536
* \class QgsComposerMap
@@ -45,7 +46,7 @@ class CORE_EXPORT QgsComposerMap : public QgsComposerItem
4546
QgsComposerMap( QgsComposition *composition, int x, int y, int width, int height );
4647
/** Constructor. Settings are read from project. */
4748
QgsComposerMap( QgsComposition *composition );
48-
~QgsComposerMap();
49+
virtual ~QgsComposerMap();
4950

5051
/** return correct graphics item type. Added in v1.7 */
5152
virtual int type() const { return ComposerMap; }
@@ -316,16 +317,39 @@ class CORE_EXPORT QgsComposerMap : public QgsComposerItem
316317
Usually, this function is called before adding the composer map to the composition*/
317318
void assignFreeId();
318319

320+
bool atlasHideCoverage() const { return mAtlasHideCoverage; }
321+
void setAtlasHideCoverage( bool hide ) { mAtlasHideCoverage = hide; }
322+
323+
bool atlasFixedScale() const { return mAtlasFixedScale; }
324+
void setAtlasFixedScale( bool fixed ) { mAtlasFixedScale = fixed; }
325+
326+
float atlasMargin() const { return mAtlasMargin; }
327+
void setAtlasMargin( float margin ) { mAtlasMargin = margin; }
328+
329+
QString atlasFilenamePattern() const { return mAtlasFilenamePattern; }
330+
void setAtlasFilenamePattern( const QString& pattern ) { mAtlasFilenamePattern = pattern; }
331+
332+
QgsVectorLayer* atlasCoverageLayer() const { return mAtlasCoverageLayer; }
333+
void setAtlasCoverageLayer( QgsVectorLayer* lmap );
334+
335+
bool atlasSingleFile() const { return mAtlasSingleFile; }
336+
void setAtlasSingleFile( bool single ) { mAtlasSingleFile = single; }
337+
319338
signals:
320339
void extentChanged();
321340

341+
void atlasCoverageLayerChanged( QgsVectorLayer* );
342+
322343
public slots:
323344

324345
/**Called if map canvas has changed*/
325346
void updateCachedImage( );
326347
/**Call updateCachedImage if item is in render mode*/
327348
void renderModeUpdateCachedImage();
328349

350+
private slots:
351+
void syncAtlasCoverageLayer( QString );
352+
329353
private:
330354

331355
enum AnnotationCoordinate
@@ -438,6 +462,13 @@ class CORE_EXPORT QgsComposerMap : public QgsComposerItem
438462
/**True if annotation items, rubber band, etc. from the main canvas should be displayed*/
439463
bool mDrawCanvasItems;
440464

465+
bool mAtlasHideCoverage;
466+
bool mAtlasFixedScale;
467+
double mAtlasMargin;
468+
QString mAtlasFilenamePattern;
469+
QgsVectorLayer* mAtlasCoverageLayer;
470+
bool mAtlasSingleFile;
471+
441472
/**Draws the map grid*/
442473
void drawGrid( QPainter* p );
443474
void drawGridFrame( QPainter* p, const QList< QPair< double, QLineF > >& hLines, const QList< QPair< double, QLineF > >& vLines );

‎src/core/composer/qgscomposition.cpp

Lines changed: 324 additions & 18 deletions
Large diffs are not rendered by default.

‎src/core/composer/qgscomposition.h

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@
1616
#ifndef QGSCOMPOSITION_H
1717
#define QGSCOMPOSITION_H
1818

19+
#include <memory>
20+
1921
#include <QDomDocument>
2022
#include <QGraphicsScene>
2123
#include <QLinkedList>
2224
#include <QSet>
2325
#include <QUndoStack>
26+
#include <QPrinter>
27+
#include <QPainter>
2428

2529
#include "qgsaddremoveitemcommand.h"
2630
#include "qgscomposeritemcommand.h"
@@ -44,6 +48,37 @@ class QgsComposerShape;
4448
class QgsComposerAttributeTable;
4549
class QgsComposerMultiFrame;
4650
class QgsComposerMultiFrameCommand;
51+
class QgsVectorLayer;
52+
53+
/** \ingroup MapComposer
54+
* Class used to render an Atlas, iterating over geometry features.
55+
* prepareForFeature() modifies the atlas map's extent to zoom on the given feature.
56+
* This class is used for printing, exporting to PDF and images.
57+
* */
58+
class QgsAtlasRendering
59+
{
60+
public:
61+
QgsAtlasRendering( QgsComposition* composition );
62+
63+
/** Begins the rendering. Sets an optional output filename pattern */
64+
void begin( const QString& filenamePattern = "" );
65+
/** Ends the rendering. Restores original extent*/
66+
void end();
67+
68+
/** Returns the number of features in the coverage layer */
69+
size_t numFeatures() const;
70+
71+
/** Prepare the atlas map for the given feature. Sets the extent and context variables */
72+
void prepareForFeature( size_t i );
73+
74+
/** Returns the current filename. Must be called after prepareForFeature( i ) */
75+
const QString& currentFilename() const;
76+
77+
private:
78+
// Use the PImpl idiom for private members.
79+
struct QgsAtlasRenderingImpl;
80+
std::auto_ptr<QgsAtlasRenderingImpl> impl;
81+
};
4782

4883
/** \ingroup MapComposer
4984
* Graphics scene for map printing. The class manages the paper item which always
@@ -115,6 +150,12 @@ class CORE_EXPORT QgsComposition: public QGraphicsScene
115150
/**Returns the topmost composer item. Ignores mPaperItem*/
116151
QgsComposerItem* composerItemAt( const QPointF & position );
117152

153+
/** Returns the page number (0-bsaed) given a coordinate */
154+
int pageNumberAt( const QPointF& position ) const;
155+
156+
/** Returns on which page number (0-based) is displayed an item */
157+
int itemPageNumber( const QgsComposerItem* ) const;
158+
118159
QList<QgsComposerItem*> selectedComposerItems();
119160

120161
/**Returns pointers to all composer maps in the scene
@@ -159,6 +200,9 @@ class CORE_EXPORT QgsComposition: public QGraphicsScene
159200
/**Returns pointer to map renderer of qgis map canvas*/
160201
QgsMapRenderer* mapRenderer() {return mMapRenderer;}
161202

203+
QgsComposerMap* atlasMap() { return mAtlasMap; }
204+
void setAtlasMap( QgsComposerMap* map );
205+
162206
QgsComposition::PlotStyle plotStyle() const {return mPlotStyle;}
163207
void setPlotStyle( QgsComposition::PlotStyle style ) {mPlotStyle = style;}
164208

@@ -271,10 +315,19 @@ class CORE_EXPORT QgsComposition: public QGraphicsScene
271315

272316
//printing
273317

274-
void exportAsPDF( const QString& file );
318+
/** Prepare the printer for printing */
319+
void beginPrint( QPrinter& printer );
320+
/** Prepare the printer for printing in a PDF */
321+
void beginPrintAsPDF( QPrinter& printer, const QString& file );
322+
/** Print on a preconfigured printer */
323+
void doPrint( QPrinter& printer, QPainter& painter );
275324

325+
/** Convenience function that prepares the printer and prints */
276326
void print( QPrinter &printer );
277327

328+
/** Convenience function that prepares the printer for printing in PDF and prints */
329+
void exportAsPDF( const QString& file );
330+
278331
//! print composer page to image
279332
//! If the image does not fit into memory, a null image is returned
280333
QImage printPageAsRaster( int page );
@@ -287,6 +340,9 @@ class CORE_EXPORT QgsComposition: public QGraphicsScene
287340
/**Casts object to the proper subclass type and calls corresponding itemAdded signal*/
288341
void sendItemAddedSignal( QgsComposerItem* item );
289342

343+
private slots:
344+
void onAtlasCoverageChanged( QgsVectorLayer* );
345+
290346
private:
291347
/**Pointer to map renderer of QGIS main map*/
292348
QgsMapRenderer* mMapRenderer;
@@ -324,6 +380,8 @@ class CORE_EXPORT QgsComposition: public QGraphicsScene
324380
QgsComposerItemCommand* mActiveItemCommand;
325381
QgsComposerMultiFrameCommand* mActiveMultiFrameCommand;
326382

383+
QgsComposerMap* mAtlasMap;
384+
327385
QgsComposition(); //default constructor is forbidden
328386

329387
/**Reset z-values of items based on position in z list*/

‎src/core/qgsexpression.cpp

Lines changed: 107 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,19 @@ static QVariant fcnFormatNumber( const QVariantList& values, QgsFeature*, QgsExp
799799
return QString( "%L1" ).arg( value, 0, 'f', places );
800800
}
801801

802+
static QVariant fcnFormatDate( const QVariantList& values, QgsFeature*, QgsExpression* parent )
803+
{
804+
QDateTime dt = getDateTimeValue( values.at( 0 ), parent );
805+
QString format = getStringValue( values.at( 1 ), parent );
806+
return dt.toString( format );
807+
}
808+
809+
static QVariant fcnSpecialColumn( const QVariantList& values, QgsFeature* /*f*/, QgsExpression* parent )
810+
{
811+
QString varName = getStringValue( values.at( 0 ), parent );
812+
return QgsExpression::specialColumn( varName );
813+
}
814+
802815
QList<QgsExpression::FunctionDef> QgsExpression::gmBuiltinFunctions;
803816

804817
const QList<QgsExpression::FunctionDef> &QgsExpression::BuiltinFunctions()
@@ -855,6 +868,7 @@ const QList<QgsExpression::FunctionDef> &QgsExpression::BuiltinFunctions()
855868
<< FunctionDef( "rpad", 3, fcnRPad, QObject::tr( "String" ) )
856869
<< FunctionDef( "lpad", 3, fcnLPad, QObject::tr( "String" ) )
857870
<< FunctionDef( "format_number", 2, fcnFormatNumber, QObject::tr( "String" ) )
871+
<< FunctionDef( "format_date", 2, fcnFormatDate, QObject::tr( "String" ) )
858872

859873
// geometry accessors
860874
<< FunctionDef( "xat", 1, fcnXat, QObject::tr( "Geometry" ), "", true )
@@ -868,12 +882,61 @@ const QList<QgsExpression::FunctionDef> &QgsExpression::BuiltinFunctions()
868882
<< FunctionDef( "$rownum", 0, fcnRowNumber, QObject::tr( "Record" ) )
869883
<< FunctionDef( "$id", 0, fcnFeatureId, QObject::tr( "Record" ) )
870884
<< FunctionDef( "$scale", 0, fcnScale, QObject::tr( "Record" ) )
885+
// private functions
886+
<< FunctionDef( "_specialcol_", 1, fcnSpecialColumn, QObject::tr( "Special" ) )
871887
;
872888
}
873889

874890
return gmBuiltinFunctions;
875891
}
876892

893+
QMap<QString, QVariant> QgsExpression::gmSpecialColumns;
894+
895+
void QgsExpression::setSpecialColumn( const QString& name, QVariant variant )
896+
{
897+
int fnIdx = functionIndex( name );
898+
if ( fnIdx != -1 )
899+
{
900+
// function of the same name already exists
901+
return;
902+
}
903+
gmSpecialColumns[ name ] = variant;
904+
}
905+
906+
void QgsExpression::unsetSpecialColumn( const QString& name )
907+
{
908+
QMap<QString, QVariant>::iterator fit = gmSpecialColumns.find( name );
909+
if ( fit != gmSpecialColumns.end() )
910+
{
911+
gmSpecialColumns.erase( fit );
912+
}
913+
}
914+
915+
QVariant QgsExpression::specialColumn( const QString& name )
916+
{
917+
int fnIdx = functionIndex( name );
918+
if ( fnIdx != -1 )
919+
{
920+
// function of the same name already exists
921+
return QVariant();
922+
}
923+
QMap<QString, QVariant>::iterator it = gmSpecialColumns.find( name );
924+
if ( it == gmSpecialColumns.end() )
925+
{
926+
return QVariant();
927+
}
928+
return it.value();
929+
}
930+
931+
QList<QgsExpression::FunctionDef> QgsExpression::specialColumns()
932+
{
933+
QList<FunctionDef> defs;
934+
for ( QMap<QString, QVariant>::const_iterator it = gmSpecialColumns.begin(); it != gmSpecialColumns.end(); ++it )
935+
{
936+
defs << FunctionDef( it.key(), 0, 0, QObject::tr( "Record" ));
937+
}
938+
return defs;
939+
}
877940

878941
bool QgsExpression::isFunctionName( QString name )
879942
{
@@ -1049,12 +1112,27 @@ void QgsExpression::acceptVisitor( QgsExpression::Visitor& v )
10491112
mRootNode->accept( v );
10501113
}
10511114

1052-
QString QgsExpression::replaceExpressionText( QString action, QgsFeature &feat,
1115+
QString QgsExpression::replaceExpressionText( QString action, QgsFeature* feat,
10531116
QgsVectorLayer* layer,
10541117
const QMap<QString, QVariant> *substitutionMap )
10551118
{
10561119
QString expr_action;
10571120

1121+
QMap<QString, QVariant> savedValues;
1122+
if ( substitutionMap )
1123+
{
1124+
// variables with a local scope (must be restored after evaluation)
1125+
for ( QMap<QString, QVariant>::const_iterator sit = substitutionMap->begin(); sit != substitutionMap->end(); ++sit )
1126+
{
1127+
QVariant oldValue = QgsExpression::specialColumn( sit.key() );
1128+
if ( !oldValue.isNull() )
1129+
savedValues.insert( sit.key(), oldValue );
1130+
1131+
// set the new value
1132+
QgsExpression::setSpecialColumn( sit.key(), sit.value() );
1133+
}
1134+
}
1135+
10581136
int index = 0;
10591137
while ( index < action.size() )
10601138
{
@@ -1070,12 +1148,6 @@ QString QgsExpression::replaceExpressionText( QString action, QgsFeature &feat,
10701148
QString to_replace = rx.cap( 1 ).trimmed();
10711149
QgsDebugMsg( "Found expression: " + to_replace );
10721150

1073-
if ( substitutionMap && substitutionMap->contains( to_replace ) )
1074-
{
1075-
expr_action += action.mid( start, pos - start ) + substitutionMap->value( to_replace ).toString();
1076-
continue;
1077-
}
1078-
10791151
QgsExpression exp( to_replace );
10801152
if ( exp.hasParserError() )
10811153
{
@@ -1084,7 +1156,15 @@ QString QgsExpression::replaceExpressionText( QString action, QgsFeature &feat,
10841156
continue;
10851157
}
10861158

1087-
QVariant result = exp.evaluate( &feat, layer->pendingFields() );
1159+
QVariant result;
1160+
if ( layer )
1161+
{
1162+
result = exp.evaluate( feat, layer->pendingFields() );
1163+
}
1164+
else
1165+
{
1166+
result = exp.evaluate( feat );
1167+
}
10881168
if ( exp.hasEvalError() )
10891169
{
10901170
QgsDebugMsg( "Expression parser eval error: " + exp.evalErrorString() );
@@ -1097,10 +1177,24 @@ QString QgsExpression::replaceExpressionText( QString action, QgsFeature &feat,
10971177
}
10981178

10991179
expr_action += action.mid( index );
1180+
1181+
// restore overwritten local values
1182+
for ( QMap<QString, QVariant>::const_iterator sit = savedValues.begin(); sit != savedValues.end(); ++sit )
1183+
{
1184+
QgsExpression::setSpecialColumn( sit.key(), sit.value() );
1185+
}
1186+
11001187
return expr_action;
11011188
}
11021189

11031190

1191+
QString QgsExpression::replaceExpressionText( QString action, QgsFeature& feat,
1192+
QgsVectorLayer* layer,
1193+
const QMap<QString, QVariant> *substitutionMap )
1194+
{
1195+
return replaceExpressionText( action, &feat, layer, substitutionMap );
1196+
}
1197+
11041198
QgsExpression::Node* QgsExpression::Node::createFromOgcFilter( QDomElement &element, QString &errorMessage )
11051199
{
11061200
if ( element.isNull() )
@@ -2048,7 +2142,11 @@ QgsExpression::Node* QgsExpression::NodeLiteral::createFromOgcFilter( QDomElemen
20482142

20492143
QVariant QgsExpression::NodeColumnRef::eval( QgsExpression* /*parent*/, QgsFeature* f )
20502144
{
2051-
return f->attributeMap()[mIndex];
2145+
if ( f )
2146+
{
2147+
return f->attributeMap()[mIndex];
2148+
}
2149+
return QVariant("[" + mName + "]");
20522150
}
20532151

20542152
bool QgsExpression::NodeColumnRef::prepare( QgsExpression* parent, const QgsFieldMap& fields )

‎src/core/qgsexpression.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ class CORE_EXPORT QgsExpression
122122
//! Return the number used for $rownum special column
123123
int currentRowNumber() { return mRowNumber; }
124124

125+
//! Assign a special column
126+
static void setSpecialColumn( const QString& name, QVariant value );
127+
//! Unset a special column
128+
static void unsetSpecialColumn( const QString& name );
129+
//! Return the value of the given special column or a null QVariant if undefined
130+
static QVariant specialColumn( const QString& name );
131+
125132
void setScale( double scale ) { mScale = scale; }
126133

127134
int scale() {return mScale; }
@@ -140,10 +147,14 @@ class CORE_EXPORT QgsExpression
140147
Additional substitutions can be passed through the substitutionMap
141148
parameter
142149
*/
143-
static QString replaceExpressionText( QString action, QgsFeature &feat,
150+
static QString replaceExpressionText( QString action, QgsFeature* feat,
144151
QgsVectorLayer* layer,
145152
const QMap<QString, QVariant> *substitutionMap = 0 );
146153

154+
155+
static QString replaceExpressionText( QString action, QgsFeature& feat,
156+
QgsVectorLayer* layer,
157+
const QMap<QString, QVariant> *substitutionMap = 0 );
147158
//
148159

149160
enum UnaryOperator
@@ -226,6 +237,11 @@ class CORE_EXPORT QgsExpression
226237
*/
227238
static int functionCount();
228239

240+
/**
241+
* Returns a list of special Column definitions
242+
*/
243+
static QList<FunctionDef> specialColumns();
244+
229245
//! return quoted column reference (in double quotes)
230246
static QString quotedColumnRef( QString name ) { return QString( "\"%1\"" ).arg( name.replace( "\"", "\"\"" ) ); }
231247
//! return quoted string (in single quotes)
@@ -532,6 +548,8 @@ class CORE_EXPORT QgsExpression
532548
int mRowNumber;
533549
double mScale;
534550

551+
static QMap<QString, QVariant> gmSpecialColumns;
552+
535553
QgsDistanceArea* mCalc;
536554
};
537555

‎src/core/qgsexpressionparser.yy

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,23 @@ expression:
193193
int fnIndex = QgsExpression::functionIndex(*$1);
194194
if (fnIndex == -1)
195195
{
196-
exp_error("Special column is not known");
197-
YYERROR;
196+
QVariant userVar = QgsExpression::specialColumn( *$1 );
197+
if ( userVar.isNull() )
198+
{
199+
exp_error("Special column is not known");
200+
YYERROR;
201+
}
202+
// $var is equivalent to _specialcol_( "$var" )
203+
QgsExpression::NodeList* args = new QgsExpression::NodeList();
204+
QgsExpression::NodeLiteral* literal = new QgsExpression::NodeLiteral( *$1 );
205+
args->append( literal );
206+
$$ = new QgsExpression::NodeFunction( QgsExpression::functionIndex( "_specialcol_" ), args );
198207
}
199-
$$ = new QgsExpression::NodeFunction( fnIndex, NULL );
200-
delete $1;
208+
else
209+
{
210+
$$ = new QgsExpression::NodeFunction( fnIndex, NULL );
211+
delete $1;
212+
}
201213
}
202214

203215
// literals

‎src/gui/qgscomposerview.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -311,13 +311,13 @@ void QgsComposerView::mouseReleaseEvent( QMouseEvent* e )
311311
}
312312
if ( composition() )
313313
{
314-
QgsComposerMap* composerMap = new QgsComposerMap( composition(), mRubberBandItem->transform().dx(), mRubberBandItem->transform().dy(), mRubberBandItem->rect().width(), mRubberBandItem->rect().height() );
315-
composition()->addComposerMap( composerMap );
316-
scene()->removeItem( mRubberBandItem );
317-
delete mRubberBandItem;
318-
mRubberBandItem = 0;
319-
emit actionFinished();
320-
composition()->pushAddRemoveCommand( composerMap, tr( "Map added" ) );
314+
QgsComposerMap* composerMap = new QgsComposerMap( composition(), mRubberBandItem->transform().dx(), mRubberBandItem->transform().dy(), mRubberBandItem->rect().width(), mRubberBandItem->rect().height() );
315+
composition()->addComposerMap( composerMap );
316+
scene()->removeItem( mRubberBandItem );
317+
delete mRubberBandItem;
318+
mRubberBandItem = 0;
319+
emit actionFinished();
320+
composition()->pushAddRemoveCommand( composerMap, tr( "Map added" ) );
321321
}
322322
break;
323323

‎src/gui/qgsexpressionbuilderwidget.cpp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,20 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent )
9090
{
9191
QgsExpression::FunctionDef func = QgsExpression::BuiltinFunctions()[i];
9292
QString name = func.mName;
93+
if ( name.startsWith( "_" ) ) // do not display private functions
94+
continue;
9395
if ( func.mParams >= 1 )
9496
name += "(";
9597
registerItem( func.mGroup, func.mName, " " + name + " " );
9698
}
9799

100+
QList<QgsExpression::FunctionDef> specials = QgsExpression::specialColumns();
101+
for ( int i = 0; i < specials.size(); ++i )
102+
{
103+
QString name = specials[i].mName;
104+
registerItem( specials[i].mGroup, name, " " + name + " " );
105+
}
106+
98107
#if QT_VERSION >= 0x040700
99108
txtSearchEdit->setPlaceholderText( tr( "Search" ) );
100109
#endif
@@ -259,8 +268,6 @@ void QgsExpressionBuilderWidget::on_txtExpressionString_textChanged()
259268

260269
QgsExpression exp( text );
261270

262-
// TODO We could do this without a layer.
263-
// Maybe just calling exp.evaluate()?
264271
if ( mLayer )
265272
{
266273
if ( !mFeature.isValid() )
@@ -282,6 +289,15 @@ void QgsExpressionBuilderWidget::on_txtExpressionString_textChanged()
282289
lblPreview->setText( "" );
283290
}
284291
}
292+
else
293+
{
294+
// No layer defined
295+
QVariant value = exp.evaluate();
296+
if ( !exp.hasEvalError() )
297+
{
298+
lblPreview->setText( value.toString() );
299+
}
300+
}
285301

286302
if ( exp.hasParserError() || exp.hasEvalError() )
287303
{

‎src/ui/qgscomposerlabelwidgetbase.ui

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
<rect>
77
<x>0</x>
88
<y>0</y>
9-
<width>274</width>
10-
<height>488</height>
9+
<width>307</width>
10+
<height>525</height>
1111
</rect>
1212
</property>
1313
<property name="sizePolicy">
@@ -30,8 +30,8 @@
3030
<rect>
3131
<x>0</x>
3232
<y>0</y>
33-
<width>271</width>
34-
<height>470</height>
33+
<width>276</width>
34+
<height>503</height>
3535
</rect>
3636
</property>
3737
<attribute name="label">
@@ -45,14 +45,14 @@
4545
</property>
4646
</widget>
4747
</item>
48-
<item row="2" column="0" colspan="2">
48+
<item row="3" column="0" colspan="2">
4949
<widget class="QPushButton" name="mFontColorButton">
5050
<property name="text">
5151
<string>Font color...</string>
5252
</property>
5353
</widget>
5454
</item>
55-
<item row="3" column="0" colspan="2">
55+
<item row="4" column="0" colspan="2">
5656
<widget class="QGroupBox" name="buttonGroup1">
5757
<property name="title">
5858
<string>Horizontal Alignment:</string>
@@ -98,7 +98,7 @@
9898
</layout>
9999
</widget>
100100
</item>
101-
<item row="4" column="0" colspan="2">
101+
<item row="5" column="0" colspan="2">
102102
<widget class="QGroupBox" name="buttonGroup2">
103103
<property name="title">
104104
<string>Vertical Alignment:</string>
@@ -141,7 +141,7 @@
141141
</layout>
142142
</widget>
143143
</item>
144-
<item row="5" column="0" colspan="2">
144+
<item row="6" column="0" colspan="2">
145145
<widget class="QDoubleSpinBox" name="mMarginDoubleSpinBox">
146146
<property name="prefix">
147147
<string>Margin </string>
@@ -151,7 +151,7 @@
151151
</property>
152152
</widget>
153153
</item>
154-
<item row="6" column="0">
154+
<item row="7" column="0">
155155
<widget class="QLabel" name="mRotationLabel">
156156
<property name="sizePolicy">
157157
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
@@ -170,14 +170,14 @@
170170
</property>
171171
</widget>
172172
</item>
173-
<item row="6" column="1">
173+
<item row="7" column="1">
174174
<widget class="QDoubleSpinBox" name="mRotationSpinBox">
175175
<property name="maximum">
176176
<double>360.000000000000000</double>
177177
</property>
178178
</widget>
179179
</item>
180-
<item row="1" column="0" colspan="2">
180+
<item row="2" column="0" colspan="2">
181181
<widget class="QPushButton" name="mFontButton">
182182
<property name="sizePolicy">
183183
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
@@ -190,6 +190,13 @@
190190
</property>
191191
</widget>
192192
</item>
193+
<item row="1" column="0" colspan="2">
194+
<widget class="QPushButton" name="mInsertExpressionButton">
195+
<property name="text">
196+
<string>Insert an expression</string>
197+
</property>
198+
</widget>
199+
</item>
193200
</layout>
194201
</widget>
195202
</widget>

‎src/ui/qgscomposerlegendwidgetbase.ui

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@
5050
<property name="geometry">
5151
<rect>
5252
<x>0</x>
53-
<y>-109</y>
54-
<width>370</width>
55-
<height>523</height>
53+
<y>0</y>
54+
<width>374</width>
55+
<height>549</height>
5656
</rect>
5757
</property>
5858
<attribute name="label">
@@ -220,8 +220,8 @@
220220
<rect>
221221
<x>0</x>
222222
<y>0</y>
223-
<width>367</width>
224-
<height>170</height>
223+
<width>393</width>
224+
<height>162</height>
225225
</rect>
226226
</property>
227227
<attribute name="label">

‎src/ui/qgscomposermapwidgetbase.ui

Lines changed: 212 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
<rect>
77
<x>0</x>
88
<y>0</y>
9-
<width>265</width>
10-
<height>483</height>
9+
<width>450</width>
10+
<height>501</height>
1111
</rect>
1212
</property>
1313
<property name="sizePolicy">
@@ -25,23 +25,55 @@
2525
</property>
2626
<item row="0" column="0">
2727
<widget class="QToolBox" name="toolBox">
28-
<property name="currentIndex">
28+
<property name="lineWidth">
2929
<number>0</number>
3030
</property>
31+
<property name="currentIndex">
32+
<number>3</number>
33+
</property>
3134
<widget class="QWidget" name="page">
3235
<property name="geometry">
3336
<rect>
3437
<x>0</x>
3538
<y>0</y>
36-
<width>255</width>
37-
<height>392</height>
39+
<width>437</width>
40+
<height>408</height>
3841
</rect>
3942
</property>
4043
<attribute name="label">
4144
<string>Map</string>
4245
</attribute>
4346
<layout class="QGridLayout" name="gridLayout">
44-
<item row="7" column="0" colspan="2">
47+
<item row="7" column="1">
48+
<widget class="QComboBox" name="mOverviewFrameMapComboBox"/>
49+
</item>
50+
<item row="4" column="0">
51+
<widget class="QComboBox" name="mPreviewModeComboBox">
52+
<property name="sizePolicy">
53+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
54+
<horstretch>0</horstretch>
55+
<verstretch>0</verstretch>
56+
</sizepolicy>
57+
</property>
58+
</widget>
59+
</item>
60+
<item row="0" column="0" colspan="2">
61+
<spacer name="verticalSpacer_3">
62+
<property name="orientation">
63+
<enum>Qt::Vertical</enum>
64+
</property>
65+
<property name="sizeType">
66+
<enum>QSizePolicy::Fixed</enum>
67+
</property>
68+
<property name="sizeHint" stdset="0">
69+
<size>
70+
<width>20</width>
71+
<height>10</height>
72+
</size>
73+
</property>
74+
</spacer>
75+
</item>
76+
<item row="9" column="0" colspan="2">
4577
<spacer name="verticalSpacer">
4678
<property name="orientation">
4779
<enum>Qt::Vertical</enum>
@@ -57,24 +89,35 @@
5789
</property>
5890
</spacer>
5991
</item>
60-
<item row="2" column="0">
61-
<widget class="QComboBox" name="mPreviewModeComboBox">
62-
<property name="sizePolicy">
63-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
64-
<horstretch>0</horstretch>
65-
<verstretch>0</verstretch>
66-
</sizepolicy>
92+
<item row="4" column="1">
93+
<widget class="QPushButton" name="mUpdatePreviewButton">
94+
<property name="text">
95+
<string>Update preview</string>
6796
</property>
6897
</widget>
6998
</item>
70-
<item row="2" column="1">
71-
<widget class="QPushButton" name="mUpdatePreviewButton">
99+
<item row="8" column="0">
100+
<widget class="QLabel" name="mOverviewFrameStyleLabel">
72101
<property name="text">
73-
<string>Update preview</string>
102+
<string>Overview style</string>
74103
</property>
75104
</widget>
76105
</item>
77-
<item row="4" column="0" colspan="2">
106+
<item row="8" column="1">
107+
<widget class="QPushButton" name="mOverviewFrameStyleButton">
108+
<property name="text">
109+
<string>Change...</string>
110+
</property>
111+
</widget>
112+
</item>
113+
<item row="7" column="0">
114+
<widget class="QLabel" name="mOverviewFrameMapLabel">
115+
<property name="text">
116+
<string>Overview frame</string>
117+
</property>
118+
</widget>
119+
</item>
120+
<item row="6" column="0" colspan="2">
78121
<layout class="QFormLayout" name="formLayout_2">
79122
<property name="fieldGrowthPolicy">
80123
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
@@ -188,23 +231,7 @@
188231
</item>
189232
</layout>
190233
</item>
191-
<item row="0" column="0" colspan="2">
192-
<spacer name="verticalSpacer_3">
193-
<property name="orientation">
194-
<enum>Qt::Vertical</enum>
195-
</property>
196-
<property name="sizeType">
197-
<enum>QSizePolicy::Fixed</enum>
198-
</property>
199-
<property name="sizeHint" stdset="0">
200-
<size>
201-
<width>20</width>
202-
<height>10</height>
203-
</size>
204-
</property>
205-
</spacer>
206-
</item>
207-
<item row="3" column="0" colspan="2">
234+
<item row="5" column="0" colspan="2">
208235
<spacer name="verticalSpacer_4">
209236
<property name="orientation">
210237
<enum>Qt::Vertical</enum>
@@ -220,39 +247,15 @@
220247
</property>
221248
</spacer>
222249
</item>
223-
<item row="5" column="1">
224-
<widget class="QComboBox" name="mOverviewFrameMapComboBox"/>
225-
</item>
226-
<item row="5" column="0">
227-
<widget class="QLabel" name="mOverviewFrameMapLabel">
228-
<property name="text">
229-
<string>Overview frame</string>
230-
</property>
231-
</widget>
232-
</item>
233-
<item row="6" column="0">
234-
<widget class="QLabel" name="mOverviewFrameStyleLabel">
235-
<property name="text">
236-
<string>Overview style</string>
237-
</property>
238-
</widget>
239-
</item>
240-
<item row="6" column="1">
241-
<widget class="QPushButton" name="mOverviewFrameStyleButton">
242-
<property name="text">
243-
<string>Change...</string>
244-
</property>
245-
</widget>
246-
</item>
247250
</layout>
248251
</widget>
249252
<widget class="QWidget" name="page_2">
250253
<property name="geometry">
251254
<rect>
252255
<x>0</x>
253256
<y>0</y>
254-
<width>255</width>
255-
<height>392</height>
257+
<width>450</width>
258+
<height>377</height>
256259
</rect>
257260
</property>
258261
<attribute name="label">
@@ -359,8 +362,8 @@
359362
<rect>
360363
<x>0</x>
361364
<y>0</y>
362-
<width>238</width>
363-
<height>770</height>
365+
<width>437</width>
366+
<height>893</height>
364367
</rect>
365368
</property>
366369
<attribute name="label">
@@ -787,6 +790,153 @@
787790
</item>
788791
</layout>
789792
</widget>
793+
<widget class="QWidget" name="page_4">
794+
<property name="geometry">
795+
<rect>
796+
<x>0</x>
797+
<y>0</y>
798+
<width>450</width>
799+
<height>377</height>
800+
</rect>
801+
</property>
802+
<attribute name="label">
803+
<string>Atlas</string>
804+
</attribute>
805+
<layout class="QGridLayout" name="gridLayout_9">
806+
<item row="4" column="0">
807+
<spacer name="verticalSpacer_5">
808+
<property name="orientation">
809+
<enum>Qt::Vertical</enum>
810+
</property>
811+
<property name="sizeHint" stdset="0">
812+
<size>
813+
<width>20</width>
814+
<height>76</height>
815+
</size>
816+
</property>
817+
</spacer>
818+
</item>
819+
<item row="0" column="0">
820+
<widget class="QCheckBox" name="mIsAtlasCheckBox">
821+
<property name="text">
822+
<string>Make it the atlas map</string>
823+
</property>
824+
</widget>
825+
</item>
826+
<item row="1" column="0">
827+
<widget class="QFrame" name="mAtlasFrame">
828+
<property name="enabled">
829+
<bool>false</bool>
830+
</property>
831+
<property name="sizePolicy">
832+
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
833+
<horstretch>0</horstretch>
834+
<verstretch>1</verstretch>
835+
</sizepolicy>
836+
</property>
837+
<property name="autoFillBackground">
838+
<bool>false</bool>
839+
</property>
840+
<property name="frameShape">
841+
<enum>QFrame::NoFrame</enum>
842+
</property>
843+
<property name="frameShadow">
844+
<enum>QFrame::Raised</enum>
845+
</property>
846+
<property name="lineWidth">
847+
<number>0</number>
848+
</property>
849+
<layout class="QVBoxLayout" name="verticalLayout">
850+
<property name="spacing">
851+
<number>6</number>
852+
</property>
853+
<property name="margin">
854+
<number>0</number>
855+
</property>
856+
<item>
857+
<layout class="QGridLayout" name="gridLayout_7" rowstretch="0,0,0,0,0,0,0,0,0" columnstretch="0,0,0">
858+
<item row="1" column="0" colspan="2">
859+
<widget class="QCheckBox" name="mAtlasHideCoverageCheckBox">
860+
<property name="toolTip">
861+
<string>Hide the coverage layer when generating the output</string>
862+
</property>
863+
<property name="text">
864+
<string>Hidden coverage layer</string>
865+
</property>
866+
</widget>
867+
</item>
868+
<item row="3" column="0">
869+
<widget class="QLabel" name="label">
870+
<property name="text">
871+
<string>Margin around coverage</string>
872+
</property>
873+
</widget>
874+
</item>
875+
<item row="6" column="0">
876+
<widget class="QLabel" name="label_5">
877+
<property name="text">
878+
<string>Output filename expression</string>
879+
</property>
880+
</widget>
881+
</item>
882+
<item row="3" column="1">
883+
<widget class="QSpinBox" name="mAtlasMarginSpinBox">
884+
<property name="suffix">
885+
<string> %</string>
886+
</property>
887+
<property name="maximum">
888+
<number>100</number>
889+
</property>
890+
<property name="value">
891+
<number>10</number>
892+
</property>
893+
</widget>
894+
</item>
895+
<item row="6" column="2">
896+
<widget class="QToolButton" name="mAtlasFilenameExpressionButton">
897+
<property name="text">
898+
<string>...</string>
899+
</property>
900+
</widget>
901+
</item>
902+
<item row="6" column="1">
903+
<widget class="QLineEdit" name="mAtlasFilenamePatternEdit"/>
904+
</item>
905+
<item row="0" column="0">
906+
<widget class="QLabel" name="label_6">
907+
<property name="text">
908+
<string>Coverage layer</string>
909+
</property>
910+
</widget>
911+
</item>
912+
<item row="0" column="1">
913+
<widget class="QComboBox" name="mAtlasCoverageLayerComboBox">
914+
<property name="contextMenuPolicy">
915+
<enum>Qt::NoContextMenu</enum>
916+
</property>
917+
</widget>
918+
</item>
919+
<item row="4" column="0" colspan="2">
920+
<widget class="QCheckBox" name="mAtlasFixedScaleCheckBox">
921+
<property name="text">
922+
<string>Fixed scale</string>
923+
</property>
924+
</widget>
925+
</item>
926+
<item row="7" column="0" colspan="2">
927+
<widget class="QCheckBox" name="mAtlasSingleFileCheckBox">
928+
<property name="text">
929+
<string>Single file export when possible</string>
930+
</property>
931+
</widget>
932+
</item>
933+
</layout>
934+
</item>
935+
</layout>
936+
</widget>
937+
</item>
938+
</layout>
939+
</widget>
790940
</widget>
791941
</item>
792942
</layout>

‎tests/src/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ ADD_QGIS_TEST(rulebasedrenderertest testqgsrulebasedrenderer.cpp)
101101
ADD_QGIS_TEST(ziplayertest testziplayer.cpp)
102102
ADD_QGIS_TEST(dataitemtest testqgsdataitem.cpp)
103103
ADD_QGIS_TEST(composermaptest testqgscomposermap.cpp)
104+
ADD_QGIS_TEST(composermapatlastest testqgscomposermapatlas.cpp)
105+
ADD_QGIS_TEST(composerlabeltest testqgscomposerlabel.cpp)
104106
ADD_QGIS_TEST(stylev2test testqgsstylev2.cpp)
105107
#ADD_QGIS_TEST(composerhtmltest testqgscomposerhtml.cpp )
106108
ADD_QGIS_TEST(rectangletest testqgsrectangle.cpp)
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/***************************************************************************
2+
testqgscomposerlabel.cpp
3+
----------------------
4+
begin : Sept 2012
5+
copyright : (C) 2012 by Hugo Mercier
6+
email : hugo dot mercier at oslandia dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#include "qgsapplication.h"
19+
#include "qgscomposition.h"
20+
#include "qgscomposerlabel.h"
21+
#include "qgsmaplayerregistry.h"
22+
#include "qgsmaprenderer.h"
23+
#include "qgsvectorlayer.h"
24+
#include "qgsvectordataprovider.h"
25+
#include <QObject>
26+
#include <QtTest>
27+
28+
class TestQgsComposerLabel: public QObject
29+
{
30+
Q_OBJECT;
31+
private slots:
32+
void initTestCase();// will be called before the first testfunction is executed.
33+
void cleanupTestCase();// will be called after the last testfunction was executed.
34+
void init();// will be called before each testfunction is executed.
35+
void cleanup();// will be called after every testfunction.
36+
37+
// test simple expression evaluation
38+
void evaluation();
39+
// test expression evaluation when a feature is set
40+
void feature_evaluation();
41+
// test "$page" expressions
42+
void page_evaluation();
43+
private:
44+
QgsComposition* mComposition;
45+
QgsComposerLabel* mComposerLabel;
46+
QgsMapRenderer* mMapRenderer;
47+
QgsVectorLayer* mVectorLayer;
48+
};
49+
50+
void TestQgsComposerLabel::initTestCase()
51+
{
52+
QgsApplication::init();
53+
QgsApplication::initQgis();
54+
55+
//create maplayers from testdata and add to layer registry
56+
QFileInfo vectorFileInfo( QString( TEST_DATA_DIR ) + QDir::separator() + "france_parts.shp" );
57+
mVectorLayer = new QgsVectorLayer( vectorFileInfo.filePath(),
58+
vectorFileInfo.completeBaseName(),
59+
"ogr" );
60+
QgsMapLayerRegistry::instance()->addMapLayers( QList<QgsMapLayer*>() << mVectorLayer );
61+
62+
//create composition with composer map
63+
mMapRenderer = new QgsMapRenderer();
64+
mMapRenderer->setLayerSet( QStringList() << mVectorLayer->id() );
65+
mMapRenderer->setProjectionsEnabled( false );
66+
mComposition = new QgsComposition( mMapRenderer );
67+
mComposition->setPaperSize( 297, 210 ); //A4 landscape
68+
69+
mComposerLabel = new QgsComposerLabel( mComposition );
70+
mComposition->addComposerLabel( mComposerLabel );
71+
}
72+
73+
void TestQgsComposerLabel::cleanupTestCase()
74+
{
75+
delete mComposition;
76+
delete mMapRenderer;
77+
delete mVectorLayer;
78+
}
79+
80+
void TestQgsComposerLabel::init()
81+
{
82+
83+
}
84+
85+
void TestQgsComposerLabel::cleanup()
86+
{
87+
88+
}
89+
90+
void TestQgsComposerLabel::evaluation()
91+
{
92+
{
93+
// $CURRENT_DATE evaluation
94+
QString expected = "__" + QDate::currentDate().toString() + "__";
95+
mComposerLabel->setText( "__$CURRENT_DATE__" );
96+
QString evaluated = mComposerLabel->displayText();
97+
QCOMPARE( evaluated, expected );
98+
}
99+
{
100+
// $CURRENT_DATE() evaluation
101+
QDateTime now = QDateTime::currentDateTime();
102+
QString expected = "__" + now.toString( "dd" ) + "(ok)__";
103+
mComposerLabel->setText( "__$CURRENT_DATE(dd)(ok)__" );
104+
QString evaluated = mComposerLabel->displayText();
105+
QCOMPARE( evaluated, expected );
106+
}
107+
{
108+
// $CURRENT_DATE() evaluation (inside an expression)
109+
QDate now = QDate::currentDate();
110+
int dd = now.day();
111+
112+
QString expected = "__" + QString("%1").arg(dd+1, 2, 10, QChar('0')) + "(ok)__";
113+
mComposerLabel->setText( "__[%$CURRENT_DATE(dd) + 1%](ok)__" );
114+
QString evaluated = mComposerLabel->displayText();
115+
QCOMPARE( evaluated, expected );
116+
}
117+
{
118+
// expression evaluation (without feature)
119+
QString expected = "__[NAME_1]42__";
120+
mComposerLabel->setText( "__[%\"NAME_1\"%][%21*2%]__" );
121+
QString evaluated = mComposerLabel->displayText();
122+
QCOMPARE( evaluated, expected );
123+
}
124+
}
125+
126+
void TestQgsComposerLabel::feature_evaluation()
127+
{
128+
QgsVectorDataProvider* provider = mVectorLayer->dataProvider();
129+
130+
QgsAttributeList allAttrs = provider->attributeIndexes();
131+
provider->select( allAttrs );
132+
QgsFeature feat;
133+
134+
provider->nextFeature( feat );
135+
{
136+
// evaluation with a feature
137+
mComposerLabel->setExpressionContext( &feat, mVectorLayer );
138+
mComposerLabel->setText( "[%\"NAME_1\"||'_ok'%]" );
139+
QString evaluated = mComposerLabel->displayText();
140+
QString expected = "Basse-Normandie_ok";
141+
QCOMPARE( evaluated, expected );
142+
}
143+
provider->nextFeature( feat );
144+
{
145+
// evaluation with a feature
146+
mComposerLabel->setExpressionContext( &feat, mVectorLayer );
147+
mComposerLabel->setText( "[%\"NAME_1\"||'_ok'%]" );
148+
QString evaluated = mComposerLabel->displayText();
149+
QString expected = "Bretagne_ok";
150+
QCOMPARE( evaluated, expected );
151+
}
152+
{
153+
// evaluation with a feature and local variables
154+
QMap<QString, QVariant> locals;
155+
locals.insert( "$test", "OK" );
156+
157+
mComposerLabel->setExpressionContext( &feat, mVectorLayer, locals );
158+
mComposerLabel->setText( "[%\"NAME_1\"||$test%]" );
159+
QString evaluated = mComposerLabel->displayText();
160+
QString expected = "BretagneOK";
161+
QCOMPARE( evaluated, expected );
162+
}
163+
}
164+
165+
void TestQgsComposerLabel::page_evaluation()
166+
{
167+
mComposition->setNumPages( 2 );
168+
{
169+
mComposerLabel->setText( "[%$page||'/'||$numpages%]" );
170+
QString evaluated = mComposerLabel->displayText();
171+
QString expected = "1/2";
172+
QCOMPARE( evaluated, expected );
173+
174+
// move to the second page and re-evaluate
175+
mComposerLabel->setItemPosition( 0, 320 );
176+
QCOMPARE( mComposerLabel->displayText(), QString("2/2") );
177+
}
178+
}
179+
180+
QTEST_MAIN( TestQgsComposerLabel )
181+
#include "moc_testqgscomposerlabel.cxx"
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/***************************************************************************
2+
testqgscomposermapatlas.cpp
3+
---------------------------
4+
begin : Sept 2012
5+
copyright : (C) 2012 by Hugo Mercier
6+
email : hugo dot mercier at oslandia dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#include "qgsapplication.h"
19+
#include "qgscomposition.h"
20+
#include "qgscompositionchecker.h"
21+
#include "qgscomposermap.h"
22+
#include "qgscomposerlabel.h"
23+
#include "qgsmaplayerregistry.h"
24+
#include "qgsmaprenderer.h"
25+
#include "qgsvectorlayer.h"
26+
#include "qgsvectordataprovider.h"
27+
#include "qgssymbolv2.h"
28+
#include "qgssinglesymbolrendererv2.h"
29+
#include <QObject>
30+
#include <QtTest>
31+
32+
class TestQgsComposerMapAtlas: public QObject
33+
{
34+
Q_OBJECT;
35+
private slots:
36+
void initTestCase();// will be called before the first testfunction is executed.
37+
void cleanupTestCase();// will be called after the last testfunction was executed.
38+
void init();// will be called before each testfunction is executed.
39+
void cleanup();// will be called after every testfunction.
40+
41+
// test filename pattern evaluation
42+
void filename();
43+
// test rendering with an autoscale atlas
44+
void autoscale_render();
45+
// test rendering with a fixed scale atlas
46+
void fixedscale_render();
47+
// test rendering with a hidden coverage
48+
void hiding_render();
49+
private:
50+
QgsComposition* mComposition;
51+
QgsComposerLabel* mLabel1;
52+
QgsComposerLabel* mLabel2;
53+
QgsComposerMap* mAtlasMap;
54+
QgsComposerMap* mOverview;
55+
QgsMapRenderer* mMapRenderer;
56+
QgsVectorLayer* mVectorLayer;
57+
};
58+
59+
void TestQgsComposerMapAtlas::initTestCase()
60+
{
61+
QgsApplication::init();
62+
QgsApplication::initQgis();
63+
64+
//create maplayers from testdata and add to layer registry
65+
QFileInfo vectorFileInfo( QString( TEST_DATA_DIR ) + QDir::separator() + "france_parts.shp" );
66+
mVectorLayer = new QgsVectorLayer( vectorFileInfo.filePath(),
67+
vectorFileInfo.completeBaseName(),
68+
"ogr" );
69+
70+
QgsMapLayerRegistry::instance()->addMapLayers( QList<QgsMapLayer*>() << mVectorLayer );
71+
72+
//create composition with composer map
73+
mMapRenderer = new QgsMapRenderer();
74+
mMapRenderer->setLayerSet( QStringList() << mVectorLayer->id() );
75+
mMapRenderer->setProjectionsEnabled( true );
76+
77+
// select epsg:2154
78+
QgsCoordinateReferenceSystem crs;
79+
crs.createFromSrid( 2154 );
80+
mMapRenderer->setDestinationCrs( crs );
81+
mComposition = new QgsComposition( mMapRenderer );
82+
mComposition->setPaperSize( 297, 210 ); //A4 landscape
83+
84+
// fix the renderer, fill with green
85+
QgsStringMap props;
86+
props.insert( "color", "0,127,0" );
87+
QgsFillSymbolV2* fillSymbol = QgsFillSymbolV2::createSimple( props );
88+
QgsSingleSymbolRendererV2* renderer = new QgsSingleSymbolRendererV2( fillSymbol );
89+
mVectorLayer->setRendererV2( renderer );
90+
91+
// the atlas map
92+
mAtlasMap = new QgsComposerMap( mComposition, 20, 20, 130, 130 );
93+
mAtlasMap->setFrameEnabled( true );
94+
mAtlasMap->setAtlasCoverageLayer( mVectorLayer );
95+
mComposition->addComposerMap( mAtlasMap );
96+
mComposition->setAtlasMap( mAtlasMap );
97+
98+
// an overview
99+
mOverview = new QgsComposerMap( mComposition, 180, 20, 50, 50 );
100+
mOverview->setFrameEnabled( true );
101+
mOverview->setOverviewFrameMap( mAtlasMap->id() );
102+
mComposition->addComposerMap( mOverview );
103+
mOverview->setNewExtent( QgsRectangle( 49670.718, 6415139.086, 699672.519, 7065140.887 ) );
104+
105+
// header label
106+
mLabel1 = new QgsComposerLabel( mComposition );
107+
mComposition->addComposerLabel( mLabel1 );
108+
mLabel1->setText( "[% \"NAME_1\" %] area" );
109+
mLabel1->adjustSizeToText();
110+
mLabel1->setItemPosition( 150, 5 );
111+
112+
// feature number label
113+
mLabel2 = new QgsComposerLabel( mComposition );
114+
mComposition->addComposerLabel( mLabel2 );
115+
mLabel2->setText( "# [%$feature || ' / ' || $numfeatures%]" );
116+
mLabel2->adjustSizeToText();
117+
mLabel2->setItemPosition( 150, 200 );
118+
}
119+
120+
void TestQgsComposerMapAtlas::cleanupTestCase()
121+
{
122+
delete mComposition;
123+
delete mMapRenderer;
124+
delete mVectorLayer;
125+
}
126+
127+
void TestQgsComposerMapAtlas::init()
128+
{
129+
130+
}
131+
132+
void TestQgsComposerMapAtlas::cleanup()
133+
{
134+
135+
}
136+
137+
void TestQgsComposerMapAtlas::filename()
138+
{
139+
QgsAtlasRendering atlasRender( mComposition );
140+
atlasRender.begin( "'output_' || $feature" );
141+
for ( size_t fi = 0; fi < atlasRender.numFeatures(); ++fi )
142+
{
143+
atlasRender.prepareForFeature( fi );
144+
QString expected = QString( "output_%1" ).arg( (int)(fi+1) );
145+
QCOMPARE( atlasRender.currentFilename(), expected );
146+
}
147+
atlasRender.end();
148+
}
149+
150+
151+
void TestQgsComposerMapAtlas::autoscale_render()
152+
{
153+
mAtlasMap->setAtlasFixedScale( false );
154+
mAtlasMap->setAtlasMargin( 0.10 );
155+
156+
QgsAtlasRendering atlasRender( mComposition );
157+
158+
atlasRender.begin();
159+
160+
for ( size_t fit = 0; fit < 2; ++fit )
161+
{
162+
atlasRender.prepareForFeature( fit );
163+
mLabel1->adjustSizeToText();
164+
165+
QgsCompositionChecker checker( "Atlas autoscale test", mComposition,
166+
QString( TEST_DATA_DIR ) + QDir::separator() + "control_images" + QDir::separator() +
167+
"expected_composermapatlas" + QDir::separator() +
168+
QString( "autoscale_%1.png" ).arg((int)fit) );
169+
QVERIFY( checker.testComposition( 0 ) );
170+
}
171+
atlasRender.end();
172+
}
173+
174+
void TestQgsComposerMapAtlas::fixedscale_render()
175+
{
176+
mAtlasMap->setNewExtent( QgsRectangle( 209838.166, 6528781.020, 610491.166, 6920530.620 ) );
177+
mAtlasMap->setAtlasFixedScale( true );
178+
179+
QgsAtlasRendering atlasRender( mComposition );
180+
181+
atlasRender.begin();
182+
183+
for ( size_t fit = 0; fit < 2; ++fit )
184+
{
185+
atlasRender.prepareForFeature( fit );
186+
mLabel1->adjustSizeToText();
187+
188+
QgsCompositionChecker checker( "Atlas fixedscale test", mComposition,
189+
QString( TEST_DATA_DIR ) + QDir::separator() + "control_images" + QDir::separator() +
190+
"expected_composermapatlas" + QDir::separator() +
191+
QString( "fixedscale_%1.png" ).arg((int)fit) );
192+
QVERIFY( checker.testComposition( 0 ) );
193+
}
194+
atlasRender.end();
195+
196+
}
197+
198+
void TestQgsComposerMapAtlas::hiding_render()
199+
{
200+
mAtlasMap->setNewExtent( QgsRectangle( 209838.166, 6528781.020, 610491.166, 6920530.620 ) );
201+
mAtlasMap->setAtlasFixedScale( true );
202+
mAtlasMap->setAtlasHideCoverage( true );
203+
204+
QgsAtlasRendering atlasRender( mComposition );
205+
206+
atlasRender.begin();
207+
208+
for ( size_t fit = 0; fit < 2; ++fit )
209+
{
210+
atlasRender.prepareForFeature( fit );
211+
mLabel1->adjustSizeToText();
212+
213+
QgsCompositionChecker checker( "Atlas hidden test", mComposition,
214+
QString( TEST_DATA_DIR ) + QDir::separator() + "control_images" + QDir::separator() +
215+
"expected_composermapatlas" + QDir::separator() +
216+
QString( "hiding_%1.png" ).arg((int)fit) );
217+
QVERIFY( checker.testComposition( 0 ) );
218+
}
219+
atlasRender.end();
220+
221+
}
222+
223+
QTEST_MAIN( TestQgsComposerMapAtlas )
224+
#include "moc_testqgscomposermapatlas.cxx"

‎tests/src/core/testqgsexpression.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,33 @@ class TestQgsExpression: public QObject
544544
QVariant vPerimeter = exp3.evaluate( &fPolygon );
545545
QCOMPARE( vPerimeter.toDouble(), 20. );
546546
}
547+
548+
void eval_special_columns()
549+
{
550+
QTest::addColumn<QString>( "string" );
551+
QTest::addColumn<QVariant>( "result" );
552+
553+
QgsExpression::setSpecialColumn( "$var1", QVariant((int)42) );
554+
555+
QgsExpression exp( "$var1 + 1" );
556+
QVariant v1 = exp.evaluate();
557+
QCOMPARE( v1.toInt(), 43 );
558+
559+
QgsExpression::setSpecialColumn( "$var1", QVariant((int)100) );
560+
QVariant v2 = exp.evaluate();
561+
QCOMPARE( v2.toInt(), 101 );
562+
563+
QgsExpression exp2( "_specialcol_('$var1')+1" );
564+
QVariant v3 = exp2.evaluate();
565+
QCOMPARE( v3.toInt(), 101 );
566+
567+
QgsExpression exp3( "_specialcol_('undefined')");
568+
QVariant v4 = exp3.evaluate();
569+
QCOMPARE( v4, QVariant() );
570+
571+
QgsExpression::unsetSpecialColumn( "$var1" );
572+
}
573+
547574
};
548575

549576
QTEST_MAIN( TestQgsExpression )
Loading
Loading
Loading
Loading
Loading
Loading

‎tests/testdata/france_parts.dbf

10.4 KB
Binary file not shown.

‎tests/testdata/france_parts.prj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]

‎tests/testdata/france_parts.qpj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]

‎tests/testdata/france_parts.shp

48.3 KB
Binary file not shown.

‎tests/testdata/france_parts.shx

132 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)
Please sign in to comment.