QGIS API Documentation  3.6.0-Noosa (5873452)
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  //draw background
414  p->save();
415  p->setPen( Qt::NoPen );
416  p->setBrush( backgroundColor( row, col ) );
417  p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, rowHeight ) );
418  p->restore();
419 
420  // currentY = gridSize;
421  currentX += mCellMargin;
422 
423  QVariant cellContents = mTableContents.at( row ).at( col );
424  QString str = cellContents.toString();
425 
426  Qt::TextFlag textFlag = static_cast< Qt::TextFlag >( 0 );
427  if ( column->width() <= 0 && mWrapBehavior == TruncateText )
428  {
429  //automatic column width, so we use the Qt::TextDontClip flag when drawing contents, as this works nicer for italicised text
430  //which may slightly exceed the calculated width
431  //if column size was manually set then we do apply text clipping, to avoid painting text outside of columns width
432  textFlag = Qt::TextDontClip;
433  }
434  else if ( textRequiresWrapping( str, column->width(), mContentFont ) )
435  {
436  str = wrappedText( str, column->width(), mContentFont );
437  }
438 
439  cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], rowHeight );
440  QgsLayoutUtils::drawText( p, cell, str, mContentFont, mContentFontColor, column->hAlignment(), column->vAlignment(), textFlag );
441 
442  currentX += mMaxColumnWidthMap[ col ];
443  currentX += mCellMargin;
444  currentX += gridSizeX;
445  col++;
446  }
447  currentY += rowHeight;
448  currentY += gridSizeY;
449  }
450  }
451 
452  if ( numberRowsToDraw > rowsDrawn )
453  {
454  p->save();
455  p->setPen( Qt::NoPen );
456 
457  //draw background of empty rows
458  for ( int row = rowsDrawn; row < numberRowsToDraw; ++row )
459  {
460  currentX = gridSizeX;
461  int col = 0;
462 
463  if ( mergeCells )
464  {
465  p->setBrush( backgroundColor( row + 10000, 0 ) );
466  p->drawRect( QRectF( gridSizeX, currentY, mTableSize.width() - 2 * gridSizeX, cellBodyHeight ) );
467  }
468  else
469  {
470  for ( QgsLayoutTableColumn *column : qgis::as_const( mColumns ) )
471  {
472  Q_UNUSED( column );
473 
474  //draw background
475 
476  //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
477  p->setBrush( backgroundColor( row + 10000, col ) );
478  p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, cellBodyHeight ) );
479 
480  // currentY = gridSize;
481  currentX += mMaxColumnWidthMap[ col ] + 2 * mCellMargin;
482  currentX += gridSizeX;
483  col++;
484  }
485  }
486  currentY += cellBodyHeight + gridSizeY;
487  }
488  p->restore();
489  }
490 
491  //and the borders
492  if ( mShowGrid )
493  {
494  QPen gridPen;
495  gridPen.setWidthF( mGridStrokeWidth );
496  gridPen.setColor( mGridColor );
497  gridPen.setJoinStyle( Qt::MiterJoin );
498  p->setPen( gridPen );
499  if ( mHorizontalGrid )
500  {
501  drawHorizontalGridLines( p, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader );
502  }
503  if ( mVerticalGrid )
504  {
505  drawVerticalGridLines( p, mMaxColumnWidthMap, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader, mergeCells );
506  }
507  }
508 
509  //special case - no records and table is set to ShowMessage mode
510  if ( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage )
511  {
512  double messageX = gridSizeX + mCellMargin;
513  double messageY = gridSizeY + ( drawHeader ? cellHeaderHeight + gridSizeY : 0 );
514  cell = QRectF( messageX, messageY, mTableSize.width() - messageX, cellBodyHeight );
515  QgsLayoutUtils::drawText( p, cell, mEmptyTableMessage, mContentFont, mContentFontColor, Qt::AlignHCenter, Qt::AlignVCenter, static_cast< Qt::TextFlag >( 0 ) );
516  }
517 
518  p->restore();
519 
520 }
521 
522 void QgsLayoutTable::setCellMargin( const double margin )
523 {
524  if ( qgsDoubleNear( margin, mCellMargin ) )
525  {
526  return;
527  }
528 
529  mCellMargin = margin;
530 
531  //since spacing has changed, we need to recalculate the table size
533 
534  emit changed();
535 }
536 
538 {
539  if ( mode == mEmptyTableMode )
540  {
541  return;
542  }
543 
544  mEmptyTableMode = mode;
545 
546  //since appearance has changed, we need to recalculate the table size
548 
549  emit changed();
550 }
551 
552 void QgsLayoutTable::setEmptyTableMessage( const QString &message )
553 {
554  if ( message == mEmptyTableMessage )
555  {
556  return;
557  }
558 
559  mEmptyTableMessage = message;
560 
561  //since message has changed, we need to recalculate the table size
563 
564  emit changed();
565 }
566 
567 void QgsLayoutTable::setShowEmptyRows( const bool showEmpty )
568 {
569  if ( showEmpty == mShowEmptyRows )
570  {
571  return;
572  }
573 
574  mShowEmptyRows = showEmpty;
575  update();
576  emit changed();
577 }
578 
579 void QgsLayoutTable::setHeaderFont( const QFont &font )
580 {
581  if ( font == mHeaderFont )
582  {
583  return;
584  }
585 
586  mHeaderFont = font;
587  //since font attributes have changed, we need to recalculate the table size
589 
590  emit changed();
591 }
592 
593 void QgsLayoutTable::setHeaderFontColor( const QColor &color )
594 {
595  if ( color == mHeaderFontColor )
596  {
597  return;
598  }
599 
600  mHeaderFontColor = color;
601  update();
602 
603  emit changed();
604 }
605 
607 {
608  if ( alignment == mHeaderHAlignment )
609  {
610  return;
611  }
612 
613  mHeaderHAlignment = alignment;
614  update();
615 
616  emit changed();
617 }
618 
620 {
621  if ( mode == mHeaderMode )
622  {
623  return;
624  }
625 
626  mHeaderMode = mode;
628 
629  emit changed();
630 }
631 
632 void QgsLayoutTable::setContentFont( const QFont &font )
633 {
634  if ( font == mContentFont )
635  {
636  return;
637  }
638 
639  mContentFont = font;
640  //since font attributes have changed, we need to recalculate the table size
642 
643  emit changed();
644 }
645 
646 void QgsLayoutTable::setContentFontColor( const QColor &color )
647 {
648  if ( color == mContentFontColor )
649  {
650  return;
651  }
652 
653  mContentFontColor = color;
654  update();
655 
656  emit changed();
657 }
658 
660 {
661  if ( showGrid == mShowGrid )
662  {
663  return;
664  }
665 
667  //since grid spacing has changed, we need to recalculate the table size
669 
670  emit changed();
671 }
672 
673 void QgsLayoutTable::setGridStrokeWidth( const double width )
674 {
675  if ( qgsDoubleNear( width, mGridStrokeWidth ) )
676  {
677  return;
678  }
679 
680  mGridStrokeWidth = width;
681  //since grid spacing has changed, we need to recalculate the table size
683 
684  emit changed();
685 }
686 
687 void QgsLayoutTable::setGridColor( const QColor &color )
688 {
689  if ( color == mGridColor )
690  {
691  return;
692  }
693 
694  mGridColor = color;
695  update();
696 
697  emit changed();
698 }
699 
701 {
702  if ( horizontalGrid == mHorizontalGrid )
703  {
704  return;
705  }
706 
708  //since grid spacing has changed, we need to recalculate the table size
710 
711  emit changed();
712 }
713 
715 {
716  if ( verticalGrid == mVerticalGrid )
717  {
718  return;
719  }
720 
722  //since grid spacing has changed, we need to recalculate the table size
724 
725  emit changed();
726 }
727 
728 void QgsLayoutTable::setBackgroundColor( const QColor &color )
729 {
730  if ( color == mBackgroundColor )
731  {
732  return;
733  }
734 
735  mBackgroundColor = color;
736  update();
737 
738  emit changed();
739 }
740 
742 {
743  if ( behavior == mWrapBehavior )
744  {
745  return;
746  }
747 
748  mWrapBehavior = behavior;
750 
751  emit changed();
752 }
753 
755 {
756  //remove existing columns
757  qDeleteAll( mColumns );
758  mColumns.clear();
759 
760  mColumns.append( columns );
761 }
762 
764 {
765  if ( mCellStyles.contains( group ) )
766  delete mCellStyles.take( group );
767 
768  mCellStyles.insert( group, new QgsLayoutTableStyle( style ) );
769 }
770 
772 {
773  if ( !mCellStyles.contains( group ) )
774  return nullptr;
775 
776  return mCellStyles.value( group );
777 }
778 
779 QMap<int, QString> QgsLayoutTable::headerLabels() const
780 {
781  QMap<int, QString> headers;
782 
783  QgsLayoutTableColumns::const_iterator columnIt = mColumns.constBegin();
784  int col = 0;
785  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
786  {
787  headers.insert( col, ( *columnIt )->heading() );
788  col++;
789  }
790  return headers;
791 }
792 
794 {
795  Q_UNUSED( frameIndex );
796  return QSizeF( mTableSize.width(), 0 );
797 }
798 
799 QSizeF QgsLayoutTable::minFrameSize( const int frameIndex ) const
800 {
801  double height = 0;
802  if ( ( mHeaderMode == QgsLayoutTable::FirstFrame && frameIndex < 1 )
804  {
805  //header required, force frame to be high enough for header
807  }
808  return QSizeF( 0, height );
809 }
810 
812 {
813  mMaxColumnWidthMap.clear();
814  mMaxRowHeightMap.clear();
815  mTableContents.clear();
816 
817  //get new contents
819  {
820  return;
821  }
822 }
823 
825 {
826  mTableSize = QSizeF( totalWidth(), totalHeight() );
828 }
829 
830 void QgsLayoutTable::initStyles()
831 {
832  mCellStyles.insert( OddColumns, new QgsLayoutTableStyle() );
834  mCellStyles.insert( OddRows, new QgsLayoutTableStyle() );
835  mCellStyles.insert( EvenRows, new QgsLayoutTableStyle() );
837  mCellStyles.insert( LastColumn, new QgsLayoutTableStyle() );
838  mCellStyles.insert( HeaderRow, new QgsLayoutTableStyle() );
839  mCellStyles.insert( FirstRow, new QgsLayoutTableStyle() );
840  mCellStyles.insert( LastRow, new QgsLayoutTableStyle() );
841 
842  mCellStyleNames.insert( OddColumns, QStringLiteral( "oddColumns" ) );
843  mCellStyleNames.insert( EvenColumns, QStringLiteral( "evenColumns" ) );
844  mCellStyleNames.insert( OddRows, QStringLiteral( "oddRows" ) );
845  mCellStyleNames.insert( EvenRows, QStringLiteral( "evenRows" ) );
846  mCellStyleNames.insert( FirstColumn, QStringLiteral( "firstColumn" ) );
847  mCellStyleNames.insert( LastColumn, QStringLiteral( "lastColumn" ) );
848  mCellStyleNames.insert( HeaderRow, QStringLiteral( "headerRow" ) );
849  mCellStyleNames.insert( FirstRow, QStringLiteral( "firstRow" ) );
850  mCellStyleNames.insert( LastRow, QStringLiteral( "lastRow" ) );
851 }
852 
854 {
855  mMaxColumnWidthMap.clear();
856 
857  //total number of cells (rows + 1 for header)
858  int cols = mColumns.count();
859  int cells = cols * ( mTableContents.count() + 1 );
860  QVector< double > widths( cells );
861 
862  //first, go through all the column headers and calculate the sizes
863  QgsLayoutTableColumns::const_iterator columnIt = mColumns.constBegin();
864  int col = 0;
865  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
866  {
867  if ( ( *columnIt )->width() > 0 )
868  {
869  //column has manually specified width
870  widths[col] = ( *columnIt )->width();
871  }
873  {
874  widths[col] = QgsLayoutUtils::textWidthMM( mHeaderFont, ( *columnIt )->heading() );
875  }
876  else
877  {
878  widths[col] = 0.0;
879  }
880  col++;
881  }
882 
883  //next, go through all the table contents and calculate the sizes
884  QgsLayoutTableContents::const_iterator rowIt = mTableContents.constBegin();
885  double currentCellTextWidth;
886  int row = 1;
887  for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
888  {
889  QgsLayoutTableRow::const_iterator colIt = rowIt->constBegin();
890  col = 0;
891  for ( ; colIt != rowIt->constEnd(); ++colIt )
892  {
893  if ( mColumns.at( col )->width() <= 0 )
894  {
895  //column width set to automatic, so check content size
896  QStringList multiLineSplit = ( *colIt ).toString().split( '\n' );
897  currentCellTextWidth = 0;
898  Q_FOREACH ( const QString &line, multiLineSplit )
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  Q_FOREACH ( const QString &line, multiLineSplit )
1115  {
1116  currentTextWidth = std::max( currentTextWidth, QgsLayoutUtils::textWidthMM( font, line ) );
1117  }
1118 
1119  return ( currentTextWidth > columnWidth );
1120 }
1121 
1122 QString QgsLayoutTable::wrappedText( const QString &value, double columnWidth, const QFont &font ) const
1123 {
1124  QStringList lines = value.split( '\n' );
1125  QStringList outLines;
1126  Q_FOREACH ( const QString &line, lines )
1127  {
1128  if ( textRequiresWrapping( line, columnWidth, font ) )
1129  {
1130  //first step is to identify words which must be on their own line (too long to fit)
1131  QStringList words = line.split( ' ' );
1132  QStringList linesToProcess;
1133  QString wordsInCurrentLine;
1134  Q_FOREACH ( const QString &word, words )
1135  {
1136  if ( textRequiresWrapping( word, columnWidth, font ) )
1137  {
1138  //too long to fit
1139  if ( !wordsInCurrentLine.isEmpty() )
1140  linesToProcess << wordsInCurrentLine;
1141  wordsInCurrentLine.clear();
1142  linesToProcess << word;
1143  }
1144  else
1145  {
1146  if ( !wordsInCurrentLine.isEmpty() )
1147  wordsInCurrentLine.append( ' ' );
1148  wordsInCurrentLine.append( word );
1149  }
1150  }
1151  if ( !wordsInCurrentLine.isEmpty() )
1152  linesToProcess << wordsInCurrentLine;
1153 
1154  Q_FOREACH ( const QString &line, linesToProcess )
1155  {
1156  QString remainingText = line;
1157  int lastPos = remainingText.lastIndexOf( ' ' );
1158  while ( lastPos > -1 )
1159  {
1160  //check if remaining text is short enough to go in one line
1161  if ( !textRequiresWrapping( remainingText, columnWidth, font ) )
1162  {
1163  break;
1164  }
1165 
1166  if ( !textRequiresWrapping( remainingText.left( lastPos ), columnWidth, font ) )
1167  {
1168  outLines << remainingText.left( lastPos );
1169  remainingText = remainingText.mid( lastPos + 1 );
1170  lastPos = 0;
1171  }
1172  lastPos = remainingText.lastIndexOf( ' ', lastPos - 1 );
1173  }
1174  outLines << remainingText;
1175  }
1176  }
1177  else
1178  {
1179  outLines << line;
1180  }
1181  }
1182 
1183  return outLines.join( QStringLiteral( "\n" ) );
1184 }
1185 
1186 QColor QgsLayoutTable::backgroundColor( int row, int column ) const
1187 {
1188  QColor color = mBackgroundColor;
1189  if ( QgsLayoutTableStyle *style = mCellStyles.value( OddColumns ) )
1190  if ( style->enabled && column % 2 == 0 )
1191  color = style->cellBackgroundColor;
1192  if ( QgsLayoutTableStyle *style = mCellStyles.value( EvenColumns ) )
1193  if ( style->enabled && column % 2 == 1 )
1194  color = style->cellBackgroundColor;
1195  if ( QgsLayoutTableStyle *style = mCellStyles.value( OddRows ) )
1196  if ( style->enabled && row % 2 == 0 )
1197  color = style->cellBackgroundColor;
1198  if ( QgsLayoutTableStyle *style = mCellStyles.value( EvenRows ) )
1199  if ( style->enabled && row % 2 == 1 )
1200  color = style->cellBackgroundColor;
1201  if ( QgsLayoutTableStyle *style = mCellStyles.value( FirstColumn ) )
1202  if ( style->enabled && column == 0 )
1203  color = style->cellBackgroundColor;
1204  if ( QgsLayoutTableStyle *style = mCellStyles.value( LastColumn ) )
1205  if ( style->enabled && column == mColumns.count() - 1 )
1206  color = style->cellBackgroundColor;
1207  if ( QgsLayoutTableStyle *style = mCellStyles.value( HeaderRow ) )
1208  if ( style->enabled && row == -1 )
1209  color = style->cellBackgroundColor;
1210  if ( QgsLayoutTableStyle *style = mCellStyles.value( FirstRow ) )
1211  if ( style->enabled && row == 0 )
1212  color = style->cellBackgroundColor;
1213  if ( QgsLayoutTableStyle *style = mCellStyles.value( LastRow ) )
1214  if ( style->enabled && row == mTableContents.count() - 1 )
1215  color = style->cellBackgroundColor;
1216 
1217  return color;
1218 }
1219 
1220 void QgsLayoutTable::drawVerticalGridLines( QPainter *painter, const QMap<int, double> &maxWidthMap, int firstRow, int lastRow, bool hasHeader, bool mergeCells ) const
1221 {
1222  //vertical lines
1223  if ( lastRow - firstRow < 1 && !hasHeader )
1224  {
1225  return;
1226  }
1227 
1228  //calculate height of table within frame
1229  double tableHeight = 0;
1230  if ( hasHeader )
1231  {
1233  }
1234  tableHeight += ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
1235  double headerHeight = tableHeight;
1236 
1237  double cellBodyHeight = QgsLayoutUtils::fontAscentMM( mContentFont );
1238  for ( int row = firstRow; row < lastRow; ++row )
1239  {
1240  double rowHeight = row < mTableContents.count() ? mMaxRowHeightMap[row + 1] : cellBodyHeight;
1241  tableHeight += rowHeight + ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2;
1242  }
1243 
1244  double halfGridStrokeWidth = ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 ) / 2.0;
1245  double currentX = halfGridStrokeWidth;
1246  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, tableHeight - halfGridStrokeWidth ) );
1247  currentX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1248  QMap<int, double>::const_iterator maxColWidthIt = maxWidthMap.constBegin();
1249  int col = 1;
1250  for ( ; maxColWidthIt != maxWidthMap.constEnd(); ++maxColWidthIt )
1251  {
1252  currentX += ( maxColWidthIt.value() + 2 * mCellMargin );
1253  if ( col == maxWidthMap.size() || !mergeCells )
1254  {
1255  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, tableHeight - halfGridStrokeWidth ) );
1256  }
1257  else if ( hasHeader )
1258  {
1259  painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, headerHeight - halfGridStrokeWidth ) );
1260  }
1261 
1262  currentX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1263  col++;
1264  }
1265 }
1266 
1268 {
1270 
1271  //force recalculation of frame rects, so that they are set to the correct
1272  //fixed and minimum frame sizes
1274 }
1275 
1277 {
1278  return ( contents.indexOf( row ) >= 0 );
1279 }
1280 
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:265
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