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