1 /***************************************************************************
2 qgslayoutitemmanualtable.cpp
3 ---------------------------
4 begin : January 2020
5 copyright : (C) 2020 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 "qgslayoutitemmanualtable.h"
19 #include "qgsconditionalstyle.h"
20 #include "qgslayoutitemregistry.h"
21 #include "qgslayouttablecolumn.h"
22 #include "qgsnumericformat.h"
23 #include "qgsxmlutils.h"
24 #include "qgsexpressioncontextutils.h"
25
26 //
27 // QgsLayoutItemManualTable
28 //
29
QgsLayoutItemManualTable(QgsLayout * layout)30 QgsLayoutItemManualTable::QgsLayoutItemManualTable( QgsLayout *layout )
31 : QgsLayoutTable( layout )
32 {
33 setHeaderMode( NoHeaders );
34 refreshAttributes();
35 }
36
~QgsLayoutItemManualTable()37 QgsLayoutItemManualTable::~QgsLayoutItemManualTable()
38 {
39 }
40
type() const41 int QgsLayoutItemManualTable::type() const
42 {
43 return QgsLayoutItemRegistry::LayoutManualTable;
44 }
45
icon() const46 QIcon QgsLayoutItemManualTable::icon() const
47 {
48 return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemTable.svg" ) );
49 }
50
create(QgsLayout * layout)51 QgsLayoutItemManualTable *QgsLayoutItemManualTable::create( QgsLayout *layout )
52 {
53 return new QgsLayoutItemManualTable( layout );
54 }
55
displayName() const56 QString QgsLayoutItemManualTable::displayName() const
57 {
58 return tr( "<Table frame>" );
59 }
60
getTableContents(QgsLayoutTableContents & contents)61 bool QgsLayoutItemManualTable::getTableContents( QgsLayoutTableContents &contents )
62 {
63 contents.clear();
64
65 QgsNumericFormatContext numericContext;
66
67 QgsExpressionContext context = createExpressionContext();
68
69 int rowNumber = 0;
70 for ( const QgsTableRow &row : std::as_const( mContents ) )
71 {
72 QgsLayoutTableRow currentRow;
73
74 for ( int columnNumber = 0; columnNumber < mColumns.count(); ++columnNumber )
75 {
76 if ( columnNumber < row.count() )
77 {
78 QVariant cellContent = row.at( columnNumber ).content();
79
80 if ( cellContent.canConvert< QgsProperty >() )
81 {
82 // expression based cell content, evaluate now
83 QgsExpressionContextScopePopper popper( context, scopeForCell( rowNumber, columnNumber ) );
84 cellContent = cellContent.value< QgsProperty >().value( context );
85 }
86
87 if ( row.at( columnNumber ).numericFormat() )
88 currentRow << row.at( columnNumber ).numericFormat()->formatDouble( cellContent.toDouble(), numericContext );
89 else
90 currentRow << cellContent.toString();
91 }
92 else
93 {
94 currentRow << QString();
95 }
96 }
97 contents << currentRow;
98 rowNumber++;
99 }
100
101 recalculateTableSize();
102 return true;
103 }
104
conditionalCellStyle(int row,int column) const105 QgsConditionalStyle QgsLayoutItemManualTable::conditionalCellStyle( int row, int column ) const
106 {
107 if ( row < 0 || row >= mContents.size() )
108 return QgsConditionalStyle();
109
110 const QgsTableRow &rowContents = mContents[ row ];
111 if ( column < 0 || column > rowContents.size() )
112 return QgsConditionalStyle();
113
114 const QgsTableCell &c = rowContents[ column ];
115 QgsConditionalStyle res;
116 if ( c.foregroundColor().isValid() )
117 res.setTextColor( c.foregroundColor() );
118 if ( c.backgroundColor().isValid() )
119 res.setBackgroundColor( c.backgroundColor() );
120
121 return res;
122 }
123
setTableContents(const QgsTableContents & contents)124 void QgsLayoutItemManualTable::setTableContents( const QgsTableContents &contents )
125 {
126 mContents = contents;
127
128 refreshColumns();
129 refreshAttributes();
130 }
131
tableContents() const132 QgsTableContents QgsLayoutItemManualTable::tableContents() const
133 {
134 return mContents;
135 }
136
setRowHeights(const QList<double> & heights)137 void QgsLayoutItemManualTable::setRowHeights( const QList<double> &heights )
138 {
139 mRowHeights = heights;
140
141 refreshAttributes();
142 }
143
setColumnWidths(const QList<double> & widths)144 void QgsLayoutItemManualTable::setColumnWidths( const QList<double> &widths )
145 {
146 mColumnWidths = widths;
147
148 refreshColumns();
149 refreshAttributes();
150 }
151
includeTableHeader() const152 bool QgsLayoutItemManualTable::includeTableHeader() const
153 {
154 return mIncludeHeader;
155 }
156
setIncludeTableHeader(bool included)157 void QgsLayoutItemManualTable::setIncludeTableHeader( bool included )
158 {
159 mIncludeHeader = included;
160
161 if ( !mIncludeHeader )
162 setHeaderMode( NoHeaders );
163 else
164 setHeaderMode( AllFrames );
165 refreshColumns();
166 refreshAttributes();
167 }
168
headers()169 QgsLayoutTableColumns &QgsLayoutItemManualTable::headers()
170 {
171 return mHeaders;
172 }
173
setHeaders(const QgsLayoutTableColumns & headers)174 void QgsLayoutItemManualTable::setHeaders( const QgsLayoutTableColumns &headers )
175 {
176 mHeaders.clear();
177
178 mHeaders.append( headers );
179 refreshColumns();
180 refreshAttributes();
181 }
182
writePropertiesToElement(QDomElement & tableElem,QDomDocument & doc,const QgsReadWriteContext & context) const183 bool QgsLayoutItemManualTable::writePropertiesToElement( QDomElement &tableElem, QDomDocument &doc, const QgsReadWriteContext &context ) const
184 {
185 if ( !QgsLayoutTable::writePropertiesToElement( tableElem, doc, context ) )
186 return false;
187
188 tableElem.setAttribute( QStringLiteral( "includeHeader" ), mIncludeHeader ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
189
190 //headers
191 QDomElement headersElem = doc.createElement( QStringLiteral( "headers" ) );
192 for ( const QgsLayoutTableColumn &header : std::as_const( mHeaders ) )
193 {
194 QDomElement headerElem = doc.createElement( QStringLiteral( "header" ) );
195 header.writeXml( headerElem, doc );
196 headersElem.appendChild( headerElem );
197 }
198 tableElem.appendChild( headersElem );
199
200 QDomElement contentsElement = doc.createElement( QStringLiteral( "contents" ) );
201 for ( const QgsTableRow &row : mContents )
202 {
203 QDomElement rowElement = doc.createElement( QStringLiteral( "row" ) );
204 for ( int i = 0; i < mColumns.count(); ++i )
205 {
206 if ( i < row.count() )
207 {
208 rowElement.appendChild( QgsXmlUtils::writeVariant( row.at( i ).properties( context ), doc ) );
209 }
210 }
211 contentsElement.appendChild( rowElement );
212 }
213 tableElem.appendChild( contentsElement );
214
215 QDomElement rowHeightsElement = doc.createElement( QStringLiteral( "rowHeights" ) );
216 for ( double height : mRowHeights )
217 {
218 QDomElement rowElement = doc.createElement( QStringLiteral( "row" ) );
219 rowElement.setAttribute( QStringLiteral( "height" ), height );
220 rowHeightsElement.appendChild( rowElement );
221 }
222 tableElem.appendChild( rowHeightsElement );
223
224 QDomElement columnWidthsElement = doc.createElement( QStringLiteral( "columnWidths" ) );
225 for ( double width : mColumnWidths )
226 {
227 QDomElement columnElement = doc.createElement( QStringLiteral( "column" ) );
228 columnElement.setAttribute( QStringLiteral( "width" ), width );
229 columnWidthsElement.appendChild( columnElement );
230 }
231 tableElem.appendChild( columnWidthsElement );
232
233 return true;
234 }
235
readPropertiesFromElement(const QDomElement & itemElem,const QDomDocument & doc,const QgsReadWriteContext & context)236 bool QgsLayoutItemManualTable::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context )
237 {
238 if ( !QgsLayoutTable::readPropertiesFromElement( itemElem, doc, context ) )
239 return false;
240
241 mIncludeHeader = itemElem.attribute( QStringLiteral( "includeHeader" ) ).toInt();
242 //restore header specifications
243 mHeaders.clear();
244 QDomNodeList headersList = itemElem.elementsByTagName( QStringLiteral( "headers" ) );
245 if ( !headersList.isEmpty() )
246 {
247 QDomElement headersElem = headersList.at( 0 ).toElement();
248 QDomNodeList headerEntryList = headersElem.elementsByTagName( QStringLiteral( "header" ) );
249 for ( int i = 0; i < headerEntryList.size(); ++i )
250 {
251 QDomElement headerElem = headerEntryList.at( i ).toElement();
252 QgsLayoutTableColumn header;
253 header.readXml( headerElem );
254 mHeaders.append( header );
255 }
256 }
257
258 mRowHeights.clear();
259 const QDomNodeList rowHeightNodeList = itemElem.firstChildElement( QStringLiteral( "rowHeights" ) ).childNodes();
260 mRowHeights.reserve( rowHeightNodeList.size() );
261 for ( int r = 0; r < rowHeightNodeList.size(); ++r )
262 {
263 const QDomElement rowHeightElement = rowHeightNodeList.at( r ).toElement();
264 mRowHeights.append( rowHeightElement.attribute( QStringLiteral( "height" ) ).toDouble() );
265 }
266
267 mColumnWidths.clear();
268 const QDomNodeList columnWidthNodeList = itemElem.firstChildElement( QStringLiteral( "columnWidths" ) ).childNodes();
269 mColumnWidths.reserve( columnWidthNodeList.size() );
270 for ( int r = 0; r < columnWidthNodeList.size(); ++r )
271 {
272 const QDomElement columnWidthElement = columnWidthNodeList.at( r ).toElement();
273 mColumnWidths.append( columnWidthElement.attribute( QStringLiteral( "width" ) ).toDouble() );
274 }
275
276 QgsTableContents newContents;
277 const QDomElement contentsElement = itemElem.firstChildElement( QStringLiteral( "contents" ) );
278 const QDomNodeList rowNodeList = contentsElement.childNodes();
279 newContents.reserve( rowNodeList.size() );
280 for ( int r = 0; r < rowNodeList.size(); ++r )
281 {
282 QgsTableRow row;
283 const QDomElement rowElement = rowNodeList.at( r ).toElement();
284 const QDomNodeList cellNodeList = rowElement.childNodes();
285 row.reserve( cellNodeList.size() );
286 for ( int c = 0; c < cellNodeList.size(); ++c )
287 {
288 const QDomElement cellElement = cellNodeList.at( c ).toElement();
289 QgsTableCell newCell;
290 newCell.setProperties( QgsXmlUtils::readVariant( cellElement ).toMap(), context );
291 row << newCell;
292 }
293 newContents << row;
294 }
295 setTableContents( newContents );
296
297 emit changed();
298 return true;
299 }
300
calculateMaxRowHeights()301 bool QgsLayoutItemManualTable::calculateMaxRowHeights()
302 {
303 if ( !QgsLayoutTable::calculateMaxRowHeights() )
304 return false;
305
306 QMap<int, double> newHeights;
307 for ( auto it = mMaxRowHeightMap.constBegin(); it != mMaxRowHeightMap.constEnd(); ++it )
308 {
309 // first row in mMaxRowHeightMap corresponds to header, which we ignore here
310 const int row = it.key() - 1;
311 const double presetHeight = mRowHeights.value( row );
312 double thisRowHeight = it.value();
313 if ( presetHeight > 0 )
314 thisRowHeight = presetHeight;
315 newHeights.insert( row + 1, thisRowHeight );
316 }
317 mMaxRowHeightMap = newHeights;
318 return true;
319 }
320
textFormatForHeader(int column) const321 QgsTextFormat QgsLayoutItemManualTable::textFormatForHeader( int column ) const
322 {
323 // if ( mHeaders.value( column ).)
324 return QgsLayoutTable::textFormatForHeader( column );
325 }
326
textFormatForCell(int row,int column) const327 QgsTextFormat QgsLayoutItemManualTable::textFormatForCell( int row, int column ) const
328 {
329 if ( mContents.value( row ).value( column ).textFormat().isValid() )
330 return mContents.value( row ).value( column ).textFormat();
331
332 return QgsLayoutTable::textFormatForCell( row, column );
333 }
334
horizontalAlignmentForCell(int row,int column) const335 Qt::Alignment QgsLayoutItemManualTable::horizontalAlignmentForCell( int row, int column ) const
336 {
337 if ( row < mContents.size() && column < mContents.at( row ).size() )
338 return mContents.value( row ).value( column ).horizontalAlignment();
339
340 return QgsLayoutTable::horizontalAlignmentForCell( row, column );
341 }
342
verticalAlignmentForCell(int row,int column) const343 Qt::Alignment QgsLayoutItemManualTable::verticalAlignmentForCell( int row, int column ) const
344 {
345 if ( row < mContents.size() && column < mContents.at( row ).size() )
346 return mContents.value( row ).value( column ).verticalAlignment();
347
348 return QgsLayoutTable::verticalAlignmentForCell( row, column );
349 }
350
refreshColumns()351 void QgsLayoutItemManualTable::refreshColumns()
352 {
353 // refresh columns
354 QgsLayoutTableColumns columns;
355 if ( !mContents.empty() )
356 {
357 int colIndex = 0;
358 const QgsTableRow &firstRow = mContents[ 0 ];
359 columns.reserve( firstRow.size() );
360 for ( const QgsTableCell &cell : firstRow )
361 {
362 ( void )cell;
363 QgsLayoutTableColumn newCol( mHeaders.value( colIndex ).heading() );
364 newCol.setWidth( mColumnWidths.value( colIndex ) );
365 columns << newCol;
366 colIndex++;
367 }
368 }
369 setColumns( columns );
370 }
371