QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 "qgslayouttable.h"
20 #include "qgslayout.h"
21 #include "qgslayoututils.h"
22 #include "qgslayouttablecolumn.h"
23 #include "qgssymbollayerutils.h"
24 #include "qgslayoutframe.h"
25 #include "qgsfontutils.h"
26 #include "qgssettings.h"
28 #include "qgstextrenderer.h"
29 
30 //
31 // QgsLayoutTableStyle
32 //
33 
34 bool QgsLayoutTableStyle::writeXml( QDomElement &styleElem, QDomDocument &doc ) const
35 {
36  Q_UNUSED( doc )
37  styleElem.setAttribute( QStringLiteral( "cellBackgroundColor" ), QgsSymbolLayerUtils::encodeColor( cellBackgroundColor ) );
38  styleElem.setAttribute( QStringLiteral( "enabled" ), enabled );
39  return true;
40 }
41 
42 bool QgsLayoutTableStyle::readXml( const QDomElement &styleElem )
43 {
44  cellBackgroundColor = QgsSymbolLayerUtils::decodeColor( styleElem.attribute( QStringLiteral( "cellBackgroundColor" ), QStringLiteral( "255,255,255,255" ) ) );
45  enabled = ( styleElem.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ) != QLatin1String( "0" ) );
46  return true;
47 }
48 
49 
50 //
51 // QgsLayoutTable
52 //
53 
55  : QgsLayoutMultiFrame( layout )
56 {
57  initStyles();
58 }
59 
61 {
62  mColumns.clear();
63  mSortColumns.clear();
64 
65  qDeleteAll( mCellStyles );
66  mCellStyles.clear();
67 }
68 
69 bool QgsLayoutTable::writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const
70 {
71  elem.setAttribute( QStringLiteral( "cellMargin" ), QString::number( mCellMargin ) );
72  elem.setAttribute( QStringLiteral( "emptyTableMode" ), QString::number( static_cast< int >( mEmptyTableMode ) ) );
73  elem.setAttribute( QStringLiteral( "emptyTableMessage" ), mEmptyTableMessage );
74  elem.setAttribute( QStringLiteral( "showEmptyRows" ), mShowEmptyRows );
75 
76  QDomElement headerElem = doc.createElement( QStringLiteral( "headerTextFormat" ) );
77  const QDomElement headerTextElem = mHeaderTextFormat.writeXml( doc, context );
78  headerElem.appendChild( headerTextElem );
79  elem.appendChild( headerElem );
80  elem.setAttribute( QStringLiteral( "headerHAlignment" ), QString::number( static_cast< int >( mHeaderHAlignment ) ) );
81  elem.setAttribute( QStringLiteral( "headerMode" ), QString::number( static_cast< int >( mHeaderMode ) ) );
82 
83  QDomElement contentElem = doc.createElement( QStringLiteral( "contentTextFormat" ) );
84  const QDomElement contentTextElem = mContentTextFormat.writeXml( doc, context );
85  contentElem.appendChild( contentTextElem );
86  elem.appendChild( contentElem );
87  elem.setAttribute( QStringLiteral( "gridStrokeWidth" ), QString::number( mGridStrokeWidth ) );
88  elem.setAttribute( QStringLiteral( "gridColor" ), QgsSymbolLayerUtils::encodeColor( mGridColor ) );
89  elem.setAttribute( QStringLiteral( "horizontalGrid" ), mHorizontalGrid );
90  elem.setAttribute( QStringLiteral( "verticalGrid" ), mVerticalGrid );
91  elem.setAttribute( QStringLiteral( "showGrid" ), mShowGrid );
92  elem.setAttribute( QStringLiteral( "backgroundColor" ), QgsSymbolLayerUtils::encodeColor( mBackgroundColor ) );
93  elem.setAttribute( QStringLiteral( "wrapBehavior" ), QString::number( static_cast< int >( mWrapBehavior ) ) );
94 
95  // display columns
96  QDomElement displayColumnsElem = doc.createElement( QStringLiteral( "displayColumns" ) );
97  for ( const QgsLayoutTableColumn &column : qgis::as_const( mColumns ) )
98  {
99  QDomElement columnElem = doc.createElement( QStringLiteral( "column" ) );
100  column.writeXml( columnElem, doc );
101  displayColumnsElem.appendChild( columnElem );
102  }
103  elem.appendChild( displayColumnsElem );
104  // sort columns
105  QDomElement sortColumnsElem = doc.createElement( QStringLiteral( "sortColumns" ) );
106  for ( const QgsLayoutTableColumn &column : qgis::as_const( mSortColumns ) )
107  {
108  QDomElement columnElem = doc.createElement( QStringLiteral( "column" ) );
109  column.writeXml( columnElem, doc );
110  sortColumnsElem.appendChild( columnElem );
111  }
112  elem.appendChild( sortColumnsElem );
113 
114 
115  //cell styles
116  QDomElement stylesElem = doc.createElement( QStringLiteral( "cellStyles" ) );
117  QMap< CellStyleGroup, QString >::const_iterator it = mCellStyleNames.constBegin();
118  for ( ; it != mCellStyleNames.constEnd(); ++it )
119  {
120  QString styleName = it.value();
121  QDomElement styleElem = doc.createElement( styleName );
122  QgsLayoutTableStyle *style = mCellStyles.value( it.key() );
123  if ( style )
124  {
125  style->writeXml( styleElem, doc );
126  stylesElem.appendChild( styleElem );
127  }
128  }
129  elem.appendChild( stylesElem );
130  return true;
131 }
132 
133 bool QgsLayoutTable::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
134 {
135  mEmptyTableMode = QgsLayoutTable::EmptyTableMode( itemElem.attribute( QStringLiteral( "emptyTableMode" ), QStringLiteral( "0" ) ).toInt() );
136  mEmptyTableMessage = itemElem.attribute( QStringLiteral( "emptyTableMessage" ), tr( "No matching records" ) );
137  mShowEmptyRows = itemElem.attribute( QStringLiteral( "showEmptyRows" ), QStringLiteral( "0" ) ).toInt();
138 
139  const QDomElement headerTextFormat = itemElem.firstChildElement( QStringLiteral( "headerTextFormat" ) );
140  if ( !headerTextFormat.isNull() )
141  {
142  QDomNodeList textFormatNodeList = headerTextFormat.elementsByTagName( QStringLiteral( "text-style" ) );
143  QDomElement textFormatElem = textFormatNodeList.at( 0 ).toElement();
144  mHeaderTextFormat.readXml( textFormatElem, context );
145  }
146  else
147  {
148  QFont headerFont;
149  if ( !QgsFontUtils::setFromXmlChildNode( headerFont, itemElem, QStringLiteral( "headerFontProperties" ) ) )
150  {
151  headerFont.fromString( itemElem.attribute( QStringLiteral( "headerFont" ), QString() ) );
152  }
153  QColor headerFontColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "headerFontColor" ), QStringLiteral( "0,0,0,255" ) ) );
155  if ( headerFont.pointSizeF() > 0 )
156  {
157  mHeaderTextFormat.setSize( headerFont.pointSizeF() );
159  }
160  else if ( headerFont.pixelSize() > 0 )
161  {
162  mHeaderTextFormat.setSize( headerFont.pixelSize() );
164  }
166  }
167 
168  mHeaderHAlignment = QgsLayoutTable::HeaderHAlignment( itemElem.attribute( QStringLiteral( "headerHAlignment" ), QStringLiteral( "0" ) ).toInt() );
169  mHeaderMode = QgsLayoutTable::HeaderMode( itemElem.attribute( QStringLiteral( "headerMode" ), QStringLiteral( "0" ) ).toInt() );
170 
171  const QDomElement contentTextFormat = itemElem.firstChildElement( QStringLiteral( "contentTextFormat" ) );
172  if ( !contentTextFormat.isNull() )
173  {
174  QDomNodeList textFormatNodeList = contentTextFormat.elementsByTagName( QStringLiteral( "text-style" ) );
175  QDomElement textFormatElem = textFormatNodeList.at( 0 ).toElement();
176  mContentTextFormat.readXml( textFormatElem, context );
177  }
178  else
179  {
180  QFont contentFont;
181  if ( !QgsFontUtils::setFromXmlChildNode( contentFont, itemElem, QStringLiteral( "contentFontProperties" ) ) )
182  {
183  contentFont.fromString( itemElem.attribute( QStringLiteral( "contentFont" ), QString() ) );
184  }
185  QColor contentFontColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "contentFontColor" ), QStringLiteral( "0,0,0,255" ) ) );
187  if ( contentFont.pointSizeF() > 0 )
188  {
189  mContentTextFormat.setSize( contentFont.pointSizeF() );
191  }
192  else if ( contentFont.pixelSize() > 0 )
193  {
194  mContentTextFormat.setSize( contentFont.pixelSize() );
196  }
198  }
199 
200  mCellMargin = itemElem.attribute( QStringLiteral( "cellMargin" ), QStringLiteral( "1.0" ) ).toDouble();
201  mGridStrokeWidth = itemElem.attribute( QStringLiteral( "gridStrokeWidth" ), QStringLiteral( "0.5" ) ).toDouble();
202  mHorizontalGrid = itemElem.attribute( QStringLiteral( "horizontalGrid" ), QStringLiteral( "1" ) ).toInt();
203  mVerticalGrid = itemElem.attribute( QStringLiteral( "verticalGrid" ), QStringLiteral( "1" ) ).toInt();
204  mShowGrid = itemElem.attribute( QStringLiteral( "showGrid" ), QStringLiteral( "1" ) ).toInt();
205  mGridColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "gridColor" ), QStringLiteral( "0,0,0,255" ) ) );
206  mBackgroundColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "backgroundColor" ), QStringLiteral( "255,255,255,0" ) ) );
207  mWrapBehavior = QgsLayoutTable::WrapBehavior( itemElem.attribute( QStringLiteral( "wrapBehavior" ), QStringLiteral( "0" ) ).toInt() );
208 
209  //restore display column specifications
210  mColumns.clear();
211  QDomNodeList columnsList = itemElem.elementsByTagName( QStringLiteral( "displayColumns" ) );
212  if ( !columnsList.isEmpty() )
213  {
214  QDomElement columnsElem = columnsList.at( 0 ).toElement();
215  QDomNodeList columnEntryList = columnsElem.elementsByTagName( QStringLiteral( "column" ) );
216  for ( int i = 0; i < columnEntryList.size(); ++i )
217  {
218  QDomElement columnElem = columnEntryList.at( i ).toElement();
219  QgsLayoutTableColumn column;
220  column.readXml( columnElem );
221  mColumns.append( column );
222  }
223  }
224  // sort columns
225  mSortColumns.clear();
226  QDomNodeList sortColumnsList = itemElem.elementsByTagName( QStringLiteral( "sortColumns" ) );
227  if ( !sortColumnsList.isEmpty() )
228  {
229  QDomElement columnsElem = sortColumnsList.at( 0 ).toElement();
230  QDomNodeList columnEntryList = columnsElem.elementsByTagName( QStringLiteral( "column" ) );
231  for ( int i = 0; i < columnEntryList.size(); ++i )
232  {
233  QDomElement columnElem = columnEntryList.at( i ).toElement();
234  QgsLayoutTableColumn column;
235  column.readXml( columnElem );
236  mSortColumns.append( column );
237  }
238  }
239  else
240  {
241  // backward compatibility for QGIS < 3.14
242  // copy the display columns if sortByRank > 0 and then, sort them by rank
244  std::copy_if( mColumns.begin(), mColumns.end(), std::back_inserter( mSortColumns ), []( const QgsLayoutTableColumn & col ) {return col.sortByRank() > 0;} );
245  std::sort( mSortColumns.begin(), mSortColumns.end(), []( const QgsLayoutTableColumn & a, const QgsLayoutTableColumn & b ) {return a.sortByRank() < b.sortByRank();} );
247  }
248 
249  //restore cell styles
250  QDomNodeList stylesList = itemElem.elementsByTagName( QStringLiteral( "cellStyles" ) );
251  if ( !stylesList.isEmpty() )
252  {
253  QDomElement stylesElem = stylesList.at( 0 ).toElement();
254 
255  QMap< CellStyleGroup, QString >::const_iterator it = mCellStyleNames.constBegin();
256  for ( ; it != mCellStyleNames.constEnd(); ++it )
257  {
258  QString styleName = it.value();
259  QDomNodeList styleList = stylesElem.elementsByTagName( styleName );
260  if ( !styleList.isEmpty() )
261  {
262  QDomElement styleElem = styleList.at( 0 ).toElement();
263  QgsLayoutTableStyle *style = mCellStyles.value( it.key() );
264  if ( style )
265  style->readXml( styleElem );
266  }
267  }
268  }
269 
270  emit changed();
271  return true;
272 }
273 
275 {
276  return mTableSize;
277 }
278 
280 {
283 }
284 
285 int QgsLayoutTable::rowsVisible( QgsRenderContext &context, double frameHeight, int firstRow, bool includeHeader, bool includeEmptyRows ) const
286 {
287  //calculate header height
288  double headerHeight = 0;
289  if ( includeHeader )
290  {
291  for ( int col = 0; col < mColumns.count(); ++ col )
292  {
293  const QFontMetricsF headerFontMetrics = QgsTextRenderer::fontMetrics( context, textFormatForHeader( col ), QgsTextRenderer::FONT_WORKAROUND_SCALE );
294  //frame has a header
295  headerHeight = std::max( headerHeight, 2 * ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin + headerFontMetrics.ascent() / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) / QgsTextRenderer::FONT_WORKAROUND_SCALE );
296  }
297  }
298  else
299  {
300  //frame has no header text, just the stroke
301  headerHeight = ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
302  }
303 
304  //remaining height available for content rows
305  double contentHeight = frameHeight - headerHeight;
306 
307  double gridHeight = ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
308 
309  int currentRow = firstRow;
310  while ( contentHeight > 0 && currentRow <= mTableContents.count() )
311  {
312  double currentRowHeight = mMaxRowHeightMap.value( currentRow + 1 ) + gridHeight + 2 * mCellMargin;
313  contentHeight -= currentRowHeight;
314  currentRow++;
315  }
316 
317  if ( includeEmptyRows && contentHeight > 0 )
318  {
319  const QFontMetricsF emptyRowContentFontMetrics = QgsTextRenderer::fontMetrics( context, mContentTextFormat, QgsTextRenderer::FONT_WORKAROUND_SCALE );
320  double rowHeight = ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin + emptyRowContentFontMetrics.ascent() / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) / QgsTextRenderer::FONT_WORKAROUND_SCALE;
321  currentRow += std::max( std::floor( contentHeight / rowHeight ), 0.0 );
322  }
323 
324  return currentRow - firstRow - 1;
325 }
326 
327 int QgsLayoutTable::rowsVisible( QgsRenderContext &context, int frameIndex, int firstRow, bool includeEmptyRows ) const
328 {
329  //get frame extent
330  if ( frameIndex >= frameCount() )
331  {
332  return 0;
333  }
334  QRectF frameExtent = frame( frameIndex )->extent();
335 
336  bool includeHeader = false;
339  {
340  includeHeader = true;
341  }
342  return rowsVisible( context, frameExtent.height(), firstRow, includeHeader, includeEmptyRows );
343 }
344 
345 QPair<int, int> QgsLayoutTable::rowRange( QgsRenderContext &context, const int frameIndex ) const
346 {
347  //calculate row height
348  if ( frameIndex >= frameCount() )
349  {
350  //bad frame index
351  return qMakePair( 0, 0 );
352  }
353 
354  //loop through all previous frames to calculate how many rows are visible in each
355  //as the entire height of a frame may not be utilized for content rows
356  int rowsAlreadyShown = 0;
357  for ( int idx = 0; idx < frameIndex; ++idx )
358  {
359  rowsAlreadyShown += rowsVisible( context, idx, rowsAlreadyShown, false );
360  }
361 
362  //using zero based indexes
363  int firstVisible = std::min( rowsAlreadyShown, mTableContents.length() );
364  int possibleRowsVisible = rowsVisible( context, frameIndex, rowsAlreadyShown, false );
365  int lastVisible = std::min( firstVisible + possibleRowsVisible, mTableContents.length() );
366 
367  return qMakePair( firstVisible, lastVisible );
368 }
369 
370 void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &, const int frameIndex )
371 {
372  bool emptyTable = mTableContents.length() == 0;
373  if ( emptyTable && mEmptyTableMode == QgsLayoutTable::HideTable )
374  {
375  //empty table set to hide table mode, so don't draw anything
376  return;
377  }
378 
379  if ( !mLayout->renderContext().isPreviewRender() )
380  {
381  //exporting composition, so force an attribute refresh
382  //we do this in case vector layer has changed via an external source (e.g., another database user)
384  }
385 
386  const bool prevTextFormatScaleFlag = context.renderContext().testFlag( QgsRenderContext::ApplyScalingWorkaroundForTextRendering );
388 
389  //calculate which rows to show in this frame
390  QPair< int, int > rowsToShow = rowRange( context.renderContext(), frameIndex );
391 
392  double gridSizeX = mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0;
393  double gridSizeY = mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0;
394  double cellHeaderHeight = mMaxRowHeightMap[0] + 2 * mCellMargin;
396  QRectF cell;
397 
398  //calculate whether a header is required
399  bool drawHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && frameIndex < 1 )
401  //calculate whether drawing table contents is required
402  bool drawContents = !( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage );
403 
404  int numberRowsToDraw = rowsToShow.second - rowsToShow.first;
405  int numberEmptyRows = 0;
406  if ( drawContents && mShowEmptyRows )
407  {
408  numberRowsToDraw = rowsVisible( context.renderContext(), frameIndex, rowsToShow.first, true );
409  numberEmptyRows = numberRowsToDraw - rowsToShow.second + rowsToShow.first;
410  }
411  bool mergeCells = false;
412  if ( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage )
413  {
414  //draw a merged row for the empty table message
415  numberRowsToDraw++;
416  rowsToShow.second++;
417  mergeCells = true;
418  }
419 
420  QPainter *p = context.renderContext().painter();
421  QgsScopedQPainterState painterState( p );
422  // painter is scaled to dots, so scale back to layout units
423  p->scale( context.renderContext().scaleFactor(), context.renderContext().scaleFactor() );
424 
425  //draw the text
426  p->setPen( Qt::SolidLine );
427 
428  double currentX = gridSizeX;
429  double currentY = gridSizeY;
430  if ( drawHeader )
431  {
432  //draw the headers
433  int col = 0;
434  for ( const QgsLayoutTableColumn &column : qgis::as_const( mColumns ) )
435  {
436  std::unique_ptr< QgsExpressionContextScope > headerCellScope = qgis::make_unique< QgsExpressionContextScope >();
437  headerCellScope->setVariable( QStringLiteral( "column_number" ), col + 1, true );
438  QgsExpressionContextScopePopper popper( context.renderContext().expressionContext(), headerCellScope.release() );
439 
440  const QgsTextFormat headerFormat = textFormatForHeader( col );
441  //draw background
442  p->save();
443  p->setPen( Qt::NoPen );
444  p->setBrush( backgroundColor( -1, col ) );
445  p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, cellHeaderHeight ) );
446  p->restore();
447 
448  currentX += mCellMargin;
449 
450  cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], cellHeaderHeight );
451 
452  //calculate alignment of header
454  switch ( mHeaderHAlignment )
455  {
456  case FollowColumn:
457  headerAlign = QgsTextRenderer::convertQtHAlignment( column.hAlignment() );
458  break;
459  case HeaderLeft:
460  headerAlign = QgsTextRenderer::AlignLeft;
461  break;
462  case HeaderCenter:
463  headerAlign = QgsTextRenderer::AlignCenter;
464  break;
465  case HeaderRight:
466  headerAlign = QgsTextRenderer::AlignRight;
467  break;
468  }
469 
470  const QRectF textCell = QRectF( currentX, currentY + mCellMargin, mMaxColumnWidthMap[col], cellHeaderHeight - 2 * mCellMargin );
471 
472  // disable text clipping to target text rectangle, because we manually clip to the full cell bounds below
473  // and it's ok if text overlaps into the margin (e.g. extenders or italicized text)
474  QStringList str = column.heading().split( '\n' );
475  if ( ( mWrapBehavior != TruncateText || column.width() > 0 ) && textRequiresWrapping( context.renderContext(), column.heading(), column.width(), headerFormat ) )
476  {
477  str = wrappedText( context.renderContext(), column.heading(), column.width(), headerFormat );
478  }
479 
480  // scale to dots
481  {
483  QgsTextRenderer::drawText( QRectF( textCell.left() * context.renderContext().scaleFactor(),
484  textCell.top() * context.renderContext().scaleFactor(),
485  textCell.width() * context.renderContext().scaleFactor(),
486  textCell.height() * context.renderContext().scaleFactor() ), 0,
487  headerAlign, str, context.renderContext(), headerFormat, true, QgsTextRenderer::AlignVCenter );
488  }
489 
490  currentX += mMaxColumnWidthMap[ col ];
491  currentX += mCellMargin;
492  currentX += gridSizeX;
493  col++;
494  }
495 
496  currentY += cellHeaderHeight;
497  currentY += gridSizeY;
498  }
499 
500  //now draw the body cells
501  int rowsDrawn = 0;
502  if ( drawContents )
503  {
504  //draw the attribute values
505  for ( int row = rowsToShow.first; row < rowsToShow.second; ++row )
506  {
507  rowsDrawn++;
508  currentX = gridSizeX;
509  int col = 0;
510 
511  //calculate row height
512  double rowHeight = mMaxRowHeightMap[row + 1] + 2 * mCellMargin;
513 
514  for ( const QgsLayoutTableColumn &column : qgis::as_const( mColumns ) )
515  {
516  const QRectF fullCell( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, rowHeight );
517  //draw background
518  p->save();
519  p->setPen( Qt::NoPen );
520  p->setBrush( backgroundColor( row, col ) );
521  p->drawRect( fullCell );
522  p->restore();
523 
524  // currentY = gridSize;
525  currentX += mCellMargin;
526 
527  QVariant cellContents = mTableContents.at( row ).at( col );
528  QStringList str = cellContents.toString().split( '\n' );
529 
530  QgsTextFormat cellFormat = textFormatForCell( row, col );
532  cellFormat.updateDataDefinedProperties( context.renderContext() );
533 
534  // disable text clipping to target text rectangle, because we manually clip to the full cell bounds below
535  // and it's ok if text overlaps into the margin (e.g. extenders or italicized text)
536  if ( ( mWrapBehavior != TruncateText || column.width() > 0 ) && textRequiresWrapping( context.renderContext(), cellContents.toString(), column.width(), cellFormat ) )
537  {
538  str = wrappedText( context.renderContext(), cellContents.toString(), column.width(), cellFormat );
539  }
540 
541  p->save();
542  p->setClipRect( fullCell );
543  const QRectF textCell = QRectF( currentX, currentY + mCellMargin, mMaxColumnWidthMap[col], rowHeight - 2 * mCellMargin );
544 
545  const QgsConditionalStyle style = conditionalCellStyle( row, col );
546  QColor foreColor = cellFormat.color();
547  if ( style.textColor().isValid() )
548  foreColor = style.textColor();
549 
550  cellFormat.setColor( foreColor );
551 
552  // scale to dots
553  {
555  QgsTextRenderer::drawText( QRectF( textCell.left() * context.renderContext().scaleFactor(),
556  textCell.top() * context.renderContext().scaleFactor(),
557  textCell.width() * context.renderContext().scaleFactor(),
558  textCell.height() * context.renderContext().scaleFactor() ), 0,
559  QgsTextRenderer::convertQtHAlignment( horizontalAlignmentForCell( row, col ) ), str, context.renderContext(), cellFormat, true,
561  }
562  p->restore();
563 
564  currentX += mMaxColumnWidthMap[ col ];
565  currentX += mCellMargin;
566  currentX += gridSizeX;
567  col++;
568  }
569  currentY += rowHeight;
570  currentY += gridSizeY;
571  }
572  }
573 
574  if ( numberRowsToDraw > rowsDrawn )
575  {
576  p->save();
577  p->setPen( Qt::NoPen );
578 
579  //draw background of empty rows
580  for ( int row = rowsDrawn; row < numberRowsToDraw; ++row )
581  {
582  currentX = gridSizeX;
583  int col = 0;
584 
585  if ( mergeCells )
586  {
587  p->setBrush( backgroundColor( row + 10000, 0 ) );
588  p->drawRect( QRectF( gridSizeX, currentY, mTableSize.width() - 2 * gridSizeX, cellBodyHeightForEmptyRows ) );
589  }
590  else
591  {
592  for ( const QgsLayoutTableColumn &column : qgis::as_const( mColumns ) )
593  {
594  Q_UNUSED( column )
595 
596  //draw background
597 
598  //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
599  p->setBrush( backgroundColor( row + 10000, col ) );
600  p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, cellBodyHeightForEmptyRows ) );
601 
602  // currentY = gridSize;
603  currentX += mMaxColumnWidthMap[ col ] + 2 * mCellMargin;
604  currentX += gridSizeX;
605  col++;
606  }
607  }
608  currentY += cellBodyHeightForEmptyRows + gridSizeY;
609  }
610  p->restore();
611  }
612 
613  //and the borders
614  if ( mShowGrid )
615  {
616  QPen gridPen;
617  gridPen.setWidthF( mGridStrokeWidth );
618  gridPen.setColor( mGridColor );
619  gridPen.setJoinStyle( Qt::MiterJoin );
620  p->setPen( gridPen );
621  if ( mHorizontalGrid )
622  {
623  drawHorizontalGridLines( context, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader );
624  }
625  if ( mVerticalGrid )
626  {
627  drawVerticalGridLines( context, mMaxColumnWidthMap, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader, mergeCells );
628  }
629  }
630 
631  //special case - no records and table is set to ShowMessage mode
632  if ( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage )
633  {
634  double messageX = gridSizeX + mCellMargin;
635  double messageY = gridSizeY + ( drawHeader ? cellHeaderHeight + gridSizeY : 0 );
636  cell = QRectF( messageX, messageY, mTableSize.width() - messageX, cellBodyHeightForEmptyRows );
637 
638  // scale to dots
639  {
641  QgsTextRenderer::drawText( QRectF( cell.left() * context.renderContext().scaleFactor(),
642  cell.top() * context.renderContext().scaleFactor(),
643  cell.width() * context.renderContext().scaleFactor(),
644  cell.height() * context.renderContext().scaleFactor() ), 0,
646  }
647  }
648 
650 }
651 
652 void QgsLayoutTable::setCellMargin( const double margin )
653 {
654  if ( qgsDoubleNear( margin, mCellMargin ) )
655  {
656  return;
657  }
658 
659  mCellMargin = margin;
660 
661  //since spacing has changed, we need to recalculate the table size
663 
664  emit changed();
665 }
666 
668 {
669  if ( mode == mEmptyTableMode )
670  {
671  return;
672  }
673 
674  mEmptyTableMode = mode;
675 
676  //since appearance has changed, we need to recalculate the table size
678 
679  emit changed();
680 }
681 
682 void QgsLayoutTable::setEmptyTableMessage( const QString &message )
683 {
684  if ( message == mEmptyTableMessage )
685  {
686  return;
687  }
688 
689  mEmptyTableMessage = message;
690 
691  //since message has changed, we need to recalculate the table size
693 
694  emit changed();
695 }
696 
697 void QgsLayoutTable::setShowEmptyRows( const bool showEmpty )
698 {
699  if ( showEmpty == mShowEmptyRows )
700  {
701  return;
702  }
703 
704  mShowEmptyRows = showEmpty;
705  update();
706  emit changed();
707 }
708 
709 void QgsLayoutTable::setHeaderFont( const QFont &font )
710 {
711  mHeaderTextFormat.setFont( font );
712  if ( font.pointSizeF() > 0 )
713  {
714  mHeaderTextFormat.setSize( font.pointSizeF() );
716  }
717  else if ( font.pixelSize() > 0 )
718  {
719  mHeaderTextFormat.setSize( font.pixelSize() );
721  }
722 
723  //since font attributes have changed, we need to recalculate the table size
725 
726  emit changed();
727 }
728 
730 {
731  return mHeaderTextFormat.toQFont();
732 }
733 
734 void QgsLayoutTable::setHeaderFontColor( const QColor &color )
735 {
736  if ( color == mHeaderTextFormat.color() )
737  {
738  return;
739  }
740 
741  mHeaderTextFormat.setColor( color );
742  update();
743 
744  emit changed();
745 }
746 
748 {
749  return mHeaderTextFormat.color();
750 }
751 
753 {
754  mHeaderTextFormat = format;
755 
756  //since font attributes have changed, we need to recalculate the table size
758 
759  emit changed();
760 }
761 
763 {
764  return mHeaderTextFormat;
765 }
766 
768 {
769  if ( alignment == mHeaderHAlignment )
770  {
771  return;
772  }
773 
774  mHeaderHAlignment = alignment;
775  update();
776 
777  emit changed();
778 }
779 
781 {
782  if ( mode == mHeaderMode )
783  {
784  return;
785  }
786 
787  mHeaderMode = mode;
789 
790  emit changed();
791 }
792 
793 void QgsLayoutTable::setContentFont( const QFont &font )
794 {
795  mContentTextFormat.setFont( font );
796  if ( font.pointSizeF() > 0 )
797  {
798  mContentTextFormat.setSize( font.pointSizeF() );
800  }
801  else if ( font.pixelSize() > 0 )
802  {
803  mContentTextFormat.setSize( font.pixelSize() );
805  }
806 
807  //since font attributes have changed, we need to recalculate the table size
809 
810  emit changed();
811 }
812 
814 {
815  return mContentTextFormat.toQFont();
816 }
817 
818 void QgsLayoutTable::setContentFontColor( const QColor &color )
819 {
820  if ( color == mContentTextFormat.color() )
821  {
822  return;
823  }
824 
825  mContentTextFormat.setColor( color );
826  update();
827 
828  emit changed();
829 }
830 
832 {
833  return mContentTextFormat.color();
834 }
835 
837 {
838  mContentTextFormat = format;
839 
840  //since spacing has changed, we need to recalculate the table size
842 
843  emit changed();
844 }
845 
847 {
848  return mContentTextFormat;
849 }
850 
851 void QgsLayoutTable::setShowGrid( const bool showGrid )
852 {
853  if ( showGrid == mShowGrid )
854  {
855  return;
856  }
857 
859  //since grid spacing has changed, we need to recalculate the table size
861 
862  emit changed();
863 }
864 
865 void QgsLayoutTable::setGridStrokeWidth( const double width )
866 {
867  if ( qgsDoubleNear( width, mGridStrokeWidth ) )
868  {
869  return;
870  }
871 
872  mGridStrokeWidth = width;
873  //since grid spacing has changed, we need to recalculate the table size
875 
876  emit changed();
877 }
878 
879 void QgsLayoutTable::setGridColor( const QColor &color )
880 {
881  if ( color == mGridColor )
882  {
883  return;
884  }
885 
886  mGridColor = color;
887  update();
888 
889  emit changed();
890 }
891 
892 void QgsLayoutTable::setHorizontalGrid( const bool horizontalGrid )
893 {
895  {
896  return;
897  }
898 
900  //since grid spacing has changed, we need to recalculate the table size
902 
903  emit changed();
904 }
905 
906 void QgsLayoutTable::setVerticalGrid( const bool verticalGrid )
907 {
908  if ( verticalGrid == mVerticalGrid )
909  {
910  return;
911  }
912 
914  //since grid spacing has changed, we need to recalculate the table size
916 
917  emit changed();
918 }
919 
920 void QgsLayoutTable::setBackgroundColor( const QColor &color )
921 {
922  if ( color == mBackgroundColor )
923  {
924  return;
925  }
926 
927  mBackgroundColor = color;
928  update();
929 
930  emit changed();
931 }
932 
934 {
935  if ( behavior == mWrapBehavior )
936  {
937  return;
938  }
939 
940  mWrapBehavior = behavior;
942 
943  emit changed();
944 }
945 
947 {
948  //remove existing columns
949  mColumns = columns;
950 
951  // backward compatibility
952  // test if sorting is provided with the columns and call setSortColumns in such case
953  QgsLayoutTableSortColumns newSortColumns;
955  std::copy_if( mColumns.begin(), mColumns.end(), std::back_inserter( newSortColumns ), []( const QgsLayoutTableColumn & col ) {return col.sortByRank() > 0;} );
956  if ( !newSortColumns.isEmpty() )
957  {
958  std::sort( newSortColumns.begin(), newSortColumns.end(), []( const QgsLayoutTableColumn & a, const QgsLayoutTableColumn & b ) {return a.sortByRank() < b.sortByRank();} );
959  setSortColumns( newSortColumns );
960  }
962 }
963 
965 {
967 }
968 
970 {
971  if ( mCellStyles.contains( group ) )
972  delete mCellStyles.take( group );
973 
974  mCellStyles.insert( group, new QgsLayoutTableStyle( style ) );
975 }
976 
978 {
979  if ( !mCellStyles.contains( group ) )
980  return nullptr;
981 
982  return mCellStyles.value( group );
983 }
984 
985 QMap<int, QString> QgsLayoutTable::headerLabels() const
986 {
987  QMap<int, QString> headers;
988 
989  int i = 0;
990  for ( const QgsLayoutTableColumn &col : qgis::as_const( mColumns ) )
991  {
992  headers.insert( i, col.heading() );
993  i++;
994  }
995  return headers;
996 }
997 
999 {
1000  std::unique_ptr< QgsExpressionContextScope > cellScope = qgis::make_unique< QgsExpressionContextScope >();
1001  cellScope->setVariable( QStringLiteral( "row_number" ), row + 1, true );
1002  cellScope->setVariable( QStringLiteral( "column_number" ), column + 1, true );
1003  return cellScope.release();
1004 }
1005 
1007 {
1008  return QgsConditionalStyle();
1009 }
1010 
1011 QSizeF QgsLayoutTable::fixedFrameSize( const int frameIndex ) const
1012 {
1013  Q_UNUSED( frameIndex )
1014  return QSizeF( mTableSize.width(), 0 );
1015 }
1016 
1017 QSizeF QgsLayoutTable::minFrameSize( const int frameIndex ) const
1018 {
1021 
1022  double height = 0;
1025  {
1026  //header required, force frame to be high enough for header
1027  for ( int col = 0; col < mColumns.size(); ++ col )
1028  {
1030  }
1031  }
1032  return QSizeF( 0, height );
1033 }
1034 
1036 {
1037  mMaxColumnWidthMap.clear();
1038  mMaxRowHeightMap.clear();
1039  mTableContents.clear();
1040 
1041  //get new contents
1042  if ( !getTableContents( mTableContents ) )
1043  {
1044  return;
1045  }
1046 }
1047 
1049 {
1050  mTableSize = QSizeF( totalWidth(), totalHeight() );
1052 }
1053 
1054 void QgsLayoutTable::initStyles()
1055 {
1056  mCellStyles.insert( OddColumns, new QgsLayoutTableStyle() );
1057  mCellStyles.insert( EvenColumns, new QgsLayoutTableStyle() );
1058  mCellStyles.insert( OddRows, new QgsLayoutTableStyle() );
1059  mCellStyles.insert( EvenRows, new QgsLayoutTableStyle() );
1060  mCellStyles.insert( FirstColumn, new QgsLayoutTableStyle() );
1061  mCellStyles.insert( LastColumn, new QgsLayoutTableStyle() );
1062  mCellStyles.insert( HeaderRow, new QgsLayoutTableStyle() );
1063  mCellStyles.insert( FirstRow, new QgsLayoutTableStyle() );
1064  mCellStyles.insert( LastRow, new QgsLayoutTableStyle() );
1065 
1066  mCellStyleNames.insert( OddColumns, QStringLiteral( "oddColumns" ) );
1067  mCellStyleNames.insert( EvenColumns, QStringLiteral( "evenColumns" ) );
1068  mCellStyleNames.insert( OddRows, QStringLiteral( "oddRows" ) );
1069  mCellStyleNames.insert( EvenRows, QStringLiteral( "evenRows" ) );
1070  mCellStyleNames.insert( FirstColumn, QStringLiteral( "firstColumn" ) );
1071  mCellStyleNames.insert( LastColumn, QStringLiteral( "lastColumn" ) );
1072  mCellStyleNames.insert( HeaderRow, QStringLiteral( "headerRow" ) );
1073  mCellStyleNames.insert( FirstRow, QStringLiteral( "firstRow" ) );
1074  mCellStyleNames.insert( LastRow, QStringLiteral( "lastRow" ) );
1075 }
1076 
1078 {
1079  mMaxColumnWidthMap.clear();
1080 
1081  //total number of cells (rows + 1 for header)
1082  int cols = mColumns.count();
1083  int cells = cols * ( mTableContents.count() + 1 );
1084  QVector< double > widths( cells );
1085 
1086  double currentCellTextWidth;
1087 
1090 
1091  //first, go through all the column headers and calculate the sizes
1092  int i = 0;
1093  for ( const QgsLayoutTableColumn &col : qgis::as_const( mColumns ) )
1094  {
1095  if ( col.width() > 0 )
1096  {
1097  //column has manually specified width
1098  widths[i] = col.width();
1099  }
1100  else if ( mHeaderMode != QgsLayoutTable::NoHeaders )
1101  {
1102  std::unique_ptr< QgsExpressionContextScope > headerCellScope = qgis::make_unique< QgsExpressionContextScope >();
1103  headerCellScope->setVariable( QStringLiteral( "column_number" ), i + 1, true );
1104  QgsExpressionContextScopePopper popper( context.expressionContext(), headerCellScope.release() );
1105 
1106  //column width set to automatic, so check content size
1107  const QStringList multiLineSplit = col.heading().split( '\n' );
1108  currentCellTextWidth = QgsTextRenderer::textWidth( context, textFormatForHeader( i ), multiLineSplit ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
1109  widths[i] = currentCellTextWidth;
1110  }
1111  else
1112  {
1113  widths[i] = 0.0;
1114  }
1115  i++;
1116  }
1117 
1118  //next, go through all the table contents and calculate the sizes
1119  QgsLayoutTableContents::const_iterator rowIt = mTableContents.constBegin();
1120  int row = 1;
1121  for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
1122  {
1123  QgsLayoutTableRow::const_iterator colIt = rowIt->constBegin();
1124  int col = 0;
1125  for ( ; colIt != rowIt->constEnd(); ++colIt )
1126  {
1127  if ( mColumns.at( col ).width() <= 0 )
1128  {
1129  //column width set to automatic, so check content size
1130  const QStringList multiLineSplit = ( *colIt ).toString().split( '\n' );
1131 
1132  QgsTextFormat cellFormat = textFormatForCell( row - 1, col );
1133  QgsExpressionContextScopePopper popper( context.expressionContext(), scopeForCell( row - 1, col ) );
1134  cellFormat.updateDataDefinedProperties( context );
1135 
1136  currentCellTextWidth = QgsTextRenderer::textWidth( context, cellFormat, multiLineSplit ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
1137  widths[ row * cols + col ] = currentCellTextWidth;
1138  }
1139  else
1140  {
1141  widths[ row * cols + col ] = 0;
1142  }
1143 
1144  col++;
1145  }
1146  row++;
1147  }
1148 
1149  //calculate maximum
1150  for ( int col = 0; col < cols; ++col )
1151  {
1152  double maxColWidth = 0;
1153  for ( int row = 0; row < mTableContents.count() + 1; ++row )
1154  {
1155  maxColWidth = std::max( widths[ row * cols + col ], maxColWidth );
1156  }
1157  mMaxColumnWidthMap.insert( col, maxColWidth );
1158  }
1159 
1160  return true;
1161 }
1162 
1164 {
1165  mMaxRowHeightMap.clear();
1166 
1167  //total number of cells (rows + 1 for header)
1168  int cols = mColumns.count();
1169  int cells = cols * ( mTableContents.count() + 1 );
1170  QVector< double > heights( cells );
1171 
1174 
1175  //first, go through all the column headers and calculate the sizes
1176  int i = 0;
1177  for ( const QgsLayoutTableColumn &col : qgis::as_const( mColumns ) )
1178  {
1179  std::unique_ptr< QgsExpressionContextScope > headerCellScope = qgis::make_unique< QgsExpressionContextScope >();
1180  headerCellScope->setVariable( QStringLiteral( "column_number" ), i + 1, true );
1181  QgsExpressionContextScopePopper popper( context.expressionContext(), headerCellScope.release() );
1182 
1183  const QgsTextFormat cellFormat = textFormatForHeader( i );
1185  //height
1187  {
1188  heights[i] = 0;
1189  }
1190  else if ( textRequiresWrapping( context, col.heading(), mColumns.at( i ).width(), cellFormat ) )
1191  {
1192  //contents too wide for cell, need to wrap
1193  heights[i] = QgsTextRenderer::textHeight( context, cellFormat, wrappedText( context, col.heading(), mColumns.at( i ).width(), cellFormat ), QgsTextRenderer::Rect )
1195  - headerDescentMm;
1196  }
1197  else
1198  {
1199  heights[i] = QgsTextRenderer::textHeight( context, cellFormat, QStringList() << col.heading(), QgsTextRenderer::Rect ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters )
1200  - headerDescentMm;
1201  }
1202  i++;
1203  }
1204 
1205  //next, go through all the table contents and calculate the sizes
1206  QgsLayoutTableContents::const_iterator rowIt = mTableContents.constBegin();
1207  int row = 1;
1208  for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
1209  {
1210  QgsLayoutTableRow::const_iterator colIt = rowIt->constBegin();
1211  int i = 0;
1212  for ( ; colIt != rowIt->constEnd(); ++colIt )
1213  {
1214  QgsTextFormat cellFormat = textFormatForCell( row - 1, i );
1215  QgsExpressionContextScopePopper popper( context.expressionContext(), scopeForCell( row - 1, i ) );
1216  cellFormat.updateDataDefinedProperties( context );
1218 
1219  if ( textRequiresWrapping( context, ( *colIt ).toString(), mColumns.at( i ).width(), cellFormat ) )
1220  {
1221  //contents too wide for cell, need to wrap
1222  heights[ row * cols + i ] = QgsTextRenderer::textHeight( context, cellFormat, wrappedText( context, ( *colIt ).toString(), mColumns.at( i ).width(), cellFormat ), QgsTextRenderer::Rect ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) - contentDescentMm;
1223  }
1224  else
1225  {
1226  heights[ row * cols + i ] = QgsTextRenderer::textHeight( context, cellFormat, QStringList() << ( *colIt ).toString().split( '\n' ), QgsTextRenderer::Rect ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) - contentDescentMm;
1227  }
1228 
1229  i++;
1230  }
1231  row++;
1232  }
1233 
1234  //calculate maximum
1235  for ( int row = 0; row < mTableContents.count() + 1; ++row )
1236  {
1237  double maxRowHeight = 0;
1238  for ( int col = 0; col < cols; ++col )
1239  {
1240  maxRowHeight = std::max( heights[ row * cols + col ], maxRowHeight );
1241  }
1242  mMaxRowHeightMap.insert( row, maxRowHeight );
1243  }
1244 
1245  return true;
1246 }
1247 
1249 {
1250  //check how much space each column needs
1251  if ( !calculateMaxColumnWidths() )
1252  {
1253  return 0;
1254  }
1255 
1256  //adapt frame to total width
1257  double totalWidth = 0;
1258  QMap<int, double>::const_iterator maxColWidthIt = mMaxColumnWidthMap.constBegin();
1259  for ( ; maxColWidthIt != mMaxColumnWidthMap.constEnd(); ++maxColWidthIt )
1260  {
1261  totalWidth += maxColWidthIt.value();
1262  }
1263  totalWidth += ( 2 * mMaxColumnWidthMap.size() * mCellMargin );
1264  totalWidth += ( mMaxColumnWidthMap.size() + 1 ) * ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1265 
1266  return totalWidth;
1267 }
1268 
1270 {
1271  //check how much space each row needs
1272  if ( !calculateMaxRowHeights() )
1273  {
1274  return 0;
1275  }
1276 
1277  double height = 0;
1278 
1281 
1282  //loop through all existing frames to calculate how many rows are visible in each
1283  //as the entire height of a frame may not be utilized for content rows
1284  int rowsAlreadyShown = 0;
1285  int numberExistingFrames = frameCount();
1286  int rowsVisibleInLastFrame = 0;
1287  double heightOfLastFrame = 0;
1288  for ( int idx = 0; idx < numberExistingFrames; ++idx )
1289  {
1290  bool hasHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && idx == 0 )
1292  heightOfLastFrame = frame( idx )->rect().height();
1293  rowsVisibleInLastFrame = rowsVisible( context, heightOfLastFrame, rowsAlreadyShown, hasHeader, false );
1294  rowsAlreadyShown += rowsVisibleInLastFrame;
1295  height += heightOfLastFrame;
1296  if ( rowsAlreadyShown >= mTableContents.length() )
1297  {
1298  //shown entire contents of table, nothing remaining
1299  return height;
1300  }
1301  }
1302 
1303  //calculate how many rows left to show
1304  int remainingRows = mTableContents.length() - rowsAlreadyShown;
1305 
1306  if ( remainingRows <= 0 )
1307  {
1308  //no remaining rows
1309  return height;
1310  }
1311 
1313  {
1314  QgsLayoutItemPage *page = mLayout->pageCollection()->page( mLayout->pageCollection()->pageCount() - 1 );
1315  if ( page )
1316  heightOfLastFrame = page->sizeWithUnits().height();
1317  }
1318 
1319  bool hasHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && numberExistingFrames < 1 )
1321 
1322  int numberFramesMissing = 0;
1323  while ( remainingRows > 0 )
1324  {
1325  numberFramesMissing++;
1326 
1327  rowsVisibleInLastFrame = rowsVisible( context, heightOfLastFrame, rowsAlreadyShown, hasHeader, false );
1328  if ( rowsVisibleInLastFrame < 1 )
1329  {
1330  //if no rows are visible in the last frame, calculation of missing frames
1331  //is impossible. So just return total height of existing frames
1332  return height;
1333  }
1334 
1335  rowsAlreadyShown += rowsVisibleInLastFrame;
1336  remainingRows = mTableContents.length() - rowsAlreadyShown;
1337  }
1338 
1339  //rows remain unshown -- how many extra frames would we need to complete the table?
1340  //assume all added frames are same size as final frame
1341  height += heightOfLastFrame * numberFramesMissing;
1342  return height;
1343 }
1344 
1345 void QgsLayoutTable::drawHorizontalGridLines( QgsLayoutItemRenderContext &context, int firstRow, int lastRow, bool drawHeaderLines ) const
1346 {
1347  //horizontal lines
1348  if ( lastRow - firstRow < 1 && !drawHeaderLines )
1349  {
1350  return;
1351  }
1352 
1353  QPainter *painter = context.renderContext().painter();
1354 
1356  double halfGridStrokeWidth = ( mShowGrid ? mGridStrokeWidth : 0 ) / 2.0;
1357  double currentY = 0;
1358  currentY = halfGridStrokeWidth;
1359  if ( drawHeaderLines )
1360  {
1361  painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
1362  currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
1363  currentY += mMaxRowHeightMap[0] + 2 * mCellMargin;
1364  }
1365  for ( int row = firstRow; row < lastRow; ++row )
1366  {
1367  painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
1368  currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
1369  double rowHeight = row < mTableContents.count() ? mMaxRowHeightMap[row + 1] : cellBodyHeightForEmptyRows;
1370  currentY += ( rowHeight + 2 * mCellMargin );
1371  }
1372  painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
1373 }
1374 
1375 bool QgsLayoutTable::textRequiresWrapping( QgsRenderContext &context, const QString &text, double columnWidth, const QgsTextFormat &format ) const
1376 {
1377  if ( qgsDoubleNear( columnWidth, 0.0 ) || mWrapBehavior != WrapText )
1378  return false;
1379 
1380  const QStringList multiLineSplit = text.split( '\n' );
1381  const double currentTextWidth = QgsTextRenderer::textWidth( context, format, multiLineSplit ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
1382  return currentTextWidth > columnWidth;
1383 }
1384 
1385 QStringList QgsLayoutTable::wrappedText( QgsRenderContext &context, const QString &value, double columnWidth, const QgsTextFormat &format ) const
1386 {
1387  QStringList lines = value.split( '\n' );
1388  QStringList outLines;
1389  const auto constLines = lines;
1390  for ( const QString &line : constLines )
1391  {
1392  if ( textRequiresWrapping( context, line, columnWidth, format ) )
1393  {
1394  //first step is to identify words which must be on their own line (too long to fit)
1395  QStringList words = line.split( ' ' );
1396  QStringList linesToProcess;
1397  QString wordsInCurrentLine;
1398  const auto constWords = words;
1399  for ( const QString &word : constWords )
1400  {
1401  if ( textRequiresWrapping( context, word, columnWidth, format ) )
1402  {
1403  //too long to fit
1404  if ( !wordsInCurrentLine.isEmpty() )
1405  linesToProcess << wordsInCurrentLine;
1406  wordsInCurrentLine.clear();
1407  linesToProcess << word;
1408  }
1409  else
1410  {
1411  if ( !wordsInCurrentLine.isEmpty() )
1412  wordsInCurrentLine.append( ' ' );
1413  wordsInCurrentLine.append( word );
1414  }
1415  }
1416  if ( !wordsInCurrentLine.isEmpty() )
1417  linesToProcess << wordsInCurrentLine;
1418 
1419  const auto constLinesToProcess = linesToProcess;
1420  for ( const QString &line : constLinesToProcess )
1421  {
1422  QString remainingText = line;
1423  int lastPos = remainingText.lastIndexOf( ' ' );
1424  while ( lastPos > -1 )
1425  {
1426  //check if remaining text is short enough to go in one line
1427  if ( !textRequiresWrapping( context, remainingText, columnWidth, format ) )
1428  {
1429  break;
1430  }
1431 
1432  if ( !textRequiresWrapping( context, remainingText.left( lastPos ), columnWidth, format ) )
1433  {
1434  outLines << remainingText.left( lastPos );
1435  remainingText = remainingText.mid( lastPos + 1 );
1436  lastPos = 0;
1437  }
1438  lastPos = remainingText.lastIndexOf( ' ', lastPos - 1 );
1439  }
1440  outLines << remainingText;
1441  }
1442  }
1443  else
1444  {
1445  outLines << line;
1446  }
1447  }
1448 
1449  return outLines;
1450 }
1451 
1452 QColor QgsLayoutTable::backgroundColor( int row, int column ) const
1453 {
1454  QColor color = mBackgroundColor;
1455  if ( QgsLayoutTableStyle *style = mCellStyles.value( OddColumns ) )
1456  if ( style->enabled && column % 2 == 0 )
1457  color = style->cellBackgroundColor;
1458  if ( QgsLayoutTableStyle *style = mCellStyles.value( EvenColumns ) )
1459  if ( style->enabled && column % 2 == 1 )
1460  color = style->cellBackgroundColor;
1461  if ( QgsLayoutTableStyle *style = mCellStyles.value( OddRows ) )
1462  if ( style->enabled && row % 2 == 0 )
1463  color = style->cellBackgroundColor;
1464  if ( QgsLayoutTableStyle *style = mCellStyles.value( EvenRows ) )
1465  if ( style->enabled && row % 2 == 1 )
1466  color = style->cellBackgroundColor;
1467  if ( QgsLayoutTableStyle *style = mCellStyles.value( FirstColumn ) )
1468  if ( style->enabled && column == 0 )
1469  color = style->cellBackgroundColor;
1470  if ( QgsLayoutTableStyle *style = mCellStyles.value( LastColumn ) )
1471  if ( style->enabled && column == mColumns.count() - 1 )
1472  color = style->cellBackgroundColor;
1473  if ( QgsLayoutTableStyle *style = mCellStyles.value( HeaderRow ) )
1474  if ( style->enabled && row == -1 )
1475  color = style->cellBackgroundColor;
1476  if ( QgsLayoutTableStyle *style = mCellStyles.value( FirstRow ) )
1477  if ( style->enabled && row == 0 )
1478  color = style->cellBackgroundColor;
1479  if ( QgsLayoutTableStyle *style = mCellStyles.value( LastRow ) )
1480  if ( style->enabled && row == mTableContents.count() - 1 )
1481  color = style->cellBackgroundColor;
1482 
1483  if ( row >= 0 )
1484  {
1485  QgsConditionalStyle conditionalStyle = conditionalCellStyle( row, column );
1486  if ( conditionalStyle.backgroundColor().isValid() )
1487  color = conditionalStyle.backgroundColor();
1488  }
1489 
1490  return color;
1491 }
1492 
1493 void QgsLayoutTable::drawVerticalGridLines( QgsLayoutItemRenderContext &context, const QMap<int, double> &maxWidthMap, int firstRow, int lastRow, bool hasHeader, bool mergeCells ) const
1494 {
1495  //vertical lines
1496  if ( lastRow - firstRow < 1 && !hasHeader )
1497  {
1498  return;
1499  }
1500 
1501  QPainter *painter = context.renderContext().painter();
1502 
1503  //calculate height of table within frame
1504  double tableHeight = 0;
1505  if ( hasHeader )
1506  {
1507  tableHeight += ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2 + mMaxRowHeightMap[0];
1508  }
1509  tableHeight += ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
1510  double headerHeight = tableHeight;
1511 
1513  for ( int row = firstRow; row < lastRow; ++row )
1514  {
1515  double rowHeight = row < mTableContents.count() ? mMaxRowHeightMap[row + 1] : cellBodyHeightForEmptyRows;
1516  tableHeight += rowHeight + ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2;
1517  }
1518 
1519  double halfGridStrokeWidth = ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 ) / 2.0;
1520  double currentX = halfGridStrokeWidth;
1521  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, tableHeight - halfGridStrokeWidth ) );
1522  currentX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1523  QMap<int, double>::const_iterator maxColWidthIt = maxWidthMap.constBegin();
1524  int col = 1;
1525  for ( ; maxColWidthIt != maxWidthMap.constEnd(); ++maxColWidthIt )
1526  {
1527  currentX += ( maxColWidthIt.value() + 2 * mCellMargin );
1528  if ( col == maxWidthMap.size() || !mergeCells )
1529  {
1530  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, tableHeight - halfGridStrokeWidth ) );
1531  }
1532  else if ( hasHeader )
1533  {
1534  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, headerHeight - halfGridStrokeWidth ) );
1535  }
1536 
1537  currentX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1538  col++;
1539  }
1540 }
1541 
1543 {
1545 
1546  //force recalculation of frame rects, so that they are set to the correct
1547  //fixed and minimum frame sizes
1549 }
1550 
1552 {
1553  return ( contents.indexOf( row ) >= 0 );
1554 }
1555 
1557 {
1558  return mContentTextFormat;
1559 }
1560 
1562 {
1563  return mHeaderTextFormat;
1564 }
1565 
1566 Qt::Alignment QgsLayoutTable::horizontalAlignmentForCell( int, int column ) const
1567 {
1568  return mColumns.value( column ).hAlignment();
1569 }
1570 
1571 Qt::Alignment QgsLayoutTable::verticalAlignmentForCell( int, int column ) const
1572 {
1573  return mColumns.value( column ).vAlignment();
1574 }
1575 
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:50
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.
void setFlag(Flag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
@ ApplyScalingWorkaroundForTextRendering
Whether a scaling workaround designed to stablise the rendering of small font sizes (or for painters ...
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
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:172
@ RenderPixels
Pixels.
Definition: qgsunittypes.h:170
@ RenderMillimeters
Millimeters.
Definition: qgsunittypes.h:168
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 Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:798
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:797
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:316