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