QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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  QgsLayoutUtils::drawText( p, textCell, str, mContentFont, mContentFontColor, column->hAlignment(), column->vAlignment(), textFlag );
439  p->restore();
440 
441  currentX += mMaxColumnWidthMap[ col ];
442  currentX += mCellMargin;
443  currentX += gridSizeX;
444  col++;
445  }
446  currentY += rowHeight;
447  currentY += gridSizeY;
448  }
449  }
450 
451  if ( numberRowsToDraw > rowsDrawn )
452  {
453  p->save();
454  p->setPen( Qt::NoPen );
455 
456  //draw background of empty rows
457  for ( int row = rowsDrawn; row < numberRowsToDraw; ++row )
458  {
459  currentX = gridSizeX;
460  int col = 0;
461 
462  if ( mergeCells )
463  {
464  p->setBrush( backgroundColor( row + 10000, 0 ) );
465  p->drawRect( QRectF( gridSizeX, currentY, mTableSize.width() - 2 * gridSizeX, cellBodyHeight ) );
466  }
467  else
468  {
469  for ( QgsLayoutTableColumn *column : qgis::as_const( mColumns ) )
470  {
471  Q_UNUSED( column )
472 
473  //draw background
474 
475  //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
476  p->setBrush( backgroundColor( row + 10000, col ) );
477  p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, cellBodyHeight ) );
478 
479  // currentY = gridSize;
480  currentX += mMaxColumnWidthMap[ col ] + 2 * mCellMargin;
481  currentX += gridSizeX;
482  col++;
483  }
484  }
485  currentY += cellBodyHeight + gridSizeY;
486  }
487  p->restore();
488  }
489 
490  //and the borders
491  if ( mShowGrid )
492  {
493  QPen gridPen;
494  gridPen.setWidthF( mGridStrokeWidth );
495  gridPen.setColor( mGridColor );
496  gridPen.setJoinStyle( Qt::MiterJoin );
497  p->setPen( gridPen );
498  if ( mHorizontalGrid )
499  {
500  drawHorizontalGridLines( p, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader );
501  }
502  if ( mVerticalGrid )
503  {
504  drawVerticalGridLines( p, mMaxColumnWidthMap, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader, mergeCells );
505  }
506  }
507 
508  //special case - no records and table is set to ShowMessage mode
509  if ( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage )
510  {
511  double messageX = gridSizeX + mCellMargin;
512  double messageY = gridSizeY + ( drawHeader ? cellHeaderHeight + gridSizeY : 0 );
513  cell = QRectF( messageX, messageY, mTableSize.width() - messageX, cellBodyHeight );
514  QgsLayoutUtils::drawText( p, cell, mEmptyTableMessage, mContentFont, mContentFontColor, Qt::AlignHCenter, Qt::AlignVCenter, static_cast< Qt::TextFlag >( 0 ) );
515  }
516 
517  p->restore();
518 
519 }
520 
521 void QgsLayoutTable::setCellMargin( const double margin )
522 {
523  if ( qgsDoubleNear( margin, mCellMargin ) )
524  {
525  return;
526  }
527 
528  mCellMargin = margin;
529 
530  //since spacing has changed, we need to recalculate the table size
532 
533  emit changed();
534 }
535 
537 {
538  if ( mode == mEmptyTableMode )
539  {
540  return;
541  }
542 
543  mEmptyTableMode = mode;
544 
545  //since appearance has changed, we need to recalculate the table size
547 
548  emit changed();
549 }
550 
551 void QgsLayoutTable::setEmptyTableMessage( const QString &message )
552 {
553  if ( message == mEmptyTableMessage )
554  {
555  return;
556  }
557 
558  mEmptyTableMessage = message;
559 
560  //since message has changed, we need to recalculate the table size
562 
563  emit changed();
564 }
565 
566 void QgsLayoutTable::setShowEmptyRows( const bool showEmpty )
567 {
568  if ( showEmpty == mShowEmptyRows )
569  {
570  return;
571  }
572 
573  mShowEmptyRows = showEmpty;
574  update();
575  emit changed();
576 }
577 
578 void QgsLayoutTable::setHeaderFont( const QFont &font )
579 {
580  if ( font == mHeaderFont )
581  {
582  return;
583  }
584 
585  mHeaderFont = font;
586  //since font attributes have changed, we need to recalculate the table size
588 
589  emit changed();
590 }
591 
592 void QgsLayoutTable::setHeaderFontColor( const QColor &color )
593 {
594  if ( color == mHeaderFontColor )
595  {
596  return;
597  }
598 
599  mHeaderFontColor = color;
600  update();
601 
602  emit changed();
603 }
604 
606 {
607  if ( alignment == mHeaderHAlignment )
608  {
609  return;
610  }
611 
612  mHeaderHAlignment = alignment;
613  update();
614 
615  emit changed();
616 }
617 
619 {
620  if ( mode == mHeaderMode )
621  {
622  return;
623  }
624 
625  mHeaderMode = mode;
627 
628  emit changed();
629 }
630 
631 void QgsLayoutTable::setContentFont( const QFont &font )
632 {
633  if ( font == mContentFont )
634  {
635  return;
636  }
637 
638  mContentFont = font;
639  //since font attributes have changed, we need to recalculate the table size
641 
642  emit changed();
643 }
644 
645 void QgsLayoutTable::setContentFontColor( const QColor &color )
646 {
647  if ( color == mContentFontColor )
648  {
649  return;
650  }
651 
652  mContentFontColor = color;
653  update();
654 
655  emit changed();
656 }
657 
659 {
660  if ( showGrid == mShowGrid )
661  {
662  return;
663  }
664 
666  //since grid spacing has changed, we need to recalculate the table size
668 
669  emit changed();
670 }
671 
672 void QgsLayoutTable::setGridStrokeWidth( const double width )
673 {
674  if ( qgsDoubleNear( width, mGridStrokeWidth ) )
675  {
676  return;
677  }
678 
679  mGridStrokeWidth = width;
680  //since grid spacing has changed, we need to recalculate the table size
682 
683  emit changed();
684 }
685 
686 void QgsLayoutTable::setGridColor( const QColor &color )
687 {
688  if ( color == mGridColor )
689  {
690  return;
691  }
692 
693  mGridColor = color;
694  update();
695 
696  emit changed();
697 }
698 
700 {
701  if ( horizontalGrid == mHorizontalGrid )
702  {
703  return;
704  }
705 
707  //since grid spacing has changed, we need to recalculate the table size
709 
710  emit changed();
711 }
712 
714 {
715  if ( verticalGrid == mVerticalGrid )
716  {
717  return;
718  }
719 
721  //since grid spacing has changed, we need to recalculate the table size
723 
724  emit changed();
725 }
726 
727 void QgsLayoutTable::setBackgroundColor( const QColor &color )
728 {
729  if ( color == mBackgroundColor )
730  {
731  return;
732  }
733 
734  mBackgroundColor = color;
735  update();
736 
737  emit changed();
738 }
739 
741 {
742  if ( behavior == mWrapBehavior )
743  {
744  return;
745  }
746 
747  mWrapBehavior = behavior;
749 
750  emit changed();
751 }
752 
754 {
755  //remove existing columns
756  qDeleteAll( mColumns );
757  mColumns.clear();
758 
759  mColumns.append( columns );
760 }
761 
763 {
764  if ( mCellStyles.contains( group ) )
765  delete mCellStyles.take( group );
766 
767  mCellStyles.insert( group, new QgsLayoutTableStyle( style ) );
768 }
769 
771 {
772  if ( !mCellStyles.contains( group ) )
773  return nullptr;
774 
775  return mCellStyles.value( group );
776 }
777 
778 QMap<int, QString> QgsLayoutTable::headerLabels() const
779 {
780  QMap<int, QString> headers;
781 
782  QgsLayoutTableColumns::const_iterator columnIt = mColumns.constBegin();
783  int col = 0;
784  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
785  {
786  headers.insert( col, ( *columnIt )->heading() );
787  col++;
788  }
789  return headers;
790 }
791 
793 {
794  Q_UNUSED( frameIndex )
795  return QSizeF( mTableSize.width(), 0 );
796 }
797 
798 QSizeF QgsLayoutTable::minFrameSize( const int frameIndex ) const
799 {
800  double height = 0;
801  if ( ( mHeaderMode == QgsLayoutTable::FirstFrame && frameIndex < 1 )
803  {
804  //header required, force frame to be high enough for header
806  }
807  return QSizeF( 0, height );
808 }
809 
811 {
812  mMaxColumnWidthMap.clear();
813  mMaxRowHeightMap.clear();
814  mTableContents.clear();
815 
816  //get new contents
818  {
819  return;
820  }
821 }
822 
824 {
825  mTableSize = QSizeF( totalWidth(), totalHeight() );
827 }
828 
829 void QgsLayoutTable::initStyles()
830 {
831  mCellStyles.insert( OddColumns, new QgsLayoutTableStyle() );
833  mCellStyles.insert( OddRows, new QgsLayoutTableStyle() );
834  mCellStyles.insert( EvenRows, new QgsLayoutTableStyle() );
836  mCellStyles.insert( LastColumn, new QgsLayoutTableStyle() );
837  mCellStyles.insert( HeaderRow, new QgsLayoutTableStyle() );
838  mCellStyles.insert( FirstRow, new QgsLayoutTableStyle() );
839  mCellStyles.insert( LastRow, new QgsLayoutTableStyle() );
840 
841  mCellStyleNames.insert( OddColumns, QStringLiteral( "oddColumns" ) );
842  mCellStyleNames.insert( EvenColumns, QStringLiteral( "evenColumns" ) );
843  mCellStyleNames.insert( OddRows, QStringLiteral( "oddRows" ) );
844  mCellStyleNames.insert( EvenRows, QStringLiteral( "evenRows" ) );
845  mCellStyleNames.insert( FirstColumn, QStringLiteral( "firstColumn" ) );
846  mCellStyleNames.insert( LastColumn, QStringLiteral( "lastColumn" ) );
847  mCellStyleNames.insert( HeaderRow, QStringLiteral( "headerRow" ) );
848  mCellStyleNames.insert( FirstRow, QStringLiteral( "firstRow" ) );
849  mCellStyleNames.insert( LastRow, QStringLiteral( "lastRow" ) );
850 }
851 
853 {
854  mMaxColumnWidthMap.clear();
855 
856  //total number of cells (rows + 1 for header)
857  int cols = mColumns.count();
858  int cells = cols * ( mTableContents.count() + 1 );
859  QVector< double > widths( cells );
860 
861  //first, go through all the column headers and calculate the sizes
862  QgsLayoutTableColumns::const_iterator columnIt = mColumns.constBegin();
863  int col = 0;
864  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
865  {
866  if ( ( *columnIt )->width() > 0 )
867  {
868  //column has manually specified width
869  widths[col] = ( *columnIt )->width();
870  }
872  {
873  widths[col] = QgsLayoutUtils::textWidthMM( mHeaderFont, ( *columnIt )->heading() );
874  }
875  else
876  {
877  widths[col] = 0.0;
878  }
879  col++;
880  }
881 
882  //next, go through all the table contents and calculate the sizes
883  QgsLayoutTableContents::const_iterator rowIt = mTableContents.constBegin();
884  double currentCellTextWidth;
885  int row = 1;
886  for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
887  {
888  QgsLayoutTableRow::const_iterator colIt = rowIt->constBegin();
889  col = 0;
890  for ( ; colIt != rowIt->constEnd(); ++colIt )
891  {
892  if ( mColumns.at( col )->width() <= 0 )
893  {
894  //column width set to automatic, so check content size
895  QStringList multiLineSplit = ( *colIt ).toString().split( '\n' );
896  currentCellTextWidth = 0;
897  const auto constMultiLineSplit = multiLineSplit;
898  for ( const QString &line : constMultiLineSplit )
899  {
900  currentCellTextWidth = std::max( currentCellTextWidth, QgsLayoutUtils::textWidthMM( mContentFont, line ) );
901  }
902  widths[ row * cols + col ] = currentCellTextWidth;
903  }
904  else
905  {
906  widths[ row * cols + col ] = 0;
907  }
908 
909  col++;
910  }
911  row++;
912  }
913 
914  //calculate maximum
915  for ( int col = 0; col < cols; ++col )
916  {
917  double maxColWidth = 0;
918  for ( int row = 0; row < mTableContents.count() + 1; ++row )
919  {
920  maxColWidth = std::max( widths[ row * cols + col ], maxColWidth );
921  }
922  mMaxColumnWidthMap.insert( col, maxColWidth );
923  }
924 
925  return true;
926 }
927 
929 {
930  mMaxRowHeightMap.clear();
931 
932  //total number of cells (rows + 1 for header)
933  int cols = mColumns.count();
934  int cells = cols * ( mTableContents.count() + 1 );
935  QVector< double > heights( cells );
936 
937  //first, go through all the column headers and calculate the sizes
938  QgsLayoutTableColumns::const_iterator columnIt = mColumns.constBegin();
939  int col = 0;
940  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
941  {
942  //height
943  heights[col] = mHeaderMode != QgsLayoutTable::NoHeaders ? QgsLayoutUtils::textHeightMM( mHeaderFont, ( *columnIt )->heading() ) : 0;
944  col++;
945  }
946 
947  //next, go through all the table contents and calculate the sizes
948  QgsLayoutTableContents::const_iterator rowIt = mTableContents.constBegin();
949  int row = 1;
950  for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
951  {
952  QgsLayoutTableRow::const_iterator colIt = rowIt->constBegin();
953  col = 0;
954  for ( ; colIt != rowIt->constEnd(); ++colIt )
955  {
956  if ( textRequiresWrapping( ( *colIt ).toString(), mColumns.at( col )->width(), mContentFont ) )
957  {
958  //contents too wide for cell, need to wrap
959  heights[ row * cols + col ] = QgsLayoutUtils::textHeightMM( mContentFont, wrappedText( ( *colIt ).toString(), mColumns.at( col )->width(), mContentFont ) );
960  }
961  else
962  {
963  heights[ row * cols + col ] = QgsLayoutUtils::textHeightMM( mContentFont, ( *colIt ).toString() );
964  }
965 
966  col++;
967  }
968  row++;
969  }
970 
971  //calculate maximum
972  for ( int row = 0; row < mTableContents.count() + 1; ++row )
973  {
974  double maxRowHeight = 0;
975  for ( int col = 0; col < cols; ++col )
976  {
977  maxRowHeight = std::max( heights[ row * cols + col ], maxRowHeight );
978  }
979  mMaxRowHeightMap.insert( row, maxRowHeight );
980  }
981 
982  return true;
983 }
984 
986 {
987  //check how much space each column needs
988  if ( !calculateMaxColumnWidths() )
989  {
990  return 0;
991  }
992 
993  //adapt frame to total width
994  double totalWidth = 0;
995  QMap<int, double>::const_iterator maxColWidthIt = mMaxColumnWidthMap.constBegin();
996  for ( ; maxColWidthIt != mMaxColumnWidthMap.constEnd(); ++maxColWidthIt )
997  {
998  totalWidth += maxColWidthIt.value();
999  }
1000  totalWidth += ( 2 * mMaxColumnWidthMap.size() * mCellMargin );
1001  totalWidth += ( mMaxColumnWidthMap.size() + 1 ) * ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1002 
1003  return totalWidth;
1004 }
1005 
1007 {
1008  //check how much space each row needs
1009  if ( !calculateMaxRowHeights() )
1010  {
1011  return 0;
1012  }
1013 
1014  double height = 0;
1015 
1016  //loop through all existing frames to calculate how many rows are visible in each
1017  //as the entire height of a frame may not be utilized for content rows
1018  int rowsAlreadyShown = 0;
1019  int numberExistingFrames = frameCount();
1020  int rowsVisibleInLastFrame = 0;
1021  double heightOfLastFrame = 0;
1022  for ( int idx = 0; idx < numberExistingFrames; ++idx )
1023  {
1024  bool hasHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && idx == 0 )
1026  heightOfLastFrame = frame( idx )->rect().height();
1027  rowsVisibleInLastFrame = rowsVisible( heightOfLastFrame, rowsAlreadyShown, hasHeader, false );
1028  rowsAlreadyShown += rowsVisibleInLastFrame;
1029  height += heightOfLastFrame;
1030  if ( rowsAlreadyShown >= mTableContents.length() )
1031  {
1032  //shown entire contents of table, nothing remaining
1033  return height;
1034  }
1035  }
1036 
1037  //calculate how many rows left to show
1038  int remainingRows = mTableContents.length() - rowsAlreadyShown;
1039 
1040  if ( remainingRows <= 0 )
1041  {
1042  //no remaining rows
1043  return height;
1044  }
1045 
1047  {
1048  QgsLayoutItemPage *page = mLayout->pageCollection()->page( mLayout->pageCollection()->pageCount() - 1 );
1049  if ( page )
1050  heightOfLastFrame = page->sizeWithUnits().height();
1051  }
1052 
1053  bool hasHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && numberExistingFrames < 1 )
1055 
1056  int numberFramesMissing = 0;
1057  while ( remainingRows > 0 )
1058  {
1059  numberFramesMissing++;
1060 
1061  rowsVisibleInLastFrame = rowsVisible( heightOfLastFrame, rowsAlreadyShown, hasHeader, false );
1062  if ( rowsVisibleInLastFrame < 1 )
1063  {
1064  //if no rows are visible in the last frame, calculation of missing frames
1065  //is impossible. So just return total height of existing frames
1066  return height;
1067  }
1068 
1069  rowsAlreadyShown += rowsVisibleInLastFrame;
1070  remainingRows = mTableContents.length() - rowsAlreadyShown;
1071  }
1072 
1073  //rows remain unshown -- how many extra frames would we need to complete the table?
1074  //assume all added frames are same size as final frame
1075  height += heightOfLastFrame * numberFramesMissing;
1076  return height;
1077 }
1078 
1079 void QgsLayoutTable::drawHorizontalGridLines( QPainter *painter, int firstRow, int lastRow, bool drawHeaderLines ) const
1080 {
1081  //horizontal lines
1082  if ( lastRow - firstRow < 1 && !drawHeaderLines )
1083  {
1084  return;
1085  }
1086 
1087  double cellBodyHeight = QgsLayoutUtils::fontAscentMM( mContentFont );
1088  double halfGridStrokeWidth = ( mShowGrid ? mGridStrokeWidth : 0 ) / 2.0;
1089  double currentY = 0;
1090  currentY = halfGridStrokeWidth;
1091  if ( drawHeaderLines )
1092  {
1093  painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
1094  currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
1095  currentY += ( QgsLayoutUtils::fontAscentMM( mHeaderFont ) + 2 * mCellMargin );
1096  }
1097  for ( int row = firstRow; row < lastRow; ++row )
1098  {
1099  painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
1100  currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
1101  double rowHeight = row < mTableContents.count() ? mMaxRowHeightMap[row + 1] : cellBodyHeight;
1102  currentY += ( rowHeight + 2 * mCellMargin );
1103  }
1104  painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
1105 }
1106 
1107 bool QgsLayoutTable::textRequiresWrapping( const QString &text, double columnWidth, const QFont &font ) const
1108 {
1109  if ( qgsDoubleNear( columnWidth, 0.0 ) || mWrapBehavior != WrapText )
1110  return false;
1111 
1112  QStringList multiLineSplit = text.split( '\n' );
1113  double currentTextWidth = 0;
1114  const auto constMultiLineSplit = multiLineSplit;
1115  for ( const QString &line : constMultiLineSplit )
1116  {
1117  currentTextWidth = std::max( currentTextWidth, QgsLayoutUtils::textWidthMM( font, line ) );
1118  }
1119 
1120  return ( currentTextWidth > columnWidth );
1121 }
1122 
1123 QString QgsLayoutTable::wrappedText( const QString &value, double columnWidth, const QFont &font ) const
1124 {
1125  QStringList lines = value.split( '\n' );
1126  QStringList outLines;
1127  const auto constLines = lines;
1128  for ( const QString &line : constLines )
1129  {
1130  if ( textRequiresWrapping( line, columnWidth, font ) )
1131  {
1132  //first step is to identify words which must be on their own line (too long to fit)
1133  QStringList words = line.split( ' ' );
1134  QStringList linesToProcess;
1135  QString wordsInCurrentLine;
1136  const auto constWords = words;
1137  for ( const QString &word : constWords )
1138  {
1139  if ( textRequiresWrapping( word, columnWidth, font ) )
1140  {
1141  //too long to fit
1142  if ( !wordsInCurrentLine.isEmpty() )
1143  linesToProcess << wordsInCurrentLine;
1144  wordsInCurrentLine.clear();
1145  linesToProcess << word;
1146  }
1147  else
1148  {
1149  if ( !wordsInCurrentLine.isEmpty() )
1150  wordsInCurrentLine.append( ' ' );
1151  wordsInCurrentLine.append( word );
1152  }
1153  }
1154  if ( !wordsInCurrentLine.isEmpty() )
1155  linesToProcess << wordsInCurrentLine;
1156 
1157  const auto constLinesToProcess = linesToProcess;
1158  for ( const QString &line : constLinesToProcess )
1159  {
1160  QString remainingText = line;
1161  int lastPos = remainingText.lastIndexOf( ' ' );
1162  while ( lastPos > -1 )
1163  {
1164  //check if remaining text is short enough to go in one line
1165  if ( !textRequiresWrapping( remainingText, columnWidth, font ) )
1166  {
1167  break;
1168  }
1169 
1170  if ( !textRequiresWrapping( remainingText.left( lastPos ), columnWidth, font ) )
1171  {
1172  outLines << remainingText.left( lastPos );
1173  remainingText = remainingText.mid( lastPos + 1 );
1174  lastPos = 0;
1175  }
1176  lastPos = remainingText.lastIndexOf( ' ', lastPos - 1 );
1177  }
1178  outLines << remainingText;
1179  }
1180  }
1181  else
1182  {
1183  outLines << line;
1184  }
1185  }
1186 
1187  return outLines.join( QStringLiteral( "\n" ) );
1188 }
1189 
1190 QColor QgsLayoutTable::backgroundColor( int row, int column ) const
1191 {
1192  QColor color = mBackgroundColor;
1193  if ( QgsLayoutTableStyle *style = mCellStyles.value( OddColumns ) )
1194  if ( style->enabled && column % 2 == 0 )
1195  color = style->cellBackgroundColor;
1196  if ( QgsLayoutTableStyle *style = mCellStyles.value( EvenColumns ) )
1197  if ( style->enabled && column % 2 == 1 )
1198  color = style->cellBackgroundColor;
1199  if ( QgsLayoutTableStyle *style = mCellStyles.value( OddRows ) )
1200  if ( style->enabled && row % 2 == 0 )
1201  color = style->cellBackgroundColor;
1202  if ( QgsLayoutTableStyle *style = mCellStyles.value( EvenRows ) )
1203  if ( style->enabled && row % 2 == 1 )
1204  color = style->cellBackgroundColor;
1205  if ( QgsLayoutTableStyle *style = mCellStyles.value( FirstColumn ) )
1206  if ( style->enabled && column == 0 )
1207  color = style->cellBackgroundColor;
1208  if ( QgsLayoutTableStyle *style = mCellStyles.value( LastColumn ) )
1209  if ( style->enabled && column == mColumns.count() - 1 )
1210  color = style->cellBackgroundColor;
1211  if ( QgsLayoutTableStyle *style = mCellStyles.value( HeaderRow ) )
1212  if ( style->enabled && row == -1 )
1213  color = style->cellBackgroundColor;
1214  if ( QgsLayoutTableStyle *style = mCellStyles.value( FirstRow ) )
1215  if ( style->enabled && row == 0 )
1216  color = style->cellBackgroundColor;
1217  if ( QgsLayoutTableStyle *style = mCellStyles.value( LastRow ) )
1218  if ( style->enabled && row == mTableContents.count() - 1 )
1219  color = style->cellBackgroundColor;
1220 
1221  return color;
1222 }
1223 
1224 void QgsLayoutTable::drawVerticalGridLines( QPainter *painter, const QMap<int, double> &maxWidthMap, int firstRow, int lastRow, bool hasHeader, bool mergeCells ) const
1225 {
1226  //vertical lines
1227  if ( lastRow - firstRow < 1 && !hasHeader )
1228  {
1229  return;
1230  }
1231 
1232  //calculate height of table within frame
1233  double tableHeight = 0;
1234  if ( hasHeader )
1235  {
1237  }
1238  tableHeight += ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
1239  double headerHeight = tableHeight;
1240 
1241  double cellBodyHeight = QgsLayoutUtils::fontAscentMM( mContentFont );
1242  for ( int row = firstRow; row < lastRow; ++row )
1243  {
1244  double rowHeight = row < mTableContents.count() ? mMaxRowHeightMap[row + 1] : cellBodyHeight;
1245  tableHeight += rowHeight + ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2;
1246  }
1247 
1248  double halfGridStrokeWidth = ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 ) / 2.0;
1249  double currentX = halfGridStrokeWidth;
1250  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, tableHeight - halfGridStrokeWidth ) );
1251  currentX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1252  QMap<int, double>::const_iterator maxColWidthIt = maxWidthMap.constBegin();
1253  int col = 1;
1254  for ( ; maxColWidthIt != maxWidthMap.constEnd(); ++maxColWidthIt )
1255  {
1256  currentX += ( maxColWidthIt.value() + 2 * mCellMargin );
1257  if ( col == maxWidthMap.size() || !mergeCells )
1258  {
1259  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, tableHeight - halfGridStrokeWidth ) );
1260  }
1261  else if ( hasHeader )
1262  {
1263  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, headerHeight - halfGridStrokeWidth ) );
1264  }
1265 
1266  currentX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1267  col++;
1268  }
1269 }
1270 
1272 {
1274 
1275  //force recalculation of frame rects, so that they are set to the correct
1276  //fixed and minimum frame sizes
1278 }
1279 
1281 {
1282  return ( contents.indexOf( row ) >= 0 );
1283 }
1284 
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:280
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 composer 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.
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
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.
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.
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 QgsComposerTable.
void recalculateTableSize()
Recalculates and updates the size of the table and all table frames.
void refresh() override