QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
qgslayertreemodellegendnode.cpp
Go to the documentation of this file.
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 
20 
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"
34 #include "qgsfeatureid.h"
35 #include "qgslayoutitem.h"
37 #include "qgsexpression.h"
38 #include "qgstextrenderer.h"
39 #include "qgssettings.h"
40 #include "qgsfileutils.h"
41 
42 #include <QBuffer>
43 
45  : QObject( parent )
46  , mLayerNode( nodeL )
47  , mEmbeddedInParent( false )
48 {
49 }
50 
52 {
53  return qobject_cast<QgsLayerTreeModel *>( parent() );
54 }
55 
57 {
58  return Qt::ItemIsEnabled;
59 }
60 
61 bool QgsLayerTreeModelLegendNode::setData( const QVariant &value, int role )
62 {
63  Q_UNUSED( value )
64  Q_UNUSED( role )
65  return false;
66 }
67 
69 {
70  if ( mEmbeddedInParent )
71  return mLayerNode->patchSize();
72 
73  return mUserSize;
74 }
75 
77 {
78  if ( mUserSize == size )
79  return;
80 
81  mUserSize = size;
82  emit sizeChanged();
83 }
84 
86 {
87  QFont symbolLabelFont = settings.style( QgsLegendStyle::SymbolLabel ).font();
88 
89  double textHeight = settings.fontHeightCharacterMM( symbolLabelFont, QChar( '0' ) );
90  // itemHeight here is not really item height, it is only for symbol
91  // vertical alignment purpose, i.e. OK take single line height
92  // if there are more lines, those run under the symbol
93  double itemHeight = std::max( static_cast< double >( ctx && ctx->patchSize.height() > 0 ? ctx->patchSize.height() : settings.symbolSize().height() ), textHeight );
94 
95  ItemMetrics im;
96  im.symbolSize = drawSymbol( settings, ctx, itemHeight );
97  im.labelSize = drawSymbolText( settings, ctx, im.symbolSize );
98  return im;
99 }
100 
102 {
103  QJsonObject json = exportSymbolToJson( settings, context );
104  const QString text = data( Qt::DisplayRole ).toString();
105  json[ QStringLiteral( "title" ) ] = text;
106  return json;
107 }
108 
109 QSizeF QgsLayerTreeModelLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight ) const
110 {
111  QIcon symbolIcon = data( Qt::DecorationRole ).value<QIcon>();
112  if ( symbolIcon.isNull() )
113  return QSizeF();
114 
115  QSizeF size = settings.symbolSize();
116  if ( ctx )
117  {
118  if ( ctx->patchSize.width() > 0 )
119  size.setWidth( ctx->patchSize.width( ) );
120  if ( ctx->patchSize.height() > 0 )
121  size.setHeight( ctx->patchSize.height( ) );
122  }
123 
124  if ( ctx && ctx->painter )
125  {
126  switch ( settings.symbolAlignment() )
127  {
128  case Qt::AlignLeft:
129  default:
130  symbolIcon.paint( ctx->painter,
131  static_cast< int >( ctx->columnLeft ),
132  static_cast< int >( ctx->top + ( itemHeight - size.height() ) / 2 ),
133  static_cast< int >( size.width() ),
134  static_cast< int >( size.height() ) );
135  break;
136 
137  case Qt::AlignRight:
138  symbolIcon.paint( ctx->painter,
139  static_cast< int >( ctx->columnRight - size.width() ),
140  static_cast< int >( ctx->top + ( itemHeight - size.height() ) / 2 ),
141  static_cast< int >( size.width() ),
142  static_cast< int >( size.height() ) );
143  break;
144  }
145  }
146  return size;
147 }
148 
150 {
151  const QIcon icon = data( Qt::DecorationRole ).value<QIcon>();
152  if ( icon.isNull() )
153  return QJsonObject();
154 
155  const QImage image( icon.pixmap( settings.symbolSize().width(), settings.symbolSize().height() ).toImage() );
156  QByteArray byteArray;
157  QBuffer buffer( &byteArray );
158  image.save( &buffer, "PNG" );
159  const QString base64 = QString::fromLatin1( byteArray.toBase64().data() );
160 
161  QJsonObject json;
162  json[ QStringLiteral( "icon" ) ] = base64;
163  return json;
164 }
165 
166 QSizeF QgsLayerTreeModelLegendNode::drawSymbolText( const QgsLegendSettings &settings, ItemContext *ctx, QSizeF symbolSize ) const
167 {
168  QSizeF labelSize( 0, 0 );
169 
170  QFont symbolLabelFont = settings.style( QgsLegendStyle::SymbolLabel ).font();
171  double textHeight = settings.fontHeightCharacterMM( symbolLabelFont, QChar( '0' ) );
172  double textDescent = settings.fontDescentMillimeters( symbolLabelFont );
173 
174  QgsExpressionContext tempContext;
175 
176  const QStringList lines = settings.evaluateItemText( data( Qt::DisplayRole ).toString(), ctx && ctx->context ? ctx->context->expressionContext() : tempContext );
177 
178  labelSize.rheight() = lines.count() * textHeight + ( lines.count() - 1 ) * ( settings.lineSpacing() + textDescent );
179 
180  double labelXMin = 0.0;
181  double labelXMax = 0.0;
182  double labelY = 0.0;
183  if ( ctx && ctx->painter )
184  {
185  ctx->painter->setPen( settings.fontColor() );
186  switch ( settings.symbolAlignment() )
187  {
188  case Qt::AlignLeft:
189  default:
190  labelXMin = ctx->columnLeft + std::max( static_cast< double >( symbolSize.width() ), ctx->maxSiblingSymbolWidth )
193  labelXMax = ctx->columnRight;
194  break;
195 
196  case Qt::AlignRight:
197  labelXMin = ctx->columnLeft;
198  // NOTE -- while the below calculations use the flipped margins from the style, that's only done because
199  // those are the only margins we expose and use for now! (and we expose them as generic margins, not side-specific
200  // ones) TODO when/if we expose other margin settings, these should be reversed...
201  labelXMax = ctx->columnRight - std::max( static_cast< double >( symbolSize.width() ), ctx->maxSiblingSymbolWidth )
204  break;
205  }
206 
207  labelY = ctx->top;
208 
209  // Vertical alignment of label with symbol
210  if ( labelSize.height() < symbolSize.height() )
211  labelY += symbolSize.height() / 2 - labelSize.height() / 2; // label centered with symbol
212 
213  labelY += textHeight;
214  }
215 
216  for ( QStringList::ConstIterator itemPart = lines.constBegin(); itemPart != lines.constEnd(); ++itemPart )
217  {
218  const double lineWidth = settings.textWidthMillimeters( symbolLabelFont, *itemPart );
219  labelSize.rwidth() = std::max( lineWidth, double( labelSize.width() ) );
220 
221  if ( ctx && ctx->painter )
222  {
223  switch ( settings.style( QgsLegendStyle::SymbolLabel ).alignment() )
224  {
225  case Qt::AlignLeft:
226  default:
227  settings.drawText( ctx->painter, labelXMin, labelY, *itemPart, symbolLabelFont );
228  break;
229 
230  case Qt::AlignRight:
231  settings.drawText( ctx->painter, labelXMax - lineWidth, labelY, *itemPart, symbolLabelFont );
232  break;
233 
234  case Qt::AlignHCenter:
235  settings.drawText( ctx->painter, labelXMin + ( labelXMax - labelXMin - lineWidth ) / 2.0, labelY, *itemPart, symbolLabelFont );
236  break;
237  }
238 
239  if ( itemPart != ( lines.end() - 1 ) )
240  labelY += textDescent + settings.lineSpacing() + textHeight;
241  }
242  }
243 
244  return labelSize;
245 }
246 
248 {
249  checkAll( true );
250 }
251 
253 {
254  checkAll( false );
255 }
256 
258 {
259  if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() ) )
260  {
261  if ( !vlayer->renderer() )
262  return;
263 
264  const QgsLegendSymbolList symbolList = vlayer->renderer()->legendSymbolItems();
265  for ( const auto &item : symbolList )
266  {
267  vlayer->renderer()->checkLegendSymbolItem( item.ruleKey(), ! vlayer->renderer()->legendSymbolItemChecked( item.ruleKey() ) );
268  }
269 
270  emit dataChanged();
271  vlayer->emitStyleChanged();
272  vlayer->triggerRepaint();
273  }
274  else if ( QgsPointCloudLayer *pclayer = qobject_cast<QgsPointCloudLayer *>( mLayerNode->layer() ) )
275  {
276  if ( !pclayer->renderer() )
277  return;
278 
279  const QStringList ruleKeys = pclayer->renderer()->legendRuleKeys();
280  for ( const QString &rule : ruleKeys )
281  {
282  pclayer->renderer()->checkLegendItem( rule, !pclayer->renderer()->legendItemChecked( rule ) );
283  }
284 
285  emit dataChanged();
286  pclayer->emitStyleChanged();
287  pclayer->triggerRepaint();
288  }
289 }
290 
291 // -------------------------------------------------------------------------
292 
295 
297  : QgsLayerTreeModelLegendNode( nodeLayer, parent )
298  , mItem( item )
299  , mSymbolUsesMapUnits( false )
300 {
301  const int iconSize = QgsLayerTreeModel::scaleIconSize( 16 );
302  mIconSize = QSize( iconSize, iconSize );
303 
304  if ( MINIMUM_SIZE < 0 )
305  {
306  // it's FAR too expensive to construct a QgsSettings object for every symbol node, especially for complex
307  // projects. So only read the valid size ranges once, and store them for subsequent use
308  QgsSettings settings;
309  MINIMUM_SIZE = settings.value( "/qgis/legendsymbolMinimumSize", 0.5 ).toDouble();
310  MAXIMUM_SIZE = settings.value( "/qgis/legendsymbolMaximumSize", 20.0 ).toDouble();
311  }
312 
313  updateLabel();
314  if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( nodeLayer->layer() ) )
315  connect( vl, &QgsVectorLayer::symbolFeatureCountMapChanged, this, &QgsSymbolLegendNode::updateLabel );
316 
317  connect( nodeLayer, &QObject::destroyed, this, [ = ]() { mLayerNode = nullptr; } );
318 
319  if ( const QgsSymbol *symbol = mItem.symbol() )
320  {
321  mSymbolUsesMapUnits = symbol->usesMapUnits();
322  }
323 }
324 
325 Qt::ItemFlags QgsSymbolLegendNode::flags() const
326 {
327  if ( mItem.isCheckable() )
328  return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
329  else
330  return Qt::ItemIsEnabled;
331 }
332 
333 
335 {
336  std::unique_ptr<QgsRenderContext> context( createTemporaryRenderContext() );
337  return minimumIconSize( context.get() );
338 }
339 
341 {
342  const int iconSize = QgsLayerTreeModel::scaleIconSize( 16 );
343  const int largeIconSize = QgsLayerTreeModel::scaleIconSize( 512 );
344  QSize minSz( iconSize, iconSize );
345  if ( mItem.symbol() && mItem.symbol()->type() == QgsSymbol::Marker )
346  {
347  // unusued width, height variables
348  double width = 0.0;
349  double height = 0.0;
350  std::unique_ptr<QgsSymbol> symbol( QgsSymbolLayerUtils::restrictedSizeSymbol( mItem.symbol(), MINIMUM_SIZE, MAXIMUM_SIZE, context, width, height ) );
352  QgsSymbolLayerUtils::symbolPreviewPixmap( symbol ? symbol.get() : mItem.symbol(), QSize( largeIconSize, largeIconSize ), 0,
353  context ).toImage(),
354  minSz,
355  true ).size();
356  }
357  else if ( mItem.symbol() && mItem.symbol()->type() == QgsSymbol::Line )
358  {
359  double width = 0.0;
360  double height = 0.0;
361  std::unique_ptr<QgsSymbol> symbol( QgsSymbolLayerUtils::restrictedSizeSymbol( mItem.symbol(), MINIMUM_SIZE, MAXIMUM_SIZE, context, width, height ) );
363  QgsSymbolLayerUtils::symbolPreviewPixmap( symbol ? symbol.get() : mItem.symbol(), QSize( minSz.width(), largeIconSize ), 0,
364  context ).toImage(),
365  minSz,
366  true ).size();
367  }
368 
369  if ( !mTextOnSymbolLabel.isEmpty() && context )
370  {
371  double w = QgsTextRenderer::textWidth( *context, mTextOnSymbolTextFormat, QStringList() << mTextOnSymbolLabel );
372  double h = QgsTextRenderer::textHeight( *context, mTextOnSymbolTextFormat, QStringList() << mTextOnSymbolLabel, QgsTextRenderer::Point );
373  int wInt = ceil( w ), hInt = ceil( h );
374  if ( wInt > minSz.width() ) minSz.setWidth( wInt );
375  if ( hInt > minSz.height() ) minSz.setHeight( hInt );
376  }
377 
378  return minSz;
379 }
380 
382 {
383  return mItem.symbol();
384 }
385 
387 {
388  QString label;
389  if ( mEmbeddedInParent )
390  {
391  QVariant legendlabel = mLayerNode->customProperty( QStringLiteral( "legend/title-label" ) );
392  QString layerName = legendlabel.isNull() ? mLayerNode->name() : legendlabel.toString();
393  label = mUserLabel.isEmpty() ? layerName : mUserLabel;
394  }
395  else
396  label = mUserLabel.isEmpty() ? mItem.label() : mUserLabel;
397  return label;
398 }
399 
401 {
402  if ( mEmbeddedInParent )
403  {
404  return mLayerNode->patchShape();
405  }
406  else
407  {
408  return mPatchShape;
409  }
410 }
411 
413 {
414  mPatchShape = shape;
415 }
416 
418 {
419  return mCustomSymbol.get();
420 }
421 
423 {
424  mCustomSymbol.reset( symbol );
425 }
426 
428 {
429  if ( !symbol )
430  return;
431 
432  std::unique_ptr< QgsSymbol > s( symbol ); // this method takes ownership of symbol
433  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() );
434  if ( !vlayer || !vlayer->renderer() )
435  return;
436 
437  mItem.setSymbol( s.get() ); // doesn't transfer ownership
438  vlayer->renderer()->setLegendSymbolItem( mItem.ruleKey(), s.release() ); // DOES transfer ownership!
439 
440  mPixmap = QPixmap();
441 
442  emit dataChanged();
443  vlayer->triggerRepaint();
444 }
445 
447 {
448  double scale = 0.0;
449  double mupp = 0.0;
450  int dpi = 0;
451  if ( auto *lModel = model() )
452  lModel->legendMapViewData( &mupp, &dpi, &scale );
453 
454  if ( qgsDoubleNear( mupp, 0.0 ) || dpi == 0 || qgsDoubleNear( scale, 0.0 ) )
455  return nullptr;
456 
457  // setup temporary render context
458  std::unique_ptr<QgsRenderContext> context = qgis::make_unique<QgsRenderContext>( );
459  context->setScaleFactor( dpi / 25.4 );
460  context->setRendererScale( scale );
461  context->setMapToPixel( QgsMapToPixel( mupp ) );
462  context->setFlag( QgsRenderContext::Antialiasing, true );
463  context->setFlag( QgsRenderContext::RenderSymbolPreview, true );
464  return context.release();
465 }
466 
467 void QgsLayerTreeModelLegendNode::checkAll( bool state )
468 {
469  if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() ) )
470  {
471  if ( !vlayer->renderer() )
472  return;
473 
474  const QgsLegendSymbolList symbolList = vlayer->renderer()->legendSymbolItems();
475  for ( const auto &item : symbolList )
476  {
477  vlayer->renderer()->checkLegendSymbolItem( item.ruleKey(), state );
478  }
479 
480  emit dataChanged();
481  vlayer->emitStyleChanged();
482  vlayer->triggerRepaint();
483  }
484  else if ( QgsPointCloudLayer *pclayer = qobject_cast<QgsPointCloudLayer *>( mLayerNode->layer() ) )
485  {
486  if ( !pclayer->renderer() )
487  return;
488 
489  const QStringList ruleKeys = pclayer->renderer()->legendRuleKeys();
490  for ( const QString &rule : ruleKeys )
491  {
492  pclayer->renderer()->checkLegendItem( rule, state );
493  }
494 
495  emit dataChanged();
496  pclayer->emitStyleChanged();
497  pclayer->triggerRepaint();
498  }
499 }
500 
501 QVariant QgsSymbolLegendNode::data( int role ) const
502 {
503  if ( role == Qt::DisplayRole )
504  {
505  return mLabel;
506  }
507  else if ( role == Qt::EditRole )
508  {
509  return mUserLabel.isEmpty() ? mItem.label() : mUserLabel;
510  }
511  else if ( role == Qt::DecorationRole )
512  {
513  if ( mPixmap.isNull() || mPixmap.size() != mIconSize )
514  {
515  QPixmap pix;
516  if ( mItem.symbol() )
517  {
518  std::unique_ptr<QgsRenderContext> context( createTemporaryRenderContext() );
519 
520  // unusued width, height variables
521  double width = 0.0;
522  double height = 0.0;
523  std::unique_ptr<QgsSymbol> symbol( QgsSymbolLayerUtils::restrictedSizeSymbol( mItem.symbol(), MINIMUM_SIZE, MAXIMUM_SIZE, context.get(), width, height ) );
524  pix = QgsSymbolLayerUtils::symbolPreviewPixmap( symbol ? symbol.get() : mItem.symbol(), mIconSize, 0, context.get() );
525 
526  if ( !mTextOnSymbolLabel.isEmpty() && context )
527  {
528  QPainter painter( &pix );
529  painter.setRenderHint( QPainter::Antialiasing );
530  context->setPainter( &painter );
531  QFontMetricsF fm( mTextOnSymbolTextFormat.scaledFont( *context ) );
532  qreal yBaselineVCenter = ( mIconSize.height() + fm.ascent() - fm.descent() ) / 2;
533  QgsTextRenderer::drawText( QPointF( mIconSize.width() / 2, yBaselineVCenter ), 0, QgsTextRenderer::AlignCenter,
534  QStringList() << mTextOnSymbolLabel, *context, mTextOnSymbolTextFormat );
535  }
536  }
537  else
538  {
539  pix = QPixmap( mIconSize );
540  pix.fill( Qt::transparent );
541  }
542 
543  mPixmap = pix;
544  }
545  return mPixmap;
546  }
547  else if ( role == Qt::CheckStateRole )
548  {
549  if ( !mItem.isCheckable() )
550  return QVariant();
551 
552  if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() ) )
553  {
554  if ( !vlayer->renderer() )
555  return QVariant();
556 
557  return vlayer->renderer()->legendSymbolItemChecked( mItem.ruleKey() ) ? Qt::Checked : Qt::Unchecked;
558  }
559  }
560  else if ( role == RuleKeyRole )
561  {
562  return mItem.ruleKey();
563  }
564  else if ( role == ParentRuleKeyRole )
565  {
566  return mItem.parentRuleKey();
567  }
569  {
571  }
572 
573  return QVariant();
574 }
575 
576 bool QgsSymbolLegendNode::setData( const QVariant &value, int role )
577 {
578  if ( role != Qt::CheckStateRole )
579  return false;
580 
581  if ( !mItem.isCheckable() )
582  return false;
583 
584  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() );
585  if ( !vlayer || !vlayer->renderer() )
586  return false;
587 
588  vlayer->renderer()->checkLegendSymbolItem( mItem.ruleKey(), value == Qt::Checked );
589 
590  emit dataChanged();
591  vlayer->emitStyleChanged();
592 
593  vlayer->triggerRepaint();
594 
595  return true;
596 }
597 
598 
599 
600 QSizeF QgsSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight ) const
601 {
602  QgsSymbol *s = mCustomSymbol ? mCustomSymbol.get() : mItem.symbol();
603  if ( !s )
604  {
605  return QSizeF();
606  }
607 
608  // setup temporary render context
609  QgsRenderContext *context = nullptr;
610  std::unique_ptr< QgsRenderContext > tempRenderContext;
612  if ( ctx && ctx->context )
613  context = ctx->context;
614  else
615  {
616  tempRenderContext = qgis::make_unique< QgsRenderContext >();
617  // QGIS 4.0 - make ItemContext compulsory, so we don't have to construct temporary render contexts here
619  tempRenderContext->setScaleFactor( settings.dpi() / 25.4 );
620  tempRenderContext->setRendererScale( settings.mapScale() );
621  tempRenderContext->setFlag( QgsRenderContext::Antialiasing, true );
622  tempRenderContext->setMapToPixel( QgsMapToPixel( 1 / ( settings.mmPerMapUnit() * tempRenderContext->scaleFactor() ) ) );
624  tempRenderContext->setForceVectorOutput( true );
625  tempRenderContext->setPainter( ctx ? ctx->painter : nullptr );
626 
627  // setup a minimal expression context
628  QgsExpressionContext expContext;
630  tempRenderContext->setExpressionContext( expContext );
631  context = tempRenderContext.get();
632  }
633 
634  //Consider symbol size for point markers
635  const double desiredHeight = ctx && ctx->patchSize.height() > 0 ? ctx->patchSize.height() : settings.symbolSize().height();
636  const double desiredWidth = ctx && ctx->patchSize.width() > 0 ? ctx->patchSize.width() : settings.symbolSize().width();
637  double height = desiredHeight;
638  double width = desiredWidth;
639 
640  //Center small marker symbols
641  double widthOffset = 0;
642  double heightOffset = 0;
643 
644  double maxSymbolSize = settings.maximumSymbolSize();
645  double minSymbolSize = settings.minimumSymbolSize();
646 
647  if ( QgsMarkerSymbol *markerSymbol = dynamic_cast<QgsMarkerSymbol *>( s ) )
648  {
649  double size = markerSymbol->size( *context ) / context->scaleFactor();
650  height = size;
651  width = size;
652  }
653 
654  std::unique_ptr<QgsSymbol> minMaxSizeSymbol( QgsSymbolLayerUtils::restrictedSizeSymbol( s, minSymbolSize, maxSymbolSize, context, width, height ) );
655  if ( minMaxSizeSymbol )
656  {
657  s = minMaxSizeSymbol.get();
658  }
659 
660  if ( s->type() == QgsSymbol::Marker )
661  {
662  if ( width < desiredWidth )
663  {
664  widthOffset = ( desiredWidth - width ) / 2.0;
665  }
666  if ( height < desiredHeight )
667  {
668  heightOffset = ( desiredHeight - height ) / 2.0;
669  }
670  }
671  if ( ctx && ctx->painter )
672  {
673  double currentYCoord = ctx->top + ( itemHeight - desiredHeight ) / 2;
674  QPainter *p = ctx->painter;
675 
676  //setup painter scaling to dots so that raster symbology is drawn to scale
677  double dotsPerMM = context->scaleFactor();
678 
679  int opacity = 255;
680  if ( QgsMapLayer *layer = layerNode()->layer() )
681  opacity = static_cast<int >( std::round( 255 * layer->opacity() ) );
682 
683  QgsScopedQPainterState painterState( p );
684  context->setPainterFlagsUsingContext( p );
685 
686  switch ( settings.symbolAlignment() )
687  {
688  case Qt::AlignLeft:
689  default:
690  p->translate( ctx->columnLeft + widthOffset, currentYCoord + heightOffset );
691  break;
692  case Qt::AlignRight:
693  p->translate( ctx->columnRight - widthOffset - width, currentYCoord + heightOffset );
694  break;
695  }
696 
697  p->scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
699  // QGIS 4.0 -- ctx->context will be mandatory
700  const bool useAdvancedEffects = ctx->context ? ctx->context->flags() & QgsRenderContext::UseAdvancedEffects : settings.useAdvancedEffects();
702  if ( opacity != 255 && useAdvancedEffects )
703  {
704  const int maxBleed = static_cast< int >( std::ceil( QgsSymbolLayerUtils::estimateMaxSymbolBleed( s, *context ) ) );
705 
706  //semi transparent layer, so need to draw symbol to an image (to flatten it first)
707  //create image which is same size as legend rect, in case symbol bleeds outside its allotted space
708  const QSize symbolSize( static_cast< int >( std::round( width * dotsPerMM ) ), static_cast<int >( std::round( height * dotsPerMM ) ) );
709  const QSize tempImageSize( symbolSize.width() + maxBleed * 2, symbolSize.height() + maxBleed * 2 );
710  QImage tempImage = QImage( tempImageSize, QImage::Format_ARGB32 );
711  tempImage.fill( Qt::transparent );
712  QPainter imagePainter( &tempImage );
713  context->setPainterFlagsUsingContext( &imagePainter );
714 
715  context->setPainter( &imagePainter );
716  imagePainter.translate( maxBleed, maxBleed );
717  s->drawPreviewIcon( &imagePainter, symbolSize, context, false, nullptr, &patchShape );
718  imagePainter.translate( -maxBleed, -maxBleed );
719  context->setPainter( ctx->painter );
720  //reduce opacity of image
721  imagePainter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
722  imagePainter.fillRect( tempImage.rect(), QColor( 0, 0, 0, opacity ) );
723  imagePainter.end();
724  //draw rendered symbol image
725  p->drawImage( -maxBleed, -maxBleed, tempImage );
726  }
727  else
728  {
729  s->drawPreviewIcon( p, QSize( static_cast< int >( std::round( width * dotsPerMM ) ), static_cast< int >( std::round( height * dotsPerMM ) ) ), context, false, nullptr, &patchShape );
730  }
731 
732  if ( !mTextOnSymbolLabel.isEmpty() )
733  {
734  QFontMetricsF fm( mTextOnSymbolTextFormat.scaledFont( *context ) );
735  qreal yBaselineVCenter = ( height * dotsPerMM + fm.ascent() - fm.descent() ) / 2;
736  QgsTextRenderer::drawText( QPointF( width * dotsPerMM / 2, yBaselineVCenter ), 0, QgsTextRenderer::AlignCenter,
737  QStringList() << mTextOnSymbolLabel, *context, mTextOnSymbolTextFormat );
738  }
739  }
740 
741  return QSizeF( std::max( width + 2 * widthOffset, static_cast< double >( desiredWidth ) ),
742  std::max( height + 2 * heightOffset, static_cast< double >( desiredHeight ) ) );
743 }
744 
745 QJsonObject QgsSymbolLegendNode::exportSymbolToJson( const QgsLegendSettings &settings, const QgsRenderContext &context ) const
746 {
747  const QgsSymbol *s = mCustomSymbol ? mCustomSymbol.get() : mItem.symbol();
748  if ( !s )
749  {
750  return QJsonObject();
751  }
752 
753 
754  QgsRenderContext ctx;
755  // QGIS 4.0 - use render context directly here, and note in the dox that the context must be correctly setup
757  ctx.setScaleFactor( settings.dpi() / 25.4 );
758  ctx.setRendererScale( settings.mapScale() );
759  ctx.setMapToPixel( QgsMapToPixel( 1 / ( settings.mmPerMapUnit() * ctx.scaleFactor() ) ) );
760  ctx.setForceVectorOutput( true );
763 
765 
766  // ensure that a minimal expression context is available
767  QgsExpressionContext expContext = context.expressionContext();
769  ctx.setExpressionContext( expContext );
770 
771  const QPixmap pix = QgsSymbolLayerUtils::symbolPreviewPixmap( mItem.symbol(), minimumIconSize(), 0, &ctx );
772  QImage img( pix.toImage().convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
773 
774  int opacity = 255;
775  if ( QgsMapLayer *layer = layerNode()->layer() )
776  opacity = ( 255 * layer->opacity() );
777 
778  if ( opacity != 255 )
779  {
780  QPainter painter;
781  painter.begin( &img );
782  painter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
783  painter.fillRect( pix.rect(), QColor( 0, 0, 0, opacity ) );
784  painter.end();
785  }
786 
787  QByteArray byteArray;
788  QBuffer buffer( &byteArray );
789  img.save( &buffer, "PNG" );
790  const QString base64 = QString::fromLatin1( byteArray.toBase64().data() );
791 
792  QJsonObject json;
793  json[ QStringLiteral( "icon" ) ] = base64;
794  if ( mItem.scaleMaxDenom() > 0 )
795  {
796  json[ QStringLiteral( "scaleMaxDenom" ) ] = mItem.scaleMaxDenom();
797  }
798  if ( mItem.scaleMinDenom() > 0 )
799  {
800  json[ QStringLiteral( "scaleMinDenom" ) ] = mItem.scaleMinDenom();
801  }
802  mItem.scaleMaxDenom();
803  return json;
804 }
805 
807 {
809  updateLabel();
810 }
811 
812 
814 {
815  if ( mSymbolUsesMapUnits )
816  {
817  mPixmap = QPixmap();
818  emit dataChanged();
819  }
820 }
821 
822 
823 void QgsSymbolLegendNode::updateLabel()
824 {
825  if ( !mLayerNode )
826  return;
827 
828  bool showFeatureCount = mLayerNode->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toBool();
829  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() );
830  mLabel = symbolLabel();
831 
832  if ( showFeatureCount && vl )
833  {
834  qlonglong count = mEmbeddedInParent ? vl->featureCount() : vl->featureCount( mItem.ruleKey() ) ;
835  mLabel += QStringLiteral( " [%1]" ).arg( count != -1 ? QLocale().toString( count ) : tr( "N/A" ) );
836  }
837 
838  emit dataChanged();
839 }
840 
841 QString QgsSymbolLegendNode::evaluateLabel( const QgsExpressionContext &context, const QString &label )
842 {
843  if ( !mLayerNode )
844  return QString();
845 
846  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() );
847 
848  if ( vl )
849  {
850  QgsExpressionContext contextCopy = QgsExpressionContext( context );
851  QgsExpressionContextScope *symbolScope = createSymbolScope();
852  contextCopy.appendScope( symbolScope );
853  contextCopy.appendScope( vl->createExpressionContextScope() );
854 
855  if ( label.isEmpty() )
856  {
857  if ( ! mLayerNode->labelExpression().isEmpty() )
858  mLabel = QgsExpression::replaceExpressionText( "[%" + mLayerNode->labelExpression() + "%]", &contextCopy );
859  else if ( mLabel.contains( "[%" ) )
860  {
861  const QString symLabel = symbolLabel();
862  mLabel = QgsExpression::replaceExpressionText( symLabel, &contextCopy );
863  }
864  return mLabel;
865  }
866  else
867  {
868  QString eLabel;
869  if ( ! mLayerNode->labelExpression().isEmpty() )
870  eLabel = QgsExpression::replaceExpressionText( label + "[%" + mLayerNode->labelExpression() + "%]", &contextCopy );
871  else if ( label.contains( "[%" ) )
872  eLabel = QgsExpression::replaceExpressionText( label, &contextCopy );
873  return eLabel;
874  }
875  }
876  return mLabel;
877 }
878 
879 QgsExpressionContextScope *QgsSymbolLegendNode::createSymbolScope() const
880 {
881  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mLayerNode->layer() );
882 
883  QgsExpressionContextScope *scope = new QgsExpressionContextScope( tr( "Symbol scope" ) );
884  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_label" ), symbolLabel().remove( "[%" ).remove( "%]" ), true ) );
885  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_id" ), mItem.ruleKey(), true ) );
886  if ( vl )
887  {
888  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_count" ), QVariant::fromValue( vl->featureCount( mItem.ruleKey() ) ), true ) );
889  }
890  return scope;
891 }
892 
893 // -------------------------------------------------------------------------
894 
895 
896 QgsSimpleLegendNode::QgsSimpleLegendNode( QgsLayerTreeLayer *nodeLayer, const QString &label, const QIcon &icon, QObject *parent, const QString &key )
897  : QgsLayerTreeModelLegendNode( nodeLayer, parent )
898  , mLabel( label )
899  , mIcon( icon )
900  , mKey( key )
901 {
902 }
903 
904 QVariant QgsSimpleLegendNode::data( int role ) const
905 {
906  if ( role == Qt::DisplayRole || role == Qt::EditRole )
907  return mUserLabel.isEmpty() ? mLabel : mUserLabel;
908  else if ( role == Qt::DecorationRole )
909  return mIcon;
910  else if ( role == RuleKeyRole && !mKey.isEmpty() )
911  return mKey;
914  else
915  return QVariant();
916 }
917 
918 
919 // -------------------------------------------------------------------------
920 
921 QgsImageLegendNode::QgsImageLegendNode( QgsLayerTreeLayer *nodeLayer, const QImage &img, QObject *parent )
922  : QgsLayerTreeModelLegendNode( nodeLayer, parent )
923  , mImage( img )
924 {
925 }
926 
927 QVariant QgsImageLegendNode::data( int role ) const
928 {
929  if ( role == Qt::DecorationRole )
930  {
931  return QPixmap::fromImage( mImage );
932  }
933  else if ( role == Qt::SizeHintRole )
934  {
935  return mImage.size();
936  }
938  {
940  }
941  return QVariant();
942 }
943 
944 QSizeF QgsImageLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight ) const
945 {
946  Q_UNUSED( itemHeight )
947 
948  if ( ctx && ctx->painter )
949  {
950  switch ( settings.symbolAlignment() )
951  {
952  case Qt::AlignLeft:
953  default:
954  ctx->painter->drawImage( QRectF( ctx->columnLeft, ctx->top, settings.wmsLegendSize().width(), settings.wmsLegendSize().height() ),
955  mImage, QRectF( 0, 0, mImage.width(), mImage.height() ) );
956  break;
957 
958  case Qt::AlignRight:
959  ctx->painter->drawImage( QRectF( ctx->columnRight - settings.wmsLegendSize().width(), ctx->top, settings.wmsLegendSize().width(), settings.wmsLegendSize().height() ),
960  mImage, QRectF( 0, 0, mImage.width(), mImage.height() ) );
961  break;
962  }
963  }
964  return settings.wmsLegendSize();
965 }
966 
968 {
969  QByteArray byteArray;
970  QBuffer buffer( &byteArray );
971  mImage.save( &buffer, "PNG" );
972  const QString base64 = QString::fromLatin1( byteArray.toBase64().data() );
973 
974  QJsonObject json;
975  json[ QStringLiteral( "icon" ) ] = base64;
976  return json;
977 }
978 
979 // -------------------------------------------------------------------------
980 
981 QgsRasterSymbolLegendNode::QgsRasterSymbolLegendNode( QgsLayerTreeLayer *nodeLayer, const QColor &color, const QString &label, QObject *parent, bool isCheckable, const QString &ruleKey )
982  : QgsLayerTreeModelLegendNode( nodeLayer, parent )
983  , mColor( color )
984  , mLabel( label )
985  , mCheckable( isCheckable )
986  , mRuleKey( ruleKey )
987 {
988 }
989 
990 Qt::ItemFlags QgsRasterSymbolLegendNode::flags() const
991 {
992  if ( mCheckable )
993  return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
994  else
995  return Qt::ItemIsEnabled;
996 }
997 
998 QVariant QgsRasterSymbolLegendNode::data( int role ) const
999 {
1000  switch ( role )
1001  {
1002  case Qt::DecorationRole:
1003  {
1004  const int iconSize = QgsLayerTreeModel::scaleIconSize( 16 ); // TODO: configurable?
1005  QPixmap pix( iconSize, iconSize );
1006  pix.fill( mColor );
1007  return QIcon( pix );
1008  }
1009 
1010  case Qt::DisplayRole:
1011  case Qt::EditRole:
1012  return mUserLabel.isEmpty() ? mLabel : mUserLabel;
1013 
1016 
1018  return mRuleKey;
1019 
1020  case Qt::CheckStateRole:
1021  {
1022  if ( !mCheckable )
1023  return QVariant();
1024 
1025  if ( QgsPointCloudLayer *pclayer = qobject_cast<QgsPointCloudLayer *>( mLayerNode->layer() ) )
1026  {
1027  if ( !pclayer->renderer() )
1028  return QVariant();
1029 
1030  return pclayer->renderer()->legendItemChecked( mRuleKey ) ? Qt::Checked : Qt::Unchecked;
1031  }
1032 
1033  return QVariant();
1034  }
1035 
1036  default:
1037  return QVariant();
1038  }
1039 }
1040 
1041 bool QgsRasterSymbolLegendNode::setData( const QVariant &value, int role )
1042 {
1043  if ( role != Qt::CheckStateRole )
1044  return false;
1045 
1046  if ( !mCheckable )
1047  return false;
1048 
1049  if ( QgsPointCloudLayer *pclayer = qobject_cast<QgsPointCloudLayer *>( mLayerNode->layer() ) )
1050  {
1051  if ( !pclayer->renderer() )
1052  return false;
1053 
1054  pclayer->renderer()->checkLegendItem( mRuleKey, value == Qt::Checked );
1055 
1056  emit dataChanged();
1057  pclayer->emitStyleChanged();
1058 
1059  pclayer->triggerRepaint();
1060  return true;
1061  }
1062  else
1063  {
1064  return false;
1065  }
1066 }
1067 
1068 
1069 QSizeF QgsRasterSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight ) const
1070 {
1071  QSizeF size = settings.symbolSize();
1072  double offsetX = 0;
1073  if ( ctx )
1074  {
1075  if ( ctx->patchSize.width() > 0 )
1076  {
1077  if ( ctx->patchSize.width() < size.width() )
1078  offsetX = ( size.width() - ctx->patchSize.width() ) / 2.0;
1079  size.setWidth( ctx->patchSize.width() );
1080  }
1081  if ( ctx->patchSize.height() > 0 )
1082  {
1083  size.setHeight( ctx->patchSize.height() );
1084  }
1085  }
1086 
1087  if ( ctx && ctx->painter )
1088  {
1089  QColor itemColor = mColor;
1090  if ( QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layerNode()->layer() ) )
1091  {
1092  if ( QgsRasterRenderer *rasterRenderer = rasterLayer->renderer() )
1093  itemColor.setAlpha( rasterRenderer->opacity() * 255.0 );
1094  }
1095  ctx->painter->setBrush( itemColor );
1096 
1097  if ( settings.drawRasterStroke() )
1098  {
1099  QPen pen;
1100  pen.setColor( settings.rasterStrokeColor() );
1101  pen.setWidthF( settings.rasterStrokeWidth() );
1102  pen.setJoinStyle( Qt::MiterJoin );
1103  ctx->painter->setPen( pen );
1104  }
1105  else
1106  {
1107  ctx->painter->setPen( Qt::NoPen );
1108  }
1109 
1110  switch ( settings.symbolAlignment() )
1111  {
1112  case Qt::AlignLeft:
1113  default:
1114  ctx->painter->drawRect( QRectF( ctx->columnLeft + offsetX, ctx->top + ( itemHeight - size.height() ) / 2,
1115  size.width(), size.height() ) );
1116  break;
1117 
1118  case Qt::AlignRight:
1119  ctx->painter->drawRect( QRectF( ctx->columnRight - size.width() - offsetX, ctx->top + ( itemHeight - size.height() ) / 2,
1120  size.width(), size.height() ) );
1121  break;
1122  }
1123  }
1124  return size;
1125 }
1126 
1128 {
1129  QImage img = QImage( settings.symbolSize().toSize(), QImage::Format_ARGB32 );
1130  img.fill( Qt::transparent );
1131 
1132  QPainter painter( &img );
1133  painter.setRenderHint( QPainter::Antialiasing );
1134 
1135  QColor itemColor = mColor;
1136  if ( QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layerNode()->layer() ) )
1137  {
1138  if ( QgsRasterRenderer *rasterRenderer = rasterLayer->renderer() )
1139  itemColor.setAlpha( rasterRenderer->opacity() * 255.0 );
1140  }
1141  painter.setBrush( itemColor );
1142 
1143  if ( settings.drawRasterStroke() )
1144  {
1145  QPen pen;
1146  pen.setColor( settings.rasterStrokeColor() );
1147  pen.setWidthF( settings.rasterStrokeWidth() );
1148  pen.setJoinStyle( Qt::MiterJoin );
1149  painter.setPen( pen );
1150  }
1151  else
1152  {
1153  painter.setPen( Qt::NoPen );
1154  }
1155 
1156  painter.drawRect( QRectF( 0, 0, settings.symbolSize().width(), settings.symbolSize().height() ) );
1157 
1158  QByteArray byteArray;
1159  QBuffer buffer( &byteArray );
1160  img.save( &buffer, "PNG" );
1161  const QString base64 = QString::fromLatin1( byteArray.toBase64().data() );
1162 
1163  QJsonObject json;
1164  json[ QStringLiteral( "icon" ) ] = base64;
1165  return json;
1166 }
1167 
1168 // -------------------------------------------------------------------------
1169 
1171  : QgsLayerTreeModelLegendNode( nodeLayer, parent )
1172  , mValid( false )
1173 {
1174 }
1175 
1177 
1178 QImage QgsWmsLegendNode::getLegendGraphic() const
1179 {
1180  if ( ! mValid && ! mFetcher )
1181  {
1182  // or maybe in presence of a downloader we should just delete it
1183  // and start a new one ?
1184 
1185  QgsRasterLayer *layer = qobject_cast<QgsRasterLayer *>( mLayerNode->layer() );
1186  const QgsLayerTreeModel *mod = model();
1187  if ( ! mod )
1188  return mImage;
1189  const QgsMapSettings *ms = mod->legendFilterMapSettings();
1190 
1191  QgsRasterDataProvider *prov = layer->dataProvider();
1192  if ( ! prov )
1193  return mImage;
1194 
1195  Q_ASSERT( ! mFetcher );
1196  mFetcher.reset( prov->getLegendGraphicFetcher( ms ) );
1197  if ( mFetcher )
1198  {
1199  connect( mFetcher.get(), &QgsImageFetcher::finish, this, &QgsWmsLegendNode::getLegendGraphicFinished );
1200  connect( mFetcher.get(), &QgsImageFetcher::error, this, &QgsWmsLegendNode::getLegendGraphicErrored );
1201  connect( mFetcher.get(), &QgsImageFetcher::progress, this, &QgsWmsLegendNode::getLegendGraphicProgress );
1202  mFetcher->start();
1203  }
1204  }
1205 
1206  return mImage;
1207 }
1208 
1209 QVariant QgsWmsLegendNode::data( int role ) const
1210 {
1211  if ( role == Qt::DecorationRole )
1212  {
1213  return QPixmap::fromImage( getLegendGraphic() );
1214  }
1215  else if ( role == Qt::SizeHintRole )
1216  {
1217  return getLegendGraphic().size();
1218  }
1219  else if ( role == QgsLayerTreeModelLegendNode::NodeTypeRole )
1220  {
1222  }
1223  return QVariant();
1224 }
1225 
1226 QSizeF QgsWmsLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight ) const
1227 {
1228  Q_UNUSED( itemHeight )
1229 
1230  if ( ctx && ctx->painter )
1231  {
1232  switch ( settings.symbolAlignment() )
1233  {
1234  case Qt::AlignLeft:
1235  default:
1236  ctx->painter->drawImage( QRectF( ctx->columnLeft,
1237  ctx->top,
1238  settings.wmsLegendSize().width(),
1239  settings.wmsLegendSize().height() ),
1240  mImage,
1241  QRectF( QPointF( 0, 0 ), mImage.size() ) );
1242  break;
1243 
1244  case Qt::AlignRight:
1245  ctx->painter->drawImage( QRectF( ctx->columnRight - settings.wmsLegendSize().width(),
1246  ctx->top,
1247  settings.wmsLegendSize().width(),
1248  settings.wmsLegendSize().height() ),
1249  mImage,
1250  QRectF( QPointF( 0, 0 ), mImage.size() ) );
1251  break;
1252  }
1253  }
1254  return settings.wmsLegendSize();
1255 }
1256 
1258 {
1259  QByteArray byteArray;
1260  QBuffer buffer( &byteArray );
1261  mImage.save( &buffer, "PNG" );
1262  const QString base64 = QString::fromLatin1( byteArray.toBase64().data() );
1263 
1264  QJsonObject json;
1265  json[ QStringLiteral( "icon" ) ] = base64;
1266  return json;
1267 }
1268 
1269 QImage QgsWmsLegendNode::renderMessage( const QString &msg ) const
1270 {
1271  const int fontHeight = 10;
1272  const int margin = fontHeight / 2;
1273  const int nlines = 1;
1274 
1275  const int w = 512, h = fontHeight * nlines + margin * ( nlines + 1 );
1276  QImage image( w, h, QImage::Format_ARGB32_Premultiplied );
1277  QPainter painter;
1278  painter.begin( &image );
1279  painter.setPen( QColor( 255, 0, 0 ) );
1280  painter.setFont( QFont( QStringLiteral( "Chicago" ), fontHeight ) );
1281  painter.fillRect( 0, 0, w, h, QColor( 255, 255, 255 ) );
1282  painter.drawText( 0, margin + fontHeight, msg );
1283  //painter.drawText(0,2*(margin+fontHeight),tr("retrying in 5 seconds…"));
1284  painter.end();
1285 
1286  return image;
1287 }
1288 
1289 void QgsWmsLegendNode::getLegendGraphicProgress( qint64 cur, qint64 tot )
1290 {
1291  const QString msg = tot > 0 ? tr( "Downloading: %1% (%2)" ).arg( static_cast< int >( std::round( 100 * cur / tot ) ) ).arg( QgsFileUtils::representFileSize( tot ) )
1292  : tr( "Downloading: %1" ).arg( QgsFileUtils::representFileSize( cur ) );
1293  mImage = renderMessage( msg );
1294  emit dataChanged();
1295 }
1296 
1297 void QgsWmsLegendNode::getLegendGraphicErrored( const QString & )
1298 {
1299  if ( ! mFetcher )
1300  return; // must be coming after finish
1301 
1302  mImage = QImage();
1303  emit dataChanged();
1304 
1305  mFetcher.reset();
1306 
1307  mValid = true; // we consider it valid anyway
1308 }
1309 
1310 void QgsWmsLegendNode::getLegendGraphicFinished( const QImage &image )
1311 {
1312  if ( ! mFetcher )
1313  return; // must be coming after error
1314 
1315  if ( ! image.isNull() )
1316  {
1317  if ( image != mImage )
1318  {
1319  mImage = image;
1320  setUserPatchSize( mImage.size() );
1321  emit dataChanged();
1322  }
1323  mValid = true; // only if not null I guess
1324  }
1325  mFetcher.reset();
1326 }
1327 
1329 {
1330  // TODO: do this only if this extent != prev extent ?
1331  mValid = false;
1332  emit dataChanged();
1333 }
1334 
1335 // -------------------------------------------------------------------------
1336 
1338  : QgsLayerTreeModelLegendNode( nodeLayer, parent )
1339  , mSettings( new QgsDataDefinedSizeLegend( settings ) )
1340 {
1341 }
1342 
1344 {
1345  delete mSettings;
1346 }
1347 
1348 QVariant QgsDataDefinedSizeLegendNode::data( int role ) const
1349 {
1350  if ( role == Qt::DecorationRole )
1351  {
1352  cacheImage();
1353  return QPixmap::fromImage( mImage );
1354  }
1355  else if ( role == Qt::SizeHintRole )
1356  {
1357  cacheImage();
1358  return mImage.size();
1359  }
1360  else if ( role == QgsLayerTreeModelLegendNode::NodeTypeRole )
1361  {
1363  }
1364  return QVariant();
1365 }
1366 
1368 {
1369  // setup temporary render context if none specified
1370  QgsRenderContext *context = nullptr;
1371  std::unique_ptr< QgsRenderContext > tempRenderContext;
1372  if ( ctx && ctx->context )
1373  context = ctx->context;
1374  else
1375  {
1376  tempRenderContext = qgis::make_unique< QgsRenderContext >();
1377  // QGIS 4.0 - make ItemContext compulsory, so we don't have to construct temporary render contexts here
1379  tempRenderContext->setScaleFactor( settings.dpi() / 25.4 );
1380  tempRenderContext->setRendererScale( settings.mapScale() );
1381  tempRenderContext->setFlag( QgsRenderContext::Antialiasing, true );
1382  tempRenderContext->setMapToPixel( QgsMapToPixel( 1 / ( settings.mmPerMapUnit() * tempRenderContext->scaleFactor() ) ) );
1383  tempRenderContext->setForceVectorOutput( true );
1384  tempRenderContext->setPainter( ctx ? ctx->painter : nullptr );
1385  tempRenderContext->setFlag( QgsRenderContext::Antialiasing, true );
1387 
1388  // setup a minimal expression context
1389  QgsExpressionContext expContext;
1391  tempRenderContext->setExpressionContext( expContext );
1392  context = tempRenderContext.get();
1393  }
1394 
1395  if ( context->painter() )
1396  {
1397  context->painter()->save();
1398  context->painter()->translate( ctx->columnLeft, ctx->top );
1399 
1400  // scale to pixels
1401  context->painter()->scale( 1 / context->scaleFactor(), 1 / context->scaleFactor() );
1402  }
1403 
1404  QgsDataDefinedSizeLegend ddsLegend( *mSettings );
1405  ddsLegend.setFont( settings.style( QgsLegendStyle::SymbolLabel ).font() );
1406  ddsLegend.setTextColor( settings.fontColor() );
1407 
1408  QSizeF contentSize;
1409  double labelXOffset;
1410  ddsLegend.drawCollapsedLegend( *context, &contentSize, &labelXOffset );
1411 
1412  if ( context->painter() )
1413  context->painter()->restore();
1414 
1415  ItemMetrics im;
1416  im.symbolSize = QSizeF( ( contentSize.width() - labelXOffset ) / context->scaleFactor(), contentSize.height() / context->scaleFactor() );
1417  im.labelSize = QSizeF( labelXOffset / context->scaleFactor(), contentSize.height() / context->scaleFactor() );
1418  return im;
1419 }
1420 
1421 
1422 void QgsDataDefinedSizeLegendNode::cacheImage() const
1423 {
1424  if ( mImage.isNull() )
1425  {
1426  std::unique_ptr<QgsRenderContext> context( createTemporaryRenderContext() );
1427  if ( !context )
1428  {
1429  context.reset( new QgsRenderContext );
1430  Q_ASSERT( context ); // to make cppcheck happy
1431  context->setScaleFactor( 96 / 25.4 );
1432  }
1433  mImage = mSettings->collapsedLegendImage( *context );
1434  }
1435 }
1436 
QgsDataDefinedSizeLegendNode(QgsLayerTreeLayer *nodeLayer, const QgsDataDefinedSizeLegend &settings, QObject *parent=nullptr)
Construct the node using QgsDataDefinedSizeLegend as definition of the node's appearance.
ItemMetrics draw(const QgsLegendSettings &settings, ItemContext *ctx) override
Entry point called from QgsLegendRenderer to do the rendering.
QVariant data(int role) const override
Returns data associated with the item. Must be implemented in derived class.
Object that keeps configuration of appearance of marker symbol's data-defined size in legend.
void setFont(const QFont &font)
Sets font used for rendering of labels - only valid for collapsed legend.
void setTextColor(const QColor &color)
Sets text color for rendering of labels - only valid for collapsed legend.
QImage collapsedLegendImage(QgsRenderContext &context, const QColor &backgroundColor=Qt::transparent, double paddingMM=1) const
Returns output image that would be shown in the legend. Returns invalid image if legend is not config...
void drawCollapsedLegend(QgsRenderContext &context, QSizeF *outputSize SIP_OUT=nullptr, double *labelXOffset SIP_OUT=nullptr) const
Draw the legend if using LegendOneNodeForAll and optionally output size of the legend and x offset of...
Single scope for storing variables and functions for use within a QgsExpressionContext.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
static QString replaceExpressionText(const QString &action, const QgsExpressionContext *context, const QgsDistanceArea *distanceArea=nullptr)
This function replaces each expression between [% and %] in the string with the result of its evaluat...
virtual void setLegendSymbolItem(const QString &key, QgsSymbol *symbol)
Sets the symbol to be used for a legend symbol item.
virtual void checkLegendSymbolItem(const QString &key, bool state=true)
item in symbology was checked
static QString representFileSize(qint64 bytes)
Returns the human size from bytes.
void progress(qint64 received, qint64 total)
Emitted to report progress.
void error(const QString &msg)
Emitted when an error occurs.
void finish(const QImage &legend)
Emitted when the download completes.
QVariant data(int role) const override
Returns data associated with the item. Must be implemented in derived class.
QgsImageLegendNode(QgsLayerTreeLayer *nodeLayer, const QImage &img, QObject *parent=nullptr)
Constructor for QgsImageLegendNode.
QSizeF drawSymbol(const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight) const override
Draws symbol on the left side of the item.
QJsonObject exportSymbolToJson(const QgsLegendSettings &settings, const QgsRenderContext &context) const override
Adds a symbol in base64 string within a JSON object with the key "icon".
static QRect nonTransparentImageRect(const QImage &image, QSize minSize=QSize(), bool center=false)
Calculates the non-transparent region of an image.
Layer tree node points to a map layer.
QString labelExpression() const
Returns the expression member of the LayerTreeNode.
QgsLegendPatchShape patchShape() const
Returns the symbol patch shape to use when rendering the legend node symbol.
QString name() const override
Returns the layer's name.
QgsMapLayer * layer() const
Returns the map layer associated with this node.
QSizeF patchSize() const
Returns the user (overridden) size for the legend node.
The QgsLegendRendererItem class is abstract interface for legend items returned from QgsMapLayerLegen...
virtual QVariant data(int role) const =0
Returns data associated with the item. Must be implemented in derived class.
QJsonObject exportToJson(const QgsLegendSettings &settings, const QgsRenderContext &context)
Entry point called from QgsLegendRenderer to do the rendering in a JSON object.
@ SimpleLegend
Simple label with icon legend node type.
@ RasterSymbolLegend
Raster symbol legend node type.
@ ImageLegend
Raster image legend node type.
@ DataDefinedSizeLegend
Marker symbol legend node type.
@ SymbolLegend
Vector symbol legend node type.
void checkAllItems()
Checks all checkable items belonging to the same layer as this node.
void uncheckAllItems()
Unchecks all checkable items belonging to the same layer as this node.
QgsLayerTreeLayer * layerNode() const
Returns pointer to the parent layer node.
QgsLayerTreeModelLegendNode(QgsLayerTreeLayer *nodeL, QObject *parent=nullptr)
Construct the node with pointer to its parent layer node.
virtual void setUserPatchSize(QSizeF size)
Sets the user (overridden) size for the legend node.
@ ParentRuleKeyRole
Rule key of the parent legend node - for legends with tree hierarchy (QString). Added in 2....
@ RuleKeyRole
Rule key of the node (QString)
@ NodeTypeRole
Type of node. Added in 3.16.
void sizeChanged()
Emitted when the size of this node changes.
void dataChanged()
Emitted on internal data change so the layer tree model can forward the signal to views.
QgsRenderContext * createTemporaryRenderContext() const
Returns a temporary context or nullptr if legendMapViewData are not valid.
virtual QJsonObject exportSymbolToJson(const QgsLegendSettings &settings, const QgsRenderContext &context) const
Adds a symbol in base64 string within a JSON object with the key "icon".
QgsLayerTreeModel * model() const
Returns pointer to model owning this legend node.
void toggleAllItems()
Toggle all checkable items belonging to the same layer as this node.
virtual QSizeF userPatchSize() const
Returns the user (overridden) size for the legend node.
virtual ItemMetrics draw(const QgsLegendSettings &settings, ItemContext *ctx)
Entry point called from QgsLegendRenderer to do the rendering.
virtual Qt::ItemFlags flags() const
Returns item flags associated with the item. Default implementation returns Qt::ItemIsEnabled.
virtual QSizeF drawSymbol(const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight) const
Draws symbol on the left side of the item.
virtual void setEmbeddedInParent(bool embedded)
virtual bool setData(const QVariant &value, int role)
Sets some data associated with the item. Default implementation does nothing and returns false.
virtual QSizeF drawSymbolText(const QgsLegendSettings &settings, ItemContext *ctx, QSizeF symbolSize) const
Draws label on the right side of the item.
The QgsLayerTreeModel class is model implementation for Qt item views framework.
const QgsMapSettings * legendFilterMapSettings() const
Returns the current map settings used for the current legend filter (or nullptr if none is enabled)
static int scaleIconSize(int standardSize)
Scales an layer tree model icon size to compensate for display pixel density, making the icon size hi...
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer. Properties are stored in a map and saved in project file.
Represents a patch shape for use in map legends.
The QgsLegendSettings class stores the appearance and layout settings for legend drawing with QgsLege...
void drawText(QPainter *p, double x, double y, const QString &text, const QFont &font) const
Draws Text.
QgsLegendStyle style(QgsLegendStyle::Style s) const
Returns the style for a legend component.
bool drawRasterStroke() const
Returns whether a stroke will be drawn around raster symbol items.
QSizeF wmsLegendSize() const
Returns the size (in millimeters) of WMS legend graphics shown in the legend.
double minimumSymbolSize() const
Returns the minimum symbol size (in mm).
double rasterStrokeWidth() const
Returns the stroke width (in millimeters) for the stroke drawn around raster symbol items.
double fontDescentMillimeters(const QFont &font) const
Returns the font descent in Millimeters (considers upscaling and downscaling with FONT_WORKAROUND_SCA...
QSizeF symbolSize() const
Returns the default symbol size (in millimeters) used for legend items.
QColor fontColor() const
Returns the font color used for legend items.
double maximumSymbolSize() const
Returns the maximum symbol size (in mm).
QColor rasterStrokeColor() const
Returns the stroke color for the stroke drawn around raster symbol items.
double textWidthMillimeters(const QFont &font, const QString &text) const
Returns the font width in millimeters (considers upscaling and downscaling with FONT_WORKAROUND_SCALE...
Q_DECL_DEPRECATED bool useAdvancedEffects() const
double fontHeightCharacterMM(const QFont &font, QChar c) const
Returns the font height of a character in millimeters.
Q_DECL_DEPRECATED int dpi() const
double lineSpacing() const
Returns the line spacing to use between lines of legend text.
Q_DECL_DEPRECATED double mmPerMapUnit() const
QStringList evaluateItemText(const QString &text, const QgsExpressionContext &context) const
Splits a string using the wrap char taking into account handling empty wrap char which means no wrapp...
Qt::AlignmentFlag symbolAlignment() const
Returns the alignment for placement of legend symbols.
Q_DECL_DEPRECATED double mapScale() const
Returns the legend map scale.
double margin(Side side)
Returns the margin (in mm) for the specified side of the component.
Qt::Alignment alignment() const
Returns the alignment for the legend component.
QFont font() const
Returns the font used for rendering this legend component.
@ Right
Right side.
@ Left
Left side.
@ Symbol
Symbol icon (excluding label)
@ SymbolLabel
Symbol label (excluding icon)
The class stores information about one class/rule of a vector layer renderer in a unified way that ca...
QString parentRuleKey() const
Key of the parent legend node.
int scaleMaxDenom() const
Max scale denominator of the scale range.
void setSymbol(QgsSymbol *s)
Sets the symbol of the item.
QgsSymbol * symbol() const
Returns associated symbol. May be nullptr.
int scaleMinDenom() const
Min scale denominator of the scale range.
QString ruleKey() const
Returns unique identifier of the rule for identification of the item within renderer.
bool isCheckable() const
Returns whether the item is user-checkable - whether renderer supports enabling/disabling it.
QString label() const
Returns text label.
Base class for all map layer types.
Definition: qgsmaplayer.h:85
void triggerRepaint(bool deferredUpdate=false)
Will advise the map canvas (and any other interested party) that this layer requires to be repainted.
void emitStyleChanged()
Triggers an emission of the styleChanged() signal.
The QgsMapSettings class contains configuration for rendering of the map.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
A marker symbol type, for rendering Point and MultiPoint geometries.
Definition: qgssymbol.h:1004
Represents a map layer supporting display of point clouds.
Base class for raster data providers.
virtual QgsImageFetcher * getLegendGraphicFetcher(const QgsMapSettings *mapSettings)
Returns a new image downloader for the raster legend.
Represents a raster layer.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
Raster renderer pipe that applies colors to a raster.
Qt::ItemFlags flags() const override
Returns item flags associated with the item. Default implementation returns Qt::ItemIsEnabled.
bool setData(const QVariant &value, int role) override
Sets some data associated with the item. Default implementation does nothing and returns false.
QJsonObject exportSymbolToJson(const QgsLegendSettings &settings, const QgsRenderContext &context) const override
Adds a symbol in base64 string within a JSON object with the key "icon".
QgsRasterSymbolLegendNode(QgsLayerTreeLayer *nodeLayer, const QColor &color, const QString &label, QObject *parent=nullptr, bool isCheckable=false, const QString &ruleKey=QString())
Constructor for QgsRasterSymbolLegendNode.
QVariant data(int role) const override
Returns data associated with the item. Must be implemented in derived class.
QSizeF drawSymbol(const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight) const override
Draws symbol on the left side of the item.
Contains information about the context of a rendering operation.
void setForceVectorOutput(bool force)
Sets whether rendering operations should use vector operations instead of any faster raster shortcuts...
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
void setScaleFactor(double factor)
Sets the scaling factor for the render to convert painter units to physical sizes.
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
void setFlag(Flag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
void setMapToPixel(const QgsMapToPixel &mtp)
Sets the context's map to pixel transform, which transforms between map coordinates and device coordi...
@ Antialiasing
Use antialiasing while drawing.
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
@ UseAdvancedEffects
Enable layer opacity and blending effects.
@ LosslessImageRendering
Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some...
Flags flags() const
Returns combination of flags used for rendering.
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
void setRendererScale(double scale)
Sets the renderer map scale.
Scoped object for saving and restoring a QPainter object's state.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
QVariant data(int role) const override
Returns data associated with the item. Must be implemented in derived class.
QgsSimpleLegendNode(QgsLayerTreeLayer *nodeLayer, const QString &label, const QIcon &icon=QIcon(), QObject *parent=nullptr, const QString &key=QString())
Constructor for QgsSimpleLegendNode.
static QPixmap symbolPreviewPixmap(const QgsSymbol *symbol, QSize size, int padding=0, QgsRenderContext *customContext=nullptr, bool selected=false, const QgsExpressionContext *expressionContext=nullptr, const QgsLegendPatchShape *shape=nullptr)
Returns a pixmap preview for a color ramp.
static QgsSymbol * restrictedSizeSymbol(const QgsSymbol *s, double minSize, double maxSize, QgsRenderContext *context, double &width, double &height)
Creates a new symbol with size restricted to min/max size if original size is out of min/max range.
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
QString evaluateLabel(const QgsExpressionContext &context=QgsExpressionContext(), const QString &label=QString())
Evaluates and returns the text label of the current node.
const QgsSymbol * symbol() const
Returns the symbol used by the legend node.
void setPatchShape(const QgsLegendPatchShape &shape)
Sets the symbol patch shape to use when rendering the legend node symbol.
QgsSymbolLegendNode(QgsLayerTreeLayer *nodeLayer, const QgsLegendSymbolItem &item, QObject *parent=nullptr)
Constructor for QgsSymbolLegendNode.
QgsLegendPatchShape patchShape() const
Returns the symbol patch shape to use when rendering the legend node symbol.
QSize minimumIconSize() const
Calculates the minimum icon size to prevent cropping.
QSizeF drawSymbol(const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight) const override
Draws symbol on the left side of the item.
void invalidateMapBasedData() override
Notification from model that information from associated map view has changed.
bool setData(const QVariant &value, int role) override
Sets some data associated with the item. Default implementation does nothing and returns false.
Qt::ItemFlags flags() const override
Returns item flags associated with the item. Default implementation returns Qt::ItemIsEnabled.
void setCustomSymbol(QgsSymbol *symbol)
Sets the node's custom symbol.
void setEmbeddedInParent(bool embedded) override
QgsSymbol * customSymbol() const
Returns the node's custom symbol.
QString symbolLabel() const
Label of the symbol, user defined label will be used, otherwise will default to the label made by QGI...
void setSymbol(QgsSymbol *symbol)
Sets the symbol to be used by the legend node.
QVariant data(int role) const override
Returns data associated with the item. Must be implemented in derived class.
QJsonObject exportSymbolToJson(const QgsLegendSettings &settings, const QgsRenderContext &context) const override
Adds a symbol in base64 string within a JSON object with the key "icon".
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:65
void drawPreviewIcon(QPainter *painter, QSize size, QgsRenderContext *customContext=nullptr, bool selected=false, const QgsExpressionContext *expressionContext=nullptr, const QgsLegendPatchShape *patchShape=nullptr)
Draws an icon of the symbol that occupies an area given by size using the specified painter.
Definition: qgssymbol.cpp:553
bool usesMapUnits() const
Returns true if the symbol has any components which use map unit based sizes.
Definition: qgssymbol.cpp:287
SymbolType type() const
Returns the symbol's type.
Definition: qgssymbol.h:138
@ Line
Line symbol.
Definition: qgssymbol.h:89
@ Marker
Marker symbol.
Definition: qgssymbol.h:88
QFont scaledFont(const QgsRenderContext &context, double scaleFactor=1.0) const
Returns a font with the size scaled to match the format's size settings (including units and map unit...
static double textWidth(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF *fontMetrics=nullptr)
Returns the width of a text based on a given format.
@ AlignCenter
Center align.
static double textHeight(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode=Point, QFontMetricsF *fontMetrics=nullptr)
Returns the height of a text based on a given format.
static void drawText(const QRectF &rect, double rotation, HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, VAlignment vAlignment=AlignTop)
Draws text within a rectangle using the specified settings.
@ Point
Text at point of origin draw mode.
Represents a vector layer which manages a vector based data sets.
QgsExpressionContextScope * createExpressionContextScope() const FINAL
This method needs to be reimplemented in all classes which implement this interface and return an exp...
void symbolFeatureCountMapChanged()
Emitted when the feature count for symbols on this layer has been recalculated.
long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
QSizeF drawSymbol(const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight) const override
Draws symbol on the left side of the item.
void invalidateMapBasedData() override
Notification from model that information from associated map view has changed.
QVariant data(int role) const override
Returns data associated with the item. Must be implemented in derived class.
~QgsWmsLegendNode() override
QgsWmsLegendNode(QgsLayerTreeLayer *nodeLayer, QObject *parent=nullptr)
Constructor for QgsWmsLegendNode.
QJsonObject exportSymbolToJson(const QgsLegendSettings &settings, const QgsRenderContext &context) const override
Adds a symbol in base64 string within a JSON object with the key "icon".
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:798
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:797
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:316
QList< QgsLegendSymbolItem > QgsLegendSymbolList
Single variable definition for use within a QgsExpressionContextScope.
double top
Top y-position of legend item.
QgsLegendPatchShape patchShape
The patch shape to render for the node.
double maxSiblingSymbolWidth
Largest symbol width, considering all other sibling legend components associated with the current com...
QSizeF patchSize
Symbol patch size to render for the node.
double columnLeft
Left side of current legend column.
double columnRight
Right side of current legend column.
Q_NOWARN_DEPRECATED_POP QgsRenderContext * context
Render context, if available.