Skip to content
Permalink
Browse files
[FEATURE][composer] Allow evaluation of QGIS expressions inside html …
…item source. Expressions are evaluated before HTML is rendered, allowing the expression results to modify how the HTML content is rendered. Sponsored by City of Uster, Switzerland.
  • Loading branch information
nyalldawson committed Jul 16, 2014
1 parent 5e4510f commit 19708be
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 19 deletions.
@@ -73,6 +73,26 @@ class QgsComposerHtml: QgsComposerMultiFrame
* @note added in 2.5
*/
QString html() const;

/**Returns whether html item will evaluate QGIS expressions prior to rendering
* the HTML content. If set, any content inside [% %] tags will be
* treated as a QGIS expression and evaluated against the current atlas
* feature.
* @returns true if html item will evaluate expressions in the content
* @see setEvaluateExpressions
* @note added in QGIS 2.5
*/
bool evaluateExpressions() const;

/**Sets whether the html item will evaluate QGIS expressions prior to rendering
* the HTML content. If set, any content inside [% %] tags will be
* treated as a QGIS expression and evaluated against the current atlas
* feature.
* @param evaluateExpressions set to true to evaluate expressions in the HTML content
* @see evaluateExpressions
* @note added in QGIS 2.5
*/
void setEvaluateExpressions( bool evaluateExpressions );

QSizeF totalSize() const;
void render( QPainter* p, const QRectF& renderExtent );
@@ -18,6 +18,7 @@
#include "qgscomposermultiframecommand.h"
#include "qgscomposerhtml.h"
#include "qgscomposition.h"
#include "qgsexpressionbuilderdialog.h"
#include <QFileDialog>
#include <QSettings>
#include <Qsci/qsciscintilla.h>
@@ -77,6 +78,7 @@ void QgsComposerHtmlWidget::blockSignals( bool block )
mHtmlEditor->blockSignals( block );
mRadioManualSource->blockSignals( block );
mRadioUrlSource->blockSignals( block );
mEvaluateExpressionsCheckbox->blockSignals( block );
}

void QgsComposerHtmlWidget::on_mUrlLineEdit_editingFinished()
@@ -133,6 +135,24 @@ void QgsComposerHtmlWidget::on_mResizeModeComboBox_currentIndexChanged( int inde
mAddFramePushButton->setEnabled( mHtml->resizeMode() == QgsComposerMultiFrame::UseExistingFrames );
}

void QgsComposerHtmlWidget::on_mEvaluateExpressionsCheckbox_toggled( bool checked )
{
if ( !mHtml )
{
return;
}

QgsComposition* composition = mHtml->composition();
if ( composition )
{
blockSignals( true );
composition->beginMultiFrameCommand( mHtml, tr( "Evaluate expressions changed" ) );
mHtml->setEvaluateExpressions( checked );
composition->endMultiFrameCommand();
blockSignals( false );
}
}

void QgsComposerHtmlWidget::on_mUseSmartBreaksCheckBox_toggled( bool checked )
{
if ( !mHtml )
@@ -158,7 +178,15 @@ void QgsComposerHtmlWidget::on_mMaxDistanceSpinBox_valueChanged( double val )
return;
}

mHtml->setMaxBreakDistance( val );
QgsComposition* composition = mHtml->composition();
if ( composition )
{
blockSignals( true );
composition->beginMultiFrameCommand( mHtml, tr( "Page break distance changed" ) );
mHtml->setMaxBreakDistance( val );
composition->endMultiFrameCommand();
blockSignals( false );
}
}

void QgsComposerHtmlWidget::htmlEditorChanged()
@@ -168,7 +196,16 @@ void QgsComposerHtmlWidget::htmlEditorChanged()
return;
}

mHtml->setHtml( mHtmlEditor->text() );
QgsComposition* composition = mHtml->composition();
if ( composition )
{
blockSignals( true );
composition->beginMultiFrameCommand( mHtml, tr( "HTML changed" ) );
mHtml->setHtml( mHtmlEditor->text() );
composition->endMultiFrameCommand();
blockSignals( false );
}

}

void QgsComposerHtmlWidget::on_mRadioManualSource_clicked( bool checked )
@@ -178,8 +215,17 @@ void QgsComposerHtmlWidget::on_mRadioManualSource_clicked( bool checked )
return;
}

mHtml->setContentMode( checked ? QgsComposerHtml::ManualHtml : QgsComposerHtml::Url );
QgsComposition* composition = mHtml->composition();
if ( composition )
{
blockSignals( true );
composition->beginMultiFrameCommand( mHtml, tr( "HTML source changed" ) );
mHtml->setContentMode( checked ? QgsComposerHtml::ManualHtml : QgsComposerHtml::Url );
composition->endMultiFrameCommand();
blockSignals( false );
}
mHtmlEditor->setEnabled( checked );
mInsertExpressionButton->setEnabled( checked );
mUrlLineEdit->setEnabled( !checked );
mFileToolButton->setEnabled( !checked );

@@ -193,14 +239,73 @@ void QgsComposerHtmlWidget::on_mRadioUrlSource_clicked( bool checked )
return;
}

mHtml->setContentMode( checked ? QgsComposerHtml::Url : QgsComposerHtml::ManualHtml );
QgsComposition* composition = mHtml->composition();
if ( composition )
{
blockSignals( true );
composition->beginMultiFrameCommand( mHtml, tr( "HTML source changed" ) );
mHtml->setContentMode( checked ? QgsComposerHtml::Url : QgsComposerHtml::ManualHtml );
composition->endMultiFrameCommand();
blockSignals( false );
}
mHtmlEditor->setEnabled( !checked );
mInsertExpressionButton->setEnabled( !checked );
mUrlLineEdit->setEnabled( checked );
mFileToolButton->setEnabled( checked );

mHtml->loadHtml();
}

void QgsComposerHtmlWidget::on_mInsertExpressionButton_clicked()
{
if ( !mHtml )
{
return;
}

int line = 0;
int index = 0;
QString selText;
if ( mHtmlEditor->hasSelectedText() )
{
selText = mHtmlEditor->selectedText();

// edit the selected expression if there's one
if ( selText.startsWith( "[%" ) && selText.endsWith( "%]" ) )
selText = selText.mid( 2, selText.size() - 4 );
}
else
{
mHtmlEditor->getCursorPosition( &line, &index );
}

// use the atlas coverage layer, if any
QgsVectorLayer* coverageLayer = atlasCoverageLayer();
QgsExpressionBuilderDialog exprDlg( coverageLayer, selText, this );
exprDlg.setWindowTitle( tr( "Insert expression" ) );
if ( exprDlg.exec() == QDialog::Accepted )
{
QString expression = exprDlg.expressionText();
QgsComposition* composition = mHtml->composition();
if ( !expression.isEmpty() && composition )
{
blockSignals( true );
composition->beginMultiFrameCommand( mHtml, tr( "HTML source changed" ) );
if ( mHtmlEditor->hasSelectedText() )
{
mHtmlEditor->replaceSelectedText( "[%" + expression + "%]" );
}
else
{
mHtmlEditor->insertAt( "[%" + expression + "%]", line, index );
}
composition->endMultiFrameCommand();
blockSignals( false );
}
}

}

void QgsComposerHtmlWidget::on_mReloadPushButton_clicked()
{
if ( !mHtml )
@@ -244,6 +349,7 @@ void QgsComposerHtmlWidget::setGuiElementValues()
blockSignals( true );
mUrlLineEdit->setText( mHtml->url().toString() );
mResizeModeComboBox->setCurrentIndex( mResizeModeComboBox->findData( mHtml->resizeMode() ) );
mEvaluateExpressionsCheckbox->setChecked( mHtml->evaluateExpressions() );
mUseSmartBreaksCheckBox->setChecked( mHtml->useSmartBreaks() );
mMaxDistanceSpinBox->setValue( mHtml->maxBreakDistance() );

@@ -255,5 +361,6 @@ void QgsComposerHtmlWidget::setGuiElementValues()
mFileToolButton->setEnabled( mHtml->contentMode() == QgsComposerHtml::Url );
mRadioManualSource->setChecked( mHtml->contentMode() == QgsComposerHtml::ManualHtml );
mHtmlEditor->setEnabled( mHtml->contentMode() == QgsComposerHtml::ManualHtml );
mInsertExpressionButton->setEnabled( mHtml->contentMode() == QgsComposerHtml::ManualHtml );
blockSignals( false );
}
@@ -33,11 +33,13 @@ class QgsComposerHtmlWidget: public QgsComposerItemBaseWidget, private Ui::QgsCo
void on_mUrlLineEdit_editingFinished();
void on_mFileToolButton_clicked();
void on_mResizeModeComboBox_currentIndexChanged( int index );
void on_mEvaluateExpressionsCheckbox_toggled( bool checked );
void on_mUseSmartBreaksCheckBox_toggled( bool checked );
void on_mMaxDistanceSpinBox_valueChanged( double val );
void htmlEditorChanged();
void on_mRadioManualSource_clicked( bool checked );
void on_mRadioUrlSource_clicked( bool checked );
void on_mInsertExpressionButton_clicked();

void on_mReloadPushButton_clicked();
void on_mAddFramePushButton_clicked();
@@ -19,6 +19,7 @@
#include "qgsaddremovemultiframecommand.h"
#include "qgsnetworkaccessmanager.h"
#include "qgsmessagelog.h"
#include "qgsexpression.h"

#include <QCoreApplication>
#include <QPainter>
@@ -33,8 +34,11 @@ QgsComposerHtml::QgsComposerHtml( QgsComposition* c, bool createUndoCommands ):
mLoaded( false ),
mHtmlUnitsToMM( 1.0 ),
mRenderedPage( 0 ),
mEvaluateExpressions( true ),
mUseSmartBreaks( true ),
mMaxBreakDistance( 10 )
mMaxBreakDistance( 10 ),
mExpressionFeature( 0 ),
mExpressionLayer( 0 )
{
mHtmlUnitsToMM = htmlUnitsToMM();
mWebPage = new QWebPage();
@@ -45,6 +49,18 @@ QgsComposerHtml::QgsComposerHtml( QgsComposition* c, bool createUndoCommands ):
QObject::connect( mComposition, SIGNAL( itemRemoved( QgsComposerItem* ) ), this, SLOT( handleFrameRemoval( QgsComposerItem* ) ) );
connect( mComposition, SIGNAL( refreshItemsTriggered() ), this, SLOT( loadHtml() ) );
}

if ( mComposition && mComposition->atlasMode() == QgsComposition::PreviewAtlas )
{
//a html item added while atlas preview is enabled needs to have the expression context set,
//otherwise fields in the html aren't correctly evaluated until atlas preview feature changes (#9457)
setExpressionContext( mComposition->atlasComposition().currentFeature(), mComposition->atlasComposition().coverageLayer() );
}

//connect to atlas feature changes
//to update the expression context
connect( &mComposition->atlasComposition(), SIGNAL( featureChanged( QgsFeature* ) ), this, SLOT( refreshExpressionContext() ) );

}

QgsComposerHtml::QgsComposerHtml(): QgsComposerMultiFrame( 0, false ),
@@ -54,7 +70,9 @@ QgsComposerHtml::QgsComposerHtml(): QgsComposerMultiFrame( 0, false ),
mHtmlUnitsToMM( 1.0 ),
mRenderedPage( 0 ),
mUseSmartBreaks( true ),
mMaxBreakDistance( 10 )
mMaxBreakDistance( 10 ),
mExpressionFeature( 0 ),
mExpressionLayer( 0 )
{
}

@@ -80,6 +98,12 @@ void QgsComposerHtml::setHtml( const QString html )
mHtml = html;
}

void QgsComposerHtml::setEvaluateExpressions( bool evaluateExpressions )
{
mEvaluateExpressions = evaluateExpressions;
loadHtml();
}

QString QgsComposerHtml::fetchHtml( QUrl url )
{
QUrl nextUrlToFetch = url;
@@ -151,6 +175,12 @@ void QgsComposerHtml::loadHtml()
break;
}

//evaluate expressions
if ( mEvaluateExpressions )
{
loadedHtml = QgsExpression::replaceExpressionText( loadedHtml, mExpressionFeature, mExpressionLayer );
}

mLoaded = false;
//set html, using the specified url as base if in Url mode
mWebPage->mainFrame()->setHtml( loadedHtml, mContentMode == QgsComposerHtml::Url ? QUrl( mUrl ) : QUrl() );
@@ -360,6 +390,7 @@ bool QgsComposerHtml::writeXML( QDomElement& elem, QDomDocument & doc, bool igno
htmlElem.setAttribute( "contentMode", QString::number(( int ) mContentMode ) );
htmlElem.setAttribute( "url", mUrl.toString() );
htmlElem.setAttribute( "html", mHtml );
htmlElem.setAttribute( "evaluateExpressions", mEvaluateExpressions ? "true" : "false" );
htmlElem.setAttribute( "useSmartBreaks", mUseSmartBreaks ? "true" : "false" );
htmlElem.setAttribute( "maxBreakDistance", QString::number( mMaxBreakDistance ) );

@@ -384,6 +415,7 @@ bool QgsComposerHtml::readXML( const QDomElement& itemElem, const QDomDocument&
{
mContentMode = QgsComposerHtml::Url;
}
mEvaluateExpressions = itemElem.attribute( "evaluateExpressions", "true" ) == "true" ? true : false;
mUseSmartBreaks = itemElem.attribute( "useSmartBreaks", "true" ) == "true" ? true : false;
mMaxBreakDistance = itemElem.attribute( "maxBreakDistance", "10" ).toDouble();
mHtml = itemElem.attribute( "html" );
@@ -399,3 +431,27 @@ bool QgsComposerHtml::readXML( const QDomElement& itemElem, const QDomDocument&
emit changed();
return true;
}

void QgsComposerHtml::setExpressionContext( QgsFeature* feature, QgsVectorLayer* layer )
{
mExpressionFeature = feature;
mExpressionLayer = layer;
}

void QgsComposerHtml::refreshExpressionContext()
{
QgsVectorLayer * vl = 0;
QgsFeature* feature = 0;

if ( mComposition->atlasComposition().enabled() )
{
vl = mComposition->atlasComposition().coverageLayer();
}
if ( mComposition->atlasMode() != QgsComposition::AtlasOff )
{
feature = mComposition->atlasComposition().currentFeature();
}

setExpressionContext( feature, vl );
loadHtml();
}

0 comments on commit 19708be

Please sign in to comment.