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