Branch data Line data Source code
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 "qgsmapthemecollection.h"
35 : : #include "qgsstyleentityvisitor.h"
36 : : #include <QDomDocument>
37 : : #include <QDomElement>
38 : : #include <QPainter>
39 : : #include "qgsexpressioncontext.h"
40 : :
41 : 0 : QgsLayoutItemLegend::QgsLayoutItemLegend( QgsLayout *layout )
42 : 0 : : QgsLayoutItem( layout )
43 : 0 : , mLegendModel( new QgsLegendModel( layout->project()->layerTreeRoot(), this ) )
44 : 0 : {
45 : : #if 0 //no longer required?
46 : : connect( &layout->atlasComposition(), &QgsAtlasComposition::renderEnded, this, &QgsLayoutItemLegend::onAtlasEnded );
47 : : #endif
48 : :
49 : 0 : mTitle = mSettings.title();
50 : :
51 : : // Connect to the main layertreeroot.
52 : : // It serves in "auto update mode" as a medium between the main app legend and this one
53 : 0 : connect( mLayout->project()->layerTreeRoot(), &QgsLayerTreeNode::customPropertyChanged, this, &QgsLayoutItemLegend::nodeCustomPropertyChanged );
54 : :
55 : : // If project colors change, we need to redraw legend, as legend symbols may rely on project colors
56 : 0 : connect( mLayout->project(), &QgsProject::projectColorsChanged, this, [ = ]
57 : : {
58 : 0 : invalidateCache();
59 : 0 : update();
60 : 0 : } );
61 : 0 : connect( mLegendModel.get(), &QgsLegendModel::refreshLegend, this, &QgsLayoutItemLegend::refresh );
62 : 0 : }
63 : :
64 : 0 : QgsLayoutItemLegend *QgsLayoutItemLegend::create( QgsLayout *layout )
65 : : {
66 : 0 : return new QgsLayoutItemLegend( layout );
67 : 0 : }
68 : :
69 : 0 : int QgsLayoutItemLegend::type() const
70 : : {
71 : 0 : return QgsLayoutItemRegistry::LayoutLegend;
72 : : }
73 : :
74 : 0 : QIcon QgsLayoutItemLegend::icon() const
75 : : {
76 : 0 : return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemLegend.svg" ) );
77 : 0 : }
78 : :
79 : 0 : QgsLayoutItem::Flags QgsLayoutItemLegend::itemFlags() const
80 : : {
81 : 0 : return QgsLayoutItem::FlagOverridesPaint;
82 : : }
83 : :
84 : 0 : void QgsLayoutItemLegend::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget )
85 : : {
86 : 0 : if ( !painter )
87 : 0 : return;
88 : :
89 : 0 : if ( mFilterAskedForUpdate )
90 : : {
91 : 0 : mFilterAskedForUpdate = false;
92 : 0 : doUpdateFilterByMap();
93 : 0 : }
94 : :
95 : 0 : int dpi = painter->device()->logicalDpiX();
96 : 0 : double dotsPerMM = dpi / 25.4;
97 : :
98 : 0 : if ( mLayout )
99 : : {
100 : : Q_NOWARN_DEPRECATED_PUSH
101 : : // no longer required, but left set for api stability
102 : 0 : mSettings.setUseAdvancedEffects( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagUseAdvancedEffects );
103 : 0 : mSettings.setDpi( dpi );
104 : : Q_NOWARN_DEPRECATED_POP
105 : 0 : }
106 : 0 : if ( mMap && mLayout )
107 : : {
108 : : Q_NOWARN_DEPRECATED_PUSH
109 : : // no longer required, but left set for api stability
110 : 0 : mSettings.setMmPerMapUnit( mLayout->convertFromLayoutUnits( mMap->mapUnitsToLayoutUnits(), QgsUnitTypes::LayoutMillimeters ).length() );
111 : : Q_NOWARN_DEPRECATED_POP
112 : :
113 : : // use a temporary QgsMapSettings to find out real map scale
114 : 0 : QSizeF mapSizePixels = QSizeF( mMap->rect().width() * dotsPerMM, mMap->rect().height() * dotsPerMM );
115 : 0 : QgsRectangle mapExtent = mMap->extent();
116 : :
117 : 0 : QgsMapSettings ms = mMap->mapSettings( mapExtent, mapSizePixels, dpi, false );
118 : :
119 : : // no longer required, but left set for api stability
120 : : Q_NOWARN_DEPRECATED_PUSH
121 : 0 : mSettings.setMapScale( ms.scale() );
122 : : Q_NOWARN_DEPRECATED_POP
123 : 0 : }
124 : 0 : mInitialMapScaleCalculated = true;
125 : :
126 : 0 : QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
127 : 0 : legendRenderer.setLegendSize( mForceResize && mSizeToContents ? QSize() : rect().size() );
128 : :
129 : : //adjust box if width or height is too small
130 : 0 : if ( mSizeToContents )
131 : : {
132 : 0 : QgsRenderContext context = mMap ? QgsLayoutUtils::createRenderContextForMap( mMap, painter )
133 : 0 : : QgsLayoutUtils::createRenderContextForLayout( mLayout, painter );
134 : :
135 : 0 : QSizeF size = legendRenderer.minimumSize( &context );
136 : 0 : if ( mForceResize )
137 : : {
138 : 0 : mForceResize = false;
139 : :
140 : : //set new rect, respecting position mode and data defined size/position
141 : 0 : QgsLayoutSize newSize = mLayout->convertFromLayoutUnits( size, sizeWithUnits().units() );
142 : 0 : attemptResize( newSize );
143 : 0 : }
144 : 0 : else if ( size.height() > rect().height() || size.width() > rect().width() )
145 : : {
146 : : //need to resize box
147 : 0 : QSizeF targetSize = rect().size();
148 : 0 : if ( size.height() > targetSize.height() )
149 : 0 : targetSize.setHeight( size.height() );
150 : 0 : if ( size.width() > targetSize.width() )
151 : 0 : targetSize.setWidth( size.width() );
152 : :
153 : 0 : QgsLayoutSize newSize = mLayout->convertFromLayoutUnits( targetSize, sizeWithUnits().units() );
154 : : //set new rect, respecting position mode and data defined size/position
155 : 0 : attemptResize( newSize );
156 : 0 : }
157 : 0 : }
158 : :
159 : 0 : QgsLayoutItem::paint( painter, itemStyle, pWidget );
160 : 0 : }
161 : :
162 : 0 : void QgsLayoutItemLegend::finalizeRestoreFromXml()
163 : : {
164 : 0 : if ( !mMapUuid.isEmpty() )
165 : : {
166 : 0 : setLinkedMap( qobject_cast< QgsLayoutItemMap * >( mLayout->itemByUuid( mMapUuid, true ) ) );
167 : 0 : }
168 : 0 : }
169 : :
170 : 0 : void QgsLayoutItemLegend::refresh()
171 : : {
172 : 0 : QgsLayoutItem::refresh();
173 : 0 : clearLegendCachedData();
174 : 0 : onAtlasFeature();
175 : 0 : }
176 : :
177 : 0 : void QgsLayoutItemLegend::draw( QgsLayoutItemRenderContext &context )
178 : : {
179 : 0 : QPainter *painter = context.renderContext().painter();
180 : :
181 : 0 : QgsRenderContext rc = mMap ? QgsLayoutUtils::createRenderContextForMap( mMap, painter, context.renderContext().scaleFactor() * 25.4 )
182 : 0 : : QgsLayoutUtils::createRenderContextForLayout( mLayout, painter, context.renderContext().scaleFactor() * 25.4 );
183 : :
184 : 0 : rc.expressionContext().appendScopes( createExpressionContext().takeScopes() );
185 : :
186 : 0 : QgsScopedQPainterState painterState( painter );
187 : :
188 : : // painter is scaled to dots, so scale back to layout units
189 : 0 : painter->scale( rc.scaleFactor(), rc.scaleFactor() );
190 : :
191 : 0 : painter->setPen( QPen( QColor( 0, 0, 0 ) ) );
192 : :
193 : 0 : if ( !mSizeToContents )
194 : : {
195 : : // set a clip region to crop out parts of legend which don't fit
196 : 0 : QRectF thisPaintRect = QRectF( 0, 0, rect().width(), rect().height() );
197 : 0 : painter->setClipRect( thisPaintRect );
198 : 0 : }
199 : :
200 : 0 : if ( mLayout )
201 : : {
202 : : // no longer required, but left for API compatibility
203 : : Q_NOWARN_DEPRECATED_PUSH
204 : 0 : mSettings.setDpi( mLayout->renderContext().dpi() );
205 : : Q_NOWARN_DEPRECATED_POP
206 : 0 : }
207 : :
208 : :
209 : :
210 : :
211 : 0 : QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
212 : 0 : legendRenderer.setLegendSize( rect().size() );
213 : :
214 : 0 : legendRenderer.drawLegend( rc );
215 : 0 : }
216 : :
217 : 0 : void QgsLayoutItemLegend::adjustBoxSize()
218 : : {
219 : 0 : if ( !mSizeToContents )
220 : 0 : return;
221 : :
222 : 0 : if ( !mInitialMapScaleCalculated )
223 : : {
224 : : // this is messy - but until we have painted the item we have no knowledge of the current DPI
225 : : // and so cannot correctly calculate the map scale. This results in incorrect size calculations
226 : : // for marker symbols with size in map units, causing the legends to initially expand to huge
227 : : // sizes if we attempt to calculate the box size first.
228 : 0 : return;
229 : : }
230 : :
231 : 0 : QgsRenderContext context = mMap ? QgsLayoutUtils::createRenderContextForMap( mMap, nullptr ) :
232 : 0 : QgsLayoutUtils::createRenderContextForLayout( mLayout, nullptr );
233 : :
234 : 0 : QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
235 : 0 : QSizeF size = legendRenderer.minimumSize( &context );
236 : 0 : QgsDebugMsg( QStringLiteral( "width = %1 height = %2" ).arg( size.width() ).arg( size.height() ) );
237 : 0 : if ( size.isValid() )
238 : : {
239 : 0 : QgsLayoutSize newSize = mLayout->convertFromLayoutUnits( size, sizeWithUnits().units() );
240 : : //set new rect, respecting position mode and data defined size/position
241 : 0 : attemptResize( newSize );
242 : 0 : }
243 : 0 : }
244 : :
245 : 0 : void QgsLayoutItemLegend::setResizeToContents( bool enabled )
246 : : {
247 : 0 : mSizeToContents = enabled;
248 : 0 : }
249 : :
250 : 0 : bool QgsLayoutItemLegend::resizeToContents() const
251 : : {
252 : 0 : return mSizeToContents;
253 : : }
254 : :
255 : 0 : void QgsLayoutItemLegend::setCustomLayerTree( QgsLayerTree *rootGroup )
256 : : {
257 : 0 : mLegendModel->setRootGroup( rootGroup ? rootGroup : ( mLayout ? mLayout->project()->layerTreeRoot() : nullptr ) );
258 : :
259 : 0 : mCustomLayerTree.reset( rootGroup );
260 : 0 : }
261 : :
262 : :
263 : 0 : void QgsLayoutItemLegend::setAutoUpdateModel( bool autoUpdate )
264 : : {
265 : 0 : if ( autoUpdate == autoUpdateModel() )
266 : 0 : return;
267 : :
268 : 0 : setCustomLayerTree( autoUpdate ? nullptr : mLayout->project()->layerTreeRoot()->clone() );
269 : 0 : adjustBoxSize();
270 : 0 : updateFilterByMap( false );
271 : 0 : }
272 : :
273 : 0 : void QgsLayoutItemLegend::nodeCustomPropertyChanged( QgsLayerTreeNode *, const QString &key )
274 : : {
275 : 0 : if ( key == QLatin1String( "cached_name" ) )
276 : 0 : return;
277 : :
278 : 0 : if ( autoUpdateModel() )
279 : : {
280 : : // in "auto update" mode, some parameters on the main app legend may have been changed (expression filtering)
281 : : // we must then call updateItem to reflect the changes
282 : 0 : updateFilterByMap( false );
283 : 0 : }
284 : 0 : }
285 : :
286 : 0 : bool QgsLayoutItemLegend::autoUpdateModel() const
287 : : {
288 : 0 : return !mCustomLayerTree;
289 : : }
290 : :
291 : 0 : void QgsLayoutItemLegend::setLegendFilterByMapEnabled( bool enabled )
292 : : {
293 : 0 : mLegendFilterByMap = enabled;
294 : 0 : updateFilterByMap( false );
295 : 0 : }
296 : :
297 : 0 : void QgsLayoutItemLegend::setTitle( const QString &t )
298 : : {
299 : 0 : mTitle = t;
300 : 0 : mSettings.setTitle( t );
301 : :
302 : 0 : if ( mLayout && id().isEmpty() )
303 : : {
304 : : //notify the model that the display name has changed
305 : 0 : mLayout->itemsModel()->updateItemDisplayName( this );
306 : 0 : }
307 : 0 : }
308 : 0 : QString QgsLayoutItemLegend::title() const
309 : : {
310 : 0 : return mTitle;
311 : : }
312 : :
313 : 0 : Qt::AlignmentFlag QgsLayoutItemLegend::titleAlignment() const
314 : : {
315 : 0 : return mSettings.titleAlignment();
316 : : }
317 : :
318 : 0 : void QgsLayoutItemLegend::setTitleAlignment( Qt::AlignmentFlag alignment )
319 : : {
320 : 0 : mSettings.setTitleAlignment( alignment );
321 : 0 : }
322 : :
323 : 0 : QgsLegendStyle &QgsLayoutItemLegend::rstyle( QgsLegendStyle::Style s )
324 : : {
325 : 0 : return mSettings.rstyle( s );
326 : : }
327 : :
328 : 0 : QgsLegendStyle QgsLayoutItemLegend::style( QgsLegendStyle::Style s ) const
329 : : {
330 : 0 : return mSettings.style( s );
331 : : }
332 : :
333 : 0 : void QgsLayoutItemLegend::setStyle( QgsLegendStyle::Style s, const QgsLegendStyle &style )
334 : : {
335 : 0 : mSettings.setStyle( s, style );
336 : 0 : }
337 : :
338 : 0 : QFont QgsLayoutItemLegend::styleFont( QgsLegendStyle::Style s ) const
339 : : {
340 : 0 : return mSettings.style( s ).font();
341 : 0 : }
342 : :
343 : 0 : void QgsLayoutItemLegend::setStyleFont( QgsLegendStyle::Style s, const QFont &f )
344 : : {
345 : 0 : rstyle( s ).setFont( f );
346 : 0 : }
347 : :
348 : 0 : void QgsLayoutItemLegend::setStyleMargin( QgsLegendStyle::Style s, double margin )
349 : : {
350 : 0 : rstyle( s ).setMargin( margin );
351 : 0 : }
352 : :
353 : 0 : void QgsLayoutItemLegend::setStyleMargin( QgsLegendStyle::Style s, QgsLegendStyle::Side side, double margin )
354 : : {
355 : 0 : rstyle( s ).setMargin( side, margin );
356 : 0 : }
357 : :
358 : 0 : double QgsLayoutItemLegend::lineSpacing() const
359 : : {
360 : 0 : return mSettings.lineSpacing();
361 : : }
362 : :
363 : 0 : void QgsLayoutItemLegend::setLineSpacing( double spacing )
364 : : {
365 : 0 : mSettings.setLineSpacing( spacing );
366 : 0 : }
367 : :
368 : 0 : double QgsLayoutItemLegend::boxSpace() const
369 : : {
370 : 0 : return mSettings.boxSpace();
371 : : }
372 : :
373 : 0 : void QgsLayoutItemLegend::setBoxSpace( double s )
374 : : {
375 : 0 : mSettings.setBoxSpace( s );
376 : 0 : }
377 : :
378 : 0 : double QgsLayoutItemLegend::columnSpace() const
379 : : {
380 : 0 : return mSettings.columnSpace();
381 : : }
382 : :
383 : 0 : void QgsLayoutItemLegend::setColumnSpace( double s )
384 : : {
385 : 0 : mSettings.setColumnSpace( s );
386 : 0 : }
387 : :
388 : 0 : QColor QgsLayoutItemLegend::fontColor() const
389 : : {
390 : 0 : return mSettings.fontColor();
391 : : }
392 : :
393 : 0 : void QgsLayoutItemLegend::setFontColor( const QColor &c )
394 : : {
395 : 0 : mSettings.setFontColor( c );
396 : 0 : }
397 : :
398 : 0 : double QgsLayoutItemLegend::symbolWidth() const
399 : : {
400 : 0 : return mSettings.symbolSize().width();
401 : : }
402 : :
403 : 0 : void QgsLayoutItemLegend::setSymbolWidth( double w )
404 : : {
405 : 0 : mSettings.setSymbolSize( QSizeF( w, mSettings.symbolSize().height() ) );
406 : 0 : }
407 : :
408 : 0 : double QgsLayoutItemLegend::maximumSymbolSize() const
409 : : {
410 : 0 : return mSettings.maximumSymbolSize();
411 : : }
412 : :
413 : 0 : void QgsLayoutItemLegend::setMaximumSymbolSize( double size )
414 : : {
415 : 0 : mSettings.setMaximumSymbolSize( size );
416 : 0 : }
417 : :
418 : 0 : double QgsLayoutItemLegend::minimumSymbolSize() const
419 : : {
420 : 0 : return mSettings.minimumSymbolSize();
421 : : }
422 : :
423 : 0 : void QgsLayoutItemLegend::setMinimumSymbolSize( double size )
424 : : {
425 : 0 : mSettings.setMinimumSymbolSize( size );
426 : 0 : }
427 : :
428 : 0 : void QgsLayoutItemLegend::setSymbolAlignment( Qt::AlignmentFlag alignment )
429 : : {
430 : 0 : mSettings.setSymbolAlignment( alignment );
431 : 0 : }
432 : :
433 : 0 : Qt::AlignmentFlag QgsLayoutItemLegend::symbolAlignment() const
434 : : {
435 : 0 : return mSettings.symbolAlignment();
436 : : }
437 : :
438 : 0 : double QgsLayoutItemLegend::symbolHeight() const
439 : : {
440 : 0 : return mSettings.symbolSize().height();
441 : : }
442 : :
443 : 0 : void QgsLayoutItemLegend::setSymbolHeight( double h )
444 : : {
445 : 0 : mSettings.setSymbolSize( QSizeF( mSettings.symbolSize().width(), h ) );
446 : 0 : }
447 : :
448 : 0 : double QgsLayoutItemLegend::wmsLegendWidth() const
449 : : {
450 : 0 : return mSettings.wmsLegendSize().width();
451 : : }
452 : :
453 : 0 : void QgsLayoutItemLegend::setWmsLegendWidth( double w )
454 : : {
455 : 0 : mSettings.setWmsLegendSize( QSizeF( w, mSettings.wmsLegendSize().height() ) );
456 : 0 : }
457 : :
458 : 0 : double QgsLayoutItemLegend::wmsLegendHeight() const
459 : : {
460 : 0 : return mSettings.wmsLegendSize().height();
461 : : }
462 : 0 : void QgsLayoutItemLegend::setWmsLegendHeight( double h )
463 : : {
464 : 0 : mSettings.setWmsLegendSize( QSizeF( mSettings.wmsLegendSize().width(), h ) );
465 : 0 : }
466 : :
467 : 0 : void QgsLayoutItemLegend::setWrapString( const QString &t )
468 : : {
469 : 0 : mSettings.setWrapChar( t );
470 : 0 : }
471 : :
472 : 0 : QString QgsLayoutItemLegend::wrapString() const
473 : : {
474 : 0 : return mSettings.wrapChar();
475 : : }
476 : :
477 : 0 : int QgsLayoutItemLegend::columnCount() const
478 : : {
479 : 0 : return mColumnCount;
480 : : }
481 : :
482 : 0 : void QgsLayoutItemLegend::setColumnCount( int c )
483 : : {
484 : 0 : mColumnCount = c;
485 : 0 : mSettings.setColumnCount( c );
486 : 0 : }
487 : :
488 : 0 : bool QgsLayoutItemLegend::splitLayer() const
489 : : {
490 : 0 : return mSettings.splitLayer();
491 : : }
492 : :
493 : 0 : void QgsLayoutItemLegend::setSplitLayer( bool s )
494 : : {
495 : 0 : mSettings.setSplitLayer( s );
496 : 0 : }
497 : :
498 : 0 : bool QgsLayoutItemLegend::equalColumnWidth() const
499 : : {
500 : 0 : return mSettings.equalColumnWidth();
501 : : }
502 : :
503 : 0 : void QgsLayoutItemLegend::setEqualColumnWidth( bool s )
504 : : {
505 : 0 : mSettings.setEqualColumnWidth( s );
506 : 0 : }
507 : :
508 : 0 : bool QgsLayoutItemLegend::drawRasterStroke() const
509 : : {
510 : 0 : return mSettings.drawRasterStroke();
511 : : }
512 : :
513 : 0 : void QgsLayoutItemLegend::setDrawRasterStroke( bool enabled )
514 : : {
515 : 0 : mSettings.setDrawRasterStroke( enabled );
516 : 0 : }
517 : :
518 : 0 : QColor QgsLayoutItemLegend::rasterStrokeColor() const
519 : : {
520 : 0 : return mSettings.rasterStrokeColor();
521 : : }
522 : :
523 : 0 : void QgsLayoutItemLegend::setRasterStrokeColor( const QColor &color )
524 : : {
525 : 0 : mSettings.setRasterStrokeColor( color );
526 : 0 : }
527 : :
528 : 0 : double QgsLayoutItemLegend::rasterStrokeWidth() const
529 : : {
530 : 0 : return mSettings.rasterStrokeWidth();
531 : : }
532 : :
533 : 0 : void QgsLayoutItemLegend::setRasterStrokeWidth( double width )
534 : : {
535 : 0 : mSettings.setRasterStrokeWidth( width );
536 : 0 : }
537 : :
538 : :
539 : 0 : void QgsLayoutItemLegend::updateLegend()
540 : : {
541 : 0 : adjustBoxSize();
542 : 0 : updateFilterByMap( false );
543 : 0 : }
544 : :
545 : 0 : bool QgsLayoutItemLegend::writePropertiesToElement( QDomElement &legendElem, QDomDocument &doc, const QgsReadWriteContext &context ) const
546 : : {
547 : :
548 : : //write general properties
549 : 0 : legendElem.setAttribute( QStringLiteral( "title" ), mTitle );
550 : 0 : legendElem.setAttribute( QStringLiteral( "titleAlignment" ), QString::number( static_cast< int >( mSettings.titleAlignment() ) ) );
551 : 0 : legendElem.setAttribute( QStringLiteral( "columnCount" ), QString::number( mColumnCount ) );
552 : 0 : legendElem.setAttribute( QStringLiteral( "splitLayer" ), QString::number( mSettings.splitLayer() ) );
553 : 0 : legendElem.setAttribute( QStringLiteral( "equalColumnWidth" ), QString::number( mSettings.equalColumnWidth() ) );
554 : :
555 : 0 : legendElem.setAttribute( QStringLiteral( "boxSpace" ), QString::number( mSettings.boxSpace() ) );
556 : 0 : legendElem.setAttribute( QStringLiteral( "columnSpace" ), QString::number( mSettings.columnSpace() ) );
557 : :
558 : 0 : legendElem.setAttribute( QStringLiteral( "symbolWidth" ), QString::number( mSettings.symbolSize().width() ) );
559 : 0 : legendElem.setAttribute( QStringLiteral( "symbolHeight" ), QString::number( mSettings.symbolSize().height() ) );
560 : 0 : legendElem.setAttribute( QStringLiteral( "maxSymbolSize" ), QString::number( mSettings.maximumSymbolSize() ) );
561 : 0 : legendElem.setAttribute( QStringLiteral( "minSymbolSize" ), QString::number( mSettings.minimumSymbolSize() ) );
562 : :
563 : 0 : legendElem.setAttribute( QStringLiteral( "symbolAlignment" ), mSettings.symbolAlignment() );
564 : :
565 : 0 : legendElem.setAttribute( QStringLiteral( "symbolAlignment" ), mSettings.symbolAlignment() );
566 : 0 : legendElem.setAttribute( QStringLiteral( "lineSpacing" ), QString::number( mSettings.lineSpacing() ) );
567 : :
568 : 0 : legendElem.setAttribute( QStringLiteral( "rasterBorder" ), mSettings.drawRasterStroke() );
569 : 0 : legendElem.setAttribute( QStringLiteral( "rasterBorderColor" ), QgsSymbolLayerUtils::encodeColor( mSettings.rasterStrokeColor() ) );
570 : 0 : legendElem.setAttribute( QStringLiteral( "rasterBorderWidth" ), QString::number( mSettings.rasterStrokeWidth() ) );
571 : :
572 : 0 : legendElem.setAttribute( QStringLiteral( "wmsLegendWidth" ), QString::number( mSettings.wmsLegendSize().width() ) );
573 : 0 : legendElem.setAttribute( QStringLiteral( "wmsLegendHeight" ), QString::number( mSettings.wmsLegendSize().height() ) );
574 : 0 : legendElem.setAttribute( QStringLiteral( "wrapChar" ), mSettings.wrapChar() );
575 : 0 : legendElem.setAttribute( QStringLiteral( "fontColor" ), mSettings.fontColor().name() );
576 : :
577 : 0 : legendElem.setAttribute( QStringLiteral( "resizeToContents" ), mSizeToContents );
578 : :
579 : 0 : if ( mMap )
580 : : {
581 : 0 : legendElem.setAttribute( QStringLiteral( "map_uuid" ), mMap->uuid() );
582 : 0 : }
583 : :
584 : 0 : QDomElement legendStyles = doc.createElement( QStringLiteral( "styles" ) );
585 : 0 : legendElem.appendChild( legendStyles );
586 : :
587 : 0 : style( QgsLegendStyle::Title ).writeXml( QStringLiteral( "title" ), legendStyles, doc );
588 : 0 : style( QgsLegendStyle::Group ).writeXml( QStringLiteral( "group" ), legendStyles, doc );
589 : 0 : style( QgsLegendStyle::Subgroup ).writeXml( QStringLiteral( "subgroup" ), legendStyles, doc );
590 : 0 : style( QgsLegendStyle::Symbol ).writeXml( QStringLiteral( "symbol" ), legendStyles, doc );
591 : 0 : style( QgsLegendStyle::SymbolLabel ).writeXml( QStringLiteral( "symbolLabel" ), legendStyles, doc );
592 : :
593 : 0 : if ( mCustomLayerTree )
594 : : {
595 : : // if not using auto-update - store the custom layer tree
596 : 0 : mCustomLayerTree->writeXml( legendElem, context );
597 : 0 : }
598 : :
599 : 0 : if ( mLegendFilterByMap )
600 : : {
601 : 0 : legendElem.setAttribute( QStringLiteral( "legendFilterByMap" ), QStringLiteral( "1" ) );
602 : 0 : }
603 : 0 : legendElem.setAttribute( QStringLiteral( "legendFilterByAtlas" ), mFilterOutAtlas ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
604 : :
605 : : return true;
606 : 0 : }
607 : :
608 : 0 : bool QgsLayoutItemLegend::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context )
609 : : {
610 : : //read general properties
611 : 0 : mTitle = itemElem.attribute( QStringLiteral( "title" ) );
612 : 0 : mSettings.setTitle( mTitle );
613 : 0 : if ( !itemElem.attribute( QStringLiteral( "titleAlignment" ) ).isEmpty() )
614 : 0 : {
615 : 0 : mSettings.setTitleAlignment( static_cast< Qt::AlignmentFlag >( itemElem.attribute( QStringLiteral( "titleAlignment" ) ).toInt() ) );
616 : 0 : }
617 : 0 : int colCount = itemElem.attribute( QStringLiteral( "columnCount" ), QStringLiteral( "1" ) ).toInt();
618 : 0 : if ( colCount < 1 ) colCount = 1;
619 : 0 : mColumnCount = colCount;
620 : 0 : mSettings.setColumnCount( mColumnCount );
621 : 0 : mSettings.setSplitLayer( itemElem.attribute( QStringLiteral( "splitLayer" ), QStringLiteral( "0" ) ).toInt() == 1 );
622 : 0 : mSettings.setEqualColumnWidth( itemElem.attribute( QStringLiteral( "equalColumnWidth" ), QStringLiteral( "0" ) ).toInt() == 1 );
623 : :
624 : 0 : QDomNodeList stylesNodeList = itemElem.elementsByTagName( QStringLiteral( "styles" ) );
625 : 0 : if ( !stylesNodeList.isEmpty() )
626 : : {
627 : 0 : QDomNode stylesNode = stylesNodeList.at( 0 );
628 : 0 : for ( int i = 0; i < stylesNode.childNodes().size(); i++ )
629 : : {
630 : 0 : QDomElement styleElem = stylesNode.childNodes().at( i ).toElement();
631 : 0 : QgsLegendStyle style;
632 : 0 : style.readXml( styleElem, doc, context );
633 : 0 : QString name = styleElem.attribute( QStringLiteral( "name" ) );
634 : : QgsLegendStyle::Style s;
635 : 0 : if ( name == QLatin1String( "title" ) ) s = QgsLegendStyle::Title;
636 : 0 : else if ( name == QLatin1String( "group" ) ) s = QgsLegendStyle::Group;
637 : 0 : else if ( name == QLatin1String( "subgroup" ) ) s = QgsLegendStyle::Subgroup;
638 : 0 : else if ( name == QLatin1String( "symbol" ) ) s = QgsLegendStyle::Symbol;
639 : 0 : else if ( name == QLatin1String( "symbolLabel" ) ) s = QgsLegendStyle::SymbolLabel;
640 : 0 : else continue;
641 : 0 : setStyle( s, style );
642 : 0 : }
643 : 0 : }
644 : :
645 : : //font color
646 : 0 : QColor fontClr;
647 : 0 : fontClr.setNamedColor( itemElem.attribute( QStringLiteral( "fontColor" ), QStringLiteral( "#000000" ) ) );
648 : 0 : mSettings.setFontColor( fontClr );
649 : :
650 : : //spaces
651 : 0 : mSettings.setBoxSpace( itemElem.attribute( QStringLiteral( "boxSpace" ), QStringLiteral( "2.0" ) ).toDouble() );
652 : 0 : mSettings.setColumnSpace( itemElem.attribute( QStringLiteral( "columnSpace" ), QStringLiteral( "2.0" ) ).toDouble() );
653 : :
654 : 0 : mSettings.setSymbolSize( QSizeF( itemElem.attribute( QStringLiteral( "symbolWidth" ), QStringLiteral( "7.0" ) ).toDouble(), itemElem.attribute( QStringLiteral( "symbolHeight" ), QStringLiteral( "14.0" ) ).toDouble() ) );
655 : 0 : mSettings.setSymbolAlignment( static_cast< Qt::AlignmentFlag >( itemElem.attribute( QStringLiteral( "symbolAlignment" ), QString::number( Qt::AlignLeft ) ).toInt() ) );
656 : :
657 : 0 : mSettings.setMaximumSymbolSize( itemElem.attribute( QStringLiteral( "maxSymbolSize" ), QStringLiteral( "0.0" ) ).toDouble() );
658 : 0 : mSettings.setMinimumSymbolSize( itemElem.attribute( QStringLiteral( "minSymbolSize" ), QStringLiteral( "0.0" ) ).toDouble() );
659 : :
660 : 0 : mSettings.setWmsLegendSize( QSizeF( itemElem.attribute( QStringLiteral( "wmsLegendWidth" ), QStringLiteral( "50" ) ).toDouble(), itemElem.attribute( QStringLiteral( "wmsLegendHeight" ), QStringLiteral( "25" ) ).toDouble() ) );
661 : 0 : mSettings.setLineSpacing( itemElem.attribute( QStringLiteral( "lineSpacing" ), QStringLiteral( "1.0" ) ).toDouble() );
662 : :
663 : 0 : mSettings.setDrawRasterStroke( itemElem.attribute( QStringLiteral( "rasterBorder" ), QStringLiteral( "1" ) ) != QLatin1String( "0" ) );
664 : 0 : mSettings.setRasterStrokeColor( QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "rasterBorderColor" ), QStringLiteral( "0,0,0" ) ) ) );
665 : 0 : mSettings.setRasterStrokeWidth( itemElem.attribute( QStringLiteral( "rasterBorderWidth" ), QStringLiteral( "0" ) ).toDouble() );
666 : :
667 : 0 : mSettings.setWrapChar( itemElem.attribute( QStringLiteral( "wrapChar" ) ) );
668 : :
669 : 0 : mSizeToContents = itemElem.attribute( QStringLiteral( "resizeToContents" ), QStringLiteral( "1" ) ) != QLatin1String( "0" );
670 : :
671 : : // map
672 : 0 : mLegendFilterByMap = itemElem.attribute( QStringLiteral( "legendFilterByMap" ), QStringLiteral( "0" ) ).toInt();
673 : :
674 : 0 : mMapUuid.clear();
675 : 0 : if ( !itemElem.attribute( QStringLiteral( "map_uuid" ) ).isEmpty() )
676 : : {
677 : 0 : mMapUuid = itemElem.attribute( QStringLiteral( "map_uuid" ) );
678 : 0 : }
679 : : // disconnect current map
680 : 0 : setupMapConnections( mMap, false );
681 : 0 : mMap = nullptr;
682 : :
683 : 0 : mFilterOutAtlas = itemElem.attribute( QStringLiteral( "legendFilterByAtlas" ), QStringLiteral( "0" ) ).toInt();
684 : :
685 : : // QGIS >= 2.6
686 : 0 : QDomElement layerTreeElem = itemElem.firstChildElement( QStringLiteral( "layer-tree" ) );
687 : 0 : if ( layerTreeElem.isNull() )
688 : 0 : layerTreeElem = itemElem.firstChildElement( QStringLiteral( "layer-tree-group" ) );
689 : :
690 : 0 : if ( !layerTreeElem.isNull() )
691 : : {
692 : 0 : std::unique_ptr< QgsLayerTree > tree( QgsLayerTree::readXml( layerTreeElem, context ) );
693 : 0 : if ( mLayout )
694 : 0 : tree->resolveReferences( mLayout->project(), true );
695 : 0 : setCustomLayerTree( tree.release() );
696 : 0 : }
697 : : else
698 : 0 : setCustomLayerTree( nullptr );
699 : :
700 : : return true;
701 : 0 : }
702 : :
703 : 0 : QString QgsLayoutItemLegend::displayName() const
704 : : {
705 : 0 : if ( !id().isEmpty() )
706 : : {
707 : 0 : return id();
708 : : }
709 : :
710 : : //if no id, default to portion of title text
711 : 0 : QString text = mSettings.title();
712 : 0 : if ( text.isEmpty() )
713 : : {
714 : 0 : return tr( "<Legend>" );
715 : : }
716 : 0 : if ( text.length() > 25 )
717 : : {
718 : 0 : return tr( "%1…" ).arg( text.left( 25 ) );
719 : : }
720 : : else
721 : : {
722 : 0 : return text;
723 : : }
724 : 0 : }
725 : :
726 : :
727 : 0 : void QgsLayoutItemLegend::setupMapConnections( QgsLayoutItemMap *map, bool connectSlots )
728 : : {
729 : 0 : if ( !map )
730 : 0 : return;
731 : :
732 : 0 : if ( !connectSlots )
733 : : {
734 : 0 : disconnect( map, &QObject::destroyed, this, &QgsLayoutItemLegend::invalidateCurrentMap );
735 : 0 : disconnect( map, &QgsLayoutObject::changed, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
736 : 0 : disconnect( map, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
737 : 0 : disconnect( map, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
738 : 0 : disconnect( map, &QgsLayoutItemMap::layerStyleOverridesChanged, this, &QgsLayoutItemLegend::mapLayerStyleOverridesChanged );
739 : 0 : disconnect( map, &QgsLayoutItemMap::themeChanged, this, &QgsLayoutItemLegend::mapThemeChanged );
740 : 0 : }
741 : : else
742 : : {
743 : 0 : connect( map, &QObject::destroyed, this, &QgsLayoutItemLegend::invalidateCurrentMap );
744 : 0 : connect( map, &QgsLayoutObject::changed, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
745 : 0 : connect( map, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
746 : 0 : connect( map, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
747 : 0 : connect( map, &QgsLayoutItemMap::layerStyleOverridesChanged, this, &QgsLayoutItemLegend::mapLayerStyleOverridesChanged );
748 : 0 : connect( map, &QgsLayoutItemMap::themeChanged, this, &QgsLayoutItemLegend::mapThemeChanged );
749 : : }
750 : 0 : }
751 : :
752 : 0 : void QgsLayoutItemLegend::setLinkedMap( QgsLayoutItemMap *map )
753 : : {
754 : 0 : if ( mMap )
755 : : {
756 : 0 : setupMapConnections( mMap, false );
757 : 0 : }
758 : :
759 : 0 : mMap = map;
760 : :
761 : 0 : if ( mMap )
762 : : {
763 : 0 : setupMapConnections( mMap, true );
764 : 0 : mapThemeChanged( mMap->themeToRender( mMap->createExpressionContext() ) );
765 : 0 : }
766 : :
767 : 0 : updateFilterByMap();
768 : 0 : }
769 : :
770 : 0 : void QgsLayoutItemLegend::invalidateCurrentMap()
771 : : {
772 : 0 : setLinkedMap( nullptr );
773 : 0 : }
774 : :
775 : 0 : void QgsLayoutItemLegend::refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property )
776 : : {
777 : 0 : QgsExpressionContext context = createExpressionContext();
778 : :
779 : 0 : bool forceUpdate = false;
780 : : //updates data defined properties and redraws item to match
781 : 0 : if ( property == QgsLayoutObject::LegendTitle || property == QgsLayoutObject::AllProperties )
782 : : {
783 : 0 : bool ok = false;
784 : 0 : QString t = mDataDefinedProperties.valueAsString( QgsLayoutObject::LegendTitle, context, mTitle, &ok );
785 : 0 : if ( ok )
786 : : {
787 : 0 : mSettings.setTitle( t );
788 : 0 : forceUpdate = true;
789 : 0 : }
790 : 0 : }
791 : 0 : if ( property == QgsLayoutObject::LegendColumnCount || property == QgsLayoutObject::AllProperties )
792 : : {
793 : 0 : bool ok = false;
794 : 0 : int cols = mDataDefinedProperties.valueAsInt( QgsLayoutObject::LegendColumnCount, context, mColumnCount, &ok );
795 : 0 : if ( ok && cols >= 0 )
796 : : {
797 : 0 : mSettings.setColumnCount( cols );
798 : 0 : forceUpdate = true;
799 : 0 : }
800 : 0 : }
801 : 0 : if ( forceUpdate )
802 : : {
803 : 0 : adjustBoxSize();
804 : 0 : update();
805 : 0 : }
806 : :
807 : 0 : QgsLayoutItem::refreshDataDefinedProperty( property );
808 : 0 : }
809 : :
810 : :
811 : 0 : void QgsLayoutItemLegend::updateFilterByMapAndRedraw()
812 : : {
813 : 0 : updateFilterByMap( true );
814 : 0 : }
815 : :
816 : 0 : void QgsLayoutItemLegend::setModelStyleOverrides( const QMap<QString, QString> &overrides )
817 : : {
818 : 0 : mLegendModel->setLayerStyleOverrides( overrides );
819 : 0 : const QList< QgsLayerTreeLayer * > layers = mLegendModel->rootGroup()->findLayers();
820 : 0 : for ( QgsLayerTreeLayer *nodeLayer : layers )
821 : 0 : mLegendModel->refreshLayerLegend( nodeLayer );
822 : :
823 : 0 : }
824 : :
825 : 0 : void QgsLayoutItemLegend::clearLegendCachedData()
826 : : {
827 : 0 : std::function< void( QgsLayerTreeNode * ) > clearNodeCache;
828 : 0 : clearNodeCache = [&]( QgsLayerTreeNode * node )
829 : : {
830 : 0 : mLegendModel->clearCachedData( node );
831 : 0 : if ( QgsLayerTree::isGroup( node ) )
832 : : {
833 : 0 : QgsLayerTreeGroup *group = QgsLayerTree::toGroup( node );
834 : 0 : const QList< QgsLayerTreeNode * > children = group->children();
835 : 0 : for ( QgsLayerTreeNode *child : children )
836 : : {
837 : 0 : clearNodeCache( child );
838 : : }
839 : 0 : }
840 : 0 : };
841 : :
842 : 0 : clearNodeCache( mLegendModel->rootGroup() );
843 : 0 : }
844 : :
845 : 0 : void QgsLayoutItemLegend::mapLayerStyleOverridesChanged()
846 : : {
847 : 0 : if ( !mMap )
848 : 0 : return;
849 : :
850 : : // map's style has been changed, so make sure to update the legend here
851 : 0 : if ( mLegendFilterByMap )
852 : : {
853 : : // legend is being filtered by map, so we need to re run the hit test too
854 : : // as the style overrides may also have affected the visible symbols
855 : 0 : updateFilterByMap( false );
856 : 0 : }
857 : : else
858 : : {
859 : 0 : setModelStyleOverrides( mMap->layerStyleOverrides() );
860 : : }
861 : :
862 : 0 : adjustBoxSize();
863 : :
864 : 0 : updateFilterByMap( false );
865 : 0 : }
866 : :
867 : 0 : void QgsLayoutItemLegend::mapThemeChanged( const QString &theme )
868 : : {
869 : 0 : if ( mThemeName == theme )
870 : 0 : return;
871 : :
872 : 0 : mThemeName = theme;
873 : :
874 : : // map's theme has been changed, so make sure to update the legend here
875 : 0 : if ( mLegendFilterByMap )
876 : : {
877 : : // legend is being filtered by map, so we need to re run the hit test too
878 : : // as the style overrides may also have affected the visible symbols
879 : 0 : updateFilterByMap( false );
880 : 0 : }
881 : : else
882 : : {
883 : 0 : if ( mThemeName.isEmpty() )
884 : : {
885 : 0 : setModelStyleOverrides( QMap<QString, QString>() );
886 : 0 : }
887 : : else
888 : : {
889 : : // get style overrides for theme
890 : 0 : const QMap<QString, QString> overrides = mLayout->project()->mapThemeCollection()->mapThemeStyleOverrides( mThemeName );
891 : 0 : setModelStyleOverrides( overrides );
892 : 0 : }
893 : : }
894 : :
895 : 0 : adjustBoxSize();
896 : :
897 : 0 : updateFilterByMap();
898 : 0 : }
899 : :
900 : 0 : void QgsLayoutItemLegend::updateFilterByMap( bool redraw )
901 : : {
902 : : // ask for update
903 : : // the actual update will take place before the redraw.
904 : : // This is to avoid multiple calls to the filter
905 : 0 : mFilterAskedForUpdate = true;
906 : :
907 : 0 : if ( redraw )
908 : 0 : update();
909 : 0 : }
910 : :
911 : 0 : void QgsLayoutItemLegend::doUpdateFilterByMap()
912 : : {
913 : 0 : if ( mMap )
914 : : {
915 : 0 : if ( !mThemeName.isEmpty() )
916 : : {
917 : : // get style overrides for theme
918 : 0 : const QMap<QString, QString> overrides = mLayout->project()->mapThemeCollection()->mapThemeStyleOverrides( mThemeName );
919 : 0 : mLegendModel->setLayerStyleOverrides( overrides );
920 : 0 : }
921 : : else
922 : : {
923 : 0 : mLegendModel->setLayerStyleOverrides( mMap->layerStyleOverrides() );
924 : : }
925 : 0 : }
926 : : else
927 : 0 : mLegendModel->setLayerStyleOverrides( QMap<QString, QString>() );
928 : :
929 : :
930 : 0 : bool filterByExpression = QgsLayerTreeUtils::hasLegendFilterExpression( *( mCustomLayerTree ? mCustomLayerTree.get() : mLayout->project()->layerTreeRoot() ) );
931 : :
932 : 0 : if ( mMap && ( mLegendFilterByMap || filterByExpression || mInAtlas ) )
933 : : {
934 : 0 : double dpi = mLayout->renderContext().dpi();
935 : :
936 : 0 : QgsRectangle requestRectangle = mMap->requestedExtent();
937 : :
938 : 0 : QSizeF size( requestRectangle.width(), requestRectangle.height() );
939 : 0 : size *= mLayout->convertFromLayoutUnits( mMap->mapUnitsToLayoutUnits(), QgsUnitTypes::LayoutMillimeters ).length() * dpi / 25.4;
940 : :
941 : 0 : QgsMapSettings ms = mMap->mapSettings( requestRectangle, size, dpi, true );
942 : :
943 : 0 : QgsGeometry filterPolygon;
944 : 0 : if ( mInAtlas )
945 : : {
946 : 0 : filterPolygon = mLayout->reportContext().currentGeometry( mMap->crs() );
947 : 0 : }
948 : 0 : mLegendModel->setLegendFilter( &ms, /* useExtent */ mInAtlas || mLegendFilterByMap, filterPolygon, /* useExpressions */ true );
949 : 0 : }
950 : : else
951 : 0 : mLegendModel->setLegendFilterByMap( nullptr );
952 : :
953 : 0 : clearLegendCachedData();
954 : 0 : mForceResize = true;
955 : 0 : }
956 : :
957 : 0 : QString QgsLayoutItemLegend::themeName() const
958 : : {
959 : 0 : return mThemeName;
960 : : }
961 : :
962 : 0 : void QgsLayoutItemLegend::setLegendFilterOutAtlas( bool doFilter )
963 : : {
964 : 0 : mFilterOutAtlas = doFilter;
965 : 0 : }
966 : :
967 : 0 : bool QgsLayoutItemLegend::legendFilterOutAtlas() const
968 : : {
969 : 0 : return mFilterOutAtlas;
970 : : }
971 : :
972 : 0 : void QgsLayoutItemLegend::onAtlasFeature()
973 : : {
974 : 0 : if ( !mLayout->reportContext().feature().isValid() )
975 : 0 : return;
976 : 0 : mInAtlas = mFilterOutAtlas;
977 : 0 : updateFilterByMap();
978 : 0 : }
979 : :
980 : 0 : void QgsLayoutItemLegend::onAtlasEnded()
981 : : {
982 : 0 : mInAtlas = false;
983 : 0 : updateFilterByMap();
984 : 0 : }
985 : :
986 : 0 : QgsExpressionContext QgsLayoutItemLegend::createExpressionContext() const
987 : : {
988 : 0 : QgsExpressionContext context = QgsLayoutItem::createExpressionContext();
989 : :
990 : : // We only want the last scope from the map's expression context, as this contains
991 : : // the map specific variables. We don't want the rest of the map's context, because that
992 : : // will contain duplicate global, project, layout, etc scopes.
993 : 0 : if ( mMap )
994 : 0 : context.appendScope( mMap->createExpressionContext().popScope() );
995 : :
996 : 0 : QgsExpressionContextScope *scope = new QgsExpressionContextScope( tr( "Legend Settings" ) );
997 : :
998 : 0 : scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_title" ), title(), true ) );
999 : 0 : scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_column_count" ), columnCount(), true ) );
1000 : 0 : scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_split_layers" ), splitLayer(), true ) );
1001 : 0 : scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_wrap_string" ), wrapString(), true ) );
1002 : 0 : scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_filter_by_map" ), legendFilterByMapEnabled(), true ) );
1003 : 0 : scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_filter_out_atlas" ), legendFilterOutAtlas(), true ) );
1004 : :
1005 : 0 : context.appendScope( scope );
1006 : 0 : return context;
1007 : 0 : }
1008 : :
1009 : 0 : QgsLayoutItem::ExportLayerBehavior QgsLayoutItemLegend::exportLayerBehavior() const
1010 : : {
1011 : 0 : return MustPlaceInOwnLayer;
1012 : : }
1013 : :
1014 : 0 : bool QgsLayoutItemLegend::accept( QgsStyleEntityVisitorInterface *visitor ) const
1015 : : {
1016 : 0 : std::function<bool( QgsLayerTreeGroup *group ) >visit;
1017 : :
1018 : 0 : visit = [ =, &visit]( QgsLayerTreeGroup * group ) -> bool
1019 : : {
1020 : 0 : const QList<QgsLayerTreeNode *> childNodes = group->children();
1021 : 0 : for ( QgsLayerTreeNode *node : childNodes )
1022 : : {
1023 : 0 : if ( QgsLayerTree::isGroup( node ) )
1024 : : {
1025 : 0 : QgsLayerTreeGroup *nodeGroup = QgsLayerTree::toGroup( node );
1026 : 0 : if ( !visit( nodeGroup ) )
1027 : 0 : return false;
1028 : 0 : }
1029 : 0 : else if ( QgsLayerTree::isLayer( node ) )
1030 : : {
1031 : 0 : QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
1032 : 0 : if ( !nodeLayer->patchShape().isNull() )
1033 : : {
1034 : 0 : QgsStyleLegendPatchShapeEntity entity( nodeLayer->patchShape() );
1035 : 0 : if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, uuid(), displayName() ) ) )
1036 : 0 : return false;
1037 : 0 : }
1038 : 0 : const QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->layerLegendNodes( nodeLayer );
1039 : 0 : for ( QgsLayerTreeModelLegendNode *legendNode : legendNodes )
1040 : : {
1041 : 0 : if ( QgsSymbolLegendNode *symbolNode = dynamic_cast< QgsSymbolLegendNode * >( legendNode ) )
1042 : : {
1043 : 0 : if ( !symbolNode->patchShape().isNull() )
1044 : : {
1045 : 0 : QgsStyleLegendPatchShapeEntity entity( symbolNode->patchShape() );
1046 : 0 : if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, uuid(), displayName() ) ) )
1047 : 0 : return false;
1048 : 0 : }
1049 : 0 : }
1050 : : }
1051 : 0 : }
1052 : : }
1053 : 0 : return true;
1054 : 0 : };
1055 : 0 : return visit( mLegendModel->rootGroup( ) );
1056 : 0 : }
1057 : :
1058 : :
1059 : : // -------------------------------------------------------------------------
1060 : : #include "qgslayertreemodellegendnode.h"
1061 : : #include "qgsvectorlayer.h"
1062 : : #include "qgsmaplayerlegend.h"
1063 : :
1064 : 0 : QgsLegendModel::QgsLegendModel( QgsLayerTree *rootNode, QObject *parent, QgsLayoutItemLegend *layout )
1065 : 0 : : QgsLayerTreeModel( rootNode, parent )
1066 : 0 : , mLayoutLegend( layout )
1067 : 0 : {
1068 : 0 : setFlag( QgsLayerTreeModel::AllowLegendChangeState, false );
1069 : 0 : setFlag( QgsLayerTreeModel::AllowNodeReorder, true );
1070 : 0 : }
1071 : :
1072 : 0 : QgsLegendModel::QgsLegendModel( QgsLayerTree *rootNode, QgsLayoutItemLegend *layout )
1073 : 0 : : QgsLayerTreeModel( rootNode )
1074 : 0 : , mLayoutLegend( layout )
1075 : 0 : {
1076 : 0 : setFlag( QgsLayerTreeModel::AllowLegendChangeState, false );
1077 : 0 : setFlag( QgsLayerTreeModel::AllowNodeReorder, true );
1078 : 0 : }
1079 : :
1080 : 0 : QVariant QgsLegendModel::data( const QModelIndex &index, int role ) const
1081 : : {
1082 : : // handle custom layer node labels
1083 : :
1084 : 0 : QgsLayerTreeNode *node = index2node( index );
1085 : 0 : QgsLayerTreeLayer *nodeLayer = QgsLayerTree::isLayer( node ) ? QgsLayerTree::toLayer( node ) : nullptr;
1086 : 0 : if ( nodeLayer && ( role == Qt::DisplayRole || role == Qt::EditRole ) )
1087 : : {
1088 : 0 : QString name = node->customProperty( QStringLiteral( "cached_name" ) ).toString();
1089 : 0 : if ( !name.isEmpty() )
1090 : 0 : return name;
1091 : :
1092 : 0 : QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( nodeLayer->layer() );
1093 : :
1094 : : //finding the first label that is stored
1095 : 0 : name = nodeLayer->customProperty( QStringLiteral( "legend/title-label" ) ).toString();
1096 : 0 : if ( name.isEmpty() )
1097 : 0 : name = nodeLayer->name();
1098 : 0 : if ( name.isEmpty() )
1099 : 0 : name = node->customProperty( QStringLiteral( "legend/title-label" ) ).toString();
1100 : 0 : if ( name.isEmpty() )
1101 : 0 : name = node->name();
1102 : 0 : if ( nodeLayer->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toInt() )
1103 : : {
1104 : 0 : if ( vlayer && vlayer->featureCount() >= 0 )
1105 : : {
1106 : 0 : name += QStringLiteral( " [%1]" ).arg( vlayer->featureCount() );
1107 : 0 : node->setCustomProperty( QStringLiteral( "cached_name" ), name );
1108 : 0 : return name;
1109 : : }
1110 : 0 : }
1111 : :
1112 : 0 : const bool evaluate = ( vlayer && !nodeLayer->labelExpression().isEmpty() ) || name.contains( "[%" );
1113 : 0 : if ( evaluate )
1114 : : {
1115 : 0 : QgsExpressionContext expressionContext;
1116 : 0 : if ( vlayer )
1117 : : {
1118 : 0 : connect( vlayer, &QgsVectorLayer::symbolFeatureCountMapChanged, this, &QgsLegendModel::forceRefresh, Qt::UniqueConnection );
1119 : : // counting is done here to ensure that a valid vector layer needs to be evaluated, count is used to validate previous count or update the count if invalidated
1120 : 0 : vlayer->countSymbolFeatures();
1121 : 0 : }
1122 : :
1123 : 0 : if ( mLayoutLegend )
1124 : 0 : expressionContext = mLayoutLegend->createExpressionContext();
1125 : :
1126 : 0 : const QList<QgsLayerTreeModelLegendNode *> legendnodes = layerLegendNodes( nodeLayer, false );
1127 : 0 : if ( legendnodes.count() > 1 ) // evaluate all existing legend nodes but leave the name for the legend evaluator
1128 : : {
1129 : 0 : for ( QgsLayerTreeModelLegendNode *treenode : legendnodes )
1130 : : {
1131 : 0 : if ( QgsSymbolLegendNode *symnode = qobject_cast<QgsSymbolLegendNode *>( treenode ) )
1132 : 0 : symnode->evaluateLabel( expressionContext );
1133 : : }
1134 : 0 : }
1135 : 0 : else if ( QgsSymbolLegendNode *symnode = qobject_cast<QgsSymbolLegendNode *>( legendnodes.first() ) )
1136 : 0 : name = symnode->evaluateLabel( expressionContext );
1137 : 0 : }
1138 : 0 : node->setCustomProperty( QStringLiteral( "cached_name" ), name );
1139 : 0 : return name;
1140 : 0 : }
1141 : 0 : return QgsLayerTreeModel::data( index, role );
1142 : 0 : }
1143 : :
1144 : 0 : Qt::ItemFlags QgsLegendModel::flags( const QModelIndex &index ) const
1145 : : {
1146 : : // make the legend nodes selectable even if they are not by default
1147 : 0 : if ( index2legendNode( index ) )
1148 : 0 : return QgsLayerTreeModel::flags( index ) | Qt::ItemIsSelectable;
1149 : :
1150 : 0 : return QgsLayerTreeModel::flags( index );
1151 : 0 : }
1152 : :
1153 : 0 : QList<QgsLayerTreeModelLegendNode *> QgsLegendModel::layerLegendNodes( QgsLayerTreeLayer *nodeLayer, bool skipNodeEmbeddedInParent ) const
1154 : : {
1155 : 0 : if ( !mLegend.contains( nodeLayer ) )
1156 : 0 : return QList<QgsLayerTreeModelLegendNode *>();
1157 : :
1158 : 0 : const LayerLegendData &data = mLegend[nodeLayer];
1159 : 0 : QList<QgsLayerTreeModelLegendNode *> lst( data.activeNodes );
1160 : 0 : if ( !skipNodeEmbeddedInParent && data.embeddedNodeInParent )
1161 : 0 : lst.prepend( data.embeddedNodeInParent );
1162 : 0 : return lst;
1163 : 0 : }
1164 : :
1165 : 0 : void QgsLegendModel::clearCachedData( QgsLayerTreeNode *node ) const
1166 : : {
1167 : 0 : node->removeCustomProperty( QStringLiteral( "cached_name" ) );
1168 : 0 : }
1169 : :
1170 : 0 : void QgsLegendModel::forceRefresh()
1171 : : {
1172 : 0 : emit refreshLegend();
1173 : 0 : }
1174 : :
1175 : :
|