QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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 "qgssymbollayerutils.h"
24#include "qgslayoutframe.h"
25#include "qgsfontutils.h"
27#include "qgstextrenderer.h"
28
29//
30// QgsLayoutTableStyle
31//
32
33bool QgsLayoutTableStyle::writeXml( QDomElement &styleElem, QDomDocument &doc ) const
34{
35 Q_UNUSED( doc )
36 styleElem.setAttribute( QStringLiteral( "cellBackgroundColor" ), QgsSymbolLayerUtils::encodeColor( cellBackgroundColor ) );
37 styleElem.setAttribute( QStringLiteral( "enabled" ), enabled );
38 return true;
39}
40
41bool QgsLayoutTableStyle::readXml( const QDomElement &styleElem )
42{
43 cellBackgroundColor = QgsSymbolLayerUtils::decodeColor( styleElem.attribute( QStringLiteral( "cellBackgroundColor" ), QStringLiteral( "255,255,255,255" ) ) );
44 enabled = ( styleElem.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ) != QLatin1String( "0" ) );
45 return true;
46}
47
48
49//
50// QgsLayoutTable
51//
52
54 : QgsLayoutMultiFrame( layout )
55{
56 initStyles();
57}
58
60{
61 mColumns.clear();
62 mSortColumns.clear();
63
64 qDeleteAll( mCellStyles );
65 mCellStyles.clear();
66}
67
68bool QgsLayoutTable::writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const
69{
70 elem.setAttribute( QStringLiteral( "cellMargin" ), QString::number( mCellMargin ) );
71 elem.setAttribute( QStringLiteral( "emptyTableMode" ), QString::number( static_cast< int >( mEmptyTableMode ) ) );
72 elem.setAttribute( QStringLiteral( "emptyTableMessage" ), mEmptyTableMessage );
73 elem.setAttribute( QStringLiteral( "showEmptyRows" ), mShowEmptyRows );
74
75 QDomElement headerElem = doc.createElement( QStringLiteral( "headerTextFormat" ) );
76 const QDomElement headerTextElem = mHeaderTextFormat.writeXml( doc, context );
77 headerElem.appendChild( headerTextElem );
78 elem.appendChild( headerElem );
79 elem.setAttribute( QStringLiteral( "headerHAlignment" ), QString::number( static_cast< int >( mHeaderHAlignment ) ) );
80 elem.setAttribute( QStringLiteral( "headerMode" ), QString::number( static_cast< int >( mHeaderMode ) ) );
81
82 QDomElement contentElem = doc.createElement( QStringLiteral( "contentTextFormat" ) );
83 const QDomElement contentTextElem = mContentTextFormat.writeXml( doc, context );
84 contentElem.appendChild( contentTextElem );
85 elem.appendChild( contentElem );
86 elem.setAttribute( QStringLiteral( "gridStrokeWidth" ), QString::number( mGridStrokeWidth ) );
87 elem.setAttribute( QStringLiteral( "gridColor" ), QgsSymbolLayerUtils::encodeColor( mGridColor ) );
88 elem.setAttribute( QStringLiteral( "horizontalGrid" ), mHorizontalGrid );
89 elem.setAttribute( QStringLiteral( "verticalGrid" ), mVerticalGrid );
90 elem.setAttribute( QStringLiteral( "showGrid" ), mShowGrid );
91 elem.setAttribute( QStringLiteral( "backgroundColor" ), QgsSymbolLayerUtils::encodeColor( mBackgroundColor ) );
92 elem.setAttribute( QStringLiteral( "wrapBehavior" ), QString::number( static_cast< int >( mWrapBehavior ) ) );
93
94 // display columns
95 QDomElement displayColumnsElem = doc.createElement( QStringLiteral( "displayColumns" ) );
96 for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
97 {
98 QDomElement columnElem = doc.createElement( QStringLiteral( "column" ) );
99 column.writeXml( columnElem, doc );
100 displayColumnsElem.appendChild( columnElem );
101 }
102 elem.appendChild( displayColumnsElem );
103 // sort columns
104 QDomElement sortColumnsElem = doc.createElement( QStringLiteral( "sortColumns" ) );
105 for ( const QgsLayoutTableColumn &column : std::as_const( mSortColumns ) )
106 {
107 QDomElement columnElem = doc.createElement( QStringLiteral( "column" ) );
108 column.writeXml( columnElem, doc );
109 sortColumnsElem.appendChild( columnElem );
110 }
111 elem.appendChild( sortColumnsElem );
112
113
114 //cell styles
115 QDomElement stylesElem = doc.createElement( QStringLiteral( "cellStyles" ) );
116 QMap< CellStyleGroup, QString >::const_iterator it = mCellStyleNames.constBegin();
117 for ( ; it != mCellStyleNames.constEnd(); ++it )
118 {
119 QString styleName = it.value();
120 QDomElement styleElem = doc.createElement( styleName );
121 QgsLayoutTableStyle *style = mCellStyles.value( it.key() );
122 if ( style )
123 {
124 style->writeXml( styleElem, doc );
125 stylesElem.appendChild( styleElem );
126 }
127 }
128 elem.appendChild( stylesElem );
129 return true;
130}
131
132bool QgsLayoutTable::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
133{
134 mEmptyTableMode = QgsLayoutTable::EmptyTableMode( itemElem.attribute( QStringLiteral( "emptyTableMode" ), QStringLiteral( "0" ) ).toInt() );
135 mEmptyTableMessage = itemElem.attribute( QStringLiteral( "emptyTableMessage" ), tr( "No matching records" ) );
136 mShowEmptyRows = itemElem.attribute( QStringLiteral( "showEmptyRows" ), QStringLiteral( "0" ) ).toInt();
137
138 const QDomElement headerTextFormat = itemElem.firstChildElement( QStringLiteral( "headerTextFormat" ) );
139 if ( !headerTextFormat.isNull() )
140 {
141 QDomNodeList textFormatNodeList = headerTextFormat.elementsByTagName( QStringLiteral( "text-style" ) );
142 QDomElement textFormatElem = textFormatNodeList.at( 0 ).toElement();
143 mHeaderTextFormat.readXml( textFormatElem, context );
144 }
145 else
146 {
147 QFont headerFont;
148 if ( !QgsFontUtils::setFromXmlChildNode( headerFont, itemElem, QStringLiteral( "headerFontProperties" ) ) )
149 {
150 headerFont.fromString( itemElem.attribute( QStringLiteral( "headerFont" ), QString() ) );
151 }
152 QColor headerFontColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "headerFontColor" ), QStringLiteral( "0,0,0,255" ) ) );
154 if ( headerFont.pointSizeF() > 0 )
155 {
156 mHeaderTextFormat.setSize( headerFont.pointSizeF() );
158 }
159 else if ( headerFont.pixelSize() > 0 )
160 {
161 mHeaderTextFormat.setSize( headerFont.pixelSize() );
163 }
165 }
166
167 mHeaderHAlignment = QgsLayoutTable::HeaderHAlignment( itemElem.attribute( QStringLiteral( "headerHAlignment" ), QStringLiteral( "0" ) ).toInt() );
168 mHeaderMode = QgsLayoutTable::HeaderMode( itemElem.attribute( QStringLiteral( "headerMode" ), QStringLiteral( "0" ) ).toInt() );
169
170 const QDomElement contentTextFormat = itemElem.firstChildElement( QStringLiteral( "contentTextFormat" ) );
171 if ( !contentTextFormat.isNull() )
172 {
173 QDomNodeList textFormatNodeList = contentTextFormat.elementsByTagName( QStringLiteral( "text-style" ) );
174 QDomElement textFormatElem = textFormatNodeList.at( 0 ).toElement();
175 mContentTextFormat.readXml( textFormatElem, context );
176 }
177 else
178 {
179 QFont contentFont;
180 if ( !QgsFontUtils::setFromXmlChildNode( contentFont, itemElem, QStringLiteral( "contentFontProperties" ) ) )
181 {
182 contentFont.fromString( itemElem.attribute( QStringLiteral( "contentFont" ), QString() ) );
183 }
184 QColor contentFontColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "contentFontColor" ), QStringLiteral( "0,0,0,255" ) ) );
186 if ( contentFont.pointSizeF() > 0 )
187 {
188 mContentTextFormat.setSize( contentFont.pointSizeF() );
190 }
191 else if ( contentFont.pixelSize() > 0 )
192 {
195 }
197 }
198
199 mCellMargin = itemElem.attribute( QStringLiteral( "cellMargin" ), QStringLiteral( "1.0" ) ).toDouble();
200 mGridStrokeWidth = itemElem.attribute( QStringLiteral( "gridStrokeWidth" ), QStringLiteral( "0.5" ) ).toDouble();
201 mHorizontalGrid = itemElem.attribute( QStringLiteral( "horizontalGrid" ), QStringLiteral( "1" ) ).toInt();
202 mVerticalGrid = itemElem.attribute( QStringLiteral( "verticalGrid" ), QStringLiteral( "1" ) ).toInt();
203 mShowGrid = itemElem.attribute( QStringLiteral( "showGrid" ), QStringLiteral( "1" ) ).toInt();
204 mGridColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "gridColor" ), QStringLiteral( "0,0,0,255" ) ) );
205 mBackgroundColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "backgroundColor" ), QStringLiteral( "255,255,255,0" ) ) );
206 mWrapBehavior = QgsLayoutTable::WrapBehavior( itemElem.attribute( QStringLiteral( "wrapBehavior" ), QStringLiteral( "0" ) ).toInt() );
207
208 //restore display column specifications
209 mColumns.clear();
210 QDomNodeList columnsList = itemElem.elementsByTagName( QStringLiteral( "displayColumns" ) );
211 if ( !columnsList.isEmpty() )
212 {
213 QDomElement columnsElem = columnsList.at( 0 ).toElement();
214 QDomNodeList columnEntryList = columnsElem.elementsByTagName( QStringLiteral( "column" ) );
215 for ( int i = 0; i < columnEntryList.size(); ++i )
216 {
217 QDomElement columnElem = columnEntryList.at( i ).toElement();
219 column.readXml( columnElem );
220 mColumns.append( column );
221 }
222 }
223 // sort columns
224 mSortColumns.clear();
225 QDomNodeList sortColumnsList = itemElem.elementsByTagName( QStringLiteral( "sortColumns" ) );
226 if ( !sortColumnsList.isEmpty() )
227 {
228 QDomElement columnsElem = sortColumnsList.at( 0 ).toElement();
229 QDomNodeList columnEntryList = columnsElem.elementsByTagName( QStringLiteral( "column" ) );
230 for ( int i = 0; i < columnEntryList.size(); ++i )
231 {
232 QDomElement columnElem = columnEntryList.at( i ).toElement();
234 column.readXml( columnElem );
235 mSortColumns.append( column );
236 }
237 }
238 else
239 {
240 // backward compatibility for QGIS < 3.14
241 // copy the display columns if sortByRank > 0 and then, sort them by rank
243 std::copy_if( mColumns.begin(), mColumns.end(), std::back_inserter( mSortColumns ), []( const QgsLayoutTableColumn & col ) {return col.sortByRank() > 0;} );
244 std::sort( mSortColumns.begin(), mSortColumns.end(), []( const QgsLayoutTableColumn & a, const QgsLayoutTableColumn & b ) {return a.sortByRank() < b.sortByRank();} );
246 }
247
248 //restore cell styles
249 QDomNodeList stylesList = itemElem.elementsByTagName( QStringLiteral( "cellStyles" ) );
250 if ( !stylesList.isEmpty() )
251 {
252 QDomElement stylesElem = stylesList.at( 0 ).toElement();
253
254 QMap< CellStyleGroup, QString >::const_iterator it = mCellStyleNames.constBegin();
255 for ( ; it != mCellStyleNames.constEnd(); ++it )
256 {
257 QString styleName = it.value();
258 QDomNodeList styleList = stylesElem.elementsByTagName( styleName );
259 if ( !styleList.isEmpty() )
260 {
261 QDomElement styleElem = styleList.at( 0 ).toElement();
262 QgsLayoutTableStyle *style = mCellStyles.value( it.key() );
263 if ( style )
264 style->readXml( styleElem );
265 }
266 }
267 }
268
269 emit changed();
270 return true;
271}
272
274{
275 return mTableSize;
276}
277
279{
282}
283
284int QgsLayoutTable::rowsVisible( QgsRenderContext &context, double frameHeight, int firstRow, bool includeHeader, bool includeEmptyRows ) const
285{
286 //calculate header height
287 double headerHeight = 0;
288 if ( includeHeader )
289 {
290 headerHeight = mMaxRowHeightMap.value( 0 ) + 2 * ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin;
291 }
292 else
293 {
294 //frame has no header text, just the stroke
295 headerHeight = ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
296 }
297
298 //remaining height available for content rows
299 double contentHeight = frameHeight - headerHeight;
300
301 double gridHeight = ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
302
303 int currentRow = firstRow;
304 while ( contentHeight > 0 && currentRow <= mTableContents.count() )
305 {
306 double currentRowHeight = mMaxRowHeightMap.value( currentRow + 1 ) + gridHeight + 2 * mCellMargin;
307 contentHeight -= currentRowHeight;
308 currentRow++;
309 }
310
311 if ( includeEmptyRows && contentHeight > 0 )
312 {
313 const QFontMetricsF emptyRowContentFontMetrics = QgsTextRenderer::fontMetrics( context, mContentTextFormat, QgsTextRenderer::FONT_WORKAROUND_SCALE );
314 double rowHeight = ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + 2 * mCellMargin + emptyRowContentFontMetrics.ascent() / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) / QgsTextRenderer::FONT_WORKAROUND_SCALE;
315 currentRow += std::max( std::floor( contentHeight / rowHeight ), 0.0 );
316 }
317
318 return currentRow - firstRow - 1;
319}
320
321int QgsLayoutTable::rowsVisible( QgsRenderContext &context, int frameIndex, int firstRow, bool includeEmptyRows ) const
322{
323 //get frame extent
324 if ( frameIndex >= frameCount() )
325 {
326 return 0;
327 }
328 QRectF frameExtent = frame( frameIndex )->extent();
329
330 bool includeHeader = false;
333 {
334 includeHeader = true;
335 }
336 return rowsVisible( context, frameExtent.height(), firstRow, includeHeader, includeEmptyRows );
337}
338
339QPair<int, int> QgsLayoutTable::rowRange( QgsRenderContext &context, const int frameIndex ) const
340{
341 //calculate row height
342 if ( frameIndex >= frameCount() )
343 {
344 //bad frame index
345 return qMakePair( 0, 0 );
346 }
347
348 //loop through all previous frames to calculate how many rows are visible in each
349 //as the entire height of a frame may not be utilized for content rows
350 int rowsAlreadyShown = 0;
351 for ( int idx = 0; idx < frameIndex; ++idx )
352 {
353 rowsAlreadyShown += rowsVisible( context, idx, rowsAlreadyShown, false );
354 }
355
356 //using zero based indexes
357 int firstVisible = std::min( rowsAlreadyShown, static_cast<int>( mTableContents.length() ) );
358 int possibleRowsVisible = rowsVisible( context, frameIndex, rowsAlreadyShown, false );
359 int lastVisible = std::min( firstVisible + possibleRowsVisible, static_cast<int>( mTableContents.length() ) );
360
361 return qMakePair( firstVisible, lastVisible );
362}
363
364void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &, const int frameIndex )
365{
366 bool emptyTable = mTableContents.length() == 0;
367 if ( emptyTable && mEmptyTableMode == QgsLayoutTable::HideTable )
368 {
369 //empty table set to hide table mode, so don't draw anything
370 return;
371 }
372
373 if ( !mLayout->renderContext().isPreviewRender() )
374 {
375 //exporting composition, so force an attribute refresh
376 //we do this in case vector layer has changed via an external source (e.g., another database user)
378 }
379
380 const bool prevTextFormatScaleFlag = context.renderContext().testFlag( Qgis::RenderContextFlag::ApplyScalingWorkaroundForTextRendering );
382
383 //calculate which rows to show in this frame
384 QPair< int, int > rowsToShow = rowRange( context.renderContext(), frameIndex );
385
386 double gridSizeX = mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0;
387 double gridSizeY = mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0;
388 double cellHeaderHeight = mMaxRowHeightMap[0] + 2 * mCellMargin;
390 QRectF cell;
391
392 //calculate whether a header is required
393 bool drawHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && frameIndex < 1 )
395 //calculate whether drawing table contents is required
396 bool drawContents = !( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage );
397
398 int numberRowsToDraw = rowsToShow.second - rowsToShow.first;
399 int numberEmptyRows = 0;
400 if ( drawContents && mShowEmptyRows )
401 {
402 numberRowsToDraw = rowsVisible( context.renderContext(), frameIndex, rowsToShow.first, true );
403 numberEmptyRows = numberRowsToDraw - rowsToShow.second + rowsToShow.first;
404 }
405 bool mergeCells = false;
406 if ( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage )
407 {
408 //draw a merged row for the empty table message
409 numberRowsToDraw++;
410 rowsToShow.second++;
411 mergeCells = true;
412 }
413
414 QPainter *p = context.renderContext().painter();
415 QgsScopedQPainterState painterState( p );
416 // painter is scaled to dots, so scale back to layout units
417 p->scale( context.renderContext().scaleFactor(), context.renderContext().scaleFactor() );
418
419 //draw the text
420 p->setPen( Qt::SolidLine );
421
422 double currentX = gridSizeX;
423 double currentY = gridSizeY;
424 if ( drawHeader )
425 {
426 //draw the headers
427 int col = 0;
428 for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
429 {
430 std::unique_ptr< QgsExpressionContextScope > headerCellScope = std::make_unique< QgsExpressionContextScope >();
431 headerCellScope->setVariable( QStringLiteral( "column_number" ), col + 1, true );
432 QgsExpressionContextScopePopper popper( context.renderContext().expressionContext(), headerCellScope.release() );
433
434 const QgsTextFormat headerFormat = textFormatForHeader( col );
435 //draw background
436 p->save();
437 p->setPen( Qt::NoPen );
438 p->setBrush( backgroundColor( -1, col ) );
439 p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, cellHeaderHeight ) );
440 p->restore();
441
442 currentX += mCellMargin;
443
444 cell = QRectF( currentX, currentY, mMaxColumnWidthMap[col], cellHeaderHeight );
445
446 //calculate alignment of header
447 Qgis::TextHorizontalAlignment headerAlign = Qgis::TextHorizontalAlignment::Left;
448 switch ( mHeaderHAlignment )
449 {
450 case FollowColumn:
451 headerAlign = QgsTextRenderer::convertQtHAlignment( column.hAlignment() );
452 break;
453 case HeaderLeft:
454 headerAlign = Qgis::TextHorizontalAlignment::Left;
455 break;
456 case HeaderCenter:
457 headerAlign = Qgis::TextHorizontalAlignment::Center;
458 break;
459 case HeaderRight:
460 headerAlign = Qgis::TextHorizontalAlignment::Right;
461 break;
462 }
463
464 const QRectF textCell = QRectF( currentX, currentY + mCellMargin, mMaxColumnWidthMap[col], cellHeaderHeight - 2 * mCellMargin );
465
466 const QStringList str = column.heading().split( '\n' );
467
468 // scale to dots
469 {
471 QgsTextRenderer::drawText( QRectF( textCell.left() * context.renderContext().scaleFactor(),
472 textCell.top() * context.renderContext().scaleFactor(),
473 textCell.width() * context.renderContext().scaleFactor(),
474 textCell.height() * context.renderContext().scaleFactor() ), 0,
475 headerAlign, str, context.renderContext(), headerFormat, true, Qgis::TextVerticalAlignment::VerticalCenter,
476 mWrapBehavior == WrapText ? Qgis::TextRendererFlag::WrapLines : Qgis::TextRendererFlags()
477 );
478 }
479
480 currentX += mMaxColumnWidthMap[ col ];
481 currentX += mCellMargin;
482 currentX += gridSizeX;
483 col++;
484 }
485
486 currentY += cellHeaderHeight;
487 currentY += gridSizeY;
488 }
489
490 //now draw the body cells
491 int rowsDrawn = 0;
492 if ( drawContents )
493 {
494 //draw the attribute values
495 for ( int row = rowsToShow.first; row < rowsToShow.second; ++row )
496 {
497 rowsDrawn++;
498 currentX = gridSizeX;
499 int col = 0;
500
501 //calculate row height
502 double rowHeight = mMaxRowHeightMap[row + 1] + 2 * mCellMargin;
503
504 for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
505 {
506 ( void )column;
507 const QRectF fullCell( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, rowHeight );
508 //draw background
509 p->save();
510 p->setPen( Qt::NoPen );
511 p->setBrush( backgroundColor( row, col ) );
512 p->drawRect( fullCell );
513 p->restore();
514
515 // currentY = gridSize;
516 currentX += mCellMargin;
517
518 QVariant cellContents = mTableContents.at( row ).at( col );
519 const QString localizedString { QgsExpressionUtils::toLocalizedString( cellContents ) };
520 const QStringList str = localizedString.split( '\n' );
521
522 QgsTextFormat cellFormat = textFormatForCell( row, col );
524 cellFormat.updateDataDefinedProperties( context.renderContext() );
525
526 p->save();
527 p->setClipRect( fullCell );
528 const QRectF textCell = QRectF( currentX, currentY + mCellMargin, mMaxColumnWidthMap[col], rowHeight - 2 * mCellMargin );
529
530 const QgsConditionalStyle style = conditionalCellStyle( row, col );
531 QColor foreColor = cellFormat.color();
532 if ( style.textColor().isValid() )
533 foreColor = style.textColor();
534
535 cellFormat.setColor( foreColor );
536
537 // scale to dots
538 {
540 QgsTextRenderer::drawText( QRectF( textCell.left() * context.renderContext().scaleFactor(),
541 textCell.top() * context.renderContext().scaleFactor(),
542 textCell.width() * context.renderContext().scaleFactor(),
543 textCell.height() * context.renderContext().scaleFactor() ), 0,
544 QgsTextRenderer::convertQtHAlignment( horizontalAlignmentForCell( row, col ) ), str, context.renderContext(), cellFormat, true,
546 mWrapBehavior == WrapText ? Qgis::TextRendererFlag::WrapLines : Qgis::TextRendererFlags() );
547 }
548 p->restore();
549
550 currentX += mMaxColumnWidthMap[ col ];
551 currentX += mCellMargin;
552 currentX += gridSizeX;
553 col++;
554 }
555 currentY += rowHeight;
556 currentY += gridSizeY;
557 }
558 }
559
560 if ( numberRowsToDraw > rowsDrawn )
561 {
562 p->save();
563 p->setPen( Qt::NoPen );
564
565 //draw background of empty rows
566 for ( int row = rowsDrawn; row < numberRowsToDraw; ++row )
567 {
568 currentX = gridSizeX;
569 int col = 0;
570
571 if ( mergeCells )
572 {
573 p->setBrush( backgroundColor( row + 10000, 0 ) );
574 p->drawRect( QRectF( gridSizeX, currentY, mTableSize.width() - 2 * gridSizeX, cellBodyHeightForEmptyRows ) );
575 }
576 else
577 {
578 for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
579 {
580 Q_UNUSED( column )
581
582 //draw background
583
584 //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
585 p->setBrush( backgroundColor( row + 10000, col ) );
586 p->drawRect( QRectF( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, cellBodyHeightForEmptyRows ) );
587
588 // currentY = gridSize;
589 currentX += mMaxColumnWidthMap[ col ] + 2 * mCellMargin;
590 currentX += gridSizeX;
591 col++;
592 }
593 }
594 currentY += cellBodyHeightForEmptyRows + gridSizeY;
595 }
596 p->restore();
597 }
598
599 //and the borders
600 if ( mShowGrid )
601 {
602 QPen gridPen;
603 gridPen.setWidthF( mGridStrokeWidth );
604 gridPen.setColor( mGridColor );
605 gridPen.setJoinStyle( Qt::MiterJoin );
606 p->setPen( gridPen );
607 if ( mHorizontalGrid )
608 {
609 drawHorizontalGridLines( context, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader );
610 }
611 if ( mVerticalGrid )
612 {
613 drawVerticalGridLines( context, mMaxColumnWidthMap, rowsToShow.first, rowsToShow.second + numberEmptyRows, drawHeader, mergeCells );
614 }
615 }
616
617 //special case - no records and table is set to ShowMessage mode
618 if ( emptyTable && mEmptyTableMode == QgsLayoutTable::ShowMessage )
619 {
620 double messageX = gridSizeX + mCellMargin;
621 double messageY = gridSizeY + ( drawHeader ? cellHeaderHeight + gridSizeY : 0 );
622 cell = QRectF( messageX, messageY, mTableSize.width() - messageX, cellBodyHeightForEmptyRows );
623
624 // scale to dots
625 {
627 QgsTextRenderer::drawText( QRectF( cell.left() * context.renderContext().scaleFactor(),
628 cell.top() * context.renderContext().scaleFactor(),
629 cell.width() * context.renderContext().scaleFactor(),
630 cell.height() * context.renderContext().scaleFactor() ), 0,
631 Qgis::TextHorizontalAlignment::Center, QStringList() << mEmptyTableMessage, context.renderContext(), mContentTextFormat, true, Qgis::TextVerticalAlignment::VerticalCenter );
632 }
633 }
634
636}
637
638void QgsLayoutTable::setCellMargin( const double margin )
639{
640 if ( qgsDoubleNear( margin, mCellMargin ) )
641 {
642 return;
643 }
644
645 mCellMargin = margin;
646
647 //since spacing has changed, we need to recalculate the table size
649
650 emit changed();
651}
652
654{
655 if ( mode == mEmptyTableMode )
656 {
657 return;
658 }
659
660 mEmptyTableMode = mode;
661
662 //since appearance has changed, we need to recalculate the table size
664
665 emit changed();
666}
667
668void QgsLayoutTable::setEmptyTableMessage( const QString &message )
669{
670 if ( message == mEmptyTableMessage )
671 {
672 return;
673 }
674
675 mEmptyTableMessage = message;
676
677 //since message has changed, we need to recalculate the table size
679
680 emit changed();
681}
682
683void QgsLayoutTable::setShowEmptyRows( const bool showEmpty )
684{
685 if ( showEmpty == mShowEmptyRows )
686 {
687 return;
688 }
689
690 mShowEmptyRows = showEmpty;
691 update();
692 emit changed();
693}
694
695void QgsLayoutTable::setHeaderFont( const QFont &font )
696{
698 if ( font.pointSizeF() > 0 )
699 {
700 mHeaderTextFormat.setSize( font.pointSizeF() );
702 }
703 else if ( font.pixelSize() > 0 )
704 {
705 mHeaderTextFormat.setSize( font.pixelSize() );
707 }
708
709 //since font attributes have changed, we need to recalculate the table size
711
712 emit changed();
713}
714
716{
717 return mHeaderTextFormat.toQFont();
718}
719
720void QgsLayoutTable::setHeaderFontColor( const QColor &color )
721{
722 if ( color == mHeaderTextFormat.color() )
723 {
724 return;
725 }
726
728 update();
729
730 emit changed();
731}
732
734{
735 return mHeaderTextFormat.color();
736}
737
739{
740 mHeaderTextFormat = format;
741
742 //since font attributes have changed, we need to recalculate the table size
744
745 emit changed();
746}
747
749{
750 return mHeaderTextFormat;
751}
752
754{
755 if ( alignment == mHeaderHAlignment )
756 {
757 return;
758 }
759
760 mHeaderHAlignment = alignment;
761 update();
762
763 emit changed();
764}
765
767{
768 if ( mode == mHeaderMode )
769 {
770 return;
771 }
772
773 mHeaderMode = mode;
775
776 emit changed();
777}
778
779void QgsLayoutTable::setContentFont( const QFont &font )
780{
782 if ( font.pointSizeF() > 0 )
783 {
784 mContentTextFormat.setSize( font.pointSizeF() );
786 }
787 else if ( font.pixelSize() > 0 )
788 {
789 mContentTextFormat.setSize( font.pixelSize() );
791 }
792
793 //since font attributes have changed, we need to recalculate the table size
795
796 emit changed();
797}
798
800{
802}
803
804void QgsLayoutTable::setContentFontColor( const QColor &color )
805{
806 if ( color == mContentTextFormat.color() )
807 {
808 return;
809 }
810
812 update();
813
814 emit changed();
815}
816
818{
819 return mContentTextFormat.color();
820}
821
823{
824 mContentTextFormat = format;
825
826 //since spacing has changed, we need to recalculate the table size
828
829 emit changed();
830}
831
833{
834 return mContentTextFormat;
835}
836
837void QgsLayoutTable::setShowGrid( const bool showGrid )
838{
839 if ( showGrid == mShowGrid )
840 {
841 return;
842 }
843
845 //since grid spacing has changed, we need to recalculate the table size
847
848 emit changed();
849}
850
851void QgsLayoutTable::setGridStrokeWidth( const double width )
852{
853 if ( qgsDoubleNear( width, mGridStrokeWidth ) )
854 {
855 return;
856 }
857
858 mGridStrokeWidth = width;
859 //since grid spacing has changed, we need to recalculate the table size
861
862 emit changed();
863}
864
865void QgsLayoutTable::setGridColor( const QColor &color )
866{
867 if ( color == mGridColor )
868 {
869 return;
870 }
871
872 mGridColor = color;
873 update();
874
875 emit changed();
876}
877
878void QgsLayoutTable::setHorizontalGrid( const bool horizontalGrid )
879{
881 {
882 return;
883 }
884
886 //since grid spacing has changed, we need to recalculate the table size
888
889 emit changed();
890}
891
892void QgsLayoutTable::setVerticalGrid( const bool verticalGrid )
893{
895 {
896 return;
897 }
898
900 //since grid spacing has changed, we need to recalculate the table size
902
903 emit changed();
904}
905
906void QgsLayoutTable::setBackgroundColor( const QColor &color )
907{
908 if ( color == mBackgroundColor )
909 {
910 return;
911 }
912
913 mBackgroundColor = color;
914 update();
915
916 emit changed();
917}
918
920{
921 if ( behavior == mWrapBehavior )
922 {
923 return;
924 }
925
926 mWrapBehavior = behavior;
928
929 emit changed();
930}
931
933{
934 //remove existing columns
936
937 // backward compatibility
938 // test if sorting is provided with the columns and call setSortColumns in such case
939 QgsLayoutTableSortColumns newSortColumns;
941 std::copy_if( mColumns.begin(), mColumns.end(), std::back_inserter( newSortColumns ), []( const QgsLayoutTableColumn & col ) {return col.sortByRank() > 0;} );
942 if ( !newSortColumns.isEmpty() )
943 {
944 std::sort( newSortColumns.begin(), newSortColumns.end(), []( const QgsLayoutTableColumn & a, const QgsLayoutTableColumn & b ) {return a.sortByRank() < b.sortByRank();} );
945 setSortColumns( newSortColumns );
946 }
948}
949
951{
953}
954
956{
957 if ( mCellStyles.contains( group ) )
958 delete mCellStyles.take( group );
959
960 mCellStyles.insert( group, new QgsLayoutTableStyle( style ) );
961}
962
964{
965 if ( !mCellStyles.contains( group ) )
966 return nullptr;
967
968 return mCellStyles.value( group );
969}
970
971QMap<int, QString> QgsLayoutTable::headerLabels() const
972{
973 QMap<int, QString> headers;
974
975 int i = 0;
976 for ( const QgsLayoutTableColumn &col : std::as_const( mColumns ) )
977 {
978 headers.insert( i, col.heading() );
979 i++;
980 }
981 return headers;
982}
983
985{
986 std::unique_ptr< QgsExpressionContextScope > cellScope = std::make_unique< QgsExpressionContextScope >();
987 cellScope->setVariable( QStringLiteral( "row_number" ), row + 1, true );
988 cellScope->setVariable( QStringLiteral( "column_number" ), column + 1, true );
989 return cellScope.release();
990}
991
993{
994 return QgsConditionalStyle();
995}
996
997QSizeF QgsLayoutTable::fixedFrameSize( const int frameIndex ) const
998{
999 Q_UNUSED( frameIndex )
1000 return QSizeF( mTableSize.width(), 0 );
1001}
1002
1003QSizeF QgsLayoutTable::minFrameSize( const int frameIndex ) const
1004{
1007
1008 double height = 0;
1011 {
1012 //header required, force frame to be high enough for header
1013 for ( int col = 0; col < mColumns.size(); ++ col )
1014 {
1016 }
1017 }
1018 return QSizeF( 0, height );
1019}
1020
1022{
1023 mMaxColumnWidthMap.clear();
1024 mMaxRowHeightMap.clear();
1025 mTableContents.clear();
1026
1027 //get new contents
1029 {
1030 return;
1031 }
1032}
1033
1035{
1036 mTableSize = QSizeF( totalWidth(), totalHeight() );
1038}
1039
1040void QgsLayoutTable::initStyles()
1041{
1044 mCellStyles.insert( OddRows, new QgsLayoutTableStyle() );
1045 mCellStyles.insert( EvenRows, new QgsLayoutTableStyle() );
1048 mCellStyles.insert( HeaderRow, new QgsLayoutTableStyle() );
1049 mCellStyles.insert( FirstRow, new QgsLayoutTableStyle() );
1050 mCellStyles.insert( LastRow, new QgsLayoutTableStyle() );
1051
1052 mCellStyleNames.insert( OddColumns, QStringLiteral( "oddColumns" ) );
1053 mCellStyleNames.insert( EvenColumns, QStringLiteral( "evenColumns" ) );
1054 mCellStyleNames.insert( OddRows, QStringLiteral( "oddRows" ) );
1055 mCellStyleNames.insert( EvenRows, QStringLiteral( "evenRows" ) );
1056 mCellStyleNames.insert( FirstColumn, QStringLiteral( "firstColumn" ) );
1057 mCellStyleNames.insert( LastColumn, QStringLiteral( "lastColumn" ) );
1058 mCellStyleNames.insert( HeaderRow, QStringLiteral( "headerRow" ) );
1059 mCellStyleNames.insert( FirstRow, QStringLiteral( "firstRow" ) );
1060 mCellStyleNames.insert( LastRow, QStringLiteral( "lastRow" ) );
1061}
1062
1064{
1065 mMaxColumnWidthMap.clear();
1066
1067 //total number of cells (rows + 1 for header)
1068 int cols = mColumns.count();
1069 int cells = cols * ( mTableContents.count() + 1 );
1070 QVector< double > widths( cells );
1071
1072 double currentCellTextWidth;
1073
1076
1077 //first, go through all the column headers and calculate the sizes
1078 int i = 0;
1079 for ( const QgsLayoutTableColumn &col : std::as_const( mColumns ) )
1080 {
1081 if ( col.width() > 0 )
1082 {
1083 //column has manually specified width
1084 widths[i] = col.width();
1085 }
1087 {
1088 std::unique_ptr< QgsExpressionContextScope > headerCellScope = std::make_unique< QgsExpressionContextScope >();
1089 headerCellScope->setVariable( QStringLiteral( "column_number" ), i + 1, true );
1090 QgsExpressionContextScopePopper popper( context.expressionContext(), headerCellScope.release() );
1091
1092 //column width set to automatic, so check content size
1093 const QStringList multiLineSplit = col.heading().split( '\n' );
1094 currentCellTextWidth = QgsTextRenderer::textWidth( context, textFormatForHeader( i ), multiLineSplit ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
1095 widths[i] = currentCellTextWidth;
1096 }
1097 else
1098 {
1099 widths[i] = 0.0;
1100 }
1101 i++;
1102 }
1103
1104 //next, go through all the table contents and calculate the sizes
1105 QgsLayoutTableContents::const_iterator rowIt = mTableContents.constBegin();
1106 int row = 1;
1107 for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
1108 {
1109 QgsLayoutTableRow::const_iterator colIt = rowIt->constBegin();
1110 int col = 0;
1111 for ( ; colIt != rowIt->constEnd(); ++colIt )
1112 {
1113 if ( mColumns.at( col ).width() <= 0 )
1114 {
1115 //column width set to automatic, so check content size
1116 const QStringList multiLineSplit = QgsExpressionUtils::toLocalizedString( *colIt ).split( '\n' );
1117
1118 QgsTextFormat cellFormat = textFormatForCell( row - 1, col );
1119 QgsExpressionContextScopePopper popper( context.expressionContext(), scopeForCell( row - 1, col ) );
1120 cellFormat.updateDataDefinedProperties( context );
1121
1122 currentCellTextWidth = QgsTextRenderer::textWidth( context, cellFormat, multiLineSplit ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
1123 widths[ row * cols + col ] = currentCellTextWidth;
1124 }
1125 else
1126 {
1127 widths[ row * cols + col ] = 0;
1128 }
1129
1130 col++;
1131 }
1132 row++;
1133 }
1134
1135 //calculate maximum
1136 for ( int col = 0; col < cols; ++col )
1137 {
1138 double maxColWidth = 0;
1139 for ( int row = 0; row < mTableContents.count() + 1; ++row )
1140 {
1141 maxColWidth = std::max( widths[ row * cols + col ], maxColWidth );
1142 }
1143 mMaxColumnWidthMap.insert( col, maxColWidth );
1144 }
1145
1146 return true;
1147}
1148
1150{
1151 mMaxRowHeightMap.clear();
1152
1153 //total number of cells (rows + 1 for header)
1154 int cols = mColumns.count();
1155 int cells = cols * ( mTableContents.count() + 1 );
1156 QVector< double > heights( cells );
1157
1160
1161 //first, go through all the column headers and calculate the sizes
1162 int i = 0;
1163 for ( const QgsLayoutTableColumn &col : std::as_const( mColumns ) )
1164 {
1165 std::unique_ptr< QgsExpressionContextScope > headerCellScope = std::make_unique< QgsExpressionContextScope >();
1166 headerCellScope->setVariable( QStringLiteral( "column_number" ), i + 1, true );
1167 QgsExpressionContextScopePopper popper( context.expressionContext(), headerCellScope.release() );
1168
1169 const QgsTextFormat cellFormat = textFormatForHeader( i );
1171 //height
1173 {
1174 heights[i] = 0;
1175 }
1176 else
1177 {
1178 heights[i] = QgsTextRenderer::textHeight( context,
1179 cellFormat,
1180 QStringList() << col.heading(), Qgis::TextLayoutMode::Rectangle,
1181 nullptr,
1182 mWrapBehavior == WrapText ? Qgis::TextRendererFlag::WrapLines : Qgis::TextRendererFlags(),
1184 )
1186 - headerDescentMm;
1187 }
1188 i++;
1189 }
1190
1191 //next, go through all the table contents and calculate the sizes
1192 QgsLayoutTableContents::const_iterator rowIt = mTableContents.constBegin();
1193 int row = 1;
1194 for ( ; rowIt != mTableContents.constEnd(); ++rowIt )
1195 {
1196 QgsLayoutTableRow::const_iterator colIt = rowIt->constBegin();
1197 int i = 0;
1198 for ( ; colIt != rowIt->constEnd(); ++colIt )
1199 {
1200 QgsTextFormat cellFormat = textFormatForCell( row - 1, i );
1201 QgsExpressionContextScopePopper popper( context.expressionContext(), scopeForCell( row - 1, i ) );
1202 cellFormat.updateDataDefinedProperties( context );
1204 const QString localizedString { QgsExpressionUtils::toLocalizedString( *colIt ) };
1205
1206 heights[ row * cols + i ] = QgsTextRenderer::textHeight( context,
1207 cellFormat,
1208 QStringList() << localizedString.split( '\n' ),
1209 Qgis::TextLayoutMode::Rectangle,
1210 nullptr,
1211 mWrapBehavior == WrapText ? Qgis::TextRendererFlag::WrapLines : Qgis::TextRendererFlags(),
1213 ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) - contentDescentMm;
1214
1215 i++;
1216 }
1217 row++;
1218 }
1219
1220 //calculate maximum
1221 for ( int row = 0; row < mTableContents.count() + 1; ++row )
1222 {
1223 double maxRowHeight = 0;
1224 for ( int col = 0; col < cols; ++col )
1225 {
1226 maxRowHeight = std::max( heights[ row * cols + col ], maxRowHeight );
1227 }
1228 mMaxRowHeightMap.insert( row, maxRowHeight );
1229 }
1230
1231 return true;
1232}
1233
1235{
1236 //check how much space each column needs
1237 if ( !calculateMaxColumnWidths() )
1238 {
1239 return 0;
1240 }
1241
1242 //adapt frame to total width
1243 double totalWidth = 0;
1244 QMap<int, double>::const_iterator maxColWidthIt = mMaxColumnWidthMap.constBegin();
1245 for ( ; maxColWidthIt != mMaxColumnWidthMap.constEnd(); ++maxColWidthIt )
1246 {
1247 totalWidth += maxColWidthIt.value();
1248 }
1249 totalWidth += ( 2 * mMaxColumnWidthMap.size() * mCellMargin );
1250 totalWidth += ( mMaxColumnWidthMap.size() + 1 ) * ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1251
1252 return totalWidth;
1253}
1254
1256{
1257 //check how much space each row needs
1258 if ( !calculateMaxRowHeights() )
1259 {
1260 return 0;
1261 }
1262
1263 double height = 0;
1264
1267
1268 //loop through all existing frames to calculate how many rows are visible in each
1269 //as the entire height of a frame may not be utilized for content rows
1270 int rowsAlreadyShown = 0;
1271 int numberExistingFrames = frameCount();
1272 int rowsVisibleInLastFrame = 0;
1273 double heightOfLastFrame = 0;
1274 for ( int idx = 0; idx < numberExistingFrames; ++idx )
1275 {
1276 bool hasHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && idx == 0 )
1278 heightOfLastFrame = frame( idx )->rect().height();
1279 rowsVisibleInLastFrame = rowsVisible( context, heightOfLastFrame, rowsAlreadyShown, hasHeader, false );
1280 rowsAlreadyShown += rowsVisibleInLastFrame;
1281 height += heightOfLastFrame;
1282 if ( rowsAlreadyShown >= mTableContents.length() )
1283 {
1284 //shown entire contents of table, nothing remaining
1285 return height;
1286 }
1287 }
1288
1289 //calculate how many rows left to show
1290 int remainingRows = mTableContents.length() - rowsAlreadyShown;
1291
1292 if ( remainingRows <= 0 )
1293 {
1294 //no remaining rows
1295 return height;
1296 }
1297
1299 {
1300 QgsLayoutItemPage *page = mLayout->pageCollection()->page( mLayout->pageCollection()->pageCount() - 1 );
1301 if ( page )
1302 heightOfLastFrame = page->sizeWithUnits().height();
1303 }
1304
1305 bool hasHeader = ( ( mHeaderMode == QgsLayoutTable::FirstFrame && numberExistingFrames < 1 )
1307
1308 int numberFramesMissing = 0;
1309 while ( remainingRows > 0 )
1310 {
1311 numberFramesMissing++;
1312
1313 rowsVisibleInLastFrame = rowsVisible( context, heightOfLastFrame, rowsAlreadyShown, hasHeader, false );
1314 if ( rowsVisibleInLastFrame < 1 )
1315 {
1316 //if no rows are visible in the last frame, calculation of missing frames
1317 //is impossible. So just return total height of existing frames
1318 return height;
1319 }
1320
1321 rowsAlreadyShown += rowsVisibleInLastFrame;
1322 remainingRows = mTableContents.length() - rowsAlreadyShown;
1323 }
1324
1325 //rows remain unshown -- how many extra frames would we need to complete the table?
1326 //assume all added frames are same size as final frame
1327 height += heightOfLastFrame * numberFramesMissing;
1328 return height;
1329}
1330
1331void QgsLayoutTable::drawHorizontalGridLines( QgsLayoutItemRenderContext &context, int firstRow, int lastRow, bool drawHeaderLines ) const
1332{
1333 //horizontal lines
1334 if ( lastRow - firstRow < 1 && !drawHeaderLines )
1335 {
1336 return;
1337 }
1338
1339 QPainter *painter = context.renderContext().painter();
1340
1342 double halfGridStrokeWidth = ( mShowGrid ? mGridStrokeWidth : 0 ) / 2.0;
1343 double currentY = 0;
1344 currentY = halfGridStrokeWidth;
1345 if ( drawHeaderLines )
1346 {
1347 painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
1348 currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
1349 currentY += mMaxRowHeightMap[0] + 2 * mCellMargin;
1350 }
1351 for ( int row = firstRow; row < lastRow; ++row )
1352 {
1353 painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
1354 currentY += ( mShowGrid ? mGridStrokeWidth : 0 );
1355 double rowHeight = row < mTableContents.count() ? mMaxRowHeightMap[row + 1] : cellBodyHeightForEmptyRows;
1356 currentY += ( rowHeight + 2 * mCellMargin );
1357 }
1358 painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
1359}
1360
1361QColor QgsLayoutTable::backgroundColor( int row, int column ) const
1362{
1363 QColor color = mBackgroundColor;
1364 if ( QgsLayoutTableStyle *style = mCellStyles.value( OddColumns ) )
1365 if ( style->enabled && column % 2 == 0 )
1366 color = style->cellBackgroundColor;
1367 if ( QgsLayoutTableStyle *style = mCellStyles.value( EvenColumns ) )
1368 if ( style->enabled && column % 2 == 1 )
1369 color = style->cellBackgroundColor;
1370 if ( QgsLayoutTableStyle *style = mCellStyles.value( OddRows ) )
1371 if ( style->enabled && row % 2 == 0 )
1372 color = style->cellBackgroundColor;
1373 if ( QgsLayoutTableStyle *style = mCellStyles.value( EvenRows ) )
1374 if ( style->enabled && row % 2 == 1 )
1375 color = style->cellBackgroundColor;
1376 if ( QgsLayoutTableStyle *style = mCellStyles.value( FirstColumn ) )
1377 if ( style->enabled && column == 0 )
1378 color = style->cellBackgroundColor;
1379 if ( QgsLayoutTableStyle *style = mCellStyles.value( LastColumn ) )
1380 if ( style->enabled && column == mColumns.count() - 1 )
1381 color = style->cellBackgroundColor;
1382 if ( QgsLayoutTableStyle *style = mCellStyles.value( HeaderRow ) )
1383 if ( style->enabled && row == -1 )
1384 color = style->cellBackgroundColor;
1385 if ( QgsLayoutTableStyle *style = mCellStyles.value( FirstRow ) )
1386 if ( style->enabled && row == 0 )
1387 color = style->cellBackgroundColor;
1388 if ( QgsLayoutTableStyle *style = mCellStyles.value( LastRow ) )
1389 if ( style->enabled && row == mTableContents.count() - 1 )
1390 color = style->cellBackgroundColor;
1391
1392 if ( row >= 0 )
1393 {
1394 QgsConditionalStyle conditionalStyle = conditionalCellStyle( row, column );
1395 if ( conditionalStyle.backgroundColor().isValid() )
1396 color = conditionalStyle.backgroundColor();
1397 }
1398
1399 return color;
1400}
1401
1402void QgsLayoutTable::drawVerticalGridLines( QgsLayoutItemRenderContext &context, const QMap<int, double> &maxWidthMap, int firstRow, int lastRow, bool hasHeader, bool mergeCells ) const
1403{
1404 //vertical lines
1405 if ( lastRow - firstRow < 1 && !hasHeader )
1406 {
1407 return;
1408 }
1409
1410 QPainter *painter = context.renderContext().painter();
1411
1412 //calculate height of table within frame
1413 double tableHeight = 0;
1414 if ( hasHeader )
1415 {
1416 tableHeight += ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2 + mMaxRowHeightMap[0];
1417 }
1418 tableHeight += ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 );
1419 double headerHeight = tableHeight;
1420
1422 for ( int row = firstRow; row < lastRow; ++row )
1423 {
1424 double rowHeight = row < mTableContents.count() ? mMaxRowHeightMap[row + 1] : cellBodyHeightForEmptyRows;
1425 tableHeight += rowHeight + ( mShowGrid && mHorizontalGrid ? mGridStrokeWidth : 0 ) + mCellMargin * 2;
1426 }
1427
1428 double halfGridStrokeWidth = ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 ) / 2.0;
1429 double currentX = halfGridStrokeWidth;
1430 painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, tableHeight - halfGridStrokeWidth ) );
1431 currentX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1432 QMap<int, double>::const_iterator maxColWidthIt = maxWidthMap.constBegin();
1433 int col = 1;
1434 for ( ; maxColWidthIt != maxWidthMap.constEnd(); ++maxColWidthIt )
1435 {
1436 currentX += ( maxColWidthIt.value() + 2 * mCellMargin );
1437 if ( col == maxWidthMap.size() || !mergeCells )
1438 {
1439 painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, tableHeight - halfGridStrokeWidth ) );
1440 }
1441 else if ( hasHeader )
1442 {
1443 painter->drawLine( QPointF( currentX, halfGridStrokeWidth ), QPointF( currentX, headerHeight - halfGridStrokeWidth ) );
1444 }
1445
1446 currentX += ( mShowGrid && mVerticalGrid ? mGridStrokeWidth : 0 );
1447 col++;
1448 }
1449}
1450
1452{
1454
1455 //force recalculation of frame rects, so that they are set to the correct
1456 //fixed and minimum frame sizes
1458}
1459
1461{
1462 return ( contents.indexOf( row ) >= 0 );
1463}
1464
1466{
1467 return mContentTextFormat;
1468}
1469
1471{
1472 return mHeaderTextFormat;
1473}
1474
1475Qt::Alignment QgsLayoutTable::horizontalAlignmentForCell( int, int column ) const
1476{
1477 return mColumns.value( column ).hAlignment();
1478}
1479
1480Qt::Alignment QgsLayoutTable::verticalAlignmentForCell( int, int column ) const
1481{
1482 return mColumns.value( column ).vAlignment();
1483}
1484
@ ApplyScalingWorkaroundForTextRendering
Whether a scaling workaround designed to stablise the rendering of small font sizes (or for painters ...
TextHorizontalAlignment
Text horizontal alignment.
Definition: qgis.h:1476
@ WrapLines
Automatically wrap long lines of text.
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.
Definition: qgslayoutitem.h:45
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Definition: qgslayoutitem.h:72
QgsLayoutSize sizeWithUnits() const
Returns the item's current size, including units.
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.
Definition: qgslayoutsize.h:90
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.
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.
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:51
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.
QPainter * painter()
Returns the destination QPainter for the render operation.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
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.
static QColor decodeColor(const QString &str)
static QString encodeColor(const QColor &color)
Container for all settings relating to text rendering.
Definition: qgstextformat.h:41
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 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.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units for the size of rendered text.
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 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 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())
Draws text within a rectangle using the specified settings.
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.
@ RenderPoints
Points (e.g., for font sizes)
Definition: qgsunittypes.h:173
@ RenderPixels
Pixels.
Definition: qgsunittypes.h:171
@ RenderMillimeters
Millimeters.
Definition: qgsunittypes.h:169
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:37
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:3061
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:3060
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2527