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