1 /***************************************************************************
2   qgslayertreemodellegendnode.cpp
3   --------------------------------------
4   Date                 : August 2014
5   Copyright            : (C) 2014 by Martin Dobias
6   Email                : wonder dot sk at gmail dot com
7 
8   QgsWMSLegendNode     : Sandro Santilli < strk at keybit dot net >
9 
10  ***************************************************************************
11  *                                                                         *
12  *   This program is free software; you can redistribute it and/or modify  *
13  *   it under the terms of the GNU General Public License as published by  *
14  *   the Free Software Foundation; either version 2 of the License, or     *
15  *   (at your option) any later version.                                   *
16  *                                                                         *
17  ***************************************************************************/
18 
19 #include "qgslayertreemodellegendnode.h"
20 
21 #include "qgsdatadefinedsizelegend.h"
22 #include "qgslayertree.h"
23 #include "qgslayertreemodel.h"
24 #include "qgslegendsettings.h"
25 #include "qgsrasterlayer.h"
26 #include "qgsrenderer.h"
27 #include "qgssymbollayerutils.h"
28 #include "qgsimageoperation.h"
29 #include "qgsvectorlayer.h"
30 #include "qgspointcloudlayer.h"
31 #include "qgspointcloudrenderer.h"
32 #include "qgsrasterrenderer.h"
33 #include "qgsexpressioncontextutils.h"
34 #include "qgsfeatureid.h"
35 #include "qgslayoutitem.h"
36 #include "qgsvectorlayerfeaturecounter.h"
37 #include "qgsexpression.h"
38 #include "qgstextrenderer.h"
39 #include "qgssettings.h"
40 #include "qgsfileutils.h"
41 #include "qgsmarkersymbol.h"
42 
43 #include <QBuffer>
44 
QgsLayerTreeModelLegendNode(QgsLayerTreeLayer * nodeL,QObject * parent)45 QgsLayerTreeModelLegendNode::QgsLayerTreeModelLegendNode( QgsLayerTreeLayer *nodeL, QObject *parent )
46   : QObject( parent )
47   , mLayerNode( nodeL )
48   , mEmbeddedInParent( false )
49 {
50 }
51 
model() const52 QgsLayerTreeModel *QgsLayerTreeModelLegendNode::model() const
53 {
54   return qobject_cast<QgsLayerTreeModel *>( parent() );
55 }
56 
flags() const57 Qt::ItemFlags QgsLayerTreeModelLegendNode::flags() const
58 {
59   return Qt::ItemIsEnabled;
60 }
61 
setData(const QVariant & value,int role)62 bool QgsLayerTreeModelLegendNode::setData( const QVariant &value, int role )
63 {
64   Q_UNUSED( value )
65   Q_UNUSED( role )
66   return false;
67 }
68 
userPatchSize() const69 QSizeF QgsLayerTreeModelLegendNode::userPatchSize() const
70 {
71   if ( mEmbeddedInParent )
72     return mLayerNode->patchSize();
73 
74   return mUserSize;
75 }
76 
setUserPatchSize(QSizeF size)77 void QgsLayerTreeModelLegendNode::setUserPatchSize( QSizeF size )
78 {
79   if ( mUserSize == size )
80     return;
81 
82   mUserSize = size;
83   emit sizeChanged();
84 }
85 
draw(const QgsLegendSettings & settings,ItemContext * ctx)86 QgsLayerTreeModelLegendNode::ItemMetrics QgsLayerTreeModelLegendNode::draw( const QgsLegendSettings &settings, ItemContext *ctx )
87 {
88   const QFont symbolLabelFont = settings.style( QgsLegendStyle::SymbolLabel ).font();
89 
90   const double textHeight = settings.fontHeightCharacterMM( symbolLabelFont, QChar( '0' ) );
91   // itemHeight here is not really item height, it is only for symbol
92   // vertical alignment purpose, i.e. OK take single line height
93   // if there are more lines, those run under the symbol
94   const double itemHeight = std::max( static_cast< double >( ctx && ctx->patchSize.height() > 0 ? ctx->patchSize.height() : settings.symbolSize().height() ), textHeight );
95 
96   ItemMetrics im;
97   im.symbolSize = drawSymbol( settings, ctx, itemHeight );
98   im.labelSize = drawSymbolText( settings, ctx, im.symbolSize );
99   return im;
100 }
101 
exportToJson(const QgsLegendSettings & settings,const QgsRenderContext & context)102 QJsonObject QgsLayerTreeModelLegendNode::exportToJson( const QgsLegendSettings &settings, const QgsRenderContext &context )
103 {
104   QJsonObject json = exportSymbolToJson( settings, context );
105   const QString text = data( Qt::DisplayRole ).toString();
106   json[ QStringLiteral( "title" ) ] = text;
107   return json;
108 }
109 
drawSymbol(const QgsLegendSettings & settings,ItemContext * ctx,double itemHeight) const110 QSizeF QgsLayerTreeModelLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight ) const
111 {
112   const QIcon symbolIcon = data( Qt::DecorationRole ).value<QIcon>();
113   if ( symbolIcon.isNull() )
114     return QSizeF();
115 
116   QSizeF size = settings.symbolSize();
117   if ( ctx )
118   {
119     if ( ctx->patchSize.width() > 0 )
120       size.setWidth( ctx->patchSize.width( ) );
121     if ( ctx->patchSize.height() > 0 )
122       size.setHeight( ctx->patchSize.height( ) );
123   }
124 
125   if ( ctx && ctx->painter )
126   {
127     switch ( settings.symbolAlignment() )
128     {
129       case Qt::AlignLeft:
130       default:
131         symbolIcon.paint( ctx->painter,
132                           static_cast< int >( ctx->columnLeft ),
133                           static_cast< int >( ctx->top + ( itemHeight - size.height() ) / 2 ),
134                           static_cast< int >( size.width() ),
135                           static_cast< int >( size.height() ) );
136         break;
137 
138       case Qt::AlignRight:
139         symbolIcon.paint( ctx->painter,
140                           static_cast< int >( ctx->columnRight - size.width() ),
141                           static_cast< int >( ctx->top + ( itemHeight - size.height() ) / 2 ),
142                           static_cast< int >( size.width() ),
143                           static_cast< int >( size.height() ) );
144         break;
145     }
146   }
147   return size;
148 }
149 
exportSymbolToJson(const QgsLegendSettings & settings,const QgsRenderContext &) const150 QJsonObject QgsLayerTreeModelLegendNode::exportSymbolToJson( const QgsLegendSettings &settings, const QgsRenderContext & ) const
151 {
152   const QIcon icon = data( Qt::DecorationRole ).value<QIcon>();
153   if ( icon.isNull() )
154     return QJsonObject();
155 
156   const QImage image( icon.pixmap( settings.symbolSize().width(), settings.symbolSize().height() ).toImage() );
157   QByteArray byteArray;
158   QBuffer buffer( &byteArray );
159   image.save( &buffer, "PNG" );
160   const QString base64 = QString::fromLatin1( byteArray.toBase64().data() );
161 
162   QJsonObject json;
163   json[ QStringLiteral( "icon" ) ] = base64;
164   return json;
165 }
166 
drawSymbolText(const QgsLegendSettings & settings,ItemContext * ctx,QSizeF symbolSize) const167 QSizeF QgsLayerTreeModelLegendNode::drawSymbolText( const QgsLegendSettings &settings, ItemContext *ctx, QSizeF symbolSize ) const
168 {
169   QSizeF labelSize( 0, 0 );
170 
171   const QFont symbolLabelFont = settings.style( QgsLegendStyle::SymbolLabel ).font();
172   const double textHeight = settings.fontHeightCharacterMM( symbolLabelFont, QChar( '0' ) );
173   const double textDescent = settings.fontDescentMillimeters( symbolLabelFont );
174 
175   const QgsExpressionContext tempContext;
176 
177   const QStringList lines = settings.evaluateItemText( data( Qt::DisplayRole ).toString(), ctx && ctx->context ? ctx->context->expressionContext() : tempContext );
178 
179   labelSize.rheight() = lines.count() * textHeight + ( lines.count() - 1 ) * ( settings.lineSpacing() + textDescent );
180 
181   double labelXMin = 0.0;
182   double labelXMax = 0.0;
183   double labelY = 0.0;
184   if ( ctx && ctx->painter )
185   {
186     ctx->painter->setPen( settings.fontColor() );
187     switch ( settings.symbolAlignment() )
188     {
189       case Qt::AlignLeft:
190       default:
191         labelXMin = ctx->columnLeft + std::max( static_cast< double >( symbolSize.width() ), ctx->maxSiblingSymbolWidth )
192                     + settings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Right )
193                     + settings.style( QgsLegendStyle::SymbolLabel ).margin( QgsLegendStyle::Left );
194         labelXMax = ctx->columnRight;
195         break;
196 
197       case Qt::AlignRight:
198         labelXMin = ctx->columnLeft;
199         // NOTE -- while the below calculations use the flipped margins from the style, that's only done because
200         // those are the only margins we expose and use for now! (and we expose them as generic margins, not side-specific
201         // ones) TODO when/if we expose other margin settings, these should be reversed...
202         labelXMax = ctx->columnRight - std::max( static_cast< double >( symbolSize.width() ), ctx->maxSiblingSymbolWidth )
203                     - settings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Right )
204                     - settings.style( QgsLegendStyle::SymbolLabel ).margin( QgsLegendStyle::Left );
205         break;
206     }
207 
208     labelY = ctx->top;
209 
210     // Vertical alignment of label with symbol
211     if ( labelSize.height() < symbolSize.height() )
212       labelY += symbolSize.height() / 2 - labelSize.height() / 2;  // label centered with symbol
213 
214     labelY += textHeight;
215   }
216 
217   for ( QStringList::ConstIterator itemPart = lines.constBegin(); itemPart != lines.constEnd(); ++itemPart )
218   {
219     const double lineWidth = settings.textWidthMillimeters( symbolLabelFont, *itemPart );
220     labelSize.rwidth() = std::max( lineWidth, double( labelSize.width() ) );
221 
222     if ( ctx && ctx->painter )
223     {
224       switch ( settings.style( QgsLegendStyle::SymbolLabel ).alignment() )
225       {
226         case Qt::AlignLeft:
227         default:
228           settings.drawText( ctx->painter, labelXMin, labelY, *itemPart, symbolLabelFont );
229           break;
230 
231         case Qt::AlignRight:
232           settings.drawText( ctx->painter, labelXMax - lineWidth, labelY, *itemPart, symbolLabelFont );
233           break;
234 
235         case Qt::AlignHCenter:
236           settings.drawText( ctx->painter, labelXMin + ( labelXMax - labelXMin - lineWidth ) / 2.0, labelY, *itemPart, symbolLabelFont );
237           break;
238       }
239 
240       if ( itemPart != ( lines.end() - 1 ) )
241         labelY += textDescent + settings.lineSpacing() + textHeight;
242     }
243   }
244 
245   return labelSize;
246 }
247 
checkAllItems()248 void QgsLayerTreeModelLegendNode::checkAllItems()
249 {
250   checkAll( true );
251 }
252 
uncheckAllItems()253 void QgsLayerTreeModelLegendNode::uncheckAllItems()
254 {
255   checkAll( false );
256 }
257 
toggleAllItems()258 void QgsLayerTreeModelLegendNode::toggleAllItems()
259 {
260   if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() ) )
261   {
262     if ( !vlayer->renderer() )
263       return;
264 
265     const QgsLegendSymbolList symbolList = vlayer->renderer()->legendSymbolItems();
266     for ( const auto &item : symbolList )
267     {
268       vlayer->renderer()->checkLegendSymbolItem( item.ruleKey(), ! vlayer->renderer()->legendSymbolItemChecked( item.ruleKey() ) );
269     }
270 
271     emit dataChanged();
272     vlayer->emitStyleChanged();
273     vlayer->triggerRepaint();
274   }
275   else if ( QgsPointCloudLayer *pclayer = qobject_cast<QgsPointCloudLayer *>( mLayerNode->layer() ) )
276   {
277     if ( !pclayer->renderer() )
278       return;
279 
280     const QStringList ruleKeys = pclayer->renderer()->legendRuleKeys();
281     for ( const QString &rule : ruleKeys )
282     {
283       pclayer->renderer()->checkLegendItem( rule, !pclayer->renderer()->legendItemChecked( rule ) );
284     }
285 
286     emit dataChanged();
287     pclayer->emitStyleChanged();
288     pclayer->triggerRepaint();
289   }
290 }
291 
292 // -------------------------------------------------------------------------
293 
294 double QgsSymbolLegendNode::MINIMUM_SIZE = -1.0;
295 double QgsSymbolLegendNode::MAXIMUM_SIZE = -1.0;
296 
QgsSymbolLegendNode(QgsLayerTreeLayer * nodeLayer,const QgsLegendSymbolItem & item,QObject * parent)297 QgsSymbolLegendNode::QgsSymbolLegendNode( QgsLayerTreeLayer *nodeLayer, const QgsLegendSymbolItem &item, QObject *parent )
298   : QgsLayerTreeModelLegendNode( nodeLayer, parent )
299   , mItem( item )
300   , mSymbolUsesMapUnits( false )
301 {
302   const int iconSize = QgsLayerTreeModel::scaleIconSize( 16 );
303   mIconSize = QSize( iconSize, iconSize );
304 
305   if ( MINIMUM_SIZE < 0 )
306   {
307     // it's FAR too expensive to construct a QgsSettings object for every symbol node, especially for complex
308     // projects. So only read the valid size ranges once, and store them for subsequent use
309     const QgsSettings settings;
310     MINIMUM_SIZE = settings.value( "/qgis/legendsymbolMinimumSize", 0.5 ).toDouble();
311     MAXIMUM_SIZE = settings.value( "/qgis/legendsymbolMaximumSize", 20.0 ).toDouble();
312   }
313 
314   updateLabel();
315   if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( nodeLayer->layer() ) )
316     connect( vl, &QgsVectorLayer::symbolFeatureCountMapChanged, this, &QgsSymbolLegendNode::updateLabel );
317 
318   connect( nodeLayer, &QObject::destroyed, this, [ = ]() { mLayerNode = nullptr; } );
319 
320   if ( const QgsSymbol *symbol = mItem.symbol() )
321   {
322     mSymbolUsesMapUnits = symbol->usesMapUnits();
323   }
324 }
325 
flags() const326 Qt::ItemFlags QgsSymbolLegendNode::flags() const
327 {
328   if ( mItem.isCheckable() )
329     return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
330   else
331     return Qt::ItemIsEnabled;
332 }
333 
334 
minimumIconSize() const335 QSize QgsSymbolLegendNode::minimumIconSize() const
336 {
337   const std::unique_ptr<QgsRenderContext> context( createTemporaryRenderContext() );
338   return minimumIconSize( context.get() );
339 }
340 
minimumIconSize(QgsRenderContext * context) const341 QSize QgsSymbolLegendNode::minimumIconSize( QgsRenderContext *context ) const
342 {
343   const int iconSize = QgsLayerTreeModel::scaleIconSize( 16 );
344   const int largeIconSize = QgsLayerTreeModel::scaleIconSize( 512 );
345   QSize minSz( iconSize, iconSize );
346   if ( mItem.symbol() && mItem.symbol()->type() == Qgis::SymbolType::Marker )
347   {
348     // unusued width, height variables
349     double width = 0.0;
350     double height = 0.0;
351     const std::unique_ptr<QgsSymbol> symbol( QgsSymbolLayerUtils::restrictedSizeSymbol( mItem.symbol(), MINIMUM_SIZE, MAXIMUM_SIZE, context, width, height ) );
352     minSz = QgsImageOperation::nonTransparentImageRect(
353               QgsSymbolLayerUtils::symbolPreviewPixmap( symbol ? symbol.get() : mItem.symbol(), QSize( largeIconSize, largeIconSize ), 0,
354                   context ).toImage(),
355               minSz,
356               true ).size();
357   }
358   else if ( mItem.symbol() && mItem.symbol()->type() == Qgis::SymbolType::Line )
359   {
360     double width = 0.0;
361     double height = 0.0;
362     const std::unique_ptr<QgsSymbol> symbol( QgsSymbolLayerUtils::restrictedSizeSymbol( mItem.symbol(), MINIMUM_SIZE, MAXIMUM_SIZE, context, width, height ) );
363     minSz = QgsImageOperation::nonTransparentImageRect(
364               QgsSymbolLayerUtils::symbolPreviewPixmap( symbol ? symbol.get() : mItem.symbol(), QSize( minSz.width(), largeIconSize ), 0,
365                   context ).toImage(),
366               minSz,
367               true ).size();
368   }
369 
370   if ( !mTextOnSymbolLabel.isEmpty() && context )
371   {
372     const double w = QgsTextRenderer::textWidth( *context, mTextOnSymbolTextFormat, QStringList() << mTextOnSymbolLabel );
373     const double h = QgsTextRenderer::textHeight( *context, mTextOnSymbolTextFormat, QStringList() << mTextOnSymbolLabel, QgsTextRenderer::Point );
374     int wInt = ceil( w ), hInt = ceil( h );
375     if ( wInt > minSz.width() ) minSz.setWidth( wInt );
376     if ( hInt > minSz.height() ) minSz.setHeight( hInt );
377   }
378 
379   return minSz;
380 }
381 
symbol() const382 const QgsSymbol *QgsSymbolLegendNode::symbol() const
383 {
384   return mItem.symbol();
385 }
386 
symbolLabel() const387 QString QgsSymbolLegendNode::symbolLabel() const
388 {
389   QString label;
390   if ( mEmbeddedInParent )
391   {
392     const QVariant legendlabel = mLayerNode->customProperty( QStringLiteral( "legend/title-label" ) );
393     const QString layerName = legendlabel.isNull() ? mLayerNode->name() : legendlabel.toString();
394     label = mUserLabel.isEmpty() ? layerName : mUserLabel;
395   }
396   else
397     label = mUserLabel.isEmpty() ? mItem.label() : mUserLabel;
398   return label;
399 }
400 
patchShape() const401 QgsLegendPatchShape QgsSymbolLegendNode::patchShape() const
402 {
403   if ( mEmbeddedInParent )
404   {
405     return mLayerNode->patchShape();
406   }
407   else
408   {
409     return mPatchShape;
410   }
411 }
412 
setPatchShape(const QgsLegendPatchShape & shape)413 void QgsSymbolLegendNode::setPatchShape( const QgsLegendPatchShape &shape )
414 {
415   mPatchShape = shape;
416 }
417 
customSymbol() const418 QgsSymbol *QgsSymbolLegendNode::customSymbol() const
419 {
420   return mCustomSymbol.get();
421 }
422 
setCustomSymbol(QgsSymbol * symbol)423 void QgsSymbolLegendNode::setCustomSymbol( QgsSymbol *symbol )
424 {
425   mCustomSymbol.reset( symbol );
426 }
427 
setSymbol(QgsSymbol * symbol)428 void QgsSymbolLegendNode::setSymbol( QgsSymbol *symbol )
429 {
430   if ( !symbol )
431     return;
432 
433   std::unique_ptr< QgsSymbol > s( symbol ); // this method takes ownership of symbol
434   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() );
435   if ( !vlayer || !vlayer->renderer() )
436     return;
437 
438   mItem.setSymbol( s.get() ); // doesn't transfer ownership
439   vlayer->renderer()->setLegendSymbolItem( mItem.ruleKey(), s.release() ); // DOES transfer ownership!
440 
441   mPixmap = QPixmap();
442 
443   emit dataChanged();
444   vlayer->triggerRepaint();
445 }
446 
createTemporaryRenderContext() const447 QgsRenderContext *QgsLayerTreeModelLegendNode::createTemporaryRenderContext() const
448 {
449   double scale = 0.0;
450   double mupp = 0.0;
451   int dpi = 0;
452   if ( auto *lModel = model() )
453     lModel->legendMapViewData( &mupp, &dpi, &scale );
454 
455   if ( qgsDoubleNear( mupp, 0.0 ) || dpi == 0 || qgsDoubleNear( scale, 0.0 ) )
456     return nullptr;
457 
458   // setup temporary render context
459   std::unique_ptr<QgsRenderContext> context = std::make_unique<QgsRenderContext>( );
460   context->setScaleFactor( dpi / 25.4 );
461   context->setRendererScale( scale );
462   context->setMapToPixel( QgsMapToPixel( mupp ) );
463   context->setFlag( Qgis::RenderContextFlag::Antialiasing, true );
464   context->setFlag( Qgis::RenderContextFlag::RenderSymbolPreview, true );
465   return context.release();
466 }
467 
checkAll(bool state)468 void QgsLayerTreeModelLegendNode::checkAll( bool state )
469 {
470   if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() ) )
471   {
472     if ( !vlayer->renderer() )
473       return;
474 
475     const QgsLegendSymbolList symbolList = vlayer->renderer()->legendSymbolItems();
476     for ( const auto &item : symbolList )
477     {
478       vlayer->renderer()->checkLegendSymbolItem( item.ruleKey(), state );
479     }
480 
481     emit dataChanged();
482     vlayer->emitStyleChanged();
483     vlayer->triggerRepaint();
484   }
485   else if ( QgsPointCloudLayer *pclayer = qobject_cast<QgsPointCloudLayer *>( mLayerNode->layer() ) )
486   {
487     if ( !pclayer->renderer() )
488       return;
489 
490     const QStringList ruleKeys = pclayer->renderer()->legendRuleKeys();
491     for ( const QString &rule : ruleKeys )
492     {
493       pclayer->renderer()->checkLegendItem( rule, state );
494     }
495 
496     emit dataChanged();
497     pclayer->emitStyleChanged();
498     pclayer->triggerRepaint();
499   }
500 }
501 
data(int role) const502 QVariant QgsSymbolLegendNode::data( int role ) const
503 {
504   if ( role == Qt::DisplayRole )
505   {
506     return mLabel;
507   }
508   else if ( role == Qt::EditRole )
509   {
510     return mUserLabel.isEmpty() ? mItem.label() : mUserLabel;
511   }
512   else if ( role == Qt::DecorationRole )
513   {
514     if ( mPixmap.isNull() || mPixmap.size() != mIconSize )
515     {
516       QPixmap pix;
517       if ( mItem.symbol() )
518       {
519         std::unique_ptr<QgsRenderContext> context( createTemporaryRenderContext() );
520 
521         // unusued width, height variables
522         double width = 0.0;
523         double height = 0.0;
524         const std::unique_ptr<QgsSymbol> symbol( QgsSymbolLayerUtils::restrictedSizeSymbol( mItem.symbol(), MINIMUM_SIZE, MAXIMUM_SIZE, context.get(), width, height ) );
525         pix = QgsSymbolLayerUtils::symbolPreviewPixmap( symbol ? symbol.get() : mItem.symbol(), mIconSize, 0, context.get() );
526 
527         if ( !mTextOnSymbolLabel.isEmpty() && context )
528         {
529           QPainter painter( &pix );
530           painter.setRenderHint( QPainter::Antialiasing );
531           context->setPainter( &painter );
532           bool isNullSize = false;
533           const QFontMetricsF fm( mTextOnSymbolTextFormat.scaledFont( *context, 1.0, &isNullSize ) );
534           if ( !isNullSize )
535           {
536             const qreal yBaselineVCenter = ( mIconSize.height() + fm.ascent() - fm.descent() ) / 2;
537             QgsTextRenderer::drawText( QPointF( mIconSize.width() / 2, yBaselineVCenter ), 0, QgsTextRenderer::AlignCenter,
538                                        QStringList() << mTextOnSymbolLabel, *context, mTextOnSymbolTextFormat );
539           }
540         }
541       }
542       else
543       {
544         pix = QPixmap( mIconSize );
545         pix.fill( Qt::transparent );
546       }
547 
548       mPixmap = pix;
549     }
550     return mPixmap;
551   }
552   else if ( role == Qt::CheckStateRole )
553   {
554     if ( !mItem.isCheckable() )
555       return QVariant();
556 
557     if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() ) )
558     {
559       if ( !vlayer->renderer() )
560         return QVariant();
561 
562       return vlayer->renderer()->legendSymbolItemChecked( mItem.ruleKey() ) ? Qt::Checked : Qt::Unchecked;
563     }
564   }
565   else if ( role == RuleKeyRole )
566   {
567     return mItem.ruleKey();
568   }
569   else if ( role == ParentRuleKeyRole )
570   {
571     return mItem.parentRuleKey();
572   }
573   else if ( role == QgsLayerTreeModelLegendNode::NodeTypeRole )
574   {
575     return QgsLayerTreeModelLegendNode::SymbolLegend;
576   }
577 
578   return QVariant();
579 }
580 
setData(const QVariant & value,int role)581 bool QgsSymbolLegendNode::setData( const QVariant &value, int role )
582 {
583   if ( role != Qt::CheckStateRole )
584     return false;
585 
586   if ( !mItem.isCheckable() )
587     return false;
588 
589   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() );
590   if ( !vlayer || !vlayer->renderer() )
591     return false;
592 
593   vlayer->renderer()->checkLegendSymbolItem( mItem.ruleKey(), value == Qt::Checked );
594 
595   emit dataChanged();
596   vlayer->emitStyleChanged();
597 
598   vlayer->triggerRepaint();
599 
600   return true;
601 }
602 
603 
604 
drawSymbol(const QgsLegendSettings & settings,ItemContext * ctx,double itemHeight) const605 QSizeF QgsSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight ) const
606 {
607   QgsSymbol *s = mCustomSymbol ? mCustomSymbol.get() : mItem.symbol();
608   if ( !s )
609   {
610     return QSizeF();
611   }
612 
613   // setup temporary render context
614   QgsRenderContext *context = nullptr;
615   std::unique_ptr< QgsRenderContext > tempRenderContext;
616   const QgsLegendPatchShape patchShape = ctx ? ctx->patchShape : QgsLegendPatchShape();
617   if ( ctx && ctx->context )
618     context = ctx->context;
619   else
620   {
621     tempRenderContext = std::make_unique< QgsRenderContext >();
622     // QGIS 4.0 - make ItemContext compulsory, so we don't have to construct temporary render contexts here
623     Q_NOWARN_DEPRECATED_PUSH
624     tempRenderContext->setScaleFactor( settings.dpi() / 25.4 );
625     tempRenderContext->setRendererScale( settings.mapScale() );
626     tempRenderContext->setFlag( Qgis::RenderContextFlag::Antialiasing, true );
627     tempRenderContext->setMapToPixel( QgsMapToPixel( 1 / ( settings.mmPerMapUnit() * tempRenderContext->scaleFactor() ) ) );
628     Q_NOWARN_DEPRECATED_POP
629     tempRenderContext->setForceVectorOutput( true );
630     tempRenderContext->setPainter( ctx ? ctx->painter : nullptr );
631 
632     // setup a minimal expression context
633     QgsExpressionContext expContext;
634     expContext.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) );
635     tempRenderContext->setExpressionContext( expContext );
636     context = tempRenderContext.get();
637   }
638 
639   //Consider symbol size for point markers
640   const double desiredHeight = ctx && ctx->patchSize.height() > 0 ? ctx->patchSize.height() : settings.symbolSize().height();
641   const double desiredWidth = ctx && ctx->patchSize.width() > 0 ? ctx->patchSize.width() : settings.symbolSize().width();
642   double height = desiredHeight;
643   double width = desiredWidth;
644 
645   //Center small marker symbols
646   double widthOffset = 0;
647   double heightOffset = 0;
648 
649   const double maxSymbolSize = settings.maximumSymbolSize();
650   const double minSymbolSize = settings.minimumSymbolSize();
651 
652   if ( QgsMarkerSymbol *markerSymbol = dynamic_cast<QgsMarkerSymbol *>( s ) )
653   {
654     const double size = markerSymbol->size( *context ) / context->scaleFactor();
655     height = size;
656     width = size;
657   }
658 
659   const std::unique_ptr<QgsSymbol> minMaxSizeSymbol( QgsSymbolLayerUtils::restrictedSizeSymbol( s, minSymbolSize, maxSymbolSize, context, width, height ) );
660   if ( minMaxSizeSymbol )
661   {
662     s = minMaxSizeSymbol.get();
663   }
664 
665   if ( s->type() == Qgis::SymbolType::Marker )
666   {
667     if ( width < desiredWidth )
668     {
669       widthOffset = ( desiredWidth - width ) / 2.0;
670     }
671     if ( height < desiredHeight )
672     {
673       heightOffset = ( desiredHeight - height ) / 2.0;
674     }
675   }
676   if ( ctx && ctx->painter )
677   {
678     const double currentYCoord = ctx->top + ( itemHeight - desiredHeight ) / 2;
679     QPainter *p = ctx->painter;
680 
681     //setup painter scaling to dots so that raster symbology is drawn to scale
682     const double dotsPerMM = context->scaleFactor();
683 
684     int opacity = 255;
685     if ( QgsMapLayer *layer = layerNode()->layer() )
686       opacity = static_cast<int >( std::round( 255 * layer->opacity() ) );
687 
688     const QgsScopedQPainterState painterState( p );
689     context->setPainterFlagsUsingContext( p );
690 
691     switch ( settings.symbolAlignment() )
692     {
693       case Qt::AlignLeft:
694       default:
695         p->translate( ctx->columnLeft + widthOffset, currentYCoord + heightOffset );
696         break;
697       case Qt::AlignRight:
698         p->translate( ctx->columnRight - widthOffset - width, currentYCoord + heightOffset );
699         break;
700     }
701 
702     p->scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
703     Q_NOWARN_DEPRECATED_PUSH
704     // QGIS 4.0 -- ctx->context will be mandatory
705     const bool useAdvancedEffects = ctx->context ? ctx->context->flags() & Qgis::RenderContextFlag::UseAdvancedEffects : settings.useAdvancedEffects();
706     Q_NOWARN_DEPRECATED_POP
707     if ( opacity != 255 && useAdvancedEffects )
708     {
709       const int maxBleed = static_cast< int >( std::ceil( QgsSymbolLayerUtils::estimateMaxSymbolBleed( s, *context ) ) );
710 
711       //semi transparent layer, so need to draw symbol to an image (to flatten it first)
712       //create image which is same size as legend rect, in case symbol bleeds outside its allotted space
713       const QSize symbolSize( static_cast< int >( std::round( width * dotsPerMM ) ), static_cast<int >( std::round( height * dotsPerMM ) ) );
714       const QSize tempImageSize( symbolSize.width() + maxBleed * 2, symbolSize.height() + maxBleed * 2 );
715       QImage tempImage = QImage( tempImageSize, QImage::Format_ARGB32 );
716       tempImage.fill( Qt::transparent );
717       QPainter imagePainter( &tempImage );
718       context->setPainterFlagsUsingContext( &imagePainter );
719 
720       context->setPainter( &imagePainter );
721       imagePainter.translate( maxBleed, maxBleed );
722       s->drawPreviewIcon( &imagePainter, symbolSize, context, false, nullptr, &patchShape );
723       imagePainter.translate( -maxBleed, -maxBleed );
724       context->setPainter( ctx->painter );
725       //reduce opacity of image
726       imagePainter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
727       imagePainter.fillRect( tempImage.rect(), QColor( 0, 0, 0, opacity ) );
728       imagePainter.end();
729       //draw rendered symbol image
730       p->drawImage( -maxBleed, -maxBleed, tempImage );
731     }
732     else
733     {
734       s->drawPreviewIcon( p, QSize( static_cast< int >( std::round( width * dotsPerMM ) ), static_cast< int >( std::round( height * dotsPerMM ) ) ), context, false, nullptr, &patchShape );
735     }
736 
737     if ( !mTextOnSymbolLabel.isEmpty() )
738     {
739       bool isNullSize = false;
740       const QFontMetricsF fm( mTextOnSymbolTextFormat.scaledFont( *context, 1.0, &isNullSize ) );
741       if ( !isNullSize )
742       {
743         const qreal yBaselineVCenter = ( height * dotsPerMM + fm.ascent() - fm.descent() ) / 2;
744         QgsTextRenderer::drawText( QPointF( width * dotsPerMM / 2, yBaselineVCenter ), 0, QgsTextRenderer::AlignCenter,
745                                    QStringList() << mTextOnSymbolLabel, *context, mTextOnSymbolTextFormat );
746       }
747     }
748   }
749 
750   return QSizeF( std::max( width + 2 * widthOffset, static_cast< double >( desiredWidth ) ),
751                  std::max( height + 2 * heightOffset, static_cast< double >( desiredHeight ) ) );
752 }
753 
exportSymbolToJson(const QgsLegendSettings & settings,const QgsRenderContext & context) const754 QJsonObject QgsSymbolLegendNode::exportSymbolToJson( const QgsLegendSettings &settings, const QgsRenderContext &context ) const
755 {
756   const QgsSymbol *s = mCustomSymbol ? mCustomSymbol.get() : mItem.symbol();
757   if ( !s )
758   {
759     return QJsonObject();
760   }
761 
762 
763   QgsRenderContext ctx;
764   // QGIS 4.0 - use render context directly here, and note in the dox that the context must be correctly setup
765   Q_NOWARN_DEPRECATED_PUSH
766   ctx.setScaleFactor( settings.dpi() / 25.4 );
767   ctx.setRendererScale( settings.mapScale() );
768   ctx.setMapToPixel( QgsMapToPixel( 1 / ( settings.mmPerMapUnit() * ctx.scaleFactor() ) ) );
769   ctx.setForceVectorOutput( true );
770   ctx.setFlag( Qgis::RenderContextFlag::Antialiasing, context.flags() & Qgis::RenderContextFlag::Antialiasing );
771   ctx.setFlag( Qgis::RenderContextFlag::LosslessImageRendering, context.flags() & Qgis::RenderContextFlag::LosslessImageRendering );
772 
773   Q_NOWARN_DEPRECATED_POP
774 
775   // ensure that a minimal expression context is available
776   QgsExpressionContext expContext = context.expressionContext();
777   expContext.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) );
778   ctx.setExpressionContext( expContext );
779 
780   const QPixmap pix = QgsSymbolLayerUtils::symbolPreviewPixmap( mItem.symbol(), minimumIconSize(), 0, &ctx );
781   QImage img( pix.toImage().convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
782 
783   int opacity = 255;
784   if ( QgsMapLayer *layer = layerNode()->layer() )
785     opacity = ( 255 * layer->opacity() );
786 
787   if ( opacity != 255 )
788   {
789     QPainter painter;
790     painter.begin( &img );
791     painter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
792     painter.fillRect( pix.rect(), QColor( 0, 0, 0, opacity ) );
793     painter.end();
794   }
795 
796   QByteArray byteArray;
797   QBuffer buffer( &byteArray );
798   img.save( &buffer, "PNG" );
799   const QString base64 = QString::fromLatin1( byteArray.toBase64().data() );
800 
801   QJsonObject json;
802   json[ QStringLiteral( "icon" ) ] = base64;
803   if ( mItem.scaleMaxDenom() > 0 )
804   {
805     json[ QStringLiteral( "scaleMaxDenom" ) ] = mItem.scaleMaxDenom();
806   }
807   if ( mItem.scaleMinDenom() > 0 )
808   {
809     json[ QStringLiteral( "scaleMinDenom" ) ] = mItem.scaleMinDenom();
810   }
811   mItem.scaleMaxDenom();
812   return json;
813 }
814 
setEmbeddedInParent(bool embedded)815 void QgsSymbolLegendNode::setEmbeddedInParent( bool embedded )
816 {
817   QgsLayerTreeModelLegendNode::setEmbeddedInParent( embedded );
818   updateLabel();
819 }
820 
821 
invalidateMapBasedData()822 void QgsSymbolLegendNode::invalidateMapBasedData()
823 {
824   if ( mSymbolUsesMapUnits )
825   {
826     mPixmap = QPixmap();
827     emit dataChanged();
828   }
829 }
830 
831 
updateLabel()832 void QgsSymbolLegendNode::updateLabel()
833 {
834   if ( !mLayerNode )
835     return;
836 
837   const bool showFeatureCount = mLayerNode->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toBool();
838   QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() );
839   mLabel = symbolLabel();
840 
841   if ( showFeatureCount && vl )
842   {
843     const qlonglong count = mEmbeddedInParent ? vl->featureCount() : vl->featureCount( mItem.ruleKey() ) ;
844     mLabel += QStringLiteral( " [%1]" ).arg( count != -1 ? QLocale().toString( count ) : tr( "N/A" ) );
845   }
846 
847   emit dataChanged();
848 }
849 
evaluateLabel(const QgsExpressionContext & context,const QString & label)850 QString QgsSymbolLegendNode::evaluateLabel( const QgsExpressionContext &context, const QString &label )
851 {
852   if ( !mLayerNode )
853     return QString();
854 
855   QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() );
856 
857   if ( vl )
858   {
859     QgsExpressionContext contextCopy = QgsExpressionContext( context );
860     QgsExpressionContextScope *symbolScope = createSymbolScope();
861     contextCopy.appendScope( symbolScope );
862     contextCopy.appendScope( vl->createExpressionContextScope() );
863 
864     if ( label.isEmpty() )
865     {
866       if ( ! mLayerNode->labelExpression().isEmpty() )
867         mLabel = QgsExpression::replaceExpressionText( "[%" + mLayerNode->labelExpression() + "%]", &contextCopy );
868       else if ( mLabel.contains( "[%" ) )
869       {
870         const QString symLabel = symbolLabel();
871         mLabel = QgsExpression::replaceExpressionText( symLabel, &contextCopy );
872       }
873       return mLabel;
874     }
875     else
876     {
877       QString eLabel;
878       if ( ! mLayerNode->labelExpression().isEmpty() )
879         eLabel = QgsExpression::replaceExpressionText( label + "[%" + mLayerNode->labelExpression() + "%]", &contextCopy );
880       else if ( label.contains( "[%" ) )
881         eLabel = QgsExpression::replaceExpressionText( label, &contextCopy );
882       return eLabel;
883     }
884   }
885   return mLabel;
886 }
887 
createSymbolScope() const888 QgsExpressionContextScope *QgsSymbolLegendNode::createSymbolScope() const
889 {
890   QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() );
891 
892   QgsExpressionContextScope *scope = new QgsExpressionContextScope( tr( "Symbol scope" ) );
893   scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_label" ), symbolLabel().remove( "[%" ).remove( "%]" ), true ) );
894   scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_id" ), mItem.ruleKey(), true ) );
895   if ( vl )
896   {
897     scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_count" ), QVariant::fromValue( vl->featureCount( mItem.ruleKey() ) ), true ) );
898   }
899   return scope;
900 }
901 
902 // -------------------------------------------------------------------------
903 
904 
QgsSimpleLegendNode(QgsLayerTreeLayer * nodeLayer,const QString & label,const QIcon & icon,QObject * parent,const QString & key)905 QgsSimpleLegendNode::QgsSimpleLegendNode( QgsLayerTreeLayer *nodeLayer, const QString &label, const QIcon &icon, QObject *parent, const QString &key )
906   : QgsLayerTreeModelLegendNode( nodeLayer, parent )
907   , mLabel( label )
908   , mIcon( icon )
909   , mKey( key )
910 {
911 }
912 
data(int role) const913 QVariant QgsSimpleLegendNode::data( int role ) const
914 {
915   if ( role == Qt::DisplayRole || role == Qt::EditRole )
916     return mUserLabel.isEmpty() ? mLabel : mUserLabel;
917   else if ( role == Qt::DecorationRole )
918     return mIcon;
919   else if ( role == RuleKeyRole && !mKey.isEmpty() )
920     return mKey;
921   else if ( role == QgsLayerTreeModelLegendNode::NodeTypeRole )
922     return QgsLayerTreeModelLegendNode::SimpleLegend;
923   else
924     return QVariant();
925 }
926 
927 
928 // -------------------------------------------------------------------------
929 
QgsImageLegendNode(QgsLayerTreeLayer * nodeLayer,const QImage & img,QObject * parent)930 QgsImageLegendNode::QgsImageLegendNode( QgsLayerTreeLayer *nodeLayer, const QImage &img, QObject *parent )
931   : QgsLayerTreeModelLegendNode( nodeLayer, parent )
932   , mImage( img )
933 {
934 }
935 
data(int role) const936 QVariant QgsImageLegendNode::data( int role ) const
937 {
938   if ( role == Qt::DecorationRole )
939   {
940     return QPixmap::fromImage( mImage );
941   }
942   else if ( role == Qt::SizeHintRole )
943   {
944     return mImage.size();
945   }
946   else if ( role == QgsLayerTreeModelLegendNode::NodeTypeRole )
947   {
948     return QgsLayerTreeModelLegendNode::ImageLegend;
949   }
950   return QVariant();
951 }
952 
drawSymbol(const QgsLegendSettings & settings,ItemContext * ctx,double itemHeight) const953 QSizeF QgsImageLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight ) const
954 {
955   Q_UNUSED( itemHeight )
956 
957   if ( ctx && ctx->painter && ctx->context )
958   {
959     const QgsScopedRenderContextScaleToPixels scopedScaleToPixels( *( ctx->context ) );
960     const double scaleFactor = ctx->context->scaleFactor();
961     const double imgWidth = settings.wmsLegendSize().width() * scaleFactor;
962     const double imgHeight = settings.wmsLegendSize().height() * scaleFactor;
963 
964     const QImage scaledImg = mImage.scaled( QSizeF( imgWidth, imgHeight ).toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation );
965     switch ( settings.symbolAlignment() )
966     {
967       case Qt::AlignLeft:
968       default:
969         ctx->painter->drawImage( QPointF( ctx->columnLeft * scaleFactor, ctx->top * scaleFactor ), scaledImg );
970         break;
971 
972       case Qt::AlignRight:
973         ctx->painter->drawImage( QPointF( ctx->columnRight * scaleFactor - imgWidth, ctx->top * scaleFactor ), scaledImg );
974         break;
975     }
976   }
977   return settings.wmsLegendSize();
978 }
979 
exportSymbolToJson(const QgsLegendSettings &,const QgsRenderContext &) const980 QJsonObject QgsImageLegendNode::exportSymbolToJson( const QgsLegendSettings &, const QgsRenderContext & ) const
981 {
982   QByteArray byteArray;
983   QBuffer buffer( &byteArray );
984   mImage.save( &buffer, "PNG" );
985   const QString base64 = QString::fromLatin1( byteArray.toBase64().data() );
986 
987   QJsonObject json;
988   json[ QStringLiteral( "icon" ) ] = base64;
989   return json;
990 }
991 
992 // -------------------------------------------------------------------------
993 
QgsRasterSymbolLegendNode(QgsLayerTreeLayer * nodeLayer,const QColor & color,const QString & label,QObject * parent,bool isCheckable,const QString & ruleKey)994 QgsRasterSymbolLegendNode::QgsRasterSymbolLegendNode( QgsLayerTreeLayer *nodeLayer, const QColor &color, const QString &label, QObject *parent, bool isCheckable, const QString &ruleKey )
995   : QgsLayerTreeModelLegendNode( nodeLayer, parent )
996   , mColor( color )
997   , mLabel( label )
998   , mCheckable( isCheckable )
999   , mRuleKey( ruleKey )
1000 {
1001 }
1002 
flags() const1003 Qt::ItemFlags QgsRasterSymbolLegendNode::flags() const
1004 {
1005   if ( mCheckable )
1006     return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
1007   else
1008     return Qt::ItemIsEnabled;
1009 }
1010 
data(int role) const1011 QVariant QgsRasterSymbolLegendNode::data( int role ) const
1012 {
1013   switch ( role )
1014   {
1015     case Qt::DecorationRole:
1016     {
1017       const int iconSize = QgsLayerTreeModel::scaleIconSize( 16 ); // TODO: configurable?
1018       QPixmap pix( iconSize, iconSize );
1019       pix.fill( mColor );
1020       return QIcon( pix );
1021     }
1022 
1023     case Qt::DisplayRole:
1024     case Qt::EditRole:
1025       return mUserLabel.isEmpty() ? mLabel : mUserLabel;
1026 
1027     case QgsLayerTreeModelLegendNode::NodeTypeRole:
1028       return QgsLayerTreeModelLegendNode::RasterSymbolLegend;
1029 
1030     case QgsLayerTreeModelLegendNode::RuleKeyRole:
1031       return mRuleKey;
1032 
1033     case Qt::CheckStateRole:
1034     {
1035       if ( !mCheckable )
1036         return QVariant();
1037 
1038       if ( QgsPointCloudLayer *pclayer = qobject_cast<QgsPointCloudLayer *>( mLayerNode->layer() ) )
1039       {
1040         if ( !pclayer->renderer() )
1041           return QVariant();
1042 
1043         return pclayer->renderer()->legendItemChecked( mRuleKey ) ? Qt::Checked : Qt::Unchecked;
1044       }
1045 
1046       return QVariant();
1047     }
1048 
1049     default:
1050       return QVariant();
1051   }
1052 }
1053 
setData(const QVariant & value,int role)1054 bool QgsRasterSymbolLegendNode::setData( const QVariant &value, int role )
1055 {
1056   if ( role != Qt::CheckStateRole )
1057     return false;
1058 
1059   if ( !mCheckable )
1060     return false;
1061 
1062   if ( QgsPointCloudLayer *pclayer = qobject_cast<QgsPointCloudLayer *>( mLayerNode->layer() ) )
1063   {
1064     if ( !pclayer->renderer() )
1065       return false;
1066 
1067     pclayer->renderer()->checkLegendItem( mRuleKey, value == Qt::Checked );
1068 
1069     emit dataChanged();
1070     pclayer->emitStyleChanged();
1071 
1072     pclayer->triggerRepaint();
1073     return true;
1074   }
1075   else
1076   {
1077     return false;
1078   }
1079 }
1080 
1081 
drawSymbol(const QgsLegendSettings & settings,ItemContext * ctx,double itemHeight) const1082 QSizeF QgsRasterSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight ) const
1083 {
1084   QSizeF size = settings.symbolSize();
1085   double offsetX = 0;
1086   if ( ctx )
1087   {
1088     if ( ctx->patchSize.width() > 0 )
1089     {
1090       if ( ctx->patchSize.width() < size.width() )
1091         offsetX = ( size.width() - ctx->patchSize.width() ) / 2.0;
1092       size.setWidth( ctx->patchSize.width() );
1093     }
1094     if ( ctx->patchSize.height() > 0 )
1095     {
1096       size.setHeight( ctx->patchSize.height() );
1097     }
1098   }
1099 
1100   if ( ctx && ctx->painter )
1101   {
1102     QColor itemColor = mColor;
1103     if ( QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layerNode()->layer() ) )
1104     {
1105       if ( QgsRasterRenderer *rasterRenderer = rasterLayer->renderer() )
1106         itemColor.setAlpha( rasterRenderer->opacity() * 255.0 );
1107     }
1108     ctx->painter->setBrush( itemColor );
1109 
1110     if ( settings.drawRasterStroke() )
1111     {
1112       QPen pen;
1113       pen.setColor( settings.rasterStrokeColor() );
1114       pen.setWidthF( settings.rasterStrokeWidth() );
1115       pen.setJoinStyle( Qt::MiterJoin );
1116       ctx->painter->setPen( pen );
1117     }
1118     else
1119     {
1120       ctx->painter->setPen( Qt::NoPen );
1121     }
1122 
1123     switch ( settings.symbolAlignment() )
1124     {
1125       case Qt::AlignLeft:
1126       default:
1127         ctx->painter->drawRect( QRectF( ctx->columnLeft + offsetX, ctx->top + ( itemHeight - size.height() ) / 2,
1128                                         size.width(), size.height() ) );
1129         break;
1130 
1131       case Qt::AlignRight:
1132         ctx->painter->drawRect( QRectF( ctx->columnRight - size.width() - offsetX, ctx->top + ( itemHeight - size.height() ) / 2,
1133                                         size.width(), size.height() ) );
1134         break;
1135     }
1136   }
1137   return size;
1138 }
1139 
exportSymbolToJson(const QgsLegendSettings & settings,const QgsRenderContext &) const1140 QJsonObject QgsRasterSymbolLegendNode::exportSymbolToJson( const QgsLegendSettings &settings, const QgsRenderContext & ) const
1141 {
1142   QImage img = QImage( settings.symbolSize().toSize(), QImage::Format_ARGB32 );
1143   img.fill( Qt::transparent );
1144 
1145   QPainter painter( &img );
1146   painter.setRenderHint( QPainter::Antialiasing );
1147 
1148   QColor itemColor = mColor;
1149   if ( QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layerNode()->layer() ) )
1150   {
1151     if ( QgsRasterRenderer *rasterRenderer = rasterLayer->renderer() )
1152       itemColor.setAlpha( rasterRenderer->opacity() * 255.0 );
1153   }
1154   painter.setBrush( itemColor );
1155 
1156   if ( settings.drawRasterStroke() )
1157   {
1158     QPen pen;
1159     pen.setColor( settings.rasterStrokeColor() );
1160     pen.setWidthF( settings.rasterStrokeWidth() );
1161     pen.setJoinStyle( Qt::MiterJoin );
1162     painter.setPen( pen );
1163   }
1164   else
1165   {
1166     painter.setPen( Qt::NoPen );
1167   }
1168 
1169   painter.drawRect( QRectF( 0, 0, settings.symbolSize().width(), settings.symbolSize().height() ) );
1170 
1171   QByteArray byteArray;
1172   QBuffer buffer( &byteArray );
1173   img.save( &buffer, "PNG" );
1174   const QString base64 = QString::fromLatin1( byteArray.toBase64().data() );
1175 
1176   QJsonObject json;
1177   json[ QStringLiteral( "icon" ) ] = base64;
1178   return json;
1179 }
1180 
1181 // -------------------------------------------------------------------------
1182 
QgsWmsLegendNode(QgsLayerTreeLayer * nodeLayer,QObject * parent)1183 QgsWmsLegendNode::QgsWmsLegendNode( QgsLayerTreeLayer *nodeLayer, QObject *parent )
1184   : QgsLayerTreeModelLegendNode( nodeLayer, parent )
1185   , mValid( false )
1186 {
1187 }
1188 
1189 QgsWmsLegendNode::~QgsWmsLegendNode() = default;
1190 
getLegendGraphic() const1191 QImage QgsWmsLegendNode::getLegendGraphic() const
1192 {
1193   if ( ! mValid && ! mFetcher )
1194   {
1195     // or maybe in presence of a downloader we should just delete it
1196     // and start a new one ?
1197 
1198     QgsRasterLayer *layer = qobject_cast<QgsRasterLayer *>( mLayerNode->layer() );
1199 
1200     if ( layer && layer->isValid() )
1201     {
1202       const QgsLayerTreeModel *mod = model();
1203       if ( ! mod )
1204         return mImage;
1205       const QgsMapSettings *ms = mod->legendFilterMapSettings();
1206 
1207       QgsRasterDataProvider *prov = layer->dataProvider();
1208       if ( ! prov )
1209         return mImage;
1210 
1211       Q_ASSERT( ! mFetcher );
1212       mFetcher.reset( prov->getLegendGraphicFetcher( ms ) );
1213       if ( mFetcher )
1214       {
1215         connect( mFetcher.get(), &QgsImageFetcher::finish, this, &QgsWmsLegendNode::getLegendGraphicFinished );
1216         connect( mFetcher.get(), &QgsImageFetcher::error, this, &QgsWmsLegendNode::getLegendGraphicErrored );
1217         connect( mFetcher.get(), &QgsImageFetcher::progress, this, &QgsWmsLegendNode::getLegendGraphicProgress );
1218         mFetcher->start();
1219       }
1220     }
1221     else
1222     {
1223       QgsDebugMsg( tr( "Failed to download legend graphics: layer is not valid." ) );
1224     }
1225   }
1226 
1227   return mImage;
1228 }
1229 
data(int role) const1230 QVariant QgsWmsLegendNode::data( int role ) const
1231 {
1232   if ( role == Qt::DecorationRole )
1233   {
1234     return QPixmap::fromImage( getLegendGraphic() );
1235   }
1236   else if ( role == Qt::SizeHintRole )
1237   {
1238     return getLegendGraphic().size();
1239   }
1240   else if ( role == QgsLayerTreeModelLegendNode::NodeTypeRole )
1241   {
1242     return QgsLayerTreeModelLegendNode::WmsLegend;
1243   }
1244   return QVariant();
1245 }
1246 
drawSymbol(const QgsLegendSettings & settings,ItemContext * ctx,double itemHeight) const1247 QSizeF QgsWmsLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight ) const
1248 {
1249   Q_UNUSED( itemHeight )
1250 
1251   if ( ctx && ctx->painter )
1252   {
1253     const QImage image = getLegendGraphic();
1254 
1255     switch ( settings.symbolAlignment() )
1256     {
1257       case Qt::AlignLeft:
1258       default:
1259         ctx->painter->drawImage( QRectF( ctx->columnLeft,
1260                                          ctx->top,
1261                                          settings.wmsLegendSize().width(),
1262                                          settings.wmsLegendSize().height() ),
1263                                  image,
1264                                  QRectF( QPointF( 0, 0 ), image.size() ) );
1265         break;
1266 
1267       case Qt::AlignRight:
1268         ctx->painter->drawImage( QRectF( ctx->columnRight - settings.wmsLegendSize().width(),
1269                                          ctx->top,
1270                                          settings.wmsLegendSize().width(),
1271                                          settings.wmsLegendSize().height() ),
1272                                  image,
1273                                  QRectF( QPointF( 0, 0 ), image.size() ) );
1274         break;
1275     }
1276   }
1277   return settings.wmsLegendSize();
1278 }
1279 
exportSymbolToJson(const QgsLegendSettings &,const QgsRenderContext &) const1280 QJsonObject QgsWmsLegendNode::exportSymbolToJson( const QgsLegendSettings &, const QgsRenderContext & ) const
1281 {
1282   QByteArray byteArray;
1283   QBuffer buffer( &byteArray );
1284   mImage.save( &buffer, "PNG" );
1285   const QString base64 = QString::fromLatin1( byteArray.toBase64().data() );
1286 
1287   QJsonObject json;
1288   json[ QStringLiteral( "icon" ) ] = base64;
1289   return json;
1290 }
1291 
renderMessage(const QString & msg) const1292 QImage QgsWmsLegendNode::renderMessage( const QString &msg ) const
1293 {
1294   const int fontHeight = 10;
1295   const int margin = fontHeight / 2;
1296   const int nlines = 1;
1297 
1298   const int w = 512, h = fontHeight * nlines + margin * ( nlines + 1 );
1299   QImage image( w, h, QImage::Format_ARGB32_Premultiplied );
1300   QPainter painter;
1301   painter.begin( &image );
1302   painter.setPen( QColor( 255, 0, 0 ) );
1303   painter.setFont( QFont( QStringLiteral( "Chicago" ), fontHeight ) );
1304   painter.fillRect( 0, 0, w, h, QColor( 255, 255, 255 ) );
1305   painter.drawText( 0, margin + fontHeight, msg );
1306   //painter.drawText(0,2*(margin+fontHeight),tr("retrying in 5 seconds…"));
1307   painter.end();
1308 
1309   return image;
1310 }
1311 
getLegendGraphicProgress(qint64 cur,qint64 tot)1312 void QgsWmsLegendNode::getLegendGraphicProgress( qint64 cur, qint64 tot )
1313 {
1314   const QString msg = tot > 0 ? tr( "Downloading: %1% (%2)" ).arg( static_cast< int >( std::round( 100 * cur / tot ) ) ).arg( QgsFileUtils::representFileSize( tot ) )
1315                       : tr( "Downloading: %1" ).arg( QgsFileUtils::representFileSize( cur ) );
1316   mImage = renderMessage( msg );
1317   emit dataChanged();
1318 }
1319 
getLegendGraphicErrored(const QString &)1320 void QgsWmsLegendNode::getLegendGraphicErrored( const QString & )
1321 {
1322   if ( ! mFetcher )
1323     return; // must be coming after finish
1324 
1325   mImage = QImage();
1326   emit dataChanged();
1327 
1328   mFetcher.reset();
1329 
1330   mValid = true; // we consider it valid anyway
1331 }
1332 
getLegendGraphicFinished(const QImage & image)1333 void QgsWmsLegendNode::getLegendGraphicFinished( const QImage &image )
1334 {
1335   if ( ! mFetcher )
1336     return; // must be coming after error
1337 
1338   if ( ! image.isNull() )
1339   {
1340     if ( image != mImage )
1341     {
1342       mImage = image;
1343       setUserPatchSize( mImage.size() );
1344       emit dataChanged();
1345     }
1346     mValid = true; // only if not null I guess
1347   }
1348   mFetcher.reset();
1349 }
1350 
invalidateMapBasedData()1351 void QgsWmsLegendNode::invalidateMapBasedData()
1352 {
1353   // TODO: do this only if this extent != prev extent ?
1354   mValid = false;
1355   emit dataChanged();
1356 }
1357 
1358 // -------------------------------------------------------------------------
1359 
QgsDataDefinedSizeLegendNode(QgsLayerTreeLayer * nodeLayer,const QgsDataDefinedSizeLegend & settings,QObject * parent)1360 QgsDataDefinedSizeLegendNode::QgsDataDefinedSizeLegendNode( QgsLayerTreeLayer *nodeLayer, const QgsDataDefinedSizeLegend &settings, QObject *parent )
1361   : QgsLayerTreeModelLegendNode( nodeLayer, parent )
1362   , mSettings( new QgsDataDefinedSizeLegend( settings ) )
1363 {
1364 }
1365 
~QgsDataDefinedSizeLegendNode()1366 QgsDataDefinedSizeLegendNode::~QgsDataDefinedSizeLegendNode()
1367 {
1368   delete mSettings;
1369 }
1370 
data(int role) const1371 QVariant QgsDataDefinedSizeLegendNode::data( int role ) const
1372 {
1373   if ( role == Qt::DecorationRole )
1374   {
1375     cacheImage();
1376     return QPixmap::fromImage( mImage );
1377   }
1378   else if ( role == Qt::SizeHintRole )
1379   {
1380     cacheImage();
1381     return mImage.size();
1382   }
1383   else if ( role == QgsLayerTreeModelLegendNode::NodeTypeRole )
1384   {
1385     return QgsLayerTreeModelLegendNode::DataDefinedSizeLegend;
1386   }
1387   return QVariant();
1388 }
1389 
draw(const QgsLegendSettings & settings,QgsLayerTreeModelLegendNode::ItemContext * ctx)1390 QgsLayerTreeModelLegendNode::ItemMetrics QgsDataDefinedSizeLegendNode::draw( const QgsLegendSettings &settings, QgsLayerTreeModelLegendNode::ItemContext *ctx )
1391 {
1392   // setup temporary render context if none specified
1393   QgsRenderContext *context = nullptr;
1394   std::unique_ptr< QgsRenderContext > tempRenderContext;
1395   if ( ctx && ctx->context )
1396     context = ctx->context;
1397   else
1398   {
1399     tempRenderContext = std::make_unique< QgsRenderContext >();
1400     // QGIS 4.0 - make ItemContext compulsory, so we don't have to construct temporary render contexts here
1401     Q_NOWARN_DEPRECATED_PUSH
1402     tempRenderContext->setScaleFactor( settings.dpi() / 25.4 );
1403     tempRenderContext->setRendererScale( settings.mapScale() );
1404     tempRenderContext->setFlag( Qgis::RenderContextFlag::Antialiasing, true );
1405     tempRenderContext->setMapToPixel( QgsMapToPixel( 1 / ( settings.mmPerMapUnit() * tempRenderContext->scaleFactor() ) ) );
1406     tempRenderContext->setForceVectorOutput( true );
1407     tempRenderContext->setPainter( ctx ? ctx->painter : nullptr );
1408     tempRenderContext->setFlag( Qgis::RenderContextFlag::Antialiasing, true );
1409     Q_NOWARN_DEPRECATED_POP
1410 
1411     // setup a minimal expression context
1412     QgsExpressionContext expContext;
1413     expContext.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) );
1414     tempRenderContext->setExpressionContext( expContext );
1415     context = tempRenderContext.get();
1416   }
1417 
1418   if ( context->painter() )
1419   {
1420     context->painter()->save();
1421     context->painter()->translate( ctx->columnLeft, ctx->top );
1422 
1423     // scale to pixels
1424     context->painter()->scale( 1 / context->scaleFactor(), 1 / context->scaleFactor() );
1425   }
1426 
1427   QgsDataDefinedSizeLegend ddsLegend( *mSettings );
1428   ddsLegend.setFont( settings.style( QgsLegendStyle::SymbolLabel ).font() );
1429   ddsLegend.setTextColor( settings.fontColor() );
1430 
1431   QSizeF contentSize;
1432   double labelXOffset;
1433   ddsLegend.drawCollapsedLegend( *context, &contentSize, &labelXOffset );
1434 
1435   if ( context->painter() )
1436     context->painter()->restore();
1437 
1438   ItemMetrics im;
1439   im.symbolSize = QSizeF( ( contentSize.width() - labelXOffset ) / context->scaleFactor(), contentSize.height() / context->scaleFactor() );
1440   im.labelSize = QSizeF( labelXOffset / context->scaleFactor(), contentSize.height() / context->scaleFactor() );
1441   return im;
1442 }
1443 
1444 
cacheImage() const1445 void QgsDataDefinedSizeLegendNode::cacheImage() const
1446 {
1447   if ( mImage.isNull() )
1448   {
1449     std::unique_ptr<QgsRenderContext> context( createTemporaryRenderContext() );
1450     if ( !context )
1451     {
1452       context.reset( new QgsRenderContext );
1453       Q_ASSERT( context ); // to make cppcheck happy
1454       context->setScaleFactor( 96 / 25.4 );
1455     }
1456     mImage = mSettings->collapsedLegendImage( *context );
1457   }
1458 }
1459 
QgsVectorLabelLegendNode(QgsLayerTreeLayer * nodeLayer,const QgsPalLayerSettings & labelSettings,QObject * parent)1460 QgsVectorLabelLegendNode::QgsVectorLabelLegendNode( QgsLayerTreeLayer *nodeLayer, const QgsPalLayerSettings &labelSettings, QObject *parent ): QgsLayerTreeModelLegendNode( nodeLayer, parent ), mLabelSettings( labelSettings )
1461 {
1462 }
1463 
~QgsVectorLabelLegendNode()1464 QgsVectorLabelLegendNode::~QgsVectorLabelLegendNode()
1465 {
1466 }
1467 
data(int role) const1468 QVariant QgsVectorLabelLegendNode::data( int role ) const
1469 {
1470   if ( role == Qt::DisplayRole )
1471   {
1472     return mUserLabel;
1473   }
1474   if ( role == Qt::DecorationRole )
1475   {
1476     const int iconSize = QgsLayerTreeModel::scaleIconSize( 16 );
1477     return QgsPalLayerSettings::labelSettingsPreviewPixmap( mLabelSettings, QSize( iconSize, iconSize ), mLabelSettings.legendString() );
1478   }
1479   return QVariant();
1480 }
1481 
drawSymbol(const QgsLegendSettings & settings,ItemContext * ctx,double itemHeight) const1482 QSizeF QgsVectorLabelLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight ) const
1483 {
1484   Q_UNUSED( itemHeight );
1485   if ( !ctx )
1486   {
1487     return QSizeF( 0, 0 );
1488   }
1489 
1490   const QgsRenderContext *renderContext = ctx->context;
1491   if ( renderContext )
1492   {
1493     return drawSymbol( settings, *renderContext, ctx->columnLeft, ctx->top );
1494   }
1495 
1496   return QSizeF( 0, 0 );
1497 }
1498 
drawSymbol(const QgsLegendSettings & settings,const QgsRenderContext & renderContext,double xOffset,double yOffset) const1499 QSizeF QgsVectorLabelLegendNode::drawSymbol( const QgsLegendSettings &settings, const QgsRenderContext &renderContext, double xOffset, double yOffset ) const
1500 {
1501   const QStringList textLines( mLabelSettings.legendString() );
1502   const QgsTextFormat textFormat = mLabelSettings.format();
1503   QgsRenderContext ctx( renderContext );
1504   double textWidth, textHeight;
1505   textWidthHeight( textWidth, textHeight, ctx, textFormat, textLines );
1506   textWidth /= renderContext.scaleFactor();
1507   textHeight /= renderContext.scaleFactor();
1508   const QPointF textPos( renderContext.scaleFactor() * ( xOffset + settings.symbolSize().width() / 2.0 - textWidth / 2.0 ), renderContext.scaleFactor() * ( yOffset + settings.symbolSize().height() / 2.0 + textHeight / 2.0 ) );
1509 
1510   const QgsScopedRenderContextScaleToPixels scopedScaleToPixels( ctx );
1511   QgsTextRenderer::drawText( textPos, 0.0, QgsTextRenderer::AlignLeft, textLines, ctx, textFormat );
1512 
1513   const double symbolWidth = std::max( textWidth, settings.symbolSize().width() );
1514   const double symbolHeight = std::max( textHeight, settings.symbolSize().height() );
1515   return QSizeF( symbolWidth, symbolHeight );
1516 }
1517 
exportSymbolToJson(const QgsLegendSettings & settings,const QgsRenderContext & context) const1518 QJsonObject QgsVectorLabelLegendNode::exportSymbolToJson( const QgsLegendSettings &settings, const QgsRenderContext &context ) const
1519 {
1520   Q_UNUSED( settings );
1521 
1522   const double mmToPixel = 96.0 / 25.4; //settings.dpi() is deprecated
1523 
1524   const QStringList textLines( mLabelSettings.legendString() );
1525   const QgsTextFormat textFormat = mLabelSettings.format();
1526   QgsRenderContext ctx( context );
1527   ctx.setScaleFactor( mmToPixel );
1528 
1529   double textWidth, textHeight;
1530   textWidthHeight( textWidth, textHeight, ctx, textFormat, textLines );
1531   const QPixmap previewPixmap = mLabelSettings.labelSettingsPreviewPixmap( mLabelSettings, QSize( textWidth, textHeight ), mLabelSettings.legendString() );
1532 
1533   QByteArray byteArray;
1534   QBuffer buffer( &byteArray );
1535   previewPixmap.save( &buffer, "PNG" );
1536   const QString base64 = QString::fromLatin1( byteArray.toBase64().data() );
1537 
1538   QJsonObject json;
1539   json[ QStringLiteral( "icon" ) ] = base64;
1540   return json;
1541 }
1542 
textWidthHeight(double & width,double & height,QgsRenderContext & ctx,const QgsTextFormat & textFormat,const QStringList & textLines) const1543 void QgsVectorLabelLegendNode::textWidthHeight( double &width, double &height, QgsRenderContext &ctx, const QgsTextFormat &textFormat, const QStringList &textLines ) const
1544 {
1545   QFontMetricsF fm = QgsTextRenderer::fontMetrics( ctx, textFormat );
1546   height = QgsTextRenderer::textHeight( ctx, textFormat, 'A', true );
1547   width = QgsTextRenderer::textWidth( ctx, textFormat, textLines, &fm );
1548 }
1549 
1550