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