QGIS API Documentation  2.5.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgscomposerlabel.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscomposerlabel.cpp
3  -------------------
4  begin : January 2005
5  copyright : (C) 2005 by Radim Blazek
6  email : blazek@itc.it
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 "qgscomposerlabel.h"
19 #include "qgscomposition.h"
20 #include "qgscomposerutils.h"
21 #include "qgsexpression.h"
23 
24 #include <QCoreApplication>
25 #include <QDate>
26 #include <QDomElement>
27 #include <QPainter>
28 #include <QSettings>
29 #include <QTimer>
30 #include <QWebFrame>
31 #include <QWebPage>
32 #include <QEventLoop>
33 
35  QgsComposerItem( composition ), mHtmlState( 0 ), mHtmlUnitsToMM( 1.0 ),
36  mHtmlLoaded( false ), mMargin( 1.0 ), mFontColor( QColor( 0, 0, 0 ) ),
37  mHAlignment( Qt::AlignLeft ), mVAlignment( Qt::AlignTop ),
38  mExpressionFeature( 0 ), mExpressionLayer( 0 )
39 {
41 
42  //get default composer font from settings
43  QSettings settings;
44  QString defaultFontString = settings.value( "/Composer/defaultFont" ).toString();
45  if ( !defaultFontString.isEmpty() )
46  {
47  mFont.setFamily( defaultFontString );
48  }
49 
50  //default to a 10 point font size
51  mFont.setPointSizeF( 10 );
52 
53  //default to no background
54  setBackgroundEnabled( false );
55 
57  {
58  //a label added while atlas preview is enabled needs to have the expression context set,
59  //otherwise fields in the label aren't correctly evaluated until atlas preview feature changes (#9457)
61  }
62 
63  //connect to atlas feature changes
64  //to update the expression context
65  connect( &mComposition->atlasComposition(), SIGNAL( featureChanged( QgsFeature* ) ), this, SLOT( refreshExpressionContext() ) );
66 
67 }
68 
70 {
71 }
72 
73 void QgsComposerLabel::paint( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle, QWidget* pWidget )
74 {
75  Q_UNUSED( itemStyle );
76  Q_UNUSED( pWidget );
77  if ( !painter )
78  {
79  return;
80  }
81 
82  drawBackground( painter );
83  painter->save();
84 
85  //antialiasing on
86  painter->setRenderHint( QPainter::Antialiasing, true );
87 
88  double penWidth = hasFrame() ? pen().widthF() : 0;
89  QRectF painterRect( penWidth + mMargin, penWidth + mMargin, rect().width() - 2 * penWidth - 2 * mMargin, rect().height() - 2 * penWidth - 2 * mMargin );
90 
91  QString textToDraw = displayText();
92 
93  if ( mHtmlState )
94  {
95  painter->scale( 1.0 / mHtmlUnitsToMM / 10.0, 1.0 / mHtmlUnitsToMM / 10.0 );
96 
97  QWebPage *webPage = new QWebPage();
98  webPage->setNetworkAccessManager( QgsNetworkAccessManager::instance() );
99 
100  //Setup event loop and timeout for rendering html
101  QEventLoop loop;
102  QTimer timeoutTimer;
103  timeoutTimer.setSingleShot( true );
104 
105  //This makes the background transparent. Found on http://blog.qt.digia.com/blog/2009/06/30/transparent-qwebview-or-qwebpage/
106  QPalette palette = webPage->palette();
107  palette.setBrush( QPalette::Base, Qt::transparent );
108  webPage->setPalette( palette );
109  //webPage->setAttribute(Qt::WA_OpaquePaintEvent, false); //this does not compile, why ?
110 
111  webPage->setViewportSize( QSize( painterRect.width() * mHtmlUnitsToMM * 10.0, painterRect.height() * mHtmlUnitsToMM * 10.0 ) );
112  webPage->mainFrame()->setZoomFactor( 10.0 );
113  webPage->mainFrame()->setScrollBarPolicy( Qt::Horizontal, Qt::ScrollBarAlwaysOff );
114  webPage->mainFrame()->setScrollBarPolicy( Qt::Vertical, Qt::ScrollBarAlwaysOff );
115 
116  // QGIS segfaults when rendering web page while in composer if html
117  // contains images. So if we are not printing the composition, then
118  // disable image loading
121  {
122  webPage->settings()->setAttribute( QWebSettings::AutoLoadImages, false );
123  }
124 
125  //Connect timeout and webpage loadFinished signals to loop
126  connect( &timeoutTimer, SIGNAL( timeout() ), &loop, SLOT( quit() ) );
127  connect( webPage, SIGNAL( loadFinished( bool ) ), &loop, SLOT( quit() ) );
128 
129  //mHtmlLoaded tracks whether the QWebPage has completed loading
130  //its html contents, set it initially to false. The loadingHtmlFinished slot will
131  //set this to true after html is loaded.
132  mHtmlLoaded = false;
133  connect( webPage, SIGNAL( loadFinished( bool ) ), SLOT( loadingHtmlFinished( bool ) ) );
134 
135  webPage->mainFrame()->setHtml( textToDraw );
136 
137  //For very basic html labels with no external assets, the html load will already be
138  //complete before we even get a chance to start the QEventLoop. Make sure we check
139  //this before starting the loop
140  if ( !mHtmlLoaded )
141  {
142  // Start a 20 second timeout in case html loading will never complete
143  timeoutTimer.start( 20000 );
144  // Pause until html is loaded
145  loop.exec();
146  }
147 
148  webPage->mainFrame()->render( painter );//DELETE WEBPAGE ?
149  }
150  else
151  {
152  painter->setFont( mFont );
153  //debug
154  //painter->setPen( QColor( Qt::red ) );
155  //painter->drawRect( painterRect );
156  QgsComposerUtils::drawText( painter, painterRect, textToDraw, mFont, mFontColor, mHAlignment, mVAlignment, Qt::TextWordWrap );
157  }
158 
159  painter->restore();
160 
161  drawFrame( painter );
162  if ( isSelected() )
163  {
164  drawSelectionBoxes( painter );
165  }
166 }
167 
168 /*Track when QWebPage has finished loading its html contents*/
170 {
171  Q_UNUSED( result );
172  mHtmlLoaded = true;
173 }
174 
176 {
177  if ( !mComposition )
178  {
179  return 1.0;
180  }
181 
182  //TODO : fix this more precisely so that the label's default text size is the same with or without "display as html"
183  return ( mComposition->printResolution() / 72.0 ); //webkit seems to assume a standard dpi of 72
184 }
185 
186 void QgsComposerLabel::setText( const QString& text )
187 {
188  mText = text;
189  emit itemChanged();
190 }
191 
192 void QgsComposerLabel::setExpressionContext( QgsFeature* feature, QgsVectorLayer* layer, QMap<QString, QVariant> substitutions )
193 {
194  mExpressionFeature = feature;
195  mExpressionLayer = layer;
196  mSubstitutions = substitutions;
197  // Force label to redraw -- fixes label printing for labels with blend modes when used with atlas
198  update();
199 }
200 
202 {
203  QgsVectorLayer * vl = 0;
204  QgsFeature* feature = 0;
205 
207  {
209  }
211  {
213  }
214 
215  setExpressionContext( feature, vl );
216 }
217 
219 {
220  QString displayText = mText;
221  replaceDateText( displayText );
222  QMap<QString, QVariant> subs = mSubstitutions;
223  subs[ "$page" ] = QVariant(( int )mComposition->itemPageNumber( this ) + 1 );
225 }
226 
227 void QgsComposerLabel::replaceDateText( QString& text ) const
228 {
229  QString constant = "$CURRENT_DATE";
230  int currentDatePos = text.indexOf( constant );
231  if ( currentDatePos != -1 )
232  {
233  //check if there is a bracket just after $CURRENT_DATE
234  QString formatText;
235  int openingBracketPos = text.indexOf( "(", currentDatePos );
236  int closingBracketPos = text.indexOf( ")", openingBracketPos + 1 );
237  if ( openingBracketPos != -1 &&
238  closingBracketPos != -1 &&
239  ( closingBracketPos - openingBracketPos ) > 1 &&
240  openingBracketPos == currentDatePos + constant.size() )
241  {
242  formatText = text.mid( openingBracketPos + 1, closingBracketPos - openingBracketPos - 1 );
243  text.replace( currentDatePos, closingBracketPos - currentDatePos + 1, QDate::currentDate().toString( formatText ) );
244  }
245  else //no bracket
246  {
247  text.replace( "$CURRENT_DATE", QDate::currentDate().toString() );
248  }
249  }
250 }
251 
252 void QgsComposerLabel::setFont( const QFont& f )
253 {
254  mFont = f;
255 }
256 
258 {
259  double textWidth = QgsComposerUtils::textWidthMM( mFont, displayText() );
260  double fontHeight = QgsComposerUtils::fontHeightMM( mFont );
261 
262  double penWidth = hasFrame() ? pen().widthF() : 0;
263 
264  double width = textWidth + 2 * mMargin + 2 * penWidth + 1;
265  double height = fontHeight + 2 * mMargin + 2 * penWidth;
266 
267  //keep alignment point constant
268  double xShift = 0;
269  double yShift = 0;
270  itemShiftAdjustSize( width, height, xShift, yShift );
271 
272  //update rect for data defined size and position
273  QRectF evaluatedRect = evalItemRect( QRectF( pos().x() + xShift, pos().y() + yShift, width, height ) );
274  setSceneRect( evaluatedRect );
275 }
276 
278 {
279  return mFont;
280 }
281 
282 bool QgsComposerLabel::writeXML( QDomElement& elem, QDomDocument & doc ) const
283 {
284  QString alignment;
285 
286  if ( elem.isNull() )
287  {
288  return false;
289  }
290 
291  QDomElement composerLabelElem = doc.createElement( "ComposerLabel" );
292 
293  composerLabelElem.setAttribute( "htmlState", mHtmlState );
294 
295  composerLabelElem.setAttribute( "labelText", mText );
296  composerLabelElem.setAttribute( "margin", QString::number( mMargin ) );
297 
298  composerLabelElem.setAttribute( "halign", mHAlignment );
299  composerLabelElem.setAttribute( "valign", mVAlignment );
300 
301  //font
302  QDomElement labelFontElem = doc.createElement( "LabelFont" );
303  labelFontElem.setAttribute( "description", mFont.toString() );
304  composerLabelElem.appendChild( labelFontElem );
305 
306  //font color
307  QDomElement fontColorElem = doc.createElement( "FontColor" );
308  fontColorElem.setAttribute( "red", mFontColor.red() );
309  fontColorElem.setAttribute( "green", mFontColor.green() );
310  fontColorElem.setAttribute( "blue", mFontColor.blue() );
311  composerLabelElem.appendChild( fontColorElem );
312 
313  elem.appendChild( composerLabelElem );
314  return _writeXML( composerLabelElem, doc );
315 }
316 
317 bool QgsComposerLabel::readXML( const QDomElement& itemElem, const QDomDocument& doc )
318 {
319  QString alignment;
320 
321  if ( itemElem.isNull() )
322  {
323  return false;
324  }
325 
326  //restore label specific properties
327 
328  //text
329  mText = itemElem.attribute( "labelText" );
330 
331  //html state
332  mHtmlState = itemElem.attribute( "htmlState" ).toInt();
333 
334  //margin
335  mMargin = itemElem.attribute( "margin" ).toDouble();
336 
337  //Horizontal alignment
338  mHAlignment = ( Qt::AlignmentFlag )( itemElem.attribute( "halign" ).toInt() );
339 
340  //Vertical alignment
341  mVAlignment = ( Qt::AlignmentFlag )( itemElem.attribute( "valign" ).toInt() );
342 
343  //font
344  QDomNodeList labelFontList = itemElem.elementsByTagName( "LabelFont" );
345  if ( labelFontList.size() > 0 )
346  {
347  QDomElement labelFontElem = labelFontList.at( 0 ).toElement();
348  mFont.fromString( labelFontElem.attribute( "description" ) );
349  }
350 
351  //font color
352  QDomNodeList fontColorList = itemElem.elementsByTagName( "FontColor" );
353  if ( fontColorList.size() > 0 )
354  {
355  QDomElement fontColorElem = fontColorList.at( 0 ).toElement();
356  int red = fontColorElem.attribute( "red", "0" ).toInt();
357  int green = fontColorElem.attribute( "green", "0" ).toInt();
358  int blue = fontColorElem.attribute( "blue", "0" ).toInt();
359  mFontColor = QColor( red, green, blue );
360  }
361  else
362  {
363  mFontColor = QColor( 0, 0, 0 );
364  }
365 
366  //restore general composer item properties
367  QDomNodeList composerItemList = itemElem.elementsByTagName( "ComposerItem" );
368  if ( composerItemList.size() > 0 )
369  {
370  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
371 
372  //rotation
373  if ( composerItemElem.attribute( "rotation", "0" ).toDouble() != 0 )
374  {
375  //check for old (pre 2.1) rotation attribute
376  setItemRotation( composerItemElem.attribute( "rotation", "0" ).toDouble() );
377  }
378 
379  _readXML( composerItemElem, doc );
380  }
381  emit itemChanged();
382  return true;
383 }
384 
386 {
387  if ( !id().isEmpty() )
388  {
389  return id();
390  }
391 
392  //if no id, default to portion of label text
393  QString text = displayText();
394  if ( text.length() > 25 )
395  {
396  return QString( tr( "%1..." ) ).arg( text.left( 25 ) );
397  }
398  else
399  {
400  return text;
401  }
402 }
403 
404 void QgsComposerLabel::itemShiftAdjustSize( double newWidth, double newHeight, double& xShift, double& yShift ) const
405 {
406  //keep alignment point constant
407  double currentWidth = rect().width();
408  double currentHeight = rect().height();
409  xShift = 0;
410  yShift = 0;
411 
412  if ( mItemRotation >= 0 && mItemRotation < 90 )
413  {
414  if ( mHAlignment == Qt::AlignHCenter )
415  {
416  xShift = - ( newWidth - currentWidth ) / 2.0;
417  }
418  else if ( mHAlignment == Qt::AlignRight )
419  {
420  xShift = - ( newWidth - currentWidth );
421  }
422  if ( mVAlignment == Qt::AlignVCenter )
423  {
424  yShift = -( newHeight - currentHeight ) / 2.0;
425  }
426  else if ( mVAlignment == Qt::AlignBottom )
427  {
428  yShift = - ( newHeight - currentHeight );
429  }
430  }
431  if ( mItemRotation >= 90 && mItemRotation < 180 )
432  {
433  if ( mHAlignment == Qt::AlignHCenter )
434  {
435  yShift = -( newHeight - currentHeight ) / 2.0;
436  }
437  else if ( mHAlignment == Qt::AlignRight )
438  {
439  yShift = -( newHeight - currentHeight );
440  }
441  if ( mVAlignment == Qt::AlignTop )
442  {
443  xShift = -( newWidth - currentWidth );
444  }
445  else if ( mVAlignment == Qt::AlignVCenter )
446  {
447  xShift = -( newWidth - currentWidth / 2.0 );
448  }
449  }
450  else if ( mItemRotation >= 180 && mItemRotation < 270 )
451  {
452  if ( mHAlignment == Qt::AlignHCenter )
453  {
454  xShift = -( newWidth - currentWidth ) / 2.0;
455  }
456  else if ( mHAlignment == Qt::AlignLeft )
457  {
458  xShift = -( newWidth - currentWidth );
459  }
460  if ( mVAlignment == Qt::AlignVCenter )
461  {
462  yShift = ( newHeight - currentHeight ) / 2.0;
463  }
464  else if ( mVAlignment == Qt::AlignTop )
465  {
466  yShift = ( newHeight - currentHeight );
467  }
468  }
469  else if ( mItemRotation >= 270 && mItemRotation < 360 )
470  {
471  if ( mHAlignment == Qt::AlignHCenter )
472  {
473  yShift = -( newHeight - currentHeight ) / 2.0;
474  }
475  else if ( mHAlignment == Qt::AlignLeft )
476  {
477  yShift = -( newHeight - currentHeight );
478  }
479  if ( mVAlignment == Qt::AlignBottom )
480  {
481  xShift = -( newWidth - currentWidth );
482  }
483  else if ( mVAlignment == Qt::AlignVCenter )
484  {
485  xShift = -( newWidth - currentWidth / 2.0 );
486  }
487  }
488 }
QgsComposition::AtlasMode atlasMode() const
Returns the current atlas mode of the composition.
QgsVectorLayer * mExpressionLayer
void itemChanged()
Emitted when the item changes.
void replaceDateText(QString &text) const
Replaces replace '$CURRENT_DATE<(FORMAT)>' with the current date (e.g.
A item that forms part of a map composition.
static void drawText(QPainter *painter, const QPointF &pos, const QString &text, const QFont &font, const QColor &color=QColor())
Draws text on a painter at a specific position, taking care of composer specific issues (calculation ...
bool enabled() const
Returns whether the atlas generation is enabled.
virtual void drawFrame(QPainter *p)
Draw black frame around item.
void setFont(const QFont &f)
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:113
bool _readXML(const QDomElement &itemElem, const QDomDocument &doc)
Reads parameter that are not subclass specific in document.
QgsFeature * mExpressionFeature
int itemPageNumber(const QgsComposerItem *) const
Returns on which page number (0-based) is displayed an item.
QFont font() const
virtual void drawSelectionBoxes(QPainter *p)
Draw selection boxes around item.
void itemShiftAdjustSize(double newWidth, double newHeight, double &xShift, double &yShift) const
Helper function to calculate x/y shift for adjustSizeToText() depending on rotation, current size and alignment.
int printResolution() const
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget)
Reimplementation of QCanvasItem::paint.
QMap< QString, QVariant > mSubstitutions
virtual QString displayName() const
Get item display name.
QgsComposerLabel(QgsComposition *composition)
Qt::AlignmentFlag mHAlignment
void setBackgroundEnabled(const bool drawBackground)
Set whether this item has a Background drawn around it or not.
QRectF evalItemRect(const QRectF &newRect)
Update an item rect to consider data defined position and size of item.
Graphics scene for map printing.
QgsFeature * currentFeature()
Returns the current atlas feature.
static double textWidthMM(const QFont &font, const QString &text)
Calculate font width in millimeters for a string, including workarounds for QT font rendering issues...
QgsComposition * mComposition
static double fontHeightMM(const QFont &font)
Calculate font height in millimeters, including workarounds for QT font rendering issues The font hei...
bool _writeXML(QDomElement &itemElem, QDomDocument &doc) const
Writes parameter that are not subclass specific in document.
static QgsNetworkAccessManager * instance()
returns a pointer to the single instance
Qt::AlignmentFlag mVAlignment
virtual void setItemRotation(const double r, const bool adjustPosition=false)
Sets the item rotation.
virtual void drawBackground(QPainter *p)
Draw background.
bool hasFrame() const
Whether this item has a frame or not.
virtual void setSceneRect(const QRectF &rectangle)
Sets this items bound in scene coordinates such that 1 item size units corresponds to 1 scene size un...
void setText(const QString &text)
QgsAtlasComposition & atlasComposition()
void setExpressionContext(QgsFeature *feature, QgsVectorLayer *layer, QMap< QString, QVariant > substitutions=(QMap< QString, QVariant >()))
Sets the current feature, the current layer and a list of local variable substitutions for evaluating...
QgsVectorLayer * coverageLayer() const
Returns the coverage layer used for the atlas features.
QgsComposition::PlotStyle plotStyle() const
Represents a vector layer which manages a vector based data sets.
QString displayText() const
Returns the text as it appears on screen (with replaced data field)
double mItemRotation
Item rotation in degrees, clockwise.
bool readXML(const QDomElement &itemElem, const QDomDocument &doc)
sets state from Dom document
static QString replaceExpressionText(const QString &action, const QgsFeature *feat, QgsVectorLayer *layer, const QMap< QString, QVariant > *substitutionMap=0)
This function currently replaces each expression between [% and %] in the string with the result of i...
void loadingHtmlFinished(bool)
void adjustSizeToText()
resizes the widget such that the text fits to the item.
bool writeXML(QDomElement &elem, QDomDocument &doc) const
stores state in Dom element
#define tr(sourceText)
QString id() const
Get item's id (which is not necessarly unique)