278 changes: 278 additions & 0 deletions src/gui/qgshtmlannotationitem.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
/***************************************************************************
qgshtmlannotationitem.h
------------------------
begin : February 26, 2010
copyright : (C) 2010 by Marco Hugentobler
email : marco dot hugentobler at hugis dot net
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgshtmlannotationitem.h"
#include "qgsattributeeditor.h"
#include "qgsfeature.h"
#include "qgslogger.h"
#include "qgsmapcanvas.h"
#include "qgsmaplayerregistry.h"
#include "qgsvectorlayer.h"
#include "qgsexpression.h"

#include <QDomElement>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QGraphicsProxyWidget>
#include <QPainter>
#include <QSettings>
#include <QWidget>


QgsHtmlAnnotationItem::QgsHtmlAnnotationItem( QgsMapCanvas* canvas, QgsVectorLayer* vlayer, bool hasFeature, int feature )
: QgsAnnotationItem( canvas ), mWidgetContainer( 0 ), mWebView( 0 ), mVectorLayer( vlayer ),
mHasAssociatedFeature( hasFeature ), mFeatureId( feature )
{
mWebView = new QWebView();
mWidgetContainer = new QGraphicsProxyWidget( this );
mWidgetContainer->setWidget( mWebView );

QObject::connect( mWebView->page()->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(javascript()));

if ( mVectorLayer && mMapCanvas )
{
QObject::connect( mVectorLayer, SIGNAL( layerModified( bool ) ), this, SLOT( setFeatureForMapPosition() ) );
QObject::connect( mMapCanvas, SIGNAL( renderComplete( QPainter* ) ), this, SLOT( setFeatureForMapPosition() ) );
QObject::connect( mMapCanvas, SIGNAL( layersChanged() ), this, SLOT( updateVisibility() ) );
}

setFeatureForMapPosition();
}

QgsHtmlAnnotationItem::~QgsHtmlAnnotationItem()
{
delete mWebView;
}

void QgsHtmlAnnotationItem::setHTMLPage( const QString& htmlFile )
{
QFile file( htmlFile );
mHtmlFile = htmlFile;
if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
{
mHtmlSource = "";
}
else
{
QTextStream in( &file );
in.setCodec( "UTF-8" );
mHtmlSource = in.readAll();
}

file.close();
setFeatureForMapPosition();
}

void QgsHtmlAnnotationItem::setMapPosition( const QgsPoint& pos )
{
QgsAnnotationItem::setMapPosition( pos );
setFeatureForMapPosition();
}

void QgsHtmlAnnotationItem::paint( QPainter * painter )
{
Q_UNUSED( painter );
}

void QgsHtmlAnnotationItem::paint( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget )
{
Q_UNUSED( option );
Q_UNUSED( widget );
if ( !painter || !mWidgetContainer )
{
return;
}

drawFrame( painter );
if ( mMapPositionFixed )
{
drawMarkerSymbol( painter );
}

mWidgetContainer->setGeometry( QRectF( mOffsetFromReferencePoint.x() + mFrameBorderWidth / 2.0, mOffsetFromReferencePoint.y()
+ mFrameBorderWidth / 2.0, mFrameSize.width() - mFrameBorderWidth, mFrameSize.height()
- mFrameBorderWidth ) );
if (data(1).toString() == "composer")
{
mWidgetContainer->widget()->render( painter, mOffsetFromReferencePoint.toPoint());
}

if ( isSelected() )
{
drawSelectionBoxes( painter );
}
}

QSizeF QgsHtmlAnnotationItem::minimumFrameSize() const
{
if ( mWebView )
{
QSizeF widgetMinSize = mWebView->minimumSize();
return QSizeF( 2 * mFrameBorderWidth + widgetMinSize.width(), 2 * mFrameBorderWidth + widgetMinSize.height() );
}
else
{
return QSizeF( 0, 0 );
}
}

void QgsHtmlAnnotationItem::writeXML( QDomDocument& doc ) const
{
QDomElement documentElem = doc.documentElement();
if ( documentElem.isNull() )
{
return;
}

QDomElement formAnnotationElem = doc.createElement( "HtmlAnnotationItem" );
if ( mVectorLayer )
{
formAnnotationElem.setAttribute( "vectorLayer", mVectorLayer->id() );
}
formAnnotationElem.setAttribute( "hasFeature", mHasAssociatedFeature );
formAnnotationElem.setAttribute( "feature", mFeatureId );
formAnnotationElem.setAttribute( "htmlfile", htmlPage() );

_writeXML( doc, formAnnotationElem );
documentElem.appendChild( formAnnotationElem );
}

void QgsHtmlAnnotationItem::readXML( const QDomDocument& doc, const QDomElement& itemElem )
{
mVectorLayer = 0;
if ( itemElem.hasAttribute( "vectorLayer" ) )
{
mVectorLayer = dynamic_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( itemElem.attribute( "vectorLayer", "" ) ) );
if ( mVectorLayer )
{
QObject::connect( mVectorLayer, SIGNAL( layerModified( bool ) ), this, SLOT( setFeatureForMapPosition() ) );
QObject::connect( mMapCanvas, SIGNAL( renderComplete( QPainter* ) ), this, SLOT( setFeatureForMapPosition() ) );
QObject::connect( mMapCanvas, SIGNAL( layersChanged() ), this, SLOT( updateVisibility() ) );
}
}
mHasAssociatedFeature = itemElem.attribute( "hasFeature", "0" ).toInt();
mFeatureId = itemElem.attribute( "feature", "0" ).toInt();
mHtmlFile = itemElem.attribute( "htmlfile", "");
QDomElement annotationElem = itemElem.firstChildElement( "AnnotationItem" );
if ( !annotationElem.isNull() )
{
_readXML( doc, annotationElem );
}

if ( mWebView )
{
setHTMLPage( mHtmlFile );
}
updateVisibility();
}

void QgsHtmlAnnotationItem::setFeatureForMapPosition()
{
if ( !mVectorLayer || !mMapCanvas )
{
return;
}

QSettings settings;
double identifyValue = settings.value( "/Map/identifyRadius", QGis::DEFAULT_IDENTIFY_RADIUS ).toDouble();
double halfIdentifyWidth = mMapCanvas->extent().width() / 100 / 2 * identifyValue;
QgsRectangle searchRect( mMapPosition.x() - halfIdentifyWidth, mMapPosition.y() - halfIdentifyWidth,
mMapPosition.x() + halfIdentifyWidth, mMapPosition.y() + halfIdentifyWidth );
mVectorLayer->select( mVectorLayer->pendingAllAttributesList() , searchRect, false, true );

QgsFeature currentFeature;
QgsFeatureId currentFeatureId = 0;
bool featureFound = false;

while ( mVectorLayer->nextFeature( currentFeature ) )
{
currentFeatureId = currentFeature.id();
featureFound = true;
break;
}

mHasAssociatedFeature = featureFound;
mFeatureId = currentFeatureId;
mFeature = currentFeature;

QString newtext = replaceText( mHtmlSource, vectorLayer(), mFeature );
mWebView->setHtml( newtext );

}

void QgsHtmlAnnotationItem::updateVisibility()
{
bool visible = true;
if ( mVectorLayer && mMapCanvas )
{
visible = mMapCanvas->layers().contains( mVectorLayer );
}
setVisible( visible );
}

void QgsHtmlAnnotationItem::javascript()
{
QWebFrame *frame = mWebView->page()->mainFrame();
frame->addToJavaScriptWindowObject("canvas", mMapCanvas );
}

QString QgsHtmlAnnotationItem::replaceText( QString displayText, QgsVectorLayer *layer, QgsFeature &feat )
{
QString expr_action;

int index = 0;
while ( index < displayText.size() )
{
QRegExp rx = QRegExp( "\\[%([^\\]]+)%\\]" );

int pos = rx.indexIn( displayText, index );
if ( pos < 0 )
break;

int start = index;
index = pos + rx.matchedLength();

QString to_replace = rx.cap( 1 ).trimmed();
QgsDebugMsg( "Found expression: " + to_replace );

QgsExpression exp( to_replace );
if ( exp.hasParserError() )
{
QgsDebugMsg( "Expression parser error: " + exp.parserErrorString() );
expr_action += displayText.mid( start, index - start );
continue;
}

QVariant result = exp.evaluate( &feat, layer->pendingFields() );
if ( exp.hasEvalError() )
{
QgsDebugMsg( "Expression parser eval error: " + exp.evalErrorString() );
expr_action += displayText.mid( start, index - start );
continue;
}

QgsDebugMsg( "Expression result is: " + result.toString() );
expr_action += displayText.mid( start, pos - start ) + result.toString();
}

expr_action += displayText.mid( index );
return expr_action;
}



79 changes: 79 additions & 0 deletions src/gui/qgshtmlannotationitem.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/***************************************************************************
qgshtmlannotationitem.h
------------------------
begin : February 9, 2010
copyright : (C) 2010 by Marco Hugentobler
email : marco dot hugentobler at hugis dot net
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSHTMLANNOTATIONITEM_H
#define QGSHTMLANNOTATIONITEM_H

#include "qgsannotationitem.h"
#include "qgsfeature.h"
#include <QObject>
#include <QWebView>
#include <QWebFrame>

class QGraphicsProxyWidget;

/**An annotation item that embedds a designer form showing the feature attribute*/
class GUI_EXPORT QgsHtmlAnnotationItem: public QObject, public QgsAnnotationItem
{
Q_OBJECT
public:
QgsHtmlAnnotationItem( QgsMapCanvas* canvas, QgsVectorLayer* vlayer = 0, bool hasFeature = false, int feature = 0 );
~QgsHtmlAnnotationItem();

void paint( QPainter * painter );

//! paint function called by map canvas
void paint( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0 );

QSizeF minimumFrameSize() const;

/**Reimplemented from QgsAnnotationItem*/
void setMapPosition( const QgsPoint& pos );

void setHTMLPage( const QString& htmlFile );
QString htmlPage() const { return mHtmlFile; }

void writeXML( QDomDocument& doc ) const;
void readXML( const QDomDocument& doc, const QDomElement& itemElem );

QgsVectorLayer* vectorLayer() const { return mVectorLayer; }

private slots:
/**Sets a feature for the current map position and updates the dialog*/
void setFeatureForMapPosition();
/**Sets visibility status based on mVectorLayer visibility*/
void updateVisibility();

void javascript();

private:
QGraphicsProxyWidget* mWidgetContainer;
QWebView* mWebView;
/**Associated vectorlayer (or 0 if attributes are not supposed to be replaced)*/
QgsVectorLayer* mVectorLayer;
/**True if the item is related to a vector feature*/
bool mHasAssociatedFeature;
/**Associated feature*/
QgsFeatureId mFeatureId;
QgsFeature mFeature;
QString mHtmlFile;
QString mHtmlSource;

QString replaceText( QString displayText, QgsVectorLayer *layer, QgsFeature &feat );
};

#endif // QGSHTMLANNOTATIONITEM_H
15 changes: 14 additions & 1 deletion src/ui/qgisapp.ui
Original file line number Diff line number Diff line change
Expand Up @@ -1788,7 +1788,7 @@ Acts on all editable layers</string>
</property>
</action>
<action name="mActionShowHideLabels">
<property name="checkable">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
Expand All @@ -1803,6 +1803,19 @@ Acts on all editable layers</string>
Click or marquee on feature to show label
Shift+click or marquee on label to hide it
Acts on currently active editable layer</string>
</property>
</action>
<action name="mActionHtmlAnnotation">
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mActionFormAnnotation.png</normaloff>:/images/themes/default/mActionFormAnnotation.png
</iconset>
</property>
<property name="text">
<string>Html Annotation</string>
</property>
<property name="toolTip">
<string>Html Annotation</string>
</property>
</action>
</widget>
Expand Down