QGIS API Documentation  2.99.0-Master (9fdd060)
qgscomposerlegend.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscomposerlegend.cpp - description
3  ---------------------
4  begin : June 2008
5  copyright : (C) 2008 by Marco Hugentobler
6  email : marco dot hugentobler at karto dot baug dot ethz dot ch
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 #include <limits>
18 
19 #include "qgscomposerlegend.h"
20 #include "qgscomposermap.h"
21 #include "qgscomposition.h"
22 #include "qgscomposermodel.h"
23 #include "qgslayertree.h"
24 #include "qgslayertreemodel.h"
25 #include "qgslegendrenderer.h"
26 #include "qgslegendstyle.h"
27 #include "qgslogger.h"
28 #include "qgsmapsettings.h"
29 #include "qgsproject.h"
30 #include "qgssymbollayerutils.h"
31 #include "qgslayertreeutils.h"
32 #include <QDomDocument>
33 #include <QDomElement>
34 #include <QPainter>
35 
37  : QgsComposerItem( composition )
38  , mLegendModel( new QgsLegendModel( mComposition->project()->layerTreeRoot() ) )
39 {
40  connect( &composition->atlasComposition(), &QgsAtlasComposition::renderEnded, this, &QgsComposerLegend::onAtlasEnded );
41  connect( &composition->atlasComposition(), &QgsAtlasComposition::featureChanged, this, &QgsComposerLegend::onAtlasFeature );
42 
43  // Connect to the main layertreeroot.
44  // It serves in "auto update mode" as a medium between the main app legend and this one
45  connect( mComposition->project()->layerTreeRoot(), &QgsLayerTreeNode::customPropertyChanged, this, &QgsComposerLegend::nodeCustomPropertyChanged );
46 }
47 
49  : QgsComposerItem( nullptr )
50 {
51 
52 }
53 
54 void QgsComposerLegend::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget )
55 {
56  Q_UNUSED( itemStyle );
57  Q_UNUSED( pWidget );
58 
59  if ( !painter )
60  return;
61 
62  if ( !shouldDrawItem() )
63  {
64  return;
65  }
66 
67  if ( mFilterAskedForUpdate )
68  {
69  mFilterAskedForUpdate = false;
70  doUpdateFilterByMap();
71  }
72 
73  int dpi = painter->device()->logicalDpiX();
74  double dotsPerMM = dpi / 25.4;
75 
76  if ( mComposition )
77  {
79  mSettings.setDpi( dpi );
80  }
81  if ( mComposerMap )
82  {
83  mSettings.setMmPerMapUnit( mComposerMap->mapUnitsToMM() );
84 
85  // use a temporary QgsMapSettings to find out real map scale
86  QSizeF mapSizePixels = QSizeF( mComposerMap->rect().width() * dotsPerMM, mComposerMap->rect().height() * dotsPerMM );
87  QgsRectangle mapExtent = *mComposerMap->currentMapExtent();
88 
89  QgsMapSettings ms = mComposerMap->mapSettings( mapExtent, mapSizePixels, dpi );
90  mSettings.setMapScale( ms.scale() );
91  }
92  mInitialMapScaleCalculated = true;
93 
94  QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
95  legendRenderer.setLegendSize( mForceResize && mSizeToContents ? QSize() : rect().size() );
96 
97  //adjust box if width or height is too small
98  if ( mSizeToContents )
99  {
100  QSizeF size = legendRenderer.minimumSize();
101  if ( mForceResize )
102  {
103  mForceResize = false;
104  //set new rect, respecting position mode and data defined size/position
105  QRectF targetRect = QRectF( pos().x(), pos().y(), size.width(), size.height() );
106  setSceneRect( evalItemRect( targetRect, true ) );
107  }
108  else if ( size.height() > rect().height() || size.width() > rect().width() )
109  {
110  //need to resize box
111  QRectF targetRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
112  if ( size.height() > targetRect.height() )
113  targetRect.setHeight( size.height() );
114  if ( size.width() > rect().width() )
115  targetRect.setWidth( size.width() );
116 
117  //set new rect, respecting position mode and data defined size/position
118  setSceneRect( evalItemRect( targetRect, true ) );
119  }
120  }
121 
122  drawBackground( painter );
123  painter->save();
124  //antialiasing on
125  painter->setRenderHint( QPainter::Antialiasing, true );
126  painter->setPen( QPen( QColor( 0, 0, 0 ) ) );
127 
128  if ( !mSizeToContents )
129  {
130  // set a clip region to crop out parts of legend which don't fit
131  QRectF thisPaintRect = QRectF( 0, 0, rect().width(), rect().height() );
132  painter->setClipRect( thisPaintRect );
133  }
134 
135  legendRenderer.drawLegend( painter );
136 
137  painter->restore();
138 
139  //draw frame and selection boxes if necessary
140  drawFrame( painter );
141  if ( isSelected() )
142  {
143  drawSelectionBoxes( painter );
144  }
145 }
146 
147 QSizeF QgsComposerLegend::paintAndDetermineSize( QPainter *painter )
148 {
149  if ( mFilterAskedForUpdate )
150  {
151  mFilterAskedForUpdate = false;
152  doUpdateFilterByMap();
153  }
154 
155  QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
156  QSizeF size = legendRenderer.minimumSize();
157  if ( painter )
158  legendRenderer.drawLegend( painter );
159  return size;
160 }
161 
162 
164 {
165  if ( !mSizeToContents )
166  return;
167 
168  if ( !mInitialMapScaleCalculated )
169  {
170  // this is messy - but until we have painted the item we have no knowledge of the current DPI
171  // and so cannot correctly calculate the map scale. This results in incorrect size calculations
172  // for marker symbols with size in map units, causing the legends to initially expand to huge
173  // sizes if we attempt to calculate the box size first.
174  return;
175  }
176 
177  QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
178  QSizeF size = legendRenderer.minimumSize();
179  QgsDebugMsg( QString( "width = %1 height = %2" ).arg( size.width() ).arg( size.height() ) );
180  if ( size.isValid() )
181  {
182  QRectF targetRect = QRectF( pos().x(), pos().y(), size.width(), size.height() );
183  //set new rect, respecting position mode and data defined size/position
184  setSceneRect( evalItemRect( targetRect, true ) );
185  }
186 }
187 
189 {
190  mSizeToContents = enabled;
191 }
192 
194 {
195  return mSizeToContents;
196 }
197 
198 void QgsComposerLegend::setCustomLayerTree( QgsLayerTree *rootGroup )
199 {
200  mLegendModel->setRootGroup( rootGroup ? rootGroup : ( mComposition ? mComposition->project()->layerTreeRoot() : nullptr ) );
201 
202  mCustomLayerTree.reset( rootGroup );
203 }
204 
205 
207 {
208  if ( autoUpdate == autoUpdateModel() )
209  return;
210 
211  setCustomLayerTree( autoUpdate ? nullptr : mComposition->project()->layerTreeRoot()->clone() );
212  adjustBoxSize();
213  updateItem();
214 }
215 
216 void QgsComposerLegend::nodeCustomPropertyChanged( QgsLayerTreeNode *, const QString & )
217 {
218  if ( autoUpdateModel() )
219  {
220  // in "auto update" mode, some parameters on the main app legend may have been changed (expression filtering)
221  // we must then call updateItem to reflect the changes
222  updateItem();
223  }
224 }
225 
227 {
228  return !mCustomLayerTree;
229 }
230 
232 {
233  mLegendFilterByMap = enabled;
234  updateItem();
235 }
236 
237 void QgsComposerLegend::setTitle( const QString &t )
238 {
239  mTitle = t;
240  mSettings.setTitle( t );
241 
242  if ( mComposition && id().isEmpty() )
243  {
244  //notify the model that the display name has changed
246  }
247 }
248 QString QgsComposerLegend::title() const { return mTitle; }
249 
250 Qt::AlignmentFlag QgsComposerLegend::titleAlignment() const { return mSettings.titleAlignment(); }
251 void QgsComposerLegend::setTitleAlignment( Qt::AlignmentFlag alignment ) { mSettings.setTitleAlignment( alignment ); }
252 
256 
257 QFont QgsComposerLegend::styleFont( QgsLegendStyle::Style s ) const { return mSettings.style( s ).font(); }
259 
260 void QgsComposerLegend::setStyleMargin( QgsLegendStyle::Style s, double margin ) { rstyle( s ).setMargin( margin ); }
261 void QgsComposerLegend::setStyleMargin( QgsLegendStyle::Style s, QgsLegendStyle::Side side, double margin ) { rstyle( s ).setMargin( side, margin ); }
262 
263 double QgsComposerLegend::lineSpacing() const { return mSettings.lineSpacing(); }
264 void QgsComposerLegend::setLineSpacing( double spacing ) { mSettings.setLineSpacing( spacing ); }
265 
266 double QgsComposerLegend::boxSpace() const { return mSettings.boxSpace(); }
267 void QgsComposerLegend::setBoxSpace( double s ) { mSettings.setBoxSpace( s ); }
268 
269 double QgsComposerLegend::columnSpace() const { return mSettings.columnSpace(); }
270 void QgsComposerLegend::setColumnSpace( double s ) { mSettings.setColumnSpace( s ); }
271 
272 QColor QgsComposerLegend::fontColor() const { return mSettings.fontColor(); }
273 void QgsComposerLegend::setFontColor( const QColor &c ) { mSettings.setFontColor( c ); }
274 
275 double QgsComposerLegend::symbolWidth() const { return mSettings.symbolSize().width(); }
276 void QgsComposerLegend::setSymbolWidth( double w ) { mSettings.setSymbolSize( QSizeF( w, mSettings.symbolSize().height() ) ); }
277 
278 double QgsComposerLegend::symbolHeight() const { return mSettings.symbolSize().height(); }
279 void QgsComposerLegend::setSymbolHeight( double h ) { mSettings.setSymbolSize( QSizeF( mSettings.symbolSize().width(), h ) ); }
280 
281 double QgsComposerLegend::wmsLegendWidth() const { return mSettings.wmsLegendSize().width(); }
282 void QgsComposerLegend::setWmsLegendWidth( double w ) { mSettings.setWmsLegendSize( QSizeF( w, mSettings.wmsLegendSize().height() ) ); }
283 
284 double QgsComposerLegend::wmsLegendHeight() const {return mSettings.wmsLegendSize().height(); }
285 void QgsComposerLegend::setWmsLegendHeight( double h ) { mSettings.setWmsLegendSize( QSizeF( mSettings.wmsLegendSize().width(), h ) ); }
286 
287 void QgsComposerLegend::setWrapChar( const QString &t ) { mSettings.setWrapChar( t ); }
288 QString QgsComposerLegend::wrapChar() const {return mSettings.wrapChar(); }
289 
290 int QgsComposerLegend::columnCount() const { return mColumnCount; }
291 void QgsComposerLegend::setColumnCount( int c ) { mColumnCount = c; mSettings.setColumnCount( c ); }
292 
293 bool QgsComposerLegend::splitLayer() const { return mSettings.splitLayer(); }
294 void QgsComposerLegend::setSplitLayer( bool s ) { mSettings.setSplitLayer( s ); }
295 
296 bool QgsComposerLegend::equalColumnWidth() const { return mSettings.equalColumnWidth(); }
298 
299 bool QgsComposerLegend::drawRasterStroke() const { return mSettings.drawRasterStroke(); }
300 void QgsComposerLegend::setDrawRasterStroke( bool enabled ) { mSettings.setDrawRasterStroke( enabled ); }
301 
302 QColor QgsComposerLegend::rasterStrokeColor() const { return mSettings.rasterStrokeColor(); }
303 void QgsComposerLegend::setRasterStrokeColor( const QColor &color ) { mSettings.setRasterStrokeColor( color ); }
304 
305 double QgsComposerLegend::rasterStrokeWidth() const { return mSettings.rasterStrokeWidth(); }
306 void QgsComposerLegend::setRasterStrokeWidth( double width ) { mSettings.setRasterStrokeWidth( width ); }
307 
309 {
310  adjustBoxSize();
311  updateItem();
312 }
313 
315 {
316  adjustBoxSize();
317  updateItem();
318 }
319 
321 {
322  if ( !updatesEnabled() )
323  return;
324 
325  updateFilterByMap( false );
327 }
328 
329 bool QgsComposerLegend::writeXml( QDomElement &elem, QDomDocument &doc ) const
330 {
331  if ( elem.isNull() )
332  {
333  return false;
334  }
335 
336  QDomElement composerLegendElem = doc.createElement( QStringLiteral( "ComposerLegend" ) );
337  elem.appendChild( composerLegendElem );
338 
339  //write general properties
340  composerLegendElem.setAttribute( QStringLiteral( "title" ), mTitle );
341  composerLegendElem.setAttribute( QStringLiteral( "titleAlignment" ), QString::number( static_cast< int >( mSettings.titleAlignment() ) ) );
342  composerLegendElem.setAttribute( QStringLiteral( "columnCount" ), QString::number( mColumnCount ) );
343  composerLegendElem.setAttribute( QStringLiteral( "splitLayer" ), QString::number( mSettings.splitLayer() ) );
344  composerLegendElem.setAttribute( QStringLiteral( "equalColumnWidth" ), QString::number( mSettings.equalColumnWidth() ) );
345 
346  composerLegendElem.setAttribute( QStringLiteral( "boxSpace" ), QString::number( mSettings.boxSpace() ) );
347  composerLegendElem.setAttribute( QStringLiteral( "columnSpace" ), QString::number( mSettings.columnSpace() ) );
348 
349  composerLegendElem.setAttribute( QStringLiteral( "symbolWidth" ), QString::number( mSettings.symbolSize().width() ) );
350  composerLegendElem.setAttribute( QStringLiteral( "symbolHeight" ), QString::number( mSettings.symbolSize().height() ) );
351  composerLegendElem.setAttribute( QStringLiteral( "lineSpacing" ), QString::number( mSettings.lineSpacing() ) );
352 
353  composerLegendElem.setAttribute( QStringLiteral( "rasterBorder" ), mSettings.drawRasterStroke() );
354  composerLegendElem.setAttribute( QStringLiteral( "rasterBorderColor" ), QgsSymbolLayerUtils::encodeColor( mSettings.rasterStrokeColor() ) );
355  composerLegendElem.setAttribute( QStringLiteral( "rasterBorderWidth" ), QString::number( mSettings.rasterStrokeWidth() ) );
356 
357  composerLegendElem.setAttribute( QStringLiteral( "wmsLegendWidth" ), QString::number( mSettings.wmsLegendSize().width() ) );
358  composerLegendElem.setAttribute( QStringLiteral( "wmsLegendHeight" ), QString::number( mSettings.wmsLegendSize().height() ) );
359  composerLegendElem.setAttribute( QStringLiteral( "wrapChar" ), mSettings.wrapChar() );
360  composerLegendElem.setAttribute( QStringLiteral( "fontColor" ), mSettings.fontColor().name() );
361 
362  composerLegendElem.setAttribute( QStringLiteral( "resizeToContents" ), mSizeToContents );
363 
364  if ( mComposerMap )
365  {
366  composerLegendElem.setAttribute( QStringLiteral( "map" ), mComposerMap->id() );
367  }
368 
369  QDomElement composerLegendStyles = doc.createElement( QStringLiteral( "styles" ) );
370  composerLegendElem.appendChild( composerLegendStyles );
371 
372  style( QgsLegendStyle::Title ).writeXml( QStringLiteral( "title" ), composerLegendStyles, doc );
373  style( QgsLegendStyle::Group ).writeXml( QStringLiteral( "group" ), composerLegendStyles, doc );
374  style( QgsLegendStyle::Subgroup ).writeXml( QStringLiteral( "subgroup" ), composerLegendStyles, doc );
375  style( QgsLegendStyle::Symbol ).writeXml( QStringLiteral( "symbol" ), composerLegendStyles, doc );
376  style( QgsLegendStyle::SymbolLabel ).writeXml( QStringLiteral( "symbolLabel" ), composerLegendStyles, doc );
377 
378  if ( mCustomLayerTree )
379  {
380  // if not using auto-update - store the custom layer tree
381  mCustomLayerTree->writeXml( composerLegendElem );
382  }
383 
384  if ( mLegendFilterByMap )
385  {
386  composerLegendElem.setAttribute( QStringLiteral( "legendFilterByMap" ), QStringLiteral( "1" ) );
387  }
388  composerLegendElem.setAttribute( QStringLiteral( "legendFilterByAtlas" ), mFilterOutAtlas ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
389 
390  return _writeXml( composerLegendElem, doc );
391 }
392 
393 bool QgsComposerLegend::readXml( const QDomElement &itemElem, const QDomDocument &doc )
394 {
395  if ( itemElem.isNull() )
396  {
397  return false;
398  }
399 
400  //read general properties
401  mTitle = itemElem.attribute( QStringLiteral( "title" ) );
402  mSettings.setTitle( mTitle );
403  if ( !itemElem.attribute( QStringLiteral( "titleAlignment" ) ).isEmpty() )
404  {
405  mSettings.setTitleAlignment( static_cast< Qt::AlignmentFlag >( itemElem.attribute( QStringLiteral( "titleAlignment" ) ).toInt() ) );
406  }
407  int colCount = itemElem.attribute( QStringLiteral( "columnCount" ), QStringLiteral( "1" ) ).toInt();
408  if ( colCount < 1 ) colCount = 1;
409  mColumnCount = colCount;
410  mSettings.setColumnCount( mColumnCount );
411  mSettings.setSplitLayer( itemElem.attribute( QStringLiteral( "splitLayer" ), QStringLiteral( "0" ) ).toInt() == 1 );
412  mSettings.setEqualColumnWidth( itemElem.attribute( QStringLiteral( "equalColumnWidth" ), QStringLiteral( "0" ) ).toInt() == 1 );
413 
414  QDomNodeList stylesNodeList = itemElem.elementsByTagName( QStringLiteral( "styles" ) );
415  if ( !stylesNodeList.isEmpty() )
416  {
417  QDomNode stylesNode = stylesNodeList.at( 0 );
418  for ( int i = 0; i < stylesNode.childNodes().size(); i++ )
419  {
420  QDomElement styleElem = stylesNode.childNodes().at( i ).toElement();
422  style.readXml( styleElem, doc );
423  QString name = styleElem.attribute( QStringLiteral( "name" ) );
425  if ( name == QLatin1String( "title" ) ) s = QgsLegendStyle::Title;
426  else if ( name == QLatin1String( "group" ) ) s = QgsLegendStyle::Group;
427  else if ( name == QLatin1String( "subgroup" ) ) s = QgsLegendStyle::Subgroup;
428  else if ( name == QLatin1String( "symbol" ) ) s = QgsLegendStyle::Symbol;
429  else if ( name == QLatin1String( "symbolLabel" ) ) s = QgsLegendStyle::SymbolLabel;
430  else continue;
431  setStyle( s, style );
432  }
433  }
434 
435  //font color
436  QColor fontClr;
437  fontClr.setNamedColor( itemElem.attribute( QStringLiteral( "fontColor" ), QStringLiteral( "#000000" ) ) );
438  mSettings.setFontColor( fontClr );
439 
440  //spaces
441  mSettings.setBoxSpace( itemElem.attribute( QStringLiteral( "boxSpace" ), QStringLiteral( "2.0" ) ).toDouble() );
442  mSettings.setColumnSpace( itemElem.attribute( QStringLiteral( "columnSpace" ), QStringLiteral( "2.0" ) ).toDouble() );
443 
444  mSettings.setSymbolSize( QSizeF( itemElem.attribute( QStringLiteral( "symbolWidth" ), QStringLiteral( "7.0" ) ).toDouble(), itemElem.attribute( QStringLiteral( "symbolHeight" ), QStringLiteral( "14.0" ) ).toDouble() ) );
445  mSettings.setWmsLegendSize( QSizeF( itemElem.attribute( QStringLiteral( "wmsLegendWidth" ), QStringLiteral( "50" ) ).toDouble(), itemElem.attribute( QStringLiteral( "wmsLegendHeight" ), QStringLiteral( "25" ) ).toDouble() ) );
446  mSettings.setLineSpacing( itemElem.attribute( QStringLiteral( "lineSpacing" ), QStringLiteral( "1.0" ) ).toDouble() );
447 
448  mSettings.setDrawRasterStroke( itemElem.attribute( QStringLiteral( "rasterBorder" ), QStringLiteral( "1" ) ) != QLatin1String( "0" ) );
449  mSettings.setRasterStrokeColor( QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "rasterBorderColor" ), QStringLiteral( "0,0,0" ) ) ) );
450  mSettings.setRasterStrokeWidth( itemElem.attribute( QStringLiteral( "rasterBorderWidth" ), QStringLiteral( "0" ) ).toDouble() );
451 
452  mSettings.setWrapChar( itemElem.attribute( QStringLiteral( "wrapChar" ) ) );
453 
454  mSizeToContents = itemElem.attribute( QStringLiteral( "resizeToContents" ), QStringLiteral( "1" ) ) != QLatin1String( "0" );
455 
456  //composer map
457  mLegendFilterByMap = itemElem.attribute( QStringLiteral( "legendFilterByMap" ), QStringLiteral( "0" ) ).toInt();
458  if ( !itemElem.attribute( QStringLiteral( "map" ) ).isEmpty() )
459  {
460  setComposerMap( mComposition->getComposerMapById( itemElem.attribute( QStringLiteral( "map" ) ).toInt() ) );
461  }
462  mFilterOutAtlas = itemElem.attribute( QStringLiteral( "legendFilterByAtlas" ), QStringLiteral( "0" ) ).toInt();
463 
464  // QGIS >= 2.6
465  QDomElement layerTreeElem = itemElem.firstChildElement( QStringLiteral( "layer-tree" ) );
466  if ( layerTreeElem.isNull() )
467  layerTreeElem = itemElem.firstChildElement( QStringLiteral( "layer-tree-group" ) );
468 
469  if ( !layerTreeElem.isNull() )
470  {
471  std::unique_ptr< QgsLayerTree > tree( QgsLayerTree::readXml( layerTreeElem ) );
472  if ( mComposition )
473  tree->resolveReferences( mComposition->project(), true );
474  setCustomLayerTree( tree.release() );
475  }
476  else
477  setCustomLayerTree( nullptr );
478 
479  //restore general composer item properties
480  QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
481  if ( !composerItemList.isEmpty() )
482  {
483  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
484  _readXml( composerItemElem, doc );
485  }
486 
487  // < 2.0 projects backward compatibility >>>>>
488  //title font
489  QString titleFontString = itemElem.attribute( QStringLiteral( "titleFont" ) );
490  if ( !titleFontString.isEmpty() )
491  {
492  rstyle( QgsLegendStyle::Title ).rfont().fromString( titleFontString );
493  }
494  //group font
495  QString groupFontString = itemElem.attribute( QStringLiteral( "groupFont" ) );
496  if ( !groupFontString.isEmpty() )
497  {
498  rstyle( QgsLegendStyle::Group ).rfont().fromString( groupFontString );
499  }
500 
501  //layer font
502  QString layerFontString = itemElem.attribute( QStringLiteral( "layerFont" ) );
503  if ( !layerFontString.isEmpty() )
504  {
505  rstyle( QgsLegendStyle::Subgroup ).rfont().fromString( layerFontString );
506  }
507  //item font
508  QString itemFontString = itemElem.attribute( QStringLiteral( "itemFont" ) );
509  if ( !itemFontString.isEmpty() )
510  {
511  rstyle( QgsLegendStyle::SymbolLabel ).rfont().fromString( itemFontString );
512  }
513 
514  if ( !itemElem.attribute( QStringLiteral( "groupSpace" ) ).isEmpty() )
515  {
516  rstyle( QgsLegendStyle::Group ).setMargin( QgsLegendStyle::Top, itemElem.attribute( QStringLiteral( "groupSpace" ), QStringLiteral( "3.0" ) ).toDouble() );
517  }
518  if ( !itemElem.attribute( QStringLiteral( "layerSpace" ) ).isEmpty() )
519  {
520  rstyle( QgsLegendStyle::Subgroup ).setMargin( QgsLegendStyle::Top, itemElem.attribute( QStringLiteral( "layerSpace" ), QStringLiteral( "3.0" ) ).toDouble() );
521  }
522  if ( !itemElem.attribute( QStringLiteral( "symbolSpace" ) ).isEmpty() )
523  {
524  rstyle( QgsLegendStyle::Symbol ).setMargin( QgsLegendStyle::Top, itemElem.attribute( QStringLiteral( "symbolSpace" ), QStringLiteral( "2.0" ) ).toDouble() );
525  rstyle( QgsLegendStyle::SymbolLabel ).setMargin( QgsLegendStyle::Top, itemElem.attribute( QStringLiteral( "symbolSpace" ), QStringLiteral( "2.0" ) ).toDouble() );
526  }
527  // <<<<<<< < 2.0 projects backward compatibility
528 
529  emit itemChanged();
530  return true;
531 }
532 
534 {
535  if ( !id().isEmpty() )
536  {
537  return id();
538  }
539 
540  //if no id, default to portion of title text
541  QString text = mSettings.title();
542  if ( text.isEmpty() )
543  {
544  return tr( "<legend>" );
545  }
546  if ( text.length() > 25 )
547  {
548  return QString( tr( "%1..." ) ).arg( text.left( 25 ) );
549  }
550  else
551  {
552  return text;
553  }
554 }
555 
557 {
558  if ( mComposerMap )
559  {
560  disconnect( mComposerMap, &QObject::destroyed, this, &QgsComposerLegend::invalidateCurrentMap );
561  disconnect( mComposerMap, &QgsComposerObject::itemChanged, this, &QgsComposerLegend::updateFilterByMapAndRedraw );
562  disconnect( mComposerMap, &QgsComposerMap::extentChanged, this, &QgsComposerLegend::updateFilterByMapAndRedraw );
563  disconnect( mComposerMap, &QgsComposerMap::layerStyleOverridesChanged, this, &QgsComposerLegend::mapLayerStyleOverridesChanged );
564  }
565 
566  mComposerMap = map;
567 
568  if ( map )
569  {
570  connect( map, &QObject::destroyed, this, &QgsComposerLegend::invalidateCurrentMap );
571  connect( map, &QgsComposerObject::itemChanged, this, &QgsComposerLegend::updateFilterByMapAndRedraw );
572  connect( map, &QgsComposerMap::extentChanged, this, &QgsComposerLegend::updateFilterByMapAndRedraw );
573  connect( map, &QgsComposerMap::layerStyleOverridesChanged, this, &QgsComposerLegend::mapLayerStyleOverridesChanged );
574  }
575 
576  updateItem();
577 }
578 
580 {
581  setComposerMap( nullptr );
582 }
583 
585 {
587  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
588 
589  bool forceUpdate = false;
590  //updates data defined properties and redraws item to match
591  if ( property == QgsComposerObject::LegendTitle || property == QgsComposerObject::AllProperties )
592  {
593  bool ok = false;
594  QString t = mDataDefinedProperties.valueAsString( QgsComposerObject::LegendTitle, *evalContext, mTitle, &ok );
595  if ( ok )
596  {
597  mSettings.setTitle( t );
598  forceUpdate = true;
599  }
600  }
602  {
603  bool ok = false;
604  int cols = mDataDefinedProperties.valueAsInt( QgsComposerObject::LegendColumnCount, *evalContext, mColumnCount, &ok );
605  if ( ok && cols >= 0 )
606  {
607  mSettings.setColumnCount( cols );
608  forceUpdate = true;
609  }
610  }
611  if ( forceUpdate )
612  {
613  adjustBoxSize();
614  update();
615  }
616 
618 }
619 
620 void QgsComposerLegend::updateFilterByMapAndRedraw()
621 {
622  updateFilterByMap( true );
623 }
624 
625 void QgsComposerLegend::mapLayerStyleOverridesChanged()
626 {
627  if ( !mComposerMap )
628  return;
629 
630  // map's style has been changed, so make sure to update the legend here
631  if ( mLegendFilterByMap )
632  {
633  // legend is being filtered by map, so we need to re run the hit test too
634  // as the style overrides may also have affected the visible symbols
635  updateFilterByMap( false );
636  }
637  else
638  {
639  mLegendModel->setLayerStyleOverrides( mComposerMap->layerStyleOverrides() );
640 
641  Q_FOREACH ( QgsLayerTreeLayer *nodeLayer, mLegendModel->rootGroup()->findLayers() )
642  mLegendModel->refreshLayerLegend( nodeLayer );
643  }
644 
645  adjustBoxSize();
646  updateItem();
647 }
648 
649 void QgsComposerLegend::updateFilterByMap( bool redraw )
650 {
651  if ( isRemoved() )
652  return;
653  // ask for update
654  // the actual update will take place before the redraw.
655  // This is to avoid multiple calls to the filter
656  mFilterAskedForUpdate = true;
657 
658  if ( redraw )
660 }
661 
662 void QgsComposerLegend::doUpdateFilterByMap()
663 {
664  if ( mComposerMap )
665  mLegendModel->setLayerStyleOverrides( mComposerMap->layerStyleOverrides() );
666  else
667  mLegendModel->setLayerStyleOverrides( QMap<QString, QString>() );
668 
669 
670  bool filterByExpression = QgsLayerTreeUtils::hasLegendFilterExpression( *( mCustomLayerTree ? mCustomLayerTree.get() : mComposition->project()->layerTreeRoot() ) );
671 
672  if ( mComposerMap && ( mLegendFilterByMap || filterByExpression || mInAtlas ) )
673  {
674  int dpi = mComposition->printResolution();
675 
676  QgsRectangle requestRectangle;
677  mComposerMap->requestedExtent( requestRectangle );
678 
679  QSizeF size( requestRectangle.width(), requestRectangle.height() );
680  size *= mComposerMap->mapUnitsToMM() * dpi / 25.4;
681 
682  QgsMapSettings ms = mComposerMap->mapSettings( requestRectangle, size, dpi );
683 
684  QgsGeometry filterPolygon;
685  if ( mInAtlas )
686  {
687  filterPolygon = composition()->atlasComposition().currentGeometry( mComposerMap->crs() );
688  }
689  mLegendModel->setLegendFilter( &ms, /* useExtent */ mInAtlas || mLegendFilterByMap, filterPolygon, /* useExpressions */ true );
690  }
691  else
692  mLegendModel->setLegendFilterByMap( nullptr );
693 
694  mForceResize = true;
695 }
696 
698 {
699  mFilterOutAtlas = doFilter;
700 }
701 
703 {
704  return mFilterOutAtlas;
705 }
706 
707 void QgsComposerLegend::onAtlasFeature( QgsFeature *feat )
708 {
709  if ( !feat )
710  return;
711  mInAtlas = mFilterOutAtlas;
712  updateFilterByMap();
713 }
714 
715 void QgsComposerLegend::onAtlasEnded()
716 {
717  mInAtlas = false;
718  updateFilterByMap();
719 }
720 
721 // -------------------------------------------------------------------------
723 #include "qgsvectorlayer.h"
724 
725 QgsLegendModel::QgsLegendModel( QgsLayerTree *rootNode, QObject *parent )
726  : QgsLayerTreeModel( rootNode, parent )
727 {
730 }
731 
732 QVariant QgsLegendModel::data( const QModelIndex &index, int role ) const
733 {
734  // handle custom layer node labels
735  if ( QgsLayerTreeNode *node = index2node( index ) )
736  {
737  if ( QgsLayerTree::isLayer( node ) && ( role == Qt::DisplayRole || role == Qt::EditRole ) && !node->customProperty( QStringLiteral( "legend/title-label" ) ).isNull() )
738  {
739  QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
740  QString name = node->customProperty( QStringLiteral( "legend/title-label" ) ).toString();
741  if ( nodeLayer->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toInt() && role == Qt::DisplayRole )
742  {
743  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( nodeLayer->layer() );
744  if ( vlayer && vlayer->featureCount() >= 0 )
745  name += QStringLiteral( " [%1]" ).arg( vlayer->featureCount() );
746  }
747  return name;
748  }
749  }
750 
751  return QgsLayerTreeModel::data( index, role );
752 }
753 
754 Qt::ItemFlags QgsLegendModel::flags( const QModelIndex &index ) const
755 {
756  // make the legend nodes selectable even if they are not by default
757  if ( index2legendNode( index ) )
758  return QgsLayerTreeModel::flags( index ) | Qt::ItemIsSelectable;
759 
760  return QgsLayerTreeModel::flags( index );
761 }
void setTitleAlignment(Qt::AlignmentFlag alignment)
Sets the alignment of the legend title.
void setWrapChar(const QString &t)
void setLegendSize(QSizeF s)
Set the preferred resulting legend size.
QgsMapSettings mapSettings(const QgsRectangle &extent, QSizeF size, int dpi) const
Return map settings that would be used for drawing of the map.
QgsComposerLegend(QgsComposition *composition)
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
Definition: qgslayertree.h:75
A rectangle specified with double values.
Definition: qgsrectangle.h:39
double mapUnitsToMM() const
Returns the conversion factor map units -> mm.
bool readXml(const QDomElement &itemElem, const QDomDocument &doc) override
Sets state from Dom document.
void setEqualColumnWidth(bool s)
Item model implementation based on layer tree model for composer legend.
void renderEnded()
Is emitted when atlas rendering has ended.
const QgsComposerMap * getComposerMapById(const int id) const
Returns the composer map with specified id.
void setBoxSpace(double s)
void setMmPerMapUnit(double mmPerMapUnit)
bool shouldDrawItem() const
Returns whether the item should be drawn in the current context.
void setRasterStrokeWidth(double width)
Sets the stroke width for the stroke drawn around raster symbol items.
QString wrapChar() const
void setSplitLayer(bool s)
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget) override
Reimplementation of QCanvasItem::paint.
QgsComposerModel * itemsModel()
Returns the items model attached to the composition.
#define QgsDebugMsg(str)
Definition: qgslogger.h:37
void itemChanged()
Emitted when the item changes.
void setSymbolSize(QSizeF s)
int printResolution() const
void setColumnSpace(double s)
bool drawRasterStroke() const
Returns whether a stroke will be drawn around raster symbol items.
A item that forms part of a map composition.
QFont font() const
The font for this style.
virtual void refreshDataDefinedProperty(const DataDefinedProperty property=AllProperties, const QgsExpressionContext *context=nullptr)
Refreshes a data defined property for the item by reevaluating the property&#39;s value and redrawing the...
QRectF evalItemRect(const QRectF &newRect, const bool resizeOnly=false, const QgsExpressionContext *context=nullptr)
Evaluates an item&#39;s bounding rect to consider data defined position and size of item and reference po...
int id() const
Get identification number.
QgsLegendStyle style(QgsLegendStyle::Style s) const
Returns style.
Composer legend components style.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:94
bool writeXml(QDomElement &elem, QDomDocument &doc) const override
Stores state in Dom node.
void setLineSpacing(double spacing)
Sets the spacing in-between multiple lines.
virtual void drawFrame(QPainter *p)
Draw black frame around item.
bool drawRasterStroke() const
Returns whether a stroke will be drawn around raster symbol items.
void setTitle(const QString &t)
QColor rasterStrokeColor() const
Returns the stroke color for the stroke drawn around raster symbol items.
bool updatesEnabled() const
Returns whether updates to the item are enabled.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:62
static QgsLayerTree * readXml(QDomElement &element)
Load the layer tree from an XML element.
void updateLegend()
Updates the model and all legend entries.
void updateItemDisplayName(QgsComposerItem *item)
Must be called when an item&#39;s display name is modified.
DataDefinedProperty
Data defined properties for different item types.
QColor rasterStrokeColor() const
Returns the stroke color for the stroke drawn around raster symbol items.
void setWmsLegendHeight(double h)
Allow check boxes for legend nodes (if supported by layer&#39;s legend)
int valueAsInt(int key, const QgsExpressionContext &context, int defaultValue=0, bool *ok=0) const
Calculates the current value of the property with the specified key and interprets it as an integer...
void setResizeToContents(bool enabled)
Sets whether the legend should automatically resize to fit its contents.
void setStyle(QgsLegendStyle::Style s, const QgsLegendStyle &style)
void adjustBoxSize()
Sets item box to the whole content.
The QgsMapSettings class contains configuration for rendering of the map.
static QString encodeColor(const QColor &color)
void setStyleMargin(QgsLegendStyle::Style s, double margin)
Set style margin.
The QgsLayerTreeModel class is model implementation for Qt item views framework.
QSizeF wmsLegendSize() const
QgsLegendStyle style(QgsLegendStyle::Style s) const
Returns style.
bool _writeXml(QDomElement &itemElem, QDomDocument &doc) const
Writes parameter that are not subclass specific in document. Usually called from writeXml methods of ...
void setFont(const QFont &font)
The font for this style.
Namespace with helper functions for layer tree operations.
Definition: qgslayertree.h:32
bool equalColumnWidth() const
void setSymbolHeight(double h)
void setLegendFilterOutAtlas(bool doFilter)
When set to true, during an atlas rendering, it will filter out legend elements where features are ou...
virtual void drawSelectionBoxes(QPainter *p)
Draws additional graphics on selected items.
double scale() const
Returns the calculated map scale.
long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
virtual void refreshDataDefinedProperty(const QgsComposerObject::DataDefinedProperty property=QgsComposerObject::AllProperties, const QgsExpressionContext *context=nullptr) override
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:131
static QgsLayerTreeModelLegendNode * index2legendNode(const QModelIndex &index)
Return legend node for given index.
bool useAdvancedEffects() const
Returns true if a composition should use advanced effects such as blend modes.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setMapScale(double scale)
Sets the legend map scale.
QVariant data(const QModelIndex &index, int role) const override
double rasterStrokeWidth() const
Returns the stroke width (in millimeters) for the stroke drawn around raster symbol items...
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer. Properties are stored in a map and saved in project file...
QColor fontColor() const
void setComposerMap(const QgsComposerMap *map)
void synchronizeWithModel()
Data changed.
Symbol without label.
void setRasterStrokeColor(const QColor &color)
Sets the stroke color for the stroke drawn around raster symbol items.
void setTitle(const QString &t)
QFont styleFont(QgsLegendStyle::Style s) const
QgsPropertyCollection mDataDefinedProperties
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
Definition: qgslayertree.h:53
This class is a base class for nodes in a layer tree.
void setRasterStrokeWidth(double width)
Sets the stroke width for the stroke drawn around raster symbol items.
void setMargin(Side side, double margin)
QMap< QString, QString > layerStyleOverrides() const
Getter for stored overrides of styles for layers.
double symbolWidth() const
Qt::AlignmentFlag titleAlignment() const
Returns the alignment of the legend title.
double wmsLegendHeight() const
void setDpi(int dpi)
double boxSpace() const
QString wrapChar() const
Side
Margin side.
void setFontColor(const QColor &c)
static bool hasLegendFilterExpression(const QgsLayerTreeGroup &group)
Test if one of the layers in a group has an expression filter.
bool _readXml(const QDomElement &itemElem, const QDomDocument &doc)
Reads parameter that are not subclass specific in document. Usually called from readXml methods of su...
void setColumnSpace(double s)
void setFlag(Flag f, bool on=true)
Enable or disable a model flag.
Graphics scene for map printing.
virtual void updateItem() override
Update() overloading.
QSizeF minimumSize()
Run the layout algorithm and determine the size required for legend.
void setLegendFilterByMapEnabled(bool enabled)
Set whether legend items should be filtered to show just the ones visible in the associated map...
Object representing map window.
void setSymbolWidth(double w)
QSizeF paintAndDetermineSize(QPainter *painter)
Paints the legend and calculates its size. If painter is 0, only size is calculated.
void invalidateCurrentMap()
Sets mCompositionMap to 0 if the map is deleted.
virtual QString displayName() const override
Get item display name.
void featureChanged(QgsFeature *feature)
Is emitted when the current atlas feature changes.
bool autoUpdateModel() const
virtual bool isRemoved() const
Returns whether this item has been removed from the composition.
void setWmsLegendWidth(double w)
QSizeF symbolSize() const
void setLineSpacing(double s)
virtual QgsExpressionContext createExpressionContext() const override
Creates an expression context relating to the item&#39;s current state.
QgsLayerTree * layerTreeRoot() const
Return pointer to the root (invisible) node of the project&#39;s layer tree.
bool resizeToContents() const
Returns whether the legend should automatically resize to fit its contents.
QgsMapLayer * layer() const
void readXml(const QDomElement &elem, const QDomDocument &doc)
double symbolHeight() const
QgsComposition * mComposition
void layerStyleOverridesChanged()
Emitted when layer style overrides are changed...
void setWmsLegendSize(QSizeF s)
QgsLegendStyle & rstyle(QgsLegendStyle::Style s)
Returns reference to modifiable style.
QgsGeometry currentGeometry(const QgsCoordinateReferenceSystem &projectedTo=QgsCoordinateReferenceSystem()) const
Returns the current atlas geometry in the given projection system (default to the coverage layer&#39;s CR...
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used for rendering the map.
void setUseAdvancedEffects(bool use)
void setAutoUpdateModel(bool autoUpdate)
QgsLayerTreeNode * index2node(const QModelIndex &index) const
Return layer tree node for given index.
void setDrawRasterStroke(bool enabled)
Sets whether a stroke will be drawn around raster symbol items.
const QgsComposition * composition() const
Returns the composition the item is attached to.
void setWrapChar(const QString &t)
void setStyle(QgsLegendStyle::Style s, const QgsLegendStyle &style)
QgsProject * project() const
The project associated with the composition.
QgsLegendModel(QgsLayerTree *rootNode, QObject *parent=0)
Construct the model based on the given layer tree.
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=0) const
Calculates the current value of the property with the specified key and interprets it as a string...
bool equalColumnWidth() const
virtual void drawBackground(QPainter *p)
Draw background.
void setColumnCount(int c)
double columnSpace() const
void requestedExtent(QgsRectangle &extent) const
Calculates the extent to request and the yShift of the top-left point in case of rotation.
void setDrawRasterStroke(bool enabled)
Sets whether a stroke will be drawn around raster symbol items.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
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 setEqualColumnWidth(bool s)
QString title() const
void setTitleAlignment(Qt::AlignmentFlag alignment)
Sets the alignment of the legend title.
QFont & rfont()
Modifiable reference to font.
virtual void updateItem()
Updates (redraws) the item, with the possibility to do custom update for subclasses.
void writeXml(const QString &name, QDomElement &elem, QDomDocument &doc) const
void setStyleFont(QgsLegendStyle::Style s, const QFont &f)
Set style font.
QgsAtlasComposition & atlasComposition()
double wmsLegendWidth() const
Flags flags() const
Return OR-ed combination of model flags.
void extentChanged()
QgsComposerItem(QgsComposition *composition, bool manageZValue=true)
Constructor.
void customPropertyChanged(QgsLayerTreeNode *node, const QString &key)
Emitted when a custom property of a node within the tree has been changed or removed.
void setFontColor(const QColor &c)
double lineSpacing() const
bool legendFilterOutAtlas() const
Whether to filter out legend elements outside of the current atlas feature.
QColor fontColor() const
Represents a vector layer which manages a vector based data sets.
QgsLegendStyle & rstyle(QgsLegendStyle::Style s)
Returns reference to modifiable style.
bool splitLayer() const
double rasterStrokeWidth() const
Returns the stroke width (in millimeters) for the stroke drawn around raster symbol items...
void setRasterStrokeColor(const QColor &color)
Sets the stroke color for the stroke drawn around raster symbol items.
virtual QgsLayerTree * clone() const override
Return a clone of the group.
QString id() const
Get item&#39;s id (which is not necessarly unique)
const QgsRectangle * currentMapExtent() const
Returns a pointer to the current map extent, which is either the original user specified extent or th...
void setBoxSpace(double s)
Allow reordering with drag&#39;n&#39;drop.
double lineSpacing() const
Returns the spacing in-between lines in mm.
double columnSpace() const
void setSplitLayer(bool s)
static QColor decodeColor(const QString &str)
All properties for item.
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:138
Layer tree node points to a map layer.
The QgsLegendRenderer class handles automatic layout and rendering of legend.
QString title() const
Qt::AlignmentFlag titleAlignment() const
Returns the alignment of the legend title.
double boxSpace() const