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