QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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 
18 #include "qgslayouttable.h"
19 #include "qgslayout.h"
20 #include "qgslayoututils.h"
21 #include "qgslayouttablecolumn.h"
22 #include "qgssymbollayerutils.h"
23 #include "qgslayoutframe.h"
24 #include "qgsfontutils.h"
25 #include "qgssettings.h"
27 
28 //
29 // QgsLayoutTableStyle
30 //
31 
32 bool QgsLayoutTableStyle::writeXml( QDomElement &styleElem, QDomDocument &doc ) const
33 {
34  Q_UNUSED( doc )
35  styleElem.setAttribute( QStringLiteral( "cellBackgroundColor" ), QgsSymbolLayerUtils::encodeColor( cellBackgroundColor ) );
36  styleElem.setAttribute( QStringLiteral( "enabled" ), enabled );
37  return true;
38 }
39 
40 bool QgsLayoutTableStyle::readXml( const QDomElement &styleElem )
41 {
42  cellBackgroundColor = QgsSymbolLayerUtils::decodeColor( styleElem.attribute( QStringLiteral( "cellBackgroundColor" ), QStringLiteral( "255,255,255,255" ) ) );
43  enabled = ( styleElem.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ) != QLatin1String( "0" ) );
44  return true;
45 }
46 
47 
48 //
49 // QgsLayoutTable
50 //
51 
53  : QgsLayoutMultiFrame( layout )
54 {
55  //get default composer font from settings
56  QgsSettings settings;
57  QString defaultFontString = settings.value( QStringLiteral( "LayoutDesigner/defaultFont" ), QVariant(), QgsSettings::Gui ).toString();
58  if ( !defaultFontString.isEmpty() )
59  {
60  mHeaderFont.setFamily( defaultFontString );
61  mContentFont.setFamily( defaultFontString );
62  }
63 
64  initStyles();
65 }
66 
68 {
69  qDeleteAll( mColumns );
70  mColumns.clear();
71 
72  qDeleteAll( mCellStyles );
73  mCellStyles.clear();
74 }
75 
76 bool QgsLayoutTable::writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext & ) const
77 {
78  elem.setAttribute( QStringLiteral( "cellMargin" ), QString::number( mCellMargin ) );
79  elem.setAttribute( QStringLiteral( "emptyTableMode" ), QString::number( static_cast< int >( mEmptyTableMode ) ) );
80  elem.setAttribute( QStringLiteral( "emptyTableMessage" ), mEmptyTableMessage );
81  elem.setAttribute( QStringLiteral( "showEmptyRows" ), mShowEmptyRows );
82  elem.appendChild( QgsFontUtils::toXmlElement( mHeaderFont, doc, QStringLiteral( "headerFontProperties" ) ) );
83  elem.setAttribute( QStringLiteral( "headerFontColor" ), QgsSymbolLayerUtils::encodeColor( mHeaderFontColor ) );
84  elem.setAttribute( QStringLiteral( "headerHAlignment" ), QString::number( static_cast< int >( mHeaderHAlignment ) ) );
85  elem.setAttribute( QStringLiteral( "headerMode" ), QString::number( static_cast< int >( mHeaderMode ) ) );
86  elem.appendChild( QgsFontUtils::toXmlElement( mContentFont, doc, QStringLiteral( "contentFontProperties" ) ) );
87  elem.setAttribute( QStringLiteral( "contentFontColor" ), QgsSymbolLayerUtils::encodeColor( mContentFontColor ) );
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  //columns
97  QDomElement displayColumnsElem = doc.createElement( QStringLiteral( "displayColumns" ) );
98  for ( QgsLayoutTableColumn *column : qgis::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 
106  //cell styles
107  QDomElement stylesElem = doc.createElement( QStringLiteral( "cellStyles" ) );
108  QMap< CellStyleGroup, QString >::const_iterator it = mCellStyleNames.constBegin();
109  for ( ; it != mCellStyleNames.constEnd(); ++it )
110  {
111  QString styleName = it.value();
112  QDomElement styleElem = doc.createElement( styleName );
113  QgsLayoutTableStyle *style = mCellStyles.value( it.key() );
114  if ( style )
115  {
116  style->writeXml( styleElem, doc );
117  stylesElem.appendChild( styleElem );
118  }
119  }
120  elem.appendChild( stylesElem );
121  return true;
122 }
123 
124 bool QgsLayoutTable::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext & )
125 {
126  mEmptyTableMode = QgsLayoutTable::EmptyTableMode( itemElem.attribute( QStringLiteral( "emptyTableMode" ), QStringLiteral( "0" ) ).toInt() );
127  mEmptyTableMessage = itemElem.attribute( QStringLiteral( "emptyTableMessage" ), tr( "No matching records" ) );
128  mShowEmptyRows = itemElem.attribute( QStringLiteral( "showEmptyRows" ), QStringLiteral( "0" ) ).toInt();
129  if ( !QgsFontUtils::setFromXmlChildNode( mHeaderFont, itemElem, QStringLiteral( "headerFontProperties" ) ) )
130  {
131  mHeaderFont.fromString( itemElem.attribute( QStringLiteral( "headerFont" ), QString() ) );
132  }
133  mHeaderFontColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "headerFontColor" ), QStringLiteral( "0,0,0,255" ) ) );
134  mHeaderHAlignment = QgsLayoutTable::HeaderHAlignment( itemElem.attribute( QStringLiteral( "headerHAlignment" ), QStringLiteral( "0" ) ).toInt() );
135  mHeaderMode = QgsLayoutTable::HeaderMode( itemElem.attribute( QStringLiteral( "headerMode" ), QStringLiteral( "0" ) ).toInt() );
136  if ( !QgsFontUtils::setFromXmlChildNode( mContentFont, itemElem, QStringLiteral( "contentFontProperties" ) ) )
137  {
138  mContentFont.fromString( itemElem.attribute( QStringLiteral( "contentFont" ), QString() ) );
139  }
140  mContentFontColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "contentFontColor" ), QStringLiteral( "0,0,0,255" ) ) );
141  mCellMargin = itemElem.attribute( QStringLiteral( "cellMargin" ), QStringLiteral( "1.0" ) ).toDouble();
142  mGridStrokeWidth = itemElem.attribute( QStringLiteral( "gridStrokeWidth" ), QStringLiteral( "0.5" ) ).toDouble();
143  mHorizontalGrid = itemElem.attribute( QStringLiteral( "horizontalGrid" ), QStringLiteral( "1" ) ).toInt();
144  mVerticalGrid = itemElem.attribute( QStringLiteral( "verticalGrid" ), QStringLiteral( "1" ) ).toInt();
145  mShowGrid = itemElem.attribute( QStringLiteral( "showGrid" ), QStringLiteral( "1" ) ).toInt();
146  mGridColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "gridColor" ), QStringLiteral( "0,0,0,255" ) ) );
147  mBackgroundColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "backgroundColor" ), QStringLiteral( "255,255,255,0" ) ) );
148  mWrapBehavior = QgsLayoutTable::WrapBehavior( itemElem.attribute( QStringLiteral( "wrapBehavior" ), QStringLiteral( "0" ) ).toInt() );
149 
150  //restore column specifications
151  qDeleteAll( mColumns );
152  mColumns.clear();
153  QDomNodeList columnsList = itemElem.elementsByTagName( QStringLiteral( "displayColumns" ) );
154  if ( !columnsList.isEmpty() )
155  {
156  QDomElement columnsElem = columnsList.at( 0 ).toElement();
157  QDomNodeList columnEntryList = columnsElem.elementsByTagName( QStringLiteral( "column" ) );
158  for ( int i = 0; i < columnEntryList.size(); ++i )
159  {
160  QDomElement columnElem = columnEntryList.at( i ).toElement();
162  column->readXml( columnElem );
163  mColumns.append( column );
164  }
165  }
166 
167  //restore cell styles
168  QDomNodeList stylesList = itemElem.elementsByTagName( QStringLiteral( "cellStyles" ) );
169  if ( !stylesList.isEmpty() )
170  {
171  QDomElement stylesElem = stylesList.at( 0 ).toElement();
172 
173  QMap< CellStyleGroup, QString >::const_iterator it = mCellStyleNames.constBegin();
174  for ( ; it != mCellStyleNames.constEnd(); ++it )
175  {
176  QString styleName = it.value();
177  QDomNodeList styleList = stylesElem.elementsByTagName( styleName );
178  if ( !styleList.isEmpty() )
179  {
180  QDomElement styleElem = styleList.at( 0 ).toElement();
181  QgsLayoutTableStyle *style = mCellStyles.value( it.key() );
182  if ( style )
183  style->readXml( styleElem );
184  }
185  }
186  }
187 
188  emit changed();
189  return true;
190 }
191 
193 {
194  return mTableSize;
195 }
196 
198 {
201 }
202 
203 int QgsLayoutTable::rowsVisible( double frameHeight, int firstRow, bool includeHeader, bool includeEmptyRows ) const
204 {
205  //calculate header height
206  double headerHeight = 0;
207  if ( includeHeader )
208  {
209  //frame has a header
211  }
212  else
213  {
214  //frame has no header text, just the stroke
215  headerHeight = ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
216  }
217 
218  //remaining height available for content rows
219  double contentHeight = frameHeight - headerHeight;
220 
221  double gridHeight = ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
222 
223  int currentRow = firstRow;
224  while ( contentHeight > 0 && currentRow <= mTableContents.count() )
225  {
226  double currentRowHeight = mMaxRowHeightMap.value( currentRow + 1 ) + gridHeight + 2 * mCellMargin;
227  contentHeight -= currentRowHeight;
228  currentRow++;
229  }
230 
231  if ( includeEmptyRows && contentHeight > 0 )
232  {
234  currentRow += std::max( std::floor( contentHeight / rowHeight ), 0.0 );
235  }
236 
237  return currentRow - firstRow - 1;
238 }
239 
240 int QgsLayoutTable::rowsVisible( int frameIndex, int firstRow, bool includeEmptyRows ) const
241 {
242  //get frame extent
243  if ( frameIndex >= frameCount() )
244  {
245  return 0;
246  }
247  QRectF frameExtent = frame( frameIndex )->extent();
248 
249  bool includeHeader = false;
250  if ( ( mHeaderMode == QgsLayoutTable::FirstFrame && frameIndex < 1 )
252  {
253  includeHeader = true;
254  }
255  return rowsVisible( frameExtent.height(), firstRow, includeHeader, includeEmptyRows );
256 }
257 
258 QPair<int, int> QgsLayoutTable::rowRange( const int frameIndex ) const
259 {
260  //calculate row height
261  if ( frameIndex >= frameCount() )
262  {
263  //bad frame index
264  return qMakePair( 0, 0 );
265  }
266 
267  //loop through all previous frames to calculate how many rows are visible in each
268  //as the entire height of a frame may not be utilized for content rows
269  int rowsAlreadyShown = 0;
270  for ( int idx = 0; idx < frameIndex; ++idx )
271  {
272  rowsAlreadyShown += rowsVisible( idx, rowsAlreadyShown, false );
273  }
274 
275  //using zero based indexes
276  int firstVisible = std::min( rowsAlreadyShown, mTableContents.length() );
277  int possibleRowsVisible = rowsVisible( frameIndex, rowsAlreadyShown, false );
278  int lastVisible = std::min( firstVisible + possibleRowsVisible, mTableContents.length() );
279 
280  return qMakePair( firstVisible, lastVisible );
281 }
282 
283 void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &, const int frameIndex )
284 {
285  bool emptyTable = mTableContents.length() == 0;
286  if ( emptyTable && mEmptyTableMode == QgsLayoutTable::HideTable )
287  {
288  //empty table set to hide table mode, so don't draw anything
289  return;
290  }
291 
292  if ( !mLayout->renderContext().isPreviewRender() )
293  {
294  //exporting composition, so force an attribute refresh
295  //we do this in case vector layer has changed via an external source (e.g., another database user)
297  }
298 
299  //calculate which rows to show in this frame
300  QPair< int, int > rowsToShow = rowRange( frameIndex );
301 
302  double gridSizeX = mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0;
303  double gridSizeY = mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0;
304  double cellHeaderHeight = QgsLayoutUtils::fontAscentMM( mHeaderFont ) + 2 * mCellMargin;
305  double cellBodyHeight = QgsLayoutUtils::fontAscentMM( mContentFont ) + 2 * mCellMargin;
306  QRectF cell;
307 
308  //calculate whether a header is required
309  bool drawHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && frameIndex < 1 )
311  //calculate whether drawing table contents is required
312  bool drawContents = !( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage );
313 
314  int numberRowsToDraw = rowsToShow.second - rowsToShow.first;
315  int numberEmptyRows = 0;
316  if ( drawContents && mShowEmptyRows )
317  {
318  numberRowsToDraw = rowsVisible( frameIndex, rowsToShow.first, true );
319  numberEmptyRows = numberRowsToDraw - rowsToShow.second + rowsToShow.first;
320  }
321  bool mergeCells = false;
322  if ( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage )
323  {
324  //draw a merged row for the empty table message
325  numberRowsToDraw++;
326  rowsToShow.second++;
327  mergeCells = true;
328  }
329 
330  QPainter *p = context.renderContext().painter();
331  p->save();
332  // painter is scaled to dots, so scale back to layout units
333  p->scale( context.renderContext().scaleFactor(), context.renderContext().scaleFactor() );
334 
335  //draw the text
336  p->setPen( Qt::SolidLine );
337 
338  double currentX = gridSizeX;
339  double currentY = gridSizeY;
340  if ( drawHeader )
341  {
342  //draw the headers
343  int col = 0;
344  for ( const QgsLayoutTableColumn *column : qgis::as_const( mColumns ) )
345  {
346  //draw background
347  p->save();
348  p->setPen( Qt::NoPen );
349  p->setBrush( backgroundColor( -1, col ) );
350  p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, cellHeaderHeight ) );
351  p->restore();
352 
353  currentX += mCellMargin;
354 
355  Qt::TextFlag textFlag = static_cast< Qt::TextFlag >( 0 );
356  if ( column->width() <= 0 )
357  {
358  //automatic column width, so we use the Qt::TextDontClip flag when drawing contents, as this works nicer for italicised text
359  //which may slightly exceed the calculated width
360  //if column size was manually set then we do apply text clipping, to avoid painting text outside of columns width
361  textFlag = Qt::TextDontClip;
362  }
363 
364  cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], cellHeaderHeight );
365 
366  //calculate alignment of header
367  Qt::AlignmentFlag headerAlign = Qt::AlignLeft;
368  switch ( mHeaderHAlignment )
369  {
370  case FollowColumn:
371  headerAlign = column->hAlignment();
372  break;
373  case HeaderLeft:
374  headerAlign = Qt::AlignLeft;
375  break;
376  case HeaderCenter:
377  headerAlign = Qt::AlignHCenter;
378  break;
379  case HeaderRight:
380  headerAlign = Qt::AlignRight;
381  break;
382  }
383 
384  QgsLayoutUtils::drawText( p, cell, column->heading(), mHeaderFont, mHeaderFontColor, headerAlign, Qt::AlignVCenter, textFlag );
385 
386  currentX += mMaxColumnWidthMap[ col ];
387  currentX += mCellMargin;
388  currentX += gridSizeX;
389  col++;
390  }
391 
392  currentY += cellHeaderHeight;
393  currentY += gridSizeY;
394  }
395 
396  //now draw the body cells
397  int rowsDrawn = 0;
398  if ( drawContents )
399  {
400  //draw the attribute values
401  for ( int row = rowsToShow.first; row < rowsToShow.second; ++row )
402  {
403  rowsDrawn++;
404  currentX = gridSizeX;
405  int col = 0;
406 
407  //calculate row height
408  double rowHeight = mMaxRowHeightMap[row + 1] + 2 * mCellMargin;
409 
410 
411  for ( const QgsLayoutTableColumn *column : qgis::as_const( mColumns ) )
412  {
413  const QRectF fullCell( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, rowHeight );
414  //draw background
415  p->save();
416  p->setPen( Qt::NoPen );
417  p->setBrush( backgroundColor( row, col ) );
418  p->drawRect( fullCell );
419  p->restore();
420 
421  // currentY = gridSize;
422  currentX += mCellMargin;
423 
424  QVariant cellContents = mTableContents.at( row ).at( col );
425  QString str = cellContents.toString();
426 
427  // disable text clipping to target text rectangle, because we manually clip to the full cell bounds below
428  // and it's ok if text overlaps into the margin (e.g. extenders or italicized text)
429  Qt::TextFlag textFlag = static_cast< Qt::TextFlag >( Qt::TextDontClip );
430  if ( ( mWrapBehavior != TruncateText || column->width() > 0 ) && textRequiresWrapping( str, column->width(), mContentFont ) )
431  {
432  str = wrappedText( str, column->width(), mContentFont );
433  }
434 
435  p->save();
436  p->setClipRect( fullCell );
437  const QRectF textCell = QRectF( currentX, currentY + mCellMargin, mMaxColumnWidthMap[col], rowHeight - 2 * mCellMargin );
438 
439  const QgsConditionalStyle style = conditionalCellStyle( row, col );
440  QColor foreColor = mContentFontColor;
441  if ( style.textColor().isValid() )
442  foreColor = style.textColor();
443 
444  QgsLayoutUtils::drawText( p, textCell, str, mContentFont, foreColor, column->hAlignment(), column->vAlignment(), textFlag );
445  p->restore();
446 
447  currentX += mMaxColumnWidthMap[ col ];
448  currentX += mCellMargin;
449  currentX += gridSizeX;
450  col++;
451  }
452  currentY += rowHeight;
453  currentY += gridSizeY;
454  }
455  }
456 
457  if ( numberRowsToDraw > rowsDrawn )
458  {
459  p->save();
460  p->setPen( Qt::NoPen );
461 
462  //draw background of empty rows
463  for ( int row = rowsDrawn; row < numberRowsToDraw; ++row )
464  {
465  currentX = gridSizeX;
466  int col = 0;
467 
468  if ( mergeCells )
469  {
470  p->setBrush( backgroundColor( row + 10000, 0 ) );
471  p->drawRect( QRectF( gridSizeX, currentY, mTableSize.width() - 2 * gridSizeX, cellBodyHeight ) );
472  }
473  else
474  {
475  for ( QgsLayoutTableColumn *column : qgis::as_const( mColumns ) )
476  {
477  Q_UNUSED( column )
478 
479  //draw background
480 
481  //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
482  p->setBrush( backgroundColor( row + 10000, col ) );
483  p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, cellBodyHeight ) );
484 
485  // currentY = gridSize;
486  currentX += mMaxColumnWidthMap[ col ] + 2 * mCellMargin;
487  currentX += gridSizeX;
488  col++;
489  }
490  }
491  currentY += cellBodyHeight + gridSizeY;
492  }
493  p->restore();
494  }
495 
496  //and the borders
497  if ( mShowGrid )
498  {
499  QPen gridPen;
500  gridPen.setWidthF( mGridStrokeWidth );
501  gridPen.setColor( mGridColor );
502  gridPen.setJoinStyle( Qt::MiterJoin );
503  p->setPen( gridPen );
504  if ( mHorizontalGrid )
505  {
506  drawHorizontalGridLines( p, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader );
507  }
508  if ( mVerticalGrid )
509  {
510  drawVerticalGridLines( p, mMaxColumnWidthMap, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader, mergeCells );
511  }
512  }
513 
514  //special case - no records and table is set to ShowMessage mode
515  if ( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage )
516  {
517  double messageX = gridSizeX + mCellMargin;
518  double messageY = gridSizeY + ( drawHeader ? cellHeaderHeight + gridSizeY : 0 );
519  cell = QRectF( messageX, messageY, mTableSize.width() - messageX, cellBodyHeight );
520  QgsLayoutUtils::drawText( p, cell, mEmptyTableMessage, mContentFont, mContentFontColor, Qt::AlignHCenter, Qt::AlignVCenter, static_cast< Qt::TextFlag >( 0 ) );
521  }
522 
523  p->restore();
524 
525 }
526 
527 void QgsLayoutTable::setCellMargin( const double margin )
528 {
529  if ( qgsDoubleNear( margin, mCellMargin ) )
530  {
531  return;
532  }
533 
534  mCellMargin = margin;
535 
536  //since spacing has changed, we need to recalculate the table size
538 
539  emit changed();
540 }
541 
543 {
544  if ( mode == mEmptyTableMode )
545  {
546  return;
547  }
548 
549  mEmptyTableMode = mode;
550 
551  //since appearance has changed, we need to recalculate the table size
553 
554  emit changed();
555 }
556 
557 void QgsLayoutTable::setEmptyTableMessage( const QString &message )
558 {
559  if ( message == mEmptyTableMessage )
560  {
561  return;
562  }
563 
564  mEmptyTableMessage = message;
565 
566  //since message has changed, we need to recalculate the table size
568 
569  emit changed();
570 }
571 
572 void QgsLayoutTable::setShowEmptyRows( const bool showEmpty )
573 {
574  if ( showEmpty == mShowEmptyRows )
575  {
576  return;
577  }
578 
579  mShowEmptyRows = showEmpty;
580  update();
581  emit changed();
582 }
583 
584 void QgsLayoutTable::setHeaderFont( const QFont &font )
585 {
586  if ( font == mHeaderFont )
587  {
588  return;
589  }
590 
591  mHeaderFont = font;
592  //since font attributes have changed, we need to recalculate the table size
594 
595  emit changed();
596 }
597 
598 void QgsLayoutTable::setHeaderFontColor( const QColor &color )
599 {
600  if ( color == mHeaderFontColor )
601  {
602  return;
603  }
604 
605  mHeaderFontColor = color;
606  update();
607 
608  emit changed();
609 }
610 
612 {
613  if ( alignment == mHeaderHAlignment )
614  {
615  return;
616  }
617 
618  mHeaderHAlignment = alignment;
619  update();
620 
621  emit changed();
622 }
623 
625 {
626  if ( mode == mHeaderMode )
627  {
628  return;
629  }
630 
631  mHeaderMode = mode;
633 
634  emit changed();
635 }
636 
637 void QgsLayoutTable::setContentFont( const QFont &font )
638 {
639  if ( font == mContentFont )
640  {
641  return;
642  }
643 
644  mContentFont = font;
645  //since font attributes have changed, we need to recalculate the table size
647 
648  emit changed();
649 }
650 
651 void QgsLayoutTable::setContentFontColor( const QColor &color )
652 {
653  if ( color == mContentFontColor )
654  {
655  return;
656  }
657 
658  mContentFontColor = color;
659  update();
660 
661  emit changed();
662 }
663 
665 {
666  if ( showGrid == mShowGrid )
667  {
668  return;
669  }
670 
672  //since grid spacing has changed, we need to recalculate the table size
674 
675  emit changed();
676 }
677 
678 void QgsLayoutTable::setGridStrokeWidth( const double width )
679 {
680  if ( qgsDoubleNear( width, mGridStrokeWidth ) )
681  {
682  return;
683  }
684 
685  mGridStrokeWidth = width;
686  //since grid spacing has changed, we need to recalculate the table size
688 
689  emit changed();
690 }
691 
692 void QgsLayoutTable::setGridColor( const QColor &color )
693 {
694  if ( color == mGridColor )
695  {
696  return;
697  }
698 
699  mGridColor = color;
700  update();
701 
702  emit changed();
703 }
704 
706 {
707  if ( horizontalGrid == mHorizontalGrid )
708  {
709  return;
710  }
711 
713  //since grid spacing has changed, we need to recalculate the table size
715 
716  emit changed();
717 }
718 
720 {
721  if ( verticalGrid == mVerticalGrid )
722  {
723  return;
724  }
725 
727  //since grid spacing has changed, we need to recalculate the table size
729 
730  emit changed();
731 }
732 
733 void QgsLayoutTable::setBackgroundColor( const QColor &color )
734 {
735  if ( color == mBackgroundColor )
736  {
737  return;
738  }
739 
740  mBackgroundColor = color;
741  update();
742 
743  emit changed();
744 }
745 
747 {
748  if ( behavior == mWrapBehavior )
749  {
750  return;
751  }
752 
753  mWrapBehavior = behavior;
755 
756  emit changed();
757 }
758 
760 {
761  //remove existing columns
762  qDeleteAll( mColumns );
763  mColumns.clear();
764 
765  mColumns.append( columns );
766 }
767 
769 {
770  if ( mCellStyles.contains( group ) )
771  delete mCellStyles.take( group );
772 
773  mCellStyles.insert( group, new QgsLayoutTableStyle( style ) );
774 }
775 
777 {
778  if ( !mCellStyles.contains( group ) )
779  return nullptr;
780 
781  return mCellStyles.value( group );
782 }
783 
784 QMap<int, QString> QgsLayoutTable::headerLabels() const
785 {
786  QMap<int, QString> headers;
787 
788  QgsLayoutTableColumns::const_iterator columnIt = mColumns.constBegin();
789  int col = 0;
790  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
791  {
792  headers.insert( col, ( *columnIt )->heading() );
793  col++;
794  }
795  return headers;
796 }
797 
799 {
800  return QgsConditionalStyle();
801 }
802 
804 {
805  Q_UNUSED( frameIndex )
806  return QSizeF( mTableSize.width(), 0 );
807 }
808 
809 QSizeF QgsLayoutTable::minFrameSize( const int frameIndex ) const
810 {
811  double height = 0;
812  if ( ( mHeaderMode == QgsLayoutTable::FirstFrame && frameIndex < 1 )
814  {
815  //header required, force frame to be high enough for header
817  }
818  return QSizeF( 0, height );
819 }
820 
822 {
823  mMaxColumnWidthMap.clear();
824  mMaxRowHeightMap.clear();
825  mTableContents.clear();
826 
827  //get new contents
829  {
830  return;
831  }
832 }
833 
835 {
836  mTableSize = QSizeF( totalWidth(), totalHeight() );
838 }
839 
840 void QgsLayoutTable::initStyles()
841 {
842  mCellStyles.insert( OddColumns, new QgsLayoutTableStyle() );
844  mCellStyles.insert( OddRows, new QgsLayoutTableStyle() );
845  mCellStyles.insert( EvenRows, new QgsLayoutTableStyle() );
847  mCellStyles.insert( LastColumn, new QgsLayoutTableStyle() );
848  mCellStyles.insert( HeaderRow, new QgsLayoutTableStyle() );
849  mCellStyles.insert( FirstRow, new QgsLayoutTableStyle() );
850  mCellStyles.insert( LastRow, new QgsLayoutTableStyle() );
851 
852  mCellStyleNames.insert( OddColumns, QStringLiteral( "oddColumns" ) );
853  mCellStyleNames.insert( EvenColumns, QStringLiteral( "evenColumns" ) );
854  mCellStyleNames.insert( OddRows, QStringLiteral( "oddRows" ) );
855  mCellStyleNames.insert( EvenRows, QStringLiteral( "evenRows" ) );
856  mCellStyleNames.insert( FirstColumn, QStringLiteral( "firstColumn" ) );
857  mCellStyleNames.insert( LastColumn, QStringLiteral( "lastColumn" ) );
858  mCellStyleNames.insert( HeaderRow, QStringLiteral( "headerRow" ) );
859  mCellStyleNames.insert( FirstRow, QStringLiteral( "firstRow" ) );
860  mCellStyleNames.insert( LastRow, QStringLiteral( "lastRow" ) );
861 }
862 
864 {
865  mMaxColumnWidthMap.clear();
866 
867  //total number of cells (rows + 1 for header)
868  int cols = mColumns.count();
869  int cells = cols * ( mTableContents.count() + 1 );
870  QVector< double > widths( cells );
871 
872  //first, go through all the column headers and calculate the sizes
873  QgsLayoutTableColumns::const_iterator columnIt = mColumns.constBegin();
874  int col = 0;
875  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
876  {
877  if ( ( *columnIt )->width() > 0 )
878  {
879  //column has manually specified width
880  widths[col] = ( *columnIt )->width();
881  }
883  {
884  widths[col] = QgsLayoutUtils::textWidthMM( mHeaderFont, ( *columnIt )->heading() );
885  }
886  else
887  {
888  widths[col] = 0.0;
889  }
890  col++;
891  }
892 
893  //next, go through all the table contents and calculate the sizes
894  QgsLayoutTableContents::const_iterator rowIt = mTableContents.constBegin();
895  double currentCellTextWidth;
896  int row = 1;
897  for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
898  {
899  QgsLayoutTableRow::const_iterator colIt = rowIt->constBegin();
900  col = 0;
901  for ( ; colIt != rowIt->constEnd(); ++colIt )
902  {
903  if ( mColumns.at( col )->width() <= 0 )
904  {
905  //column width set to automatic, so check content size
906  QStringList multiLineSplit = ( *colIt ).toString().split( '\n' );
907  currentCellTextWidth = 0;
908  const auto constMultiLineSplit = multiLineSplit;
909  for ( const QString &line : constMultiLineSplit )
910  {
911  currentCellTextWidth = std::max( currentCellTextWidth, QgsLayoutUtils::textWidthMM( mContentFont, line ) );
912  }
913  widths[ row * cols + col ] = currentCellTextWidth;
914  }
915  else
916  {
917  widths[ row * cols + col ] = 0;
918  }
919 
920  col++;
921  }
922  row++;
923  }
924 
925  //calculate maximum
926  for ( int col = 0; col < cols; ++col )
927  {
928  double maxColWidth = 0;
929  for ( int row = 0; row < mTableContents.count() + 1; ++row )
930  {
931  maxColWidth = std::max( widths[ row * cols + col ], maxColWidth );
932  }
933  mMaxColumnWidthMap.insert( col, maxColWidth );
934  }
935 
936  return true;
937 }
938 
940 {
941  mMaxRowHeightMap.clear();
942 
943  //total number of cells (rows + 1 for header)
944  int cols = mColumns.count();
945  int cells = cols * ( mTableContents.count() + 1 );
946  QVector< double > heights( cells );
947 
948  //first, go through all the column headers and calculate the sizes
949  QgsLayoutTableColumns::const_iterator columnIt = mColumns.constBegin();
950  int col = 0;
951  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
952  {
953  //height
954  heights[col] = mHeaderMode != QgsLayoutTable::NoHeaders ? QgsLayoutUtils::textHeightMM( mHeaderFont, ( *columnIt )->heading() ) : 0;
955  col++;
956  }
957 
958  //next, go through all the table contents and calculate the sizes
959  QgsLayoutTableContents::const_iterator rowIt = mTableContents.constBegin();
960  int row = 1;
961  for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
962  {
963  QgsLayoutTableRow::const_iterator colIt = rowIt->constBegin();
964  col = 0;
965  for ( ; colIt != rowIt->constEnd(); ++colIt )
966  {
967  if ( textRequiresWrapping( ( *colIt ).toString(), mColumns.at( col )->width(), mContentFont ) )
968  {
969  //contents too wide for cell, need to wrap
970  heights[ row * cols + col ] = QgsLayoutUtils::textHeightMM( mContentFont, wrappedText( ( *colIt ).toString(), mColumns.at( col )->width(), mContentFont ) );
971  }
972  else
973  {
974  heights[ row * cols + col ] = QgsLayoutUtils::textHeightMM( mContentFont, ( *colIt ).toString() );
975  }
976 
977  col++;
978  }
979  row++;
980  }
981 
982  //calculate maximum
983  for ( int row = 0; row < mTableContents.count() + 1; ++row )
984  {
985  double maxRowHeight = 0;
986  for ( int col = 0; col < cols; ++col )
987  {
988  maxRowHeight = std::max( heights[ row * cols + col ], maxRowHeight );
989  }
990  mMaxRowHeightMap.insert( row, maxRowHeight );
991  }
992 
993  return true;
994 }
995 
997 {
998  //check how much space each column needs
999  if ( !calculateMaxColumnWidths() )
1000  {
1001  return 0;
1002  }
1003 
1004  //adapt frame to total width
1005  double totalWidth = 0;
1006  QMap<int, double>::const_iterator maxColWidthIt = mMaxColumnWidthMap.constBegin();
1007  for ( ; maxColWidthIt != mMaxColumnWidthMap.constEnd(); ++maxColWidthIt )
1008  {
1009  totalWidth += maxColWidthIt.value();
1010  }
1011  totalWidth += ( 2 * mMaxColumnWidthMap.size() * mCellMargin );
1012  totalWidth += ( mMaxColumnWidthMap.size() + 1 ) * ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1013 
1014  return totalWidth;
1015 }
1016 
1018 {
1019  //check how much space each row needs
1020  if ( !calculateMaxRowHeights() )
1021  {
1022  return 0;
1023  }
1024 
1025  double height = 0;
1026 
1027  //loop through all existing frames to calculate how many rows are visible in each
1028  //as the entire height of a frame may not be utilized for content rows
1029  int rowsAlreadyShown = 0;
1030  int numberExistingFrames = frameCount();
1031  int rowsVisibleInLastFrame = 0;
1032  double heightOfLastFrame = 0;
1033  for ( int idx = 0; idx < numberExistingFrames; ++idx )
1034  {
1035  bool hasHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && idx == 0 )
1037  heightOfLastFrame = frame( idx )->rect().height();
1038  rowsVisibleInLastFrame = rowsVisible( heightOfLastFrame, rowsAlreadyShown, hasHeader, false );
1039  rowsAlreadyShown += rowsVisibleInLastFrame;
1040  height += heightOfLastFrame;
1041  if ( rowsAlreadyShown >= mTableContents.length() )
1042  {
1043  //shown entire contents of table, nothing remaining
1044  return height;
1045  }
1046  }
1047 
1048  //calculate how many rows left to show
1049  int remainingRows = mTableContents.length() - rowsAlreadyShown;
1050 
1051  if ( remainingRows <= 0 )
1052  {
1053  //no remaining rows
1054  return height;
1055  }
1056 
1058  {
1059  QgsLayoutItemPage *page = mLayout->pageCollection()->page( mLayout->pageCollection()->pageCount() - 1 );
1060  if ( page )
1061  heightOfLastFrame = page->sizeWithUnits().height();
1062  }
1063 
1064  bool hasHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && numberExistingFrames < 1 )
1066 
1067  int numberFramesMissing = 0;
1068  while ( remainingRows > 0 )
1069  {
1070  numberFramesMissing++;
1071 
1072  rowsVisibleInLastFrame = rowsVisible( heightOfLastFrame, rowsAlreadyShown, hasHeader, false );
1073  if ( rowsVisibleInLastFrame < 1 )
1074  {
1075  //if no rows are visible in the last frame, calculation of missing frames
1076  //is impossible. So just return total height of existing frames
1077  return height;
1078  }
1079 
1080  rowsAlreadyShown += rowsVisibleInLastFrame;
1081  remainingRows = mTableContents.length() - rowsAlreadyShown;
1082  }
1083 
1084  //rows remain unshown -- how many extra frames would we need to complete the table?
1085  //assume all added frames are same size as final frame
1086  height += heightOfLastFrame * numberFramesMissing;
1087  return height;
1088 }
1089 
1090 void QgsLayoutTable::drawHorizontalGridLines( QPainter *painter, int firstRow, int lastRow, bool drawHeaderLines ) const
1091 {
1092  //horizontal lines
1093  if ( lastRow - firstRow < 1 && !drawHeaderLines )
1094  {
1095  return;
1096  }
1097 
1098  double cellBodyHeight = QgsLayoutUtils::fontAscentMM( mContentFont );
1099  double halfGridStrokeWidth = ( mShowGrid ? mGridStrokeWidth : 0 ) / 2.0;
1100  double currentY = 0;
1101  currentY = halfGridStrokeWidth;
1102  if ( drawHeaderLines )
1103  {
1104  painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
1105  currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
1106  currentY += ( QgsLayoutUtils::fontAscentMM( mHeaderFont ) + 2 * mCellMargin );
1107  }
1108  for ( int row = firstRow; row < lastRow; ++row )
1109  {
1110  painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
1111  currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
1112  double rowHeight = row < mTableContents.count() ? mMaxRowHeightMap[row + 1] : cellBodyHeight;
1113  currentY += ( rowHeight + 2 * mCellMargin );
1114  }
1115  painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
1116 }
1117 
1118 bool QgsLayoutTable::textRequiresWrapping( const QString &text, double columnWidth, const QFont &font ) const
1119 {
1120  if ( qgsDoubleNear( columnWidth, 0.0 ) || mWrapBehavior != WrapText )
1121  return false;
1122 
1123  QStringList multiLineSplit = text.split( '\n' );
1124  double currentTextWidth = 0;
1125  const auto constMultiLineSplit = multiLineSplit;
1126  for ( const QString &line : constMultiLineSplit )
1127  {
1128  currentTextWidth = std::max( currentTextWidth, QgsLayoutUtils::textWidthMM( font, line ) );
1129  }
1130 
1131  return ( currentTextWidth > columnWidth );
1132 }
1133 
1134 QString QgsLayoutTable::wrappedText( const QString &value, double columnWidth, const QFont &font ) const
1135 {
1136  QStringList lines = value.split( '\n' );
1137  QStringList outLines;
1138  const auto constLines = lines;
1139  for ( const QString &line : constLines )
1140  {
1141  if ( textRequiresWrapping( line, columnWidth, font ) )
1142  {
1143  //first step is to identify words which must be on their own line (too long to fit)
1144  QStringList words = line.split( ' ' );
1145  QStringList linesToProcess;
1146  QString wordsInCurrentLine;
1147  const auto constWords = words;
1148  for ( const QString &word : constWords )
1149  {
1150  if ( textRequiresWrapping( word, columnWidth, font ) )
1151  {
1152  //too long to fit
1153  if ( !wordsInCurrentLine.isEmpty() )
1154  linesToProcess << wordsInCurrentLine;
1155  wordsInCurrentLine.clear();
1156  linesToProcess << word;
1157  }
1158  else
1159  {
1160  if ( !wordsInCurrentLine.isEmpty() )
1161  wordsInCurrentLine.append( ' ' );
1162  wordsInCurrentLine.append( word );
1163  }
1164  }
1165  if ( !wordsInCurrentLine.isEmpty() )
1166  linesToProcess << wordsInCurrentLine;
1167 
1168  const auto constLinesToProcess = linesToProcess;
1169  for ( const QString &line : constLinesToProcess )
1170  {
1171  QString remainingText = line;
1172  int lastPos = remainingText.lastIndexOf( ' ' );
1173  while ( lastPos > -1 )
1174  {
1175  //check if remaining text is short enough to go in one line
1176  if ( !textRequiresWrapping( remainingText, columnWidth, font ) )
1177  {
1178  break;
1179  }
1180 
1181  if ( !textRequiresWrapping( remainingText.left( lastPos ), columnWidth, font ) )
1182  {
1183  outLines << remainingText.left( lastPos );
1184  remainingText = remainingText.mid( lastPos + 1 );
1185  lastPos = 0;
1186  }
1187  lastPos = remainingText.lastIndexOf( ' ', lastPos - 1 );
1188  }
1189  outLines << remainingText;
1190  }
1191  }
1192  else
1193  {
1194  outLines << line;
1195  }
1196  }
1197 
1198  return outLines.join( QStringLiteral( "\n" ) );
1199 }
1200 
1201 QColor QgsLayoutTable::backgroundColor( int row, int column ) const
1202 {
1203  QColor color = mBackgroundColor;
1204  if ( QgsLayoutTableStyle *style = mCellStyles.value( OddColumns ) )
1205  if ( style->enabled && column % 2 == 0 )
1206  color = style->cellBackgroundColor;
1207  if ( QgsLayoutTableStyle *style = mCellStyles.value( EvenColumns ) )
1208  if ( style->enabled && column % 2 == 1 )
1209  color = style->cellBackgroundColor;
1210  if ( QgsLayoutTableStyle *style = mCellStyles.value( OddRows ) )
1211  if ( style->enabled && row % 2 == 0 )
1212  color = style->cellBackgroundColor;
1213  if ( QgsLayoutTableStyle *style = mCellStyles.value( EvenRows ) )
1214  if ( style->enabled && row % 2 == 1 )
1215  color = style->cellBackgroundColor;
1216  if ( QgsLayoutTableStyle *style = mCellStyles.value( FirstColumn ) )
1217  if ( style->enabled && column == 0 )
1218  color = style->cellBackgroundColor;
1219  if ( QgsLayoutTableStyle *style = mCellStyles.value( LastColumn ) )
1220  if ( style->enabled && column == mColumns.count() - 1 )
1221  color = style->cellBackgroundColor;
1222  if ( QgsLayoutTableStyle *style = mCellStyles.value( HeaderRow ) )
1223  if ( style->enabled && row == -1 )
1224  color = style->cellBackgroundColor;
1225  if ( QgsLayoutTableStyle *style = mCellStyles.value( FirstRow ) )
1226  if ( style->enabled && row == 0 )
1227  color = style->cellBackgroundColor;
1228  if ( QgsLayoutTableStyle *style = mCellStyles.value( LastRow ) )
1229  if ( style->enabled && row == mTableContents.count() - 1 )
1230  color = style->cellBackgroundColor;
1231 
1232  if ( row >= 0 )
1233  {
1234  QgsConditionalStyle conditionalStyle = conditionalCellStyle( row, column );
1235  if ( conditionalStyle.backgroundColor().isValid() )
1236  color = conditionalStyle.backgroundColor();
1237  }
1238 
1239  return color;
1240 }
1241 
1242 void QgsLayoutTable::drawVerticalGridLines( QPainter *painter, const QMap<int, double> &maxWidthMap, int firstRow, int lastRow, bool hasHeader, bool mergeCells ) const
1243 {
1244  //vertical lines
1245  if ( lastRow - firstRow < 1 && !hasHeader )
1246  {
1247  return;
1248  }
1249 
1250  //calculate height of table within frame
1251  double tableHeight = 0;
1252  if ( hasHeader )
1253  {
1255  }
1256  tableHeight += ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
1257  double headerHeight = tableHeight;
1258 
1259  double cellBodyHeight = QgsLayoutUtils::fontAscentMM( mContentFont );
1260  for ( int row = firstRow; row < lastRow; ++row )
1261  {
1262  double rowHeight = row < mTableContents.count() ? mMaxRowHeightMap[row + 1] : cellBodyHeight;
1263  tableHeight += rowHeight + ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2;
1264  }
1265 
1266  double halfGridStrokeWidth = ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 ) / 2.0;
1267  double currentX = halfGridStrokeWidth;
1268  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, tableHeight - halfGridStrokeWidth ) );
1269  currentX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1270  QMap<int, double>::const_iterator maxColWidthIt = maxWidthMap.constBegin();
1271  int col = 1;
1272  for ( ; maxColWidthIt != maxWidthMap.constEnd(); ++maxColWidthIt )
1273  {
1274  currentX += ( maxColWidthIt.value() + 2 * mCellMargin );
1275  if ( col == maxWidthMap.size() || !mergeCells )
1276  {
1277  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, tableHeight - halfGridStrokeWidth ) );
1278  }
1279  else if ( hasHeader )
1280  {
1281  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, headerHeight - halfGridStrokeWidth ) );
1282  }
1283 
1284  currentX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1285  col++;
1286  }
1287 }
1288 
1290 {
1292 
1293  //force recalculation of frame rects, so that they are set to the correct
1294  //fixed and minimum frame sizes
1296 }
1297 
1299 {
1300  return ( contents.indexOf( row ) >= 0 );
1301 }
1302 
EmptyTableMode
Controls how empty tables are displayed.
The class is used as a container of context for various read/write operations on other objects...
QMap< int, double > mMaxRowHeightMap
Map of maximum height for each row.
void refresh() override
Refreshes the multiframe, causing a recalculation of any property overrides.
void setWrapBehavior(WrapBehavior behavior)
Sets the wrap behavior for the table, which controls how text within cells is automatically wrapped...
virtual bool calculateMaxRowHeights()
Calculates the maximum height of text shown in rows.
QMap< CellStyleGroup, QgsLayoutTableStyle *> mCellStyles
Style odd numbered columns.
Style first column only.
~QgsLayoutTable() override
WrapBehavior
Controls how long strings in the table are handled.
static double textWidthMM(const QFont &font, const QString &text)
Calculate a font width in millimeters for a text string, including workarounds for QT font rendering ...
bool horizontalGrid() const
Returns whether the grid&#39;s horizontal lines are drawn in the table.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
void setCellMargin(double margin)
Sets the margin distance in mm between cell borders and their contents.
Header uses the same alignment as the column.
QgsLayoutTable(QgsLayout *layout)
Constructor for QgsLayoutTable, belonging to the specified layout.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
int frameIndex(QgsLayoutFrame *frame) const
Returns the index of a frame within the multiframe.
QRectF extent() const
Returns the visible portion of the multi frame&#39;s content which is shown in this frame, in layout units.
virtual QMap< int, QString > headerLabels() const
Returns the text used in the column headers for the table.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:315
QgsLayoutSize sizeWithUnits() const
Returns the item&#39;s current size, including units.
void setHeaderFontColor(const QColor &color)
Sets the color used to draw header text in the table.
double mCellMargin
Margin between cell borders and cell text.
Styling option for a layout table cell.
bool writeXml(QDomElement &styleElem, QDomDocument &doc) const
Writes the style&#39;s properties to XML for storage.
bool writePropertiesToElement(QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context) const override
Stores multiframe state within an XML DOM element.
QVector< QgsLayoutTableColumn *> QgsLayoutTableColumns
List of column definitions for a QgsLayoutTable.
bool readXml(const QDomElement &columnElem)
Reads the column&#39;s properties from xml.
void setEmptyTableMessage(const QString &message)
Sets the message for empty tables with no content rows.
WrapBehavior mWrapBehavior
virtual void refreshAttributes()
Refreshes the contents shown in the table by querying for new data.
QSizeF minFrameSize(int frameIndex=-1) const override
Returns the minimum size for a frames, if desired.
Style odd numbered rows.
void setVerticalGrid(bool verticalGrid)
Sets whether the grid&#39;s vertical lines should be drawn in the table.
Text which doesn&#39;t fit inside the cell is truncated.
void setCellStyle(CellStyleGroup group, const QgsLayoutTableStyle &style)
Sets the cell style for a cell group.
Style even numbered columns.
void render(QgsLayoutItemRenderContext &context, const QRectF &renderExtent, int frameIndex) override
Renders a portion of the multiframe&#39;s content into a render context.
QString mEmptyTableMessage
String to show in empty tables.
Shows preset message instead of table contents.
int frameCount() const
Returns the number of frames associated with this multiframe.
Text which doesn&#39;t fit inside the cell is wrapped. Note that this only applies to text in columns wit...
void recalculateFrameRects()
Forces a recalculation of all the associated frame&#39;s scene rectangles.
const QgsLayoutTableStyle * cellStyle(CellStyleGroup group) const
Returns the cell style for a cell group.
double totalHeight()
Returns total height of table contents.
Creates new full page frames on the following page(s) until the entire multiframe content is visible...
Stores properties of a column for a QgsLayoutTable.
QSizeF fixedFrameSize(int frameIndex=-1) const override
Returns the fixed size for a frame, if desired.
Abstract base class for layout items with the ability to distribute the content to several frames (Qg...
QColor mGridColor
Color for grid lines.
static QString encodeColor(const QColor &color)
QSizeF totalSize() const override
Returns the total size of the multiframe&#39;s content, in layout units.
bool mShowGrid
True if grid should be shown.
No headers shown for table.
void setHeaderMode(HeaderMode mode)
Sets the display mode for headers in the table.
QColor mContentFontColor
Table contents font color.
Style first row only.
Align headers right.
Conditional styling for a rule.
bool mVerticalGrid
True if grid should be shown.
QColor mHeaderFontColor
Header font color.
void setHorizontalGrid(bool horizontalGrid)
Sets whether the grid&#39;s horizontal lines should be drawn in the table.
void recalculateFrameSizes() override
virtual bool calculateMaxColumnWidths()
Calculates the maximum width of text shown in columns.
void setHeaderHAlignment(HeaderHAlignment alignment)
Sets the horizontal alignment for table headers.
QgsRenderContext & renderContext()
Returns a reference to the context&#39;s render context.
Definition: qgslayoutitem.h:72
QColor cellBackgroundColor
Cell background color.
void drawVerticalGridLines(QPainter *painter, const QMap< int, double > &maxWidthMap, int firstRow, int lastRow, bool hasHeader, bool mergeCells=false) const
Draws the vertical grid lines for the table.
QPointer< QgsLayout > mLayout
QColor backgroundColor() const
The background color for style.
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.
QColor mBackgroundColor
Color for table background.
void setContentFontColor(const QColor &color)
Sets the color used to draw text in table body cells.
Header shown on first frame only.
bool mHorizontalGrid
True if grid should be shown.
QFont mHeaderFont
Header font.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
void setGridStrokeWidth(double width)
Sets the width in mm for grid lines in the table.
virtual bool getTableContents(QgsLayoutTableContents &contents)=0
Fetches the contents used for the cells in the table.
bool mShowEmptyRows
True if empty rows should be shown in the table.
virtual void recalculateFrameSizes()
Recalculates the portion of the multiframe item which is shown in each of its component frames...
static void drawText(QPainter *painter, QPointF position, const QString &text, const QFont &font, const QColor &color=QColor())
Draws text on a painter at a specific position, taking care of layout specific issues (calculation to...
QFont mContentFont
Table contents font.
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:44
QgsLayoutFrame * frame(int index) const
Returns the child frame at a specified index from the multiframe.
Style header row.
Align headers left.
virtual QgsConditionalStyle conditionalCellStyle(int row, int column) const
Returns the conditional style to use for the cell at row, column.
Style last row only.
double mGridStrokeWidth
Width of grid lines.
bool verticalGrid() const
Returns whether the grid&#39;s vertical lines are drawn in the table.
bool showGrid() const
Returns whether grid lines are drawn in the table.
QColor textColor() const
The text color set for style.
QgsLayoutTableColumns mColumns
Columns to show in table.
void setContentFont(const QFont &font)
Sets the font used to draw text in table body cells.
bool readXml(const QDomElement &styleElem)
Reads the style&#39;s properties from XML.
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsLayoutTableContents & contents()
Returns the current contents of the table.
void drawHorizontalGridLines(QPainter *painter, int firstRow, int lastRow, bool drawHeaderLines) const
Draws the horizontal grid lines for the table.
static double fontAscentMM(const QFont &font)
Calculates a font ascent in millimeters, including workarounds for QT font rendering issues...
void update()
Forces a redraw of all child frames.
Headers shown on all frames.
QgsLayoutTableContents mTableContents
Contents to show in table.
void setBackgroundColor(const QColor &color)
Sets the color used for background of table.
QPair< int, int > rowRange(int frameIndex) const
Calculates a range of rows which should be visible in a given frame.
static double textHeightMM(const QFont &font, const QString &text, double multiLineHeight=1.0)
Calculate a font height in millimeters for a text string, including workarounds for QT font rendering...
EmptyTableMode mEmptyTableMode
Behavior for empty tables.
QColor backgroundColor() const
Returns the color used for the background of the table.
double totalWidth()
Returns total width of table contents.
QMap< int, double > mMaxColumnWidthMap
Map of maximum width for each column.
static QDomElement toXmlElement(const QFont &font, QDomDocument &document, const QString &elementName)
Returns a DOM element containing the properties of the font.
void setHeaderFont(const QFont &font)
Sets the font used to draw header text in the table.
bool enabled
Whether the styling option is enabled.
HeaderMode
Controls where headers are shown in the table.
HeaderHAlignment
Controls how headers are horizontally aligned in a table.
Style even numbered rows.
void setEmptyTableBehavior(EmptyTableMode mode)
Sets the behavior mode for empty tables with no content rows.
QgsLayoutTableColumns & columns()
Returns a reference to the list of QgsLayoutTableColumns shown in the table.
void setShowGrid(bool showGrid)
Sets whether grid lines should be drawn in the table.
Align headers to center.
void setColumns(const QgsLayoutTableColumns &columns)
Replaces the columns in the table with a specified list of QgsLayoutTableColumns. ...
void changed()
Emitted when the object&#39;s properties change.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
double height() const
Returns the height of the size.
Definition: qgslayoutsize.h:90
CellStyleGroup
Row or column groups for cell styling.
HeaderMode mHeaderMode
Header display mode.
void setGridColor(const QColor &color)
Sets the color used for grid lines in the table.
bool contentsContainsRow(const QgsLayoutTableContents &contents, const QgsLayoutTableRow &row) const
Checks whether a table contents contains a given row.
HeaderHAlignment mHeaderHAlignment
Alignment for table headers.
int rowsVisible(double frameHeight, int firstRow, bool includeHeader, bool includeEmptyRows) const
Calculates how many content rows would be visible within a frame of the specified height...
QVector< QgsLayoutTableRow > QgsLayoutTableContents
List of QgsLayoutTableRows, representing rows and column cell contents for a QgsLayoutTable.
void setShowEmptyRows(bool showEmpty)
Sets whether empty rows should be drawn.
Style last column only.
static QColor decodeColor(const QString &str)
Hides entire table if empty.
Item representing the paper in a layout.
bool readPropertiesFromElement(const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context) override
Sets multiframe state from a DOM element.
QVector< QVariant > QgsLayoutTableRow
List of QVariants, representing a the contents of a single row in a QgsLayoutTable.
void recalculateTableSize()
Recalculates and updates the size of the table and all table frames.
void refresh() override