QGIS API Documentation 4.3.0-Master (b46232e4f9a)
Loading...
Searching...
No Matches
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
20#include <set>
21
22#include "qgscolorutils.h"
24#include "qgsexpressionutils.h"
25#include "qgsfontutils.h"
26#include "qgslayoutframe.h"
30#include "qgslayoututils.h"
31#include "qgspainting.h"
32#include "qgstextrenderer.h"
33
34#include <QString>
35
36#include "moc_qgslayouttable.cpp"
37
38using namespace Qt::StringLiterals;
39
40//
41// QgsLayoutTableStyle
42//
43
44bool QgsLayoutTableStyle::writeXml( QDomElement &styleElem, QDomDocument &doc ) const
45{
46 Q_UNUSED( doc )
47 styleElem.setAttribute( u"cellBackgroundColor"_s, QgsColorUtils::colorToString( cellBackgroundColor ) );
48 styleElem.setAttribute( u"enabled"_s, enabled );
49 return true;
50}
51
52bool QgsLayoutTableStyle::readXml( const QDomElement &styleElem )
53{
54 cellBackgroundColor = QgsColorUtils::colorFromString( styleElem.attribute( u"cellBackgroundColor"_s, u"255,255,255,255"_s ) );
55 enabled = ( styleElem.attribute( u"enabled"_s, u"0"_s ) != "0"_L1 );
56 return true;
57}
58
59
60//
61// QgsLayoutTable
62//
63
69
71{
72 mColumns.clear();
73 mSortColumns.clear();
74
75 qDeleteAll( mCellStyles );
76 mCellStyles.clear();
77}
78
79bool QgsLayoutTable::writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const
80{
81 elem.setAttribute( u"cellMargin"_s, QString::number( mCellMargin ) );
82 elem.setAttribute( u"emptyTableMode"_s, QString::number( static_cast< int >( mEmptyTableMode ) ) );
83 elem.setAttribute( u"emptyTableMessage"_s, mEmptyTableMessage );
84 elem.setAttribute( u"showEmptyRows"_s, mShowEmptyRows );
85
86 QDomElement headerElem = doc.createElement( u"headerTextFormat"_s );
87 const QDomElement headerTextElem = mHeaderTextFormat.writeXml( doc, context );
88 headerElem.appendChild( headerTextElem );
89 elem.appendChild( headerElem );
90 elem.setAttribute( u"headerHAlignment"_s, QString::number( static_cast< int >( mHeaderHAlignment ) ) );
91 elem.setAttribute( u"headerMode"_s, QString::number( static_cast< int >( mHeaderMode ) ) );
92
93 QDomElement contentElem = doc.createElement( u"contentTextFormat"_s );
94 const QDomElement contentTextElem = mContentTextFormat.writeXml( doc, context );
95 contentElem.appendChild( contentTextElem );
96 elem.appendChild( contentElem );
97 elem.setAttribute( u"gridStrokeWidth"_s, QString::number( mGridStrokeWidth ) );
98 elem.setAttribute( u"gridColor"_s, QgsColorUtils::colorToString( mGridColor ) );
99 elem.setAttribute( u"horizontalGrid"_s, mHorizontalGrid );
100 elem.setAttribute( u"verticalGrid"_s, mVerticalGrid );
101 elem.setAttribute( u"showGrid"_s, mShowGrid );
102 elem.setAttribute( u"backgroundColor"_s, QgsColorUtils::colorToString( mBackgroundColor ) );
103 elem.setAttribute( u"wrapBehavior"_s, QString::number( static_cast< int >( mWrapBehavior ) ) );
104
105 // display columns
106 QDomElement displayColumnsElem = doc.createElement( u"displayColumns"_s );
107 for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
108 {
109 QDomElement columnElem = doc.createElement( u"column"_s );
110 column.writeXml( columnElem, doc );
111 displayColumnsElem.appendChild( columnElem );
112 }
113 elem.appendChild( displayColumnsElem );
114 // sort columns
115 QDomElement sortColumnsElem = doc.createElement( u"sortColumns"_s );
116 for ( const QgsLayoutTableColumn &column : std::as_const( mSortColumns ) )
117 {
118 QDomElement columnElem = doc.createElement( u"column"_s );
119 column.writeXml( columnElem, doc );
120 sortColumnsElem.appendChild( columnElem );
121 }
122 elem.appendChild( sortColumnsElem );
123
124
125 //cell styles
126 QDomElement stylesElem = doc.createElement( u"cellStyles"_s );
127 QMap< CellStyleGroup, QString >::const_iterator it = mCellStyleNames.constBegin();
128 for ( ; it != mCellStyleNames.constEnd(); ++it )
129 {
130 QString styleName = it.value();
131 QDomElement styleElem = doc.createElement( styleName );
132 QgsLayoutTableStyle *style = mCellStyles.value( it.key() );
133 if ( style )
134 {
135 style->writeXml( styleElem, doc );
136 stylesElem.appendChild( styleElem );
137 }
138 }
139 elem.appendChild( stylesElem );
140 return true;
141}
142
143bool QgsLayoutTable::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
144{
145 mEmptyTableMode = QgsLayoutTable::EmptyTableMode( itemElem.attribute( u"emptyTableMode"_s, u"0"_s ).toInt() );
146 mEmptyTableMessage = itemElem.attribute( u"emptyTableMessage"_s, tr( "No matching records" ) );
147 mShowEmptyRows = itemElem.attribute( u"showEmptyRows"_s, u"0"_s ).toInt();
148
149 const QDomElement headerTextFormat = itemElem.firstChildElement( u"headerTextFormat"_s );
150 if ( !headerTextFormat.isNull() )
151 {
152 QDomNodeList textFormatNodeList = headerTextFormat.elementsByTagName( u"text-style"_s );
153 QDomElement textFormatElem = textFormatNodeList.at( 0 ).toElement();
154 mHeaderTextFormat.readXml( textFormatElem, context );
155 }
156 else
157 {
158 QFont headerFont;
159 if ( !QgsFontUtils::setFromXmlChildNode( headerFont, itemElem, u"headerFontProperties"_s ) )
160 {
161 headerFont.fromString( itemElem.attribute( u"headerFont"_s, QString() ) );
162 }
163 QColor headerFontColor = QgsColorUtils::colorFromString( itemElem.attribute( u"headerFontColor"_s, u"0,0,0,255"_s ) );
164 mHeaderTextFormat.setFont( headerFont );
165 if ( headerFont.pointSizeF() > 0 )
166 {
167 mHeaderTextFormat.setSize( headerFont.pointSizeF() );
169 }
170 else if ( headerFont.pixelSize() > 0 )
171 {
172 mHeaderTextFormat.setSize( headerFont.pixelSize() );
174 }
176 }
177
178 mHeaderHAlignment = QgsLayoutTable::HeaderHAlignment( itemElem.attribute( u"headerHAlignment"_s, u"0"_s ).toInt() );
179 mHeaderMode = QgsLayoutTable::HeaderMode( itemElem.attribute( u"headerMode"_s, u"0"_s ).toInt() );
180
181 const QDomElement contentTextFormat = itemElem.firstChildElement( u"contentTextFormat"_s );
182 if ( !contentTextFormat.isNull() )
183 {
184 QDomNodeList textFormatNodeList = contentTextFormat.elementsByTagName( u"text-style"_s );
185 QDomElement textFormatElem = textFormatNodeList.at( 0 ).toElement();
186 mContentTextFormat.readXml( textFormatElem, context );
187 }
188 else
189 {
190 QFont contentFont;
191 if ( !QgsFontUtils::setFromXmlChildNode( contentFont, itemElem, u"contentFontProperties"_s ) )
192 {
193 contentFont.fromString( itemElem.attribute( u"contentFont"_s, QString() ) );
194 }
195 QColor contentFontColor = QgsColorUtils::colorFromString( itemElem.attribute( u"contentFontColor"_s, u"0,0,0,255"_s ) );
197 if ( contentFont.pointSizeF() > 0 )
198 {
199 mContentTextFormat.setSize( contentFont.pointSizeF() );
201 }
202 else if ( contentFont.pixelSize() > 0 )
203 {
204 mContentTextFormat.setSize( contentFont.pixelSize() );
206 }
208 }
209
210 mCellMargin = itemElem.attribute( u"cellMargin"_s, u"1.0"_s ).toDouble();
211 mGridStrokeWidth = itemElem.attribute( u"gridStrokeWidth"_s, u"0.5"_s ).toDouble();
212 mHorizontalGrid = itemElem.attribute( u"horizontalGrid"_s, u"1"_s ).toInt();
213 mVerticalGrid = itemElem.attribute( u"verticalGrid"_s, u"1"_s ).toInt();
214 mShowGrid = itemElem.attribute( u"showGrid"_s, u"1"_s ).toInt();
215 mGridColor = QgsColorUtils::colorFromString( itemElem.attribute( u"gridColor"_s, u"0,0,0,255"_s ) );
216 mBackgroundColor = QgsColorUtils::colorFromString( itemElem.attribute( u"backgroundColor"_s, u"255,255,255,0"_s ) );
217 mWrapBehavior = QgsLayoutTable::WrapBehavior( itemElem.attribute( u"wrapBehavior"_s, u"0"_s ).toInt() );
218
219 //restore display column specifications
220 mColumns.clear();
221 QDomNodeList columnsList = itemElem.elementsByTagName( u"displayColumns"_s );
222 if ( !columnsList.isEmpty() )
223 {
224 QDomElement columnsElem = columnsList.at( 0 ).toElement();
225 QDomNodeList columnEntryList = columnsElem.elementsByTagName( u"column"_s );
226 for ( int i = 0; i < columnEntryList.size(); ++i )
227 {
228 QDomElement columnElem = columnEntryList.at( i ).toElement();
230 column.readXml( columnElem );
231 mColumns.append( column );
232 }
233 }
234 // sort columns
235 mSortColumns.clear();
236 QDomNodeList sortColumnsList = itemElem.elementsByTagName( u"sortColumns"_s );
237 if ( !sortColumnsList.isEmpty() )
238 {
239 QDomElement columnsElem = sortColumnsList.at( 0 ).toElement();
240 QDomNodeList columnEntryList = columnsElem.elementsByTagName( u"column"_s );
241 for ( int i = 0; i < columnEntryList.size(); ++i )
242 {
243 QDomElement columnElem = columnEntryList.at( i ).toElement();
245 column.readXml( columnElem );
246 mSortColumns.append( column );
247 }
248 }
249 else
250 {
251 // backward compatibility for QGIS < 3.14
252 // copy the display columns if sortByRank > 0 and then, sort them by rank
254 std::copy_if( mColumns.begin(), mColumns.end(), std::back_inserter( mSortColumns ), []( const QgsLayoutTableColumn &col ) { return col.sortByRank() > 0; } );
255 std::sort( mSortColumns.begin(), mSortColumns.end(), []( const QgsLayoutTableColumn &a, const QgsLayoutTableColumn &b ) { return a.sortByRank() < b.sortByRank(); } );
257 }
258
259 //restore cell styles
260 QDomNodeList stylesList = itemElem.elementsByTagName( u"cellStyles"_s );
261 if ( !stylesList.isEmpty() )
262 {
263 QDomElement stylesElem = stylesList.at( 0 ).toElement();
264
265 QMap< CellStyleGroup, QString >::const_iterator it = mCellStyleNames.constBegin();
266 for ( ; it != mCellStyleNames.constEnd(); ++it )
267 {
268 QString styleName = it.value();
269 QDomNodeList styleList = stylesElem.elementsByTagName( styleName );
270 if ( !styleList.isEmpty() )
271 {
272 QDomElement styleElem = styleList.at( 0 ).toElement();
273 QgsLayoutTableStyle *style = mCellStyles.value( it.key() );
274 if ( style )
275 style->readXml( styleElem );
276 }
277 }
278 }
279
280 emit changed();
281 return true;
282}
283
285{
286 return mTableSize;
287}
288
294
295int QgsLayoutTable::rowsVisible( QgsRenderContext &context, double frameHeight, int firstRow, bool includeHeader, bool includeEmptyRows ) const
296{
297 //calculate header height
298 double headerHeight = 0;
299 if ( includeHeader )
300 {
301 headerHeight = mMaxRowHeightMap.value( 0 ) + 2 * ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin;
302 }
303 else
304 {
305 //frame has no header text, just the stroke
306 headerHeight = ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
307 }
308
309 //remaining height available for content rows
310 double contentHeight = frameHeight - headerHeight;
311
312 double gridHeight = ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
313
314 int currentRow = firstRow;
315 while ( contentHeight > 0 && currentRow <= mTableContents.count() )
316 {
317 double currentRowHeight = mMaxRowHeightMap.value( currentRow + 1 ) + gridHeight + 2 * mCellMargin;
318 contentHeight -= currentRowHeight;
319 currentRow++;
320 }
321
322 if ( includeEmptyRows && contentHeight > 0 )
323 {
324 const QFontMetricsF emptyRowContentFontMetrics = QgsTextRenderer::fontMetrics( context, mContentTextFormat, QgsTextRenderer::FONT_WORKAROUND_SCALE );
325 double rowHeight = ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 )
326 + 2 * mCellMargin
327 + emptyRowContentFontMetrics.ascent() / context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters ) / QgsTextRenderer::FONT_WORKAROUND_SCALE;
328 currentRow += std::max( std::floor( contentHeight / rowHeight ), 0.0 );
329 }
330
331 return currentRow - firstRow - 1;
332}
333
334int QgsLayoutTable::rowsVisible( QgsRenderContext &context, int frameIndex, int firstRow, bool includeEmptyRows ) const
335{
336 //get frame extent
337 if ( frameIndex >= frameCount() )
338 {
339 return 0;
340 }
341 QRectF frameExtent = frame( frameIndex )->extent();
342
343 bool includeHeader = false;
345 {
346 includeHeader = true;
347 }
348 return rowsVisible( context, frameExtent.height(), firstRow, includeHeader, includeEmptyRows );
349}
350
351QPair<int, int> QgsLayoutTable::rowRange( QgsRenderContext &context, const int frameIndex ) const
352{
353 //calculate row height
354 if ( frameIndex >= frameCount() )
355 {
356 //bad frame index
357 return qMakePair( 0, 0 );
358 }
359
360 //loop through all previous frames to calculate how many rows are visible in each
361 //as the entire height of a frame may not be utilized for content rows
362 int rowsAlreadyShown = 0;
363 for ( int idx = 0; idx < frameIndex; ++idx )
364 {
365 rowsAlreadyShown += rowsVisible( context, idx, rowsAlreadyShown, false );
366 }
367
368 //using zero based indexes
369 int firstVisible = std::min( rowsAlreadyShown, static_cast<int>( mTableContents.length() ) );
370 int possibleRowsVisible = rowsVisible( context, frameIndex, rowsAlreadyShown, false );
371 int lastVisible = std::min( firstVisible + possibleRowsVisible, static_cast<int>( mTableContents.length() ) );
372
373 return qMakePair( firstVisible, lastVisible );
374}
375
376void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &, const int frameIndex )
377{
378 bool emptyTable = mTableContents.length() == 0;
379 if ( emptyTable && mEmptyTableMode == QgsLayoutTable::HideTable )
380 {
381 //empty table set to hide table mode, so don't draw anything
382 return;
383 }
384
385 if ( !mLayout->renderContext().isPreviewRender() )
386 {
387 //exporting composition, so force an attribute refresh
388 //we do this in case vector layer has changed via an external source (e.g., another database user)
390 }
391
392 const bool prevTextFormatScaleFlag = context.renderContext().testFlag( Qgis::RenderContextFlag::ApplyScalingWorkaroundForTextRendering );
394
395 //calculate which rows to show in this frame
396 QPair< int, int > rowsToShow = rowRange( context.renderContext(), frameIndex );
397
398 double gridSizeX = mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0;
399 double gridSizeY = mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0;
400 double cellHeaderHeight = mMaxRowHeightMap[0] + 2 * mCellMargin;
401 double cellBodyHeightForEmptyRows = QgsTextRenderer::fontMetrics( context.renderContext(), mContentTextFormat, QgsTextRenderer::FONT_WORKAROUND_SCALE ).ascent()
404 + 2 * mCellMargin;
405 QRectF cell;
406
407 //calculate whether a header is required
408 bool drawHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && frameIndex < 1 ) || ( mHeaderMode == QgsLayoutTable::AllFrames ) );
409 //calculate whether drawing table contents is required
410 bool drawContents = !( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage );
411
412 int numberRowsToDraw = rowsToShow.second - rowsToShow.first;
413 int numberEmptyRows = 0;
414 if ( drawContents && mShowEmptyRows )
415 {
416 numberRowsToDraw = rowsVisible( context.renderContext(), frameIndex, rowsToShow.first, true );
417 numberEmptyRows = numberRowsToDraw - rowsToShow.second + rowsToShow.first;
418 }
419 bool mergeCells = false;
420 if ( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage )
421 {
422 //draw a merged row for the empty table message
423 numberRowsToDraw++;
424 rowsToShow.second++;
425 mergeCells = true;
426 }
427
428 QPainter *p = context.renderContext().painter();
429 QgsScopedQPainterState painterState( p );
430 // painter is scaled to dots, so scale back to layout units
431 p->scale( context.renderContext().scaleFactor(), context.renderContext().scaleFactor() );
432
433 //draw the text
434 p->setPen( Qt::SolidLine );
435
436 double currentX = gridSizeX;
437 double currentY = gridSizeY;
438 if ( drawHeader )
439 {
440 //draw the headers
441 int col = 0;
442 for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
443 {
444 auto headerCellScope = std::make_unique< QgsExpressionContextScope >();
445 headerCellScope->setVariable( u"column_number"_s, col + 1, true );
446 QgsExpressionContextScopePopper popper( context.renderContext().expressionContext(), headerCellScope.release() );
447
448 const QgsTextFormat headerFormat = textFormatForHeader( col );
449 //draw background
450 QgsScopedQPainterState painterState( p );
451 p->setPen( Qt::NoPen );
452 p->setBrush( backgroundColor( -1, col ) );
453 p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, cellHeaderHeight ) );
454 painterState.restore();
455
456 currentX += mCellMargin;
457
458 cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], cellHeaderHeight );
459
460 //calculate alignment of header
462 switch ( mHeaderHAlignment )
463 {
464 case FollowColumn:
465 headerAlign = QgsTextRenderer::convertQtHAlignment( column.hAlignment() );
466 break;
467 case HeaderLeft:
469 break;
470 case HeaderCenter:
472 break;
473 case HeaderRight:
475 break;
476 }
477
478 const QRectF textCell = QRectF( currentX, currentY + mCellMargin, mMaxColumnWidthMap[col], cellHeaderHeight - 2 * mCellMargin );
479
480 const QStringList str = column.heading().split( '\n' );
481
482 // scale to dots
483 {
486 QRectF(
487 textCell.left() * context.renderContext().scaleFactor(),
488 textCell.top() * context.renderContext().scaleFactor(),
489 textCell.width() * context.renderContext().scaleFactor(),
490 textCell.height() * context.renderContext().scaleFactor()
491 ),
492 0,
493 headerAlign,
494 str,
495 context.renderContext(),
496 headerFormat,
497 true,
500 );
501 }
502
503 currentX += mMaxColumnWidthMap[col];
504 currentX += mCellMargin;
505 currentX += gridSizeX;
506 col++;
507 }
508
509 currentY += cellHeaderHeight;
510 currentY += gridSizeY;
511 }
512
513 //now draw the body cells
514 int rowsDrawn = 0;
515 std::set< std::pair< int, int > > spannedCells;
516 if ( drawContents )
517 {
518 //draw the attribute values
519 for ( int row = rowsToShow.first; row < rowsToShow.second; ++row )
520 {
521 rowsDrawn++;
522 currentX = gridSizeX;
523 int col = 0;
524
525 for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
526 {
527 ( void ) column;
528
529 bool isSpanned = false;
530 QRectF fullCell;
531
532 double cellHeight = 0;
533 double cellWidth = 0;
534 const int rowsSpan = rowSpan( row, col );
535 const int colsSpan = columnSpan( row, col );
536 if ( spannedCells.find( std::make_pair( row, col ) ) != spannedCells.end() )
537 {
538 isSpanned = true;
539 }
540 else
541 {
542 for ( int spannedRow = row; spannedRow < row + rowsSpan; ++spannedRow )
543 {
544 cellHeight += mMaxRowHeightMap[spannedRow + 1] + 2 * mCellMargin + ( spannedRow > row ? gridSizeY : 0 );
545 for ( int spannedCol = col; spannedCol < col + colsSpan; ++spannedCol )
546 {
547 spannedCells.insert( std::make_pair( spannedRow, spannedCol ) );
548 }
549 }
550 for ( int spannedCol = col; spannedCol < col + colsSpan; ++spannedCol )
551 {
552 cellWidth += mMaxColumnWidthMap[spannedCol] + 2 * mCellMargin + ( spannedCol > col ? gridSizeX : 0 );
553 }
554 }
555
556 fullCell = QRectF( currentX, currentY, cellWidth, cellHeight );
557
558 if ( !isSpanned )
559 {
560 //draw background
561 QgsScopedQPainterState painterState( p );
562 p->setPen( Qt::NoPen );
563 p->setBrush( backgroundColor( row, col, rowsSpan, colsSpan ) );
564 p->drawRect( fullCell );
565 }
566
567 // currentY = gridSize;
568 currentX += mCellMargin;
569
570 if ( !isSpanned )
571 {
572 QVariant cellContents = mTableContents.at( row ).at( col );
573 const QString localizedString { QgsExpressionUtils::toLocalizedString( cellContents ) };
574 const QStringList str = localizedString.split( '\n' );
575
576 QgsTextFormat cellFormat = textFormatForCell( row, col );
578 cellFormat.updateDataDefinedProperties( context.renderContext() );
579
580 QgsScopedQPainterState painterState( p );
581 p->setClipRect( fullCell );
582 const QRectF textCell = QRectF( currentX, currentY + mCellMargin, cellWidth - 2 * mCellMargin, cellHeight - 2 * mCellMargin );
583
584 const QgsConditionalStyle style = conditionalCellStyle( row, col );
585 QColor foreColor = cellFormat.color();
586 if ( style.textColor().isValid() )
587 foreColor = style.textColor();
588
589 cellFormat.setColor( foreColor );
590
591 // scale to dots
592 {
595 QRectF(
596 textCell.left() * context.renderContext().scaleFactor(),
597 textCell.top() * context.renderContext().scaleFactor(),
598 textCell.width() * context.renderContext().scaleFactor(),
599 textCell.height() * context.renderContext().scaleFactor()
600 ),
601 0,
603 str,
604 context.renderContext(),
605 cellFormat,
606 true,
609 );
610 }
611 }
612
613 currentX += mMaxColumnWidthMap[col];
614 currentX += mCellMargin;
615 currentX += gridSizeX;
616 col++;
617 }
618 currentY += mMaxRowHeightMap[row + 1] + 2 * mCellMargin;
619 currentY += gridSizeY;
620 }
621 }
622
623 if ( numberRowsToDraw > rowsDrawn )
624 {
625 QgsScopedQPainterState painterState( p );
626 p->setPen( Qt::NoPen );
627
628 //draw background of empty rows
629 for ( int row = rowsDrawn; row < numberRowsToDraw; ++row )
630 {
631 currentX = gridSizeX;
632 int col = 0;
633
634 if ( mergeCells )
635 {
636 p->setBrush( backgroundColor( row + 10000, 0 ) );
637 p->drawRect( QRectF( gridSizeX, currentY, mTableSize.width() - 2 * gridSizeX, cellBodyHeightForEmptyRows ) );
638 }
639 else
640 {
641 for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
642 {
643 Q_UNUSED( column )
644
645 //draw background
646
647 //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
648 p->setBrush( backgroundColor( row + 10000, col ) );
649 p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, cellBodyHeightForEmptyRows ) );
650
651 // currentY = gridSize;
652 currentX += mMaxColumnWidthMap[col] + 2 * mCellMargin;
653 currentX += gridSizeX;
654 col++;
655 }
656 }
657 currentY += cellBodyHeightForEmptyRows + gridSizeY;
658 }
659 }
660
661 //and the borders
662 if ( mShowGrid )
663 {
664 QPen gridPen;
665 gridPen.setWidthF( mGridStrokeWidth );
666 gridPen.setColor( mGridColor );
667 gridPen.setJoinStyle( Qt::MiterJoin );
668 gridPen.setCapStyle( Qt::FlatCap );
669 p->setPen( gridPen );
670 if ( mHorizontalGrid )
671 {
672 drawHorizontalGridLines( context, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader );
673 }
674 if ( mVerticalGrid )
675 {
676 drawVerticalGridLines( context, mMaxColumnWidthMap, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader, mergeCells );
677 }
678 }
679
680 //special case - no records and table is set to ShowMessage mode
681 if ( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage )
682 {
683 double messageX = gridSizeX + mCellMargin;
684 double messageY = gridSizeY + ( drawHeader ? cellHeaderHeight + gridSizeY : 0 );
685 cell = QRectF( messageX, messageY, mTableSize.width() - messageX, cellBodyHeightForEmptyRows );
686
687 // scale to dots
688 {
691 QRectF(
692 cell.left() * context.renderContext().scaleFactor(),
693 cell.top() * context.renderContext().scaleFactor(),
694 cell.width() * context.renderContext().scaleFactor(),
695 cell.height() * context.renderContext().scaleFactor()
696 ),
697 0,
699 QStringList() << mEmptyTableMessage,
700 context.renderContext(),
702 true,
704 );
705 }
706 }
707
709}
710
711void QgsLayoutTable::setCellMargin( const double margin )
712{
713 if ( qgsDoubleNear( margin, mCellMargin ) )
714 {
715 return;
716 }
717
718 mCellMargin = margin;
719
720 //since spacing has changed, we need to recalculate the table size
722
723 emit changed();
724}
725
727{
728 if ( mode == mEmptyTableMode )
729 {
730 return;
731 }
732
733 mEmptyTableMode = mode;
734
735 //since appearance has changed, we need to recalculate the table size
737
738 emit changed();
739}
740
741void QgsLayoutTable::setEmptyTableMessage( const QString &message )
742{
743 if ( message == mEmptyTableMessage )
744 {
745 return;
746 }
747
748 mEmptyTableMessage = message;
749
750 //since message has changed, we need to recalculate the table size
752
753 emit changed();
754}
755
756void QgsLayoutTable::setShowEmptyRows( const bool showEmpty )
757{
758 if ( showEmpty == mShowEmptyRows )
759 {
760 return;
761 }
762
763 mShowEmptyRows = showEmpty;
764 update();
765 emit changed();
766}
767
768void QgsLayoutTable::setHeaderFont( const QFont &font )
769{
770 mHeaderTextFormat.setFont( font );
771 if ( font.pointSizeF() > 0 )
772 {
773 mHeaderTextFormat.setSize( font.pointSizeF() );
775 }
776 else if ( font.pixelSize() > 0 )
777 {
778 mHeaderTextFormat.setSize( font.pixelSize() );
780 }
781
782 //since font attributes have changed, we need to recalculate the table size
784
785 emit changed();
786}
787
789{
790 return mHeaderTextFormat.toQFont();
791}
792
793void QgsLayoutTable::setHeaderFontColor( const QColor &color )
794{
795 if ( color == mHeaderTextFormat.color() )
796 {
797 return;
798 }
799
800 mHeaderTextFormat.setColor( color );
801 update();
802
803 emit changed();
804}
805
807{
808 return mHeaderTextFormat.color();
809}
810
812{
813 mHeaderTextFormat = format;
814
815 //since font attributes have changed, we need to recalculate the table size
817
818 emit changed();
819}
820
825
827{
828 if ( alignment == mHeaderHAlignment )
829 {
830 return;
831 }
832
833 mHeaderHAlignment = alignment;
834 update();
835
836 emit changed();
837}
838
840{
841 if ( mode == mHeaderMode )
842 {
843 return;
844 }
845
846 mHeaderMode = mode;
848
849 emit changed();
850}
851
852void QgsLayoutTable::setContentFont( const QFont &font )
853{
854 mContentTextFormat.setFont( font );
855 if ( font.pointSizeF() > 0 )
856 {
857 mContentTextFormat.setSize( font.pointSizeF() );
859 }
860 else if ( font.pixelSize() > 0 )
861 {
862 mContentTextFormat.setSize( font.pixelSize() );
864 }
865
866 //since font attributes have changed, we need to recalculate the table size
868
869 emit changed();
870}
871
873{
874 return mContentTextFormat.toQFont();
875}
876
877void QgsLayoutTable::setContentFontColor( const QColor &color )
878{
879 if ( color == mContentTextFormat.color() )
880 {
881 return;
882 }
883
884 mContentTextFormat.setColor( color );
885 update();
886
887 emit changed();
888}
889
891{
892 return mContentTextFormat.color();
893}
894
896{
897 mContentTextFormat = format;
898
899 //since spacing has changed, we need to recalculate the table size
901
902 emit changed();
903}
904
909
911{
912 if ( showGrid == mShowGrid )
913 {
914 return;
915 }
916
918 //since grid spacing has changed, we need to recalculate the table size
920
921 emit changed();
922}
923
924void QgsLayoutTable::setGridStrokeWidth( const double width )
925{
926 if ( qgsDoubleNear( width, mGridStrokeWidth ) )
927 {
928 return;
929 }
930
931 mGridStrokeWidth = width;
932 //since grid spacing has changed, we need to recalculate the table size
934
935 emit changed();
936}
937
938void QgsLayoutTable::setGridColor( const QColor &color )
939{
940 if ( color == mGridColor )
941 {
942 return;
943 }
944
945 mGridColor = color;
946 update();
947
948 emit changed();
949}
950
952{
954 {
955 return;
956 }
957
959 //since grid spacing has changed, we need to recalculate the table size
961
962 emit changed();
963}
964
966{
968 {
969 return;
970 }
971
973 //since grid spacing has changed, we need to recalculate the table size
975
976 emit changed();
977}
978
979void QgsLayoutTable::setBackgroundColor( const QColor &color )
980{
981 if ( color == mBackgroundColor )
982 {
983 return;
984 }
985
986 mBackgroundColor = color;
987 update();
988
989 emit changed();
990}
991
993{
994 if ( behavior == mWrapBehavior )
995 {
996 return;
997 }
998
999 mWrapBehavior = behavior;
1001
1002 emit changed();
1003}
1004
1006{
1007 //remove existing columns
1008 mColumns = columns;
1009
1010 // backward compatibility
1011 // test if sorting is provided with the columns and call setSortColumns in such case
1012 QgsLayoutTableSortColumns newSortColumns;
1014 std::copy_if( mColumns.begin(), mColumns.end(), std::back_inserter( newSortColumns ), []( const QgsLayoutTableColumn &col ) { return col.sortByRank() > 0; } );
1015 if ( !newSortColumns.isEmpty() )
1016 {
1017 std::sort( newSortColumns.begin(), newSortColumns.end(), []( const QgsLayoutTableColumn &a, const QgsLayoutTableColumn &b ) { return a.sortByRank() < b.sortByRank(); } );
1018 setSortColumns( newSortColumns );
1019 }
1021}
1022
1027
1029{
1030 if ( mCellStyles.contains( group ) )
1031 delete mCellStyles.take( group );
1032
1033 mCellStyles.insert( group, new QgsLayoutTableStyle( style ) );
1034}
1035
1037{
1038 if ( !mCellStyles.contains( group ) )
1039 return nullptr;
1040
1041 return mCellStyles.value( group );
1042}
1043
1044QMap<int, QString> QgsLayoutTable::headerLabels() const
1045{
1046 QMap<int, QString> headers;
1047
1048 int i = 0;
1049 for ( const QgsLayoutTableColumn &col : std::as_const( mColumns ) )
1050 {
1051 headers.insert( i, col.heading() );
1052 i++;
1053 }
1054 return headers;
1055}
1056
1058{
1059 auto cellScope = std::make_unique< QgsExpressionContextScope >();
1060 cellScope->setVariable( u"row_number"_s, row + 1, true );
1061 cellScope->setVariable( u"column_number"_s, column + 1, true );
1062 return cellScope.release();
1063}
1064
1065int QgsLayoutTable::rowSpan( int, int ) const
1066{
1067 return 1;
1068}
1069
1070int QgsLayoutTable::columnSpan( int, int ) const
1071{
1072 return 1;
1073}
1074
1079
1081{
1082 Q_UNUSED( frameIndex )
1083 return QSizeF( mTableSize.width(), 0 );
1084}
1085
1087{
1090
1091 double height = 0;
1093 {
1094 //header required, force frame to be high enough for header
1095 for ( int col = 0; col < mColumns.size(); ++col )
1096 {
1097 height = std::max(
1098 height,
1099 2 * ( mShowGrid ? mGridStrokeWidth : 0 )
1100 + 2 * mCellMargin
1104 );
1105 }
1106 }
1107 return QSizeF( 0, height );
1108}
1109
1111{
1112 mMaxColumnWidthMap.clear();
1113 mMaxRowHeightMap.clear();
1114 mTableContents.clear();
1115
1116 //get new contents
1118 {
1119 return;
1120 }
1121}
1122
1128
1129void QgsLayoutTable::initStyles()
1130{
1133 mCellStyles.insert( OddRows, new QgsLayoutTableStyle() );
1134 mCellStyles.insert( EvenRows, new QgsLayoutTableStyle() );
1137 mCellStyles.insert( HeaderRow, new QgsLayoutTableStyle() );
1138 mCellStyles.insert( FirstRow, new QgsLayoutTableStyle() );
1139 mCellStyles.insert( LastRow, new QgsLayoutTableStyle() );
1140
1141 mCellStyleNames.insert( OddColumns, u"oddColumns"_s );
1142 mCellStyleNames.insert( EvenColumns, u"evenColumns"_s );
1143 mCellStyleNames.insert( OddRows, u"oddRows"_s );
1144 mCellStyleNames.insert( EvenRows, u"evenRows"_s );
1145 mCellStyleNames.insert( FirstColumn, u"firstColumn"_s );
1146 mCellStyleNames.insert( LastColumn, u"lastColumn"_s );
1147 mCellStyleNames.insert( HeaderRow, u"headerRow"_s );
1148 mCellStyleNames.insert( FirstRow, u"firstRow"_s );
1149 mCellStyleNames.insert( LastRow, u"lastRow"_s );
1150}
1151
1153{
1154 mMaxColumnWidthMap.clear();
1155
1156 //total number of cells (rows + 1 for header)
1157 int cols = mColumns.count();
1158 int cells = cols * ( mTableContents.count() + 1 );
1159 QVector< double > widths( cells );
1160
1161 double currentCellTextWidth;
1162
1165
1166 //first, go through all the column headers and calculate the sizes
1167 int i = 0;
1168 for ( const QgsLayoutTableColumn &col : std::as_const( mColumns ) )
1169 {
1170 if ( col.width() > 0 )
1171 {
1172 //column has manually specified width
1173 widths[i] = col.width();
1174 }
1176 {
1177 auto headerCellScope = std::make_unique< QgsExpressionContextScope >();
1178 headerCellScope->setVariable( u"column_number"_s, i + 1, true );
1179 QgsExpressionContextScopePopper popper( context.expressionContext(), headerCellScope.release() );
1180
1181 //column width set to automatic, so check content size
1182 const QStringList multiLineSplit = col.heading().split( '\n' );
1183 currentCellTextWidth = QgsTextRenderer::textWidth( context, textFormatForHeader( i ), multiLineSplit ) / context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters );
1184 widths[i] = currentCellTextWidth;
1185 }
1186 else
1187 {
1188 widths[i] = 0.0;
1189 }
1190 i++;
1191 }
1192
1193 //next, go through all the table contents and calculate the sizes
1194 QgsLayoutTableContents::const_iterator rowIt = mTableContents.constBegin();
1195 int row = 1;
1196 for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
1197 {
1198 QgsLayoutTableRow::const_iterator colIt = rowIt->constBegin();
1199 int col = 0;
1200 for ( ; colIt != rowIt->constEnd(); ++colIt )
1201 {
1202 if ( mColumns.at( col ).width() <= 0 )
1203 {
1204 //column width set to automatic, so check content size
1205 const QStringList multiLineSplit = QgsExpressionUtils::toLocalizedString( *colIt ).split( '\n' );
1206
1207 QgsTextFormat cellFormat = textFormatForCell( row - 1, col );
1208 QgsExpressionContextScopePopper popper( context.expressionContext(), scopeForCell( row - 1, col ) );
1209 cellFormat.updateDataDefinedProperties( context );
1210
1211 currentCellTextWidth = QgsTextRenderer::textWidth( context, cellFormat, multiLineSplit ) / context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters );
1212 widths[row * cols + col] = currentCellTextWidth;
1213 }
1214 else
1215 {
1216 widths[row * cols + col] = 0;
1217 }
1218
1219 col++;
1220 }
1221 row++;
1222 }
1223
1224 //calculate maximum
1225 for ( int col = 0; col < cols; ++col )
1226 {
1227 double maxColWidth = 0;
1228 for ( int row = 0; row < mTableContents.count() + 1; ++row )
1229 {
1230 maxColWidth = std::max( widths[row * cols + col], maxColWidth );
1231 }
1232 mMaxColumnWidthMap.insert( col, maxColWidth );
1233 }
1234
1235 return true;
1236}
1237
1239{
1240 mMaxRowHeightMap.clear();
1241
1242 //total number of cells (rows + 1 for header)
1243 int cols = mColumns.count();
1244 int cells = cols * ( mTableContents.count() + 1 );
1245 QVector< double > heights( cells );
1246
1249
1250 //first, go through all the column headers and calculate the sizes
1251 int i = 0;
1252 for ( const QgsLayoutTableColumn &col : std::as_const( mColumns ) )
1253 {
1254 auto headerCellScope = std::make_unique< QgsExpressionContextScope >();
1255 headerCellScope->setVariable( u"column_number"_s, i + 1, true );
1256 QgsExpressionContextScopePopper popper( context.expressionContext(), headerCellScope.release() );
1257
1258 const QgsTextFormat cellFormat = textFormatForHeader( i );
1259 const double headerDescentMm = QgsTextRenderer::fontMetrics( context, cellFormat, QgsTextRenderer::FONT_WORKAROUND_SCALE ).descent()
1262 //height
1264 {
1265 heights[i] = 0;
1266 }
1267 else
1268 {
1269 heights[i] = QgsTextRenderer::textHeight(
1270 context,
1271 cellFormat,
1272 QStringList() << col.heading(),
1274 nullptr,
1278 - headerDescentMm;
1279 }
1280 i++;
1281 }
1282
1283 //next, go through all the table contents and calculate the sizes
1284 QgsLayoutTableContents::const_iterator rowIt = mTableContents.constBegin();
1285 int row = 1;
1286 for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
1287 {
1288 QgsLayoutTableRow::const_iterator colIt = rowIt->constBegin();
1289 int i = 0;
1290 for ( ; colIt != rowIt->constEnd(); ++colIt )
1291 {
1292 QgsTextFormat cellFormat = textFormatForCell( row - 1, i );
1293 QgsExpressionContextScopePopper popper( context.expressionContext(), scopeForCell( row - 1, i ) );
1294 cellFormat.updateDataDefinedProperties( context );
1295 const double contentDescentMm = QgsTextRenderer::fontMetrics( context, cellFormat, QgsTextRenderer::FONT_WORKAROUND_SCALE ).descent()
1298 const QString localizedString { QgsExpressionUtils::toLocalizedString( *colIt ) };
1299
1300 heights[row * cols + i] = QgsTextRenderer::textHeight(
1301 context,
1302 cellFormat,
1303 QStringList() << localizedString.split( '\n' ),
1305 nullptr,
1309 - contentDescentMm;
1310
1311 i++;
1312 }
1313 row++;
1314 }
1315
1316 //calculate maximum
1317 for ( int row = 0; row < mTableContents.count() + 1; ++row )
1318 {
1319 double maxRowHeight = 0;
1320 for ( int col = 0; col < cols; ++col )
1321 {
1322 maxRowHeight = std::max( heights[row * cols + col], maxRowHeight );
1323 }
1324 mMaxRowHeightMap.insert( row, maxRowHeight );
1325 }
1326
1327 return true;
1328}
1329
1331{
1332 //check how much space each column needs
1333 if ( !calculateMaxColumnWidths() )
1334 {
1335 return 0;
1336 }
1337
1338 //adapt frame to total width
1339 double totalWidth = 0;
1340 QMap<int, double>::const_iterator maxColWidthIt = mMaxColumnWidthMap.constBegin();
1341 for ( ; maxColWidthIt != mMaxColumnWidthMap.constEnd(); ++maxColWidthIt )
1342 {
1343 totalWidth += maxColWidthIt.value();
1344 }
1345 totalWidth += ( 2 * mMaxColumnWidthMap.size() * mCellMargin );
1346 totalWidth += ( mMaxColumnWidthMap.size() + 1 ) * ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1347
1348 return totalWidth;
1349}
1350
1352{
1353 //check how much space each row needs
1354 if ( !calculateMaxRowHeights() )
1355 {
1356 return 0;
1357 }
1358
1359 double height = 0;
1360
1363
1364 //loop through all existing frames to calculate how many rows are visible in each
1365 //as the entire height of a frame may not be utilized for content rows
1366 int rowsAlreadyShown = 0;
1367 int numberExistingFrames = frameCount();
1368 int rowsVisibleInLastFrame = 0;
1369 double heightOfLastFrame = 0;
1370 for ( int idx = 0; idx < numberExistingFrames; ++idx )
1371 {
1372 bool hasHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && idx == 0 ) || ( mHeaderMode == QgsLayoutTable::AllFrames ) );
1373 heightOfLastFrame = frame( idx )->rect().height();
1374 rowsVisibleInLastFrame = rowsVisible( context, heightOfLastFrame, rowsAlreadyShown, hasHeader, false );
1375 rowsAlreadyShown += rowsVisibleInLastFrame;
1376 height += heightOfLastFrame;
1377 if ( rowsAlreadyShown >= mTableContents.length() )
1378 {
1379 //shown entire contents of table, nothing remaining
1380 return height;
1381 }
1382 }
1383
1384 //calculate how many rows left to show
1385 int remainingRows = mTableContents.length() - rowsAlreadyShown;
1386
1387 if ( remainingRows <= 0 )
1388 {
1389 //no remaining rows
1390 return height;
1391 }
1392
1394 {
1395 QgsLayoutItemPage *page = mLayout->pageCollection()->page( mLayout->pageCollection()->pageCount() - 1 );
1396 if ( page )
1397 heightOfLastFrame = page->sizeWithUnits().height();
1398 }
1399
1400 bool hasHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && numberExistingFrames < 1 ) || ( mHeaderMode == QgsLayoutTable::AllFrames ) );
1401
1402 int numberFramesMissing = 0;
1403 while ( remainingRows > 0 )
1404 {
1405 numberFramesMissing++;
1406
1407 rowsVisibleInLastFrame = rowsVisible( context, heightOfLastFrame, rowsAlreadyShown, hasHeader, false );
1408 if ( rowsVisibleInLastFrame < 1 )
1409 {
1410 //if no rows are visible in the last frame, calculation of missing frames
1411 //is impossible. So just return total height of existing frames
1412 return height;
1413 }
1414
1415 rowsAlreadyShown += rowsVisibleInLastFrame;
1416 remainingRows = mTableContents.length() - rowsAlreadyShown;
1417 }
1418
1419 //rows remain unshown -- how many extra frames would we need to complete the table?
1420 //assume all added frames are same size as final frame
1421 height += heightOfLastFrame * numberFramesMissing;
1422 return height;
1423}
1424
1425void QgsLayoutTable::drawHorizontalGridLines( QgsLayoutItemRenderContext &context, int firstRow, int lastRow, bool drawHeaderLines ) const
1426{
1427 //horizontal lines
1428 if ( lastRow - firstRow < 1 && !drawHeaderLines )
1429 {
1430 return;
1431 }
1432
1433 QPainter *painter = context.renderContext().painter();
1434
1435 double cellBodyHeightForEmptyRows = QgsTextRenderer::fontMetrics( context.renderContext(), mContentTextFormat, QgsTextRenderer::FONT_WORKAROUND_SCALE ).ascent()
1438 double halfGridStrokeWidth = ( mShowGrid ? mGridStrokeWidth : 0 ) / 2.0;
1439 double currentY = halfGridStrokeWidth;
1440 if ( drawHeaderLines )
1441 {
1442 painter->drawLine( QPointF( 0, currentY ), QPointF( mTableSize.width(), currentY ) );
1443 currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
1444 currentY += mMaxRowHeightMap[0] + 2 * mCellMargin;
1445 }
1446
1447 QHash< QPair< int, int >, bool > skippedCellBottomBorders;
1448 for ( int row = 0; row < lastRow; ++row )
1449 {
1450 for ( int col = 0; col < mColumns.size(); ++col )
1451 {
1452 if ( skippedCellBottomBorders.constFind( qMakePair( row, col ) ) != skippedCellBottomBorders.constEnd() )
1453 continue;
1454
1455 const int rowsSpan = rowSpan( row, col );
1456 const int colsSpan = columnSpan( row, col );
1457 skippedCellBottomBorders.insert( qMakePair( row, col ), rowsSpan > 1 );
1458 for ( int rowDelta = 0; rowDelta < rowsSpan - 1; ++rowDelta )
1459 {
1460 for ( int colDelta = 0; colDelta < colsSpan; ++colDelta )
1461 {
1462 if ( rowDelta != 0 || colDelta != 0 )
1463 skippedCellBottomBorders.insert( qMakePair( row + rowDelta, col + colDelta ), true );
1464 }
1465 }
1466 }
1467 }
1468
1469 for ( int row = firstRow; row < lastRow; ++row )
1470 {
1471 double startX = 0;
1472 double endX = startX;
1473
1474 for ( int col = 0; col < mColumns.size(); ++col )
1475 {
1476 const double colWidth = mMaxColumnWidthMap.value( col ) + 2 * mCellMargin;
1477
1478 if ( skippedCellBottomBorders.value( qMakePair( row - 1, col ) ) )
1479 {
1480 // flush existing line
1481 if ( !qgsDoubleNear( startX, endX ) )
1482 {
1483 painter->drawLine( QPointF( startX, currentY ), QPointF( endX, currentY ) );
1484 }
1485 endX += colWidth;
1486 endX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1487 startX = endX;
1488 }
1489 else
1490 {
1491 endX += colWidth;
1492 endX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1493 }
1494 }
1495
1496 // flush last line
1497 if ( !qgsDoubleNear( startX, endX ) )
1498 {
1499 painter->drawLine( QPointF( startX, currentY ), QPointF( endX, currentY ) );
1500 }
1501
1502 currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
1503 double rowHeight = row < mTableContents.count() ? mMaxRowHeightMap[row + 1] : cellBodyHeightForEmptyRows;
1504 currentY += ( rowHeight + 2 * mCellMargin );
1505 }
1506 painter->drawLine( QPointF( 0, currentY ), QPointF( mTableSize.width(), currentY ) );
1507}
1508
1509QColor QgsLayoutTable::backgroundColor( int row, int column, int rowSpan, int columnSpan ) const
1510{
1511 QColor color = mBackgroundColor;
1512 if ( QgsLayoutTableStyle *style = mCellStyles.value( OddColumns ) )
1513 if ( style->enabled && column % 2 == 0 )
1514 color = style->cellBackgroundColor;
1515 if ( QgsLayoutTableStyle *style = mCellStyles.value( EvenColumns ) )
1516 if ( style->enabled && column % 2 == 1 )
1517 color = style->cellBackgroundColor;
1518 if ( QgsLayoutTableStyle *style = mCellStyles.value( OddRows ) )
1519 if ( style->enabled && row % 2 == 0 )
1520 color = style->cellBackgroundColor;
1521 if ( QgsLayoutTableStyle *style = mCellStyles.value( EvenRows ) )
1522 if ( style->enabled && row % 2 == 1 )
1523 color = style->cellBackgroundColor;
1524 if ( QgsLayoutTableStyle *style = mCellStyles.value( FirstColumn ) )
1525 if ( style->enabled && column == 0 )
1526 color = style->cellBackgroundColor;
1527 if ( QgsLayoutTableStyle *style = mCellStyles.value( LastColumn ) )
1528 if ( style->enabled && ( column + columnSpan == mColumns.count() ) )
1529 color = style->cellBackgroundColor;
1530 if ( QgsLayoutTableStyle *style = mCellStyles.value( HeaderRow ) )
1531 if ( style->enabled && row == -1 )
1532 color = style->cellBackgroundColor;
1533 if ( QgsLayoutTableStyle *style = mCellStyles.value( FirstRow ) )
1534 if ( style->enabled && row == 0 )
1535 color = style->cellBackgroundColor;
1536 if ( QgsLayoutTableStyle *style = mCellStyles.value( LastRow ) )
1537 if ( style->enabled && ( row + rowSpan == mTableContents.count() ) )
1538 color = style->cellBackgroundColor;
1539
1540 if ( row >= 0 )
1541 {
1542 QgsConditionalStyle conditionalStyle = conditionalCellStyle( row, column );
1543 if ( conditionalStyle.backgroundColor().isValid() )
1544 color = conditionalStyle.backgroundColor();
1545 }
1546
1547 return color;
1548}
1549
1550void QgsLayoutTable::drawVerticalGridLines( QgsLayoutItemRenderContext &context, const QMap<int, double> &maxWidthMap, int firstRow, int lastRow, bool hasHeader, bool mergeCells ) const
1551{
1552 //vertical lines
1553 if ( lastRow - firstRow < 1 && !hasHeader )
1554 {
1555 return;
1556 }
1557
1558 QHash< QPair< int, int >, bool > skippedCellRightBorders;
1559 for ( int row = 0; row < lastRow; ++row )
1560 {
1561 for ( int col = 0; col < mColumns.size(); ++col )
1562 {
1563 if ( skippedCellRightBorders.constFind( qMakePair( row, col ) ) != skippedCellRightBorders.constEnd() )
1564 continue;
1565
1566 const int rowsSpan = rowSpan( row, col );
1567 const int colsSpan = columnSpan( row, col );
1568 skippedCellRightBorders.insert( qMakePair( row, col ), colsSpan > 1 );
1569
1570 for ( int colDelta = 0; colDelta < colsSpan - 1; ++colDelta )
1571 {
1572 for ( int rowDelta = 0; rowDelta < rowsSpan; ++rowDelta )
1573 {
1574 if ( rowDelta != 0 || colDelta != 0 )
1575 skippedCellRightBorders.insert( qMakePair( row + rowDelta, col + colDelta ), true );
1576 }
1577 }
1578 }
1579 }
1580
1581 QPainter *painter = context.renderContext().painter();
1582
1583 //calculate height of table within frame
1584 double tableHeight = 0;
1585 QList< double > rowHeights;
1586 if ( hasHeader )
1587 {
1588 rowHeights << mCellMargin * 2 + mMaxRowHeightMap[0];
1589 tableHeight += ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2 + mMaxRowHeightMap[0];
1590 }
1591 else
1592 {
1593 rowHeights << 0;
1594 }
1595 tableHeight += ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
1596 double headerHeight = tableHeight;
1597
1598 double cellBodyHeightForEmptyRows = QgsTextRenderer::fontMetrics( context.renderContext(), mContentTextFormat, QgsTextRenderer::FONT_WORKAROUND_SCALE ).ascent()
1601 for ( int row = firstRow; row < lastRow; ++row )
1602 {
1603 double rowHeight = row < mTableContents.count() ? mMaxRowHeightMap[row + 1] : cellBodyHeightForEmptyRows;
1604 rowHeights << rowHeight + mCellMargin * 2;
1605 tableHeight += rowHeight + ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2;
1606 }
1607
1608 double currentX = ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 ) / 2.0;
1609 ;
1610 // left border of table
1611 painter->drawLine( QPointF( currentX, 0 ), QPointF( currentX, tableHeight ) );
1612 currentX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1613 QMap<int, double>::const_iterator maxColWidthIt = maxWidthMap.constBegin();
1614 int col = 1;
1615 for ( ; maxColWidthIt != maxWidthMap.constEnd(); ++maxColWidthIt )
1616 {
1617 currentX += ( maxColWidthIt.value() + 2 * mCellMargin );
1618 if ( col == maxWidthMap.size() )
1619 {
1620 // right border of table, always drawn
1621 painter->drawLine( QPointF( currentX, 0 ), QPointF( currentX, tableHeight ) );
1622 }
1623 else
1624 {
1625 if ( !mergeCells )
1626 {
1627 double startY = 0;
1628 double endY = startY + ( hasHeader ? ( rowHeights.value( 0 ) + ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) ) : 0 );
1629 for ( int row = firstRow; row < lastRow; ++row )
1630 {
1631 const double rowHeight = rowHeights.value( row - firstRow + 1 );
1632 if ( skippedCellRightBorders.value( qMakePair( row, col - 1 ) ) )
1633 {
1634 // flush existing line
1635 if ( !qgsDoubleNear( startY, endY ) )
1636 {
1637 painter->drawLine( QPointF( currentX, startY ), QPointF( currentX, endY ) );
1638 }
1639 endY += rowHeight;
1640 endY += ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
1641 startY = endY;
1642 }
1643 else
1644 {
1645 endY += rowHeight;
1646 endY += ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
1647 }
1648 }
1649
1650 // flush last line
1651 if ( !qgsDoubleNear( startY, endY ) )
1652 {
1653 painter->drawLine( QPointF( currentX, startY ), QPointF( currentX, endY ) );
1654 }
1655 }
1656 else if ( hasHeader )
1657 {
1658 painter->drawLine( QPointF( currentX, 0 ), QPointF( currentX, headerHeight ) );
1659 }
1660 }
1661
1662 currentX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1663 col++;
1664 }
1665}
1666
1668{
1670
1671 //force recalculation of frame rects, so that they are set to the correct
1672 //fixed and minimum frame sizes
1674}
1675
1677{
1678 return ( contents.indexOf( row ) >= 0 );
1679}
1680
1682{
1683 return mContentTextFormat;
1684}
1685
1690
1691Qt::Alignment QgsLayoutTable::horizontalAlignmentForCell( int, int column ) const
1692{
1693 return mColumns.value( column ).hAlignment();
1694}
1695
1696Qt::Alignment QgsLayoutTable::verticalAlignmentForCell( int, int column ) const
1697{
1698 return mColumns.value( column ).vAlignment();
1699}
@ Rectangle
Text within rectangle layout mode.
Definition qgis.h:3127
QFlags< TextRendererFlag > TextRendererFlags
Definition qgis.h:3649
@ Millimeters
Millimeters.
Definition qgis.h:5759
@ Points
Points (e.g., for font sizes).
Definition qgis.h:5763
@ Pixels
Pixels.
Definition qgis.h:5761
@ ApplyScalingWorkaroundForTextRendering
Whether a scaling workaround designed to stablise the rendering of small font sizes (or for painters ...
Definition qgis.h:2978
@ VerticalCenter
Center align.
Definition qgis.h:3188
TextHorizontalAlignment
Text horizontal alignment.
Definition qgis.h:3167
@ Center
Center align.
Definition qgis.h:3169
@ WrapLines
Automatically wrap long lines of text.
Definition qgis.h:3646
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
Conditional styling for a rule.
QColor backgroundColor() const
The background color for style.
QColor textColor() const
The text color set for style.
RAII class to pop scope from an expression context on destruction.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static bool setFromXmlChildNode(QFont &font, const QDomElement &element, const QString &childNode)
Sets the properties of a font to match the properties stored in an XML child node.
QRectF extent() const
Returns the visible portion of the multi frame's content which is shown in this frame,...
Item representing the paper in a layout.
Contains settings and helpers relating to a render of a QgsLayoutItem.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
QgsLayoutSize sizeWithUnits() const
Returns the item's current size, including units.
int frameCount() const
Returns the number of frames associated with this multiframe.
QgsLayoutMultiFrame(QgsLayout *layout)
Construct a new multiframe item, attached to the specified layout.
QgsLayoutFrame * frame(int index) const
Returns the child frame at a specified index from the multiframe.
@ ExtendToNextPage
Creates new full page frames on the following page(s) until the entire multiframe content is visible.
void refresh() override
Refreshes the multiframe, causing a recalculation of any property overrides.
virtual void recalculateFrameSizes()
Recalculates the portion of the multiframe item which is shown in each of its component frames.
void update()
Forces a redraw of all child frames.
int frameIndex(QgsLayoutFrame *frame) const
Returns the index of a frame within the multiframe.
void recalculateFrameRects()
Forces a recalculation of all the associated frame's scene rectangles.
const QgsLayout * layout() const
Returns the layout the object is attached to.
void changed()
Emitted when the object's properties change.
QPointer< QgsLayout > mLayout
double height() const
Returns the height of the size.
Stores properties of a column for a QgsLayoutTable.
bool readXml(const QDomElement &columnElem)
Reads the column's properties from xml.
Styling option for a layout table cell.
bool readXml(const QDomElement &styleElem)
Reads the style's properties from XML.
QColor cellBackgroundColor
Cell background color.
bool enabled
Whether the styling option is enabled.
bool writeXml(QDomElement &styleElem, QDomDocument &doc) const
Writes the style's properties to XML for storage.
bool mHorizontalGrid
True if grid should be shown.
QSizeF fixedFrameSize(int frameIndex=-1) const override
Returns the fixed size for a frame, if desired.
QgsLayoutTableColumns & columns()
Returns a reference to the list of QgsLayoutTableColumns shown in the table.
void setColumns(const QgsLayoutTableColumns &columns)
Replaces the columns in the table with a specified list of QgsLayoutTableColumns.
Q_DECL_DEPRECATED void setHeaderFontColor(const QColor &color)
Sets the color used to draw header text in the table.
QgsLayoutTable(QgsLayout *layout)
Constructor for QgsLayoutTable, belonging to the specified layout.
virtual Qt::Alignment horizontalAlignmentForCell(int row, int column) const
Returns the horizontal alignment to use for the cell at the specified row and column.
QColor backgroundColor() const
Returns the color used for the background of the table.
virtual QgsConditionalStyle conditionalCellStyle(int row, int column) const
Returns the conditional style to use for the cell at row, column.
void setHeaderHAlignment(HeaderHAlignment alignment)
Sets the horizontal alignment for table headers.
void setShowEmptyRows(bool showEmpty)
Sets whether empty rows should be drawn.
Q_DECL_DEPRECATED QFont headerFont() const
Returns the font used to draw header text in the table.
QMap< int, double > mMaxColumnWidthMap
Map of maximum width for each column.
void setVerticalGrid(bool verticalGrid)
Sets whether the grid's vertical lines should be drawn in the table.
virtual void refreshAttributes()
Refreshes the contents shown in the table by querying for new data.
void setGridColor(const QColor &color)
Sets the color used for grid lines in the table.
double totalWidth()
Returns total width of table contents.
bool horizontalGrid() const
Returns whether the grid's horizontal lines are drawn in the table.
Q_DECL_DEPRECATED void setContentFontColor(const QColor &color)
Sets the color used to draw text in table body cells.
void setCellMargin(double margin)
Sets the margin distance in mm between cell borders and their contents.
void render(QgsLayoutItemRenderContext &context, const QRectF &renderExtent, int frameIndex) override
Renders a portion of the multiframe's content into a render context.
Q_DECL_DEPRECATED void setHeaderFont(const QFont &font)
Sets the font used to draw header text in the table.
void drawVerticalGridLines(QgsLayoutItemRenderContext &context, const QMap< int, double > &maxWidthMap, int firstRow, int lastRow, bool hasHeader, bool mergeCells=false) const
Draws the vertical grid lines for the table.
QgsLayoutTableSortColumns & sortColumns()
Returns a reference to the list of QgsLayoutTableSortColumns shown in the table.
void setBackgroundColor(const QColor &color)
Sets the color used for background of table.
void setContentTextFormat(const QgsTextFormat &format)
Sets the format used to draw content text in the table.
QgsTextFormat contentTextFormat() const
Returns the format used to draw content text in the table.
QString mEmptyTableMessage
String to show in empty tables.
virtual int rowSpan(int row, int column) const
Returns the row span for the cell a row, column.
void setWrapBehavior(WrapBehavior behavior)
Sets the wrap behavior for the table, which controls how text within cells is automatically wrapped.
Q_DECL_DEPRECATED QColor headerFontColor() const
Returns the color used to draw header text in the table.
QPair< int, int > rowRange(QgsRenderContext &context, int frameIndex) const
Calculates a range of rows which should be visible in a given frame.
double mGridStrokeWidth
Width of grid lines.
EmptyTableMode mEmptyTableMode
Behavior for empty tables.
void setEmptyTableBehavior(EmptyTableMode mode)
Sets the behavior mode for empty tables with no content rows.
virtual QgsTextFormat textFormatForHeader(int column) const
Returns the text format to use for the header cell at the specified column.
void setHeaderMode(HeaderMode mode)
Sets the display mode for headers in the table.
Q_DECL_DEPRECATED void setContentFont(const QFont &font)
Sets the font used to draw text in table body cells.
WrapBehavior mWrapBehavior
bool verticalGrid() const
Returns whether the grid's vertical lines are drawn in the table.
QColor mBackgroundColor
Color for table background.
void recalculateTableSize()
Recalculates and updates the size of the table and all table frames.
void drawHorizontalGridLines(QgsLayoutItemRenderContext &context, int firstRow, int lastRow, bool drawHeaderLines) const
Draws the horizontal grid lines for the table.
virtual QgsExpressionContextScope * scopeForCell(int row, int column) const
Creates a new QgsExpressionContextScope for the cell at row, column.
Q_DECL_DEPRECATED QFont contentFont() const
Returns the font used to draw text in table body cells.
bool contentsContainsRow(const QgsLayoutTableContents &contents, const QgsLayoutTableRow &row) const
Checks whether a table contents contains a given row.
bool mShowGrid
True if grid should be shown.
CellStyleGroup
Row or column groups for cell styling.
@ FirstRow
Style first row only.
@ EvenColumns
Style even numbered columns.
@ EvenRows
Style even numbered rows.
@ HeaderRow
Style header row.
@ OddColumns
Style odd numbered columns.
@ FirstColumn
Style first column only.
@ LastColumn
Style last column only.
@ LastRow
Style last row only.
@ OddRows
Style odd numbered rows.
QgsTextFormat mHeaderTextFormat
void setHorizontalGrid(bool horizontalGrid)
Sets whether the grid's horizontal lines should be drawn in the table.
HeaderMode
Controls where headers are shown in the table.
@ FirstFrame
Header shown on first frame only.
@ AllFrames
Headers shown on all frames.
@ NoHeaders
No headers shown for table.
void setCellStyle(CellStyleGroup group, const QgsLayoutTableStyle &style)
Sets the cell style for a cell group.
QgsLayoutTableColumns mColumns
Columns to show in table.
const QgsLayoutTableStyle * cellStyle(CellStyleGroup group) const
Returns the cell style for a cell group.
void setShowGrid(bool showGrid)
Sets whether grid lines should be drawn in the table.
QgsTextFormat mContentTextFormat
virtual bool getTableContents(QgsLayoutTableContents &contents)=0
Fetches the contents used for the cells in the table.
virtual bool calculateMaxColumnWidths()
Calculates the maximum width of text shown in columns.
~QgsLayoutTable() override
QgsTextFormat headerTextFormat() const
Returns the format used to draw header text in the table.
HeaderMode mHeaderMode
Header display mode.
HeaderHAlignment mHeaderHAlignment
Alignment for table headers.
int rowsVisible(QgsRenderContext &context, double frameHeight, int firstRow, bool includeHeader, bool includeEmptyRows) const
Calculates how many content rows would be visible within a frame of the specified height.
void recalculateFrameSizes() override
bool mShowEmptyRows
True if empty rows should be shown in the table.
QColor mGridColor
Color for grid lines.
QSizeF minFrameSize(int frameIndex=-1) const override
Returns the minimum size for a frames, if desired.
double mCellMargin
Margin between cell borders and cell text.
virtual Qt::Alignment verticalAlignmentForCell(int row, int column) const
Returns the vertical alignment to use for the cell at the specified row and column.
Q_DECL_DEPRECATED QColor contentFontColor() const
Returns the color used to draw text in table body cells.
double totalHeight()
Returns total height of table contents.
QgsLayoutTableContents mTableContents
Contents to show in table.
bool writePropertiesToElement(QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context) const override
Stores multiframe state within an XML DOM element.
void setEmptyTableMessage(const QString &message)
Sets the message for empty tables with no content rows.
virtual int columnSpan(int row, int column) const
Returns the column span for the cell a row, column.
void setSortColumns(const QgsLayoutTableSortColumns &sortColumns)
Replaces the sorting columns in the table with a specified list of QgsLayoutTableSortColumns.
void setHeaderTextFormat(const QgsTextFormat &format)
Sets the format used to draw header text in the table.
QgsLayoutTableSortColumns mSortColumns
Columns to sort the table.
bool showGrid() const
Returns whether grid lines are drawn in the table.
virtual QMap< int, QString > headerLabels() const
Returns the text used in the column headers for the table.
void setGridStrokeWidth(double width)
Sets the width in mm for grid lines in the table.
virtual QgsTextFormat textFormatForCell(int row, int column) const
Returns the text format to use for the cell at the specified row and column.
bool readPropertiesFromElement(const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context) override
Sets multiframe state from a DOM element.
QgsLayoutTableContents & contents()
Returns the current contents of the table.
QSizeF totalSize() const override
Returns the total size of the multiframe's content, in layout units.
bool mVerticalGrid
True if grid should be shown.
virtual bool calculateMaxRowHeights()
Calculates the maximum height of text shown in rows.
WrapBehavior
Controls how long strings in the table are handled.
@ WrapText
Text which doesn't fit inside the cell is wrapped. Note that this only applies to text in columns wit...
HeaderHAlignment
Controls how headers are horizontally aligned in a table.
@ HeaderRight
Align headers right.
@ HeaderLeft
Align headers left.
@ HeaderCenter
Align headers to center.
@ FollowColumn
Header uses the same alignment as the column.
EmptyTableMode
Controls how empty tables are displayed.
@ HideTable
Hides entire table if empty.
@ ShowMessage
Shows preset message instead of table contents.
void refresh() override
QMap< int, double > mMaxRowHeightMap
Map of maximum height for each row.
QMap< CellStyleGroup, QgsLayoutTableStyle * > mCellStyles
static QgsRenderContext createRenderContextForLayout(QgsLayout *layout, QPainter *painter, double dpi=-1)
Creates a render context suitable for the specified layout and painter destination.
A container for the context for various read/write operations on objects.
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
bool testFlag(Qgis::RenderContextFlag flag) const
Check whether a particular flag is enabled.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected).
Scoped object for saving and restoring a QPainter object's state.
void restore()
Performs a manual restore on the painter state, popping the latest state from the painter.
Scoped object for temporary scaling of a QgsRenderContext for pixel based rendering.
Container for all settings relating to text rendering.
void setColor(const QColor &color)
Sets the color that text will be rendered in.
void updateDataDefinedProperties(QgsRenderContext &context)
Updates the format by evaluating current values of data defined properties.
QColor color() const
Returns the color that text will be rendered in.
static Qgis::TextVerticalAlignment convertQtVAlignment(Qt::Alignment alignment)
Converts a Qt vertical alignment flag to a Qgis::TextVerticalAlignment value.
static double textWidth(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF *fontMetrics=nullptr)
Returns the width of a text based on a given format.
static void drawText(const QRectF &rect, double rotation, Qgis::TextHorizontalAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, Qgis::TextVerticalAlignment vAlignment=Qgis::TextVerticalAlignment::Top, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Rectangle)
Draws text within a rectangle using the specified settings.
static QFontMetricsF fontMetrics(QgsRenderContext &context, const QgsTextFormat &format, double scaleFactor=1.0)
Returns the font metrics for the given text format, when rendered in the specified render context.
static double textHeight(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Point, QFontMetricsF *fontMetrics=nullptr, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), double maxLineWidth=0)
Returns the height of a text based on a given format.
static constexpr double FONT_WORKAROUND_SCALE
Scale factor for upscaling font sizes and downscaling destination painter devices.
static Qgis::TextHorizontalAlignment convertQtHAlignment(Qt::Alignment alignment)
Converts a Qt horizontal alignment flag to a Qgis::TextHorizontalAlignment value.
QVector< QgsLayoutTableColumn > QgsLayoutTableColumns
List of column definitions for a QgsLayoutTable.
QVector< QgsLayoutTableColumn > QgsLayoutTableSortColumns
List of column definitions for sorting a QgsLayoutTable.
QVector< QgsLayoutTableRow > QgsLayoutTableContents
List of QgsLayoutTableRows, representing rows and column cell contents for a QgsLayoutTable.
QVector< QVariant > QgsLayoutTableRow
List of QVariants, representing a the contents of a single row in a QgsLayoutTable.
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:8361
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:8360
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7725