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