Skip to content

Commit 021da60

Browse files
committedApr 28, 2014
[FEATURE][composer] Add expression support for picture source (sponsored by City of Uster, Switzerland)
1 parent ca186c7 commit 021da60

File tree

5 files changed

+384
-20
lines changed

5 files changed

+384
-20
lines changed
 

‎src/app/composer/qgscomposerpicturewidget.cpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "qgscomposerpicture.h"
2222
#include "qgscomposeritemwidget.h"
2323
#include "qgscomposition.h"
24+
#include "qgsexpressionbuilderdialog.h"
2425
#include <QDoubleValidator>
2526
#include <QFileDialog>
2627
#include <QFileInfo>
@@ -51,6 +52,8 @@ QgsComposerPictureWidget::QgsComposerPictureWidget( QgsComposerPicture* picture
5152

5253
connect( mPicture, SIGNAL( itemChanged() ), this, SLOT( setGuiElementValues() ) );
5354
connect( mPicture, SIGNAL( pictureRotationChanged( double ) ), this, SLOT( setPicRotationSpinValue( double ) ) );
55+
connect( mPictureExpressionLineEdit, SIGNAL( editingFinished() ), this, SLOT( setPictureExpression() ) );
56+
5457
}
5558

5659
QgsComposerPictureWidget::~QgsComposerPictureWidget()
@@ -120,6 +123,37 @@ void QgsComposerPictureWidget::on_mPictureLineEdit_editingFinished()
120123
}
121124
}
122125

126+
void QgsComposerPictureWidget::on_mPictureExpressionButton_clicked()
127+
{
128+
if ( !mPicture )
129+
{
130+
return;
131+
}
132+
133+
QgsVectorLayer* vl = 0;
134+
QgsComposition* composition = mPicture->composition();
135+
136+
if ( composition )
137+
{
138+
QgsAtlasComposition* atlasMap = &composition->atlasComposition();
139+
if ( atlasMap )
140+
{
141+
vl = atlasMap->coverageLayer();
142+
}
143+
}
144+
145+
QgsExpressionBuilderDialog exprDlg( vl, mPictureExpressionLineEdit->text(), this );
146+
exprDlg.setWindowTitle( tr( "Expression based image path" ) );
147+
if ( exprDlg.exec() == QDialog::Accepted )
148+
{
149+
QString expression = exprDlg.expressionText();
150+
if ( !expression.isEmpty() )
151+
{
152+
mPictureExpressionLineEdit->setText( expression );
153+
setPictureExpression();
154+
}
155+
}
156+
}
123157

124158
void QgsComposerPictureWidget::on_mPictureRotationSpinBox_valueChanged( double d )
125159
{
@@ -213,6 +247,52 @@ void QgsComposerPictureWidget::on_mResizeModeComboBox_currentIndexChanged( int i
213247
mRotationGroupBox->setEnabled( mPicture->resizeMode() == QgsComposerPicture::Zoom );
214248
}
215249

250+
void QgsComposerPictureWidget::on_mRadioPath_clicked()
251+
{
252+
if ( !mPicture )
253+
{
254+
return;
255+
}
256+
257+
mPicture->beginCommand( tr( "Picture source changed" ) );
258+
mPicture->setUsePictureExpression( false );
259+
mPicture->endCommand();
260+
261+
mPictureLineEdit->setEnabled( true );
262+
mPictureBrowseButton->setEnabled( true );
263+
mPictureExpressionLineEdit->setEnabled( false );
264+
mPictureExpressionButton->setEnabled( false );
265+
}
266+
267+
void QgsComposerPictureWidget::on_mRadioExpression_clicked()
268+
{
269+
if ( !mPicture )
270+
{
271+
return;
272+
}
273+
274+
mPicture->beginCommand( tr( "Picture source changed" ) );
275+
mPicture->setUsePictureExpression( true );
276+
mPicture->endCommand();
277+
278+
mPictureLineEdit->setEnabled( false );
279+
mPictureBrowseButton->setEnabled( false );
280+
mPictureExpressionLineEdit->setEnabled( true );
281+
mPictureExpressionButton->setEnabled( true );
282+
}
283+
284+
void QgsComposerPictureWidget::setPictureExpression()
285+
{
286+
if ( !mPicture )
287+
{
288+
return;
289+
}
290+
291+
mPicture->beginCommand( tr( "Picture source expression" ) );
292+
mPicture->setPictureExpression( mPictureExpressionLineEdit->text() );
293+
mPicture->endCommand();
294+
}
295+
216296
void QgsComposerPictureWidget::on_mRotationFromComposerMapCheckBox_stateChanged( int state )
217297
{
218298
if ( !mPicture )
@@ -342,6 +422,9 @@ void QgsComposerPictureWidget::setGuiElementValues()
342422
mComposerMapComboBox->blockSignals( true );
343423
mRotationFromComposerMapCheckBox->blockSignals( true );
344424
mResizeModeComboBox->blockSignals( true );
425+
mRadioPath->blockSignals( true );
426+
mRadioExpression->blockSignals( true );
427+
mPictureExpressionLineEdit->blockSignals( true );
345428

346429
mPictureLineEdit->setText( mPicture->pictureFile() );
347430
// QRectF pictureRect = mPicture->rect();
@@ -372,11 +455,23 @@ void QgsComposerPictureWidget::setGuiElementValues()
372455
//disable picture rotation for non-zoom modes
373456
mRotationGroupBox->setEnabled( mPicture->resizeMode() == QgsComposerPicture::Zoom );
374457

458+
mRadioPath->setChecked( !( mPicture->usePictureExpression() ) );
459+
mRadioExpression->setChecked( mPicture->usePictureExpression() );
460+
mPictureLineEdit->setEnabled( !( mPicture->usePictureExpression() ) );
461+
mPictureBrowseButton->setEnabled( !( mPicture->usePictureExpression() ) );
462+
mPictureExpressionLineEdit->setEnabled( mPicture->usePictureExpression() );
463+
mPictureExpressionButton->setEnabled( mPicture->usePictureExpression() );
464+
465+
mPictureExpressionLineEdit->setText( mPicture->pictureExpression() );
466+
375467
mRotationFromComposerMapCheckBox->blockSignals( false );
376468
mPictureRotationSpinBox->blockSignals( false );
377469
mPictureLineEdit->blockSignals( false );
378470
mComposerMapComboBox->blockSignals( false );
379471
mResizeModeComboBox->blockSignals( false );
472+
mRadioPath->blockSignals( false );
473+
mRadioExpression->blockSignals( false );
474+
mPictureExpressionLineEdit->blockSignals( false );
380475
}
381476
}
382477

‎src/app/composer/qgscomposerpicturewidget.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,17 @@ class QgsComposerPictureWidget: public QWidget, private Ui::QgsComposerPictureWi
3939
public slots:
4040
void on_mPictureBrowseButton_clicked();
4141
void on_mPictureLineEdit_editingFinished();
42+
void on_mPictureExpressionButton_clicked();
4243
void on_mPictureRotationSpinBox_valueChanged( double d );
4344
void on_mPreviewListWidget_currentItemChanged( QListWidgetItem* current, QListWidgetItem* previous );
4445
void on_mAddDirectoryButton_clicked();
4546
void on_mRemoveDirectoryButton_clicked();
4647
void on_mRotationFromComposerMapCheckBox_stateChanged( int state );
4748
void on_mComposerMapComboBox_activated( const QString & text );
4849
void on_mResizeModeComboBox_currentIndexChanged( int index );
50+
void on_mRadioPath_clicked();
51+
void on_mRadioExpression_clicked();
52+
void setPictureExpression();
4953

5054
protected:
5155
void showEvent( QShowEvent * event );

‎src/core/composer/qgscomposerpicture.cpp

Lines changed: 185 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@
1818
#include "qgscomposerpicture.h"
1919
#include "qgscomposermap.h"
2020
#include "qgscomposition.h"
21+
#include "qgsatlascomposition.h"
2122
#include "qgsproject.h"
23+
#include "qgsexpression.h"
24+
#include "qgsvectorlayer.h"
25+
#include "qgsmessagelog.h"
2226
#include <QDomDocument>
2327
#include <QDomElement>
2428
#include <QFileInfo>
@@ -27,16 +31,43 @@
2731
#include <QSvgRenderer>
2832

2933

30-
QgsComposerPicture::QgsComposerPicture( QgsComposition *composition )
31-
: QgsComposerItem( composition ), mMode( Unknown ), mPictureRotation( 0 ), mRotationMap( 0 ),
32-
mResizeMode( QgsComposerPicture::Zoom )
34+
QgsComposerPicture::QgsComposerPicture( QgsComposition *composition ) :
35+
QgsComposerItem( composition ),
36+
mMode( Unknown ),
37+
mUseSourceExpression( false ),
38+
mPictureRotation( 0 ),
39+
mRotationMap( 0 ),
40+
mResizeMode( QgsComposerPicture::Zoom ),
41+
mPictureExpr( 0 )
3342
{
3443
mPictureWidth = rect().width();
44+
init();
3545
}
3646

37-
QgsComposerPicture::QgsComposerPicture(): QgsComposerItem( 0 ), mMode( Unknown ), mPictureRotation( 0 ), mRotationMap( 0 ), mResizeMode( QgsComposerPicture::Zoom )
47+
QgsComposerPicture::QgsComposerPicture() : QgsComposerItem( 0 ),
48+
mMode( Unknown ),
49+
mUseSourceExpression( false ),
50+
mPictureRotation( 0 ),
51+
mRotationMap( 0 ),
52+
mResizeMode( QgsComposerPicture::Zoom ),
53+
mPictureExpr( 0 )
3854
{
3955
mPictureHeight = rect().height();
56+
init();
57+
}
58+
59+
void QgsComposerPicture::init()
60+
{
61+
//connect some signals
62+
63+
//connect to atlas toggling on/off and coverage layer changes
64+
//to update the picture source expression
65+
connect( &mComposition->atlasComposition(), SIGNAL( toggled( bool ) ), this, SLOT( updatePictureExpression() ) );
66+
connect( &mComposition->atlasComposition(), SIGNAL( coverageLayerChanged( QgsVectorLayer* ) ), this, SLOT( updatePictureExpression() ) );
67+
68+
//connect to atlas feature changing
69+
connect( &mComposition->atlasComposition(), SIGNAL( featureChanged( QgsFeature* ) ), this, SLOT( refreshPicture() ) );
70+
4071
}
4172

4273
QgsComposerPicture::~QgsComposerPicture()
@@ -116,17 +147,106 @@ void QgsComposerPicture::paint( QPainter* painter, const QStyleOptionGraphicsIte
116147
void QgsComposerPicture::setPictureFile( const QString& path )
117148
{
118149
mSourceFile.setFileName( path );
119-
if ( !mSourceFile.exists() )
150+
refreshPicture();
151+
}
152+
153+
void QgsComposerPicture::updatePictureExpression()
154+
{
155+
QgsVectorLayer * vl = 0;
156+
if ( mComposition->atlasComposition().enabled() )
157+
{
158+
vl = mComposition->atlasComposition().coverageLayer();
159+
}
160+
161+
if ( mSourceExpression.size() > 0 )
162+
{
163+
if ( mPictureExpr )
164+
{
165+
delete mPictureExpr;
166+
}
167+
mPictureExpr = new QgsExpression( mSourceExpression );
168+
// expression used to evaluate picture source
169+
// test for evaluation errors
170+
if ( mPictureExpr->hasParserError() )
171+
{
172+
QgsMessageLog::logMessage( tr( "Picture expression parsing error: %1" ).arg( mPictureExpr->parserErrorString() ), tr( "Composer" ) );
173+
}
174+
175+
if ( vl )
176+
{
177+
const QgsFields& fields = vl->pendingFields();
178+
mPictureExpr->prepare( fields );
179+
}
180+
}
181+
}
182+
183+
QString QgsComposerPicture::evalPictureExpression()
184+
{
185+
//generate filename for picture
186+
if ( mSourceExpression.size() > 0 && mUseSourceExpression )
187+
{
188+
if ( ! mPictureExpr )
189+
{
190+
return QString();
191+
}
192+
193+
QVariant filenameRes;
194+
QgsAtlasComposition* atlas = &( mComposition->atlasComposition() );
195+
if ( atlas->enabled() )
196+
{
197+
//expression needs to be evaluated considering the current atlas feature
198+
filenameRes = mPictureExpr->evaluate( atlas->currentFeature(),
199+
atlas->coverageLayer()->pendingFields() );
200+
}
201+
else
202+
{
203+
filenameRes = mPictureExpr->evaluate();
204+
}
205+
206+
if ( mPictureExpr->hasEvalError() )
207+
{
208+
QgsMessageLog::logMessage( tr( "Picture expression eval error: %1" ).arg( mPictureExpr->evalErrorString() ), tr( "Composer" ) );
209+
}
210+
211+
return filenameRes.toString();
212+
}
213+
else
214+
{
215+
return QString();
216+
}
217+
}
218+
219+
void QgsComposerPicture::refreshPicture()
220+
{
221+
if ( mUseSourceExpression )
222+
{
223+
//using expression for picture source file
224+
225+
//evaluate expression
226+
QFile path;
227+
path.setFileName( evalPictureExpression() );
228+
loadPicture( path );
229+
}
230+
else
231+
{
232+
//using a static picture path
233+
loadPicture( mSourceFile );
234+
}
235+
}
236+
237+
void QgsComposerPicture::loadPicture( const QFile& file )
238+
{
239+
if ( !file.exists() )
120240
{
121241
mMode = Unknown;
122242
}
123243

124-
QFileInfo sourceFileInfo( mSourceFile );
244+
QFileInfo sourceFileInfo( file );
125245
QString sourceFileSuffix = sourceFileInfo.suffix();
126246
if ( sourceFileSuffix.compare( "svg", Qt::CaseInsensitive ) == 0 )
127247
{
128248
//try to open svg
129-
mSVG.load( mSourceFile.fileName() );
249+
mSVG.load( file.fileName() );
130250
if ( mSVG.isValid() )
131251
{
132252
mMode = SVG;
@@ -142,7 +262,7 @@ void QgsComposerPicture::setPictureFile( const QString& path )
142262
else
143263
{
144264
//try to open raster with QImageReader
145-
QImageReader imageReader( mSourceFile.fileName() );
265+
QImageReader imageReader( file.fileName() );
146266
if ( imageReader.read( &mImage ) )
147267
{
148268
mMode = RASTER;
@@ -345,6 +465,38 @@ void QgsComposerPicture::setResizeMode( QgsComposerPicture::ResizeMode mode )
345465
update();
346466
}
347467

468+
void QgsComposerPicture::setUsePictureExpression( bool useExpression )
469+
{
470+
if ( useExpression == mUseSourceExpression )
471+
{
472+
return;
473+
}
474+
475+
mUseSourceExpression = useExpression;
476+
if ( useExpression )
477+
{
478+
updatePictureExpression();
479+
}
480+
481+
refreshPicture();
482+
}
483+
484+
void QgsComposerPicture::setPictureExpression( QString expression )
485+
{
486+
if ( expression == mSourceExpression )
487+
{
488+
return;
489+
}
490+
491+
mSourceExpression = expression;
492+
493+
if ( mUseSourceExpression )
494+
{
495+
updatePictureExpression();
496+
refreshPicture();
497+
}
498+
}
499+
348500
QString QgsComposerPicture::pictureFile() const
349501
{
350502
return mSourceFile.fileName();
@@ -361,6 +513,15 @@ bool QgsComposerPicture::writeXML( QDomElement& elem, QDomDocument & doc ) const
361513
composerPictureElem.setAttribute( "pictureWidth", QString::number( mPictureWidth ) );
362514
composerPictureElem.setAttribute( "pictureHeight", QString::number( mPictureHeight ) );
363515
composerPictureElem.setAttribute( "resizeMode", QString::number(( int )mResizeMode ) );
516+
if ( mUseSourceExpression )
517+
{
518+
composerPictureElem.setAttribute( "useExpression", "true" );
519+
}
520+
else
521+
{
522+
composerPictureElem.setAttribute( "useExpression", "false" );
523+
}
524+
composerPictureElem.setAttribute( "sourceExpression", mSourceExpression );
364525

365526
//rotation
366527
composerPictureElem.setAttribute( "pictureRotation", QString::number( mPictureRotation ) );
@@ -405,8 +566,23 @@ bool QgsComposerPicture::readXML( const QDomElement& itemElem, const QDomDocumen
405566

406567
mDefaultSvgSize = QSize( 0, 0 );
407568

569+
570+
mSourceExpression = itemElem.attribute( "sourceExpression", "" );
571+
QString useExpression = itemElem.attribute( "useExpression" );
572+
if ( useExpression.compare( "true", Qt::CaseInsensitive ) == 0 )
573+
{
574+
mUseSourceExpression = true;
575+
updatePictureExpression();
576+
}
577+
else
578+
{
579+
mUseSourceExpression = false;
580+
}
581+
408582
QString fileName = QgsProject::instance()->readPath( itemElem.attribute( "file" ) );
409-
setPictureFile( fileName );
583+
mSourceFile.setFileName( fileName );
584+
585+
refreshPicture();
410586

411587
//picture rotation
412588
if ( itemElem.attribute( "pictureRotation", "0" ).toDouble() != 0 )

‎src/core/composer/qgscomposerpicture.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <QSvgRenderer>
2424

2525
class QgsComposerMap;
26+
class QgsExpression;
2627

2728
/** \ingroup MapComposer
2829
* A composer class that displays svg files or raster format (jpg, png, ...)
@@ -101,6 +102,25 @@ class CORE_EXPORT QgsComposerPicture: public QgsComposerItem
101102
*/
102103
ResizeMode resizeMode() const { return mResizeMode;}
103104

105+
/**Returns whether the picture item is using an expression for the image source.
106+
* @returns true if the picture is using an expression for the source, false if
107+
* it is using a single static file path for the source.
108+
* @note added in 2.3
109+
* @see setUsePictureExpression
110+
* @see pictureFile
111+
* @see pictureExpression
112+
*/
113+
bool usePictureExpression() const { return mUseSourceExpression;}
114+
115+
/**Returns the expression the item is using for the picture source. This is only
116+
* used if usePictureExpression() is true.
117+
* @returns expression for the picture item's image path
118+
* @note added in 2.3
119+
* @see setPictureExpression
120+
* @see usePictureExpression
121+
*/
122+
QString pictureExpression() const { return mSourceExpression;}
123+
104124
/**Calculates width and hight of the picture (in mm) such that it fits into the item frame with the given rotation
105125
* @deprecated Use bool QgsComposerItem::imageSizeConsideringRotation( double& width, double& height, double rotation )
106126
* instead
@@ -138,6 +158,29 @@ class CORE_EXPORT QgsComposerPicture: public QgsComposerItem
138158
*/
139159
virtual void setResizeMode( ResizeMode mode );
140160

161+
/**Sets whether the picture should use an expression based image source path
162+
* @param useExpression set to true to use an expression based image source,
163+
* set to false to use a single image source path
164+
* @note added in 2.3
165+
* @see usePictureExpression
166+
* @see setPictureFile
167+
* @see setPictureExpression
168+
*/
169+
virtual void setUsePictureExpression( bool useExpression );
170+
171+
/**Sets an expression to use for the picture source. This expression is only
172+
* used if usePictureExpression() is true.
173+
* @param expression to use for picture path
174+
* @note added in 2.3
175+
* @see setUsePictureExpression
176+
* @see pictureExpression
177+
*/
178+
virtual void setPictureExpression( QString expression );
179+
180+
void refreshPicture();
181+
182+
void updatePictureExpression();
183+
141184
signals:
142185
/**Is emitted on picture rotation change*/
143186
void pictureRotationChanged( double newRotation );
@@ -158,6 +201,8 @@ class CORE_EXPORT QgsComposerPicture: public QgsComposerItem
158201
QSvgRenderer mSVG;
159202
QFile mSourceFile;
160203
Mode mMode;
204+
bool mUseSourceExpression;
205+
QString mSourceExpression;
161206

162207
QSize mDefaultSvgSize;
163208

@@ -171,6 +216,13 @@ class CORE_EXPORT QgsComposerPicture: public QgsComposerItem
171216
double mPictureHeight;
172217

173218
ResizeMode mResizeMode;
219+
220+
QgsExpression* mPictureExpr;
221+
222+
void loadPicture( const QFile &file );
223+
224+
QString evalPictureExpression();
225+
void init();
174226
};
175227

176228
#endif

‎src/ui/qgscomposerpicturewidgetbase.ui

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,21 @@
6868
<property name="collapsed" stdset="0">
6969
<bool>false</bool>
7070
</property>
71-
<layout class="QVBoxLayout" name="verticalLayout_3">
72-
<item>
71+
<layout class="QGridLayout" name="gridLayout">
72+
<item row="0" column="0">
73+
<widget class="QLabel" name="label">
74+
<property name="text">
75+
<string>Image source</string>
76+
</property>
77+
</widget>
78+
</item>
79+
<item row="1" column="0">
7380
<layout class="QHBoxLayout" name="horizontalLayout_2">
7481
<item>
75-
<widget class="QLabel" name="label">
82+
<widget class="QRadioButton" name="mRadioPath">
7683
<property name="text">
7784
<string>Path</string>
7885
</property>
79-
<property name="buddy">
80-
<cstring>mPictureLineEdit</cstring>
81-
</property>
8286
</widget>
8387
</item>
8488
<item>
@@ -94,7 +98,7 @@
9498
</property>
9599
<property name="maximumSize">
96100
<size>
97-
<width>150</width>
101+
<width>30</width>
98102
<height>32767</height>
99103
</size>
100104
</property>
@@ -105,14 +109,45 @@
105109
</item>
106110
</layout>
107111
</item>
108-
<item>
112+
<item row="2" column="0">
113+
<layout class="QHBoxLayout" name="horizontalLayout_3">
114+
<item>
115+
<widget class="QRadioButton" name="mRadioExpression">
116+
<property name="text">
117+
<string>Expression</string>
118+
</property>
119+
</widget>
120+
</item>
121+
<item>
122+
<widget class="QLineEdit" name="mPictureExpressionLineEdit"/>
123+
</item>
124+
<item>
125+
<widget class="QToolButton" name="mPictureExpressionButton">
126+
<property name="sizePolicy">
127+
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
128+
<horstretch>0</horstretch>
129+
<verstretch>0</verstretch>
130+
</sizepolicy>
131+
</property>
132+
<property name="text">
133+
<string>...</string>
134+
</property>
135+
<property name="icon">
136+
<iconset resource="../../images/images.qrc">
137+
<normaloff>:/images/themes/default/mIconExpression.svg</normaloff>:/images/themes/default/mIconExpression.svg</iconset>
138+
</property>
139+
</widget>
140+
</item>
141+
</layout>
142+
</item>
143+
<item row="3" column="0">
109144
<widget class="QLabel" name="label_4">
110145
<property name="text">
111146
<string>Resize mode</string>
112147
</property>
113148
</widget>
114149
</item>
115-
<item>
150+
<item row="4" column="0">
116151
<widget class="QComboBox" name="mResizeModeComboBox">
117152
<item>
118153
<property name="text">
@@ -298,7 +333,7 @@
298333
<customwidget>
299334
<class>QgsCollapsibleGroupBoxBasic</class>
300335
<extends>QGroupBox</extends>
301-
<header location="global">qgscollapsiblegroupbox.h</header>
336+
<header>qgscollapsiblegroupbox.h</header>
302337
<container>1</container>
303338
</customwidget>
304339
</customwidgets>
@@ -308,6 +343,8 @@
308343
<tabstop>mPictureBrowseButton</tabstop>
309344
<tabstop>mRotationFromComposerMapCheckBox</tabstop>
310345
</tabstops>
311-
<resources/>
346+
<resources>
347+
<include location="../../images/images.qrc"/>
348+
</resources>
312349
<connections/>
313350
</ui>

0 commit comments

Comments
 (0)
Please sign in to comment.