QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgslegendrenderer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslegendrenderer.cpp
3 --------------------------------------
4 Date : July 2014
5 Copyright : (C) 2014 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgslegendrenderer.h"
17
18#include <memory>
19
21#include "qgslayertree.h"
23#include "qgslayertreemodel.h"
25#include "qgslegendstyle.h"
26#include "qgsrendercontext.h"
27#include "qgstextrenderer.h"
28
29#include <QJsonObject>
30#include <QPainter>
31#include <QString>
32
33using namespace Qt::StringLiterals;
34
36 : mLegendModel( legendModel )
37 , mProxyModel( std::make_unique< QgsLayerTreeFilterProxyModel >() )
38 , mSettings( settings )
39{
40 mProxyModel->setLayerTreeModel( mLegendModel );
41}
42
44 : mLegendModel( other.mLegendModel )
45 , mProxyModel( std::move( other.mProxyModel ) )
46 , mSettings( std::move( other.mSettings ) )
47 , mLegendSize( other.mLegendSize )
48{
49 mProxyModel->setLayerTreeModel( mLegendModel );
50}
51
53{
54 if ( mProxyModel.get() == model )
55 return;
56
57 mProxyModel.reset( model );
58 mProxyModel->setLayerTreeModel( mLegendModel );
59}
60
62
64{
65 std::unique_ptr< QgsRenderContext > tmpContext;
66
67 if ( !renderContext )
68 {
69 // QGIS 5.0 - make render context mandatory
71 tmpContext = std::make_unique<QgsRenderContext>( QgsRenderContext::fromQPainter( nullptr ) );
72 tmpContext->setRendererScale( mSettings.mapScale() );
73 tmpContext->setMapToPixel( QgsMapToPixel( 1 / ( mSettings.mmPerMapUnit() * tmpContext->scaleFactor() ) ) );
75 renderContext = tmpContext.get();
77 }
78
79 QgsScopedRenderContextPainterSwap nullPainterSwap( *renderContext, nullptr );
80 return paintAndDetermineSize( *renderContext );
81}
82
83void QgsLegendRenderer::drawLegend( QPainter *painter )
84{
87 QgsScopedRenderContextScaleToMm scaleToMm( context );
88
89 context.setRendererScale( mSettings.mapScale() );
90 context.setMapToPixel( QgsMapToPixel( 1 / ( mSettings.mmPerMapUnit() * context.scaleFactor() ) ) );
92
93 paintAndDetermineSize( context );
94}
95
97{
98 QJsonObject json;
99
100 QgsLayerTreeGroup *rootGroup = mLegendModel->rootGroup();
101 if ( !rootGroup )
102 return json;
103
104 json = exportLegendToJson( context, rootGroup );
105 json[u"title"_s] = mSettings.title();
106 return json;
107}
108
109QJsonObject QgsLegendRenderer::exportLegendToJson( const QgsRenderContext &context, QgsLayerTreeGroup *nodeGroup )
110{
111 QJsonObject json;
112 QJsonArray nodes;
113 const QList<QgsLayerTreeNode *> childNodes = nodeGroup->children();
114 for ( QgsLayerTreeNode *node : childNodes )
115 {
116 if ( !mProxyModel->nodeShown( node ) )
117 continue;
118
119 if ( QgsLayerTree::isGroup( node ) )
120 {
121 QgsLayerTreeGroup *nodeGroup = QgsLayerTree::toGroup( node );
122 const QModelIndex idx = mLegendModel->node2index( nodeGroup );
123 const QString text = mLegendModel->data( idx, Qt::DisplayRole ).toString();
124
125 QJsonObject group = exportLegendToJson( context, nodeGroup );
126 group[ u"type"_s ] = u"group"_s;
127 group[ u"title"_s ] = text;
128 nodes.append( group );
129 }
130 else if ( QgsLayerTree::isLayer( node ) )
131 {
132 QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
133
134 QString text;
136 {
137 const QModelIndex idx = mLegendModel->node2index( nodeLayer );
138 text = mLegendModel->data( idx, Qt::DisplayRole ).toString();
139 }
140
141 QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->layerLegendNodes( nodeLayer );
142
143 if ( legendNodes.isEmpty() && mLegendModel->legendFilterMapSettings() )
144 continue;
145
146 if ( legendNodes.count() == 1 )
147 {
148 QJsonObject group = legendNodes.at( 0 )->exportToJson( mSettings, context );
149 group[ u"type"_s ] = u"layer"_s;
150 if ( mSettings.jsonRenderFlags().testFlag( Qgis::LegendJsonRenderFlag::ShowRuleDetails ) )
151 {
152 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( nodeLayer->layer() ) )
153 {
154 if ( vLayer->renderer() )
155 {
156 const QString ruleKey { legendNodes.at( 0 )->data( static_cast< int >( QgsLayerTreeModelLegendNode::CustomRole::RuleKey ) ).toString() };
157 if ( ! ruleKey.isEmpty() )
158 {
159 bool ok = false;
160 const QString ruleExp { vLayer->renderer()->legendKeyToExpression( ruleKey, vLayer, ok ) };
161 if ( ok )
162 {
163 group[ u"rule"_s ] = ruleExp;
164 }
165 }
166 }
167 }
168 }
169 nodes.append( group );
170 }
171 else if ( legendNodes.count() > 1 )
172 {
173 QJsonObject group;
174 group[ u"type"_s ] = u"layer"_s;
175 group[ u"title"_s ] = text;
176
177 QJsonArray symbols;
178 for ( int j = 0; j < legendNodes.count(); j++ )
179 {
180 QgsLayerTreeModelLegendNode *legendNode = legendNodes.at( j );
181 QJsonObject symbol = legendNode->exportToJson( mSettings, context );
182 if ( mSettings.jsonRenderFlags().testFlag( Qgis::LegendJsonRenderFlag::ShowRuleDetails ) )
183 {
184 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( nodeLayer->layer() ) )
185 {
186 if ( vLayer->renderer() )
187 {
188 const QString ruleKey { legendNode->data( static_cast< int >( QgsLayerTreeModelLegendNode::CustomRole::RuleKey ) ).toString() };
189 if ( ! ruleKey.isEmpty() )
190 {
191 bool ok = false;
192 const QString ruleExp { vLayer->renderer()->legendKeyToExpression( ruleKey, vLayer, ok ) };
193 if ( ok )
194 {
195 symbol[ u"rule"_s ] = ruleExp;
196 }
197 }
198 }
199 }
200 }
201 symbols.append( symbol );
202 }
203 group[ u"symbols"_s ] = symbols;
204
205 nodes.append( group );
206 }
207 }
208 }
209
210 json[u"nodes"_s] = nodes;
211 return json;
212}
213
214QSizeF QgsLegendRenderer::paintAndDetermineSize( QgsRenderContext &context )
215{
216 QSizeF size( 0, 0 );
217 QgsLayerTreeGroup *rootGroup = mLegendModel->rootGroup();
218 if ( !rootGroup )
219 return size;
220
221 mSettings.updateDataDefinedProperties( context );
222
223 // temporarily remove painter from context -- we don't need to actually draw anything yet. But we DO need
224 // to send the full render context so that an expression context is available during the size calculation
225 QgsScopedRenderContextPainterSwap noPainter( context, nullptr );
226
227 QList<LegendComponentGroup> componentGroups = createComponentGroupList( rootGroup, context );
228
229 const int columnCount = setColumns( componentGroups );
230
231 QMap< int, double > maxColumnWidths;
232 qreal maxEqualColumnWidth = 0;
233 // another iteration -- this one is required to calculate the maximum item width for each
234 // column. Unfortunately, we can't trust the component group widths at this stage, as they are minimal widths
235 // only. When actually rendering a symbol node, the text is aligned according to the WIDEST
236 // symbol in a column. So that means we can't possibly determine the exact size of legend components
237 // until now. BUUUUUUUUUUUUT. Because everything sucks, we can't even start the actual render of items
238 // at the same time we calculate this -- legend items REQUIRE the REAL width of the columns in order to
239 // correctly align right or center-aligned symbols/text. Bah -- A triple iteration it is!
240 for ( const LegendComponentGroup &group : std::as_const( componentGroups ) )
241 {
242 const QSizeF actualSize = drawGroup( group, context, ColumnContext() );
243 maxEqualColumnWidth = std::max( actualSize.width(), maxEqualColumnWidth );
244 maxColumnWidths[ group.column ] = std::max( actualSize.width(), maxColumnWidths.value( group.column, 0 ) );
245 }
246
247 if ( columnCount == 1 )
248 {
249 // single column - use the full available width
250 maxEqualColumnWidth = std::max( maxEqualColumnWidth, mLegendSize.width() - 2 * mSettings.boxSpace() );
251 maxColumnWidths[ 0 ] = maxEqualColumnWidth;
252 }
253
254 //calculate size of title
255 QSizeF titleSize = drawTitle( context, 0 );
256 //add title margin to size of title text
257 titleSize.rwidth() += mSettings.boxSpace() * 2.0;
258 double columnTop = mSettings.boxSpace() + titleSize.height() + mSettings.style( Qgis::LegendComponent::Title ).margin( QgsLegendStyle::Bottom );
259
260 noPainter.reset();
261
262 bool firstInColumn = true;
263 double columnMaxHeight = 0;
264 qreal columnWidth = 0;
265 int column = -1;
266 ColumnContext columnContext;
267 columnContext.left = mSettings.boxSpace();
268 columnContext.right = std::max( mLegendSize.width() - mSettings.boxSpace(), mSettings.boxSpace() );
269 double currentY = columnTop;
270
271 for ( const LegendComponentGroup &group : std::as_const( componentGroups ) )
272 {
273 if ( group.column > column )
274 {
275 // Switch to next column
276 columnContext.left = group.column > 0 ? ( columnContext.right + mSettings.columnSpace() ) : mSettings.boxSpace();
277 columnWidth = mSettings.equalColumnWidth() ? maxEqualColumnWidth : maxColumnWidths.value( group.column );
278 columnContext.right = columnContext.left + columnWidth;
279 currentY = columnTop;
280 column++;
281 firstInColumn = true;
282 }
283 if ( !firstInColumn )
284 {
285 currentY += spaceAboveGroup( group );
286 }
287
288 drawGroup( group, context, columnContext, currentY );
289
290 currentY += group.size.height();
291 columnMaxHeight = std::max( currentY - columnTop, columnMaxHeight );
292
293 firstInColumn = false;
294 }
295 const double totalWidth = columnContext.right + mSettings.boxSpace();
296
297 size.rheight() = columnTop + columnMaxHeight + mSettings.boxSpace();
298 size.rwidth() = totalWidth;
299 if ( !mSettings.title().isEmpty() )
300 {
301 size.rwidth() = std::max( titleSize.width(), size.width() );
302 }
303
304 // override the size if it was set by the user
305 if ( mLegendSize.isValid() )
306 {
307 qreal w = std::max( size.width(), mLegendSize.width() );
308 qreal h = std::max( size.height(), mLegendSize.height() );
309 size = QSizeF( w, h );
310 }
311
312 // Now we have set the correct total item width and can draw the title centered
313 if ( !mSettings.title().isEmpty() )
314 {
315 drawTitle( context, mSettings.boxSpace(), mSettings.titleAlignment(), size.width() );
316 }
317
318 return size;
319}
320
321void QgsLegendRenderer::widthAndOffsetForTitleText( const Qt::AlignmentFlag halignment, const double legendWidth, double &textBoxWidth, double &textBoxLeft ) const
322{
323 switch ( halignment )
324 {
325 default:
326 textBoxLeft = mSettings.boxSpace();
327 textBoxWidth = legendWidth - 2 * mSettings.boxSpace();
328 break;
329
330 case Qt::AlignHCenter:
331 {
332 // not sure on this logic, I just moved it -- don't blame me for it being totally obscure!
333 const double centerX = legendWidth / 2;
334 textBoxWidth = ( std::min( static_cast< double >( centerX ), legendWidth - centerX ) - mSettings.boxSpace() ) * 2.0;
335 textBoxLeft = centerX - textBoxWidth / 2.;
336 break;
337 }
338 }
339}
340
341QList<QgsLegendRenderer::LegendComponentGroup> QgsLegendRenderer::createComponentGroupList( QgsLayerTreeGroup *parentGroup, QgsRenderContext &context, double indent )
342{
343 QList<LegendComponentGroup> componentGroups;
344
345 if ( !parentGroup )
346 return componentGroups;
347
348 const QList<QgsLayerTreeNode *> childNodes = parentGroup->children();
349 for ( QgsLayerTreeNode *node : childNodes )
350 {
351 if ( !mProxyModel->nodeShown( node ) )
352 continue;
353
354 if ( QgsLayerTree::isGroup( node ) )
355 {
356 QgsLayerTreeGroup *nodeGroup = QgsLayerTree::toGroup( node );
357 QString style = node->customProperty( u"legend/title-style"_s ).toString();
358 // Update the required indent for the group/subgroup items, starting from the indent accumulated from parent groups
359 double newIndent = indent;
360 if ( style == "subgroup"_L1 )
361 {
362 newIndent += mSettings.style( Qgis::LegendComponent::Subgroup ).indent( );
363 }
364 else
365 {
366 newIndent += mSettings.style( Qgis::LegendComponent::Group ).indent( );
367 }
368
369 // Group subitems
370 QList<LegendComponentGroup> subgroups = createComponentGroupList( nodeGroup, context, newIndent );
371
372 bool hasSubItems = !subgroups.empty();
373
375 {
376 LegendComponent component;
377 component.item = node;
378 component.indent = newIndent;
379 component.size = drawGroupTitle( nodeGroup, context );
380
381 if ( !subgroups.isEmpty() )
382 {
383 // Add internal space between this group title and the next component
384 subgroups[0].size.rheight() += spaceAboveGroup( subgroups[0] );
385 // Prepend this group title to the first group
386 subgroups[0].components.prepend( component );
387 subgroups[0].size.rheight() += component.size.height();
388 subgroups[0].size.rwidth() = std::max( component.size.width(), subgroups[0].size.width() );
389 if ( nodeGroup->customProperty( u"legend/column-break"_s ).toInt() )
390 subgroups[0].placeColumnBreakBeforeGroup = true;
391 }
392 else
393 {
394 // no subitems, create new group
395 LegendComponentGroup group;
396 group.placeColumnBreakBeforeGroup = nodeGroup->customProperty( u"legend/column-break"_s ).toInt();
397 group.components.append( component );
398 group.size.rwidth() += component.size.width();
399 group.size.rheight() += component.size.height();
400 group.size.rwidth() = std::max( component.size.width(), group.size.width() );
401 subgroups.append( group );
402 }
403 }
404
405 if ( hasSubItems ) //leave away groups without content
406 {
407 componentGroups.append( subgroups );
408 }
409
410 }
411 else if ( QgsLayerTree::isLayer( node ) )
412 {
413 QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
414 Qgis::LegendComponent layerStyle = nodeLegendStyle( nodeLayer );
415 bool allowColumnSplit = false;
416 switch ( nodeLayer->legendSplitBehavior() )
417 {
419 allowColumnSplit = mSettings.splitLayer();
420 break;
422 allowColumnSplit = true;
423 break;
425 allowColumnSplit = false;
426 break;
427 }
428
429 LegendComponentGroup group;
430 group.placeColumnBreakBeforeGroup = nodeLayer->customProperty( u"legend/column-break"_s ).toInt();
431
432 if ( layerStyle != Qgis::LegendComponent::Hidden )
433 {
434 LegendComponent component;
435 component.item = node;
436 component.size = drawLayerTitle( nodeLayer, context );
437 component.indent = indent;
438 group.components.append( component );
439 group.size.rwidth() = component.size.width();
440 group.size.rheight() = component.size.height();
441 }
442
443 QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->layerLegendNodes( nodeLayer );
444
445 // workaround for the issue that "filtering by map" does not remove layer nodes that have no symbols present
446 // on the map. We explicitly skip such layers here. In future ideally that should be handled directly
447 // in the layer tree model
448 if ( legendNodes.isEmpty() && mLegendModel->legendFilterMapSettings() )
449 continue;
450
451 QList<LegendComponentGroup> layerGroups;
452 layerGroups.reserve( legendNodes.count() );
453
454 bool groupIsLayerGroup = true;
455 double symbolIndent = indent;
456 switch ( layerStyle )
457 {
460 symbolIndent += mSettings.style( layerStyle ).indent( );
461 break;
462 default:
463 break;
464 }
465 for ( int j = 0; j < legendNodes.count(); j++ )
466 {
467 QgsLayerTreeModelLegendNode *legendNode = legendNodes.at( j );
468
469 LegendComponent symbolComponent = drawSymbolItem( legendNode, context, ColumnContext(), 0 );
470
471 const bool forceBreak = legendNode->columnBreak();
472
473 if ( !allowColumnSplit || j == 0 )
474 {
475 if ( forceBreak )
476 {
477 if ( groupIsLayerGroup )
478 layerGroups.prepend( group );
479 else
480 layerGroups.append( group );
481
482 group = LegendComponentGroup();
483 group.placeColumnBreakBeforeGroup = true;
484 groupIsLayerGroup = false;
485 }
486
487 // append to layer group
488 // the width is not correct at this moment, we must align all symbol labels
489 group.size.rwidth() = std::max( symbolComponent.size.width(), group.size.width() );
490 // Add symbol space only if there is already title or another item above
491 if ( !group.components.isEmpty() )
492 {
493 // TODO: for now we keep Symbol and SymbolLabel Top margin in sync
494 group.size.rheight() += mSettings.style( Qgis::LegendComponent::Symbol ).margin( QgsLegendStyle::Top );
495 }
496 group.size.rheight() += symbolComponent.size.height();
497 symbolComponent.indent = symbolIndent;
498 group.components.append( symbolComponent );
499 }
500 else
501 {
502 if ( group.size.height() > 0 )
503 {
504 if ( groupIsLayerGroup )
505 layerGroups.prepend( group );
506 else
507 layerGroups.append( group );
508 group = LegendComponentGroup();
509 groupIsLayerGroup = false;
510 }
511 LegendComponentGroup symbolGroup;
512 symbolGroup.placeColumnBreakBeforeGroup = forceBreak;
513 symbolComponent.indent = symbolIndent;
514 symbolGroup.components.append( symbolComponent );
515 symbolGroup.size.rwidth() = symbolComponent.size.width();
516 symbolGroup.size.rheight() = symbolComponent.size.height();
517 layerGroups.append( symbolGroup );
518 }
519 }
520 if ( group.size.height() > 0 )
521 {
522 if ( groupIsLayerGroup )
523 layerGroups.prepend( group );
524 else
525 layerGroups.append( group );
526 }
527 componentGroups.append( layerGroups );
528 }
529 }
530
531 return componentGroups;
532}
533
534
535int QgsLegendRenderer::setColumns( QList<LegendComponentGroup> &componentGroups )
536{
537 // Divide groups to columns
538 double totalHeight = 0;
539 qreal maxGroupHeight = 0;
540 int forcedColumnBreaks = 0;
541 double totalSpaceAboveGroups = 0;
542
543 for ( const LegendComponentGroup &group : std::as_const( componentGroups ) )
544 {
545 const double topMargin = spaceAboveGroup( group );
546 totalHeight += topMargin;
547 totalSpaceAboveGroups += topMargin;
548
549 const double groupHeight = group.size.height();
550 totalHeight += groupHeight;
551 maxGroupHeight = std::max( groupHeight, maxGroupHeight );
552
553 if ( group.placeColumnBreakBeforeGroup )
554 forcedColumnBreaks++;
555 }
556 const double totalGroupHeight = ( totalHeight - totalSpaceAboveGroups );
557 double averageGroupHeight = totalGroupHeight / componentGroups.size();
558
559 if ( mSettings.columnCount() == 0 && forcedColumnBreaks == 0 )
560 return 0;
561
562 // the target number of columns allowed is dictated by the number of forced column
563 // breaks OR the manually set column count (whichever is greater!)
564 const int targetNumberColumns = std::max( forcedColumnBreaks + 1, mSettings.columnCount() );
565 const int numberAutoPlacedBreaks = targetNumberColumns - forcedColumnBreaks - 1;
566
567 // We know height of each group and we have to split them into columns
568 // minimizing max column height. It is sort of bin packing problem, NP-hard.
569 // We are using simple heuristic, brute fore appeared to be to slow,
570 // the number of combinations is N = n!/(k!*(n-k)!) where n = groupCount-1
571 // and k = columnsCount-1
572 double maxColumnHeight = 0;
573 int currentColumn = 0;
574 int currentColumnGroupCount = 0; // number of groups in current column
575 double currentColumnHeight = 0;
576 int autoPlacedBreaks = 0;
577
578 // Calculate the expected average space between items
579 double averageSpaceAboveGroups = 0;
580 if ( componentGroups.size() > targetNumberColumns )
581 averageSpaceAboveGroups = totalSpaceAboveGroups / ( componentGroups.size() );
582
583 double totalRemainingGroupHeight = totalGroupHeight;
584 double totalRemainingSpaceAboveGroups = totalSpaceAboveGroups;
585 for ( int i = 0; i < componentGroups.size(); i++ )
586 {
587 const LegendComponentGroup &group = componentGroups.at( i );
588 const double currentGroupHeight = group.size.height();
589 const double spaceAboveCurrentGroup = spaceAboveGroup( group );
590
591 totalRemainingGroupHeight -= currentGroupHeight;
592 totalRemainingSpaceAboveGroups -= spaceAboveCurrentGroup;
593
594 double currentColumnHeightIfGroupIsIncluded = currentColumnHeight;
595 if ( currentColumnGroupCount > 0 )
596 currentColumnHeightIfGroupIsIncluded += spaceAboveCurrentGroup;
597 currentColumnHeightIfGroupIsIncluded += currentGroupHeight;
598
599 const int numberRemainingGroupsIncludingThisOne = componentGroups.size() - i;
600 const int numberRemainingColumnsIncludingThisOne = numberAutoPlacedBreaks + 1 - autoPlacedBreaks;
601 const int numberRemainingColumnBreaks = numberRemainingColumnsIncludingThisOne - 1;
602
603 const double averageRemainingSpaceAboveGroups = numberRemainingGroupsIncludingThisOne > 1 ? ( totalRemainingSpaceAboveGroups / ( numberRemainingGroupsIncludingThisOne - 1 ) ) : 0;
604 const double estimatedRemainingSpaceAboveGroupsWhichWontBeUsedBecauseGroupsAreFirstInColumn = numberRemainingColumnBreaks * averageRemainingSpaceAboveGroups;
605 const double estimatedRemainingTotalHeightAfterThisGroup = totalRemainingGroupHeight
606 + totalRemainingSpaceAboveGroups
607 - estimatedRemainingSpaceAboveGroupsWhichWontBeUsedBecauseGroupsAreFirstInColumn;
608
609 const double estimatedTotalHeightOfRemainingColumnsIncludingThisOne = currentColumnHeightIfGroupIsIncluded
610 + estimatedRemainingTotalHeightAfterThisGroup;
611
612 // Recalc average height for remaining columns including current
613 double averageRemainingColumnHeightIncludingThisOne = estimatedTotalHeightOfRemainingColumnsIncludingThisOne / numberRemainingColumnsIncludingThisOne;
614
615 // Round up to the next full number of groups to put in one column
616 // This ensures that earlier columns contain more elements than later columns
617 const int averageGroupsPerRemainingColumnsIncludingThisOne = std::ceil( averageRemainingColumnHeightIncludingThisOne / ( averageGroupHeight + averageSpaceAboveGroups ) );
618
619 averageRemainingColumnHeightIncludingThisOne = averageGroupsPerRemainingColumnsIncludingThisOne * ( averageGroupHeight + averageSpaceAboveGroups ) - averageSpaceAboveGroups;
620
621 bool canCreateNewColumn = ( currentColumnGroupCount > 0 ) // do not leave empty column
622 && ( currentColumn < targetNumberColumns - 1 ) // must not exceed max number of columns
623 && ( autoPlacedBreaks < numberAutoPlacedBreaks );
624
625 bool shouldCreateNewColumn = currentColumnHeightIfGroupIsIncluded > averageRemainingColumnHeightIncludingThisOne // current group height is greater than expected group height
626 && currentColumnGroupCount > 0 // do not leave empty column
627 && currentColumnHeightIfGroupIsIncluded > maxGroupHeight // no sense to make smaller columns than max group height
628 && currentColumnHeightIfGroupIsIncluded > maxColumnHeight; // no sense to make smaller columns than max column already created
629
630 shouldCreateNewColumn |= group.placeColumnBreakBeforeGroup;
631 canCreateNewColumn |= group.placeColumnBreakBeforeGroup;
632
633 // also should create a new column if the number of items left < number of columns left
634 // in this case we should spread the remaining items out over the remaining columns
635 shouldCreateNewColumn |= ( componentGroups.size() - i < targetNumberColumns - currentColumn );
636
637 if ( canCreateNewColumn && shouldCreateNewColumn )
638 {
639 // New column
640 currentColumn++;
641 if ( !group.placeColumnBreakBeforeGroup )
642 autoPlacedBreaks++;
643 currentColumnGroupCount = 0;
644 currentColumnHeight = group.size.height();
645 }
646 else
647 {
648 currentColumnHeight = currentColumnHeightIfGroupIsIncluded;
649 }
650 componentGroups[i].column = currentColumn;
651 currentColumnGroupCount++;
652 maxColumnHeight = std::max( currentColumnHeight, maxColumnHeight );
653 }
654
655 auto refineColumns = [&componentGroups, this]() -> bool
656 {
657 QHash< int, double > columnHeights;
658 QHash< int, int > columnGroupCounts;
659 double currentColumnHeight = 0;
660 int currentColumn = -1;
661 int columnCount = 0;
662 int groupCount = 0;
663 double maxCurrentColumnHeight = 0;
664 for ( int i = 0; i < componentGroups.size(); i++ )
665 {
666 const LegendComponentGroup &group = componentGroups.at( i );
667 if ( group.column != currentColumn )
668 {
669 if ( currentColumn >= 0 )
670 {
671 columnHeights.insert( currentColumn, currentColumnHeight );
672 columnGroupCounts.insert( currentColumn, groupCount );
673 }
674
675 currentColumn = group.column;
676 currentColumnHeight = 0;
677 groupCount = 0;
678 columnCount = std::max( columnCount, currentColumn + 1 );
679 }
680
681 const double spaceAbove = spaceAboveGroup( group );
682 currentColumnHeight += spaceAbove + group.size.height();
683 groupCount++;
684 }
685 columnHeights.insert( currentColumn, currentColumnHeight );
686 columnGroupCounts.insert( currentColumn, groupCount );
687
688 double totalColumnHeights = 0;
689 for ( int i = 0; i < columnCount; ++ i )
690 {
691 totalColumnHeights += columnHeights[i];
692 maxCurrentColumnHeight = std::max( maxCurrentColumnHeight, columnHeights[i] );
693 }
694
695 const double averageColumnHeight = totalColumnHeights / columnCount;
696
697 bool changed = false;
698 int nextCandidateColumnForShift = 1;
699 for ( int i = 0; i < componentGroups.size(); i++ )
700 {
701 LegendComponentGroup &group = componentGroups[ i ];
702 if ( group.column < nextCandidateColumnForShift )
703 continue;
704
705 // try shifting item to previous group
706 const bool canShift = !group.placeColumnBreakBeforeGroup
707 && columnGroupCounts[ group.column ] >= 2;
708
709 if ( canShift
710 && columnHeights[ group.column - 1 ] < averageColumnHeight
711 && ( columnHeights[ group.column - 1 ] + group.size.height() ) * 0.9 < maxCurrentColumnHeight
712 )
713 {
714 group.column -= 1;
715 columnHeights[ group.column ] += group.size.height() + spaceAboveGroup( group );
716 columnGroupCounts[ group.column ]++;
717 columnHeights[ group.column + 1 ] -= group.size.height();
718 columnGroupCounts[ group.column + 1]--;
719 changed = true;
720 }
721 else
722 {
723 nextCandidateColumnForShift = group.column + 1;
724 }
725 }
726 return changed;
727 };
728
729 bool wasRefined = true;
730 int iterations = 0;
731 while ( wasRefined && iterations < 2 )
732 {
733 wasRefined = refineColumns();
734 iterations++;
735 }
736
737 // Align labels of symbols for each layer/column to the same labelXOffset
738 QMap<QString, qreal> maxSymbolWidth;
739 for ( int i = 0; i < componentGroups.size(); i++ )
740 {
741 LegendComponentGroup &group = componentGroups[i];
742 for ( int j = 0; j < group.components.size(); j++ )
743 {
744 if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( group.components.at( j ).item ) )
745 {
746 QString key = u"%1-%2"_s.arg( reinterpret_cast< qulonglong >( legendNode->layerNode() ) ).arg( group.column );
747 maxSymbolWidth[key] = std::max( group.components.at( j ).symbolSize.width(), maxSymbolWidth[key] );
748 }
749 }
750 }
751 for ( int i = 0; i < componentGroups.size(); i++ )
752 {
753 LegendComponentGroup &group = componentGroups[i];
754 for ( int j = 0; j < group.components.size(); j++ )
755 {
756 if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( group.components.at( j ).item ) )
757 {
758 QString key = u"%1-%2"_s.arg( reinterpret_cast< qulonglong >( legendNode->layerNode() ) ).arg( group.column );
759 double space = mSettings.style( Qgis::LegendComponent::Symbol ).margin( QgsLegendStyle::Right ) +
760 mSettings.style( Qgis::LegendComponent::SymbolLabel ).margin( QgsLegendStyle::Left );
761 group.components[j].labelXOffset = maxSymbolWidth[key] + space;
762 group.components[j].maxSiblingSymbolWidth = maxSymbolWidth[key];
763 group.components[j].size.rwidth() = maxSymbolWidth[key] + space + group.components.at( j ).labelSize.width();
764 }
765 }
766 }
767 return targetNumberColumns;
768}
769
770QSizeF QgsLegendRenderer::drawTitle( QgsRenderContext &context, double top, Qt::AlignmentFlag halignment, double legendWidth ) const
771{
772 QSizeF size( 0, 0 );
773 if ( mSettings.title().isEmpty() )
774 {
775 return size;
776 }
777
778 QStringList lines = mSettings.splitStringForWrapping( mSettings.title() );
779
780 //calculate width and left pos of rectangle to draw text into
781 double textBoxWidth;
782 double textBoxLeft;
783 widthAndOffsetForTitleText( halignment, legendWidth, textBoxWidth, textBoxLeft );
784
785 const QgsTextFormat titleFormat = mSettings.style( Qgis::LegendComponent::Title ).textFormat();
786 const double dotsPerMM = context.scaleFactor();
787
788 double overallTextHeight = 0;
789 double overallTextWidth = 0;
790
791 {
792 QgsScopedRenderContextScaleToPixels contextToPixels( context );
793 overallTextHeight = QgsTextRenderer::textHeight( context, titleFormat, lines, Qgis::TextLayoutMode::Rectangle );
794 overallTextWidth = QgsTextRenderer::textWidth( context, titleFormat, lines );
795 }
796
797 size.rheight() = overallTextHeight / dotsPerMM;
798 size.rwidth() = overallTextWidth / dotsPerMM;
799
800 if ( context.painter() )
801 {
802 QgsScopedRenderContextScaleToPixels contextToPixels( context );
803
804 const QRectF r( textBoxLeft * dotsPerMM, top * dotsPerMM, textBoxWidth * dotsPerMM, overallTextHeight );
805
806 Qgis::TextHorizontalAlignment halign = halignment == Qt::AlignLeft ? Qgis::TextHorizontalAlignment::Left :
808
809 QgsTextRenderer::drawText( r, 0, halign, lines, context, titleFormat );
810 }
811
812 return size;
813}
814
815
816double QgsLegendRenderer::spaceAboveGroup( const LegendComponentGroup &group )
817{
818 if ( group.components.isEmpty() ) return 0;
819
820 LegendComponent component = group.components.first();
821
822 if ( QgsLayerTreeGroup *nodeGroup = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
823 {
824 return mSettings.style( nodeLegendStyle( nodeGroup ) ).margin( QgsLegendStyle::Top );
825 }
826 else if ( QgsLayerTreeLayer *nodeLayer = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
827 {
828 return mSettings.style( nodeLegendStyle( nodeLayer ) ).margin( QgsLegendStyle::Top );
829 }
830 else if ( qobject_cast<QgsLayerTreeModelLegendNode *>( component.item ) )
831 {
832 // TODO: use Symbol or SymbolLabel Top margin
833 return mSettings.style( Qgis::LegendComponent::Symbol ).margin( QgsLegendStyle::Top );
834 }
835
836 return 0;
837}
838
839QSizeF QgsLegendRenderer::drawGroup( const LegendComponentGroup &group, QgsRenderContext &context, ColumnContext columnContext, double top )
840{
841 bool first = true;
842 QSizeF size = QSizeF( group.size );
843 double currentY = top;
844 for ( const LegendComponent &component : std::as_const( group.components ) )
845 {
846 if ( QgsLayerTreeGroup *groupItem = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
847 {
848 Qgis::LegendComponent s = nodeLegendStyle( groupItem );
850 {
851 if ( !first )
852 {
853 currentY += mSettings.style( s ).margin( QgsLegendStyle::Top );
854 }
855 QSizeF groupSize;
856 ColumnContext columnContextForItem = columnContext;
857 double indentWidth = component.indent;
859 {
860 // Remove indent - the subgroup items should be indented, not the subgroup title
861 indentWidth -= mSettings.style( Qgis::LegendComponent::Subgroup ).indent( );
862 }
863 else
864 {
865 // Remove indent - the group items should be indented, not the group title
866 indentWidth -= mSettings.style( Qgis::LegendComponent::Group ).indent( );
867 }
868 if ( mSettings.style( Qgis::LegendComponent::SymbolLabel ).alignment() == Qt::AlignLeft )
869 {
870 columnContextForItem.left += indentWidth;
871 }
872 if ( mSettings.style( Qgis::LegendComponent::SymbolLabel ).alignment() == Qt::AlignRight )
873 {
874 columnContextForItem.right -= indentWidth;
875 }
876 groupSize = drawGroupTitle( groupItem, context, columnContextForItem, currentY );
877 size.rwidth() = std::max( groupSize.width(), size.width() );
878 }
879 }
880 else if ( QgsLayerTreeLayer *layerItem = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
881 {
882 Qgis::LegendComponent s = nodeLegendStyle( layerItem );
884 {
885 if ( !first )
886 {
887 currentY += mSettings.style( s ).margin( QgsLegendStyle::Top );
888 }
889 QSizeF subGroupSize;
890
891 ColumnContext columnContextForItem = columnContext;
892 double indentWidth = component.indent;
893 columnContextForItem.left += indentWidth;
894 subGroupSize = drawLayerTitle( layerItem, context, columnContextForItem, currentY );
895 size.rwidth() = std::max( subGroupSize.width(), size.width() );
896 }
897 }
898 else if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( component.item ) )
899 {
900 if ( !first )
901 {
902 currentY += mSettings.style( Qgis::LegendComponent::Symbol ).margin( QgsLegendStyle::Top );
903 }
904
905 ColumnContext columnContextForItem = columnContext;
906 double indentWidth = 0;
907 indentWidth = component.indent;
908 if ( mSettings.style( Qgis::LegendComponent::SymbolLabel ).alignment() == Qt::AlignLeft )
909 {
910 columnContextForItem.left += indentWidth;
911 }
912 if ( mSettings.style( Qgis::LegendComponent::SymbolLabel ).alignment() == Qt::AlignRight )
913 {
914 columnContextForItem.right -= indentWidth;
915 }
916
917 LegendComponent symbolComponent = drawSymbolItem( legendNode, context, columnContextForItem, currentY, component.maxSiblingSymbolWidth );
918 // expand width, it may be wider because of label offsets
919 size.rwidth() = std::max( symbolComponent.size.width() + indentWidth, size.width() );
920 }
921 currentY += component.size.height();
922 first = false;
923 }
924 return size;
925}
926
927QgsLegendRenderer::LegendComponent QgsLegendRenderer::drawSymbolItem( QgsLayerTreeModelLegendNode *symbolItem, QgsRenderContext &context, ColumnContext columnContext, double top, double maxSiblingSymbolWidth )
928{
929 QgsLayerTreeModelLegendNode::ItemContext ctx;
930 ctx.context = &context;
931
932 // add a layer expression context scope
933 QgsExpressionContextScope *layerScope = nullptr;
934 if ( symbolItem->layerNode()->layer() )
935 {
936 layerScope = QgsExpressionContextUtils::layerScope( symbolItem->layerNode()->layer() );
937 context.expressionContext().appendScope( layerScope );
938 }
939
940 ctx.painter = context.painter();
942 ctx.point = QPointF( columnContext.left, top );
943 ctx.labelXOffset = maxSiblingSymbolWidth;
945
946 ctx.top = top;
947
948 ctx.columnLeft = columnContext.left;
949 ctx.columnRight = columnContext.right;
950
951 switch ( mSettings.symbolAlignment() )
952 {
953 case Qt::AlignLeft:
954 default:
955 ctx.columnLeft += mSettings.style( Qgis::LegendComponent::Symbol ).margin( QgsLegendStyle::Left );
956 break;
957
958 case Qt::AlignRight:
959 ctx.columnRight -= mSettings.style( Qgis::LegendComponent::Symbol ).margin( QgsLegendStyle::Left );
960 break;
961 }
962
963 ctx.maxSiblingSymbolWidth = maxSiblingSymbolWidth;
964
965 QgsExpressionContextScope *symbolScope = nullptr;
966 if ( const QgsSymbolLegendNode *symbolNode = dynamic_cast< const QgsSymbolLegendNode * >( symbolItem ) )
967 {
968 symbolScope = symbolNode->createSymbolScope();
969 context.expressionContext().appendScope( symbolScope );
970 ctx.patchShape = symbolNode->patchShape();
971 }
972
973 ctx.patchSize = symbolItem->userPatchSize();
974
975 QgsLayerTreeModelLegendNode::ItemMetrics im = symbolItem->draw( mSettings, ctx );
976
977 if ( symbolScope )
978 delete context.expressionContext().popScope();
979
980 if ( layerScope )
981 delete context.expressionContext().popScope();
982
983 LegendComponent component;
984 component.item = symbolItem;
985 component.symbolSize = im.symbolSize;
986 component.labelSize = im.labelSize;
987 //QgsDebugMsgLevel( u"symbol height = %1 label height = %2"_s.arg( symbolSize.height()).arg( labelSize.height() ), 2);
988 // NOTE -- we hard code left/right margins below, because those are the only ones exposed for use currently.
989 // ideally we could (should?) expose all these margins as settings, and then adapt the below to respect the current symbol/text alignment
990 // and consider the correct margin sides...
991 double width = std::max( static_cast< double >( im.symbolSize.width() ), maxSiblingSymbolWidth )
992 + mSettings.style( Qgis::LegendComponent::Symbol ).margin( QgsLegendStyle::Left )
993 + mSettings.style( Qgis::LegendComponent::Symbol ).margin( QgsLegendStyle::Right )
994 + mSettings.style( Qgis::LegendComponent::SymbolLabel ).margin( QgsLegendStyle::Left )
995 + im.labelSize.width();
996
997 double height = std::max( im.symbolSize.height(), im.labelSize.height() );
998 component.size = QSizeF( width, height );
999 return component;
1000}
1001
1002QSizeF QgsLegendRenderer::drawLayerTitle( QgsLayerTreeLayer *nodeLayer, QgsRenderContext &context, ColumnContext columnContext, double top )
1003{
1004 QSizeF size( 0, 0 );
1005 QModelIndex idx = mLegendModel->node2index( nodeLayer );
1006 QString titleString = mLegendModel->data( idx, Qt::DisplayRole ).toString();
1007 //Let the user omit the layer title item by having an empty layer title string
1008 if ( titleString.isEmpty() )
1009 return size;
1010
1011 const QgsTextFormat layerFormat = mSettings.style( nodeLegendStyle( nodeLayer ) ).textFormat();
1012
1013 QgsExpressionContextScope *layerScope = nullptr;
1014 if ( nodeLayer->layer() )
1015 {
1016 layerScope = QgsExpressionContextUtils::layerScope( nodeLayer->layer() );
1017 context.expressionContext().appendScope( layerScope );
1018 }
1019
1020 const QStringList lines = mSettings.evaluateItemText( titleString, context.expressionContext() );
1021
1022 const double dotsPerMM = context.scaleFactor();
1023
1024 double overallTextHeight = 0;
1025 double overallTextWidth = 0;
1026 {
1027 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1028 overallTextHeight = QgsTextRenderer::textHeight( context, layerFormat, lines, Qgis::TextLayoutMode::RectangleAscentBased );
1029 overallTextWidth = QgsTextRenderer::textWidth( context, layerFormat, lines );
1030 }
1031 const double sideMargin = mSettings.style( nodeLegendStyle( nodeLayer ) ).margin( QgsLegendStyle::Left );
1032
1033 size.rheight() = overallTextHeight / dotsPerMM;
1034 size.rwidth() = overallTextWidth / dotsPerMM + sideMargin *
1035 ( mSettings.style( nodeLegendStyle( nodeLayer ) ).alignment() == Qt::AlignHCenter ? 2 : 1 );
1036
1037 if ( context.painter() )
1038 {
1039 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1040 Qgis::TextHorizontalAlignment halign = mSettings.style( nodeLegendStyle( nodeLayer ) ).alignment() == Qt::AlignLeft ? Qgis::TextHorizontalAlignment::Left :
1041 mSettings.style( nodeLegendStyle( nodeLayer ) ).alignment() == Qt::AlignRight ? Qgis::TextHorizontalAlignment::Right : Qgis::TextHorizontalAlignment::Center;
1042
1043 const QRectF r( ( columnContext.left + ( halign == Qgis::TextHorizontalAlignment::Left ? sideMargin : 0 ) ) * dotsPerMM, top * dotsPerMM,
1044 ( ( columnContext.right - columnContext.left ) - ( halign == Qgis::TextHorizontalAlignment::Right ? sideMargin : 0 ) ) * dotsPerMM, overallTextHeight );
1045 QgsTextRenderer::drawText( r, 0, halign, lines, context, layerFormat );
1046 }
1047
1048 size.rheight() += mSettings.style( nodeLegendStyle( nodeLayer ) ).margin( QgsLegendStyle::Side::Bottom );
1049
1050 if ( layerScope )
1051 delete context.expressionContext().popScope();
1052
1053 return size;
1054}
1055
1056QSizeF QgsLegendRenderer::drawGroupTitle( QgsLayerTreeGroup *nodeGroup, QgsRenderContext &context, ColumnContext columnContext, double top )
1057{
1058 QSizeF size( 0, 0 );
1059 QModelIndex idx = mLegendModel->node2index( nodeGroup );
1060
1061 const QgsTextFormat groupFormat = mSettings.style( nodeLegendStyle( nodeGroup ) ).textFormat();
1062
1063 const QStringList lines = mSettings.evaluateItemText( mLegendModel->data( idx, Qt::DisplayRole ).toString(), context.expressionContext() );
1064
1065 double overallTextHeight = 0;
1066 double overallTextWidth = 0;
1067
1068 {
1069 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1070 overallTextHeight = QgsTextRenderer::textHeight( context, groupFormat, lines, Qgis::TextLayoutMode::RectangleAscentBased );
1071 overallTextWidth = QgsTextRenderer::textWidth( context, groupFormat, lines );
1072 }
1073
1074 const double sideMargin = mSettings.style( nodeLegendStyle( nodeGroup ) ).margin( QgsLegendStyle::Left );
1075 const double dotsPerMM = context.scaleFactor();
1076
1077 size.rheight() = overallTextHeight / dotsPerMM;
1078 size.rwidth() = overallTextWidth / dotsPerMM + sideMargin *
1079 ( mSettings.style( nodeLegendStyle( nodeGroup ) ).alignment() == Qt::AlignHCenter ? 2 : 1 );
1080
1081 if ( context.painter() )
1082 {
1083 QgsScopedRenderContextScaleToPixels contextToPixels( context );
1084
1085 Qgis::TextHorizontalAlignment halign = mSettings.style( nodeLegendStyle( nodeGroup ) ).alignment() == Qt::AlignLeft ? Qgis::TextHorizontalAlignment::Left :
1086 mSettings.style( nodeLegendStyle( nodeGroup ) ).alignment() == Qt::AlignRight ? Qgis::TextHorizontalAlignment::Right : Qgis::TextHorizontalAlignment::Center;
1087
1088 const QRectF r( dotsPerMM * ( columnContext.left + ( halign == Qgis::TextHorizontalAlignment::Left ? sideMargin : 0 ) ), top * dotsPerMM,
1089 dotsPerMM * ( ( columnContext.right - columnContext.left ) - ( halign == Qgis::TextHorizontalAlignment::Right ? sideMargin : 0 ) ), overallTextHeight );
1090 QgsTextRenderer::drawText( r, 0, halign, lines, context, groupFormat );
1091 }
1092
1093 size.rheight() += mSettings.style( nodeLegendStyle( nodeGroup ) ).margin( QgsLegendStyle::Bottom );
1094 return size;
1095}
1096
1098{
1099 QString style = node->customProperty( u"legend/title-style"_s ).toString();
1100 if ( style == "hidden"_L1 )
1102 else if ( style == "group"_L1 )
1104 else if ( style == "subgroup"_L1 )
1106
1107 // use a default otherwise
1108 if ( QgsLayerTree::isGroup( node ) )
1110 else if ( QgsLayerTree::isLayer( node ) )
1111 {
1112 if ( model->legendNodeEmbeddedInParent( QgsLayerTree::toLayer( node ) ) )
1115 }
1116
1117 return Qgis::LegendComponent::Undefined; // should not happen, only if corrupted project file
1118}
1119
1121{
1122 return nodeLegendStyle( node, mLegendModel );
1123}
1124
1126{
1127 return mProxyModel.get();
1128}
1129
1131{
1132 QString str;
1133 switch ( style )
1134 {
1136 str = u"hidden"_s;
1137 break;
1139 str = u"group"_s;
1140 break;
1142 str = u"subgroup"_s;
1143 break;
1144 default:
1145 break; // nothing
1146 }
1147
1148 if ( !str.isEmpty() )
1149 node->setCustomProperty( u"legend/title-style"_s, str );
1150 else
1151 node->removeCustomProperty( u"legend/title-style"_s );
1152}
1153
1155{
1156 paintAndDetermineSize( context );
1157}
1158
LegendComponent
Component of legends which can be styled.
Definition qgis.h:4634
@ Symbol
Symbol icon (excluding label).
Definition qgis.h:4640
@ Group
Legend group title.
Definition qgis.h:4638
@ Hidden
Special style, item is hidden including margins around.
Definition qgis.h:4636
@ Subgroup
Legend subgroup title.
Definition qgis.h:4639
@ Title
Legend title.
Definition qgis.h:4637
@ SymbolLabel
Symbol label (excluding icon).
Definition qgis.h:4641
@ Undefined
Should not happen, only if corrupted project file.
Definition qgis.h:4635
@ RectangleAscentBased
Similar to Rectangle mode, but uses ascents only when calculating font and line heights.
Definition qgis.h:2964
@ Rectangle
Text within rectangle layout mode.
Definition qgis.h:2960
@ ShowRuleDetails
If set, the rule expression of a rule based renderer legend item will be added to the JSON.
Definition qgis.h:4668
@ ApplyScalingWorkaroundForTextRendering
Whether a scaling workaround designed to stablise the rendering of small font sizes (or for painters ...
Definition qgis.h:2820
TextHorizontalAlignment
Text horizontal alignment.
Definition qgis.h:3000
@ Center
Center align.
Definition qgis.h:3002
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
QgsExpressionContextScope * popScope()
Removes the last scope from the expression context and return it.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
A sort filter proxy model to easily reproduce the legend/layer tree in a tree view.
Layer tree group node serves as a container for layers and further groups.
Layer tree node points to a map layer.
@ AllowSplittingLegendNodesOverMultipleColumns
Allow splitting node's legend nodes across multiple columns.
@ PreventSplittingLegendNodesOverMultipleColumns
Prevent splitting node's legend nodes across multiple columns.
@ UseDefaultLegendSetting
Inherit default legend column splitting setting.
LegendNodesSplitBehavior legendSplitBehavior() const
Returns the column split behavior for the node.
QgsMapLayer * layer() const
Returns the map layer associated with this node.
An abstract interface for legend items returned from QgsMapLayerLegend implementation.
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.
virtual bool columnBreak() const
Returns whether a forced column break should occur before the node.
virtual ItemMetrics draw(const QgsLegendSettings &settings, ItemContext &ctx)
Entry point called from QgsLegendRenderer to do the rendering.
virtual QSizeF userPatchSize() const
Returns the user (overridden) size for the legend node.
QgsLayerTreeLayer * layerNode() const
Returns pointer to the parent layer node.
A model representing the layer tree, including layers and groups of layers.
QModelIndex node2index(QgsLayerTreeNode *node) const
Returns index for a given node. If the node does not belong to the layer tree, the result is undefine...
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
QgsLayerTreeModelLegendNode * legendNodeEmbeddedInParent(QgsLayerTreeLayer *nodeLayer) const
Returns legend node that may be embedded in parent (i.e.
Base class for nodes in a layer tree.
void setCustomProperty(const QString &key, const QVariant &value)
Sets a custom property for the node. Properties are stored in a map and saved in project file.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
void removeCustomProperty(const QString &key)
Remove a custom property from layer. Properties are stored in a map and saved in project file.
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.
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
static bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
static QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group.
QSizeF minimumSize(QgsRenderContext *renderContext=nullptr)
Runs the layout algorithm and returns the minimum size required for the legend.
QgsLayerTreeFilterProxyModel * proxyModel()
Returns the filter proxy model used for filtering the legend model content during rendering.
QJsonObject exportLegendToJson(const QgsRenderContext &context)
Renders the legend in a json object.
QgsLegendRenderer(QgsLayerTreeModel *legendModel, const QgsLegendSettings &settings)
Constructor for QgsLegendRenderer.
Q_DECL_DEPRECATED void drawLegend(QPainter *painter)
Draws the legend with given painter.
static void setNodeLegendStyle(QgsLayerTreeNode *node, Qgis::LegendComponent style)
Sets the style of a node.
static Qgis::LegendComponent nodeLegendStyle(QgsLayerTreeNode *node, QgsLayerTreeModel *model)
Returns the style for the given node, within the specified model.
void setProxyModel(QgsLayerTreeFilterProxyModel *model)
Sets the filter proxy model to use for filtering the legend model content during rendering.
Stores the appearance and layout settings for legend drawing with QgsLegendRenderer.
@ Right
Right side.
@ Left
Left side.
@ Bottom
Bottom side.
Perform transforms between map coordinates and device coordinates.
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns 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.
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.
Scoped object for temporary replacement of a QgsRenderContext destination painter.
Scoped object for temporary scaling of a QgsRenderContext for millimeter based rendering.
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.
static void drawText(const QRectF &rect, double rotation, Qgis::TextHorizontalAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, Qgis::TextVerticalAlignment vAlignment=Qgis::TextVerticalAlignment::Top, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Rectangle)
Draws text within a rectangle using the specified settings.
static double textHeight(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Point, QFontMetricsF *fontMetrics=nullptr, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), double maxLineWidth=0)
Returns the height of a text based on a given format.
QgsLayerTreeModelLegendNode * legendNode(const QString &rule, QgsLayerTreeModel &model)
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:7451
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7450
Q_DECL_DEPRECATED double labelXOffset
Offset from the left side where label should start.
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_DECL_DEPRECATED QPointF point
Top-left corner of the legend item.
Q_NOWARN_DEPRECATED_POP QgsRenderContext * context
Render context, if available.