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 #include "qgscomposermodel.h"
24 
25 #include <QCoreApplication>
26 #include <QDate>
27 #include <QDomElement>
28 #include <QPainter>
29 #include <QSettings>
30 #include <QTimer>
31 #include <QWebFrame>
32 #include <QWebPage>
33 #include <QEventLoop>
34 
36  QgsComposerItem( composition ), mHtmlState( 0 ), mHtmlUnitsToMM( 1.0 ),
37  mHtmlLoaded( false ), mMargin( 1.0 ), mFontColor( QColor( 0, 0, 0 ) ),
38  mHAlignment( Qt::AlignLeft ), mVAlignment( Qt::AlignTop ),
39  mExpressionFeature( 0 ), mExpressionLayer( 0 )
40 {
42 
43  //get default composer font from settings
44  QSettings settings;
45  QString defaultFontString = settings.value( "/Composer/defaultFont" ).toString();
46  if ( !defaultFontString.isEmpty() )
47  {
48  mFont.setFamily( defaultFontString );
49  }
50 
51  //default to a 10 point font size
52  mFont.setPointSizeF( 10 );
53 
54  //default to no background
55  setBackgroundEnabled( false );
56 
58  {
59  //a label added while atlas preview is enabled needs to have the expression context set,
60  //otherwise fields in the label aren't correctly evaluated until atlas preview feature changes (#9457)
62  }
63 
64  //connect to atlas feature changes
65  //to update the expression context
66  connect( &mComposition->atlasComposition(), SIGNAL( featureChanged( QgsFeature* ) ), this, SLOT( refreshExpressionContext() ) );
67 
68 }
69 
71 {
72 }
73 
74 void QgsComposerLabel::paint( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle, QWidget* pWidget )
75 {
76  Q_UNUSED( itemStyle );
77  Q_UNUSED( pWidget );
78  if ( !painter )
79  {
80  return;
81  }
82 
83  drawBackground( painter );
84  painter->save();
85 
86  //antialiasing on
87  painter->setRenderHint( QPainter::Antialiasing, true );
88 
89  double penWidth = hasFrame() ? pen().widthF() : 0;
90  QRectF painterRect( penWidth + mMargin, penWidth + mMargin, rect().width() - 2 * penWidth - 2 * mMargin, rect().height() - 2 * penWidth - 2 * mMargin );
91 
92  QString textToDraw = displayText();
93 
94  if ( mHtmlState )
95  {
96  painter->scale( 1.0 / mHtmlUnitsToMM / 10.0, 1.0 / mHtmlUnitsToMM / 10.0 );
97 
98  QWebPage *webPage = new QWebPage();
99  webPage->setNetworkAccessManager( QgsNetworkAccessManager::instance() );
100 
101  //Setup event loop and timeout for rendering html
102  QEventLoop loop;
103  QTimer timeoutTimer;
104  timeoutTimer.setSingleShot( true );
105 
106  //This makes the background transparent. Found on http://blog.qt.digia.com/blog/2009/06/30/transparent-qwebview-or-qwebpage/
107  QPalette palette = webPage->palette();
108  palette.setBrush( QPalette::Base, Qt::transparent );
109  webPage->setPalette( palette );
110  //webPage->setAttribute(Qt::WA_OpaquePaintEvent, false); //this does not compile, why ?
111 
112  webPage->setViewportSize( QSize( painterRect.width() * mHtmlUnitsToMM * 10.0, painterRect.height() * mHtmlUnitsToMM * 10.0 ) );
113  webPage->mainFrame()->setZoomFactor( 10.0 );
114  webPage->mainFrame()->setScrollBarPolicy( Qt::Horizontal, Qt::ScrollBarAlwaysOff );
115  webPage->mainFrame()->setScrollBarPolicy( Qt::Vertical, Qt::ScrollBarAlwaysOff );
116 
117  // QGIS segfaults when rendering web page while in composer if html
118  // contains images. So if we are not printing the composition, then
119  // disable image loading
122  {
123  webPage->settings()->setAttribute( QWebSettings::AutoLoadImages, false );
124  }
125 
126  //Connect timeout and webpage loadFinished signals to loop
127  connect( &timeoutTimer, SIGNAL( timeout() ), &loop, SLOT( quit() ) );
128  connect( webPage, SIGNAL( loadFinished( bool ) ), &loop, SLOT( quit() ) );
129 
130  //mHtmlLoaded tracks whether the QWebPage has completed loading
131  //its html contents, set it initially to false. The loadingHtmlFinished slot will
132  //set this to true after html is loaded.
133  mHtmlLoaded = false;
134  connect( webPage, SIGNAL( loadFinished( bool ) ), SLOT( loadingHtmlFinished( bool ) ) );
135 
136  webPage->mainFrame()->setHtml( textToDraw );
137 
138  //For very basic html labels with no external assets, the html load will already be
139  //complete before we even get a chance to start the QEventLoop. Make sure we check
140  //this before starting the loop
141  if ( !mHtmlLoaded )
142  {
143  // Start a 20 second timeout in case html loading will never complete
144  timeoutTimer.start( 20000 );
145  // Pause until html is loaded
146  loop.exec();
147  }
148 
149  webPage->mainFrame()->render( painter );//DELETE WEBPAGE ?
150  }
151  else
152  {
153  painter->setFont( mFont );
154  //debug
155  //painter->setPen( QColor( Qt::red ) );
156  //painter->drawRect( painterRect );
157  QgsComposerUtils::drawText( painter, painterRect, textToDraw, mFont, mFontColor, mHAlignment, mVAlignment, Qt::TextWordWrap );
158  }
159 
160  painter->restore();
161 
162  drawFrame( painter );
163  if ( isSelected() )
164  {
165  drawSelectionBoxes( painter );
166  }
167 }
168 
169 /*Track when QWebPage has finished loading its html contents*/
171 {
172  Q_UNUSED( result );
173  mHtmlLoaded = true;
174 }
175 
177 {
178  if ( !mComposition )
179  {
180  return 1.0;
181  }
182 
183  //TODO : fix this more precisely so that the label's default text size is the same with or without "display as html"
184  return ( mComposition->printResolution() / 72.0 ); //webkit seems to assume a standard dpi of 72
185 }
186 
187 void QgsComposerLabel::setText( const QString& text )
188 {
189  mText = text;
190  emit itemChanged();
191 
192  if ( mComposition && id().isEmpty() && !mHtmlState )
193  {
194  //notify the model that the display name has changed
196  }
197 }
198 
200 {
201  if ( state == mHtmlState )
202  {
203  return;
204  }
205 
206  mHtmlState = state;
207 
208  if ( mComposition && id().isEmpty() )
209  {
210  //notify the model that the display name has changed
212  }
213 }
214 
215 void QgsComposerLabel::setExpressionContext( QgsFeature* feature, QgsVectorLayer* layer, QMap<QString, QVariant> substitutions )
216 {
217  mExpressionFeature = feature;
218  mExpressionLayer = layer;
219  mSubstitutions = substitutions;
220  // Force label to redraw -- fixes label printing for labels with blend modes when used with atlas
221  update();
222 }
223 
225 {
226  QgsVectorLayer * vl = 0;
227  QgsFeature* feature = 0;
228 
230  {
232  }
234  {
236  }
237 
238  setExpressionContext( feature, vl );
239 }
240 
242 {
243  QString displayText = mText;
244  replaceDateText( displayText );
245  QMap<QString, QVariant> subs = mSubstitutions;
246  subs[ "$page" ] = QVariant(( int )mComposition->itemPageNumber( this ) + 1 );
248 }
249 
250 void QgsComposerLabel::replaceDateText( QString& text ) const
251 {
252  QString constant = "$CURRENT_DATE";
253  int currentDatePos = text.indexOf( constant );
254  if ( currentDatePos != -1 )
255  {
256  //check if there is a bracket just after $CURRENT_DATE
257  QString formatText;
258  int openingBracketPos = text.indexOf( "(", currentDatePos );
259  int closingBracketPos = text.indexOf( ")", openingBracketPos + 1 );
260  if ( openingBracketPos != -1 &&
261  closingBracketPos != -1 &&
262  ( closingBracketPos - openingBracketPos ) > 1 &&
263  openingBracketPos == currentDatePos + constant.size() )
264  {
265  formatText = text.mid( openingBracketPos + 1, closingBracketPos - openingBracketPos - 1 );
266  text.replace( currentDatePos, closingBracketPos - currentDatePos + 1, QDate::currentDate().toString( formatText ) );
267  }
268  else //no bracket
269  {
270  text.replace( "$CURRENT_DATE", QDate::currentDate().toString() );
271  }
272  }
273 }
274 
275 void QgsComposerLabel::setFont( const QFont& f )
276 {
277  mFont = f;
278 }
279 
281 {
282  double textWidth = QgsComposerUtils::textWidthMM( mFont, displayText() );
283  double fontHeight = QgsComposerUtils::fontHeightMM( mFont );
284 
285  double penWidth = hasFrame() ? pen().widthF() : 0;
286 
287  double width = textWidth + 2 * mMargin + 2 * penWidth + 1;
288  double height = fontHeight + 2 * mMargin + 2 * penWidth;
289 
290  //keep alignment point constant
291  double xShift = 0;
292  double yShift = 0;
293  itemShiftAdjustSize( width, height, xShift, yShift );
294 
295  //update rect for data defined size and position
296  QRectF evaluatedRect = evalItemRect( QRectF( pos().x() + xShift, pos().y() + yShift, width, height ) );
297  setSceneRect( evaluatedRect );
298 }
299 
301 {
302  return mFont;
303 }
304 
305 bool QgsComposerLabel::writeXML( QDomElement& elem, QDomDocument & doc ) const
306 {
307  QString alignment;
308 
309  if ( elem.isNull() )
310  {
311  return false;
312  }
313 
314  QDomElement composerLabelElem = doc.createElement( "ComposerLabel" );
315 
316  composerLabelElem.setAttribute( "htmlState", mHtmlState );
317 
318  composerLabelElem.setAttribute( "labelText", mText );
319  composerLabelElem.setAttribute( "margin", QString::number( mMargin ) );
320 
321  composerLabelElem.setAttribute( "halign", mHAlignment );
322  composerLabelElem.setAttribute( "valign", mVAlignment );
323 
324  //font
325  QDomElement labelFontElem = doc.createElement( "LabelFont" );
326  labelFontElem.setAttribute( "description", mFont.toString() );
327  composerLabelElem.appendChild( labelFontElem );
328 
329  //font color
330  QDomElement fontColorElem = doc.createElement( "FontColor" );
331  fontColorElem.setAttribute( "red", mFontColor.red() );
332  fontColorElem.setAttribute( "green", mFontColor.green() );
333  fontColorElem.setAttribute( "blue", mFontColor.blue() );
334  composerLabelElem.appendChild( fontColorElem );
335 
336  elem.appendChild( composerLabelElem );
337  return _writeXML( composerLabelElem, doc );
338 }
339 
340 bool QgsComposerLabel::readXML( const QDomElement& itemElem, const QDomDocument& doc )
341 {
342  QString alignment;
343 
344  if ( itemElem.isNull() )
345  {
346  return false;
347  }
348 
349  //restore label specific properties
350 
351  //text
352  mText = itemElem.attribute( "labelText" );
353 
354  //html state
355  mHtmlState = itemElem.attribute( "htmlState" ).toInt();
356 
357  //margin
358  mMargin = itemElem.attribute( "margin" ).toDouble();
359 
360  //Horizontal alignment
361  mHAlignment = ( Qt::AlignmentFlag )( itemElem.attribute( "halign" ).toInt() );
362 
363  //Vertical alignment
364  mVAlignment = ( Qt::AlignmentFlag )( itemElem.attribute( "valign" ).toInt() );
365 
366  //font
367  QDomNodeList labelFontList = itemElem.elementsByTagName( "LabelFont" );
368  if ( labelFontList.size() > 0 )
369  {
370  QDomElement labelFontElem = labelFontList.at( 0 ).toElement();
371  mFont.fromString( labelFontElem.attribute( "description" ) );
372  }
373 
374  //font color
375  QDomNodeList fontColorList = itemElem.elementsByTagName( "FontColor" );
376  if ( fontColorList.size() > 0 )
377  {
378  QDomElement fontColorElem = fontColorList.at( 0 ).toElement();
379  int red = fontColorElem.attribute( "red", "0" ).toInt();
380  int green = fontColorElem.attribute( "green", "0" ).toInt();
381  int blue = fontColorElem.attribute( "blue", "0" ).toInt();
382  mFontColor = QColor( red, green, blue );
383  }
384  else
385  {
386  mFontColor = QColor( 0, 0, 0 );
387  }
388 
389  //restore general composer item properties
390  QDomNodeList composerItemList = itemElem.elementsByTagName( "ComposerItem" );
391  if ( composerItemList.size() > 0 )
392  {
393  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
394 
395  //rotation
396  if ( composerItemElem.attribute( "rotation", "0" ).toDouble() != 0 )
397  {
398  //check for old (pre 2.1) rotation attribute
399  setItemRotation( composerItemElem.attribute( "rotation", "0" ).toDouble() );
400  }
401 
402  _readXML( composerItemElem, doc );
403  }
404  emit itemChanged();
405  return true;
406 }
407 
409 {
410  if ( !id().isEmpty() )
411  {
412  return id();
413  }
414 
415  if ( mHtmlState )
416  {
417  return tr( "<HTML label>" );
418  }
419 
420  //if no id, default to portion of label text
421  QString text = displayText();
422  if ( text.isEmpty() )
423  {
424  return tr( "<label>" );
425  }
426  if ( text.length() > 25 )
427  {
428  return QString( tr( "%1..." ) ).arg( text.left( 25 ) );
429  }
430  else
431  {
432  return text;
433  }
434 }
435 
436 void QgsComposerLabel::itemShiftAdjustSize( double newWidth, double newHeight, double& xShift, double& yShift ) const
437 {
438  //keep alignment point constant
439  double currentWidth = rect().width();
440  double currentHeight = rect().height();
441  xShift = 0;
442  yShift = 0;
443 
444  if ( mItemRotation >= 0 && mItemRotation < 90 )
445  {
446  if ( mHAlignment == Qt::AlignHCenter )
447  {
448  xShift = - ( newWidth - currentWidth ) / 2.0;
449  }
450  else if ( mHAlignment == Qt::AlignRight )
451  {
452  xShift = - ( newWidth - currentWidth );
453  }
454  if ( mVAlignment == Qt::AlignVCenter )
455  {
456  yShift = -( newHeight - currentHeight ) / 2.0;
457  }
458  else if ( mVAlignment == Qt::AlignBottom )
459  {
460  yShift = - ( newHeight - currentHeight );
461  }
462  }
463  if ( mItemRotation >= 90 && mItemRotation < 180 )
464  {
465  if ( mHAlignment == Qt::AlignHCenter )
466  {
467  yShift = -( newHeight - currentHeight ) / 2.0;
468  }
469  else if ( mHAlignment == Qt::AlignRight )
470  {
471  yShift = -( newHeight - currentHeight );
472  }
473  if ( mVAlignment == Qt::AlignTop )
474  {
475  xShift = -( newWidth - currentWidth );
476  }
477  else if ( mVAlignment == Qt::AlignVCenter )
478  {
479  xShift = -( newWidth - currentWidth / 2.0 );
480  }
481  }
482  else if ( mItemRotation >= 180 && mItemRotation < 270 )
483  {
484  if ( mHAlignment == Qt::AlignHCenter )
485  {
486  xShift = -( newWidth - currentWidth ) / 2.0;
487  }
488  else if ( mHAlignment == Qt::AlignLeft )
489  {
490  xShift = -( newWidth - currentWidth );
491  }
492  if ( mVAlignment == Qt::AlignVCenter )
493  {
494  yShift = ( newHeight - currentHeight ) / 2.0;
495  }
496  else if ( mVAlignment == Qt::AlignTop )
497  {
498  yShift = ( newHeight - currentHeight );
499  }
500  }
501  else if ( mItemRotation >= 270 && mItemRotation < 360 )
502  {
503  if ( mHAlignment == Qt::AlignHCenter )
504  {
505  yShift = -( newHeight - currentHeight ) / 2.0;
506  }
507  else if ( mHAlignment == Qt::AlignLeft )
508  {
509  yShift = -( newHeight - currentHeight );
510  }
511  if ( mVAlignment == Qt::AlignBottom )
512  {
513  xShift = -( newWidth - currentWidth );
514  }
515  else if ( mVAlignment == Qt::AlignVCenter )
516  {
517  xShift = -( newWidth - currentWidth / 2.0 );
518  }
519  }
520 }