LCOV - code coverage report
Current view: top level - core - qgslegendrenderer.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 562 0.0 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       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"
      20                 :            : #include "qgslayertreemodellegendnode.h"
      21                 :            : #include "qgslegendstyle.h"
      22                 :            : #include "qgsmaplayerlegend.h"
      23                 :            : #include "qgssymbol.h"
      24                 :            : #include "qgsrendercontext.h"
      25                 :            : #include "qgsvectorlayer.h"
      26                 :            : #include "qgsexpressioncontextutils.h"
      27                 :            : 
      28                 :            : #include <QJsonObject>
      29                 :            : #include <QPainter>
      30                 :            : 
      31                 :            : 
      32                 :            : 
      33                 :          0 : QgsLegendRenderer::QgsLegendRenderer( QgsLayerTreeModel *legendModel, const QgsLegendSettings &settings )
      34                 :          0 :   : mLegendModel( legendModel )
      35                 :          0 :   , mSettings( settings )
      36                 :            : {
      37                 :          0 : }
      38                 :            : 
      39                 :          0 : QSizeF QgsLegendRenderer::minimumSize( QgsRenderContext *renderContext )
      40                 :            : {
      41                 :          0 :   std::unique_ptr< QgsRenderContext > tmpContext;
      42                 :            : 
      43                 :          0 :   if ( !renderContext )
      44                 :            :   {
      45                 :            :     // QGIS 4.0 - make render context mandatory
      46                 :            :     Q_NOWARN_DEPRECATED_PUSH
      47                 :          0 :     tmpContext.reset( new QgsRenderContext( QgsRenderContext::fromQPainter( nullptr ) ) );
      48                 :          0 :     tmpContext->setRendererScale( mSettings.mapScale() );
      49                 :          0 :     tmpContext->setMapToPixel( QgsMapToPixel( 1 / ( mSettings.mmPerMapUnit() * tmpContext->scaleFactor() ) ) );
      50                 :          0 :     renderContext = tmpContext.get();
      51                 :            :     Q_NOWARN_DEPRECATED_POP
      52                 :          0 :   }
      53                 :            : 
      54                 :          0 :   QgsScopedRenderContextPainterSwap nullPainterSwap( *renderContext, nullptr );
      55                 :          0 :   return paintAndDetermineSize( *renderContext );
      56                 :          0 : }
      57                 :            : 
      58                 :          0 : void QgsLegendRenderer::drawLegend( QPainter *painter )
      59                 :            : {
      60                 :            :   Q_NOWARN_DEPRECATED_PUSH
      61                 :          0 :   QgsRenderContext context = QgsRenderContext::fromQPainter( painter );
      62                 :          0 :   QgsScopedRenderContextScaleToMm scaleToMm( context );
      63                 :            : 
      64                 :          0 :   context.setRendererScale( mSettings.mapScale() );
      65                 :          0 :   context.setMapToPixel( QgsMapToPixel( 1 / ( mSettings.mmPerMapUnit() * context.scaleFactor() ) ) );
      66                 :            :   Q_NOWARN_DEPRECATED_POP
      67                 :            : 
      68                 :          0 :   paintAndDetermineSize( context );
      69                 :          0 : }
      70                 :            : 
      71                 :          0 : QJsonObject QgsLegendRenderer::exportLegendToJson( const QgsRenderContext &context )
      72                 :            : {
      73                 :          0 :   QJsonObject json;
      74                 :            : 
      75                 :          0 :   QgsLayerTreeGroup *rootGroup = mLegendModel->rootGroup();
      76                 :          0 :   if ( !rootGroup )
      77                 :          0 :     return json;
      78                 :            : 
      79                 :          0 :   json = exportLegendToJson( context, rootGroup );
      80                 :          0 :   json[QStringLiteral( "title" )] = mSettings.title();
      81                 :          0 :   return json;
      82                 :          0 : }
      83                 :            : 
      84                 :          0 : QJsonObject QgsLegendRenderer::exportLegendToJson( const QgsRenderContext &context, QgsLayerTreeGroup *nodeGroup )
      85                 :            : {
      86                 :          0 :   QJsonObject json;
      87                 :          0 :   QJsonArray nodes;
      88                 :          0 :   const QList<QgsLayerTreeNode *> childNodes = nodeGroup->children();
      89                 :          0 :   for ( QgsLayerTreeNode *node : childNodes )
      90                 :            :   {
      91                 :          0 :     if ( QgsLayerTree::isGroup( node ) )
      92                 :            :     {
      93                 :          0 :       QgsLayerTreeGroup *nodeGroup = QgsLayerTree::toGroup( node );
      94                 :          0 :       const QModelIndex idx = mLegendModel->node2index( nodeGroup );
      95                 :          0 :       const QString text = mLegendModel->data( idx, Qt::DisplayRole ).toString();
      96                 :            : 
      97                 :          0 :       QJsonObject group = exportLegendToJson( context, nodeGroup );
      98                 :          0 :       group[ QStringLiteral( "type" ) ] = QStringLiteral( "group" );
      99                 :          0 :       group[ QStringLiteral( "title" ) ] = text;
     100                 :          0 :       nodes.append( group );
     101                 :          0 :     }
     102                 :          0 :     else if ( QgsLayerTree::isLayer( node ) )
     103                 :            :     {
     104                 :          0 :       QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
     105                 :            : 
     106                 :          0 :       QString text;
     107                 :          0 :       if ( nodeLegendStyle( nodeLayer ) != QgsLegendStyle::Hidden )
     108                 :            :       {
     109                 :          0 :         const QModelIndex idx = mLegendModel->node2index( nodeLayer );
     110                 :          0 :         text = mLegendModel->data( idx, Qt::DisplayRole ).toString();
     111                 :          0 :       }
     112                 :            : 
     113                 :          0 :       QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->layerLegendNodes( nodeLayer );
     114                 :            : 
     115                 :          0 :       if ( legendNodes.isEmpty() && mLegendModel->legendFilterMapSettings() )
     116                 :          0 :         continue;
     117                 :            : 
     118                 :          0 :       if ( legendNodes.count() == 1 )
     119                 :            :       {
     120                 :          0 :         QJsonObject group = legendNodes.at( 0 )->exportToJson( mSettings, context );
     121                 :          0 :         group[ QStringLiteral( "type" ) ] = QStringLiteral( "layer" );
     122                 :          0 :         nodes.append( group );
     123                 :          0 :       }
     124                 :          0 :       else if ( legendNodes.count() > 1 )
     125                 :            :       {
     126                 :          0 :         QJsonObject group;
     127                 :          0 :         group[ QStringLiteral( "type" ) ] = QStringLiteral( "layer" );
     128                 :          0 :         group[ QStringLiteral( "title" ) ] = text;
     129                 :            : 
     130                 :          0 :         QJsonArray symbols;
     131                 :          0 :         for ( int j = 0; j < legendNodes.count(); j++ )
     132                 :            :         {
     133                 :          0 :           QgsLayerTreeModelLegendNode *legendNode = legendNodes.at( j );
     134                 :          0 :           QJsonObject symbol = legendNode->exportToJson( mSettings, context );
     135                 :          0 :           symbols.append( symbol );
     136                 :          0 :         }
     137                 :          0 :         group[ QStringLiteral( "symbols" ) ] = symbols;
     138                 :            : 
     139                 :          0 :         nodes.append( group );
     140                 :          0 :       }
     141                 :          0 :     }
     142                 :            :   }
     143                 :            : 
     144                 :          0 :   json[QStringLiteral( "nodes" )] = nodes;
     145                 :          0 :   return json;
     146                 :          0 : }
     147                 :            : 
     148                 :          0 : QSizeF QgsLegendRenderer::paintAndDetermineSize( QgsRenderContext &context )
     149                 :            : {
     150                 :          0 :   QSizeF size( 0, 0 );
     151                 :          0 :   QgsLayerTreeGroup *rootGroup = mLegendModel->rootGroup();
     152                 :          0 :   if ( !rootGroup )
     153                 :          0 :     return size;
     154                 :            : 
     155                 :            :   // temporarily remove painter from context -- we don't need to actually draw anything yet. But we DO need
     156                 :            :   // to send the full render context so that an expression context is available during the size calculation
     157                 :          0 :   QgsScopedRenderContextPainterSwap noPainter( context, nullptr );
     158                 :            : 
     159                 :          0 :   QList<LegendComponentGroup> componentGroups = createComponentGroupList( rootGroup, context );
     160                 :            : 
     161                 :          0 :   const int columnCount = setColumns( componentGroups );
     162                 :            : 
     163                 :          0 :   QMap< int, double > maxColumnWidths;
     164                 :          0 :   qreal maxEqualColumnWidth = 0;
     165                 :            :   // another iteration -- this one is required to calculate the maximum item width for each
     166                 :            :   // column. Unfortunately, we can't trust the component group widths at this stage, as they are minimal widths
     167                 :            :   // only. When actually rendering a symbol node, the text is aligned according to the WIDEST
     168                 :            :   // symbol in a column. So that means we can't possibly determine the exact size of legend components
     169                 :            :   // until now. BUUUUUUUUUUUUT. Because everything sucks, we can't even start the actual render of items
     170                 :            :   // at the same time we calculate this -- legend items REQUIRE the REAL width of the columns in order to
     171                 :            :   // correctly align right or center-aligned symbols/text. Bah -- A triple iteration it is!
     172                 :          0 :   for ( const LegendComponentGroup &group : std::as_const( componentGroups ) )
     173                 :            :   {
     174                 :          0 :     const QSizeF actualSize = drawGroup( group, context, ColumnContext() );
     175                 :          0 :     maxEqualColumnWidth = std::max( actualSize.width(), maxEqualColumnWidth );
     176                 :          0 :     maxColumnWidths[ group.column ] = std::max( actualSize.width(), maxColumnWidths.value( group.column, 0 ) );
     177                 :            :   }
     178                 :            : 
     179                 :          0 :   if ( columnCount == 1 )
     180                 :            :   {
     181                 :            :     // single column - use the full available width
     182                 :          0 :     maxEqualColumnWidth = std::max( maxEqualColumnWidth, mLegendSize.width() - 2 * mSettings.boxSpace() );
     183                 :          0 :     maxColumnWidths[ 0 ] = maxEqualColumnWidth;
     184                 :          0 :   }
     185                 :            : 
     186                 :            :   //calculate size of title
     187                 :          0 :   QSizeF titleSize = drawTitle( context, 0 );
     188                 :            :   //add title margin to size of title text
     189                 :          0 :   titleSize.rwidth() += mSettings.boxSpace() * 2.0;
     190                 :          0 :   double columnTop = mSettings.boxSpace() + titleSize.height() + mSettings.style( QgsLegendStyle::Title ).margin( QgsLegendStyle::Bottom );
     191                 :            : 
     192                 :          0 :   noPainter.reset();
     193                 :            : 
     194                 :          0 :   bool firstInColumn = true;
     195                 :          0 :   double columnMaxHeight = 0;
     196                 :          0 :   qreal columnWidth = 0;
     197                 :          0 :   int column = -1;
     198                 :          0 :   ColumnContext columnContext;
     199                 :          0 :   columnContext.left = mSettings.boxSpace();
     200                 :          0 :   columnContext.right = std::max( mLegendSize.width() - mSettings.boxSpace(), mSettings.boxSpace() );
     201                 :          0 :   double currentY = columnTop;
     202                 :            : 
     203                 :          0 :   for ( const LegendComponentGroup &group : std::as_const( componentGroups ) )
     204                 :            :   {
     205                 :          0 :     if ( group.column > column )
     206                 :            :     {
     207                 :            :       // Switch to next column
     208                 :          0 :       columnContext.left = group.column > 0 ? columnContext.right + mSettings.columnSpace() : mSettings.boxSpace();
     209                 :          0 :       columnWidth = mSettings.equalColumnWidth() ? maxEqualColumnWidth : maxColumnWidths.value( group.column );
     210                 :          0 :       columnContext.right = columnContext.left + columnWidth;
     211                 :          0 :       currentY = columnTop;
     212                 :          0 :       column++;
     213                 :          0 :       firstInColumn = true;
     214                 :          0 :     }
     215                 :          0 :     if ( !firstInColumn )
     216                 :            :     {
     217                 :          0 :       currentY += spaceAboveGroup( group );
     218                 :          0 :     }
     219                 :            : 
     220                 :          0 :     drawGroup( group, context, columnContext, currentY );
     221                 :            : 
     222                 :          0 :     currentY += group.size.height();
     223                 :          0 :     columnMaxHeight = std::max( currentY - columnTop, columnMaxHeight );
     224                 :            : 
     225                 :          0 :     firstInColumn = false;
     226                 :            :   }
     227                 :          0 :   const double totalWidth = columnContext.right + mSettings.boxSpace();
     228                 :            : 
     229                 :          0 :   size.rheight() = columnTop + columnMaxHeight + mSettings.boxSpace();
     230                 :          0 :   size.rwidth() = totalWidth;
     231                 :          0 :   if ( !mSettings.title().isEmpty() )
     232                 :            :   {
     233                 :          0 :     size.rwidth() = std::max( titleSize.width(), size.width() );
     234                 :          0 :   }
     235                 :            : 
     236                 :            :   // override the size if it was set by the user
     237                 :          0 :   if ( mLegendSize.isValid() )
     238                 :            :   {
     239                 :          0 :     qreal w = std::max( size.width(), mLegendSize.width() );
     240                 :          0 :     qreal h = std::max( size.height(), mLegendSize.height() );
     241                 :          0 :     size = QSizeF( w, h );
     242                 :          0 :   }
     243                 :            : 
     244                 :            :   // Now we have set the correct total item width and can draw the title centered
     245                 :          0 :   if ( !mSettings.title().isEmpty() )
     246                 :            :   {
     247                 :          0 :     drawTitle( context, mSettings.boxSpace(), mSettings.titleAlignment(), size.width() );
     248                 :          0 :   }
     249                 :            : 
     250                 :            :   return size;
     251                 :          0 : }
     252                 :            : 
     253                 :          0 : void QgsLegendRenderer::widthAndOffsetForTitleText( const Qt::AlignmentFlag halignment, const double legendWidth, double &textBoxWidth, double &textBoxLeft )
     254                 :            : {
     255                 :          0 :   switch ( halignment )
     256                 :            :   {
     257                 :            :     default:
     258                 :          0 :       textBoxLeft = mSettings.boxSpace();
     259                 :          0 :       textBoxWidth = legendWidth - 2 * mSettings.boxSpace();
     260                 :          0 :       break;
     261                 :            : 
     262                 :            :     case Qt::AlignHCenter:
     263                 :            :     {
     264                 :            :       // not sure on this logic, I just moved it -- don't blame me for it being totally obscure!
     265                 :          0 :       const double centerX = legendWidth / 2;
     266                 :          0 :       textBoxWidth = ( std::min( static_cast< double >( centerX ), legendWidth - centerX ) - mSettings.boxSpace() ) * 2.0;
     267                 :          0 :       textBoxLeft = centerX - textBoxWidth / 2.;
     268                 :          0 :       break;
     269                 :            :     }
     270                 :            :   }
     271                 :          0 : }
     272                 :            : 
     273                 :          0 : QList<QgsLegendRenderer::LegendComponentGroup> QgsLegendRenderer::createComponentGroupList( QgsLayerTreeGroup *parentGroup, QgsRenderContext &context )
     274                 :            : {
     275                 :          0 :   QList<LegendComponentGroup> componentGroups;
     276                 :            : 
     277                 :          0 :   if ( !parentGroup )
     278                 :          0 :     return componentGroups;
     279                 :            : 
     280                 :          0 :   const QList<QgsLayerTreeNode *> childNodes = parentGroup->children();
     281                 :          0 :   for ( QgsLayerTreeNode *node : childNodes )
     282                 :            :   {
     283                 :          0 :     if ( QgsLayerTree::isGroup( node ) )
     284                 :            :     {
     285                 :          0 :       QgsLayerTreeGroup *nodeGroup = QgsLayerTree::toGroup( node );
     286                 :            : 
     287                 :            :       // Group subitems
     288                 :          0 :       QList<LegendComponentGroup> subgroups = createComponentGroupList( nodeGroup, context );
     289                 :          0 :       bool hasSubItems = !subgroups.empty();
     290                 :            : 
     291                 :          0 :       if ( nodeLegendStyle( nodeGroup ) != QgsLegendStyle::Hidden )
     292                 :            :       {
     293                 :          0 :         LegendComponent component;
     294                 :          0 :         component.item = node;
     295                 :          0 :         component.size = drawGroupTitle( nodeGroup, context );
     296                 :            : 
     297                 :          0 :         if ( !subgroups.isEmpty() )
     298                 :            :         {
     299                 :            :           // Add internal space between this group title and the next component
     300                 :          0 :           subgroups[0].size.rheight() += spaceAboveGroup( subgroups[0] );
     301                 :            :           // Prepend this group title to the first group
     302                 :          0 :           subgroups[0].components.prepend( component );
     303                 :          0 :           subgroups[0].size.rheight() += component.size.height();
     304                 :          0 :           subgroups[0].size.rwidth() = std::max( component.size.width(), subgroups[0].size.width() );
     305                 :          0 :           if ( nodeGroup->customProperty( QStringLiteral( "legend/column-break" ) ).toInt() )
     306                 :          0 :             subgroups[0].placeColumnBreakBeforeGroup = true;
     307                 :          0 :         }
     308                 :            :         else
     309                 :            :         {
     310                 :            :           // no subitems, create new group
     311                 :          0 :           LegendComponentGroup group;
     312                 :          0 :           group.placeColumnBreakBeforeGroup = nodeGroup->customProperty( QStringLiteral( "legend/column-break" ) ).toInt();
     313                 :          0 :           group.components.append( component );
     314                 :          0 :           group.size.rwidth() += component.size.width();
     315                 :          0 :           group.size.rheight() += component.size.height();
     316                 :          0 :           group.size.rwidth() = std::max( component.size.width(), group.size.width() );
     317                 :          0 :           subgroups.append( group );
     318                 :          0 :         }
     319                 :          0 :       }
     320                 :            : 
     321                 :          0 :       if ( hasSubItems ) //leave away groups without content
     322                 :            :       {
     323                 :          0 :         componentGroups.append( subgroups );
     324                 :          0 :       }
     325                 :            : 
     326                 :          0 :     }
     327                 :          0 :     else if ( QgsLayerTree::isLayer( node ) )
     328                 :            :     {
     329                 :          0 :       QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
     330                 :            : 
     331                 :          0 :       bool allowColumnSplit = false;
     332                 :          0 :       switch ( nodeLayer->legendSplitBehavior() )
     333                 :            :       {
     334                 :            :         case QgsLayerTreeLayer::UseDefaultLegendSetting:
     335                 :          0 :           allowColumnSplit = mSettings.splitLayer();
     336                 :          0 :           break;
     337                 :            :         case QgsLayerTreeLayer::AllowSplittingLegendNodesOverMultipleColumns:
     338                 :          0 :           allowColumnSplit = true;
     339                 :          0 :           break;
     340                 :            :         case QgsLayerTreeLayer::PreventSplittingLegendNodesOverMultipleColumns:
     341                 :          0 :           allowColumnSplit = false;
     342                 :          0 :           break;
     343                 :            :       }
     344                 :            : 
     345                 :          0 :       LegendComponentGroup group;
     346                 :          0 :       group.placeColumnBreakBeforeGroup = nodeLayer->customProperty( QStringLiteral( "legend/column-break" ) ).toInt();
     347                 :            : 
     348                 :          0 :       if ( nodeLegendStyle( nodeLayer ) != QgsLegendStyle::Hidden )
     349                 :            :       {
     350                 :          0 :         LegendComponent component;
     351                 :          0 :         component.item = node;
     352                 :          0 :         component.size = drawLayerTitle( nodeLayer, context );
     353                 :          0 :         group.components.append( component );
     354                 :          0 :         group.size.rwidth() = component.size.width();
     355                 :          0 :         group.size.rheight() = component.size.height();
     356                 :          0 :       }
     357                 :            : 
     358                 :          0 :       QList<QgsLayerTreeModelLegendNode *> legendNodes = mLegendModel->layerLegendNodes( nodeLayer );
     359                 :            : 
     360                 :            :       // workaround for the issue that "filtering by map" does not remove layer nodes that have no symbols present
     361                 :            :       // on the map. We explicitly skip such layers here. In future ideally that should be handled directly
     362                 :            :       // in the layer tree model
     363                 :          0 :       if ( legendNodes.isEmpty() && mLegendModel->legendFilterMapSettings() )
     364                 :          0 :         continue;
     365                 :            : 
     366                 :          0 :       QList<LegendComponentGroup> layerGroups;
     367                 :          0 :       layerGroups.reserve( legendNodes.count() );
     368                 :            : 
     369                 :          0 :       bool groupIsLayerGroup = true;
     370                 :            : 
     371                 :          0 :       for ( int j = 0; j < legendNodes.count(); j++ )
     372                 :            :       {
     373                 :          0 :         QgsLayerTreeModelLegendNode *legendNode = legendNodes.at( j );
     374                 :            : 
     375                 :          0 :         LegendComponent symbolComponent = drawSymbolItem( legendNode, context, ColumnContext(), 0 );
     376                 :            : 
     377                 :          0 :         const bool forceBreak = legendNode->columnBreak();
     378                 :            : 
     379                 :          0 :         if ( !allowColumnSplit || j == 0 )
     380                 :            :         {
     381                 :          0 :           if ( forceBreak )
     382                 :            :           {
     383                 :          0 :             if ( groupIsLayerGroup )
     384                 :          0 :               layerGroups.prepend( group );
     385                 :            :             else
     386                 :          0 :               layerGroups.append( group );
     387                 :            : 
     388                 :          0 :             group = LegendComponentGroup();
     389                 :          0 :             group.placeColumnBreakBeforeGroup = true;
     390                 :          0 :             groupIsLayerGroup = false;
     391                 :          0 :           }
     392                 :            : 
     393                 :            :           // append to layer group
     394                 :            :           // the width is not correct at this moment, we must align all symbol labels
     395                 :          0 :           group.size.rwidth() = std::max( symbolComponent.size.width(), group.size.width() );
     396                 :            :           // Add symbol space only if there is already title or another item above
     397                 :          0 :           if ( !group.components.isEmpty() )
     398                 :            :           {
     399                 :            :             // TODO: for now we keep Symbol and SymbolLabel Top margin in sync
     400                 :          0 :             group.size.rheight() += mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Top );
     401                 :          0 :           }
     402                 :          0 :           group.size.rheight() += symbolComponent.size.height();
     403                 :          0 :           group.components.append( symbolComponent );
     404                 :          0 :         }
     405                 :            :         else
     406                 :            :         {
     407                 :          0 :           if ( group.size.height() > 0 )
     408                 :            :           {
     409                 :          0 :             if ( groupIsLayerGroup )
     410                 :          0 :               layerGroups.prepend( group );
     411                 :            :             else
     412                 :          0 :               layerGroups.append( group );
     413                 :          0 :             group = LegendComponentGroup();
     414                 :          0 :             groupIsLayerGroup = false;
     415                 :          0 :           }
     416                 :          0 :           LegendComponentGroup symbolGroup;
     417                 :          0 :           symbolGroup.placeColumnBreakBeforeGroup = forceBreak;
     418                 :          0 :           symbolGroup.components.append( symbolComponent );
     419                 :          0 :           symbolGroup.size.rwidth() = symbolComponent.size.width();
     420                 :          0 :           symbolGroup.size.rheight() = symbolComponent.size.height();
     421                 :          0 :           layerGroups.append( symbolGroup );
     422                 :          0 :         }
     423                 :          0 :       }
     424                 :          0 :       if ( group.size.height() > 0 )
     425                 :            :       {
     426                 :          0 :         if ( groupIsLayerGroup )
     427                 :          0 :           layerGroups.prepend( group );
     428                 :            :         else
     429                 :          0 :           layerGroups.append( group );
     430                 :          0 :       }
     431                 :          0 :       componentGroups.append( layerGroups );
     432                 :          0 :     }
     433                 :            :   }
     434                 :            : 
     435                 :          0 :   return componentGroups;
     436                 :          0 : }
     437                 :            : 
     438                 :            : 
     439                 :          0 : int QgsLegendRenderer::setColumns( QList<LegendComponentGroup> &componentGroups )
     440                 :            : {
     441                 :            :   // Divide groups to columns
     442                 :          0 :   double totalHeight = 0;
     443                 :          0 :   qreal maxGroupHeight = 0;
     444                 :          0 :   int forcedColumnBreaks = 0;
     445                 :          0 :   double totalSpaceAboveGroups = 0;
     446                 :          0 :   for ( const LegendComponentGroup &group : std::as_const( componentGroups ) )
     447                 :            :   {
     448                 :          0 :     totalHeight += spaceAboveGroup( group );
     449                 :          0 :     totalSpaceAboveGroups += spaceAboveGroup( group );
     450                 :          0 :     totalHeight += group.size.height();
     451                 :          0 :     maxGroupHeight = std::max( group.size.height(), maxGroupHeight );
     452                 :            : 
     453                 :          0 :     if ( group.placeColumnBreakBeforeGroup )
     454                 :          0 :       forcedColumnBreaks++;
     455                 :            :   }
     456                 :          0 :   double averageGroupHeight = ( totalHeight - totalSpaceAboveGroups ) / componentGroups.size();
     457                 :            : 
     458                 :          0 :   if ( mSettings.columnCount() == 0 && forcedColumnBreaks == 0 )
     459                 :          0 :     return 0;
     460                 :            : 
     461                 :            :   // the target number of columns allowed is dictated by the number of forced column
     462                 :            :   // breaks OR the manually set column count (whichever is greater!)
     463                 :          0 :   const int targetNumberColumns = std::max( forcedColumnBreaks + 1, mSettings.columnCount() );
     464                 :          0 :   const int numberAutoPlacedBreaks = targetNumberColumns - forcedColumnBreaks - 1;
     465                 :            : 
     466                 :            :   // We know height of each group and we have to split them into columns
     467                 :            :   // minimizing max column height. It is sort of bin packing problem, NP-hard.
     468                 :            :   // We are using simple heuristic, brute fore appeared to be to slow,
     469                 :            :   // the number of combinations is N = n!/(k!*(n-k)!) where n = groupCount-1
     470                 :            :   // and k = columnsCount-1
     471                 :          0 :   double maxColumnHeight = 0;
     472                 :          0 :   int currentColumn = 0;
     473                 :          0 :   int currentColumnGroupCount = 0; // number of groups in current column
     474                 :          0 :   double currentColumnHeight = 0;
     475                 :          0 :   double closedColumnsHeight = 0;
     476                 :          0 :   int autoPlacedBreaks = 0;
     477                 :            : 
     478                 :            :   // Calculate the expected average space between items
     479                 :          0 :   double averageSpaceAboveGroups = 0;
     480                 :          0 :   if ( componentGroups.size() > targetNumberColumns )
     481                 :          0 :     averageSpaceAboveGroups = totalSpaceAboveGroups / ( componentGroups.size() );
     482                 :            :   // Correct the totalHeight using the number of columns because the first item
     483                 :            :   // in each column does not get any space above it
     484                 :          0 :   totalHeight -= targetNumberColumns * averageSpaceAboveGroups;
     485                 :            : 
     486                 :          0 :   for ( int i = 0; i < componentGroups.size(); i++ )
     487                 :            :   {
     488                 :          0 :     LegendComponentGroup group = componentGroups.at( i );
     489                 :          0 :     double currentHeight = currentColumnHeight;
     490                 :          0 :     if ( currentColumnGroupCount > 0 )
     491                 :          0 :       currentHeight += spaceAboveGroup( group );
     492                 :          0 :     currentHeight += group.size.height();
     493                 :            : 
     494                 :          0 :     int numberRemainingGroups = componentGroups.size() - i;
     495                 :            : 
     496                 :            :     // Recalc average height for remaining columns including current
     497                 :          0 :     int numberRemainingColumns = numberAutoPlacedBreaks + 1 - autoPlacedBreaks;
     498                 :          0 :     double avgColumnHeight = ( currentHeight + numberRemainingGroups * averageGroupHeight + ( numberRemainingGroups - numberRemainingColumns - 1 ) *  averageSpaceAboveGroups ) / numberRemainingColumns;
     499                 :            :     // Round up to the next full number of groups to put in one column
     500                 :            :     // This ensures that earlier columns contain more elements than later columns
     501                 :          0 :     int averageGroupsPerColumn = std::ceil( avgColumnHeight / ( averageGroupHeight + averageSpaceAboveGroups ) );
     502                 :          0 :     avgColumnHeight = averageGroupsPerColumn * ( averageGroupHeight + averageSpaceAboveGroups ) - averageSpaceAboveGroups;
     503                 :            : 
     504                 :          0 :     bool canCreateNewColumn = ( currentColumnGroupCount > 0 )  // do not leave empty column
     505                 :          0 :                               && ( currentColumn < targetNumberColumns - 1 ) // must not exceed max number of columns
     506                 :          0 :                               && ( autoPlacedBreaks < numberAutoPlacedBreaks );
     507                 :            : 
     508                 :          0 :     bool shouldCreateNewColumn = currentHeight  > avgColumnHeight  // current group height is greater than expected group height
     509                 :          0 :                                  && currentColumnGroupCount > 0 // do not leave empty column
     510                 :          0 :                                  && currentHeight > maxGroupHeight  // no sense to make smaller columns than max group height
     511                 :          0 :                                  && currentHeight > maxColumnHeight; // no sense to make smaller columns than max column already created
     512                 :            : 
     513                 :          0 :     shouldCreateNewColumn |= group.placeColumnBreakBeforeGroup;
     514                 :          0 :     canCreateNewColumn |= group.placeColumnBreakBeforeGroup;
     515                 :            : 
     516                 :            :     // also should create a new column if the number of items left < number of columns left
     517                 :            :     // in this case we should spread the remaining items out over the remaining columns
     518                 :          0 :     shouldCreateNewColumn |= ( componentGroups.size() - i < targetNumberColumns - currentColumn );
     519                 :            : 
     520                 :          0 :     if ( canCreateNewColumn && shouldCreateNewColumn )
     521                 :            :     {
     522                 :            :       // New column
     523                 :          0 :       currentColumn++;
     524                 :          0 :       if ( !group.placeColumnBreakBeforeGroup )
     525                 :          0 :         autoPlacedBreaks++;
     526                 :          0 :       currentColumnGroupCount = 0;
     527                 :          0 :       closedColumnsHeight += currentColumnHeight;
     528                 :          0 :       currentColumnHeight = group.size.height();
     529                 :          0 :     }
     530                 :            :     else
     531                 :            :     {
     532                 :          0 :       currentColumnHeight = currentHeight;
     533                 :            :     }
     534                 :          0 :     componentGroups[i].column = currentColumn;
     535                 :          0 :     currentColumnGroupCount++;
     536                 :          0 :     maxColumnHeight = std::max( currentColumnHeight, maxColumnHeight );
     537                 :          0 :   }
     538                 :            : 
     539                 :            :   // Align labels of symbols for each layer/column to the same labelXOffset
     540                 :          0 :   QMap<QString, qreal> maxSymbolWidth;
     541                 :          0 :   for ( int i = 0; i < componentGroups.size(); i++ )
     542                 :            :   {
     543                 :          0 :     LegendComponentGroup &group = componentGroups[i];
     544                 :          0 :     for ( int j = 0; j < group.components.size(); j++ )
     545                 :            :     {
     546                 :          0 :       if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( group.components.at( j ).item ) )
     547                 :            :       {
     548                 :          0 :         QString key = QStringLiteral( "%1-%2" ).arg( reinterpret_cast< qulonglong >( legendNode->layerNode() ) ).arg( group.column );
     549                 :          0 :         maxSymbolWidth[key] = std::max( group.components.at( j ).symbolSize.width(), maxSymbolWidth[key] );
     550                 :          0 :       }
     551                 :          0 :     }
     552                 :          0 :   }
     553                 :          0 :   for ( int i = 0; i < componentGroups.size(); i++ )
     554                 :            :   {
     555                 :          0 :     LegendComponentGroup &group = componentGroups[i];
     556                 :          0 :     for ( int j = 0; j < group.components.size(); j++ )
     557                 :            :     {
     558                 :          0 :       if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( group.components.at( j ).item ) )
     559                 :            :       {
     560                 :          0 :         QString key = QStringLiteral( "%1-%2" ).arg( reinterpret_cast< qulonglong >( legendNode->layerNode() ) ).arg( group.column );
     561                 :          0 :         double space = mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Right ) +
     562                 :          0 :                        mSettings.style( QgsLegendStyle::SymbolLabel ).margin( QgsLegendStyle::Left );
     563                 :          0 :         group.components[j].labelXOffset = maxSymbolWidth[key] + space;
     564                 :          0 :         group.components[j].maxSiblingSymbolWidth = maxSymbolWidth[key];
     565                 :          0 :         group.components[j].size.rwidth() = maxSymbolWidth[key] + space + group.components.at( j ).labelSize.width();
     566                 :          0 :       }
     567                 :          0 :     }
     568                 :          0 :   }
     569                 :          0 :   return targetNumberColumns;
     570                 :          0 : }
     571                 :            : 
     572                 :          0 : QSizeF QgsLegendRenderer::drawTitle( QgsRenderContext &context, double top, Qt::AlignmentFlag halignment, double legendWidth )
     573                 :            : {
     574                 :          0 :   QSizeF size( 0, 0 );
     575                 :          0 :   if ( mSettings.title().isEmpty() )
     576                 :            :   {
     577                 :          0 :     return size;
     578                 :            :   }
     579                 :            : 
     580                 :          0 :   QStringList lines = mSettings.splitStringForWrapping( mSettings.title() );
     581                 :          0 :   double y = top;
     582                 :            : 
     583                 :          0 :   if ( auto *lPainter = context.painter() )
     584                 :            :   {
     585                 :          0 :     lPainter->setPen( mSettings.fontColor() );
     586                 :          0 :   }
     587                 :            : 
     588                 :            :   //calculate width and left pos of rectangle to draw text into
     589                 :            :   double textBoxWidth;
     590                 :            :   double textBoxLeft;
     591                 :          0 :   widthAndOffsetForTitleText( halignment, legendWidth, textBoxWidth, textBoxLeft );
     592                 :            : 
     593                 :          0 :   QFont titleFont = mSettings.style( QgsLegendStyle::Title ).font();
     594                 :            : 
     595                 :          0 :   for ( QStringList::Iterator titlePart = lines.begin(); titlePart != lines.end(); ++titlePart )
     596                 :            :   {
     597                 :            :     //last word is not drawn if rectangle width is exactly text width, so add 1
     598                 :            :     //TODO - correctly calculate size of italicized text, since QFontMetrics does not
     599                 :          0 :     qreal width = mSettings.textWidthMillimeters( titleFont, *titlePart ) + 1;
     600                 :          0 :     qreal height = mSettings.fontAscentMillimeters( titleFont ) + mSettings.fontDescentMillimeters( titleFont );
     601                 :            : 
     602                 :          0 :     QRectF r( textBoxLeft, y, textBoxWidth, height );
     603                 :            : 
     604                 :          0 :     if ( context.painter() )
     605                 :            :     {
     606                 :          0 :       mSettings.drawText( context.painter(), r, *titlePart, titleFont, halignment, Qt::AlignVCenter, Qt::TextDontClip );
     607                 :          0 :     }
     608                 :            : 
     609                 :            :     //update max width of title
     610                 :          0 :     size.rwidth() = std::max( width, size.rwidth() );
     611                 :            : 
     612                 :          0 :     y += height;
     613                 :          0 :     if ( titlePart != ( lines.end() - 1 ) )
     614                 :            :     {
     615                 :          0 :       y += mSettings.lineSpacing();
     616                 :          0 :     }
     617                 :          0 :   }
     618                 :          0 :   size.rheight() = y - top;
     619                 :            : 
     620                 :            :   return size;
     621                 :          0 : }
     622                 :            : 
     623                 :            : 
     624                 :          0 : double QgsLegendRenderer::spaceAboveGroup( const LegendComponentGroup &group )
     625                 :            : {
     626                 :          0 :   if ( group.components.isEmpty() ) return 0;
     627                 :            : 
     628                 :          0 :   LegendComponent component = group.components.first();
     629                 :            : 
     630                 :          0 :   if ( QgsLayerTreeGroup *nodeGroup = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
     631                 :            :   {
     632                 :          0 :     return mSettings.style( nodeLegendStyle( nodeGroup ) ).margin( QgsLegendStyle::Top );
     633                 :            :   }
     634                 :          0 :   else if ( QgsLayerTreeLayer *nodeLayer = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
     635                 :            :   {
     636                 :          0 :     return mSettings.style( nodeLegendStyle( nodeLayer ) ).margin( QgsLegendStyle::Top );
     637                 :            :   }
     638                 :          0 :   else if ( qobject_cast<QgsLayerTreeModelLegendNode *>( component.item ) )
     639                 :            :   {
     640                 :            :     // TODO: use Symbol or SymbolLabel Top margin
     641                 :          0 :     return mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Top );
     642                 :            :   }
     643                 :            : 
     644                 :          0 :   return 0;
     645                 :          0 : }
     646                 :            : 
     647                 :          0 : QSizeF QgsLegendRenderer::drawGroup( const LegendComponentGroup &group, QgsRenderContext &context, ColumnContext columnContext, double top )
     648                 :            : {
     649                 :          0 :   bool first = true;
     650                 :          0 :   QSizeF size = QSizeF( group.size );
     651                 :          0 :   double currentY = top;
     652                 :          0 :   for ( const LegendComponent &component : std::as_const( group.components ) )
     653                 :            :   {
     654                 :          0 :     if ( QgsLayerTreeGroup *groupItem = qobject_cast<QgsLayerTreeGroup *>( component.item ) )
     655                 :            :     {
     656                 :          0 :       QgsLegendStyle::Style s = nodeLegendStyle( groupItem );
     657                 :          0 :       if ( s != QgsLegendStyle::Hidden )
     658                 :            :       {
     659                 :          0 :         if ( !first )
     660                 :            :         {
     661                 :          0 :           currentY += mSettings.style( s ).margin( QgsLegendStyle::Top );
     662                 :          0 :         }
     663                 :          0 :         QSizeF groupSize;
     664                 :          0 :         groupSize = drawGroupTitle( groupItem, context, columnContext, currentY );
     665                 :          0 :         size.rwidth() = std::max( groupSize.width(), size.width() );
     666                 :          0 :       }
     667                 :          0 :     }
     668                 :          0 :     else if ( QgsLayerTreeLayer *layerItem = qobject_cast<QgsLayerTreeLayer *>( component.item ) )
     669                 :            :     {
     670                 :          0 :       QgsLegendStyle::Style s = nodeLegendStyle( layerItem );
     671                 :          0 :       if ( s != QgsLegendStyle::Hidden )
     672                 :            :       {
     673                 :          0 :         if ( !first )
     674                 :            :         {
     675                 :          0 :           currentY += mSettings.style( s ).margin( QgsLegendStyle::Top );
     676                 :          0 :         }
     677                 :          0 :         QSizeF subGroupSize;
     678                 :          0 :         subGroupSize = drawLayerTitle( layerItem, context, columnContext, currentY );
     679                 :          0 :         size.rwidth() = std::max( subGroupSize.width(), size.width() );
     680                 :          0 :       }
     681                 :          0 :     }
     682                 :          0 :     else if ( QgsLayerTreeModelLegendNode *legendNode = qobject_cast<QgsLayerTreeModelLegendNode *>( component.item ) )
     683                 :            :     {
     684                 :          0 :       if ( !first )
     685                 :            :       {
     686                 :          0 :         currentY += mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Top );
     687                 :          0 :       }
     688                 :            : 
     689                 :          0 :       LegendComponent symbolComponent = drawSymbolItem( legendNode, context, columnContext, currentY, component.maxSiblingSymbolWidth );
     690                 :            :       // expand width, it may be wider because of label offsets
     691                 :          0 :       size.rwidth() = std::max( symbolComponent.size.width(), size.width() );
     692                 :          0 :     }
     693                 :          0 :     currentY += component.size.height();
     694                 :          0 :     first = false;
     695                 :            :   }
     696                 :          0 :   return size;
     697                 :          0 : }
     698                 :            : 
     699                 :          0 : QgsLegendRenderer::LegendComponent QgsLegendRenderer::drawSymbolItem( QgsLayerTreeModelLegendNode *symbolItem, QgsRenderContext &context, ColumnContext columnContext, double top, double maxSiblingSymbolWidth )
     700                 :            : {
     701                 :          0 :   QgsLayerTreeModelLegendNode::ItemContext ctx;
     702                 :          0 :   ctx.context = &context;
     703                 :            : 
     704                 :            :   // add a layer expression context scope
     705                 :          0 :   QgsExpressionContextScope *layerScope = nullptr;
     706                 :          0 :   if ( symbolItem->layerNode()->layer() )
     707                 :            :   {
     708                 :          0 :     layerScope = QgsExpressionContextUtils::layerScope( symbolItem->layerNode()->layer() );
     709                 :          0 :     context.expressionContext().appendScope( layerScope );
     710                 :          0 :   }
     711                 :            : 
     712                 :          0 :   ctx.painter = context.painter();
     713                 :            :   Q_NOWARN_DEPRECATED_PUSH
     714                 :          0 :   ctx.point = QPointF( columnContext.left, top );
     715                 :          0 :   ctx.labelXOffset = maxSiblingSymbolWidth;
     716                 :            :   Q_NOWARN_DEPRECATED_POP
     717                 :            : 
     718                 :          0 :   ctx.top = top;
     719                 :            : 
     720                 :          0 :   ctx.columnLeft = columnContext.left;
     721                 :          0 :   ctx.columnRight = columnContext.right;
     722                 :            : 
     723                 :          0 :   switch ( mSettings.symbolAlignment() )
     724                 :          0 :   {
     725                 :            :     case Qt::AlignLeft:
     726                 :            :     default:
     727                 :          0 :       ctx.columnLeft += mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Left );
     728                 :          0 :       break;
     729                 :            : 
     730                 :            :     case Qt::AlignRight:
     731                 :          0 :       ctx.columnRight -= mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Left );
     732                 :          0 :       break;
     733                 :            :   }
     734                 :            : 
     735                 :          0 :   ctx.maxSiblingSymbolWidth = maxSiblingSymbolWidth;
     736                 :            : 
     737                 :          0 :   if ( const QgsSymbolLegendNode *symbolNode = dynamic_cast< const QgsSymbolLegendNode * >( symbolItem ) )
     738                 :          0 :     ctx.patchShape = symbolNode->patchShape();
     739                 :            : 
     740                 :          0 :   ctx.patchSize = symbolItem->userPatchSize();
     741                 :            : 
     742                 :          0 :   QgsLayerTreeModelLegendNode::ItemMetrics im = symbolItem->draw( mSettings, &ctx );
     743                 :            : 
     744                 :          0 :   if ( layerScope )
     745                 :          0 :     delete context.expressionContext().popScope();
     746                 :            : 
     747                 :          0 :   LegendComponent component;
     748                 :          0 :   component.item = symbolItem;
     749                 :          0 :   component.symbolSize = im.symbolSize;
     750                 :          0 :   component.labelSize = im.labelSize;
     751                 :            :   //QgsDebugMsg( QStringLiteral( "symbol height = %1 label height = %2").arg( symbolSize.height()).arg( labelSize.height() ));
     752                 :            :   // NOTE -- we hard code left/right margins below, because those are the only ones exposed for use currently.
     753                 :            :   // ideally we could (should?) expose all these margins as settings, and then adapt the below to respect the current symbol/text alignment
     754                 :            :   // and consider the correct margin sides...
     755                 :          0 :   double width = std::max( static_cast< double >( im.symbolSize.width() ), maxSiblingSymbolWidth )
     756                 :          0 :                  + mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Left )
     757                 :          0 :                  + mSettings.style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Right )
     758                 :          0 :                  + mSettings.style( QgsLegendStyle::SymbolLabel ).margin( QgsLegendStyle::Left )
     759                 :          0 :                  + im.labelSize.width();
     760                 :            : 
     761                 :          0 :   double height = std::max( im.symbolSize.height(), im.labelSize.height() );
     762                 :          0 :   component.size = QSizeF( width, height );
     763                 :            :   return component;
     764                 :          0 : }
     765                 :            : 
     766                 :          0 : QSizeF QgsLegendRenderer::drawLayerTitle( QgsLayerTreeLayer *nodeLayer, QgsRenderContext &context, ColumnContext columnContext, double top )
     767                 :            : {
     768                 :          0 :   QSizeF size( 0, 0 );
     769                 :          0 :   QModelIndex idx = mLegendModel->node2index( nodeLayer );
     770                 :          0 :   QString titleString = mLegendModel->data( idx, Qt::DisplayRole ).toString();
     771                 :            :   //Let the user omit the layer title item by having an empty layer title string
     772                 :          0 :   if ( titleString.isEmpty() )
     773                 :          0 :     return size;
     774                 :            : 
     775                 :          0 :   double y = top;
     776                 :            : 
     777                 :          0 :   if ( auto *lPainter = context.painter() )
     778                 :          0 :     lPainter->setPen( mSettings.layerFontColor() );
     779                 :            : 
     780                 :          0 :   QFont layerFont = mSettings.style( nodeLegendStyle( nodeLayer ) ).font();
     781                 :            : 
     782                 :          0 :   QgsExpressionContextScope *layerScope = nullptr;
     783                 :          0 :   if ( nodeLayer->layer() )
     784                 :            :   {
     785                 :          0 :     layerScope = QgsExpressionContextUtils::layerScope( nodeLayer->layer() );
     786                 :          0 :     context.expressionContext().appendScope( layerScope );
     787                 :          0 :   }
     788                 :            : 
     789                 :          0 :   const QStringList lines = mSettings.evaluateItemText( titleString, context.expressionContext() );
     790                 :          0 :   int i = 0;
     791                 :            : 
     792                 :          0 :   const double sideMargin = mSettings.style( nodeLegendStyle( nodeLayer ) ).margin( QgsLegendStyle::Left );
     793                 :          0 :   for ( QStringList::ConstIterator layerItemPart = lines.constBegin(); layerItemPart != lines.constEnd(); ++layerItemPart )
     794                 :            :   {
     795                 :          0 :     y += mSettings.fontAscentMillimeters( layerFont );
     796                 :          0 :     if ( QPainter *destPainter = context.painter() )
     797                 :            :     {
     798                 :          0 :       double x = columnContext.left + sideMargin;
     799                 :          0 :       if ( mSettings.style( nodeLegendStyle( nodeLayer ) ).alignment() != Qt::AlignLeft )
     800                 :            :       {
     801                 :          0 :         const double labelWidth = mSettings.textWidthMillimeters( layerFont, *layerItemPart );
     802                 :          0 :         if ( mSettings.style( nodeLegendStyle( nodeLayer ) ).alignment() == Qt::AlignRight )
     803                 :          0 :           x = columnContext.right - labelWidth - sideMargin;
     804                 :          0 :         else if ( mSettings.style( nodeLegendStyle( nodeLayer ) ).alignment() == Qt::AlignHCenter )
     805                 :          0 :           x = columnContext.left + ( columnContext.right - columnContext.left - labelWidth ) / 2;
     806                 :          0 :       }
     807                 :          0 :       mSettings.drawText( destPainter, x, y, *layerItemPart, layerFont );
     808                 :          0 :     }
     809                 :          0 :     qreal width = mSettings.textWidthMillimeters( layerFont, *layerItemPart ) + sideMargin *
     810                 :          0 :                   ( mSettings.style( nodeLegendStyle( nodeLayer ) ).alignment() == Qt::AlignHCenter  ? 2 : 1 );
     811                 :          0 :     size.rwidth() = std::max( width, size.width() );
     812                 :          0 :     if ( layerItemPart != ( lines.end() - 1 ) )
     813                 :            :     {
     814                 :          0 :       y += mSettings.lineSpacing();
     815                 :          0 :     }
     816                 :          0 :     i++;
     817                 :          0 :   }
     818                 :          0 :   size.rheight() = y - top;
     819                 :          0 :   size.rheight() += mSettings.style( nodeLegendStyle( nodeLayer ) ).margin( QgsLegendStyle::Side::Bottom );
     820                 :            : 
     821                 :          0 :   if ( layerScope )
     822                 :          0 :     delete context.expressionContext().popScope();
     823                 :            : 
     824                 :            :   return size;
     825                 :          0 : }
     826                 :            : 
     827                 :          0 : QSizeF QgsLegendRenderer::drawGroupTitle( QgsLayerTreeGroup *nodeGroup, QgsRenderContext &context, ColumnContext columnContext, double top )
     828                 :            : {
     829                 :          0 :   QSizeF size( 0, 0 );
     830                 :          0 :   QModelIndex idx = mLegendModel->node2index( nodeGroup );
     831                 :            : 
     832                 :          0 :   double y = top;
     833                 :            : 
     834                 :          0 :   if ( auto *lPainter = context.painter() )
     835                 :          0 :     lPainter->setPen( mSettings.fontColor() );
     836                 :            : 
     837                 :          0 :   QFont groupFont = mSettings.style( nodeLegendStyle( nodeGroup ) ).font();
     838                 :            : 
     839                 :          0 :   const double sideMargin = mSettings.style( nodeLegendStyle( nodeGroup ) ).margin( QgsLegendStyle::Left );
     840                 :            : 
     841                 :          0 :   const QStringList lines = mSettings.evaluateItemText( mLegendModel->data( idx, Qt::DisplayRole ).toString(), context.expressionContext() );
     842                 :          0 :   for ( QStringList::ConstIterator groupPart = lines.constBegin(); groupPart != lines.constEnd(); ++groupPart )
     843                 :            :   {
     844                 :          0 :     y += mSettings.fontAscentMillimeters( groupFont );
     845                 :            : 
     846                 :          0 :     if ( QPainter *destPainter = context.painter() )
     847                 :            :     {
     848                 :          0 :       double x = columnContext.left + sideMargin;
     849                 :          0 :       if ( mSettings.style( nodeLegendStyle( nodeGroup ) ).alignment() != Qt::AlignLeft )
     850                 :            :       {
     851                 :          0 :         const double labelWidth = mSettings.textWidthMillimeters( groupFont, *groupPart );
     852                 :          0 :         if ( mSettings.style( nodeLegendStyle( nodeGroup ) ).alignment() == Qt::AlignRight )
     853                 :          0 :           x = columnContext.right - labelWidth - sideMargin;
     854                 :          0 :         else if ( mSettings.style( nodeLegendStyle( nodeGroup ) ).alignment() == Qt::AlignHCenter )
     855                 :          0 :           x = columnContext.left + ( columnContext.right - columnContext.left - labelWidth ) / 2;
     856                 :          0 :       }
     857                 :          0 :       mSettings.drawText( destPainter, x, y, *groupPart, groupFont );
     858                 :          0 :     }
     859                 :          0 :     qreal width = mSettings.textWidthMillimeters( groupFont, *groupPart ) + sideMargin * ( mSettings.style( nodeLegendStyle( nodeGroup ) ).alignment() == Qt::AlignHCenter ? 2 : 1 );
     860                 :          0 :     size.rwidth() = std::max( width, size.width() );
     861                 :          0 :     if ( groupPart != ( lines.end() - 1 ) )
     862                 :            :     {
     863                 :          0 :       y += mSettings.lineSpacing();
     864                 :          0 :     }
     865                 :          0 :   }
     866                 :          0 :   size.rheight() = y - top + mSettings.style( nodeLegendStyle( nodeGroup ) ).margin( QgsLegendStyle::Bottom );
     867                 :            :   return size;
     868                 :          0 : }
     869                 :            : 
     870                 :          0 : QgsLegendStyle::Style QgsLegendRenderer::nodeLegendStyle( QgsLayerTreeNode *node, QgsLayerTreeModel *model )
     871                 :            : {
     872                 :          0 :   QString style = node->customProperty( QStringLiteral( "legend/title-style" ) ).toString();
     873                 :          0 :   if ( style == QLatin1String( "hidden" ) )
     874                 :          0 :     return QgsLegendStyle::Hidden;
     875                 :          0 :   else if ( style == QLatin1String( "group" ) )
     876                 :          0 :     return QgsLegendStyle::Group;
     877                 :          0 :   else if ( style == QLatin1String( "subgroup" ) )
     878                 :          0 :     return QgsLegendStyle::Subgroup;
     879                 :            : 
     880                 :            :   // use a default otherwise
     881                 :          0 :   if ( QgsLayerTree::isGroup( node ) )
     882                 :          0 :     return QgsLegendStyle::Group;
     883                 :          0 :   else if ( QgsLayerTree::isLayer( node ) )
     884                 :            :   {
     885                 :          0 :     if ( model->legendNodeEmbeddedInParent( QgsLayerTree::toLayer( node ) ) )
     886                 :          0 :       return QgsLegendStyle::Hidden;
     887                 :          0 :     return QgsLegendStyle::Subgroup;
     888                 :            :   }
     889                 :            : 
     890                 :          0 :   return QgsLegendStyle::Undefined; // should not happen, only if corrupted project file
     891                 :          0 : }
     892                 :            : 
     893                 :          0 : QgsLegendStyle::Style QgsLegendRenderer::nodeLegendStyle( QgsLayerTreeNode *node )
     894                 :            : {
     895                 :          0 :   return nodeLegendStyle( node, mLegendModel );
     896                 :            : }
     897                 :            : 
     898                 :          0 : void QgsLegendRenderer::setNodeLegendStyle( QgsLayerTreeNode *node, QgsLegendStyle::Style style )
     899                 :            : {
     900                 :          0 :   QString str;
     901                 :          0 :   switch ( style )
     902                 :            :   {
     903                 :            :     case QgsLegendStyle::Hidden:
     904                 :          0 :       str = QStringLiteral( "hidden" );
     905                 :          0 :       break;
     906                 :            :     case QgsLegendStyle::Group:
     907                 :          0 :       str = QStringLiteral( "group" );
     908                 :          0 :       break;
     909                 :            :     case QgsLegendStyle::Subgroup:
     910                 :          0 :       str = QStringLiteral( "subgroup" );
     911                 :          0 :       break;
     912                 :            :     default:
     913                 :          0 :       break; // nothing
     914                 :            :   }
     915                 :            : 
     916                 :          0 :   if ( !str.isEmpty() )
     917                 :          0 :     node->setCustomProperty( QStringLiteral( "legend/title-style" ), str );
     918                 :            :   else
     919                 :          0 :     node->removeCustomProperty( QStringLiteral( "legend/title-style" ) );
     920                 :          0 : }
     921                 :            : 
     922                 :          0 : void QgsLegendRenderer::drawLegend( QgsRenderContext &context )
     923                 :            : {
     924                 :          0 :   paintAndDetermineSize( context );
     925                 :          0 : }
     926                 :            : 

Generated by: LCOV version 1.14