QGIS API Documentation 3.39.0-Master (d85f3c2a281)
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
19#include "qgsexpressionutils.h"
20#include "qgslayouttable.h"
21#include "qgslayoututils.h"
23#include "qgscolorutils.h"
24#include "qgslayoutframe.h"
25#include "qgsfontutils.h"
27#include "qgstextrenderer.h"
29
30#include <set>
31//
32// QgsLayoutTableStyle
33//
34
35bool QgsLayoutTableStyle::writeXml( QDomElement &styleElem, QDomDocument &doc ) const
36{
37 Q_UNUSED( doc )
38 styleElem.setAttribute( QStringLiteral( "cellBackgroundColor" ), QgsColorUtils::colorToString( cellBackgroundColor ) );
39 styleElem.setAttribute( QStringLiteral( "enabled" ), enabled );
40 return true;
41}
42
43bool QgsLayoutTableStyle::readXml( const QDomElement &styleElem )
44{
45 cellBackgroundColor = QgsColorUtils::colorFromString( styleElem.attribute( QStringLiteral( "cellBackgroundColor" ), QStringLiteral( "255,255,255,255" ) ) );
46 enabled = ( styleElem.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ) != QLatin1String( "0" ) );
47 return true;
48}
49
50
51//
52// QgsLayoutTable
53//
54
56 : QgsLayoutMultiFrame( layout )
57{
58 initStyles();
59}
60
62{
63 mColumns.clear();
64 mSortColumns.clear();
65
66 qDeleteAll( mCellStyles );
67 mCellStyles.clear();
68}
69
70bool QgsLayoutTable::writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const
71{
72 elem.setAttribute( QStringLiteral( "cellMargin" ), QString::number( mCellMargin ) );
73 elem.setAttribute( QStringLiteral( "emptyTableMode" ), QString::number( static_cast< int >( mEmptyTableMode ) ) );
74 elem.setAttribute( QStringLiteral( "emptyTableMessage" ), mEmptyTableMessage );
75 elem.setAttribute( QStringLiteral( "showEmptyRows" ), mShowEmptyRows );
76
77 QDomElement headerElem = doc.createElement( QStringLiteral( "headerTextFormat" ) );
78 const QDomElement headerTextElem = mHeaderTextFormat.writeXml( doc, context );
79 headerElem.appendChild( headerTextElem );
80 elem.appendChild( headerElem );
81 elem.setAttribute( QStringLiteral( "headerHAlignment" ), QString::number( static_cast< int >( mHeaderHAlignment ) ) );
82 elem.setAttribute( QStringLiteral( "headerMode" ), QString::number( static_cast< int >( mHeaderMode ) ) );
83
84 QDomElement contentElem = doc.createElement( QStringLiteral( "contentTextFormat" ) );
85 const QDomElement contentTextElem = mContentTextFormat.writeXml( doc, context );
86 contentElem.appendChild( contentTextElem );
87 elem.appendChild( contentElem );
88 elem.setAttribute( QStringLiteral( "gridStrokeWidth" ), QString::number( mGridStrokeWidth ) );
89 elem.setAttribute( QStringLiteral( "gridColor" ), QgsColorUtils::colorToString( mGridColor ) );
90 elem.setAttribute( QStringLiteral( "horizontalGrid" ), mHorizontalGrid );
91 elem.setAttribute( QStringLiteral( "verticalGrid" ), mVerticalGrid );
92 elem.setAttribute( QStringLiteral( "showGrid" ), mShowGrid );
93 elem.setAttribute( QStringLiteral( "backgroundColor" ), QgsColorUtils::colorToString( mBackgroundColor ) );
94 elem.setAttribute( QStringLiteral( "wrapBehavior" ), QString::number( static_cast< int >( mWrapBehavior ) ) );
95
96 // display columns
97 QDomElement displayColumnsElem = doc.createElement( QStringLiteral( "displayColumns" ) );
98 for ( const QgsLayoutTableColumn &column : std::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 // sort columns
106 QDomElement sortColumnsElem = doc.createElement( QStringLiteral( "sortColumns" ) );
107 for ( const QgsLayoutTableColumn &column : std::as_const( mSortColumns ) )
108 {
109 QDomElement columnElem = doc.createElement( QStringLiteral( "column" ) );
110 column.writeXml( columnElem, doc );
111 sortColumnsElem.appendChild( columnElem );
112 }
113 elem.appendChild( sortColumnsElem );
114
115
116 //cell styles
117 QDomElement stylesElem = doc.createElement( QStringLiteral( "cellStyles" ) );
118 QMap< CellStyleGroup, QString >::const_iterator it = mCellStyleNames.constBegin();
119 for ( ; it != mCellStyleNames.constEnd(); ++it )
120 {
121 QString styleName = it.value();
122 QDomElement styleElem = doc.createElement( styleName );
123 QgsLayoutTableStyle *style = mCellStyles.value( it.key() );
124 if ( style )
125 {
126 style->writeXml( styleElem, doc );
127 stylesElem.appendChild( styleElem );
128 }
129 }
130 elem.appendChild( stylesElem );
131 return true;
132}
133
134bool QgsLayoutTable::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
135{
136 mEmptyTableMode = QgsLayoutTable::EmptyTableMode( itemElem.attribute( QStringLiteral( "emptyTableMode" ), QStringLiteral( "0" ) ).toInt() );
137 mEmptyTableMessage = itemElem.attribute( QStringLiteral( "emptyTableMessage" ), tr( "No matching records" ) );
138 mShowEmptyRows = itemElem.attribute( QStringLiteral( "showEmptyRows" ), QStringLiteral( "0" ) ).toInt();
139
140 const QDomElement headerTextFormat = itemElem.firstChildElement( QStringLiteral( "headerTextFormat" ) );
141 if ( !headerTextFormat.isNull() )
142 {
143 QDomNodeList textFormatNodeList = headerTextFormat.elementsByTagName( QStringLiteral( "text-style" ) );
144 QDomElement textFormatElem = textFormatNodeList.at( 0 ).toElement();
145 mHeaderTextFormat.readXml( textFormatElem, context );
146 }
147 else
148 {
149 QFont headerFont;
150 if ( !QgsFontUtils::setFromXmlChildNode( headerFont, itemElem, QStringLiteral( "headerFontProperties" ) ) )
151 {
152 headerFont.fromString( itemElem.attribute( QStringLiteral( "headerFont" ), QString() ) );
153 }
154 QColor headerFontColor = QgsColorUtils::colorFromString( itemElem.attribute( QStringLiteral( "headerFontColor" ), QStringLiteral( "0,0,0,255" ) ) );
156 if ( headerFont.pointSizeF() > 0 )
157 {
158 mHeaderTextFormat.setSize( headerFont.pointSizeF() );
160 }
161 else if ( headerFont.pixelSize() > 0 )
162 {
163 mHeaderTextFormat.setSize( headerFont.pixelSize() );
165 }
167 }
168
169 mHeaderHAlignment = QgsLayoutTable::HeaderHAlignment( itemElem.attribute( QStringLiteral( "headerHAlignment" ), QStringLiteral( "0" ) ).toInt() );
170 mHeaderMode = QgsLayoutTable::HeaderMode( itemElem.attribute( QStringLiteral( "headerMode" ), QStringLiteral( "0" ) ).toInt() );
171
172 const QDomElement contentTextFormat = itemElem.firstChildElement( QStringLiteral( "contentTextFormat" ) );
173 if ( !contentTextFormat.isNull() )
174 {
175 QDomNodeList textFormatNodeList = contentTextFormat.elementsByTagName( QStringLiteral( "text-style" ) );
176 QDomElement textFormatElem = textFormatNodeList.at( 0 ).toElement();
177 mContentTextFormat.readXml( textFormatElem, context );
178 }
179 else
180 {
181 QFont contentFont;
182 if ( !QgsFontUtils::setFromXmlChildNode( contentFont, itemElem, QStringLiteral( "contentFontProperties" ) ) )
183 {
184 contentFont.fromString( itemElem.attribute( QStringLiteral( "contentFont" ), QString() ) );
185 }
186 QColor contentFontColor = QgsColorUtils::colorFromString( itemElem.attribute( QStringLiteral( "contentFontColor" ), QStringLiteral( "0,0,0,255" ) ) );
188 if ( contentFont.pointSizeF() > 0 )
189 {
190 mContentTextFormat.setSize( contentFont.pointSizeF() );
192 }
193 else if ( contentFont.pixelSize() > 0 )
194 {
197 }
199 }
200
201 mCellMargin = itemElem.attribute( QStringLiteral( "cellMargin" ), QStringLiteral( "1.0" ) ).toDouble();
202 mGridStrokeWidth = itemElem.attribute( QStringLiteral( "gridStrokeWidth" ), QStringLiteral( "0.5" ) ).toDouble();
203 mHorizontalGrid = itemElem.attribute( QStringLiteral( "horizontalGrid" ), QStringLiteral( "1" ) ).toInt();
204 mVerticalGrid = itemElem.attribute( QStringLiteral( "verticalGrid" ), QStringLiteral( "1" ) ).toInt();
205 mShowGrid = itemElem.attribute( QStringLiteral( "showGrid" ), QStringLiteral( "1" ) ).toInt();
206 mGridColor = QgsColorUtils::colorFromString( itemElem.attribute( QStringLiteral( "gridColor" ), QStringLiteral( "0,0,0,255" ) ) );
207 mBackgroundColor = QgsColorUtils::colorFromString( itemElem.attribute( QStringLiteral( "backgroundColor" ), QStringLiteral( "255,255,255,0" ) ) );
208 mWrapBehavior = QgsLayoutTable::WrapBehavior( itemElem.attribute( QStringLiteral( "wrapBehavior" ), QStringLiteral( "0" ) ).toInt() );
209
210 //restore display column specifications
211 mColumns.clear();
212 QDomNodeList columnsList = itemElem.elementsByTagName( QStringLiteral( "displayColumns" ) );
213 if ( !columnsList.isEmpty() )
214 {
215 QDomElement columnsElem = columnsList.at( 0 ).toElement();
216 QDomNodeList columnEntryList = columnsElem.elementsByTagName( QStringLiteral( "column" ) );
217 for ( int i = 0; i < columnEntryList.size(); ++i )
218 {
219 QDomElement columnElem = columnEntryList.at( i ).toElement();
221 column.readXml( columnElem );
222 mColumns.append( column );
223 }
224 }
225 // sort columns
226 mSortColumns.clear();
227 QDomNodeList sortColumnsList = itemElem.elementsByTagName( QStringLiteral( "sortColumns" ) );
228 if ( !sortColumnsList.isEmpty() )
229 {
230 QDomElement columnsElem = sortColumnsList.at( 0 ).toElement();
231 QDomNodeList columnEntryList = columnsElem.elementsByTagName( QStringLiteral( "column" ) );
232 for ( int i = 0; i < columnEntryList.size(); ++i )
233 {
234 QDomElement columnElem = columnEntryList.at( i ).toElement();
236 column.readXml( columnElem );
237 mSortColumns.append( column );
238 }
239 }
240 else
241 {
242 // backward compatibility for QGIS < 3.14
243 // copy the display columns if sortByRank > 0 and then, sort them by rank
245 std::copy_if( mColumns.begin(), mColumns.end(), std::back_inserter( mSortColumns ), []( const QgsLayoutTableColumn & col ) {return col.sortByRank() > 0;} );
246 std::sort( mSortColumns.begin(), mSortColumns.end(), []( const QgsLayoutTableColumn & a, const QgsLayoutTableColumn & b ) {return a.sortByRank() < b.sortByRank();} );
248 }
249
250 //restore cell styles
251 QDomNodeList stylesList = itemElem.elementsByTagName( QStringLiteral( "cellStyles" ) );
252 if ( !stylesList.isEmpty() )
253 {
254 QDomElement stylesElem = stylesList.at( 0 ).toElement();
255
256 QMap< CellStyleGroup, QString >::const_iterator it = mCellStyleNames.constBegin();
257 for ( ; it != mCellStyleNames.constEnd(); ++it )
258 {
259 QString styleName = it.value();
260 QDomNodeList styleList = stylesElem.elementsByTagName( styleName );
261 if ( !styleList.isEmpty() )
262 {
263 QDomElement styleElem = styleList.at( 0 ).toElement();
264 QgsLayoutTableStyle *style = mCellStyles.value( it.key() );
265 if ( style )
266 style->readXml( styleElem );
267 }
268 }
269 }
270
271 emit changed();
272 return true;
273}
274
276{
277 return mTableSize;
278}
279
285
286int QgsLayoutTable::rowsVisible( QgsRenderContext &context, double frameHeight, int firstRow, bool includeHeader, bool includeEmptyRows ) const
287{
288 //calculate header height
289 double headerHeight = 0;
290 if ( includeHeader )
291 {
292 headerHeight = mMaxRowHeightMap.value( 0 ) + 2 * ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin;
293 }
294 else
295 {
296 //frame has no header text, just the stroke
297 headerHeight = ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
298 }
299
300 //remaining height available for content rows
301 double contentHeight = frameHeight - headerHeight;
302
303 double gridHeight = ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
304
305 int currentRow = firstRow;
306 while ( contentHeight > 0 && currentRow <= mTableContents.count() )
307 {
308 double currentRowHeight = mMaxRowHeightMap.value( currentRow + 1 ) + gridHeight + 2 * mCellMargin;
309 contentHeight -= currentRowHeight;
310 currentRow++;
311 }
312
313 if ( includeEmptyRows && contentHeight > 0 )
314 {
315 const QFontMetricsF emptyRowContentFontMetrics = QgsTextRenderer::fontMetrics( context, mContentTextFormat, QgsTextRenderer::FONT_WORKAROUND_SCALE );
316 double rowHeight = ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin + emptyRowContentFontMetrics.ascent() / context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters ) / QgsTextRenderer::FONT_WORKAROUND_SCALE;
317 currentRow += std::max( std::floor( contentHeight / rowHeight ), 0.0 );
318 }
319
320 return currentRow - firstRow - 1;
321}
322
323int QgsLayoutTable::rowsVisible( QgsRenderContext &context, int frameIndex, int firstRow, bool includeEmptyRows ) const
324{
325 //get frame extent
326 if ( frameIndex >= frameCount() )
327 {
328 return 0;
329 }
330 QRectF frameExtent = frame( frameIndex )->extent();
331
332 bool includeHeader = false;
335 {
336 includeHeader = true;
337 }
338 return rowsVisible( context, frameExtent.height(), firstRow, includeHeader, includeEmptyRows );
339}
340
341QPair<int, int> QgsLayoutTable::rowRange( QgsRenderContext &context, const int frameIndex ) const
342{
343 //calculate row height
344 if ( frameIndex >= frameCount() )
345 {
346 //bad frame index
347 return qMakePair( 0, 0 );
348 }
349
350 //loop through all previous frames to calculate how many rows are visible in each
351 //as the entire height of a frame may not be utilized for content rows
352 int rowsAlreadyShown = 0;
353 for ( int idx = 0; idx < frameIndex; ++idx )
354 {
355 rowsAlreadyShown += rowsVisible( context, idx, rowsAlreadyShown, false );
356 }
357
358 //using zero based indexes
359 int firstVisible = std::min( rowsAlreadyShown, static_cast<int>( mTableContents.length() ) );
360 int possibleRowsVisible = rowsVisible( context, frameIndex, rowsAlreadyShown, false );
361 int lastVisible = std::min( firstVisible + possibleRowsVisible, static_cast<int>( mTableContents.length() ) );
362
363 return qMakePair( firstVisible, lastVisible );
364}
365
366void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &, const int frameIndex )
367{
368 bool emptyTable = mTableContents.length() == 0;
369 if ( emptyTable && mEmptyTableMode == QgsLayoutTable::HideTable )
370 {
371 //empty table set to hide table mode, so don't draw anything
372 return;
373 }
374
375 if ( !mLayout->renderContext().isPreviewRender() )
376 {
377 //exporting composition, so force an attribute refresh
378 //we do this in case vector layer has changed via an external source (e.g., another database user)
380 }
381
382 const bool prevTextFormatScaleFlag = context.renderContext().testFlag( Qgis::RenderContextFlag::ApplyScalingWorkaroundForTextRendering );
384
385 //calculate which rows to show in this frame
386 QPair< int, int > rowsToShow = rowRange( context.renderContext(), frameIndex );
387
388 double gridSizeX = mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0;
389 double gridSizeY = mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0;
390 double cellHeaderHeight = mMaxRowHeightMap[0] + 2 * mCellMargin;
392 QRectF cell;
393
394 //calculate whether a header is required
395 bool drawHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && frameIndex < 1 )
397 //calculate whether drawing table contents is required
398 bool drawContents = !( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage );
399
400 int numberRowsToDraw = rowsToShow.second - rowsToShow.first;
401 int numberEmptyRows = 0;
402 if ( drawContents && mShowEmptyRows )
403 {
404 numberRowsToDraw = rowsVisible( context.renderContext(), frameIndex, rowsToShow.first, true );
405 numberEmptyRows = numberRowsToDraw - rowsToShow.second + rowsToShow.first;
406 }
407 bool mergeCells = false;
408 if ( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage )
409 {
410 //draw a merged row for the empty table message
411 numberRowsToDraw++;
412 rowsToShow.second++;
413 mergeCells = true;
414 }
415
416 QPainter *p = context.renderContext().painter();
417 QgsScopedQPainterState painterState( p );
418 // painter is scaled to dots, so scale back to layout units
419 p->scale( context.renderContext().scaleFactor(), context.renderContext().scaleFactor() );
420
421 //draw the text
422 p->setPen( Qt::SolidLine );
423
424 double currentX = gridSizeX;
425 double currentY = gridSizeY;
426 if ( drawHeader )
427 {
428 //draw the headers
429 int col = 0;
430 for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
431 {
432 std::unique_ptr< QgsExpressionContextScope > headerCellScope = std::make_unique< QgsExpressionContextScope >();
433 headerCellScope->setVariable( QStringLiteral( "column_number" ), col + 1, true );
434 QgsExpressionContextScopePopper popper( context.renderContext().expressionContext(), headerCellScope.release() );
435
436 const QgsTextFormat headerFormat = textFormatForHeader( col );
437 //draw background
438 p->save();
439 p->setPen( Qt::NoPen );
440 p->setBrush( backgroundColor( -1, col ) );
441 p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, cellHeaderHeight ) );
442 p->restore();
443
444 currentX += mCellMargin;
445
446 cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], cellHeaderHeight );
447
448 //calculate alignment of header
450 switch ( mHeaderHAlignment )
451 {
452 case FollowColumn:
453 headerAlign = QgsTextRenderer::convertQtHAlignment( column.hAlignment() );
454 break;
455 case HeaderLeft:
457 break;
458 case HeaderCenter:
460 break;
461 case HeaderRight:
463 break;
464 }
465
466 const QRectF textCell = QRectF( currentX, currentY + mCellMargin, mMaxColumnWidthMap[col], cellHeaderHeight - 2 * mCellMargin );
467
468 const QStringList str = column.heading().split( '\n' );
469
470 // scale to dots
471 {
473 QgsTextRenderer::drawText( QRectF( textCell.left() * context.renderContext().scaleFactor(),
474 textCell.top() * context.renderContext().scaleFactor(),
475 textCell.width() * context.renderContext().scaleFactor(),
476 textCell.height() * context.renderContext().scaleFactor() ), 0,
477 headerAlign, str, context.renderContext(), headerFormat, true, Qgis::TextVerticalAlignment::VerticalCenter,
479 );
480 }
481
482 currentX += mMaxColumnWidthMap[ col ];
483 currentX += mCellMargin;
484 currentX += gridSizeX;
485 col++;
486 }
487
488 currentY += cellHeaderHeight;
489 currentY += gridSizeY;
490 }
491
492 //now draw the body cells
493 int rowsDrawn = 0;
494 std::set< std::pair< int, int > > spannedCells;
495 if ( drawContents )
496 {
497 //draw the attribute values
498 for ( int row = rowsToShow.first; row < rowsToShow.second; ++row )
499 {
500 rowsDrawn++;
501 currentX = gridSizeX;
502 int col = 0;
503
504 for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
505 {
506 ( void )column;
507
508 bool isSpanned = false;
509 QRectF fullCell;
510
511 double cellHeight = 0;
512 double cellWidth = 0;
513 const int rowsSpan = rowSpan( row, col );
514 const int colsSpan = columnSpan( row, col );
515 if ( spannedCells.find( std::make_pair( row, col ) ) != spannedCells.end() )
516 {
517 isSpanned = true;
518 }
519 else
520 {
521 for ( int spannedRow = row; spannedRow < row + rowsSpan; ++spannedRow )
522 {
523 cellHeight += mMaxRowHeightMap[spannedRow + 1] + 2 * mCellMargin
524 + ( spannedRow > row ? gridSizeY : 0 );
525 for ( int spannedCol = col; spannedCol < col + colsSpan; ++spannedCol )
526 {
527 spannedCells.insert( std::make_pair( spannedRow, spannedCol ) );
528 }
529 }
530 for ( int spannedCol = col; spannedCol < col + colsSpan; ++spannedCol )
531 {
532 cellWidth += mMaxColumnWidthMap[spannedCol] + 2 * mCellMargin
533 + ( spannedCol > col ? gridSizeX : 0 );
534 }
535 }
536
537 fullCell = QRectF( currentX, currentY, cellWidth, cellHeight );
538
539 if ( !isSpanned )
540 {
541 //draw background
542 p->save();
543 p->setPen( Qt::NoPen );
544 p->setBrush( backgroundColor( row, col, rowsSpan, colsSpan ) );
545 p->drawRect( fullCell );
546 p->restore();
547 }
548
549 // currentY = gridSize;
550 currentX += mCellMargin;
551
552 if ( !isSpanned )
553 {
554 QVariant cellContents = mTableContents.at( row ).at( col );
555 const QString localizedString { QgsExpressionUtils::toLocalizedString( cellContents ) };
556 const QStringList str = localizedString.split( '\n' );
557
558 QgsTextFormat cellFormat = textFormatForCell( row, col );
560 cellFormat.updateDataDefinedProperties( context.renderContext() );
561
562 p->save();
563 p->setClipRect( fullCell );
564 const QRectF textCell = QRectF( currentX, currentY + mCellMargin, cellWidth - 2 * mCellMargin, cellHeight - 2 * mCellMargin );
565
566 const QgsConditionalStyle style = conditionalCellStyle( row, col );
567 QColor foreColor = cellFormat.color();
568 if ( style.textColor().isValid() )
569 foreColor = style.textColor();
570
571 cellFormat.setColor( foreColor );
572
573 // scale to dots
574 {
576 QgsTextRenderer::drawText( QRectF( textCell.left() * context.renderContext().scaleFactor(),
577 textCell.top() * context.renderContext().scaleFactor(),
578 textCell.width() * context.renderContext().scaleFactor(),
579 textCell.height() * context.renderContext().scaleFactor() ), 0,
580 QgsTextRenderer::convertQtHAlignment( horizontalAlignmentForCell( row, col ) ), str, context.renderContext(), cellFormat, true,
583 }
584 p->restore();
585 }
586
587 currentX += mMaxColumnWidthMap[ col ];
588 currentX += mCellMargin;
589 currentX += gridSizeX;
590 col++;
591 }
592 currentY += mMaxRowHeightMap[row + 1] + 2 * mCellMargin;
593 currentY += gridSizeY;
594 }
595 }
596
597 if ( numberRowsToDraw > rowsDrawn )
598 {
599 p->save();
600 p->setPen( Qt::NoPen );
601
602 //draw background of empty rows
603 for ( int row = rowsDrawn; row < numberRowsToDraw; ++row )
604 {
605 currentX = gridSizeX;
606 int col = 0;
607
608 if ( mergeCells )
609 {
610 p->setBrush( backgroundColor( row + 10000, 0 ) );
611 p->drawRect( QRectF( gridSizeX, currentY, mTableSize.width() - 2 * gridSizeX, cellBodyHeightForEmptyRows ) );
612 }
613 else
614 {
615 for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
616 {
617 Q_UNUSED( column )
618
619 //draw background
620
621 //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
622 p->setBrush( backgroundColor( row + 10000, col ) );
623 p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, cellBodyHeightForEmptyRows ) );
624
625 // currentY = gridSize;
626 currentX += mMaxColumnWidthMap[ col ] + 2 * mCellMargin;
627 currentX += gridSizeX;
628 col++;
629 }
630 }
631 currentY += cellBodyHeightForEmptyRows + gridSizeY;
632 }
633 p->restore();
634 }
635
636 //and the borders
637 if ( mShowGrid )
638 {
639 QPen gridPen;
640 gridPen.setWidthF( mGridStrokeWidth );
641 gridPen.setColor( mGridColor );
642 gridPen.setJoinStyle( Qt::MiterJoin );
643 gridPen.setCapStyle( Qt::FlatCap );
644 p->setPen( gridPen );
645 if ( mHorizontalGrid )
646 {
647 drawHorizontalGridLines( context, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader );
648 }
649 if ( mVerticalGrid )
650 {
651 drawVerticalGridLines( context, mMaxColumnWidthMap, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader, mergeCells );
652 }
653 }
654
655 //special case - no records and table is set to ShowMessage mode
656 if ( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage )
657 {
658 double messageX = gridSizeX + mCellMargin;
659 double messageY = gridSizeY + ( drawHeader ? cellHeaderHeight + gridSizeY : 0 );
660 cell = QRectF( messageX, messageY, mTableSize.width() - messageX, cellBodyHeightForEmptyRows );
661
662 // scale to dots
663 {
665 QgsTextRenderer::drawText( QRectF( cell.left() * context.renderContext().scaleFactor(),
666 cell.top() * context.renderContext().scaleFactor(),
667 cell.width() * context.renderContext().scaleFactor(),
668 cell.height() * context.renderContext().scaleFactor() ), 0,
670 }
671 }
672
674}
675
676void QgsLayoutTable::setCellMargin( const double margin )
677{
678 if ( qgsDoubleNear( margin, mCellMargin ) )
679 {
680 return;
681 }
682
683 mCellMargin = margin;
684
685 //since spacing has changed, we need to recalculate the table size
687
688 emit changed();
689}
690
692{
693 if ( mode == mEmptyTableMode )
694 {
695 return;
696 }
697
698 mEmptyTableMode = mode;
699
700 //since appearance has changed, we need to recalculate the table size
702
703 emit changed();
704}
705
706void QgsLayoutTable::setEmptyTableMessage( const QString &message )
707{
708 if ( message == mEmptyTableMessage )
709 {
710 return;
711 }
712
713 mEmptyTableMessage = message;
714
715 //since message has changed, we need to recalculate the table size
717
718 emit changed();
719}
720
721void QgsLayoutTable::setShowEmptyRows( const bool showEmpty )
722{
723 if ( showEmpty == mShowEmptyRows )
724 {
725 return;
726 }
727
728 mShowEmptyRows = showEmpty;
729 update();
730 emit changed();
731}
732
733void QgsLayoutTable::setHeaderFont( const QFont &font )
734{
736 if ( font.pointSizeF() > 0 )
737 {
738 mHeaderTextFormat.setSize( font.pointSizeF() );
740 }
741 else if ( font.pixelSize() > 0 )
742 {
743 mHeaderTextFormat.setSize( font.pixelSize() );
745 }
746
747 //since font attributes have changed, we need to recalculate the table size
749
750 emit changed();
751}
752
754{
755 return mHeaderTextFormat.toQFont();
756}
757
758void QgsLayoutTable::setHeaderFontColor( const QColor &color )
759{
760 if ( color == mHeaderTextFormat.color() )
761 {
762 return;
763 }
764
766 update();
767
768 emit changed();
769}
770
772{
773 return mHeaderTextFormat.color();
774}
775
777{
778 mHeaderTextFormat = format;
779
780 //since font attributes have changed, we need to recalculate the table size
782
783 emit changed();
784}
785
790
792{
793 if ( alignment == mHeaderHAlignment )
794 {
795 return;
796 }
797
798 mHeaderHAlignment = alignment;
799 update();
800
801 emit changed();
802}
803
805{
806 if ( mode == mHeaderMode )
807 {
808 return;
809 }
810
811 mHeaderMode = mode;
813
814 emit changed();
815}
816
817void QgsLayoutTable::setContentFont( const QFont &font )
818{
820 if ( font.pointSizeF() > 0 )
821 {
822 mContentTextFormat.setSize( font.pointSizeF() );
824 }
825 else if ( font.pixelSize() > 0 )
826 {
827 mContentTextFormat.setSize( font.pixelSize() );
829 }
830
831 //since font attributes have changed, we need to recalculate the table size
833
834 emit changed();
835}
836
838{
840}
841
842void QgsLayoutTable::setContentFontColor( const QColor &color )
843{
844 if ( color == mContentTextFormat.color() )
845 {
846 return;
847 }
848
850 update();
851
852 emit changed();
853}
854
856{
857 return mContentTextFormat.color();
858}
859
861{
862 mContentTextFormat = format;
863
864 //since spacing has changed, we need to recalculate the table size
866
867 emit changed();
868}
869
874
875void QgsLayoutTable::setShowGrid( const bool showGrid )
876{
877 if ( showGrid == mShowGrid )
878 {
879 return;
880 }
881
883 //since grid spacing has changed, we need to recalculate the table size
885
886 emit changed();
887}
888
889void QgsLayoutTable::setGridStrokeWidth( const double width )
890{
891 if ( qgsDoubleNear( width, mGridStrokeWidth ) )
892 {
893 return;
894 }
895
896 mGridStrokeWidth = width;
897 //since grid spacing has changed, we need to recalculate the table size
899
900 emit changed();
901}
902
903void QgsLayoutTable::setGridColor( const QColor &color )
904{
905 if ( color == mGridColor )
906 {
907 return;
908 }
909
910 mGridColor = color;
911 update();
912
913 emit changed();
914}
915
916void QgsLayoutTable::setHorizontalGrid( const bool horizontalGrid )
917{
919 {
920 return;
921 }
922
924 //since grid spacing has changed, we need to recalculate the table size
926
927 emit changed();
928}
929
930void QgsLayoutTable::setVerticalGrid( const bool verticalGrid )
931{
933 {
934 return;
935 }
936
938 //since grid spacing has changed, we need to recalculate the table size
940
941 emit changed();
942}
943
944void QgsLayoutTable::setBackgroundColor( const QColor &color )
945{
946 if ( color == mBackgroundColor )
947 {
948 return;
949 }
950
951 mBackgroundColor = color;
952 update();
953
954 emit changed();
955}
956
958{
959 if ( behavior == mWrapBehavior )
960 {
961 return;
962 }
963
964 mWrapBehavior = behavior;
966
967 emit changed();
968}
969
971{
972 //remove existing columns
974
975 // backward compatibility
976 // test if sorting is provided with the columns and call setSortColumns in such case
977 QgsLayoutTableSortColumns newSortColumns;
979 std::copy_if( mColumns.begin(), mColumns.end(), std::back_inserter( newSortColumns ), []( const QgsLayoutTableColumn & col ) {return col.sortByRank() > 0;} );
980 if ( !newSortColumns.isEmpty() )
981 {
982 std::sort( newSortColumns.begin(), newSortColumns.end(), []( const QgsLayoutTableColumn & a, const QgsLayoutTableColumn & b ) {return a.sortByRank() < b.sortByRank();} );
983 setSortColumns( newSortColumns );
984 }
986}
987
992
994{
995 if ( mCellStyles.contains( group ) )
996 delete mCellStyles.take( group );
997
998 mCellStyles.insert( group, new QgsLayoutTableStyle( style ) );
999}
1000
1002{
1003 if ( !mCellStyles.contains( group ) )
1004 return nullptr;
1005
1006 return mCellStyles.value( group );
1007}
1008
1009QMap<int, QString> QgsLayoutTable::headerLabels() const
1010{
1011 QMap<int, QString> headers;
1012
1013 int i = 0;
1014 for ( const QgsLayoutTableColumn &col : std::as_const( mColumns ) )
1015 {
1016 headers.insert( i, col.heading() );
1017 i++;
1018 }
1019 return headers;
1020}
1021
1023{
1024 std::unique_ptr< QgsExpressionContextScope > cellScope = std::make_unique< QgsExpressionContextScope >();
1025 cellScope->setVariable( QStringLiteral( "row_number" ), row + 1, true );
1026 cellScope->setVariable( QStringLiteral( "column_number" ), column + 1, true );
1027 return cellScope.release();
1028}
1029
1030int QgsLayoutTable::rowSpan( int, int ) const
1031{
1032 return 1;
1033}
1034
1035int QgsLayoutTable::columnSpan( int, int ) const
1036{
1037 return 1;
1038}
1039
1044
1045QSizeF QgsLayoutTable::fixedFrameSize( const int frameIndex ) const
1046{
1047 Q_UNUSED( frameIndex )
1048 return QSizeF( mTableSize.width(), 0 );
1049}
1050
1051QSizeF QgsLayoutTable::minFrameSize( const int frameIndex ) const
1052{
1055
1056 double height = 0;
1059 {
1060 //header required, force frame to be high enough for header
1061 for ( int col = 0; col < mColumns.size(); ++ col )
1062 {
1064 }
1065 }
1066 return QSizeF( 0, height );
1067}
1068
1070{
1071 mMaxColumnWidthMap.clear();
1072 mMaxRowHeightMap.clear();
1073 mTableContents.clear();
1074
1075 //get new contents
1077 {
1078 return;
1079 }
1080}
1081
1087
1088void QgsLayoutTable::initStyles()
1089{
1092 mCellStyles.insert( OddRows, new QgsLayoutTableStyle() );
1093 mCellStyles.insert( EvenRows, new QgsLayoutTableStyle() );
1096 mCellStyles.insert( HeaderRow, new QgsLayoutTableStyle() );
1097 mCellStyles.insert( FirstRow, new QgsLayoutTableStyle() );
1098 mCellStyles.insert( LastRow, new QgsLayoutTableStyle() );
1099
1100 mCellStyleNames.insert( OddColumns, QStringLiteral( "oddColumns" ) );
1101 mCellStyleNames.insert( EvenColumns, QStringLiteral( "evenColumns" ) );
1102 mCellStyleNames.insert( OddRows, QStringLiteral( "oddRows" ) );
1103 mCellStyleNames.insert( EvenRows, QStringLiteral( "evenRows" ) );
1104 mCellStyleNames.insert( FirstColumn, QStringLiteral( "firstColumn" ) );
1105 mCellStyleNames.insert( LastColumn, QStringLiteral( "lastColumn" ) );
1106 mCellStyleNames.insert( HeaderRow, QStringLiteral( "headerRow" ) );
1107 mCellStyleNames.insert( FirstRow, QStringLiteral( "firstRow" ) );
1108 mCellStyleNames.insert( LastRow, QStringLiteral( "lastRow" ) );
1109}
1110
1112{
1113 mMaxColumnWidthMap.clear();
1114
1115 //total number of cells (rows + 1 for header)
1116 int cols = mColumns.count();
1117 int cells = cols * ( mTableContents.count() + 1 );
1118 QVector< double > widths( cells );
1119
1120 double currentCellTextWidth;
1121
1124
1125 //first, go through all the column headers and calculate the sizes
1126 int i = 0;
1127 for ( const QgsLayoutTableColumn &col : std::as_const( mColumns ) )
1128 {
1129 if ( col.width() > 0 )
1130 {
1131 //column has manually specified width
1132 widths[i] = col.width();
1133 }
1135 {
1136 std::unique_ptr< QgsExpressionContextScope > headerCellScope = std::make_unique< QgsExpressionContextScope >();
1137 headerCellScope->setVariable( QStringLiteral( "column_number" ), i + 1, true );
1138 QgsExpressionContextScopePopper popper( context.expressionContext(), headerCellScope.release() );
1139
1140 //column width set to automatic, so check content size
1141 const QStringList multiLineSplit = col.heading().split( '\n' );
1142 currentCellTextWidth = QgsTextRenderer::textWidth( context, textFormatForHeader( i ), multiLineSplit ) / context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters );
1143 widths[i] = currentCellTextWidth;
1144 }
1145 else
1146 {
1147 widths[i] = 0.0;
1148 }
1149 i++;
1150 }
1151
1152 //next, go through all the table contents and calculate the sizes
1153 QgsLayoutTableContents::const_iterator rowIt = mTableContents.constBegin();
1154 int row = 1;
1155 for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
1156 {
1157 QgsLayoutTableRow::const_iterator colIt = rowIt->constBegin();
1158 int col = 0;
1159 for ( ; colIt != rowIt->constEnd(); ++colIt )
1160 {
1161 if ( mColumns.at( col ).width() <= 0 )
1162 {
1163 //column width set to automatic, so check content size
1164 const QStringList multiLineSplit = QgsExpressionUtils::toLocalizedString( *colIt ).split( '\n' );
1165
1166 QgsTextFormat cellFormat = textFormatForCell( row - 1, col );
1167 QgsExpressionContextScopePopper popper( context.expressionContext(), scopeForCell( row - 1, col ) );
1168 cellFormat.updateDataDefinedProperties( context );
1169
1170 currentCellTextWidth = QgsTextRenderer::textWidth( context, cellFormat, multiLineSplit ) / context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters );
1171 widths[ row * cols + col ] = currentCellTextWidth;
1172 }
1173 else
1174 {
1175 widths[ row * cols + col ] = 0;
1176 }
1177
1178 col++;
1179 }
1180 row++;
1181 }
1182
1183 //calculate maximum
1184 for ( int col = 0; col < cols; ++col )
1185 {
1186 double maxColWidth = 0;
1187 for ( int row = 0; row < mTableContents.count() + 1; ++row )
1188 {
1189 maxColWidth = std::max( widths[ row * cols + col ], maxColWidth );
1190 }
1191 mMaxColumnWidthMap.insert( col, maxColWidth );
1192 }
1193
1194 return true;
1195}
1196
1198{
1199 mMaxRowHeightMap.clear();
1200
1201 //total number of cells (rows + 1 for header)
1202 int cols = mColumns.count();
1203 int cells = cols * ( mTableContents.count() + 1 );
1204 QVector< double > heights( cells );
1205
1208
1209 //first, go through all the column headers and calculate the sizes
1210 int i = 0;
1211 for ( const QgsLayoutTableColumn &col : std::as_const( mColumns ) )
1212 {
1213 std::unique_ptr< QgsExpressionContextScope > headerCellScope = std::make_unique< QgsExpressionContextScope >();
1214 headerCellScope->setVariable( QStringLiteral( "column_number" ), i + 1, true );
1215 QgsExpressionContextScopePopper popper( context.expressionContext(), headerCellScope.release() );
1216
1217 const QgsTextFormat cellFormat = textFormatForHeader( i );
1219 //height
1221 {
1222 heights[i] = 0;
1223 }
1224 else
1225 {
1226 heights[i] = QgsTextRenderer::textHeight( context,
1227 cellFormat,
1228 QStringList() << col.heading(), Qgis::TextLayoutMode::Rectangle,
1229 nullptr,
1232 )
1234 - headerDescentMm;
1235 }
1236 i++;
1237 }
1238
1239 //next, go through all the table contents and calculate the sizes
1240 QgsLayoutTableContents::const_iterator rowIt = mTableContents.constBegin();
1241 int row = 1;
1242 for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
1243 {
1244 QgsLayoutTableRow::const_iterator colIt = rowIt->constBegin();
1245 int i = 0;
1246 for ( ; colIt != rowIt->constEnd(); ++colIt )
1247 {
1248 QgsTextFormat cellFormat = textFormatForCell( row - 1, i );
1249 QgsExpressionContextScopePopper popper( context.expressionContext(), scopeForCell( row - 1, i ) );
1250 cellFormat.updateDataDefinedProperties( context );
1252 const QString localizedString { QgsExpressionUtils::toLocalizedString( *colIt ) };
1253
1254 heights[ row * cols + i ] = QgsTextRenderer::textHeight( context,
1255 cellFormat,
1256 QStringList() << localizedString.split( '\n' ),
1258 nullptr,
1261 ) / context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters ) - contentDescentMm;
1262
1263 i++;
1264 }
1265 row++;
1266 }
1267
1268 //calculate maximum
1269 for ( int row = 0; row < mTableContents.count() + 1; ++row )
1270 {
1271 double maxRowHeight = 0;
1272 for ( int col = 0; col < cols; ++col )
1273 {
1274 maxRowHeight = std::max( heights[ row * cols + col ], maxRowHeight );
1275 }
1276 mMaxRowHeightMap.insert( row, maxRowHeight );
1277 }
1278
1279 return true;
1280}
1281
1283{
1284 //check how much space each column needs
1285 if ( !calculateMaxColumnWidths() )
1286 {
1287 return 0;
1288 }
1289
1290 //adapt frame to total width
1291 double totalWidth = 0;
1292 QMap<int, double>::const_iterator maxColWidthIt = mMaxColumnWidthMap.constBegin();
1293 for ( ; maxColWidthIt != mMaxColumnWidthMap.constEnd(); ++maxColWidthIt )
1294 {
1295 totalWidth += maxColWidthIt.value();
1296 }
1297 totalWidth += ( 2 * mMaxColumnWidthMap.size() * mCellMargin );
1298 totalWidth += ( mMaxColumnWidthMap.size() + 1 ) * ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1299
1300 return totalWidth;
1301}
1302
1304{
1305 //check how much space each row needs
1306 if ( !calculateMaxRowHeights() )
1307 {
1308 return 0;
1309 }
1310
1311 double height = 0;
1312
1315
1316 //loop through all existing frames to calculate how many rows are visible in each
1317 //as the entire height of a frame may not be utilized for content rows
1318 int rowsAlreadyShown = 0;
1319 int numberExistingFrames = frameCount();
1320 int rowsVisibleInLastFrame = 0;
1321 double heightOfLastFrame = 0;
1322 for ( int idx = 0; idx < numberExistingFrames; ++idx )
1323 {
1324 bool hasHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && idx == 0 )
1326 heightOfLastFrame = frame( idx )->rect().height();
1327 rowsVisibleInLastFrame = rowsVisible( context, heightOfLastFrame, rowsAlreadyShown, hasHeader, false );
1328 rowsAlreadyShown += rowsVisibleInLastFrame;
1329 height += heightOfLastFrame;
1330 if ( rowsAlreadyShown >= mTableContents.length() )
1331 {
1332 //shown entire contents of table, nothing remaining
1333 return height;
1334 }
1335 }
1336
1337 //calculate how many rows left to show
1338 int remainingRows = mTableContents.length() - rowsAlreadyShown;
1339
1340 if ( remainingRows <= 0 )
1341 {
1342 //no remaining rows
1343 return height;
1344 }
1345
1347 {
1348 QgsLayoutItemPage *page = mLayout->pageCollection()->page( mLayout->pageCollection()->pageCount() - 1 );
1349 if ( page )
1350 heightOfLastFrame = page->sizeWithUnits().height();
1351 }
1352
1353 bool hasHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && numberExistingFrames < 1 )
1355
1356 int numberFramesMissing = 0;
1357 while ( remainingRows > 0 )
1358 {
1359 numberFramesMissing++;
1360
1361 rowsVisibleInLastFrame = rowsVisible( context, heightOfLastFrame, rowsAlreadyShown, hasHeader, false );
1362 if ( rowsVisibleInLastFrame < 1 )
1363 {
1364 //if no rows are visible in the last frame, calculation of missing frames
1365 //is impossible. So just return total height of existing frames
1366 return height;
1367 }
1368
1369 rowsAlreadyShown += rowsVisibleInLastFrame;
1370 remainingRows = mTableContents.length() - rowsAlreadyShown;
1371 }
1372
1373 //rows remain unshown -- how many extra frames would we need to complete the table?
1374 //assume all added frames are same size as final frame
1375 height += heightOfLastFrame * numberFramesMissing;
1376 return height;
1377}
1378
1379void QgsLayoutTable::drawHorizontalGridLines( QgsLayoutItemRenderContext &context, int firstRow, int lastRow, bool drawHeaderLines ) const
1380{
1381 //horizontal lines
1382 if ( lastRow - firstRow < 1 && !drawHeaderLines )
1383 {
1384 return;
1385 }
1386
1387 QPainter *painter = context.renderContext().painter();
1388
1390 double halfGridStrokeWidth = ( mShowGrid ? mGridStrokeWidth : 0 ) / 2.0;
1391 double currentY = halfGridStrokeWidth;
1392 if ( drawHeaderLines )
1393 {
1394 painter->drawLine( QPointF( 0, currentY ), QPointF( mTableSize.width(), currentY ) );
1395 currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
1396 currentY += mMaxRowHeightMap[0] + 2 * mCellMargin;
1397 }
1398
1399 QHash< QPair< int, int >, bool > skippedCellBottomBorders;
1400 for ( int row = 0; row < lastRow; ++row )
1401 {
1402 for ( int col = 0; col < mColumns.size(); ++col )
1403 {
1404 if ( skippedCellBottomBorders.constFind( qMakePair( row, col ) ) != skippedCellBottomBorders.constEnd() )
1405 continue;
1406
1407 const int rowsSpan = rowSpan( row, col );
1408 const int colsSpan = columnSpan( row, col );
1409 skippedCellBottomBorders.insert( qMakePair( row, col ), rowsSpan > 1 );
1410 for ( int rowDelta = 0; rowDelta < rowsSpan - 1; ++rowDelta )
1411 {
1412 for ( int colDelta = 0; colDelta < colsSpan; ++colDelta )
1413 {
1414 if ( rowDelta != 0 || colDelta != 0 )
1415 skippedCellBottomBorders.insert( qMakePair( row + rowDelta, col + colDelta ), true );
1416 }
1417 }
1418 }
1419 }
1420
1421 for ( int row = firstRow; row < lastRow; ++row )
1422 {
1423 double startX = 0;
1424 double endX = startX;
1425
1426 for ( int col = 0; col < mColumns.size(); ++col )
1427 {
1428 const double colWidth = mMaxColumnWidthMap.value( col ) + 2 * mCellMargin;
1429
1430 if ( skippedCellBottomBorders.value( qMakePair( row - 1, col ) ) )
1431 {
1432 // flush existing line
1433 if ( !qgsDoubleNear( startX, endX ) )
1434 {
1435 painter->drawLine( QPointF( startX, currentY ), QPointF( endX, currentY ) );
1436 }
1437 endX += colWidth;
1438 endX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1439 startX = endX;
1440 }
1441 else
1442 {
1443 endX += colWidth;
1444 endX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1445 }
1446 }
1447
1448 // flush last line
1449 if ( !qgsDoubleNear( startX, endX ) )
1450 {
1451 painter->drawLine( QPointF( startX, currentY ), QPointF( endX, currentY ) );
1452 }
1453
1454 currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
1455 double rowHeight = row < mTableContents.count() ? mMaxRowHeightMap[row + 1] : cellBodyHeightForEmptyRows;
1456 currentY += ( rowHeight + 2 * mCellMargin );
1457 }
1458 painter->drawLine( QPointF( 0, currentY ), QPointF( mTableSize.width(), currentY ) );
1459}
1460
1461QColor QgsLayoutTable::backgroundColor( int row, int column, int rowSpan, int columnSpan ) const
1462{
1463 QColor color = mBackgroundColor;
1464 if ( QgsLayoutTableStyle *style = mCellStyles.value( OddColumns ) )
1465 if ( style->enabled && column % 2 == 0 )
1466 color = style->cellBackgroundColor;
1467 if ( QgsLayoutTableStyle *style = mCellStyles.value( EvenColumns ) )
1468 if ( style->enabled && column % 2 == 1 )
1469 color = style->cellBackgroundColor;
1470 if ( QgsLayoutTableStyle *style = mCellStyles.value( OddRows ) )
1471 if ( style->enabled && row % 2 == 0 )
1472 color = style->cellBackgroundColor;
1473 if ( QgsLayoutTableStyle *style = mCellStyles.value( EvenRows ) )
1474 if ( style->enabled && row % 2 == 1 )
1475 color = style->cellBackgroundColor;
1476 if ( QgsLayoutTableStyle *style = mCellStyles.value( FirstColumn ) )
1477 if ( style->enabled && column == 0 )
1478 color = style->cellBackgroundColor;
1479 if ( QgsLayoutTableStyle *style = mCellStyles.value( LastColumn ) )
1480 if ( style->enabled && ( column + columnSpan == mColumns.count() ) )
1481 color = style->cellBackgroundColor;
1482 if ( QgsLayoutTableStyle *style = mCellStyles.value( HeaderRow ) )
1483 if ( style->enabled && row == -1 )
1484 color = style->cellBackgroundColor;
1485 if ( QgsLayoutTableStyle *style = mCellStyles.value( FirstRow ) )
1486 if ( style->enabled && row == 0 )
1487 color = style->cellBackgroundColor;
1488 if ( QgsLayoutTableStyle *style = mCellStyles.value( LastRow ) )
1489 if ( style->enabled && ( row + rowSpan == mTableContents.count() ) )
1490 color = style->cellBackgroundColor;
1491
1492 if ( row >= 0 )
1493 {
1494 QgsConditionalStyle conditionalStyle = conditionalCellStyle( row, column );
1495 if ( conditionalStyle.backgroundColor().isValid() )
1496 color = conditionalStyle.backgroundColor();
1497 }
1498
1499 return color;
1500}
1501
1502void QgsLayoutTable::drawVerticalGridLines( QgsLayoutItemRenderContext &context, const QMap<int, double> &maxWidthMap, int firstRow, int lastRow, bool hasHeader, bool mergeCells ) const
1503{
1504 //vertical lines
1505 if ( lastRow - firstRow < 1 && !hasHeader )
1506 {
1507 return;
1508 }
1509
1510 QHash< QPair< int, int >, bool > skippedCellRightBorders;
1511 for ( int row = 0; row < lastRow; ++row )
1512 {
1513 for ( int col = 0; col < mColumns.size(); ++col )
1514 {
1515 if ( skippedCellRightBorders.constFind( qMakePair( row, col ) ) != skippedCellRightBorders.constEnd() )
1516 continue;
1517
1518 const int rowsSpan = rowSpan( row, col );
1519 const int colsSpan = columnSpan( row, col );
1520 skippedCellRightBorders.insert( qMakePair( row, col ), colsSpan > 1 );
1521
1522 for ( int colDelta = 0; colDelta < colsSpan - 1; ++colDelta )
1523 {
1524 for ( int rowDelta = 0; rowDelta < rowsSpan; ++rowDelta )
1525 {
1526 if ( rowDelta != 0 || colDelta != 0 )
1527 skippedCellRightBorders.insert( qMakePair( row + rowDelta, col + colDelta ), true );
1528 }
1529 }
1530 }
1531 }
1532
1533 QPainter *painter = context.renderContext().painter();
1534
1535 //calculate height of table within frame
1536 double tableHeight = 0;
1537 QList< double > rowHeights;
1538 if ( hasHeader )
1539 {
1540 rowHeights << mCellMargin * 2 + mMaxRowHeightMap[0];
1541 tableHeight += ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2 + mMaxRowHeightMap[0];
1542 }
1543 else
1544 {
1545 rowHeights << 0;
1546 }
1547 tableHeight += ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
1548 double headerHeight = tableHeight;
1549
1551 for ( int row = firstRow; row < lastRow; ++row )
1552 {
1553 double rowHeight = row < mTableContents.count() ? mMaxRowHeightMap[row + 1] : cellBodyHeightForEmptyRows;
1554 rowHeights << rowHeight + mCellMargin * 2;
1555 tableHeight += rowHeight + ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2;
1556 }
1557
1558 double currentX = ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 ) / 2.0;;
1559 // left border of table
1560 painter->drawLine( QPointF( currentX, 0 ), QPointF( currentX, tableHeight ) );
1561 currentX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1562 QMap<int, double>::const_iterator maxColWidthIt = maxWidthMap.constBegin();
1563 int col = 1;
1564 for ( ; maxColWidthIt != maxWidthMap.constEnd(); ++maxColWidthIt )
1565 {
1566 currentX += ( maxColWidthIt.value() + 2 * mCellMargin );
1567 if ( col == maxWidthMap.size() )
1568 {
1569 // right border of table, always drawn
1570 painter->drawLine( QPointF( currentX, 0 ), QPointF( currentX, tableHeight ) );
1571 }
1572 else
1573 {
1574 if ( !mergeCells )
1575 {
1576 double startY = 0;
1577 double endY = startY + ( hasHeader ? ( rowHeights.value( 0 ) + ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) ) : 0 );
1578 for ( int row = firstRow; row < lastRow; ++row )
1579 {
1580 const double rowHeight = rowHeights.value( row - firstRow + 1 );
1581 if ( skippedCellRightBorders.value( qMakePair( row, col - 1 ) ) )
1582 {
1583 // flush existing line
1584 if ( !qgsDoubleNear( startY, endY ) )
1585 {
1586 painter->drawLine( QPointF( currentX, startY ), QPointF( currentX, endY ) );
1587 }
1588 endY += rowHeight;
1589 endY += ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
1590 startY = endY;
1591 }
1592 else
1593 {
1594 endY += rowHeight;
1595 endY += ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
1596 }
1597 }
1598
1599 // flush last line
1600 if ( !qgsDoubleNear( startY, endY ) )
1601 {
1602 painter->drawLine( QPointF( currentX, startY ), QPointF( currentX, endY ) );
1603 }
1604 }
1605 else if ( hasHeader )
1606 {
1607 painter->drawLine( QPointF( currentX, 0 ), QPointF( currentX, headerHeight ) );
1608 }
1609 }
1610
1611 currentX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1612 col++;
1613 }
1614}
1615
1617{
1619
1620 //force recalculation of frame rects, so that they are set to the correct
1621 //fixed and minimum frame sizes
1623}
1624
1626{
1627 return ( contents.indexOf( row ) >= 0 );
1628}
1629
1631{
1632 return mContentTextFormat;
1633}
1634
1639
1640Qt::Alignment QgsLayoutTable::horizontalAlignmentForCell( int, int column ) const
1641{
1642 return mColumns.value( column ).hAlignment();
1643}
1644
1645Qt::Alignment QgsLayoutTable::verticalAlignmentForCell( int, int column ) const
1646{
1647 return mColumns.value( column ).vAlignment();
1648}
1649
@ Rectangle
Text within rectangle layout mode.
QFlags< TextRendererFlag > TextRendererFlags
Definition qgis.h:3152
@ Millimeters
Millimeters.
@ Points
Points (e.g., for font sizes)
@ ApplyScalingWorkaroundForTextRendering
Whether a scaling workaround designed to stablise the rendering of small font sizes (or for painters ...
@ VerticalCenter
Center align.
TextHorizontalAlignment
Text horizontal alignment.
Definition qgis.h:2730
@ WrapLines
Automatically wrap long lines of text.
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 page() const
Returns the page the item is currently on, with the first page returning 0.
Abstract base class for layout items with the ability to distribute the content to several frames (Qg...
int frameCount() const
Returns the number of frames associated with this multiframe.
QgsLayoutFrame * frame(int index) const
Returns the child frame at a specified index from the multiframe.
@ ExtendToNextPage
Creates new full page frames on the following page(s) until the entire multiframe content is visible.
void refresh() override
Refreshes the multiframe, causing a recalculation of any property overrides.
virtual void recalculateFrameSizes()
Recalculates the portion of the multiframe item which is shown in each of its component frames.
void update()
Forces a redraw of all child frames.
int frameIndex(QgsLayoutFrame *frame) const
Returns the index of a frame within the multiframe.
void recalculateFrameRects()
Forces a recalculation of all the associated frame's scene rectangles.
void changed()
Emitted when the object's properties change.
QPointer< QgsLayout > mLayout
double height() const
Returns the height of the size.
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.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition qgslayout.h:49
The class is used as a container of context for various read/write operations on other objects.
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
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.
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 setSize(double size)
Sets the size for rendered text.
void setFont(const QFont &font)
Sets the font used for rendering text.
void setSizeUnit(Qgis::RenderUnit unit)
Sets the units for the size of rendered text.
void updateDataDefinedProperties(QgsRenderContext &context)
Updates the format by evaluating current values of data defined properties.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
QFont toQFont() const
Returns a QFont matching the relevant settings from this text format.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
QColor color() const
Returns the color that text will be rendered in.
static 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 str(x)
Definition qgis.cpp:38
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:6434
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6433
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5857