QGIS API Documentation  3.21.0-Master (5b68dc587e)
qgslayouttable.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayouttable.cpp
3  ------------------
4  begin : November 2017
5  copyright : (C) 2017 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
19 #include "qgsexpressionutils.h"
20 #include "qgslayouttable.h"
21 #include "qgslayout.h"
22 #include "qgslayoututils.h"
23 #include "qgslayouttablecolumn.h"
24 #include "qgssymbollayerutils.h"
25 #include "qgslayoutframe.h"
26 #include "qgsfontutils.h"
27 #include "qgssettings.h"
29 #include "qgstextrenderer.h"
30 
31 //
32 // QgsLayoutTableStyle
33 //
34 
35 bool QgsLayoutTableStyle::writeXml( QDomElement &styleElem, QDomDocument &doc ) const
36 {
37  Q_UNUSED( doc )
38  styleElem.setAttribute( QStringLiteral( "cellBackgroundColor" ), QgsSymbolLayerUtils::encodeColor( cellBackgroundColor ) );
39  styleElem.setAttribute( QStringLiteral( "enabled" ), enabled );
40  return true;
41 }
42 
43 bool QgsLayoutTableStyle::readXml( const QDomElement &styleElem )
44 {
45  cellBackgroundColor = QgsSymbolLayerUtils::decodeColor( styleElem.attribute( QStringLiteral( "cellBackgroundColor" ), QStringLiteral( "255,255,255,255" ) ) );
46  enabled = ( styleElem.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ) != QLatin1String( "0" ) );
47  return true;
48 }
49 
50 
51 //
52 // QgsLayoutTable
53 //
54 
56  : QgsLayoutMultiFrame( layout )
57 {
58  initStyles();
59 }
60 
62 {
63  mColumns.clear();
64  mSortColumns.clear();
65 
66  qDeleteAll( mCellStyles );
67  mCellStyles.clear();
68 }
69 
70 bool QgsLayoutTable::writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const
71 {
72  elem.setAttribute( QStringLiteral( "cellMargin" ), QString::number( mCellMargin ) );
73  elem.setAttribute( QStringLiteral( "emptyTableMode" ), QString::number( static_cast< int >( mEmptyTableMode ) ) );
74  elem.setAttribute( QStringLiteral( "emptyTableMessage" ), mEmptyTableMessage );
75  elem.setAttribute( QStringLiteral( "showEmptyRows" ), mShowEmptyRows );
76 
77  QDomElement headerElem = doc.createElement( QStringLiteral( "headerTextFormat" ) );
78  const QDomElement headerTextElem = mHeaderTextFormat.writeXml( doc, context );
79  headerElem.appendChild( headerTextElem );
80  elem.appendChild( headerElem );
81  elem.setAttribute( QStringLiteral( "headerHAlignment" ), QString::number( static_cast< int >( mHeaderHAlignment ) ) );
82  elem.setAttribute( QStringLiteral( "headerMode" ), QString::number( static_cast< int >( mHeaderMode ) ) );
83 
84  QDomElement contentElem = doc.createElement( QStringLiteral( "contentTextFormat" ) );
85  const QDomElement contentTextElem = mContentTextFormat.writeXml( doc, context );
86  contentElem.appendChild( contentTextElem );
87  elem.appendChild( contentElem );
88  elem.setAttribute( QStringLiteral( "gridStrokeWidth" ), QString::number( mGridStrokeWidth ) );
89  elem.setAttribute( QStringLiteral( "gridColor" ), QgsSymbolLayerUtils::encodeColor( mGridColor ) );
90  elem.setAttribute( QStringLiteral( "horizontalGrid" ), mHorizontalGrid );
91  elem.setAttribute( QStringLiteral( "verticalGrid" ), mVerticalGrid );
92  elem.setAttribute( QStringLiteral( "showGrid" ), mShowGrid );
93  elem.setAttribute( QStringLiteral( "backgroundColor" ), QgsSymbolLayerUtils::encodeColor( mBackgroundColor ) );
94  elem.setAttribute( QStringLiteral( "wrapBehavior" ), QString::number( static_cast< int >( mWrapBehavior ) ) );
95 
96  // display columns
97  QDomElement displayColumnsElem = doc.createElement( QStringLiteral( "displayColumns" ) );
98  for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
99  {
100  QDomElement columnElem = doc.createElement( QStringLiteral( "column" ) );
101  column.writeXml( columnElem, doc );
102  displayColumnsElem.appendChild( columnElem );
103  }
104  elem.appendChild( displayColumnsElem );
105  // sort columns
106  QDomElement sortColumnsElem = doc.createElement( QStringLiteral( "sortColumns" ) );
107  for ( const QgsLayoutTableColumn &column : std::as_const( mSortColumns ) )
108  {
109  QDomElement columnElem = doc.createElement( QStringLiteral( "column" ) );
110  column.writeXml( columnElem, doc );
111  sortColumnsElem.appendChild( columnElem );
112  }
113  elem.appendChild( sortColumnsElem );
114 
115 
116  //cell styles
117  QDomElement stylesElem = doc.createElement( QStringLiteral( "cellStyles" ) );
118  QMap< CellStyleGroup, QString >::const_iterator it = mCellStyleNames.constBegin();
119  for ( ; it != mCellStyleNames.constEnd(); ++it )
120  {
121  QString styleName = it.value();
122  QDomElement styleElem = doc.createElement( styleName );
123  QgsLayoutTableStyle *style = mCellStyles.value( it.key() );
124  if ( style )
125  {
126  style->writeXml( styleElem, doc );
127  stylesElem.appendChild( styleElem );
128  }
129  }
130  elem.appendChild( stylesElem );
131  return true;
132 }
133 
134 bool QgsLayoutTable::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
135 {
136  mEmptyTableMode = QgsLayoutTable::EmptyTableMode( itemElem.attribute( QStringLiteral( "emptyTableMode" ), QStringLiteral( "0" ) ).toInt() );
137  mEmptyTableMessage = itemElem.attribute( QStringLiteral( "emptyTableMessage" ), tr( "No matching records" ) );
138  mShowEmptyRows = itemElem.attribute( QStringLiteral( "showEmptyRows" ), QStringLiteral( "0" ) ).toInt();
139 
140  const QDomElement headerTextFormat = itemElem.firstChildElement( QStringLiteral( "headerTextFormat" ) );
141  if ( !headerTextFormat.isNull() )
142  {
143  QDomNodeList textFormatNodeList = headerTextFormat.elementsByTagName( QStringLiteral( "text-style" ) );
144  QDomElement textFormatElem = textFormatNodeList.at( 0 ).toElement();
145  mHeaderTextFormat.readXml( textFormatElem, context );
146  }
147  else
148  {
149  QFont headerFont;
150  if ( !QgsFontUtils::setFromXmlChildNode( headerFont, itemElem, QStringLiteral( "headerFontProperties" ) ) )
151  {
152  headerFont.fromString( itemElem.attribute( QStringLiteral( "headerFont" ), QString() ) );
153  }
154  QColor headerFontColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "headerFontColor" ), QStringLiteral( "0,0,0,255" ) ) );
156  if ( headerFont.pointSizeF() > 0 )
157  {
158  mHeaderTextFormat.setSize( headerFont.pointSizeF() );
160  }
161  else if ( headerFont.pixelSize() > 0 )
162  {
163  mHeaderTextFormat.setSize( headerFont.pixelSize() );
165  }
167  }
168 
169  mHeaderHAlignment = QgsLayoutTable::HeaderHAlignment( itemElem.attribute( QStringLiteral( "headerHAlignment" ), QStringLiteral( "0" ) ).toInt() );
170  mHeaderMode = QgsLayoutTable::HeaderMode( itemElem.attribute( QStringLiteral( "headerMode" ), QStringLiteral( "0" ) ).toInt() );
171 
172  const QDomElement contentTextFormat = itemElem.firstChildElement( QStringLiteral( "contentTextFormat" ) );
173  if ( !contentTextFormat.isNull() )
174  {
175  QDomNodeList textFormatNodeList = contentTextFormat.elementsByTagName( QStringLiteral( "text-style" ) );
176  QDomElement textFormatElem = textFormatNodeList.at( 0 ).toElement();
177  mContentTextFormat.readXml( textFormatElem, context );
178  }
179  else
180  {
181  QFont contentFont;
182  if ( !QgsFontUtils::setFromXmlChildNode( contentFont, itemElem, QStringLiteral( "contentFontProperties" ) ) )
183  {
184  contentFont.fromString( itemElem.attribute( QStringLiteral( "contentFont" ), QString() ) );
185  }
186  QColor contentFontColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "contentFontColor" ), QStringLiteral( "0,0,0,255" ) ) );
188  if ( contentFont.pointSizeF() > 0 )
189  {
190  mContentTextFormat.setSize( contentFont.pointSizeF() );
192  }
193  else if ( contentFont.pixelSize() > 0 )
194  {
195  mContentTextFormat.setSize( contentFont.pixelSize() );
197  }
199  }
200 
201  mCellMargin = itemElem.attribute( QStringLiteral( "cellMargin" ), QStringLiteral( "1.0" ) ).toDouble();
202  mGridStrokeWidth = itemElem.attribute( QStringLiteral( "gridStrokeWidth" ), QStringLiteral( "0.5" ) ).toDouble();
203  mHorizontalGrid = itemElem.attribute( QStringLiteral( "horizontalGrid" ), QStringLiteral( "1" ) ).toInt();
204  mVerticalGrid = itemElem.attribute( QStringLiteral( "verticalGrid" ), QStringLiteral( "1" ) ).toInt();
205  mShowGrid = itemElem.attribute( QStringLiteral( "showGrid" ), QStringLiteral( "1" ) ).toInt();
206  mGridColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "gridColor" ), QStringLiteral( "0,0,0,255" ) ) );
207  mBackgroundColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "backgroundColor" ), QStringLiteral( "255,255,255,0" ) ) );
208  mWrapBehavior = QgsLayoutTable::WrapBehavior( itemElem.attribute( QStringLiteral( "wrapBehavior" ), QStringLiteral( "0" ) ).toInt() );
209 
210  //restore display column specifications
211  mColumns.clear();
212  QDomNodeList columnsList = itemElem.elementsByTagName( QStringLiteral( "displayColumns" ) );
213  if ( !columnsList.isEmpty() )
214  {
215  QDomElement columnsElem = columnsList.at( 0 ).toElement();
216  QDomNodeList columnEntryList = columnsElem.elementsByTagName( QStringLiteral( "column" ) );
217  for ( int i = 0; i < columnEntryList.size(); ++i )
218  {
219  QDomElement columnElem = columnEntryList.at( i ).toElement();
220  QgsLayoutTableColumn column;
221  column.readXml( columnElem );
222  mColumns.append( column );
223  }
224  }
225  // sort columns
226  mSortColumns.clear();
227  QDomNodeList sortColumnsList = itemElem.elementsByTagName( QStringLiteral( "sortColumns" ) );
228  if ( !sortColumnsList.isEmpty() )
229  {
230  QDomElement columnsElem = sortColumnsList.at( 0 ).toElement();
231  QDomNodeList columnEntryList = columnsElem.elementsByTagName( QStringLiteral( "column" ) );
232  for ( int i = 0; i < columnEntryList.size(); ++i )
233  {
234  QDomElement columnElem = columnEntryList.at( i ).toElement();
235  QgsLayoutTableColumn column;
236  column.readXml( columnElem );
237  mSortColumns.append( column );
238  }
239  }
240  else
241  {
242  // backward compatibility for QGIS < 3.14
243  // copy the display columns if sortByRank > 0 and then, sort them by rank
245  std::copy_if( mColumns.begin(), mColumns.end(), std::back_inserter( mSortColumns ), []( const QgsLayoutTableColumn & col ) {return col.sortByRank() > 0;} );
246  std::sort( mSortColumns.begin(), mSortColumns.end(), []( const QgsLayoutTableColumn & a, const QgsLayoutTableColumn & b ) {return a.sortByRank() < b.sortByRank();} );
248  }
249 
250  //restore cell styles
251  QDomNodeList stylesList = itemElem.elementsByTagName( QStringLiteral( "cellStyles" ) );
252  if ( !stylesList.isEmpty() )
253  {
254  QDomElement stylesElem = stylesList.at( 0 ).toElement();
255 
256  QMap< CellStyleGroup, QString >::const_iterator it = mCellStyleNames.constBegin();
257  for ( ; it != mCellStyleNames.constEnd(); ++it )
258  {
259  QString styleName = it.value();
260  QDomNodeList styleList = stylesElem.elementsByTagName( styleName );
261  if ( !styleList.isEmpty() )
262  {
263  QDomElement styleElem = styleList.at( 0 ).toElement();
264  QgsLayoutTableStyle *style = mCellStyles.value( it.key() );
265  if ( style )
266  style->readXml( styleElem );
267  }
268  }
269  }
270 
271  emit changed();
272  return true;
273 }
274 
276 {
277  return mTableSize;
278 }
279 
281 {
284 }
285 
286 int QgsLayoutTable::rowsVisible( QgsRenderContext &context, double frameHeight, int firstRow, bool includeHeader, bool includeEmptyRows ) const
287 {
288  //calculate header height
289  double headerHeight = 0;
290  if ( includeHeader )
291  {
292  for ( int col = 0; col < mColumns.count(); ++ col )
293  {
294  const QFontMetricsF headerFontMetrics = QgsTextRenderer::fontMetrics( context, textFormatForHeader( col ), QgsTextRenderer::FONT_WORKAROUND_SCALE );
295  //frame has a header
296  headerHeight = std::max( headerHeight, 2 * ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin + headerFontMetrics.ascent() / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) / QgsTextRenderer::FONT_WORKAROUND_SCALE );
297  }
298  }
299  else
300  {
301  //frame has no header text, just the stroke
302  headerHeight = ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
303  }
304 
305  //remaining height available for content rows
306  double contentHeight = frameHeight - headerHeight;
307 
308  double gridHeight = ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
309 
310  int currentRow = firstRow;
311  while ( contentHeight > 0 && currentRow <= mTableContents.count() )
312  {
313  double currentRowHeight = mMaxRowHeightMap.value( currentRow + 1 ) + gridHeight + 2 * mCellMargin;
314  contentHeight -= currentRowHeight;
315  currentRow++;
316  }
317 
318  if ( includeEmptyRows && contentHeight > 0 )
319  {
320  const QFontMetricsF emptyRowContentFontMetrics = QgsTextRenderer::fontMetrics( context, mContentTextFormat, QgsTextRenderer::FONT_WORKAROUND_SCALE );
321  double rowHeight = ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin + emptyRowContentFontMetrics.ascent() / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) / QgsTextRenderer::FONT_WORKAROUND_SCALE;
322  currentRow += std::max( std::floor( contentHeight / rowHeight ), 0.0 );
323  }
324 
325  return currentRow - firstRow - 1;
326 }
327 
328 int QgsLayoutTable::rowsVisible( QgsRenderContext &context, int frameIndex, int firstRow, bool includeEmptyRows ) const
329 {
330  //get frame extent
331  if ( frameIndex >= frameCount() )
332  {
333  return 0;
334  }
335  QRectF frameExtent = frame( frameIndex )->extent();
336 
337  bool includeHeader = false;
340  {
341  includeHeader = true;
342  }
343  return rowsVisible( context, frameExtent.height(), firstRow, includeHeader, includeEmptyRows );
344 }
345 
346 QPair<int, int> QgsLayoutTable::rowRange( QgsRenderContext &context, const int frameIndex ) const
347 {
348  //calculate row height
349  if ( frameIndex >= frameCount() )
350  {
351  //bad frame index
352  return qMakePair( 0, 0 );
353  }
354 
355  //loop through all previous frames to calculate how many rows are visible in each
356  //as the entire height of a frame may not be utilized for content rows
357  int rowsAlreadyShown = 0;
358  for ( int idx = 0; idx < frameIndex; ++idx )
359  {
360  rowsAlreadyShown += rowsVisible( context, idx, rowsAlreadyShown, false );
361  }
362 
363  //using zero based indexes
364  int firstVisible = std::min( rowsAlreadyShown, static_cast<int>( mTableContents.length() ) );
365  int possibleRowsVisible = rowsVisible( context, frameIndex, rowsAlreadyShown, false );
366  int lastVisible = std::min( firstVisible + possibleRowsVisible, static_cast<int>( mTableContents.length() ) );
367 
368  return qMakePair( firstVisible, lastVisible );
369 }
370 
371 void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &, const int frameIndex )
372 {
373  bool emptyTable = mTableContents.length() == 0;
374  if ( emptyTable && mEmptyTableMode == QgsLayoutTable::HideTable )
375  {
376  //empty table set to hide table mode, so don't draw anything
377  return;
378  }
379 
380  if ( !mLayout->renderContext().isPreviewRender() )
381  {
382  //exporting composition, so force an attribute refresh
383  //we do this in case vector layer has changed via an external source (e.g., another database user)
385  }
386 
387  const bool prevTextFormatScaleFlag = context.renderContext().testFlag( Qgis::RenderContextFlag::ApplyScalingWorkaroundForTextRendering );
389 
390  //calculate which rows to show in this frame
391  QPair< int, int > rowsToShow = rowRange( context.renderContext(), frameIndex );
392 
393  double gridSizeX = mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0;
394  double gridSizeY = mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0;
395  double cellHeaderHeight = mMaxRowHeightMap[0] + 2 * mCellMargin;
397  QRectF cell;
398 
399  //calculate whether a header is required
400  bool drawHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && frameIndex < 1 )
402  //calculate whether drawing table contents is required
403  bool drawContents = !( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage );
404 
405  int numberRowsToDraw = rowsToShow.second - rowsToShow.first;
406  int numberEmptyRows = 0;
407  if ( drawContents && mShowEmptyRows )
408  {
409  numberRowsToDraw = rowsVisible( context.renderContext(), frameIndex, rowsToShow.first, true );
410  numberEmptyRows = numberRowsToDraw - rowsToShow.second + rowsToShow.first;
411  }
412  bool mergeCells = false;
413  if ( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage )
414  {
415  //draw a merged row for the empty table message
416  numberRowsToDraw++;
417  rowsToShow.second++;
418  mergeCells = true;
419  }
420 
421  QPainter *p = context.renderContext().painter();
422  QgsScopedQPainterState painterState( p );
423  // painter is scaled to dots, so scale back to layout units
424  p->scale( context.renderContext().scaleFactor(), context.renderContext().scaleFactor() );
425 
426  //draw the text
427  p->setPen( Qt::SolidLine );
428 
429  double currentX = gridSizeX;
430  double currentY = gridSizeY;
431  if ( drawHeader )
432  {
433  //draw the headers
434  int col = 0;
435  for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
436  {
437  std::unique_ptr< QgsExpressionContextScope > headerCellScope = std::make_unique< QgsExpressionContextScope >();
438  headerCellScope->setVariable( QStringLiteral( "column_number" ), col + 1, true );
439  QgsExpressionContextScopePopper popper( context.renderContext().expressionContext(), headerCellScope.release() );
440 
441  const QgsTextFormat headerFormat = textFormatForHeader( col );
442  //draw background
443  p->save();
444  p->setPen( Qt::NoPen );
445  p->setBrush( backgroundColor( -1, col ) );
446  p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, cellHeaderHeight ) );
447  p->restore();
448 
449  currentX += mCellMargin;
450 
451  cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], cellHeaderHeight );
452 
453  //calculate alignment of header
455  switch ( mHeaderHAlignment )
456  {
457  case FollowColumn:
458  headerAlign = QgsTextRenderer::convertQtHAlignment( column.hAlignment() );
459  break;
460  case HeaderLeft:
461  headerAlign = QgsTextRenderer::AlignLeft;
462  break;
463  case HeaderCenter:
464  headerAlign = QgsTextRenderer::AlignCenter;
465  break;
466  case HeaderRight:
467  headerAlign = QgsTextRenderer::AlignRight;
468  break;
469  }
470 
471  const QRectF textCell = QRectF( currentX, currentY + mCellMargin, mMaxColumnWidthMap[col], cellHeaderHeight - 2 * mCellMargin );
472 
473  // disable text clipping to target text rectangle, because we manually clip to the full cell bounds below
474  // and it's ok if text overlaps into the margin (e.g. extenders or italicized text)
475  QStringList str = column.heading().split( '\n' );
476  if ( ( mWrapBehavior != TruncateText || column.width() > 0 ) && textRequiresWrapping( context.renderContext(), column.heading(), column.width(), headerFormat ) )
477  {
478  str = wrappedText( context.renderContext(), column.heading(), column.width(), headerFormat );
479  }
480 
481  // scale to dots
482  {
484  QgsTextRenderer::drawText( QRectF( textCell.left() * context.renderContext().scaleFactor(),
485  textCell.top() * context.renderContext().scaleFactor(),
486  textCell.width() * context.renderContext().scaleFactor(),
487  textCell.height() * context.renderContext().scaleFactor() ), 0,
488  headerAlign, str, context.renderContext(), headerFormat, true, QgsTextRenderer::AlignVCenter );
489  }
490 
491  currentX += mMaxColumnWidthMap[ col ];
492  currentX += mCellMargin;
493  currentX += gridSizeX;
494  col++;
495  }
496 
497  currentY += cellHeaderHeight;
498  currentY += gridSizeY;
499  }
500 
501  //now draw the body cells
502  int rowsDrawn = 0;
503  if ( drawContents )
504  {
505  //draw the attribute values
506  for ( int row = rowsToShow.first; row < rowsToShow.second; ++row )
507  {
508  rowsDrawn++;
509  currentX = gridSizeX;
510  int col = 0;
511 
512  //calculate row height
513  double rowHeight = mMaxRowHeightMap[row + 1] + 2 * mCellMargin;
514 
515  for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
516  {
517  const QRectF fullCell( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, rowHeight );
518  //draw background
519  p->save();
520  p->setPen( Qt::NoPen );
521  p->setBrush( backgroundColor( row, col ) );
522  p->drawRect( fullCell );
523  p->restore();
524 
525  // currentY = gridSize;
526  currentX += mCellMargin;
527 
528  QVariant cellContents = mTableContents.at( row ).at( col );
529  const QString localizedString { QgsExpressionUtils::toLocalizedString( cellContents ) };
530  QStringList str = localizedString.split( '\n' );
531 
532  QgsTextFormat cellFormat = textFormatForCell( row, col );
534  cellFormat.updateDataDefinedProperties( context.renderContext() );
535 
536  // disable text clipping to target text rectangle, because we manually clip to the full cell bounds below
537  // and it's ok if text overlaps into the margin (e.g. extenders or italicized text)
538  if ( ( mWrapBehavior != TruncateText || column.width() > 0 ) && textRequiresWrapping( context.renderContext(), localizedString, column.width(), cellFormat ) )
539  {
540  str = wrappedText( context.renderContext(), localizedString, column.width(), cellFormat );
541  }
542 
543  p->save();
544  p->setClipRect( fullCell );
545  const QRectF textCell = QRectF( currentX, currentY + mCellMargin, mMaxColumnWidthMap[col], rowHeight - 2 * mCellMargin );
546 
547  const QgsConditionalStyle style = conditionalCellStyle( row, col );
548  QColor foreColor = cellFormat.color();
549  if ( style.textColor().isValid() )
550  foreColor = style.textColor();
551 
552  cellFormat.setColor( foreColor );
553 
554  // scale to dots
555  {
557  QgsTextRenderer::drawText( QRectF( textCell.left() * context.renderContext().scaleFactor(),
558  textCell.top() * context.renderContext().scaleFactor(),
559  textCell.width() * context.renderContext().scaleFactor(),
560  textCell.height() * context.renderContext().scaleFactor() ), 0,
561  QgsTextRenderer::convertQtHAlignment( horizontalAlignmentForCell( row, col ) ), str, context.renderContext(), cellFormat, true,
563  }
564  p->restore();
565 
566  currentX += mMaxColumnWidthMap[ col ];
567  currentX += mCellMargin;
568  currentX += gridSizeX;
569  col++;
570  }
571  currentY += rowHeight;
572  currentY += gridSizeY;
573  }
574  }
575 
576  if ( numberRowsToDraw > rowsDrawn )
577  {
578  p->save();
579  p->setPen( Qt::NoPen );
580 
581  //draw background of empty rows
582  for ( int row = rowsDrawn; row < numberRowsToDraw; ++row )
583  {
584  currentX = gridSizeX;
585  int col = 0;
586 
587  if ( mergeCells )
588  {
589  p->setBrush( backgroundColor( row + 10000, 0 ) );
590  p->drawRect( QRectF( gridSizeX, currentY, mTableSize.width() - 2 * gridSizeX, cellBodyHeightForEmptyRows ) );
591  }
592  else
593  {
594  for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
595  {
596  Q_UNUSED( column )
597 
598  //draw background
599 
600  //we use a bit of a hack here - since we don't want these extra blank rows to match the firstrow/lastrow rule, add 10000 to row number
601  p->setBrush( backgroundColor( row + 10000, col ) );
602  p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, cellBodyHeightForEmptyRows ) );
603 
604  // currentY = gridSize;
605  currentX += mMaxColumnWidthMap[ col ] + 2 * mCellMargin;
606  currentX += gridSizeX;
607  col++;
608  }
609  }
610  currentY += cellBodyHeightForEmptyRows + gridSizeY;
611  }
612  p->restore();
613  }
614 
615  //and the borders
616  if ( mShowGrid )
617  {
618  QPen gridPen;
619  gridPen.setWidthF( mGridStrokeWidth );
620  gridPen.setColor( mGridColor );
621  gridPen.setJoinStyle( Qt::MiterJoin );
622  p->setPen( gridPen );
623  if ( mHorizontalGrid )
624  {
625  drawHorizontalGridLines( context, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader );
626  }
627  if ( mVerticalGrid )
628  {
629  drawVerticalGridLines( context, mMaxColumnWidthMap, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader, mergeCells );
630  }
631  }
632 
633  //special case - no records and table is set to ShowMessage mode
634  if ( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage )
635  {
636  double messageX = gridSizeX + mCellMargin;
637  double messageY = gridSizeY + ( drawHeader ? cellHeaderHeight + gridSizeY : 0 );
638  cell = QRectF( messageX, messageY, mTableSize.width() - messageX, cellBodyHeightForEmptyRows );
639 
640  // scale to dots
641  {
643  QgsTextRenderer::drawText( QRectF( cell.left() * context.renderContext().scaleFactor(),
644  cell.top() * context.renderContext().scaleFactor(),
645  cell.width() * context.renderContext().scaleFactor(),
646  cell.height() * context.renderContext().scaleFactor() ), 0,
648  }
649  }
650 
652 }
653 
654 void QgsLayoutTable::setCellMargin( const double margin )
655 {
656  if ( qgsDoubleNear( margin, mCellMargin ) )
657  {
658  return;
659  }
660 
661  mCellMargin = margin;
662 
663  //since spacing has changed, we need to recalculate the table size
665 
666  emit changed();
667 }
668 
670 {
671  if ( mode == mEmptyTableMode )
672  {
673  return;
674  }
675 
676  mEmptyTableMode = mode;
677 
678  //since appearance has changed, we need to recalculate the table size
680 
681  emit changed();
682 }
683 
684 void QgsLayoutTable::setEmptyTableMessage( const QString &message )
685 {
686  if ( message == mEmptyTableMessage )
687  {
688  return;
689  }
690 
691  mEmptyTableMessage = message;
692 
693  //since message has changed, we need to recalculate the table size
695 
696  emit changed();
697 }
698 
699 void QgsLayoutTable::setShowEmptyRows( const bool showEmpty )
700 {
701  if ( showEmpty == mShowEmptyRows )
702  {
703  return;
704  }
705 
706  mShowEmptyRows = showEmpty;
707  update();
708  emit changed();
709 }
710 
711 void QgsLayoutTable::setHeaderFont( const QFont &font )
712 {
713  mHeaderTextFormat.setFont( font );
714  if ( font.pointSizeF() > 0 )
715  {
716  mHeaderTextFormat.setSize( font.pointSizeF() );
718  }
719  else if ( font.pixelSize() > 0 )
720  {
721  mHeaderTextFormat.setSize( font.pixelSize() );
723  }
724 
725  //since font attributes have changed, we need to recalculate the table size
727 
728  emit changed();
729 }
730 
732 {
733  return mHeaderTextFormat.toQFont();
734 }
735 
736 void QgsLayoutTable::setHeaderFontColor( const QColor &color )
737 {
738  if ( color == mHeaderTextFormat.color() )
739  {
740  return;
741  }
742 
743  mHeaderTextFormat.setColor( color );
744  update();
745 
746  emit changed();
747 }
748 
750 {
751  return mHeaderTextFormat.color();
752 }
753 
755 {
756  mHeaderTextFormat = format;
757 
758  //since font attributes have changed, we need to recalculate the table size
760 
761  emit changed();
762 }
763 
765 {
766  return mHeaderTextFormat;
767 }
768 
770 {
771  if ( alignment == mHeaderHAlignment )
772  {
773  return;
774  }
775 
776  mHeaderHAlignment = alignment;
777  update();
778 
779  emit changed();
780 }
781 
783 {
784  if ( mode == mHeaderMode )
785  {
786  return;
787  }
788 
789  mHeaderMode = mode;
791 
792  emit changed();
793 }
794 
795 void QgsLayoutTable::setContentFont( const QFont &font )
796 {
797  mContentTextFormat.setFont( font );
798  if ( font.pointSizeF() > 0 )
799  {
800  mContentTextFormat.setSize( font.pointSizeF() );
802  }
803  else if ( font.pixelSize() > 0 )
804  {
805  mContentTextFormat.setSize( font.pixelSize() );
807  }
808 
809  //since font attributes have changed, we need to recalculate the table size
811 
812  emit changed();
813 }
814 
816 {
817  return mContentTextFormat.toQFont();
818 }
819 
820 void QgsLayoutTable::setContentFontColor( const QColor &color )
821 {
822  if ( color == mContentTextFormat.color() )
823  {
824  return;
825  }
826 
827  mContentTextFormat.setColor( color );
828  update();
829 
830  emit changed();
831 }
832 
834 {
835  return mContentTextFormat.color();
836 }
837 
839 {
840  mContentTextFormat = format;
841 
842  //since spacing has changed, we need to recalculate the table size
844 
845  emit changed();
846 }
847 
849 {
850  return mContentTextFormat;
851 }
852 
853 void QgsLayoutTable::setShowGrid( const bool showGrid )
854 {
855  if ( showGrid == mShowGrid )
856  {
857  return;
858  }
859 
861  //since grid spacing has changed, we need to recalculate the table size
863 
864  emit changed();
865 }
866 
867 void QgsLayoutTable::setGridStrokeWidth( const double width )
868 {
869  if ( qgsDoubleNear( width, mGridStrokeWidth ) )
870  {
871  return;
872  }
873 
874  mGridStrokeWidth = width;
875  //since grid spacing has changed, we need to recalculate the table size
877 
878  emit changed();
879 }
880 
881 void QgsLayoutTable::setGridColor( const QColor &color )
882 {
883  if ( color == mGridColor )
884  {
885  return;
886  }
887 
888  mGridColor = color;
889  update();
890 
891  emit changed();
892 }
893 
894 void QgsLayoutTable::setHorizontalGrid( const bool horizontalGrid )
895 {
897  {
898  return;
899  }
900 
902  //since grid spacing has changed, we need to recalculate the table size
904 
905  emit changed();
906 }
907 
908 void QgsLayoutTable::setVerticalGrid( const bool verticalGrid )
909 {
910  if ( verticalGrid == mVerticalGrid )
911  {
912  return;
913  }
914 
916  //since grid spacing has changed, we need to recalculate the table size
918 
919  emit changed();
920 }
921 
922 void QgsLayoutTable::setBackgroundColor( const QColor &color )
923 {
924  if ( color == mBackgroundColor )
925  {
926  return;
927  }
928 
929  mBackgroundColor = color;
930  update();
931 
932  emit changed();
933 }
934 
936 {
937  if ( behavior == mWrapBehavior )
938  {
939  return;
940  }
941 
942  mWrapBehavior = behavior;
944 
945  emit changed();
946 }
947 
949 {
950  //remove existing columns
951  mColumns = columns;
952 
953  // backward compatibility
954  // test if sorting is provided with the columns and call setSortColumns in such case
955  QgsLayoutTableSortColumns newSortColumns;
957  std::copy_if( mColumns.begin(), mColumns.end(), std::back_inserter( newSortColumns ), []( const QgsLayoutTableColumn & col ) {return col.sortByRank() > 0;} );
958  if ( !newSortColumns.isEmpty() )
959  {
960  std::sort( newSortColumns.begin(), newSortColumns.end(), []( const QgsLayoutTableColumn & a, const QgsLayoutTableColumn & b ) {return a.sortByRank() < b.sortByRank();} );
961  setSortColumns( newSortColumns );
962  }
964 }
965 
967 {
969 }
970 
972 {
973  if ( mCellStyles.contains( group ) )
974  delete mCellStyles.take( group );
975 
976  mCellStyles.insert( group, new QgsLayoutTableStyle( style ) );
977 }
978 
980 {
981  if ( !mCellStyles.contains( group ) )
982  return nullptr;
983 
984  return mCellStyles.value( group );
985 }
986 
987 QMap<int, QString> QgsLayoutTable::headerLabels() const
988 {
989  QMap<int, QString> headers;
990 
991  int i = 0;
992  for ( const QgsLayoutTableColumn &col : std::as_const( mColumns ) )
993  {
994  headers.insert( i, col.heading() );
995  i++;
996  }
997  return headers;
998 }
999 
1001 {
1002  std::unique_ptr< QgsExpressionContextScope > cellScope = std::make_unique< QgsExpressionContextScope >();
1003  cellScope->setVariable( QStringLiteral( "row_number" ), row + 1, true );
1004  cellScope->setVariable( QStringLiteral( "column_number" ), column + 1, true );
1005  return cellScope.release();
1006 }
1007 
1009 {
1010  return QgsConditionalStyle();
1011 }
1012 
1013 QSizeF QgsLayoutTable::fixedFrameSize( const int frameIndex ) const
1014 {
1015  Q_UNUSED( frameIndex )
1016  return QSizeF( mTableSize.width(), 0 );
1017 }
1018 
1019 QSizeF QgsLayoutTable::minFrameSize( const int frameIndex ) const
1020 {
1023 
1024  double height = 0;
1027  {
1028  //header required, force frame to be high enough for header
1029  for ( int col = 0; col < mColumns.size(); ++ col )
1030  {
1032  }
1033  }
1034  return QSizeF( 0, height );
1035 }
1036 
1038 {
1039  mMaxColumnWidthMap.clear();
1040  mMaxRowHeightMap.clear();
1041  mTableContents.clear();
1042 
1043  //get new contents
1044  if ( !getTableContents( mTableContents ) )
1045  {
1046  return;
1047  }
1048 }
1049 
1051 {
1052  mTableSize = QSizeF( totalWidth(), totalHeight() );
1054 }
1055 
1056 void QgsLayoutTable::initStyles()
1057 {
1058  mCellStyles.insert( OddColumns, new QgsLayoutTableStyle() );
1059  mCellStyles.insert( EvenColumns, new QgsLayoutTableStyle() );
1060  mCellStyles.insert( OddRows, new QgsLayoutTableStyle() );
1061  mCellStyles.insert( EvenRows, new QgsLayoutTableStyle() );
1062  mCellStyles.insert( FirstColumn, new QgsLayoutTableStyle() );
1063  mCellStyles.insert( LastColumn, new QgsLayoutTableStyle() );
1064  mCellStyles.insert( HeaderRow, new QgsLayoutTableStyle() );
1065  mCellStyles.insert( FirstRow, new QgsLayoutTableStyle() );
1066  mCellStyles.insert( LastRow, new QgsLayoutTableStyle() );
1067 
1068  mCellStyleNames.insert( OddColumns, QStringLiteral( "oddColumns" ) );
1069  mCellStyleNames.insert( EvenColumns, QStringLiteral( "evenColumns" ) );
1070  mCellStyleNames.insert( OddRows, QStringLiteral( "oddRows" ) );
1071  mCellStyleNames.insert( EvenRows, QStringLiteral( "evenRows" ) );
1072  mCellStyleNames.insert( FirstColumn, QStringLiteral( "firstColumn" ) );
1073  mCellStyleNames.insert( LastColumn, QStringLiteral( "lastColumn" ) );
1074  mCellStyleNames.insert( HeaderRow, QStringLiteral( "headerRow" ) );
1075  mCellStyleNames.insert( FirstRow, QStringLiteral( "firstRow" ) );
1076  mCellStyleNames.insert( LastRow, QStringLiteral( "lastRow" ) );
1077 }
1078 
1080 {
1081  mMaxColumnWidthMap.clear();
1082 
1083  //total number of cells (rows + 1 for header)
1084  int cols = mColumns.count();
1085  int cells = cols * ( mTableContents.count() + 1 );
1086  QVector< double > widths( cells );
1087 
1088  double currentCellTextWidth;
1089 
1092 
1093  //first, go through all the column headers and calculate the sizes
1094  int i = 0;
1095  for ( const QgsLayoutTableColumn &col : std::as_const( mColumns ) )
1096  {
1097  if ( col.width() > 0 )
1098  {
1099  //column has manually specified width
1100  widths[i] = col.width();
1101  }
1102  else if ( mHeaderMode != QgsLayoutTable::NoHeaders )
1103  {
1104  std::unique_ptr< QgsExpressionContextScope > headerCellScope = std::make_unique< QgsExpressionContextScope >();
1105  headerCellScope->setVariable( QStringLiteral( "column_number" ), i + 1, true );
1106  QgsExpressionContextScopePopper popper( context.expressionContext(), headerCellScope.release() );
1107 
1108  //column width set to automatic, so check content size
1109  const QStringList multiLineSplit = col.heading().split( '\n' );
1110  currentCellTextWidth = QgsTextRenderer::textWidth( context, textFormatForHeader( i ), multiLineSplit ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
1111  widths[i] = currentCellTextWidth;
1112  }
1113  else
1114  {
1115  widths[i] = 0.0;
1116  }
1117  i++;
1118  }
1119 
1120  //next, go through all the table contents and calculate the sizes
1121  QgsLayoutTableContents::const_iterator rowIt = mTableContents.constBegin();
1122  int row = 1;
1123  for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
1124  {
1125  QgsLayoutTableRow::const_iterator colIt = rowIt->constBegin();
1126  int col = 0;
1127  for ( ; colIt != rowIt->constEnd(); ++colIt )
1128  {
1129  if ( mColumns.at( col ).width() <= 0 )
1130  {
1131  //column width set to automatic, so check content size
1132  const QStringList multiLineSplit = QgsExpressionUtils::toLocalizedString( *colIt ).split( '\n' );
1133 
1134  QgsTextFormat cellFormat = textFormatForCell( row - 1, col );
1135  QgsExpressionContextScopePopper popper( context.expressionContext(), scopeForCell( row - 1, col ) );
1136  cellFormat.updateDataDefinedProperties( context );
1137 
1138  currentCellTextWidth = QgsTextRenderer::textWidth( context, cellFormat, multiLineSplit ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
1139  widths[ row * cols + col ] = currentCellTextWidth;
1140  }
1141  else
1142  {
1143  widths[ row * cols + col ] = 0;
1144  }
1145 
1146  col++;
1147  }
1148  row++;
1149  }
1150 
1151  //calculate maximum
1152  for ( int col = 0; col < cols; ++col )
1153  {
1154  double maxColWidth = 0;
1155  for ( int row = 0; row < mTableContents.count() + 1; ++row )
1156  {
1157  maxColWidth = std::max( widths[ row * cols + col ], maxColWidth );
1158  }
1159  mMaxColumnWidthMap.insert( col, maxColWidth );
1160  }
1161 
1162  return true;
1163 }
1164 
1166 {
1167  mMaxRowHeightMap.clear();
1168 
1169  //total number of cells (rows + 1 for header)
1170  int cols = mColumns.count();
1171  int cells = cols * ( mTableContents.count() + 1 );
1172  QVector< double > heights( cells );
1173 
1176 
1177  //first, go through all the column headers and calculate the sizes
1178  int i = 0;
1179  for ( const QgsLayoutTableColumn &col : std::as_const( mColumns ) )
1180  {
1181  std::unique_ptr< QgsExpressionContextScope > headerCellScope = std::make_unique< QgsExpressionContextScope >();
1182  headerCellScope->setVariable( QStringLiteral( "column_number" ), i + 1, true );
1183  QgsExpressionContextScopePopper popper( context.expressionContext(), headerCellScope.release() );
1184 
1185  const QgsTextFormat cellFormat = textFormatForHeader( i );
1187  //height
1189  {
1190  heights[i] = 0;
1191  }
1192  else if ( textRequiresWrapping( context, col.heading(), mColumns.at( i ).width(), cellFormat ) )
1193  {
1194  //contents too wide for cell, need to wrap
1195  heights[i] = QgsTextRenderer::textHeight( context, cellFormat, wrappedText( context, col.heading(), mColumns.at( i ).width(), cellFormat ), QgsTextRenderer::Rect )
1197  - headerDescentMm;
1198  }
1199  else
1200  {
1201  heights[i] = QgsTextRenderer::textHeight( context, cellFormat, QStringList() << col.heading(), QgsTextRenderer::Rect ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters )
1202  - headerDescentMm;
1203  }
1204  i++;
1205  }
1206 
1207  //next, go through all the table contents and calculate the sizes
1208  QgsLayoutTableContents::const_iterator rowIt = mTableContents.constBegin();
1209  int row = 1;
1210  for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
1211  {
1212  QgsLayoutTableRow::const_iterator colIt = rowIt->constBegin();
1213  int i = 0;
1214  for ( ; colIt != rowIt->constEnd(); ++colIt )
1215  {
1216  QgsTextFormat cellFormat = textFormatForCell( row - 1, i );
1217  QgsExpressionContextScopePopper popper( context.expressionContext(), scopeForCell( row - 1, i ) );
1218  cellFormat.updateDataDefinedProperties( context );
1220  const QString localizedString { QgsExpressionUtils::toLocalizedString( *colIt ) };
1221 
1222  if ( textRequiresWrapping( context, localizedString, mColumns.at( i ).width(), cellFormat ) )
1223  {
1224  //contents too wide for cell, need to wrap
1225  heights[ row * cols + i ] = QgsTextRenderer::textHeight( context, cellFormat, wrappedText( context, localizedString, mColumns.at( i ).width(), cellFormat ), QgsTextRenderer::Rect ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) - contentDescentMm;
1226  }
1227  else
1228  {
1229  heights[ row * cols + i ] = QgsTextRenderer::textHeight( context, cellFormat, QStringList() << localizedString.split( '\n' ), QgsTextRenderer::Rect ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) - contentDescentMm;
1230  }
1231 
1232  i++;
1233  }
1234  row++;
1235  }
1236 
1237  //calculate maximum
1238  for ( int row = 0; row < mTableContents.count() + 1; ++row )
1239  {
1240  double maxRowHeight = 0;
1241  for ( int col = 0; col < cols; ++col )
1242  {
1243  maxRowHeight = std::max( heights[ row * cols + col ], maxRowHeight );
1244  }
1245  mMaxRowHeightMap.insert( row, maxRowHeight );
1246  }
1247 
1248  return true;
1249 }
1250 
1252 {
1253  //check how much space each column needs
1254  if ( !calculateMaxColumnWidths() )
1255  {
1256  return 0;
1257  }
1258 
1259  //adapt frame to total width
1260  double totalWidth = 0;
1261  QMap<int, double>::const_iterator maxColWidthIt = mMaxColumnWidthMap.constBegin();
1262  for ( ; maxColWidthIt != mMaxColumnWidthMap.constEnd(); ++maxColWidthIt )
1263  {
1264  totalWidth += maxColWidthIt.value();
1265  }
1266  totalWidth += ( 2 * mMaxColumnWidthMap.size() * mCellMargin );
1267  totalWidth += ( mMaxColumnWidthMap.size() + 1 ) * ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1268 
1269  return totalWidth;
1270 }
1271 
1273 {
1274  //check how much space each row needs
1275  if ( !calculateMaxRowHeights() )
1276  {
1277  return 0;
1278  }
1279 
1280  double height = 0;
1281 
1284 
1285  //loop through all existing frames to calculate how many rows are visible in each
1286  //as the entire height of a frame may not be utilized for content rows
1287  int rowsAlreadyShown = 0;
1288  int numberExistingFrames = frameCount();
1289  int rowsVisibleInLastFrame = 0;
1290  double heightOfLastFrame = 0;
1291  for ( int idx = 0; idx < numberExistingFrames; ++idx )
1292  {
1293  bool hasHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && idx == 0 )
1295  heightOfLastFrame = frame( idx )->rect().height();
1296  rowsVisibleInLastFrame = rowsVisible( context, heightOfLastFrame, rowsAlreadyShown, hasHeader, false );
1297  rowsAlreadyShown += rowsVisibleInLastFrame;
1298  height += heightOfLastFrame;
1299  if ( rowsAlreadyShown >= mTableContents.length() )
1300  {
1301  //shown entire contents of table, nothing remaining
1302  return height;
1303  }
1304  }
1305 
1306  //calculate how many rows left to show
1307  int remainingRows = mTableContents.length() - rowsAlreadyShown;
1308 
1309  if ( remainingRows <= 0 )
1310  {
1311  //no remaining rows
1312  return height;
1313  }
1314 
1316  {
1317  QgsLayoutItemPage *page = mLayout->pageCollection()->page( mLayout->pageCollection()->pageCount() - 1 );
1318  if ( page )
1319  heightOfLastFrame = page->sizeWithUnits().height();
1320  }
1321 
1322  bool hasHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && numberExistingFrames < 1 )
1324 
1325  int numberFramesMissing = 0;
1326  while ( remainingRows > 0 )
1327  {
1328  numberFramesMissing++;
1329 
1330  rowsVisibleInLastFrame = rowsVisible( context, heightOfLastFrame, rowsAlreadyShown, hasHeader, false );
1331  if ( rowsVisibleInLastFrame < 1 )
1332  {
1333  //if no rows are visible in the last frame, calculation of missing frames
1334  //is impossible. So just return total height of existing frames
1335  return height;
1336  }
1337 
1338  rowsAlreadyShown += rowsVisibleInLastFrame;
1339  remainingRows = mTableContents.length() - rowsAlreadyShown;
1340  }
1341 
1342  //rows remain unshown -- how many extra frames would we need to complete the table?
1343  //assume all added frames are same size as final frame
1344  height += heightOfLastFrame * numberFramesMissing;
1345  return height;
1346 }
1347 
1348 void QgsLayoutTable::drawHorizontalGridLines( QgsLayoutItemRenderContext &context, int firstRow, int lastRow, bool drawHeaderLines ) const
1349 {
1350  //horizontal lines
1351  if ( lastRow - firstRow < 1 && !drawHeaderLines )
1352  {
1353  return;
1354  }
1355 
1356  QPainter *painter = context.renderContext().painter();
1357 
1359  double halfGridStrokeWidth = ( mShowGrid ? mGridStrokeWidth : 0 ) / 2.0;
1360  double currentY = 0;
1361  currentY = halfGridStrokeWidth;
1362  if ( drawHeaderLines )
1363  {
1364  painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
1365  currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
1366  currentY += mMaxRowHeightMap[0] + 2 * mCellMargin;
1367  }
1368  for ( int row = firstRow; row < lastRow; ++row )
1369  {
1370  painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
1371  currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
1372  double rowHeight = row < mTableContents.count() ? mMaxRowHeightMap[row + 1] : cellBodyHeightForEmptyRows;
1373  currentY += ( rowHeight + 2 * mCellMargin );
1374  }
1375  painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
1376 }
1377 
1378 bool QgsLayoutTable::textRequiresWrapping( QgsRenderContext &context, const QString &text, double columnWidth, const QgsTextFormat &format ) const
1379 {
1380  if ( qgsDoubleNear( columnWidth, 0.0 ) || mWrapBehavior != WrapText )
1381  return false;
1382 
1383  const QStringList multiLineSplit = text.split( '\n' );
1384  const double currentTextWidth = QgsTextRenderer::textWidth( context, format, multiLineSplit ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
1385  return currentTextWidth > columnWidth;
1386 }
1387 
1388 QStringList QgsLayoutTable::wrappedText( QgsRenderContext &context, const QString &value, double columnWidth, const QgsTextFormat &format ) const
1389 {
1390  QStringList lines = value.split( '\n' );
1391  QStringList outLines;
1392  const auto constLines = lines;
1393  for ( const QString &line : constLines )
1394  {
1395  if ( textRequiresWrapping( context, line, columnWidth, format ) )
1396  {
1397  //first step is to identify words which must be on their own line (too long to fit)
1398  QStringList words = line.split( ' ' );
1399  QStringList linesToProcess;
1400  QString wordsInCurrentLine;
1401  const auto constWords = words;
1402  for ( const QString &word : constWords )
1403  {
1404  if ( textRequiresWrapping( context, word, columnWidth, format ) )
1405  {
1406  //too long to fit
1407  if ( !wordsInCurrentLine.isEmpty() )
1408  linesToProcess << wordsInCurrentLine;
1409  wordsInCurrentLine.clear();
1410  linesToProcess << word;
1411  }
1412  else
1413  {
1414  if ( !wordsInCurrentLine.isEmpty() )
1415  wordsInCurrentLine.append( ' ' );
1416  wordsInCurrentLine.append( word );
1417  }
1418  }
1419  if ( !wordsInCurrentLine.isEmpty() )
1420  linesToProcess << wordsInCurrentLine;
1421 
1422  const auto constLinesToProcess = linesToProcess;
1423  for ( const QString &line : constLinesToProcess )
1424  {
1425  QString remainingText = line;
1426  int lastPos = remainingText.lastIndexOf( ' ' );
1427  while ( lastPos > -1 )
1428  {
1429  //check if remaining text is short enough to go in one line
1430  if ( !textRequiresWrapping( context, remainingText, columnWidth, format ) )
1431  {
1432  break;
1433  }
1434 
1435  if ( !textRequiresWrapping( context, remainingText.left( lastPos ), columnWidth, format ) )
1436  {
1437  outLines << remainingText.left( lastPos );
1438  remainingText = remainingText.mid( lastPos + 1 );
1439  lastPos = 0;
1440  }
1441  lastPos = remainingText.lastIndexOf( ' ', lastPos - 1 );
1442  }
1443  outLines << remainingText;
1444  }
1445  }
1446  else
1447  {
1448  outLines << line;
1449  }
1450  }
1451 
1452  return outLines;
1453 }
1454 
1455 QColor QgsLayoutTable::backgroundColor( int row, int column ) const
1456 {
1457  QColor color = mBackgroundColor;
1458  if ( QgsLayoutTableStyle *style = mCellStyles.value( OddColumns ) )
1459  if ( style->enabled && column % 2 == 0 )
1460  color = style->cellBackgroundColor;
1461  if ( QgsLayoutTableStyle *style = mCellStyles.value( EvenColumns ) )
1462  if ( style->enabled && column % 2 == 1 )
1463  color = style->cellBackgroundColor;
1464  if ( QgsLayoutTableStyle *style = mCellStyles.value( OddRows ) )
1465  if ( style->enabled && row % 2 == 0 )
1466  color = style->cellBackgroundColor;
1467  if ( QgsLayoutTableStyle *style = mCellStyles.value( EvenRows ) )
1468  if ( style->enabled && row % 2 == 1 )
1469  color = style->cellBackgroundColor;
1470  if ( QgsLayoutTableStyle *style = mCellStyles.value( FirstColumn ) )
1471  if ( style->enabled && column == 0 )
1472  color = style->cellBackgroundColor;
1473  if ( QgsLayoutTableStyle *style = mCellStyles.value( LastColumn ) )
1474  if ( style->enabled && column == mColumns.count() - 1 )
1475  color = style->cellBackgroundColor;
1476  if ( QgsLayoutTableStyle *style = mCellStyles.value( HeaderRow ) )
1477  if ( style->enabled && row == -1 )
1478  color = style->cellBackgroundColor;
1479  if ( QgsLayoutTableStyle *style = mCellStyles.value( FirstRow ) )
1480  if ( style->enabled && row == 0 )
1481  color = style->cellBackgroundColor;
1482  if ( QgsLayoutTableStyle *style = mCellStyles.value( LastRow ) )
1483  if ( style->enabled && row == mTableContents.count() - 1 )
1484  color = style->cellBackgroundColor;
1485 
1486  if ( row >= 0 )
1487  {
1488  QgsConditionalStyle conditionalStyle = conditionalCellStyle( row, column );
1489  if ( conditionalStyle.backgroundColor().isValid() )
1490  color = conditionalStyle.backgroundColor();
1491  }
1492 
1493  return color;
1494 }
1495 
1496 void QgsLayoutTable::drawVerticalGridLines( QgsLayoutItemRenderContext &context, const QMap<int, double> &maxWidthMap, int firstRow, int lastRow, bool hasHeader, bool mergeCells ) const
1497 {
1498  //vertical lines
1499  if ( lastRow - firstRow < 1 && !hasHeader )
1500  {
1501  return;
1502  }
1503 
1504  QPainter *painter = context.renderContext().painter();
1505 
1506  //calculate height of table within frame
1507  double tableHeight = 0;
1508  if ( hasHeader )
1509  {
1510  tableHeight += ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2 + mMaxRowHeightMap[0];
1511  }
1512  tableHeight += ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
1513  double headerHeight = tableHeight;
1514 
1516  for ( int row = firstRow; row < lastRow; ++row )
1517  {
1518  double rowHeight = row < mTableContents.count() ? mMaxRowHeightMap[row + 1] : cellBodyHeightForEmptyRows;
1519  tableHeight += rowHeight + ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2;
1520  }
1521 
1522  double halfGridStrokeWidth = ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 ) / 2.0;
1523  double currentX = halfGridStrokeWidth;
1524  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, tableHeight - halfGridStrokeWidth ) );
1525  currentX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1526  QMap<int, double>::const_iterator maxColWidthIt = maxWidthMap.constBegin();
1527  int col = 1;
1528  for ( ; maxColWidthIt != maxWidthMap.constEnd(); ++maxColWidthIt )
1529  {
1530  currentX += ( maxColWidthIt.value() + 2 * mCellMargin );
1531  if ( col == maxWidthMap.size() || !mergeCells )
1532  {
1533  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, tableHeight - halfGridStrokeWidth ) );
1534  }
1535  else if ( hasHeader )
1536  {
1537  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, headerHeight - halfGridStrokeWidth ) );
1538  }
1539 
1540  currentX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1541  col++;
1542  }
1543 }
1544 
1546 {
1548 
1549  //force recalculation of frame rects, so that they are set to the correct
1550  //fixed and minimum frame sizes
1552 }
1553 
1555 {
1556  return ( contents.indexOf( row ) >= 0 );
1557 }
1558 
1560 {
1561  return mContentTextFormat;
1562 }
1563 
1565 {
1566  return mHeaderTextFormat;
1567 }
1568 
1569 Qt::Alignment QgsLayoutTable::horizontalAlignmentForCell( int, int column ) const
1570 {
1571  return mColumns.value( column ).hAlignment();
1572 }
1573 
1574 Qt::Alignment QgsLayoutTable::verticalAlignmentForCell( int, int column ) const
1575 {
1576  return mColumns.value( column ).vAlignment();
1577 }
1578 
@ ApplyScalingWorkaroundForTextRendering
Whether a scaling workaround designed to stablise the rendering of small font sizes (or for painters ...
Conditional styling for a rule.
QColor backgroundColor() const
The background color for style.
QColor textColor() const
The text color set for style.
RAII class to pop scope from an expression context on destruction.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static bool setFromXmlChildNode(QFont &font, const QDomElement &element, const QString &childNode)
Sets the properties of a font to match the properties stored in an XML child node.
QRectF extent() const
Returns the visible portion of the multi frame's content which is shown in this frame,...
Item representing the paper in a layout.
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:45
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Definition: qgslayoutitem.h:72
QgsLayoutSize sizeWithUnits() const
Returns the item's current size, including units.
Abstract base class for layout items with the ability to distribute the content to several frames (Qg...
int frameCount() const
Returns the number of frames associated with this multiframe.
QgsLayoutFrame * frame(int index) const
Returns the child frame at a specified index from the multiframe.
@ ExtendToNextPage
Creates new full page frames on the following page(s) until the entire multiframe content is visible.
void refresh() override
Refreshes the multiframe, causing a recalculation of any property overrides.
virtual void recalculateFrameSizes()
Recalculates the portion of the multiframe item which is shown in each of its component frames.
void update()
Forces a redraw of all child frames.
int frameIndex(QgsLayoutFrame *frame) const
Returns the index of a frame within the multiframe.
void recalculateFrameRects()
Forces a recalculation of all the associated frame's scene rectangles.
void changed()
Emitted when the object's properties change.
QPointer< QgsLayout > mLayout
double height() const
Returns the height of the size.
Definition: qgslayoutsize.h:90
Stores properties of a column for a QgsLayoutTable.
bool readXml(const QDomElement &columnElem)
Reads the column's properties from xml.
Styling option for a layout table cell.
bool readXml(const QDomElement &styleElem)
Reads the style's properties from XML.
QColor cellBackgroundColor
Cell background color.
bool enabled
Whether the styling option is enabled.
bool writeXml(QDomElement &styleElem, QDomDocument &doc) const
Writes the style's properties to XML for storage.
bool mHorizontalGrid
True if grid should be shown.
QSizeF fixedFrameSize(int frameIndex=-1) const override
Returns the fixed size for a frame, if desired.
void setColumns(const QgsLayoutTableColumns &columns)
Replaces the columns in the table with a specified list of QgsLayoutTableColumns.
Q_DECL_DEPRECATED void setHeaderFontColor(const QColor &color)
Sets the color used to draw header text in the table.
QgsLayoutTable(QgsLayout *layout)
Constructor for QgsLayoutTable, belonging to the specified layout.
virtual Qt::Alignment horizontalAlignmentForCell(int row, int column) const
Returns the horizontal alignment to use for the cell at the specified row and column.
QColor backgroundColor() const
Returns the color used for the background of the table.
virtual QgsConditionalStyle conditionalCellStyle(int row, int column) const
Returns the conditional style to use for the cell at row, column.
void setHeaderHAlignment(HeaderHAlignment alignment)
Sets the horizontal alignment for table headers.
void setShowEmptyRows(bool showEmpty)
Sets whether empty rows should be drawn.
QgsLayoutTableColumns & columns()
Returns a reference to the list of QgsLayoutTableColumns shown in the table.
Q_DECL_DEPRECATED QFont headerFont() const
Returns the font used to draw header text in the table.
QMap< int, double > mMaxColumnWidthMap
Map of maximum width for each column.
void setVerticalGrid(bool verticalGrid)
Sets whether the grid's vertical lines should be drawn in the table.
virtual void refreshAttributes()
Refreshes the contents shown in the table by querying for new data.
void setGridColor(const QColor &color)
Sets the color used for grid lines in the table.
double totalWidth()
Returns total width of table contents.
bool horizontalGrid() const
Returns whether the grid's horizontal lines are drawn in the table.
Q_DECL_DEPRECATED void setContentFontColor(const QColor &color)
Sets the color used to draw text in table body cells.
void setCellMargin(double margin)
Sets the margin distance in mm between cell borders and their contents.
void render(QgsLayoutItemRenderContext &context, const QRectF &renderExtent, int frameIndex) override
Renders a portion of the multiframe's content into a render context.
Q_DECL_DEPRECATED void setHeaderFont(const QFont &font)
Sets the font used to draw header text in the table.
void drawVerticalGridLines(QgsLayoutItemRenderContext &context, const QMap< int, double > &maxWidthMap, int firstRow, int lastRow, bool hasHeader, bool mergeCells=false) const
Draws the vertical grid lines for the table.
void setBackgroundColor(const QColor &color)
Sets the color used for background of table.
void setContentTextFormat(const QgsTextFormat &format)
Sets the format used to draw content text in the table.
QgsTextFormat contentTextFormat() const
Returns the format used to draw content text in the table.
QString mEmptyTableMessage
String to show in empty tables.
void setWrapBehavior(WrapBehavior behavior)
Sets the wrap behavior for the table, which controls how text within cells is automatically wrapped.
Q_DECL_DEPRECATED QColor headerFontColor() const
Returns the color used to draw header text in the table.
QPair< int, int > rowRange(QgsRenderContext &context, int frameIndex) const
Calculates a range of rows which should be visible in a given frame.
double mGridStrokeWidth
Width of grid lines.
EmptyTableMode mEmptyTableMode
Behavior for empty tables.
void setEmptyTableBehavior(EmptyTableMode mode)
Sets the behavior mode for empty tables with no content rows.
virtual QgsTextFormat textFormatForHeader(int column) const
Returns the text format to use for the header cell at the specified column.
void setHeaderMode(HeaderMode mode)
Sets the display mode for headers in the table.
Q_DECL_DEPRECATED void setContentFont(const QFont &font)
Sets the font used to draw text in table body cells.
WrapBehavior mWrapBehavior
bool verticalGrid() const
Returns whether the grid's vertical lines are drawn in the table.
QColor mBackgroundColor
Color for table background.
void recalculateTableSize()
Recalculates and updates the size of the table and all table frames.
void drawHorizontalGridLines(QgsLayoutItemRenderContext &context, int firstRow, int lastRow, bool drawHeaderLines) const
Draws the horizontal grid lines for the table.
virtual QgsExpressionContextScope * scopeForCell(int row, int column) const
Creates a new QgsExpressionContextScope for the cell at row, column.
QgsLayoutTableContents & contents()
Returns the current contents of the table.
Q_DECL_DEPRECATED QFont contentFont() const
Returns the font used to draw text in table body cells.
bool contentsContainsRow(const QgsLayoutTableContents &contents, const QgsLayoutTableRow &row) const
Checks whether a table contents contains a given row.
bool mShowGrid
True if grid should be shown.
CellStyleGroup
Row or column groups for cell styling.
@ FirstRow
Style first row only.
@ EvenColumns
Style even numbered columns.
@ EvenRows
Style even numbered rows.
@ HeaderRow
Style header row.
@ OddColumns
Style odd numbered columns.
@ FirstColumn
Style first column only.
@ LastColumn
Style last column only.
@ LastRow
Style last row only.
@ OddRows
Style odd numbered rows.
QgsTextFormat mHeaderTextFormat
void setHorizontalGrid(bool horizontalGrid)
Sets whether the grid's horizontal lines should be drawn in the table.
HeaderMode
Controls where headers are shown in the table.
@ FirstFrame
Header shown on first frame only.
@ AllFrames
Headers shown on all frames.
@ NoHeaders
No headers shown for table.
void setCellStyle(CellStyleGroup group, const QgsLayoutTableStyle &style)
Sets the cell style for a cell group.
QgsLayoutTableColumns mColumns
Columns to show in table.
const QgsLayoutTableStyle * cellStyle(CellStyleGroup group) const
Returns the cell style for a cell group.
void setShowGrid(bool showGrid)
Sets whether grid lines should be drawn in the table.
QgsTextFormat mContentTextFormat
virtual bool getTableContents(QgsLayoutTableContents &contents)=0
Fetches the contents used for the cells in the table.
QgsLayoutTableSortColumns & sortColumns()
Returns a reference to the list of QgsLayoutTableSortColumns shown in the table.
virtual bool calculateMaxColumnWidths()
Calculates the maximum width of text shown in columns.
~QgsLayoutTable() override
QgsTextFormat headerTextFormat() const
Returns the format used to draw header text in the table.
HeaderMode mHeaderMode
Header display mode.
HeaderHAlignment mHeaderHAlignment
Alignment for table headers.
int rowsVisible(QgsRenderContext &context, double frameHeight, int firstRow, bool includeHeader, bool includeEmptyRows) const
Calculates how many content rows would be visible within a frame of the specified height.
void recalculateFrameSizes() override
bool mShowEmptyRows
True if empty rows should be shown in the table.
QColor mGridColor
Color for grid lines.
QSizeF minFrameSize(int frameIndex=-1) const override
Returns the minimum size for a frames, if desired.
double mCellMargin
Margin between cell borders and cell text.
virtual Qt::Alignment verticalAlignmentForCell(int row, int column) const
Returns the vertical alignment to use for the cell at the specified row and column.
Q_DECL_DEPRECATED QColor contentFontColor() const
Returns the color used to draw text in table body cells.
double totalHeight()
Returns total height of table contents.
QgsLayoutTableContents mTableContents
Contents to show in table.
bool writePropertiesToElement(QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context) const override
Stores multiframe state within an XML DOM element.
void setEmptyTableMessage(const QString &message)
Sets the message for empty tables with no content rows.
void setSortColumns(const QgsLayoutTableSortColumns &sortColumns)
Replaces the sorting columns in the table with a specified list of QgsLayoutTableSortColumns.
void setHeaderTextFormat(const QgsTextFormat &format)
Sets the format used to draw header text in the table.
QgsLayoutTableSortColumns mSortColumns
Columns to sort the table.
bool showGrid() const
Returns whether grid lines are drawn in the table.
virtual QMap< int, QString > headerLabels() const
Returns the text used in the column headers for the table.
void setGridStrokeWidth(double width)
Sets the width in mm for grid lines in the table.
virtual QgsTextFormat textFormatForCell(int row, int column) const
Returns the text format to use for the cell at the specified row and column.
bool readPropertiesFromElement(const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context) override
Sets multiframe state from a DOM element.
QSizeF totalSize() const override
Returns the total size of the multiframe's content, in layout units.
bool mVerticalGrid
True if grid should be shown.
virtual bool calculateMaxRowHeights()
Calculates the maximum height of text shown in rows.
WrapBehavior
Controls how long strings in the table are handled.
@ WrapText
Text which doesn't fit inside the cell is wrapped. Note that this only applies to text in columns wit...
@ TruncateText
Text which doesn't fit inside the cell is truncated.
HeaderHAlignment
Controls how headers are horizontally aligned in a table.
@ HeaderRight
Align headers right.
@ HeaderLeft
Align headers left.
@ HeaderCenter
Align headers to center.
@ FollowColumn
Header uses the same alignment as the column.
EmptyTableMode
Controls how empty tables are displayed.
@ HideTable
Hides entire table if empty.
@ ShowMessage
Shows preset message instead of table contents.
void refresh() override
QMap< int, double > mMaxRowHeightMap
Map of maximum height for each row.
QMap< CellStyleGroup, QgsLayoutTableStyle * > mCellStyles
static QgsRenderContext createRenderContextForLayout(QgsLayout *layout, QPainter *painter, double dpi=-1)
Creates a render context suitable for the specified layout and painter destination.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:51
The class is used as a container of context for various read/write operations on other objects.
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
bool testFlag(Qgis::RenderContextFlag flag) const
Check whether a particular flag is enabled.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
Scoped object for saving and restoring a QPainter object's state.
Scoped object for temporary scaling of a QgsRenderContext for pixel based rendering.
static QColor decodeColor(const QString &str)
static QString encodeColor(const QColor &color)
Container for all settings relating to text rendering.
Definition: qgstextformat.h:41
void setColor(const QColor &color)
Sets the color that text will be rendered in.
void setSize(double size)
Sets the size for rendered text.
void setFont(const QFont &font)
Sets the font used for rendering text.
void updateDataDefinedProperties(QgsRenderContext &context)
Updates the format by evaluating current values of data defined properties.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
QFont toQFont() const
Returns a QFont matching the relevant settings from this text format.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units for the size of rendered text.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
QColor color() const
Returns the color that text will be rendered in.
static double textWidth(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF *fontMetrics=nullptr)
Returns the width of a text based on a given format.
@ AlignVCenter
Center align.
HAlignment
Horizontal alignment.
@ AlignLeft
Left align.
@ AlignRight
Right align.
@ AlignCenter
Center align.
static double textHeight(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode=Point, QFontMetricsF *fontMetrics=nullptr)
Returns the height of a text based on a given format.
static QFontMetricsF fontMetrics(QgsRenderContext &context, const QgsTextFormat &format, double scaleFactor=1.0)
Returns the font metrics for the given text format, when rendered in the specified render context.
static HAlignment convertQtHAlignment(Qt::Alignment alignment)
Converts a Qt horizontal alignment flag to a QgsTextRenderer::HAlignment value.
static VAlignment convertQtVAlignment(Qt::Alignment alignment)
Converts a Qt vertical alignment flag to a QgsTextRenderer::VAlignment value.
static void drawText(const QRectF &rect, double rotation, HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, VAlignment vAlignment=AlignTop)
Draws text within a rectangle using the specified settings.
@ Rect
Text within rectangle draw mode.
static constexpr double FONT_WORKAROUND_SCALE
Scale factor for upscaling font sizes and downscaling destination painter devices.
@ RenderPoints
Points (e.g., for font sizes)
Definition: qgsunittypes.h:173
@ RenderPixels
Pixels.
Definition: qgsunittypes.h:171
@ RenderMillimeters
Millimeters.
Definition: qgsunittypes.h:169
QVector< QgsLayoutTableColumn > QgsLayoutTableColumns
List of column definitions for a QgsLayoutTable.
QVector< QgsLayoutTableColumn > QgsLayoutTableSortColumns
List of column definitions for sorting a QgsLayoutTable.
QVector< QgsLayoutTableRow > QgsLayoutTableContents
List of QgsLayoutTableRows, representing rows and column cell contents for a QgsLayoutTable.
QVector< QVariant > QgsLayoutTableRow
List of QVariants, representing a the contents of a single row in a QgsLayoutTable.
#define str(x)
Definition: qgis.cpp:37
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:1730
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:1729
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1234