QGIS API Documentation  3.6.0-Noosa (5873452)
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 "qgslayertree.h"
19 #include "qgslayertreemodel.h"
21 #include "qgslegendstyle.h"
22 #include "qgsmaplayerlegend.h"
23 #include "qgssymbol.h"
24 #include "qgsrendercontext.h"
25 #include "qgsvectorlayer.h"
27 
28 #include <QPainter>
29 
30 
31 
33  : mLegendModel( legendModel )
34  , mSettings( settings )
35 {
36 }
37 
39 {
40  return paintAndDetermineSize( renderContext );
41 }
42 
43 void QgsLegendRenderer::drawLegend( QPainter *painter )
44 {
45  paintAndDetermineSize( painter );
46 }
47 
48 QSizeF QgsLegendRenderer::paintAndDetermineSize( QPainter *painter )
49 {
50  return paintAndDetermineSizeInternal( nullptr, painter );
51 }
52 
53 QSizeF QgsLegendRenderer::paintAndDetermineSizeInternal( QgsRenderContext *context, QPainter *painter )
54 {
55  QSizeF size( 0, 0 );
56  QgsLayerTreeGroup *rootGroup = mLegendModel->rootGroup();
57  if ( !rootGroup )
58  return size;
59 
60  QList<Atom> atomList = createAtomList( rootGroup, mSettings.splitLayer() );
61 
62  setColumns( atomList );
63 
64  qreal maxColumnWidth = 0;
65  if ( mSettings.equalColumnWidth() )
66  {
67  Q_FOREACH ( const Atom &atom, atomList )
68  {
69  maxColumnWidth = std::max( atom.size.width(), maxColumnWidth );
70  }
71  }
72 
73  //calculate size of title
74  QSizeF titleSize = drawTitle();
75  //add title margin to size of title text
76  titleSize.rwidth() += mSettings.boxSpace() * 2.0;
77  double columnTop = mSettings.boxSpace() + titleSize.height() + mSettings.style( QgsLegendStyle::Title ).margin( QgsLegendStyle::Bottom );
78 
79  QPointF point( mSettings.boxSpace(), columnTop );
80  bool firstInColumn = true;
81  double columnMaxHeight = 0;
82  qreal columnWidth = 0;
83  int column = 0;
84  Q_FOREACH ( const Atom &atom, atomList )
85  {
86  if ( atom.column > column )
87  {
88  // Switch to next column
89  if ( mSettings.equalColumnWidth() )
90  {
91  point.rx() += mSettings.columnSpace() + maxColumnWidth;
92  }
93  else
94  {
95  point.rx() += mSettings.columnSpace() + columnWidth;
96  }
97  point.ry() = columnTop;
98  columnWidth = 0;
99  column++;
100  firstInColumn = true;
101  }
102  if ( !firstInColumn )
103  {
104  point.ry() += spaceAboveAtom( atom );
105  }
106 
107  QSizeF atomSize = context ? drawAtom( atom, context, point )
108  : drawAtom( atom, painter, point );
109  columnWidth = std::max( atomSize.width(), columnWidth );
110 
111  point.ry() += atom.size.height();
112  columnMaxHeight = std::max( point.y() - columnTop, columnMaxHeight );
113 
114  firstInColumn = false;
115  }
116  point.rx() += columnWidth + mSettings.boxSpace();
117 
118  size.rheight() = columnTop + columnMaxHeight + mSettings.boxSpace();
119  size.rwidth() = point.x();
120  if ( !mSettings.title().isEmpty() )
121  {
122  size.rwidth() = std::max( titleSize.width(), size.width() );
123  }
124 
125  // override the size if it was set by the user
126  if ( mLegendSize.isValid() )
127  {
128  qreal w = std::max( size.width(), mLegendSize.width() );
129  qreal h = std::max( size.height(), mLegendSize.height() );
130  size = QSizeF( w, h );
131  }
132 
133  // Now we have set the correct total item width and can draw the title centered
134  if ( !mSettings.title().isEmpty() )
135  {
136  if ( mSettings.titleAlignment() == Qt::AlignLeft )
137  {
138  point.rx() = mSettings.boxSpace();
139  }
140  else if ( mSettings.titleAlignment() == Qt::AlignHCenter )
141  {
142  point.rx() = size.width() / 2;
143  }
144  else
145  {
146  point.rx() = size.width() - mSettings.boxSpace();
147  }
148  point.ry() = mSettings.boxSpace();
149  if ( context )
150  drawTitle( context, point, mSettings.titleAlignment(), size.width() );
151  else
152  drawTitle( painter, point, mSettings.titleAlignment(), size.width() );
153  }
154 
155  return size;
156 }
157 
158 
159 QList<QgsLegendRenderer::Atom> QgsLegendRenderer::createAtomList( QgsLayerTreeGroup *parentGroup, bool splitLayer )
160 {
161  QList<Atom> atoms;
162 
163  if ( !parentGroup ) return atoms;
164 
165  Q_FOREACH ( QgsLayerTreeNode *node, parentGroup->children() )
166  {
167  if ( QgsLayerTree::isGroup( node ) )
168  {
169  QgsLayerTreeGroup *nodeGroup = QgsLayerTree::toGroup( node );
170 
171  // Group subitems
172  QList<Atom> groupAtoms = createAtomList( nodeGroup, splitLayer );
173  bool hasSubItems = !groupAtoms.empty();
174 
175  if ( nodeLegendStyle( nodeGroup ) != QgsLegendStyle::Hidden )
176  {
177  Nucleon nucleon;
178  nucleon.item = node;
179  nucleon.size = drawGroupTitle( nodeGroup );
180 
181  if ( !groupAtoms.isEmpty() )
182  {
183  // Add internal space between this group title and the next nucleon
184  groupAtoms[0].size.rheight() += spaceAboveAtom( groupAtoms[0] );
185  // Prepend this group title to the first atom
186  groupAtoms[0].nucleons.prepend( nucleon );
187  groupAtoms[0].size.rheight() += nucleon.size.height();
188  groupAtoms[0].size.rwidth() = std::max( nucleon.size.width(), groupAtoms[0].size.width() );
189  }
190  else
191  {
192  // no subitems, append new atom
193  Atom atom;
194  atom.nucleons.append( nucleon );
195  atom.size.rwidth() += nucleon.size.width();
196  atom.size.rheight() += nucleon.size.height();
197  atom.size.rwidth() = std::max( nucleon.size.width(), atom.size.width() );
198  groupAtoms.append( atom );
199  }
200  }
201 
202  if ( hasSubItems ) //leave away groups without content
203  {
204  atoms.append( groupAtoms );
205  }
206 
207  }
208  else if ( QgsLayerTree::isLayer( node ) )
209  {
210  QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
211 
212  Atom atom;
213 
214  if ( nodeLegendStyle( nodeLayer ) != QgsLegendStyle::Hidden )
215  {
216  Nucleon nucleon;
217  nucleon.item = node;
218  nucleon.size = drawLayerTitle( nodeLayer );
219  atom.nucleons.append( nucleon );
220  atom.size.rwidth() = nucleon.size.width();
221  atom.size.rheight() = nucleon.size.height();
222  }
223 
224  QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->layerLegendNodes( nodeLayer );
225 
226  // workaround for the issue that "filtering by map" does not remove layer nodes that have no symbols present
227  // on the map. We explicitly skip such layers here. In future ideally that should be handled directly
228  // in the layer tree model
229  if ( legendNodes.isEmpty() && mLegendModel->legendFilterMapSettings() )
230  continue;
231 
232  QList<Atom> layerAtoms;
233 
234  for ( int j = 0; j < legendNodes.count(); j++ )
235  {
236  QgsLayerTreeModelLegendNode *legendNode = legendNodes.at( j );
237 
238  Nucleon symbolNucleon = drawSymbolItem( legendNode );
239 
240  if ( !mSettings.splitLayer() || j == 0 )
241  {
242  // append to layer atom
243  // the width is not correct at this moment, we must align all symbol labels
244  atom.size.rwidth() = std::max( symbolNucleon.size.width(), atom.size.width() );
245  // Add symbol space only if there is already title or another item above
246  if ( !atom.nucleons.isEmpty() )
247  {
248  // TODO: for now we keep Symbol and SymbolLabel Top margin in sync
249  atom.size.rheight() += mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Top );
250  }
251  atom.size.rheight() += symbolNucleon.size.height();
252  atom.nucleons.append( symbolNucleon );
253  }
254  else
255  {
256  Atom symbolAtom;
257  symbolAtom.nucleons.append( symbolNucleon );
258  symbolAtom.size.rwidth() = symbolNucleon.size.width();
259  symbolAtom.size.rheight() = symbolNucleon.size.height();
260  layerAtoms.append( symbolAtom );
261  }
262  }
263  layerAtoms.prepend( atom );
264  atoms.append( layerAtoms );
265  }
266  }
267 
268  return atoms;
269 }
270 
271 
272 void QgsLegendRenderer::setColumns( QList<Atom> &atomList )
273 {
274  if ( mSettings.columnCount() == 0 ) return;
275 
276  // Divide atoms to columns
277  double totalHeight = 0;
278  qreal maxAtomHeight = 0;
279  Q_FOREACH ( const Atom &atom, atomList )
280  {
281  totalHeight += spaceAboveAtom( atom );
282  totalHeight += atom.size.height();
283  maxAtomHeight = std::max( atom.size.height(), maxAtomHeight );
284  }
285 
286  // We know height of each atom and we have to split them into columns
287  // minimizing max column height. It is sort of bin packing problem, NP-hard.
288  // We are using simple heuristic, brute fore appeared to be to slow,
289  // the number of combinations is N = n!/(k!*(n-k)!) where n = atomsCount-1
290  // and k = columnsCount-1
291  double maxColumnHeight = 0;
292  int currentColumn = 0;
293  int currentColumnAtomCount = 0; // number of atoms in current column
294  double currentColumnHeight = 0;
295  double closedColumnsHeight = 0;
296 
297  for ( int i = 0; i < atomList.size(); i++ )
298  {
299  // Recalc average height for remaining columns including current
300  double avgColumnHeight = ( totalHeight - closedColumnsHeight ) / ( mSettings.columnCount() - currentColumn );
301 
302  Atom atom = atomList.at( i );
303  double currentHeight = currentColumnHeight;
304  if ( currentColumnAtomCount > 0 )
305  currentHeight += spaceAboveAtom( atom );
306  currentHeight += atom.size.height();
307 
308  bool canCreateNewColumn = ( currentColumnAtomCount > 0 ) // do not leave empty column
309  && ( currentColumn < mSettings.columnCount() - 1 ); // must not exceed max number of columns
310 
311  bool shouldCreateNewColumn = ( currentHeight - avgColumnHeight ) > atom.size.height() / 2 // center of current atom is over average height
312  && currentColumnAtomCount > 0 // do not leave empty column
313  && currentHeight > maxAtomHeight // no sense to make smaller columns than max atom height
314  && currentHeight > maxColumnHeight; // no sense to make smaller columns than max column already created
315 
316  // also should create a new column if the number of items left < number of columns left
317  // in this case we should spread the remaining items out over the remaining columns
318  shouldCreateNewColumn |= ( atomList.size() - i < mSettings.columnCount() - currentColumn );
319 
320  if ( canCreateNewColumn && shouldCreateNewColumn )
321  {
322  // New column
323  currentColumn++;
324  currentColumnAtomCount = 0;
325  closedColumnsHeight += currentColumnHeight;
326  currentColumnHeight = atom.size.height();
327  }
328  else
329  {
330  currentColumnHeight = currentHeight;
331  }
332  atomList[i].column = currentColumn;
333  currentColumnAtomCount++;
334  maxColumnHeight = std::max( currentColumnHeight, maxColumnHeight );
335  }
336 
337  // Align labels of symbols for each layr/column to the same labelXOffset
338  QMap<QString, qreal> maxSymbolWidth;
339  for ( int i = 0; i < atomList.size(); i++ )
340  {
341  Atom &atom = atomList[i];
342  for ( int j = 0; j < atom.nucleons.size(); j++ )
343  {
344  if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( atom.nucleons.at( j ).item ) )
345  {
346  QString key = QStringLiteral( "%1-%2" ).arg( reinterpret_cast< qulonglong >( legendNode->layerNode() ) ).arg( atom.column );
347  maxSymbolWidth[key] = std::max( atom.nucleons.at( j ).symbolSize.width(), maxSymbolWidth[key] );
348  }
349  }
350  }
351  for ( int i = 0; i < atomList.size(); i++ )
352  {
353  Atom &atom = atomList[i];
354  for ( int j = 0; j < atom.nucleons.size(); j++ )
355  {
356  if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( atom.nucleons.at( j ).item ) )
357  {
358  QString key = QStringLiteral( "%1-%2" ).arg( reinterpret_cast< qulonglong >( legendNode->layerNode() ) ).arg( atom.column );
359  double space = mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Right ) +
361  atom.nucleons[j].labelXOffset = maxSymbolWidth[key] + space;
362  atom.nucleons[j].size.rwidth() = maxSymbolWidth[key] + space + atom.nucleons.at( j ).labelSize.width();
363  }
364  }
365  }
366 }
367 
368 QSizeF QgsLegendRenderer::drawTitle( QPainter *painter, QPointF point, Qt::AlignmentFlag halignment, double legendWidth )
369 {
370  return drawTitleInternal( nullptr, painter, point, halignment, legendWidth );
371 }
372 
373 QSizeF QgsLegendRenderer::drawTitleInternal( QgsRenderContext *context, QPainter *painter, QPointF point, Qt::AlignmentFlag halignment, double legendWidth )
374 {
375  QSizeF size( 0, 0 );
376  if ( mSettings.title().isEmpty() )
377  {
378  return size;
379  }
380 
381  QStringList lines = mSettings.splitStringForWrapping( mSettings.title() );
382  double y = point.y();
383 
384  if ( context && context->painter() )
385  {
386  context->painter()->setPen( mSettings.fontColor() );
387  }
388  else if ( painter )
389  {
390  painter->setPen( mSettings.fontColor() );
391  }
392 
393  //calculate width and left pos of rectangle to draw text into
394  double textBoxWidth;
395  double textBoxLeft;
396  switch ( halignment )
397  {
398  case Qt::AlignHCenter:
399  textBoxWidth = ( std::min( static_cast< double >( point.x() ), legendWidth - point.x() ) - mSettings.boxSpace() ) * 2.0;
400  textBoxLeft = point.x() - textBoxWidth / 2.;
401  break;
402  case Qt::AlignRight:
403  textBoxLeft = mSettings.boxSpace();
404  textBoxWidth = point.x() - mSettings.boxSpace();
405  break;
406  case Qt::AlignLeft:
407  default:
408  textBoxLeft = point.x();
409  textBoxWidth = legendWidth - point.x() - mSettings.boxSpace();
410  break;
411  }
412 
413  QFont titleFont = mSettings.style( QgsLegendStyle::Title ).font();
414 
415  for ( QStringList::Iterator titlePart = lines.begin(); titlePart != lines.end(); ++titlePart )
416  {
417  //last word is not drawn if rectangle width is exactly text width, so add 1
418  //TODO - correctly calculate size of italicized text, since QFontMetrics does not
419  qreal width = mSettings.textWidthMillimeters( titleFont, *titlePart ) + 1;
420  qreal height = mSettings.fontAscentMillimeters( titleFont ) + mSettings.fontDescentMillimeters( titleFont );
421 
422  QRectF r( textBoxLeft, y, textBoxWidth, height );
423 
424  if ( context && context->painter() )
425  {
426  mSettings.drawText( context->painter(), r, *titlePart, titleFont, halignment, Qt::AlignVCenter, Qt::TextDontClip );
427  }
428  else if ( painter )
429  {
430  mSettings.drawText( painter, r, *titlePart, titleFont, halignment, Qt::AlignVCenter, Qt::TextDontClip );
431  }
432 
433  //update max width of title
434  size.rwidth() = std::max( width, size.rwidth() );
435 
436  y += height;
437  if ( titlePart != ( lines.end() - 1 ) )
438  {
439  y += mSettings.lineSpacing();
440  }
441  }
442  size.rheight() = y - point.y();
443 
444  return size;
445 }
446 
447 
448 double QgsLegendRenderer::spaceAboveAtom( const Atom &atom )
449 {
450  if ( atom.nucleons.isEmpty() ) return 0;
451 
452  Nucleon nucleon = atom.nucleons.first();
453 
454  if ( QgsLayerTreeGroup *nodeGroup = qobject_cast<QgsLayerTreeGroup *>( nucleon.item ) )
455  {
456  return mSettings.style( nodeLegendStyle( nodeGroup ) ).margin( QgsLegendStyle::Top );
457  }
458  else if ( QgsLayerTreeLayer *nodeLayer = qobject_cast<QgsLayerTreeLayer *>( nucleon.item ) )
459  {
460  return mSettings.style( nodeLegendStyle( nodeLayer ) ).margin( QgsLegendStyle::Top );
461  }
462  else if ( qobject_cast<QgsLayerTreeModelLegendNode *>( nucleon.item ) )
463  {
464  // TODO: use Symbol or SymbolLabel Top margin
466  }
467 
468  return 0;
469 }
470 
471 
472 // Draw atom and expand its size (using actual nucleons labelXOffset)
473 QSizeF QgsLegendRenderer::drawAtom( const Atom &atom, QPainter *painter, QPointF point )
474 {
475  return drawAtomInternal( atom, nullptr, painter, point );
476 }
477 
478 QSizeF QgsLegendRenderer::drawAtomInternal( const Atom &atom, QgsRenderContext *context, QPainter *painter, QPointF point )
479 {
480  bool first = true;
481  QSizeF size = QSizeF( atom.size );
482  Q_FOREACH ( const Nucleon &nucleon, atom.nucleons )
483  {
484  if ( QgsLayerTreeGroup *groupItem = qobject_cast<QgsLayerTreeGroup *>( nucleon.item ) )
485  {
486  QgsLegendStyle::Style s = nodeLegendStyle( groupItem );
487  if ( s != QgsLegendStyle::Hidden )
488  {
489  if ( !first )
490  {
491  point.ry() += mSettings.style( s ).margin( QgsLegendStyle::Top );
492  }
493  if ( context )
494  drawGroupTitle( groupItem, context, point );
495  else
496  drawGroupTitle( groupItem, painter, point );
497  }
498  }
499  else if ( QgsLayerTreeLayer *layerItem = qobject_cast<QgsLayerTreeLayer *>( nucleon.item ) )
500  {
501  QgsLegendStyle::Style s = nodeLegendStyle( layerItem );
502  if ( s != QgsLegendStyle::Hidden )
503  {
504  if ( !first )
505  {
506  point.ry() += mSettings.style( s ).margin( QgsLegendStyle::Top );
507  }
508  if ( context )
509  drawLayerTitle( layerItem, context, point );
510  else
511  drawLayerTitle( layerItem, painter, point );
512  }
513  }
514  else if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( nucleon.item ) )
515  {
516  if ( !first )
517  {
518  point.ry() += mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Top );
519  }
520 
521  Nucleon symbolNucleon = context ? drawSymbolItem( legendNode, context, point, nucleon.labelXOffset )
522  : drawSymbolItem( legendNode, painter, point, nucleon.labelXOffset );
523  // expand width, it may be wider because of labelXOffset
524  size.rwidth() = std::max( symbolNucleon.size.width(), size.width() );
525  }
526  point.ry() += nucleon.size.height();
527  first = false;
528  }
529  return size;
530 }
531 
532 QgsLegendRenderer::Nucleon QgsLegendRenderer::drawSymbolItem( QgsLayerTreeModelLegendNode *symbolItem, QPainter *painter, QPointF point, double labelXOffset )
533 {
534  return drawSymbolItemInternal( symbolItem, nullptr, painter, point, labelXOffset );
535 }
536 
537 QgsLegendRenderer::Nucleon QgsLegendRenderer::drawSymbolItemInternal( QgsLayerTreeModelLegendNode *symbolItem, QgsRenderContext *context, QPainter *painter, QPointF point, double labelXOffset )
538 {
540  ctx.context = context;
541 
542  // add a layer expression context scope
543  QgsExpressionContextScope *layerScope = nullptr;
544  if ( context && symbolItem->layerNode()->layer() )
545  {
546  layerScope = QgsExpressionContextUtils::layerScope( symbolItem->layerNode()->layer() );
547  context->expressionContext().appendScope( layerScope );
548  }
549 
550  ctx.painter = context ? context->painter() : painter;
551  ctx.point = point;
552  ctx.labelXOffset = labelXOffset;
553 
554  QgsLayerTreeModelLegendNode::ItemMetrics im = symbolItem->draw( mSettings, context ? &ctx
555  : ( painter ? &ctx : nullptr ) );
556 
557  if ( layerScope )
558  delete context->expressionContext().popScope();
559 
560  Nucleon nucleon;
561  nucleon.item = symbolItem;
562  nucleon.symbolSize = im.symbolSize;
563  nucleon.labelSize = im.labelSize;
564  //QgsDebugMsg( QStringLiteral( "symbol height = %1 label height = %2").arg( symbolSize.height()).arg( labelSize.height() ));
565  double width = std::max( static_cast< double >( im.symbolSize.width() ), labelXOffset ) + im.labelSize.width();
566  double height = std::max( im.symbolSize.height(), im.labelSize.height() );
567  nucleon.size = QSizeF( width, height );
568  return nucleon;
569 }
570 
571 QSizeF QgsLegendRenderer::drawLayerTitle( QgsLayerTreeLayer *nodeLayer, QPainter *painter, QPointF point )
572 {
573  return drawLayerTitleInternal( nodeLayer, nullptr, painter, point );
574 }
575 
576 QSizeF QgsLegendRenderer::drawLayerTitleInternal( QgsLayerTreeLayer *nodeLayer, QgsRenderContext *context, QPainter *painter, QPointF point )
577 {
578  QSizeF size( 0, 0 );
579  QModelIndex idx = mLegendModel->node2index( nodeLayer );
580 
581  //Let the user omit the layer title item by having an empty layer title string
582  if ( mLegendModel->data( idx, Qt::DisplayRole ).toString().isEmpty() )
583  return size;
584 
585  double y = point.y();
586 
587  if ( context && context->painter() )
588  context->painter()->setPen( mSettings.fontColor() );
589  else if ( painter )
590  painter->setPen( mSettings.fontColor() );
591 
592  QFont layerFont = mSettings.style( nodeLegendStyle( nodeLayer ) ).font();
593 
594  QgsExpressionContextScope *layerScope = nullptr;
595  if ( context && nodeLayer->layer() )
596  {
597  layerScope = QgsExpressionContextUtils::layerScope( nodeLayer->layer() );
598  context->expressionContext().appendScope( layerScope );
599  }
600 
601  QgsExpressionContext tempContext;
602 
603  const QStringList lines = mSettings.evaluateItemText( mLegendModel->data( idx, Qt::DisplayRole ).toString(),
604  context ? context->expressionContext() : tempContext );
605  for ( QStringList::ConstIterator layerItemPart = lines.constBegin(); layerItemPart != lines.constEnd(); ++layerItemPart )
606  {
607  y += mSettings.fontAscentMillimeters( layerFont );
608  if ( context && context->painter() )
609  mSettings.drawText( context->painter(), point.x(), y, *layerItemPart, layerFont );
610  if ( painter )
611  mSettings.drawText( painter, point.x(), y, *layerItemPart, layerFont );
612  qreal width = mSettings.textWidthMillimeters( layerFont, *layerItemPart );
613  size.rwidth() = std::max( width, size.width() );
614  if ( layerItemPart != ( lines.end() - 1 ) )
615  {
616  y += mSettings.lineSpacing();
617  }
618  }
619  size.rheight() = y - point.y();
620  size.rheight() += mSettings.style( nodeLegendStyle( nodeLayer ) ).margin( QgsLegendStyle::Side::Bottom );
621 
622  if ( layerScope )
623  delete context->expressionContext().popScope();
624 
625  return size;
626 }
627 
628 QSizeF QgsLegendRenderer::drawGroupTitle( QgsLayerTreeGroup *nodeGroup, QPainter *painter, QPointF point )
629 {
630  return drawGroupTitleInternal( nodeGroup, nullptr, painter, point );
631 }
632 
633 QSizeF QgsLegendRenderer::drawGroupTitleInternal( QgsLayerTreeGroup *nodeGroup, QgsRenderContext *context, QPainter *painter, QPointF point )
634 {
635  QSizeF size( 0, 0 );
636  QModelIndex idx = mLegendModel->node2index( nodeGroup );
637 
638  double y = point.y();
639 
640  if ( context && context->painter() )
641  context->painter()->setPen( mSettings.fontColor() );
642  else if ( painter )
643  painter->setPen( mSettings.fontColor() );
644 
645  QFont groupFont = mSettings.style( nodeLegendStyle( nodeGroup ) ).font();
646 
647  QgsExpressionContext tempContext;
648 
649  const QStringList lines = mSettings.evaluateItemText( mLegendModel->data( idx, Qt::DisplayRole ).toString(),
650  context ? context->expressionContext() : tempContext );
651  for ( QStringList::ConstIterator groupPart = lines.constBegin(); groupPart != lines.constEnd(); ++groupPart )
652  {
653  y += mSettings.fontAscentMillimeters( groupFont );
654  if ( context && context->painter() )
655  mSettings.drawText( context->painter(), point.x(), y, *groupPart, groupFont );
656  else if ( painter )
657  mSettings.drawText( painter, point.x(), y, *groupPart, groupFont );
658  qreal width = mSettings.textWidthMillimeters( groupFont, *groupPart );
659  size.rwidth() = std::max( width, size.width() );
660  if ( groupPart != ( lines.end() - 1 ) )
661  {
662  y += mSettings.lineSpacing();
663  }
664  }
665  size.rheight() = y - point.y();
666  return size;
667 }
668 
670 {
671  QString style = node->customProperty( QStringLiteral( "legend/title-style" ) ).toString();
672  if ( style == QLatin1String( "hidden" ) )
673  return QgsLegendStyle::Hidden;
674  else if ( style == QLatin1String( "group" ) )
675  return QgsLegendStyle::Group;
676  else if ( style == QLatin1String( "subgroup" ) )
678 
679  // use a default otherwise
680  if ( QgsLayerTree::isGroup( node ) )
681  return QgsLegendStyle::Group;
682  else if ( QgsLayerTree::isLayer( node ) )
683  {
684  if ( model->legendNodeEmbeddedInParent( QgsLayerTree::toLayer( node ) ) )
685  return QgsLegendStyle::Hidden;
687  }
688 
689  return QgsLegendStyle::Undefined; // should not happen, only if corrupted project file
690 }
691 
693 {
694  return nodeLegendStyle( node, mLegendModel );
695 }
696 
698 {
699  QString str;
700  switch ( style )
701  {
703  str = QStringLiteral( "hidden" );
704  break;
706  str = QStringLiteral( "group" );
707  break;
709  str = QStringLiteral( "subgroup" );
710  break;
711  default:
712  break; // nothing
713  }
714 
715  if ( !str.isEmpty() )
716  node->setCustomProperty( QStringLiteral( "legend/title-style" ), str );
717  else
718  node->removeCustomProperty( QStringLiteral( "legend/title-style" ) );
719 }
720 
721 QSizeF QgsLegendRenderer::drawTitle( QgsRenderContext *rendercontext, QPointF point, Qt::AlignmentFlag halignment, double legendWidth )
722 {
723  return drawTitleInternal( rendercontext, nullptr, point, halignment, legendWidth );
724 }
725 
726 QSizeF QgsLegendRenderer::drawAtom( const Atom &atom, QgsRenderContext *rendercontext, QPointF point )
727 {
728  return drawAtomInternal( atom, rendercontext, nullptr, point );
729 }
730 
731 QgsLegendRenderer::Nucleon QgsLegendRenderer::drawSymbolItem( QgsLayerTreeModelLegendNode *symbolItem, QgsRenderContext *rendercontext, QPointF point, double labelXOffset )
732 {
733  return drawSymbolItemInternal( symbolItem, rendercontext, nullptr, point, labelXOffset );
734 }
735 
736 QSizeF QgsLegendRenderer::drawLayerTitle( QgsLayerTreeLayer *nodeLayer, QgsRenderContext *rendercontext, QPointF point )
737 {
738  return drawLayerTitleInternal( nodeLayer, rendercontext, nullptr, point );
739 }
740 
741 QSizeF QgsLegendRenderer::drawGroupTitle( QgsLayerTreeGroup *nodeGroup, QgsRenderContext *rendercontext, QPointF point )
742 {
743  return drawGroupTitleInternal( nodeGroup, rendercontext, nullptr, point );
744 }
745 
747 {
748  paintAndDetermineSize( &context );
749 }
750 
751 QSizeF QgsLegendRenderer::paintAndDetermineSize( QgsRenderContext *context )
752 {
753  return paintAndDetermineSizeInternal( context, nullptr );
754 }
static void setNodeLegendStyle(QgsLayerTreeNode *node, QgsLegendStyle::Style style)
Sets the style of a node.
Layer tree group node serves as a container for layers and further groups.
void drawText(QPainter *p, double x, double y, const QString &text, const QFont &font) const
Draws Text.
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
Definition: qgslayertree.h:75
QgsRenderContext * context
Render context, if available.
static bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
Definition: qgslayertree.h:43
int columnCount() const
double fontAscentMillimeters(const QFont &font) const
Returns the font ascent in Millimeters (considers upscaling and downscaling with FONT_WORKAROUND_SCAL...
QStringList evaluateItemText(const QString &text, const QgsExpressionContext &context) const
Splits a string using the wrap char taking into account handling empty wrap char which means no wrapp...
static QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group.
Definition: qgslayertree.h:64
void drawLegend(QPainter *painter)
Draws the legend with given painter.
QStringList splitStringForWrapping(const QString &stringToSplt) const
Splits a string using the wrap char taking into account handling empty wrap char which means no wrapp...
QgsLayerTreeModelLegendNode * legendNodeEmbeddedInParent(QgsLayerTreeLayer *nodeLayer) const
Returns legend node that may be embedded in parent (i.e.
double margin(Side side)
QFont font() const
The font for this style.
QgsLayerTreeLayer * layerNode() const
Returns pointer to the parent layer node.
double textWidthMillimeters(const QFont &font, const QString &text) const
Returns the font width in millimeters (considers upscaling and downscaling with FONT_WORKAROUND_SCALE...
Should not happen, only if corrupted project file.
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...
QgsLegendRenderer(QgsLayerTreeModel *legendModel, const QgsLegendSettings &settings)
Constructor for QgsLegendRenderer.
The QgsLayerTreeModel class is model implementation for Qt item views framework.
QgsLegendStyle style(QgsLegendStyle::Style s) const
Returns style.
static QgsLegendStyle::Style nodeLegendStyle(QgsLayerTreeNode *node, QgsLayerTreeModel *model)
Returns the style for the given node, within the specified model.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
The QgsLegendSettings class stores the appearance and layout settings for legend drawing with QgsLege...
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...
Symbol without label.
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
Definition: qgslayertree.h:53
This class is a base class for nodes in a layer tree.
Qt::AlignmentFlag titleAlignment() const
Returns the alignment of the legend title.
double boxSpace() const
Single scope for storing variables and functions for use within a QgsExpressionContext.
void removeCustomProperty(const QString &key)
Remove a custom property from layer. Properties are stored in a map and saved in project file...
virtual ItemMetrics draw(const QgsLegendSettings &settings, ItemContext *ctx)
Entry point called from QgsLegendRenderer to do the rendering.
QgsExpressionContext & expressionContext()
Gets the expression context.
const QgsMapSettings * legendFilterMapSettings() const
Returns the current map settings used for the current legend filter (or null if none is enabled) ...
QgsMapLayer * layer() const
Returns the map layer associated with this node.
Special style, item is hidden including margins around.
Contains information about the context of a rendering operation.
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsLayerTree * rootGroup() const
Returns pointer to the root node of the layer tree. Always a non-null pointer.
QPointF point
Top-left corner of the legend item.
bool equalColumnWidth() const
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
double columnSpace() const
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
The QgsLegendRendererItem class is abstract interface for legend items returned from QgsMapLayerLegen...
QList< QgsLayerTreeModelLegendNode * > layerLegendNodes(QgsLayerTreeLayer *nodeLayer, bool skipNodeEmbeddedInParent=false)
Returns filtered list of active legend nodes attached to a particular layer node (by default it retur...
QSizeF minimumSize(QgsRenderContext *renderContext=nullptr)
Runs the layout algorithm and returns the minimum size required for the legend.
QgsExpressionContextScope * popScope()
Removes the last scope from the expression context and return it.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
double lineSpacing() const
QColor fontColor() const
double labelXOffset
offset from the left side where label should start
bool splitLayer() const
double fontDescentMillimeters(const QFont &font) const
Returns the font descent in Millimeters (considers upscaling and downscaling with FONT_WORKAROUND_SCA...
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...
Layer tree node points to a map layer.
QString title() const