QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 "qgslayoututils.h"
34 #include <QDomDocument>
35 #include <QDomElement>
36 #include <QPainter>
37 
39  : QgsLayoutItem( layout )
40  , mLegendModel( new QgsLegendModel( layout->project()->layerTreeRoot() ) )
41 {
42 #if 0 //no longer required?
43  connect( &layout->atlasComposition(), &QgsAtlasComposition::renderEnded, this, &QgsLayoutItemLegend::onAtlasEnded );
44 #endif
45 
46  mTitle = mSettings.title();
47 
48  // Connect to the main layertreeroot.
49  // It serves in "auto update mode" as a medium between the main app legend and this one
50  connect( mLayout->project()->layerTreeRoot(), &QgsLayerTreeNode::customPropertyChanged, this, &QgsLayoutItemLegend::nodeCustomPropertyChanged );
51 
52  // If project colors change, we need to redraw legend, as legend symbols may rely on project colors
53  connect( mLayout->project(), &QgsProject::projectColorsChanged, this, [ = ]
54  {
56  update();
57  } );
58 }
59 
61 {
62  return new QgsLayoutItemLegend( layout );
63 }
64 
66 {
68 }
69 
71 {
72  return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemLegend.svg" ) );
73 }
74 
75 QgsLayoutItem::Flags QgsLayoutItemLegend::itemFlags() const
76 {
78 }
79 
80 void QgsLayoutItemLegend::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget )
81 {
82  if ( !painter )
83  return;
84 
85  if ( mFilterAskedForUpdate )
86  {
87  mFilterAskedForUpdate = false;
88  doUpdateFilterByMap();
89  }
90 
91  int dpi = painter->device()->logicalDpiX();
92  double dotsPerMM = dpi / 25.4;
93 
94  if ( mLayout )
95  {
97  mSettings.setDpi( dpi );
98  }
99  if ( mMap && mLayout )
100  {
101  mSettings.setMmPerMapUnit( mLayout->convertFromLayoutUnits( mMap->mapUnitsToLayoutUnits(), QgsUnitTypes::LayoutMillimeters ).length() );
102 
103  // use a temporary QgsMapSettings to find out real map scale
104  QSizeF mapSizePixels = QSizeF( mMap->rect().width() * dotsPerMM, mMap->rect().height() * dotsPerMM );
105  QgsRectangle mapExtent = mMap->extent();
106 
107  QgsMapSettings ms = mMap->mapSettings( mapExtent, mapSizePixels, dpi, false );
108  mSettings.setMapScale( ms.scale() );
109  }
110  mInitialMapScaleCalculated = true;
111 
112  QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
113  legendRenderer.setLegendSize( mForceResize && mSizeToContents ? QSize() : rect().size() );
114 
115  //adjust box if width or height is too small
116  if ( mSizeToContents )
117  {
119  QSizeF size = legendRenderer.minimumSize( &context );
120  if ( mForceResize )
121  {
122  mForceResize = false;
123 
124  //set new rect, respecting position mode and data defined size/position
125  QgsLayoutSize newSize = mLayout->convertFromLayoutUnits( size, sizeWithUnits().units() );
126  attemptResize( newSize );
127  }
128  else if ( size.height() > rect().height() || size.width() > rect().width() )
129  {
130  //need to resize box
131  QSizeF targetSize = rect().size();
132  if ( size.height() > targetSize.height() )
133  targetSize.setHeight( size.height() );
134  if ( size.width() > targetSize.width() )
135  targetSize.setWidth( size.width() );
136 
137  QgsLayoutSize newSize = mLayout->convertFromLayoutUnits( targetSize, sizeWithUnits().units() );
138  //set new rect, respecting position mode and data defined size/position
139  attemptResize( newSize );
140  }
141  }
142  QgsLayoutItem::paint( painter, itemStyle, pWidget );
143 }
144 
146 {
147  if ( !mMapUuid.isEmpty() )
148  {
149  setLinkedMap( qobject_cast< QgsLayoutItemMap * >( mLayout->itemByUuid( mMapUuid, true ) ) );
150  }
151 }
152 
154 {
156  onAtlasFeature();
157 }
158 
160 {
161  QPainter *painter = context.renderContext().painter();
162  painter->save();
163 
164  // painter is scaled to dots, so scale back to layout units
165  painter->scale( context.renderContext().scaleFactor(), context.renderContext().scaleFactor() );
166 
167  painter->setPen( QPen( QColor( 0, 0, 0 ) ) );
168 
169  if ( !mSizeToContents )
170  {
171  // set a clip region to crop out parts of legend which don't fit
172  QRectF thisPaintRect = QRectF( 0, 0, rect().width(), rect().height() );
173  painter->setClipRect( thisPaintRect );
174  }
175 
176  QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
177  legendRenderer.setLegendSize( rect().size() );
178 
179  legendRenderer.drawLegend( context.renderContext() );
180 
181  painter->restore();
182 }
183 
185 {
186  if ( !mSizeToContents )
187  return;
188 
189  if ( !mInitialMapScaleCalculated )
190  {
191  // this is messy - but until we have painted the item we have no knowledge of the current DPI
192  // and so cannot correctly calculate the map scale. This results in incorrect size calculations
193  // for marker symbols with size in map units, causing the legends to initially expand to huge
194  // sizes if we attempt to calculate the box size first.
195  return;
196  }
197 
199  QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
200  QSizeF size = legendRenderer.minimumSize( &context );
201  QgsDebugMsg( QStringLiteral( "width = %1 height = %2" ).arg( size.width() ).arg( size.height() ) );
202  if ( size.isValid() )
203  {
204  QgsLayoutSize newSize = mLayout->convertFromLayoutUnits( size, sizeWithUnits().units() );
205  //set new rect, respecting position mode and data defined size/position
206  attemptResize( newSize );
207  }
208 }
209 
211 {
212  mSizeToContents = enabled;
213 }
214 
216 {
217  return mSizeToContents;
218 }
219 
220 void QgsLayoutItemLegend::setCustomLayerTree( QgsLayerTree *rootGroup )
221 {
222  mLegendModel->setRootGroup( rootGroup ? rootGroup : ( mLayout ? mLayout->project()->layerTreeRoot() : nullptr ) );
223 
224  mCustomLayerTree.reset( rootGroup );
225 }
226 
228 {
229  if ( autoUpdate == autoUpdateModel() )
230  return;
231 
232  setCustomLayerTree( autoUpdate ? nullptr : mLayout->project()->layerTreeRoot()->clone() );
233  adjustBoxSize();
234  updateFilterByMap( false );
235 }
236 
237 void QgsLayoutItemLegend::nodeCustomPropertyChanged( QgsLayerTreeNode *, const QString & )
238 {
239  if ( autoUpdateModel() )
240  {
241  // in "auto update" mode, some parameters on the main app legend may have been changed (expression filtering)
242  // we must then call updateItem to reflect the changes
243  updateFilterByMap( false );
244  }
245 }
246 
248 {
249  return !mCustomLayerTree;
250 }
251 
253 {
254  mLegendFilterByMap = enabled;
255  updateFilterByMap( false );
256 }
257 
258 void QgsLayoutItemLegend::setTitle( const QString &t )
259 {
260  mTitle = t;
261  mSettings.setTitle( t );
262 
263  if ( mLayout && id().isEmpty() )
264  {
265  //notify the model that the display name has changed
266  mLayout->itemsModel()->updateItemDisplayName( this );
267  }
268 }
270 {
271  return mTitle;
272 }
273 
274 Qt::AlignmentFlag QgsLayoutItemLegend::titleAlignment() const
275 {
276  return mSettings.titleAlignment();
277 }
278 
279 void QgsLayoutItemLegend::setTitleAlignment( Qt::AlignmentFlag alignment )
280 {
281  mSettings.setTitleAlignment( alignment );
282 }
283 
285 {
286  return mSettings.rstyle( s );
287 }
288 
290 {
291  return mSettings.style( s );
292 }
293 
295 {
296  mSettings.setStyle( s, style );
297 }
298 
300 {
301  return mSettings.style( s ).font();
302 }
303 
305 {
306  rstyle( s ).setFont( f );
307 }
308 
310 {
311  rstyle( s ).setMargin( margin );
312 }
313 
315 {
316  rstyle( s ).setMargin( side, margin );
317 }
318 
320 {
321  return mSettings.lineSpacing();
322 }
323 
325 {
326  mSettings.setLineSpacing( spacing );
327 }
328 
330 {
331  return mSettings.boxSpace();
332 }
333 
335 {
336  mSettings.setBoxSpace( s );
337 }
338 
340 {
341  return mSettings.columnSpace();
342 }
343 
345 {
346  mSettings.setColumnSpace( s );
347 }
348 
350 {
351  return mSettings.fontColor();
352 }
353 
355 {
356  mSettings.setFontColor( c );
357 }
358 
360 {
361  return mSettings.symbolSize().width();
362 }
363 
365 {
366  mSettings.setSymbolSize( QSizeF( w, mSettings.symbolSize().height() ) );
367 }
368 
370 {
371  return mSettings.symbolSize().height();
372 }
373 
375 {
376  mSettings.setSymbolSize( QSizeF( mSettings.symbolSize().width(), h ) );
377 }
378 
380 {
381  return mSettings.wmsLegendSize().width();
382 }
383 
385 {
386  mSettings.setWmsLegendSize( QSizeF( w, mSettings.wmsLegendSize().height() ) );
387 }
388 
390 {
391  return mSettings.wmsLegendSize().height();
392 }
394 {
395  mSettings.setWmsLegendSize( QSizeF( mSettings.wmsLegendSize().width(), h ) );
396 }
397 
398 void QgsLayoutItemLegend::setWrapString( const QString &t )
399 {
400  mSettings.setWrapChar( t );
401 }
402 
404 {
405  return mSettings.wrapChar();
406 }
407 
409 {
410  return mColumnCount;
411 }
412 
414 {
415  mColumnCount = c;
416  mSettings.setColumnCount( c );
417 }
418 
420 {
421  return mSettings.splitLayer();
422 }
423 
425 {
426  mSettings.setSplitLayer( s );
427 }
428 
430 {
431  return mSettings.equalColumnWidth();
432 }
433 
435 {
436  mSettings.setEqualColumnWidth( s );
437 }
438 
440 {
441  return mSettings.drawRasterStroke();
442 }
443 
445 {
446  mSettings.setDrawRasterStroke( enabled );
447 }
448 
450 {
451  return mSettings.rasterStrokeColor();
452 }
453 
454 void QgsLayoutItemLegend::setRasterStrokeColor( const QColor &color )
455 {
456  mSettings.setRasterStrokeColor( color );
457 }
458 
460 {
461  return mSettings.rasterStrokeWidth();
462 }
463 
465 {
466  mSettings.setRasterStrokeWidth( width );
467 }
468 
469 
471 {
472  adjustBoxSize();
473  updateFilterByMap( false );
474 }
475 
476 bool QgsLayoutItemLegend::writePropertiesToElement( QDomElement &legendElem, QDomDocument &doc, const QgsReadWriteContext &context ) const
477 {
478 
479  //write general properties
480  legendElem.setAttribute( QStringLiteral( "title" ), mTitle );
481  legendElem.setAttribute( QStringLiteral( "titleAlignment" ), QString::number( static_cast< int >( mSettings.titleAlignment() ) ) );
482  legendElem.setAttribute( QStringLiteral( "columnCount" ), QString::number( mColumnCount ) );
483  legendElem.setAttribute( QStringLiteral( "splitLayer" ), QString::number( mSettings.splitLayer() ) );
484  legendElem.setAttribute( QStringLiteral( "equalColumnWidth" ), QString::number( mSettings.equalColumnWidth() ) );
485 
486  legendElem.setAttribute( QStringLiteral( "boxSpace" ), QString::number( mSettings.boxSpace() ) );
487  legendElem.setAttribute( QStringLiteral( "columnSpace" ), QString::number( mSettings.columnSpace() ) );
488 
489  legendElem.setAttribute( QStringLiteral( "symbolWidth" ), QString::number( mSettings.symbolSize().width() ) );
490  legendElem.setAttribute( QStringLiteral( "symbolHeight" ), QString::number( mSettings.symbolSize().height() ) );
491  legendElem.setAttribute( QStringLiteral( "lineSpacing" ), QString::number( mSettings.lineSpacing() ) );
492 
493  legendElem.setAttribute( QStringLiteral( "rasterBorder" ), mSettings.drawRasterStroke() );
494  legendElem.setAttribute( QStringLiteral( "rasterBorderColor" ), QgsSymbolLayerUtils::encodeColor( mSettings.rasterStrokeColor() ) );
495  legendElem.setAttribute( QStringLiteral( "rasterBorderWidth" ), QString::number( mSettings.rasterStrokeWidth() ) );
496 
497  legendElem.setAttribute( QStringLiteral( "wmsLegendWidth" ), QString::number( mSettings.wmsLegendSize().width() ) );
498  legendElem.setAttribute( QStringLiteral( "wmsLegendHeight" ), QString::number( mSettings.wmsLegendSize().height() ) );
499  legendElem.setAttribute( QStringLiteral( "wrapChar" ), mSettings.wrapChar() );
500  legendElem.setAttribute( QStringLiteral( "fontColor" ), mSettings.fontColor().name() );
501 
502  legendElem.setAttribute( QStringLiteral( "resizeToContents" ), mSizeToContents );
503 
504  if ( mMap )
505  {
506  legendElem.setAttribute( QStringLiteral( "map_uuid" ), mMap->uuid() );
507  }
508 
509  QDomElement legendStyles = doc.createElement( QStringLiteral( "styles" ) );
510  legendElem.appendChild( legendStyles );
511 
512  style( QgsLegendStyle::Title ).writeXml( QStringLiteral( "title" ), legendStyles, doc );
513  style( QgsLegendStyle::Group ).writeXml( QStringLiteral( "group" ), legendStyles, doc );
514  style( QgsLegendStyle::Subgroup ).writeXml( QStringLiteral( "subgroup" ), legendStyles, doc );
515  style( QgsLegendStyle::Symbol ).writeXml( QStringLiteral( "symbol" ), legendStyles, doc );
516  style( QgsLegendStyle::SymbolLabel ).writeXml( QStringLiteral( "symbolLabel" ), legendStyles, doc );
517 
518  if ( mCustomLayerTree )
519  {
520  // if not using auto-update - store the custom layer tree
521  mCustomLayerTree->writeXml( legendElem, context );
522  }
523 
524  if ( mLegendFilterByMap )
525  {
526  legendElem.setAttribute( QStringLiteral( "legendFilterByMap" ), QStringLiteral( "1" ) );
527  }
528  legendElem.setAttribute( QStringLiteral( "legendFilterByAtlas" ), mFilterOutAtlas ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
529 
530  return true;
531 }
532 
533 bool QgsLayoutItemLegend::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context )
534 {
535  //read general properties
536  mTitle = itemElem.attribute( QStringLiteral( "title" ) );
537  mSettings.setTitle( mTitle );
538  if ( !itemElem.attribute( QStringLiteral( "titleAlignment" ) ).isEmpty() )
539  {
540  mSettings.setTitleAlignment( static_cast< Qt::AlignmentFlag >( itemElem.attribute( QStringLiteral( "titleAlignment" ) ).toInt() ) );
541  }
542  int colCount = itemElem.attribute( QStringLiteral( "columnCount" ), QStringLiteral( "1" ) ).toInt();
543  if ( colCount < 1 ) colCount = 1;
544  mColumnCount = colCount;
545  mSettings.setColumnCount( mColumnCount );
546  mSettings.setSplitLayer( itemElem.attribute( QStringLiteral( "splitLayer" ), QStringLiteral( "0" ) ).toInt() == 1 );
547  mSettings.setEqualColumnWidth( itemElem.attribute( QStringLiteral( "equalColumnWidth" ), QStringLiteral( "0" ) ).toInt() == 1 );
548 
549  QDomNodeList stylesNodeList = itemElem.elementsByTagName( QStringLiteral( "styles" ) );
550  if ( !stylesNodeList.isEmpty() )
551  {
552  QDomNode stylesNode = stylesNodeList.at( 0 );
553  for ( int i = 0; i < stylesNode.childNodes().size(); i++ )
554  {
555  QDomElement styleElem = stylesNode.childNodes().at( i ).toElement();
557  style.readXml( styleElem, doc );
558  QString name = styleElem.attribute( QStringLiteral( "name" ) );
560  if ( name == QLatin1String( "title" ) ) s = QgsLegendStyle::Title;
561  else if ( name == QLatin1String( "group" ) ) s = QgsLegendStyle::Group;
562  else if ( name == QLatin1String( "subgroup" ) ) s = QgsLegendStyle::Subgroup;
563  else if ( name == QLatin1String( "symbol" ) ) s = QgsLegendStyle::Symbol;
564  else if ( name == QLatin1String( "symbolLabel" ) ) s = QgsLegendStyle::SymbolLabel;
565  else continue;
566  setStyle( s, style );
567  }
568  }
569 
570  //font color
571  QColor fontClr;
572  fontClr.setNamedColor( itemElem.attribute( QStringLiteral( "fontColor" ), QStringLiteral( "#000000" ) ) );
573  mSettings.setFontColor( fontClr );
574 
575  //spaces
576  mSettings.setBoxSpace( itemElem.attribute( QStringLiteral( "boxSpace" ), QStringLiteral( "2.0" ) ).toDouble() );
577  mSettings.setColumnSpace( itemElem.attribute( QStringLiteral( "columnSpace" ), QStringLiteral( "2.0" ) ).toDouble() );
578 
579  mSettings.setSymbolSize( QSizeF( itemElem.attribute( QStringLiteral( "symbolWidth" ), QStringLiteral( "7.0" ) ).toDouble(), itemElem.attribute( QStringLiteral( "symbolHeight" ), QStringLiteral( "14.0" ) ).toDouble() ) );
580  mSettings.setWmsLegendSize( QSizeF( itemElem.attribute( QStringLiteral( "wmsLegendWidth" ), QStringLiteral( "50" ) ).toDouble(), itemElem.attribute( QStringLiteral( "wmsLegendHeight" ), QStringLiteral( "25" ) ).toDouble() ) );
581  mSettings.setLineSpacing( itemElem.attribute( QStringLiteral( "lineSpacing" ), QStringLiteral( "1.0" ) ).toDouble() );
582 
583  mSettings.setDrawRasterStroke( itemElem.attribute( QStringLiteral( "rasterBorder" ), QStringLiteral( "1" ) ) != QLatin1String( "0" ) );
584  mSettings.setRasterStrokeColor( QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "rasterBorderColor" ), QStringLiteral( "0,0,0" ) ) ) );
585  mSettings.setRasterStrokeWidth( itemElem.attribute( QStringLiteral( "rasterBorderWidth" ), QStringLiteral( "0" ) ).toDouble() );
586 
587  mSettings.setWrapChar( itemElem.attribute( QStringLiteral( "wrapChar" ) ) );
588 
589  mSizeToContents = itemElem.attribute( QStringLiteral( "resizeToContents" ), QStringLiteral( "1" ) ) != QLatin1String( "0" );
590 
591  // map
592  mLegendFilterByMap = itemElem.attribute( QStringLiteral( "legendFilterByMap" ), QStringLiteral( "0" ) ).toInt();
593 
594  mMapUuid.clear();
595  if ( !itemElem.attribute( QStringLiteral( "map_uuid" ) ).isEmpty() )
596  {
597  mMapUuid = itemElem.attribute( QStringLiteral( "map_uuid" ) );
598  }
599  // disconnect current map
600  setupMapConnections( mMap, false );
601  mMap = nullptr;
602 
603  mFilterOutAtlas = itemElem.attribute( QStringLiteral( "legendFilterByAtlas" ), QStringLiteral( "0" ) ).toInt();
604 
605  // QGIS >= 2.6
606  QDomElement layerTreeElem = itemElem.firstChildElement( QStringLiteral( "layer-tree" ) );
607  if ( layerTreeElem.isNull() )
608  layerTreeElem = itemElem.firstChildElement( QStringLiteral( "layer-tree-group" ) );
609 
610  if ( !layerTreeElem.isNull() )
611  {
612  std::unique_ptr< QgsLayerTree > tree( QgsLayerTree::readXml( layerTreeElem, context ) );
613  if ( mLayout )
614  tree->resolveReferences( mLayout->project(), true );
615  setCustomLayerTree( tree.release() );
616  }
617  else
618  setCustomLayerTree( nullptr );
619 
620  return true;
621 }
622 
624 {
625  if ( !id().isEmpty() )
626  {
627  return id();
628  }
629 
630  //if no id, default to portion of title text
631  QString text = mSettings.title();
632  if ( text.isEmpty() )
633  {
634  return tr( "<Legend>" );
635  }
636  if ( text.length() > 25 )
637  {
638  return tr( "%1…" ).arg( text.left( 25 ) );
639  }
640  else
641  {
642  return text;
643  }
644 }
645 
646 
647 void QgsLayoutItemLegend::setupMapConnections( QgsLayoutItemMap *map, bool connectSlots )
648 {
649  if ( !map )
650  return;
651 
652  if ( !connectSlots )
653  {
654  disconnect( map, &QObject::destroyed, this, &QgsLayoutItemLegend::invalidateCurrentMap );
655  disconnect( map, &QgsLayoutObject::changed, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
656  disconnect( map, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
657  disconnect( map, &QgsLayoutItemMap::layerStyleOverridesChanged, this, &QgsLayoutItemLegend::mapLayerStyleOverridesChanged );
658  }
659  else
660  {
661  connect( map, &QObject::destroyed, this, &QgsLayoutItemLegend::invalidateCurrentMap );
662  connect( map, &QgsLayoutObject::changed, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
663  connect( map, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
664  connect( map, &QgsLayoutItemMap::layerStyleOverridesChanged, this, &QgsLayoutItemLegend::mapLayerStyleOverridesChanged );
665  }
666 }
667 
669 {
670  if ( mMap )
671  {
672  setupMapConnections( mMap, false );
673  }
674 
675  mMap = map;
676 
677  if ( mMap )
678  {
679  setupMapConnections( mMap, true );
680  }
681 
683 }
684 
685 void QgsLayoutItemLegend::invalidateCurrentMap()
686 {
687  setLinkedMap( nullptr );
688 }
689 
691 {
693 
694  bool forceUpdate = false;
695  //updates data defined properties and redraws item to match
696  if ( property == QgsLayoutObject::LegendTitle || property == QgsLayoutObject::AllProperties )
697  {
698  bool ok = false;
699  QString t = mDataDefinedProperties.valueAsString( QgsLayoutObject::LegendTitle, context, mTitle, &ok );
700  if ( ok )
701  {
702  mSettings.setTitle( t );
703  forceUpdate = true;
704  }
705  }
707  {
708  bool ok = false;
709  int cols = mDataDefinedProperties.valueAsInt( QgsLayoutObject::LegendColumnCount, context, mColumnCount, &ok );
710  if ( ok && cols >= 0 )
711  {
712  mSettings.setColumnCount( cols );
713  forceUpdate = true;
714  }
715  }
716  if ( forceUpdate )
717  {
718  adjustBoxSize();
719  update();
720  }
721 
723 }
724 
725 
726 void QgsLayoutItemLegend::updateFilterByMapAndRedraw()
727 {
728  updateFilterByMap( true );
729 }
730 
731 void QgsLayoutItemLegend::mapLayerStyleOverridesChanged()
732 {
733  if ( !mMap )
734  return;
735 
736  // map's style has been changed, so make sure to update the legend here
737  if ( mLegendFilterByMap )
738  {
739  // legend is being filtered by map, so we need to re run the hit test too
740  // as the style overrides may also have affected the visible symbols
741  updateFilterByMap( false );
742  }
743  else
744  {
745  mLegendModel->setLayerStyleOverrides( mMap->layerStyleOverrides() );
746 
747  const auto constFindLayers = mLegendModel->rootGroup()->findLayers();
748  for ( QgsLayerTreeLayer *nodeLayer : constFindLayers )
749  mLegendModel->refreshLayerLegend( nodeLayer );
750  }
751 
752  adjustBoxSize();
753  updateFilterByMap( false );
754 }
755 
757 {
758  // ask for update
759  // the actual update will take place before the redraw.
760  // This is to avoid multiple calls to the filter
761  mFilterAskedForUpdate = true;
762 
763  if ( redraw )
764  update();
765 }
766 
767 void QgsLayoutItemLegend::doUpdateFilterByMap()
768 {
769  if ( mMap )
770  mLegendModel->setLayerStyleOverrides( mMap->layerStyleOverrides() );
771  else
772  mLegendModel->setLayerStyleOverrides( QMap<QString, QString>() );
773 
774 
775  bool filterByExpression = QgsLayerTreeUtils::hasLegendFilterExpression( *( mCustomLayerTree ? mCustomLayerTree.get() : mLayout->project()->layerTreeRoot() ) );
776 
777  if ( mMap && ( mLegendFilterByMap || filterByExpression || mInAtlas ) )
778  {
779  int dpi = mLayout->renderContext().dpi();
780 
781  QgsRectangle requestRectangle = mMap->requestedExtent();
782 
783  QSizeF size( requestRectangle.width(), requestRectangle.height() );
784  size *= mLayout->convertFromLayoutUnits( mMap->mapUnitsToLayoutUnits(), QgsUnitTypes::LayoutMillimeters ).length() * dpi / 25.4;
785 
786  QgsMapSettings ms = mMap->mapSettings( requestRectangle, size, dpi, true );
787 
788  QgsGeometry filterPolygon;
789  if ( mInAtlas )
790  {
791  filterPolygon = mLayout->reportContext().currentGeometry( mMap->crs() );
792  }
793  mLegendModel->setLegendFilter( &ms, /* useExtent */ mInAtlas || mLegendFilterByMap, filterPolygon, /* useExpressions */ true );
794  }
795  else
796  mLegendModel->setLegendFilterByMap( nullptr );
797 
798  mForceResize = true;
799 }
800 
802 {
803  mFilterOutAtlas = doFilter;
804 }
805 
807 {
808  return mFilterOutAtlas;
809 }
810 
811 void QgsLayoutItemLegend::onAtlasFeature()
812 {
813  if ( !mLayout->reportContext().feature().isValid() )
814  return;
815  mInAtlas = mFilterOutAtlas;
817 }
818 
819 void QgsLayoutItemLegend::onAtlasEnded()
820 {
821  mInAtlas = false;
823 }
824 
826 {
828 
829  // We only want the last scope from the map's expression context, as this contains
830  // the map specific variables. We don't want the rest of the map's context, because that
831  // will contain duplicate global, project, layout, etc scopes.
832 
833  if ( mMap )
834  {
835  context.appendScope( mMap->createExpressionContext().popScope() );
836  }
837 
838  QgsExpressionContextScope *scope = new QgsExpressionContextScope( tr( "Legend Settings" ) );
839 
840  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_title" ), title(), true ) );
841  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_column_count" ), columnCount(), true ) );
842  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_split_layers" ), splitLayer(), true ) );
843  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_wrap_string" ), wrapString(), true ) );
844  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_filter_by_map" ), legendFilterByMapEnabled(), true ) );
845  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_filter_out_atlas" ), legendFilterOutAtlas(), true ) );
846 
847  context.appendScope( scope );
848 
849  return context;
850 }
851 
852 // -------------------------------------------------------------------------
854 #include "qgsvectorlayer.h"
855 
856 QgsLegendModel::QgsLegendModel( QgsLayerTree *rootNode, QObject *parent )
857  : QgsLayerTreeModel( rootNode, parent )
858 {
861 }
862 
863 QVariant QgsLegendModel::data( const QModelIndex &index, int role ) const
864 {
865  // handle custom layer node labels
866  if ( QgsLayerTreeNode *node = index2node( index ) )
867  {
868  if ( QgsLayerTree::isLayer( node ) && ( role == Qt::DisplayRole || role == Qt::EditRole ) && !node->customProperty( QStringLiteral( "legend/title-label" ) ).isNull() )
869  {
870  QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
871  QString name = node->customProperty( QStringLiteral( "legend/title-label" ) ).toString();
872  if ( nodeLayer->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toInt() && role == Qt::DisplayRole )
873  {
874  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( nodeLayer->layer() );
875  if ( vlayer && vlayer->featureCount() >= 0 )
876  name += QStringLiteral( " [%1]" ).arg( vlayer->featureCount() );
877  }
878  return name;
879  }
880  }
881 
882  return QgsLayerTreeModel::data( index, role );
883 }
884 
885 Qt::ItemFlags QgsLegendModel::flags( const QModelIndex &index ) const
886 {
887  // make the legend nodes selectable even if they are not by default
888  if ( index2legendNode( index ) )
889  return QgsLayerTreeModel::flags( index ) | Qt::ItemIsSelectable;
890 
891  return QgsLayerTreeModel::flags( index );
892 }
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)
Sets 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...
Single variable definition for use within a QgsExpressionContextScope.
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:41
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
Returns map settings that will be used for drawing of the map.
void extentChanged()
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.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
bool legendFilterByMapEnabled() const
Find out whether legend items are filtered to show just the ones visible in the associated map...
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.
void projectColorsChanged()
Emitted whenever the project&#39;s color scheme has been changed.
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.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
virtual void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::AllProperties)
Refreshes a data defined property for the item by reevaluating the property&#39;s value and redrawing the...
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
Gets item display name.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
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.
virtual void invalidateCache()
Forces a deferred update of any cached image the item uses.
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:202
static QgsLayerTreeModelLegendNode * index2legendNode(const QModelIndex &index)
Returns legend node for given index.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setMapScale(double scale)
Sets the legend map scale.
QgsRenderContext & renderContext()
Returns a reference to the context&#39;s render context.
Definition: qgslayoutitem.h:72
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.
static QgsRenderContext createRenderContextForLayout(QgsLayout *layout, QPainter *painter, double dpi=-1)
Creates a render context suitable for the specified layout and painter destination.
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...
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
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)
Single scope for storing variables and functions for use within a QgsExpressionContext.
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.
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:44
void setLineSpacing(double s)
QgsMapLayer * layer() const
Returns the map layer associated with this node.
void readXml(const QDomElement &elem, const QDomDocument &doc)
static QgsLayoutItemLegend * create(QgsLayout *layout)
Returns a new legend item for the specified layout.
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.
Contains information about the context of a rendering operation.
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
Returns 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)
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
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...
void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::AllProperties) override
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.
QSizeF minimumSize(QgsRenderContext *renderContext=nullptr)
Runs the layout algorithm and returns the minimum size required for the legend.
void layerStyleOverridesChanged()
Emitted when layer style overrides are changed...
void setWmsLegendWidth(double width)
Sets the WMS legend width.
Item overrides the default layout item painting method.
Flags flags() const
Returns OR-ed combination of model flags.
QgsExpressionContextScope * popScope()
Removes the last scope from the expression context and return it.
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.
QgsLayoutItem::Flags itemFlags() const override
Returns the item&#39;s flags, which indicate how the item behaves.
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:209
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.