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