QGIS API Documentation  3.0.2-Girona (307d082)
qgslayoutitemlegend.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayoutitemlegend.cpp
3  -----------------------
4  begin : October 2017
5  copyright : (C) 2017 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
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 "qgslayoutitemlegend.h"
20 #include "qgslayoutitemregistry.h"
21 #include "qgslayoutitemmap.h"
22 #include "qgslayout.h"
23 #include "qgslayoutmodel.h"
24 #include "qgslayertree.h"
25 #include "qgslayertreemodel.h"
26 #include "qgslegendrenderer.h"
27 #include "qgslegendstyle.h"
28 #include "qgslogger.h"
29 #include "qgsmapsettings.h"
30 #include "qgsproject.h"
31 #include "qgssymbollayerutils.h"
32 #include "qgslayertreeutils.h"
33 #include <QDomDocument>
34 #include <QDomElement>
35 #include <QPainter>
36 
38  : QgsLayoutItem( layout )
39  , mLegendModel( new QgsLegendModel( layout->project()->layerTreeRoot() ) )
40 {
41 #if 0 //no longer required?
42  connect( &layout->atlasComposition(), &QgsAtlasComposition::renderEnded, this, &QgsLayoutItemLegend::onAtlasEnded );
43 #endif
44 
45  // Connect to the main layertreeroot.
46  // It serves in "auto update mode" as a medium between the main app legend and this one
47  connect( mLayout->project()->layerTreeRoot(), &QgsLayerTreeNode::customPropertyChanged, this, &QgsLayoutItemLegend::nodeCustomPropertyChanged );
48 }
49 
51 {
52  return new QgsLayoutItemLegend( layout );
53 }
54 
56 {
58 }
59 
61 {
62  return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemLegend.svg" ) );
63 }
64 
65 void QgsLayoutItemLegend::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget )
66 {
67  if ( !painter )
68  return;
69 
70  if ( mFilterAskedForUpdate )
71  {
72  mFilterAskedForUpdate = false;
73  doUpdateFilterByMap();
74  }
75 
76  int dpi = painter->device()->logicalDpiX();
77  double dotsPerMM = dpi / 25.4;
78 
79  if ( mLayout )
80  {
82  mSettings.setDpi( dpi );
83  }
84  if ( mMap && mLayout )
85  {
86  mSettings.setMmPerMapUnit( mLayout->convertFromLayoutUnits( mMap->mapUnitsToLayoutUnits(), QgsUnitTypes::LayoutMillimeters ).length() );
87 
88  // use a temporary QgsMapSettings to find out real map scale
89  QSizeF mapSizePixels = QSizeF( mMap->rect().width() * dotsPerMM, mMap->rect().height() * dotsPerMM );
90  QgsRectangle mapExtent = mMap->extent();
91 
92  QgsMapSettings ms = mMap->mapSettings( mapExtent, mapSizePixels, dpi, false );
93  mSettings.setMapScale( ms.scale() );
94  }
95  mInitialMapScaleCalculated = true;
96 
97  QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
98  legendRenderer.setLegendSize( mForceResize && mSizeToContents ? QSize() : rect().size() );
99 
100  //adjust box if width or height is too small
101  if ( mSizeToContents )
102  {
103  QSizeF size = legendRenderer.minimumSize();
104  if ( mForceResize )
105  {
106  mForceResize = false;
107  //set new rect, respecting position mode and data defined size/position
108  QRectF targetRect = QRectF( pos().x(), pos().y(), size.width(), size.height() );
109  attemptSetSceneRect( targetRect );
110  }
111  else if ( size.height() > rect().height() || size.width() > rect().width() )
112  {
113  //need to resize box
114  QRectF targetRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
115  if ( size.height() > targetRect.height() )
116  targetRect.setHeight( size.height() );
117  if ( size.width() > rect().width() )
118  targetRect.setWidth( size.width() );
119 
120  //set new rect, respecting position mode and data defined size/position
121  attemptSetSceneRect( targetRect );
122  }
123  }
124  QgsLayoutItem::paint( painter, itemStyle, pWidget );
125 }
126 
128 {
129  if ( !mMapUuid.isEmpty() )
130  {
131  setLinkedMap( qobject_cast< QgsLayoutItemMap * >( mLayout->itemByUuid( mMapUuid, true ) ) );
132  }
133 }
134 
136 {
138  onAtlasFeature();
139 }
140 
142 {
143  QPainter *painter = context.renderContext().painter();
144  painter->save();
145 
146  // painter is scaled to dots, so scale back to layout units
147  painter->scale( context.renderContext().scaleFactor(), context.renderContext().scaleFactor() );
148 
149  painter->setPen( QPen( QColor( 0, 0, 0 ) ) );
150 
151  if ( !mSizeToContents )
152  {
153  // set a clip region to crop out parts of legend which don't fit
154  QRectF thisPaintRect = QRectF( 0, 0, rect().width(), rect().height() );
155  painter->setClipRect( thisPaintRect );
156  }
157 
158  QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
159  legendRenderer.setLegendSize( mSizeToContents ? QSize() : rect().size() );
160 
161  legendRenderer.drawLegend( painter );
162 
163  painter->restore();
164 }
165 
167 {
168  if ( !mSizeToContents )
169  return;
170 
171  if ( !mInitialMapScaleCalculated )
172  {
173  // this is messy - but until we have painted the item we have no knowledge of the current DPI
174  // and so cannot correctly calculate the map scale. This results in incorrect size calculations
175  // for marker symbols with size in map units, causing the legends to initially expand to huge
176  // sizes if we attempt to calculate the box size first.
177  return;
178  }
179 
180  QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
181  QSizeF size = legendRenderer.minimumSize();
182  QgsDebugMsg( QString( "width = %1 height = %2" ).arg( size.width() ).arg( size.height() ) );
183  if ( size.isValid() )
184  {
185  QgsLayoutSize newSize = mLayout->convertFromLayoutUnits( size, sizeWithUnits().units() );
186  //set new rect, respecting position mode and data defined size/position
187  attemptResize( newSize );
188  }
189 }
190 
192 {
193  mSizeToContents = enabled;
194 }
195 
197 {
198  return mSizeToContents;
199 }
200 
201 void QgsLayoutItemLegend::setCustomLayerTree( QgsLayerTree *rootGroup )
202 {
203  mLegendModel->setRootGroup( rootGroup ? rootGroup : ( mLayout ? mLayout->project()->layerTreeRoot() : nullptr ) );
204 
205  mCustomLayerTree.reset( rootGroup );
206 }
207 
209 {
210  if ( autoUpdate == autoUpdateModel() )
211  return;
212 
213  setCustomLayerTree( autoUpdate ? nullptr : mLayout->project()->layerTreeRoot()->clone() );
214  adjustBoxSize();
215  updateFilterByMap( false );
216 }
217 
218 void QgsLayoutItemLegend::nodeCustomPropertyChanged( QgsLayerTreeNode *, const QString & )
219 {
220  if ( autoUpdateModel() )
221  {
222  // in "auto update" mode, some parameters on the main app legend may have been changed (expression filtering)
223  // we must then call updateItem to reflect the changes
224  updateFilterByMap( false );
225  }
226 }
227 
229 {
230  return !mCustomLayerTree;
231 }
232 
234 {
235  mLegendFilterByMap = enabled;
236  updateFilterByMap( false );
237 }
238 
239 void QgsLayoutItemLegend::setTitle( const QString &t )
240 {
241  mTitle = t;
242  mSettings.setTitle( t );
243 
244  if ( mLayout && id().isEmpty() )
245  {
246  //notify the model that the display name has changed
247  mLayout->itemsModel()->updateItemDisplayName( this );
248  }
249 }
251 {
252  return mTitle;
253 }
254 
255 Qt::AlignmentFlag QgsLayoutItemLegend::titleAlignment() const
256 {
257  return mSettings.titleAlignment();
258 }
259 
260 void QgsLayoutItemLegend::setTitleAlignment( Qt::AlignmentFlag alignment )
261 {
262  mSettings.setTitleAlignment( alignment );
263 }
264 
266 {
267  return mSettings.rstyle( s );
268 }
269 
271 {
272  return mSettings.style( s );
273 }
274 
276 {
277  mSettings.setStyle( s, style );
278 }
279 
281 {
282  return mSettings.style( s ).font();
283 }
284 
286 {
287  rstyle( s ).setFont( f );
288 }
289 
291 {
292  rstyle( s ).setMargin( margin );
293 }
294 
296 {
297  rstyle( s ).setMargin( side, margin );
298 }
299 
301 {
302  return mSettings.lineSpacing();
303 }
304 
306 {
307  mSettings.setLineSpacing( spacing );
308 }
309 
311 {
312  return mSettings.boxSpace();
313 }
314 
316 {
317  mSettings.setBoxSpace( s );
318 }
319 
321 {
322  return mSettings.columnSpace();
323 }
324 
326 {
327  mSettings.setColumnSpace( s );
328 }
329 
331 {
332  return mSettings.fontColor();
333 }
334 
335 void QgsLayoutItemLegend::setFontColor( const QColor &c )
336 {
337  mSettings.setFontColor( c );
338 }
339 
341 {
342  return mSettings.symbolSize().width();
343 }
344 
346 {
347  mSettings.setSymbolSize( QSizeF( w, mSettings.symbolSize().height() ) );
348 }
349 
351 {
352  return mSettings.symbolSize().height();
353 }
354 
356 {
357  mSettings.setSymbolSize( QSizeF( mSettings.symbolSize().width(), h ) );
358 }
359 
361 {
362  return mSettings.wmsLegendSize().width();
363 }
364 
366 {
367  mSettings.setWmsLegendSize( QSizeF( w, mSettings.wmsLegendSize().height() ) );
368 }
369 
371 {
372  return mSettings.wmsLegendSize().height();
373 }
375 {
376  mSettings.setWmsLegendSize( QSizeF( mSettings.wmsLegendSize().width(), h ) );
377 }
378 
379 void QgsLayoutItemLegend::setWrapString( const QString &t )
380 {
381  mSettings.setWrapChar( t );
382 }
383 
385 {
386  return mSettings.wrapChar();
387 }
388 
390 {
391  return mColumnCount;
392 }
393 
395 {
396  mColumnCount = c;
397  mSettings.setColumnCount( c );
398 }
399 
401 {
402  return mSettings.splitLayer();
403 }
404 
406 {
407  mSettings.setSplitLayer( s );
408 }
409 
411 {
412  return mSettings.equalColumnWidth();
413 }
414 
416 {
417  mSettings.setEqualColumnWidth( s );
418 }
419 
421 {
422  return mSettings.drawRasterStroke();
423 }
424 
426 {
427  mSettings.setDrawRasterStroke( enabled );
428 }
429 
431 {
432  return mSettings.rasterStrokeColor();
433 }
434 
435 void QgsLayoutItemLegend::setRasterStrokeColor( const QColor &color )
436 {
437  mSettings.setRasterStrokeColor( color );
438 }
439 
441 {
442  return mSettings.rasterStrokeWidth();
443 }
444 
446 {
447  mSettings.setRasterStrokeWidth( width );
448 }
449 
450 void QgsLayoutItemLegend::synchronizeWithModel()
451 {
452  adjustBoxSize();
453  updateFilterByMap( false );
454 }
455 
457 {
458  adjustBoxSize();
459  updateFilterByMap( false );
460 }
461 
462 bool QgsLayoutItemLegend::writePropertiesToElement( QDomElement &legendElem, QDomDocument &doc, const QgsReadWriteContext &context ) const
463 {
464 
465  //write general properties
466  legendElem.setAttribute( QStringLiteral( "title" ), mTitle );
467  legendElem.setAttribute( QStringLiteral( "titleAlignment" ), QString::number( static_cast< int >( mSettings.titleAlignment() ) ) );
468  legendElem.setAttribute( QStringLiteral( "columnCount" ), QString::number( mColumnCount ) );
469  legendElem.setAttribute( QStringLiteral( "splitLayer" ), QString::number( mSettings.splitLayer() ) );
470  legendElem.setAttribute( QStringLiteral( "equalColumnWidth" ), QString::number( mSettings.equalColumnWidth() ) );
471 
472  legendElem.setAttribute( QStringLiteral( "boxSpace" ), QString::number( mSettings.boxSpace() ) );
473  legendElem.setAttribute( QStringLiteral( "columnSpace" ), QString::number( mSettings.columnSpace() ) );
474 
475  legendElem.setAttribute( QStringLiteral( "symbolWidth" ), QString::number( mSettings.symbolSize().width() ) );
476  legendElem.setAttribute( QStringLiteral( "symbolHeight" ), QString::number( mSettings.symbolSize().height() ) );
477  legendElem.setAttribute( QStringLiteral( "lineSpacing" ), QString::number( mSettings.lineSpacing() ) );
478 
479  legendElem.setAttribute( QStringLiteral( "rasterBorder" ), mSettings.drawRasterStroke() );
480  legendElem.setAttribute( QStringLiteral( "rasterBorderColor" ), QgsSymbolLayerUtils::encodeColor( mSettings.rasterStrokeColor() ) );
481  legendElem.setAttribute( QStringLiteral( "rasterBorderWidth" ), QString::number( mSettings.rasterStrokeWidth() ) );
482 
483  legendElem.setAttribute( QStringLiteral( "wmsLegendWidth" ), QString::number( mSettings.wmsLegendSize().width() ) );
484  legendElem.setAttribute( QStringLiteral( "wmsLegendHeight" ), QString::number( mSettings.wmsLegendSize().height() ) );
485  legendElem.setAttribute( QStringLiteral( "wrapChar" ), mSettings.wrapChar() );
486  legendElem.setAttribute( QStringLiteral( "fontColor" ), mSettings.fontColor().name() );
487 
488  legendElem.setAttribute( QStringLiteral( "resizeToContents" ), mSizeToContents );
489 
490  if ( mMap )
491  {
492  legendElem.setAttribute( QStringLiteral( "map_uuid" ), mMap->uuid() );
493  }
494 
495  QDomElement legendStyles = doc.createElement( QStringLiteral( "styles" ) );
496  legendElem.appendChild( legendStyles );
497 
498  style( QgsLegendStyle::Title ).writeXml( QStringLiteral( "title" ), legendStyles, doc );
499  style( QgsLegendStyle::Group ).writeXml( QStringLiteral( "group" ), legendStyles, doc );
500  style( QgsLegendStyle::Subgroup ).writeXml( QStringLiteral( "subgroup" ), legendStyles, doc );
501  style( QgsLegendStyle::Symbol ).writeXml( QStringLiteral( "symbol" ), legendStyles, doc );
502  style( QgsLegendStyle::SymbolLabel ).writeXml( QStringLiteral( "symbolLabel" ), legendStyles, doc );
503 
504  if ( mCustomLayerTree )
505  {
506  // if not using auto-update - store the custom layer tree
507  mCustomLayerTree->writeXml( legendElem, context );
508  }
509 
510  if ( mLegendFilterByMap )
511  {
512  legendElem.setAttribute( QStringLiteral( "legendFilterByMap" ), QStringLiteral( "1" ) );
513  }
514  legendElem.setAttribute( QStringLiteral( "legendFilterByAtlas" ), mFilterOutAtlas ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
515 
516  return true;
517 }
518 
519 bool QgsLayoutItemLegend::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context )
520 {
521  //read general properties
522  mTitle = itemElem.attribute( QStringLiteral( "title" ) );
523  mSettings.setTitle( mTitle );
524  if ( !itemElem.attribute( QStringLiteral( "titleAlignment" ) ).isEmpty() )
525  {
526  mSettings.setTitleAlignment( static_cast< Qt::AlignmentFlag >( itemElem.attribute( QStringLiteral( "titleAlignment" ) ).toInt() ) );
527  }
528  int colCount = itemElem.attribute( QStringLiteral( "columnCount" ), QStringLiteral( "1" ) ).toInt();
529  if ( colCount < 1 ) colCount = 1;
530  mColumnCount = colCount;
531  mSettings.setColumnCount( mColumnCount );
532  mSettings.setSplitLayer( itemElem.attribute( QStringLiteral( "splitLayer" ), QStringLiteral( "0" ) ).toInt() == 1 );
533  mSettings.setEqualColumnWidth( itemElem.attribute( QStringLiteral( "equalColumnWidth" ), QStringLiteral( "0" ) ).toInt() == 1 );
534 
535  QDomNodeList stylesNodeList = itemElem.elementsByTagName( QStringLiteral( "styles" ) );
536  if ( !stylesNodeList.isEmpty() )
537  {
538  QDomNode stylesNode = stylesNodeList.at( 0 );
539  for ( int i = 0; i < stylesNode.childNodes().size(); i++ )
540  {
541  QDomElement styleElem = stylesNode.childNodes().at( i ).toElement();
543  style.readXml( styleElem, doc );
544  QString name = styleElem.attribute( QStringLiteral( "name" ) );
546  if ( name == QLatin1String( "title" ) ) s = QgsLegendStyle::Title;
547  else if ( name == QLatin1String( "group" ) ) s = QgsLegendStyle::Group;
548  else if ( name == QLatin1String( "subgroup" ) ) s = QgsLegendStyle::Subgroup;
549  else if ( name == QLatin1String( "symbol" ) ) s = QgsLegendStyle::Symbol;
550  else if ( name == QLatin1String( "symbolLabel" ) ) s = QgsLegendStyle::SymbolLabel;
551  else continue;
552  setStyle( s, style );
553  }
554  }
555 
556  //font color
557  QColor fontClr;
558  fontClr.setNamedColor( itemElem.attribute( QStringLiteral( "fontColor" ), QStringLiteral( "#000000" ) ) );
559  mSettings.setFontColor( fontClr );
560 
561  //spaces
562  mSettings.setBoxSpace( itemElem.attribute( QStringLiteral( "boxSpace" ), QStringLiteral( "2.0" ) ).toDouble() );
563  mSettings.setColumnSpace( itemElem.attribute( QStringLiteral( "columnSpace" ), QStringLiteral( "2.0" ) ).toDouble() );
564 
565  mSettings.setSymbolSize( QSizeF( itemElem.attribute( QStringLiteral( "symbolWidth" ), QStringLiteral( "7.0" ) ).toDouble(), itemElem.attribute( QStringLiteral( "symbolHeight" ), QStringLiteral( "14.0" ) ).toDouble() ) );
566  mSettings.setWmsLegendSize( QSizeF( itemElem.attribute( QStringLiteral( "wmsLegendWidth" ), QStringLiteral( "50" ) ).toDouble(), itemElem.attribute( QStringLiteral( "wmsLegendHeight" ), QStringLiteral( "25" ) ).toDouble() ) );
567  mSettings.setLineSpacing( itemElem.attribute( QStringLiteral( "lineSpacing" ), QStringLiteral( "1.0" ) ).toDouble() );
568 
569  mSettings.setDrawRasterStroke( itemElem.attribute( QStringLiteral( "rasterBorder" ), QStringLiteral( "1" ) ) != QLatin1String( "0" ) );
570  mSettings.setRasterStrokeColor( QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "rasterBorderColor" ), QStringLiteral( "0,0,0" ) ) ) );
571  mSettings.setRasterStrokeWidth( itemElem.attribute( QStringLiteral( "rasterBorderWidth" ), QStringLiteral( "0" ) ).toDouble() );
572 
573  mSettings.setWrapChar( itemElem.attribute( QStringLiteral( "wrapChar" ) ) );
574 
575  mSizeToContents = itemElem.attribute( QStringLiteral( "resizeToContents" ), QStringLiteral( "1" ) ) != QLatin1String( "0" );
576 
577  // map
578  mLegendFilterByMap = itemElem.attribute( QStringLiteral( "legendFilterByMap" ), QStringLiteral( "0" ) ).toInt();
579 
580  mMapUuid.clear();
581  if ( !itemElem.attribute( QStringLiteral( "map_uuid" ) ).isEmpty() )
582  {
583  mMapUuid = itemElem.attribute( QStringLiteral( "map_uuid" ) );
584  }
585  // disconnect current map
586  setupMapConnections( mMap, false );
587  mMap = nullptr;
588 
589  mFilterOutAtlas = itemElem.attribute( QStringLiteral( "legendFilterByAtlas" ), QStringLiteral( "0" ) ).toInt();
590 
591  // QGIS >= 2.6
592  QDomElement layerTreeElem = itemElem.firstChildElement( QStringLiteral( "layer-tree" ) );
593  if ( layerTreeElem.isNull() )
594  layerTreeElem = itemElem.firstChildElement( QStringLiteral( "layer-tree-group" ) );
595 
596  if ( !layerTreeElem.isNull() )
597  {
598  std::unique_ptr< QgsLayerTree > tree( QgsLayerTree::readXml( layerTreeElem, context ) );
599  if ( mLayout )
600  tree->resolveReferences( mLayout->project(), true );
601  setCustomLayerTree( tree.release() );
602  }
603  else
604  setCustomLayerTree( nullptr );
605 
606  return true;
607 }
608 
610 {
611  if ( !id().isEmpty() )
612  {
613  return id();
614  }
615 
616  //if no id, default to portion of title text
617  QString text = mSettings.title();
618  if ( text.isEmpty() )
619  {
620  return tr( "<Legend>" );
621  }
622  if ( text.length() > 25 )
623  {
624  return tr( "%1…" ).arg( text.left( 25 ) );
625  }
626  else
627  {
628  return text;
629  }
630 }
631 
632 
633 void QgsLayoutItemLegend::setupMapConnections( QgsLayoutItemMap *map, bool connectSlots )
634 {
635  if ( !map )
636  return;
637 
638  if ( !connectSlots )
639  {
640  disconnect( map, &QObject::destroyed, this, &QgsLayoutItemLegend::invalidateCurrentMap );
641  disconnect( map, &QgsLayoutObject::changed, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
642  disconnect( map, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
643  disconnect( map, &QgsLayoutItemMap::layerStyleOverridesChanged, this, &QgsLayoutItemLegend::mapLayerStyleOverridesChanged );
644  }
645  else
646  {
647  connect( map, &QObject::destroyed, this, &QgsLayoutItemLegend::invalidateCurrentMap );
648  connect( map, &QgsLayoutObject::changed, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
649  connect( map, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
650  connect( map, &QgsLayoutItemMap::layerStyleOverridesChanged, this, &QgsLayoutItemLegend::mapLayerStyleOverridesChanged );
651  }
652 }
653 
655 {
656  if ( mMap )
657  {
658  setupMapConnections( mMap, false );
659  }
660 
661  mMap = map;
662 
663  if ( mMap )
664  {
665  setupMapConnections( mMap, true );
666  }
667 
669 }
670 
671 void QgsLayoutItemLegend::invalidateCurrentMap()
672 {
673  setLinkedMap( nullptr );
674 }
675 
677 {
679 
680  bool forceUpdate = false;
681  //updates data defined properties and redraws item to match
682  if ( property == QgsLayoutObject::LegendTitle || property == QgsLayoutObject::AllProperties )
683  {
684  bool ok = false;
685  QString t = mDataDefinedProperties.valueAsString( QgsLayoutObject::LegendTitle, context, mTitle, &ok );
686  if ( ok )
687  {
688  mSettings.setTitle( t );
689  forceUpdate = true;
690  }
691  }
693  {
694  bool ok = false;
695  int cols = mDataDefinedProperties.valueAsInt( QgsLayoutObject::LegendColumnCount, context, mColumnCount, &ok );
696  if ( ok && cols >= 0 )
697  {
698  mSettings.setColumnCount( cols );
699  forceUpdate = true;
700  }
701  }
702  if ( forceUpdate )
703  {
704  adjustBoxSize();
705  update();
706  }
707 
709 }
710 
711 
712 void QgsLayoutItemLegend::updateFilterByMapAndRedraw()
713 {
714  updateFilterByMap( true );
715 }
716 
717 void QgsLayoutItemLegend::mapLayerStyleOverridesChanged()
718 {
719  if ( !mMap )
720  return;
721 
722  // map's style has been changed, so make sure to update the legend here
723  if ( mLegendFilterByMap )
724  {
725  // legend is being filtered by map, so we need to re run the hit test too
726  // as the style overrides may also have affected the visible symbols
727  updateFilterByMap( false );
728  }
729  else
730  {
731  mLegendModel->setLayerStyleOverrides( mMap->layerStyleOverrides() );
732 
733  Q_FOREACH ( QgsLayerTreeLayer *nodeLayer, mLegendModel->rootGroup()->findLayers() )
734  mLegendModel->refreshLayerLegend( nodeLayer );
735  }
736 
737  adjustBoxSize();
738  updateFilterByMap( false );
739 }
740 
742 {
743  // ask for update
744  // the actual update will take place before the redraw.
745  // This is to avoid multiple calls to the filter
746  mFilterAskedForUpdate = true;
747 
748  if ( redraw )
749  update();
750 }
751 
752 void QgsLayoutItemLegend::doUpdateFilterByMap()
753 {
754  if ( mMap )
755  mLegendModel->setLayerStyleOverrides( mMap->layerStyleOverrides() );
756  else
757  mLegendModel->setLayerStyleOverrides( QMap<QString, QString>() );
758 
759 
760  bool filterByExpression = QgsLayerTreeUtils::hasLegendFilterExpression( *( mCustomLayerTree ? mCustomLayerTree.get() : mLayout->project()->layerTreeRoot() ) );
761 
762  if ( mMap && ( mLegendFilterByMap || filterByExpression || mInAtlas ) )
763  {
764  int dpi = mLayout->renderContext().dpi();
765 
766  QgsRectangle requestRectangle = mMap->requestedExtent();
767 
768  QSizeF size( requestRectangle.width(), requestRectangle.height() );
769  size *= mLayout->convertFromLayoutUnits( mMap->mapUnitsToLayoutUnits(), QgsUnitTypes::LayoutMillimeters ).length() * dpi / 25.4;
770 
771  QgsMapSettings ms = mMap->mapSettings( requestRectangle, size, dpi, true );
772 
773  QgsGeometry filterPolygon;
774  if ( mInAtlas )
775  {
776  filterPolygon = mLayout->reportContext().currentGeometry( mMap->crs() );
777  }
778  mLegendModel->setLegendFilter( &ms, /* useExtent */ mInAtlas || mLegendFilterByMap, filterPolygon, /* useExpressions */ true );
779  }
780  else
781  mLegendModel->setLegendFilterByMap( nullptr );
782 
783  mForceResize = true;
784 }
785 
787 {
788  mFilterOutAtlas = doFilter;
789 }
790 
792 {
793  return mFilterOutAtlas;
794 }
795 
796 void QgsLayoutItemLegend::onAtlasFeature()
797 {
798  if ( !mLayout->reportContext().feature().isValid() )
799  return;
800  mInAtlas = mFilterOutAtlas;
802 }
803 
804 void QgsLayoutItemLegend::onAtlasEnded()
805 {
806  mInAtlas = false;
808 }
809 
810 // -------------------------------------------------------------------------
812 #include "qgsvectorlayer.h"
813 
814 QgsLegendModel::QgsLegendModel( QgsLayerTree *rootNode, QObject *parent )
815  : QgsLayerTreeModel( rootNode, parent )
816 {
819 }
820 
821 QVariant QgsLegendModel::data( const QModelIndex &index, int role ) const
822 {
823  // handle custom layer node labels
824  if ( QgsLayerTreeNode *node = index2node( index ) )
825  {
826  if ( QgsLayerTree::isLayer( node ) && ( role == Qt::DisplayRole || role == Qt::EditRole ) && !node->customProperty( QStringLiteral( "legend/title-label" ) ).isNull() )
827  {
828  QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
829  QString name = node->customProperty( QStringLiteral( "legend/title-label" ) ).toString();
830  if ( nodeLayer->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toInt() && role == Qt::DisplayRole )
831  {
832  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( nodeLayer->layer() );
833  if ( vlayer && vlayer->featureCount() >= 0 )
834  name += QStringLiteral( " [%1]" ).arg( vlayer->featureCount() );
835  }
836  return name;
837  }
838  }
839 
840  return QgsLayerTreeModel::data( index, role );
841 }
842 
843 Qt::ItemFlags QgsLegendModel::flags( const QModelIndex &index ) const
844 {
845  // make the legend nodes selectable even if they are not by default
846  if ( index2legendNode( index ) )
847  return QgsLayerTreeModel::flags( index ) | Qt::ItemIsSelectable;
848 
849  return QgsLayerTreeModel::flags( index );
850 }
void setTitleAlignment(Qt::AlignmentFlag alignment)
Sets the alignment of the legend title.
void setColumnSpace(double spacing)
Sets the legend column spacing.
bool splitLayer() const
Returns whether the legend items from a single layer can be split over multiple columns.
void setWrapChar(const QString &t)
The class is used as a container of context for various read/write operations on other objects...
void setEqualColumnWidth(bool equalize)
Sets whether column widths should be equalized.
void setLegendSize(QSizeF s)
Set the preferred resulting legend size.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
void setFontColor(const QColor &color)
Sets the legend font color.
void setLegendFilterOutAtlas(bool doFilter)
When set to true, during an atlas rendering, it will filter out legend elements where features are ou...
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
Definition: qgslayertree.h:75
A rectangle specified with double values.
Definition: qgsrectangle.h:39
void setTitle(const QString &title)
Sets the legend title.
void setEqualColumnWidth(bool s)
double lineSpacing() const
Returns the spacing in-between lines in layout units.
double boxSpace() const
Returns the legend box space.
Item model implementation based on layer tree model for layout legend.
double columnSpace() const
Returns the legend column spacing.
void setBoxSpace(double s)
void setMmPerMapUnit(double mmPerMapUnit)
void setSplitLayer(bool enabled)
Sets whether the legend items from a single layer can be split over multiple columns.
QColor rasterStrokeColor() const
Returns the stroke color for the stroke drawn around raster symbol items.
Base class for graphical items within a QgsLayout.
void setSymbolWidth(double width)
Sets the legend symbol width.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
QString wrapChar() const
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget) override
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
int type() const override
void setSymbolSize(QSizeF s)
QgsMapSettings mapSettings(const QgsRectangle &extent, QSizeF size, double dpi, bool includeLayerSettings) const
Return map settings that will be used for drawing of the map.
void extentChanged()
Is emitted when the map&#39;s extent changes.
QgsLayoutSize sizeWithUnits() const
Returns the item&#39;s current size, including units.
QFont font() const
The font for this style.
Composer legend components style.
void updateFilterByMap(bool redraw=true)
Updates the legend content when filtered by map.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:111
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
QString title() const
Returns the legend title.
void setStyle(QgsLegendStyle::Style component, const QgsLegendStyle &style)
Sets the style of component to style for the legend.
bool drawRasterStroke() const
Returns whether a stroke will be drawn around raster symbol items.
void setLegendFilterByMapEnabled(bool enabled)
Set whether legend items should be filtered to show just the ones visible in the associated map...
bool resizeToContents() const
Returns whether the legend should automatically resize to fit its contents.
double rasterStrokeWidth() const
Returns the stroke width (in layout units) for the stroke drawn around raster symbol items...
int columnCount() const
Returns the legend column count.
Qt::AlignmentFlag titleAlignment() const
Returns the alignment of the legend title.
bool equalColumnWidth() const
Returns whether column widths should be equalized.
QColor rasterStrokeColor() const
Returns the stroke color for the stroke drawn around raster symbol items.
Allow check boxes for legend nodes (if supported by layer&#39;s legend)
void setDrawRasterStroke(bool enabled)
Sets whether a stroke will be drawn around raster symbol items.
The QgsMapSettings class contains configuration for rendering of the map.
static QString encodeColor(const QColor &color)
QString displayName() const override
Get item display name.
The QgsLayerTreeModel class is model implementation for Qt item views framework.
QSizeF wmsLegendSize() const
QgsLegendStyle style(QgsLegendStyle::Style s) const
Returns style.
void adjustBoxSize()
Sets the legend&#39;s item bounds to fit the whole legend content.
QgsRectangle extent() const
Returns the current map extent.
Layout graphical items for displaying a map.
void setFont(const QFont &font)
The font for this style.
QgsPropertyCollection mDataDefinedProperties
const QgsLayout * layout() const
Returns the layout the object is attached to.
void draw(QgsLayoutItemRenderContext &context) override
Draws the item&#39;s contents using the specified item render context.
double symbolWidth() const
Returns the legend symbol width.
Namespace with helper functions for layer tree operations.
Definition: qgslayertree.h:32
void finalizeRestoreFromXml() override
Called after all pending items have been restored from XML.
QColor fontColor() const
Returns the legend font color.
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget) override
Handles preparing a paint surface for the layout item and painting the item&#39;s content.
double scale() const
Returns the calculated map scale.
long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:142
static QgsLayerTreeModelLegendNode * index2legendNode(const QModelIndex &index)
Return legend node for given index.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void attemptSetSceneRect(const QRectF &rect, bool includesFrame=false)
Attempts to update the item&#39;s position and size to match the passed rect in layout coordinates...
void setMapScale(double scale)
Sets the legend map scale.
QgsRenderContext & renderContext()
Returns a reference to the context&#39;s render context.
Definition: qgslayoutitem.h:71
double symbolHeight() const
Returns the legend symbol height.
QVariant data(const QModelIndex &index, int role) const override
QgsLegendStyle & rstyle(QgsLegendStyle::Style s)
Returns reference to modifiable legend style.
double rasterStrokeWidth() const
Returns the stroke width (in millimeters) for the stroke drawn around raster symbol items...
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
double mapUnitsToLayoutUnits() const
Returns the conversion factor from map units to layout units.
QPointer< QgsLayout > mLayout
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...
virtual void attemptResize(const QgsLayoutSize &size, bool includesFrame=false)
Attempts to resize the item to a specified target size.
void setRasterStrokeColor(const QColor &color)
Sets the stroke color for the stroke drawn around raster symbol items.
Symbol without label.
QgsLegendModel(QgsLayerTree *rootNode, QObject *parent=nullptr)
Construct the model based on the given layer tree.
void setTitle(const QString &t)
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)
QgsLegendStyle style(QgsLegendStyle::Style s) const
Returns legend style.
QString id() const
Returns the item&#39;s ID name.
Qt::AlignmentFlag titleAlignment() const
Returns the alignment of the legend title.
QgsLayoutItemLegend(QgsLayout *layout)
Constructor for QgsLayoutItemLegend, with the specified parent layout.
void setLineSpacing(double spacing)
Sets the spacing in-between multiple lines.
void setDpi(int dpi)
double boxSpace() const
QgsRectangle requestedExtent() const
Calculates the extent to request and the yShift of the top-left point in case of rotation.
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.
void setColumnSpace(double s)
void setFlag(Flag f, bool on=true)
Enable or disable a model flag.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
void updateLegend()
Updates the model and all legend entries.
void setWrapString(const QString &string)
Sets the legend text wrapping string.
QSizeF minimumSize()
Run the layout algorithm and determine the size required for legend.
void setStyleFont(QgsLegendStyle::Style component, const QFont &font)
Sets the style font for a legend component.
void setResizeToContents(bool enabled)
Sets whether the legend should automatically resize to fit its contents.
virtual void redraw()
Triggers a redraw (update) of the item.
void setAutoUpdateModel(bool autoUpdate)
Sets whether the legend content should auto update to reflect changes in the project&#39;s layer tree...
void setColumnCount(int count)
Sets the legend column count.
QSizeF symbolSize() const
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:43
void setLineSpacing(double s)
QgsMapLayer * layer() const
void readXml(const QDomElement &elem, const QDomDocument &doc)
static QgsLayoutItemLegend * create(QgsLayout *layout)
Returns a new legend item for the specified layout.
void refreshDataDefinedProperty(const QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::AllProperties) override
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a string...
void setWmsLegendSize(QSizeF s)
QgsLegendStyle & rstyle(QgsLegendStyle::Style s)
Returns reference to modifiable style.
void setUseAdvancedEffects(bool use)
void setLinkedMap(QgsLayoutItemMap *map)
Sets the map to associate with the legend.
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsLayerTreeNode * index2node(const QModelIndex &index) const
Return layer tree node for given index.
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used for rendering the map.
void setStyle(QgsLegendStyle::Style s, const QgsLegendStyle &style)
Enable advanced effects such as blend modes.
QString wrapString() const
Returns the legend text wrapping string.
static QgsLayerTree * readXml(QDomElement &element, const QgsReadWriteContext &context)
Load the layer tree from an XML element.
double wmsLegendHeight() const
Returns the WMS legend height.
bool equalColumnWidth() const
void setStyleMargin(QgsLegendStyle::Style component, double margin)
Set the margin for a legend component.
void setColumnCount(int c)
double columnSpace() const
virtual QString uuid() const
Returns the item identification string.
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
bool autoUpdateModel() const
Returns whether the legend content should auto update to reflect changes in the project&#39;s layer tree...
virtual void refreshDataDefinedProperty(const QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::AllProperties)
Refreshes a data defined property for the item by reevaluating the property&#39;s value and redrawing the...
void setRasterStrokeWidth(double width)
Sets the stroke width for the stroke drawn around raster symbol items.
void writeXml(const QString &name, QDomElement &elem, QDomDocument &doc) const
void setWmsLegendHeight(double height)
Sets the WMS legend height.
void layerStyleOverridesChanged()
Emitted when layer style overrides are changed...
void setWmsLegendWidth(double width)
Sets the WMS legend width.
Flags flags() const
Return OR-ed combination of model flags.
A layout item subclass for map legends.
void customPropertyChanged(QgsLayerTreeNode *node, const QString &key)
Emitted when a custom property of a node within the tree has been changed or removed.
void setSymbolHeight(double height)
Sets the legend symbol height.
double lineSpacing() const
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:40
void setBoxSpace(double space)
Sets the legend box space.
QColor fontColor() const
void changed()
Emitted when the object&#39;s properties change.
bool legendFilterOutAtlas() const
Returns whether to filter out legend elements outside of the current atlas feature.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
Represents a vector layer which manages a vector based data sets.
bool splitLayer() const
DataDefinedProperty
Data defined properties for different item types.
int valueAsInt(int key, const QgsExpressionContext &context, int defaultValue=0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as an integer...
void setRasterStrokeColor(const QColor &color)
Sets the stroke color for the stroke drawn around raster symbol items.
void setTitleAlignment(Qt::AlignmentFlag alignment)
Sets the alignment of the legend title.
void refresh() override
Refreshes the item, causing a recalculation of any property overrides and recalculation of its positi...
bool drawRasterStroke() const
Returns whether a stroke will be drawn around raster symbol items.
QIcon icon() const override
Returns the item&#39;s icon.
double wmsLegendWidth() const
Returns the WMS legend width.
Allow reordering with drag&#39;n&#39;drop.
void setSplitLayer(bool s)
static QColor decodeColor(const QString &str)
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:149
Layer tree node points to a map layer.
The QgsLegendRenderer class handles automatic layout and rendering of legend.
QString title() const
All properties for item.
QMap< QString, QString > layerStyleOverrides() const
Returns stored overrides of styles for layers.
QFont styleFont(QgsLegendStyle::Style component) const
Returns the font settings for a legend component.