Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgslayoutitemattributetable.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 "qgslayoutitemattributetable.h"
19 : : #include "qgslayout.h"
20 : : #include "qgslayouttablecolumn.h"
21 : : #include "qgslayoutitemmap.h"
22 : : #include "qgslayoututils.h"
23 : : #include "qgsfeatureiterator.h"
24 : : #include "qgsvectorlayer.h"
25 : : #include "qgslayoutframe.h"
26 : : #include "qgsproject.h"
27 : : #include "qgsrelationmanager.h"
28 : : #include "qgsgeometry.h"
29 : : #include "qgsexception.h"
30 : : #include "qgsmapsettings.h"
31 : : #include "qgsexpressioncontextutils.h"
32 : : #include "qgsexpressionnodeimpl.h"
33 : : #include "qgsgeometryengine.h"
34 : : #include "qgsconditionalstyle.h"
35 : :
36 : : //
37 : : // QgsLayoutItemAttributeTable
38 : : //
39 : :
40 : 0 : QgsLayoutItemAttributeTable::QgsLayoutItemAttributeTable( QgsLayout *layout )
41 : 0 : : QgsLayoutTable( layout )
42 : 0 : {
43 : 0 : if ( mLayout )
44 : : {
45 : 0 : connect( mLayout->project(), static_cast < void ( QgsProject::* )( const QString & ) >( &QgsProject::layerWillBeRemoved ), this, &QgsLayoutItemAttributeTable::removeLayer );
46 : :
47 : : //coverage layer change = regenerate columns
48 : 0 : connect( &mLayout->reportContext(), &QgsLayoutReportContext::layerChanged, this, &QgsLayoutItemAttributeTable::atlasLayerChanged );
49 : 0 : }
50 : 0 : refreshAttributes();
51 : 0 : }
52 : :
53 : 0 : int QgsLayoutItemAttributeTable::type() const
54 : : {
55 : 0 : return QgsLayoutItemRegistry::LayoutAttributeTable;
56 : : }
57 : :
58 : 0 : QIcon QgsLayoutItemAttributeTable::icon() const
59 : : {
60 : 0 : return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemTable.svg" ) );
61 : 0 : }
62 : :
63 : 0 : QgsLayoutItemAttributeTable *QgsLayoutItemAttributeTable::create( QgsLayout *layout )
64 : : {
65 : 0 : return new QgsLayoutItemAttributeTable( layout );
66 : 0 : }
67 : :
68 : 0 : QString QgsLayoutItemAttributeTable::displayName() const
69 : : {
70 : 0 : return tr( "<Attribute table frame>" );
71 : : }
72 : :
73 : 0 : void QgsLayoutItemAttributeTable::setVectorLayer( QgsVectorLayer *layer )
74 : : {
75 : 0 : if ( layer == mVectorLayer.get() )
76 : : {
77 : : //no change
78 : 0 : return;
79 : : }
80 : :
81 : 0 : QgsVectorLayer *prevLayer = sourceLayer();
82 : 0 : mVectorLayer.setLayer( layer );
83 : :
84 : 0 : if ( mSource == QgsLayoutItemAttributeTable::LayerAttributes && layer != prevLayer )
85 : : {
86 : 0 : if ( prevLayer )
87 : : {
88 : : //disconnect from previous layer
89 : 0 : disconnect( prevLayer, &QgsVectorLayer::layerModified, this, &QgsLayoutTable::refreshAttributes );
90 : 0 : }
91 : :
92 : : //rebuild column list to match all columns from layer
93 : 0 : resetColumns();
94 : :
95 : : //listen for modifications to layer and refresh table when they occur
96 : 0 : connect( mVectorLayer.get(), &QgsVectorLayer::layerModified, this, &QgsLayoutTable::refreshAttributes );
97 : 0 : }
98 : :
99 : 0 : refreshAttributes();
100 : 0 : emit changed();
101 : 0 : }
102 : :
103 : 0 : void QgsLayoutItemAttributeTable::setRelationId( const QString &relationId )
104 : : {
105 : 0 : if ( relationId == mRelationId )
106 : : {
107 : : //no change
108 : 0 : return;
109 : : }
110 : :
111 : 0 : QgsVectorLayer *prevLayer = sourceLayer();
112 : 0 : mRelationId = relationId;
113 : 0 : QgsRelation relation = mLayout->project()->relationManager()->relation( mRelationId );
114 : 0 : QgsVectorLayer *newLayer = relation.referencingLayer();
115 : :
116 : 0 : if ( mSource == QgsLayoutItemAttributeTable::RelationChildren && newLayer != prevLayer )
117 : : {
118 : 0 : if ( prevLayer )
119 : : {
120 : : //disconnect from previous layer
121 : 0 : disconnect( prevLayer, &QgsVectorLayer::layerModified, this, &QgsLayoutTable::refreshAttributes );
122 : 0 : }
123 : :
124 : : //rebuild column list to match all columns from layer
125 : 0 : resetColumns();
126 : :
127 : : //listen for modifications to layer and refresh table when they occur
128 : 0 : connect( newLayer, &QgsVectorLayer::layerModified, this, &QgsLayoutTable::refreshAttributes );
129 : 0 : }
130 : :
131 : 0 : refreshAttributes();
132 : 0 : emit changed();
133 : 0 : }
134 : :
135 : 0 : void QgsLayoutItemAttributeTable::atlasLayerChanged( QgsVectorLayer *layer )
136 : : {
137 : 0 : if ( mSource != QgsLayoutItemAttributeTable::AtlasFeature || layer == mCurrentAtlasLayer )
138 : : {
139 : : //nothing to do
140 : 0 : return;
141 : : }
142 : :
143 : : //atlas feature mode, atlas layer changed, so we need to reset columns
144 : 0 : if ( mCurrentAtlasLayer )
145 : : {
146 : : //disconnect from previous layer
147 : 0 : disconnect( mCurrentAtlasLayer, &QgsVectorLayer::layerModified, this, &QgsLayoutTable::refreshAttributes );
148 : 0 : }
149 : :
150 : 0 : const bool mustRebuildColumns = static_cast< bool >( mCurrentAtlasLayer ) || mColumns.empty();
151 : 0 : mCurrentAtlasLayer = layer;
152 : :
153 : 0 : if ( mustRebuildColumns )
154 : : {
155 : : //rebuild column list to match all columns from layer
156 : 0 : resetColumns();
157 : 0 : }
158 : :
159 : 0 : refreshAttributes();
160 : :
161 : : //listen for modifications to layer and refresh table when they occur
162 : 0 : connect( layer, &QgsVectorLayer::layerModified, this, &QgsLayoutTable::refreshAttributes );
163 : 0 : }
164 : :
165 : 0 : void QgsLayoutItemAttributeTable::resetColumns()
166 : : {
167 : 0 : QgsVectorLayer *source = sourceLayer();
168 : 0 : if ( !source )
169 : : {
170 : 0 : return;
171 : : }
172 : :
173 : : //remove existing columns
174 : 0 : mColumns.clear();
175 : 0 : mSortColumns.clear();
176 : :
177 : : //rebuild columns list from vector layer fields
178 : 0 : int idx = 0;
179 : 0 : const QgsFields sourceFields = source->fields();
180 : :
181 : 0 : for ( const auto &field : sourceFields )
182 : : {
183 : 0 : QString currentAlias = source->attributeDisplayName( idx );
184 : 0 : QgsLayoutTableColumn col;
185 : 0 : col.setAttribute( field.name() );
186 : 0 : col.setHeading( currentAlias );
187 : 0 : mColumns.append( col );
188 : 0 : idx++;
189 : 0 : }
190 : 0 : }
191 : :
192 : 0 : void QgsLayoutItemAttributeTable::disconnectCurrentMap()
193 : : {
194 : 0 : if ( !mMap )
195 : : {
196 : 0 : return;
197 : : }
198 : :
199 : 0 : disconnect( mMap, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutTable::refreshAttributes );
200 : 0 : disconnect( mMap, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutTable::refreshAttributes );
201 : 0 : disconnect( mMap, &QObject::destroyed, this, &QgsLayoutItemAttributeTable::disconnectCurrentMap );
202 : 0 : mMap = nullptr;
203 : 0 : }
204 : :
205 : 0 : bool QgsLayoutItemAttributeTable::useConditionalStyling() const
206 : : {
207 : 0 : return mUseConditionalStyling;
208 : : }
209 : :
210 : 0 : void QgsLayoutItemAttributeTable::setUseConditionalStyling( bool useConditionalStyling )
211 : : {
212 : 0 : if ( useConditionalStyling == mUseConditionalStyling )
213 : : {
214 : 0 : return;
215 : : }
216 : :
217 : 0 : mUseConditionalStyling = useConditionalStyling;
218 : 0 : refreshAttributes();
219 : 0 : emit changed();
220 : 0 : }
221 : :
222 : 0 : void QgsLayoutItemAttributeTable::setMap( QgsLayoutItemMap *map )
223 : : {
224 : 0 : if ( map == mMap )
225 : : {
226 : : //no change
227 : 0 : return;
228 : : }
229 : 0 : disconnectCurrentMap();
230 : :
231 : 0 : mMap = map;
232 : 0 : if ( mMap )
233 : : {
234 : : //listen out for extent changes in linked map
235 : 0 : connect( mMap, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutTable::refreshAttributes );
236 : 0 : connect( mMap, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutTable::refreshAttributes );
237 : 0 : connect( mMap, &QObject::destroyed, this, &QgsLayoutItemAttributeTable::disconnectCurrentMap );
238 : 0 : }
239 : 0 : refreshAttributes();
240 : 0 : emit changed();
241 : 0 : }
242 : :
243 : 0 : void QgsLayoutItemAttributeTable::setMaximumNumberOfFeatures( const int features )
244 : : {
245 : 0 : if ( features == mMaximumNumberOfFeatures )
246 : : {
247 : 0 : return;
248 : : }
249 : :
250 : 0 : mMaximumNumberOfFeatures = features;
251 : 0 : refreshAttributes();
252 : 0 : emit changed();
253 : 0 : }
254 : :
255 : 0 : void QgsLayoutItemAttributeTable::setUniqueRowsOnly( const bool uniqueOnly )
256 : : {
257 : 0 : if ( uniqueOnly == mShowUniqueRowsOnly )
258 : : {
259 : 0 : return;
260 : : }
261 : :
262 : 0 : mShowUniqueRowsOnly = uniqueOnly;
263 : 0 : refreshAttributes();
264 : 0 : emit changed();
265 : 0 : }
266 : :
267 : 0 : void QgsLayoutItemAttributeTable::setDisplayOnlyVisibleFeatures( const bool visibleOnly )
268 : : {
269 : 0 : if ( visibleOnly == mShowOnlyVisibleFeatures )
270 : : {
271 : 0 : return;
272 : : }
273 : :
274 : 0 : mShowOnlyVisibleFeatures = visibleOnly;
275 : 0 : refreshAttributes();
276 : 0 : emit changed();
277 : 0 : }
278 : :
279 : 0 : void QgsLayoutItemAttributeTable::setFilterToAtlasFeature( const bool filterToAtlas )
280 : : {
281 : 0 : if ( filterToAtlas == mFilterToAtlasIntersection )
282 : : {
283 : 0 : return;
284 : : }
285 : :
286 : 0 : mFilterToAtlasIntersection = filterToAtlas;
287 : 0 : refreshAttributes();
288 : 0 : emit changed();
289 : 0 : }
290 : :
291 : 0 : void QgsLayoutItemAttributeTable::setFilterFeatures( const bool filter )
292 : : {
293 : 0 : if ( filter == mFilterFeatures )
294 : : {
295 : 0 : return;
296 : : }
297 : :
298 : 0 : mFilterFeatures = filter;
299 : 0 : refreshAttributes();
300 : 0 : emit changed();
301 : 0 : }
302 : :
303 : 0 : void QgsLayoutItemAttributeTable::setFeatureFilter( const QString &expression )
304 : : {
305 : 0 : if ( expression == mFeatureFilter )
306 : : {
307 : 0 : return;
308 : : }
309 : :
310 : 0 : mFeatureFilter = expression;
311 : 0 : refreshAttributes();
312 : 0 : emit changed();
313 : 0 : }
314 : :
315 : 0 : void QgsLayoutItemAttributeTable::setDisplayedFields( const QStringList &fields, bool refresh )
316 : : {
317 : 0 : QgsVectorLayer *source = sourceLayer();
318 : 0 : if ( !source )
319 : 0 : {
320 : 0 : return;
321 : 0 : }
322 : :
323 : : //rebuild columns list, taking only fields contained in supplied list
324 : 0 : mColumns.clear();
325 : :
326 : 0 : const QgsFields layerFields = source->fields();
327 : :
328 : 0 : if ( !fields.isEmpty() )
329 : : {
330 : 0 : for ( const QString &field : fields )
331 : : {
332 : 0 : int attrIdx = layerFields.lookupField( field );
333 : 0 : if ( attrIdx < 0 )
334 : : {
335 : 0 : continue;
336 : : }
337 : 0 : QString currentAlias = source->attributeDisplayName( attrIdx );
338 : 0 : QgsLayoutTableColumn col;
339 : 0 : col.setAttribute( layerFields.at( attrIdx ).name() );
340 : 0 : col.setHeading( currentAlias );
341 : 0 : mColumns.append( col );
342 : 0 : }
343 : 0 : }
344 : : else
345 : : {
346 : 0 : //resetting, so add all attributes to columns
347 : 0 : int idx = 0;
348 : 0 : for ( const QgsField &field : layerFields )
349 : 0 : {
350 : 0 : QString currentAlias = source->attributeDisplayName( idx );
351 : 0 : QgsLayoutTableColumn col;
352 : 0 : col.setAttribute( field.name() );
353 : 0 : col.setHeading( currentAlias );
354 : 0 : mColumns.append( col );
355 : 0 : idx++;
356 : 0 : }
357 : : }
358 : :
359 : 0 : if ( refresh )
360 : : {
361 : 0 : refreshAttributes();
362 : 0 : }
363 : 0 : }
364 : :
365 : 0 : void QgsLayoutItemAttributeTable::restoreFieldAliasMap( const QMap<int, QString> &map )
366 : : {
367 : 0 : QgsVectorLayer *source = sourceLayer();
368 : 0 : if ( !source )
369 : : {
370 : 0 : return;
371 : : }
372 : :
373 : 0 : for ( int i = 0; i < mColumns.count(); i++ )
374 : : {
375 : 0 : int attrIdx = source->fields().lookupField( mColumns[i].attribute() );
376 : 0 : if ( map.contains( attrIdx ) )
377 : : {
378 : 0 : mColumns[i].setHeading( map.value( attrIdx ) );
379 : 0 : }
380 : : else
381 : : {
382 : 0 : mColumns[i].setHeading( source->attributeDisplayName( attrIdx ) );
383 : : }
384 : 0 : }
385 : 0 : }
386 : :
387 : 0 : bool QgsLayoutItemAttributeTable::getTableContents( QgsLayoutTableContents &contents )
388 : : {
389 : 0 : contents.clear();
390 : :
391 : 0 : QgsVectorLayer *layer = sourceLayer();
392 : 0 : if ( !layer )
393 : : {
394 : : //no source layer
395 : 0 : return false;
396 : : }
397 : :
398 : 0 : const QgsConditionalLayerStyles *conditionalStyles = layer->conditionalStyles();
399 : :
400 : 0 : QgsExpressionContext context = createExpressionContext();
401 : 0 : context.setFields( layer->fields() );
402 : :
403 : 0 : QgsFeatureRequest req;
404 : 0 : req.setExpressionContext( context );
405 : :
406 : : //prepare filter expression
407 : 0 : std::unique_ptr<QgsExpression> filterExpression;
408 : 0 : bool activeFilter = false;
409 : 0 : if ( mFilterFeatures && !mFeatureFilter.isEmpty() )
410 : : {
411 : 0 : filterExpression = std::make_unique< QgsExpression >( mFeatureFilter );
412 : 0 : if ( !filterExpression->hasParserError() )
413 : : {
414 : 0 : activeFilter = true;
415 : 0 : req.setFilterExpression( mFeatureFilter );
416 : 0 : }
417 : 0 : }
418 : :
419 : : #ifdef HAVE_SERVER_PYTHON_PLUGINS
420 : : if ( mLayout->renderContext().featureFilterProvider() )
421 : : {
422 : : mLayout->renderContext().featureFilterProvider()->filterFeatures( layer, req );
423 : : }
424 : : #endif
425 : :
426 : 0 : QgsRectangle selectionRect;
427 : 0 : QgsGeometry visibleRegion;
428 : 0 : std::unique_ptr< QgsGeometryEngine > visibleMapEngine;
429 : 0 : if ( mMap && mShowOnlyVisibleFeatures )
430 : : {
431 : 0 : visibleRegion = QgsGeometry::fromQPolygonF( mMap->visibleExtentPolygon() );
432 : 0 : selectionRect = visibleRegion.boundingBox();
433 : : //transform back to layer CRS
434 : 0 : QgsCoordinateTransform coordTransform( layer->crs(), mMap->crs(), mLayout->project() );
435 : : try
436 : : {
437 : 0 : selectionRect = coordTransform.transformBoundingBox( selectionRect, QgsCoordinateTransform::ReverseTransform );
438 : 0 : visibleRegion.transform( coordTransform, QgsCoordinateTransform::ReverseTransform );
439 : 0 : }
440 : : catch ( QgsCsException &cse )
441 : : {
442 : 0 : Q_UNUSED( cse )
443 : 0 : return false;
444 : 0 : }
445 : 0 : visibleMapEngine.reset( QgsGeometry::createGeometryEngine( visibleRegion.constGet() ) );
446 : 0 : visibleMapEngine->prepareGeometry();
447 : 0 : }
448 : :
449 : 0 : QgsGeometry atlasGeometry;
450 : 0 : std::unique_ptr< QgsGeometryEngine > atlasGeometryEngine;
451 : 0 : if ( mFilterToAtlasIntersection )
452 : : {
453 : 0 : atlasGeometry = mLayout->reportContext().currentGeometry( layer->crs() );
454 : 0 : if ( !atlasGeometry.isNull() )
455 : : {
456 : 0 : if ( selectionRect.isNull() )
457 : : {
458 : 0 : selectionRect = atlasGeometry.boundingBox();
459 : 0 : }
460 : : else
461 : : {
462 : 0 : selectionRect = selectionRect.intersect( atlasGeometry.boundingBox() );
463 : : }
464 : :
465 : 0 : atlasGeometryEngine.reset( QgsGeometry::createGeometryEngine( atlasGeometry.constGet() ) );
466 : 0 : atlasGeometryEngine->prepareGeometry();
467 : 0 : }
468 : 0 : }
469 : :
470 : 0 : if ( mSource == QgsLayoutItemAttributeTable::RelationChildren )
471 : : {
472 : 0 : QgsRelation relation = mLayout->project()->relationManager()->relation( mRelationId );
473 : 0 : QgsFeature atlasFeature = mLayout->reportContext().feature();
474 : 0 : req = relation.getRelatedFeaturesRequest( atlasFeature );
475 : 0 : }
476 : :
477 : 0 : if ( !selectionRect.isEmpty() )
478 : 0 : req.setFilterRect( selectionRect );
479 : :
480 : 0 : req.setFlags( mShowOnlyVisibleFeatures ? QgsFeatureRequest::ExactIntersect : QgsFeatureRequest::NoFlags );
481 : :
482 : 0 : if ( mSource == QgsLayoutItemAttributeTable::AtlasFeature )
483 : : {
484 : : //source mode is current atlas feature
485 : 0 : QgsFeature atlasFeature = mLayout->reportContext().feature();
486 : 0 : req.setFilterFid( atlasFeature.id() );
487 : 0 : }
488 : :
489 : 0 : for ( const QgsLayoutTableColumn &column : std::as_const( mSortColumns ) )
490 : : {
491 : 0 : req.addOrderBy( column.attribute(), column.sortOrder() == Qt::AscendingOrder );
492 : : }
493 : :
494 : 0 : QgsFeature f;
495 : 0 : int counter = 0;
496 : 0 : QgsFeatureIterator fit = layer->getFeatures( req );
497 : :
498 : 0 : mConditionalStyles.clear();
499 : 0 : mFeatures.clear();
500 : :
501 : 0 : QVector< QVector< Cell > > tempContents;
502 : 0 : QgsLayoutTableContents existingContents;
503 : :
504 : 0 : while ( fit.nextFeature( f ) && counter < mMaximumNumberOfFeatures )
505 : : {
506 : 0 : context.setFeature( f );
507 : : //check feature against filter
508 : 0 : if ( activeFilter && filterExpression )
509 : : {
510 : 0 : QVariant result = filterExpression->evaluate( &context );
511 : : // skip this feature if the filter evaluation is false
512 : 0 : if ( !result.toBool() )
513 : : {
514 : 0 : continue;
515 : : }
516 : 0 : }
517 : :
518 : : // check against exact map bounds
519 : 0 : if ( visibleMapEngine )
520 : : {
521 : 0 : if ( !f.hasGeometry() )
522 : 0 : continue;
523 : :
524 : 0 : if ( !visibleMapEngine->intersects( f.geometry().constGet() ) )
525 : 0 : continue;
526 : 0 : }
527 : :
528 : : //check against atlas feature intersection
529 : 0 : if ( mFilterToAtlasIntersection )
530 : : {
531 : 0 : if ( !f.hasGeometry() || !atlasGeometryEngine )
532 : : {
533 : 0 : continue;
534 : : }
535 : :
536 : 0 : if ( !atlasGeometryEngine->intersects( f.geometry().constGet() ) )
537 : 0 : continue;
538 : 0 : }
539 : :
540 : 0 : QgsConditionalStyle rowStyle;
541 : :
542 : 0 : if ( mUseConditionalStyling )
543 : : {
544 : 0 : const QList<QgsConditionalStyle> styles = QgsConditionalStyle::matchingConditionalStyles( conditionalStyles->rowStyles(), QVariant(), context );
545 : 0 : rowStyle = QgsConditionalStyle::compressStyles( styles );
546 : 0 : }
547 : :
548 : : // We need to build up two different lists here -- one is a pair of the cell contents along with the cell style.
549 : : // We need this one because we do a sorting step later, and we need to ensure that the cell styling is attached to the right row and sorted
550 : : // correctly when this occurs
551 : : // We also need a list of just the cell contents, so that we can do a quick check for row uniqueness (when the
552 : : // corresponding option is enabled)
553 : 0 : QVector< Cell > currentRow;
554 : : #ifdef HAVE_SERVER_PYTHON_PLUGINS
555 : : mColumns = filteredColumns();
556 : : #endif
557 : 0 : currentRow.reserve( mColumns.count() );
558 : 0 : QgsLayoutTableRow rowContents;
559 : 0 : rowContents.reserve( mColumns.count() );
560 : :
561 : 0 : for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
562 : : {
563 : 0 : int idx = layer->fields().lookupField( column.attribute() );
564 : :
565 : 0 : QgsConditionalStyle style;
566 : :
567 : 0 : if ( idx != -1 )
568 : : {
569 : 0 : const QVariant val = f.attributes().at( idx );
570 : :
571 : 0 : if ( mUseConditionalStyling )
572 : : {
573 : 0 : QList<QgsConditionalStyle> styles = conditionalStyles->fieldStyles( layer->fields().at( idx ).name() );
574 : 0 : styles = QgsConditionalStyle::matchingConditionalStyles( styles, val, context );
575 : 0 : styles.insert( 0, rowStyle );
576 : 0 : style = QgsConditionalStyle::compressStyles( styles );
577 : 0 : }
578 : :
579 : 0 : QVariant v = replaceWrapChar( val );
580 : 0 : currentRow << Cell( v, style, f );
581 : 0 : rowContents << v;
582 : 0 : }
583 : : else
584 : : {
585 : : // Lets assume it's an expression
586 : 0 : std::unique_ptr< QgsExpression > expression = std::make_unique< QgsExpression >( column.attribute() );
587 : 0 : context.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "row_number" ), counter + 1, true ) );
588 : 0 : expression->prepare( &context );
589 : 0 : QVariant value = expression->evaluate( &context );
590 : :
591 : 0 : currentRow << Cell( value, rowStyle, f );
592 : 0 : rowContents << value;
593 : 0 : }
594 : 0 : }
595 : :
596 : 0 : if ( mShowUniqueRowsOnly )
597 : : {
598 : 0 : if ( contentsContainsRow( existingContents, rowContents ) )
599 : 0 : continue;
600 : 0 : }
601 : :
602 : 0 : tempContents << currentRow;
603 : 0 : existingContents << rowContents;
604 : 0 : ++counter;
605 : 0 : }
606 : :
607 : : // build final table contents
608 : 0 : contents.reserve( tempContents.size() );
609 : 0 : mConditionalStyles.reserve( tempContents.size() );
610 : 0 : mFeatures.reserve( tempContents.size() );
611 : 0 : for ( auto it = tempContents.constBegin(); it != tempContents.constEnd(); ++it )
612 : : {
613 : 0 : QgsLayoutTableRow row;
614 : 0 : QList< QgsConditionalStyle > rowStyles;
615 : 0 : row.reserve( it->size() );
616 : 0 : rowStyles.reserve( it->size() );
617 : :
618 : 0 : for ( auto cellIt = it->constBegin(); cellIt != it->constEnd(); ++cellIt )
619 : : {
620 : 0 : row << cellIt->content;
621 : 0 : rowStyles << cellIt->style;
622 : 0 : if ( cellIt == it->constBegin() )
623 : 0 : mFeatures << cellIt->feature;
624 : 0 : }
625 : 0 : contents << row;
626 : 0 : mConditionalStyles << rowStyles;
627 : 0 : }
628 : :
629 : 0 : recalculateTableSize();
630 : 0 : return true;
631 : 0 : }
632 : :
633 : 0 : QgsConditionalStyle QgsLayoutItemAttributeTable::conditionalCellStyle( int row, int column ) const
634 : : {
635 : 0 : if ( row >= mConditionalStyles.size() )
636 : 0 : return QgsConditionalStyle();
637 : :
638 : 0 : return mConditionalStyles.at( row ).at( column );
639 : 0 : }
640 : :
641 : 0 : QgsExpressionContextScope *QgsLayoutItemAttributeTable::scopeForCell( int row, int column ) const
642 : : {
643 : 0 : std::unique_ptr< QgsExpressionContextScope >scope( QgsLayoutTable::scopeForCell( row, column ) );
644 : 0 : scope->setFeature( mFeatures.value( row ) );
645 : 0 : scope->setFields( scope->feature().fields() );
646 : 0 : return scope.release();
647 : 0 : }
648 : :
649 : 0 : QgsExpressionContext QgsLayoutItemAttributeTable::createExpressionContext() const
650 : : {
651 : 0 : QgsExpressionContext context = QgsLayoutTable::createExpressionContext();
652 : :
653 : 0 : if ( mSource == LayerAttributes )
654 : : {
655 : 0 : context.appendScope( QgsExpressionContextUtils::layerScope( mVectorLayer.get() ) );
656 : 0 : }
657 : :
658 : 0 : return context;
659 : 0 : }
660 : :
661 : 0 : void QgsLayoutItemAttributeTable::finalizeRestoreFromXml()
662 : : {
663 : 0 : QgsLayoutTable::finalizeRestoreFromXml();
664 : 0 : if ( !mMap && !mMapUuid.isEmpty() && mLayout )
665 : : {
666 : 0 : mMap = qobject_cast< QgsLayoutItemMap *>( mLayout->itemByUuid( mMapUuid, true ) );
667 : 0 : if ( mMap )
668 : : {
669 : : //if we have found a valid map item, listen out to extent changes on it and refresh the table
670 : 0 : connect( mMap, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutTable::refreshAttributes );
671 : 0 : connect( mMap, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutTable::refreshAttributes );
672 : 0 : }
673 : 0 : }
674 : 0 : }
675 : :
676 : 0 : void QgsLayoutItemAttributeTable::refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property )
677 : : {
678 : 0 : QgsExpressionContext context = createExpressionContext();
679 : :
680 : 0 : if ( mSource == QgsLayoutItemAttributeTable::LayerAttributes &&
681 : 0 : ( property == QgsLayoutObject::AttributeTableSourceLayer || property == QgsLayoutObject::AllProperties ) )
682 : : {
683 : 0 : mDataDefinedVectorLayer = nullptr;
684 : :
685 : 0 : QString currentLayerIdentifier;
686 : 0 : if ( QgsVectorLayer *currentLayer = mVectorLayer.get() )
687 : 0 : currentLayerIdentifier = currentLayer->id();
688 : :
689 : 0 : const QString layerIdentifier = mDataDefinedProperties.valueAsString( QgsLayoutObject::AttributeTableSourceLayer, context, currentLayerIdentifier );
690 : 0 : QgsVectorLayer *ddLayer = qobject_cast< QgsVectorLayer * >( QgsLayoutUtils::mapLayerFromString( layerIdentifier, mLayout->project() ) );
691 : 0 : if ( ddLayer )
692 : 0 : mDataDefinedVectorLayer = ddLayer;
693 : 0 : }
694 : :
695 : 0 : QgsLayoutMultiFrame::refreshDataDefinedProperty( property );
696 : 0 : }
697 : :
698 : 0 : QVariant QgsLayoutItemAttributeTable::replaceWrapChar( const QVariant &variant ) const
699 : : {
700 : : //avoid converting variants to string if not required (try to maintain original type for sorting)
701 : 0 : if ( mWrapString.isEmpty() || !variant.toString().contains( mWrapString ) )
702 : 0 : return variant;
703 : :
704 : 0 : QString replaced = variant.toString();
705 : 0 : replaced.replace( mWrapString, QLatin1String( "\n" ) );
706 : 0 : return replaced;
707 : 0 : }
708 : :
709 : : #ifdef HAVE_SERVER_PYTHON_PLUGINS
710 : : QgsLayoutTableColumns QgsLayoutItemAttributeTable::filteredColumns()
711 : : {
712 : :
713 : : QgsLayoutTableColumns allowedColumns { mColumns };
714 : :
715 : : // Filter columns
716 : : if ( mLayout->renderContext().featureFilterProvider() )
717 : : {
718 : :
719 : : QgsVectorLayer *source { sourceLayer() };
720 : :
721 : : if ( ! source )
722 : : {
723 : : return allowedColumns;
724 : : }
725 : :
726 : : QHash<const QString, QSet<QString>> columnAttributesMap;
727 : : QSet<QString> allowedAttributes;
728 : :
729 : : for ( const auto &c : std::as_const( allowedColumns ) )
730 : : {
731 : : if ( ! c.attribute().isEmpty() && ! columnAttributesMap.contains( c.attribute() ) )
732 : : {
733 : : columnAttributesMap[ c.attribute() ] = QSet<QString>();
734 : : const QgsExpression columnExp { c.attribute() };
735 : : const auto constRefs { columnExp.findNodes<QgsExpressionNodeColumnRef>() };
736 : : for ( const auto &cref : constRefs )
737 : : {
738 : : columnAttributesMap[ c.attribute() ].insert( cref->name() );
739 : : allowedAttributes.insert( cref->name() );
740 : : }
741 : : }
742 : : }
743 : :
744 : : const QStringList filteredAttributes { layout()->renderContext().featureFilterProvider()->layerAttributes( source, allowedAttributes.values() ) };
745 : : #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
746 : : const QSet<QString> filteredAttributesSet( filteredAttributes.constBegin(), filteredAttributes.constEnd() );
747 : : #else
748 : : const QSet<QString> filteredAttributesSet { filteredAttributes.toSet() };
749 : : #endif
750 : : if ( filteredAttributesSet != allowedAttributes )
751 : : {
752 : : const auto forbidden { allowedAttributes.subtract( filteredAttributesSet ) };
753 : : allowedColumns.erase( std::remove_if( allowedColumns.begin(), allowedColumns.end(), [ &columnAttributesMap, &forbidden ]( QgsLayoutTableColumn & c ) -> bool
754 : : {
755 : : for ( const auto &f : std::as_const( forbidden ) )
756 : : {
757 : : if ( columnAttributesMap[ c.attribute() ].contains( f ) )
758 : : {
759 : : return true;
760 : : }
761 : : }
762 : : return false;
763 : : } ), allowedColumns.end() );
764 : :
765 : : }
766 : : }
767 : :
768 : : return allowedColumns;
769 : : }
770 : : #endif
771 : :
772 : 0 : QgsVectorLayer *QgsLayoutItemAttributeTable::sourceLayer() const
773 : : {
774 : 0 : switch ( mSource )
775 : : {
776 : : case QgsLayoutItemAttributeTable::AtlasFeature:
777 : 0 : return mLayout->reportContext().layer();
778 : : case QgsLayoutItemAttributeTable::LayerAttributes:
779 : : {
780 : 0 : if ( mDataDefinedVectorLayer )
781 : 0 : return mDataDefinedVectorLayer;
782 : : else
783 : 0 : return mVectorLayer.get();
784 : : }
785 : : case QgsLayoutItemAttributeTable::RelationChildren:
786 : : {
787 : 0 : QgsRelation relation = mLayout->project()->relationManager()->relation( mRelationId );
788 : 0 : return relation.referencingLayer();
789 : 0 : }
790 : : }
791 : 0 : return nullptr;
792 : 0 : }
793 : :
794 : 0 : void QgsLayoutItemAttributeTable::removeLayer( const QString &layerId )
795 : : {
796 : 0 : if ( mVectorLayer && mSource == QgsLayoutItemAttributeTable::LayerAttributes )
797 : : {
798 : 0 : if ( layerId == mVectorLayer->id() )
799 : : {
800 : 0 : mVectorLayer.setLayer( nullptr );
801 : : //remove existing columns
802 : 0 : mColumns.clear();
803 : 0 : }
804 : 0 : }
805 : 0 : }
806 : :
807 : 0 : void QgsLayoutItemAttributeTable::setWrapString( const QString &wrapString )
808 : : {
809 : 0 : if ( wrapString == mWrapString )
810 : : {
811 : 0 : return;
812 : : }
813 : :
814 : 0 : mWrapString = wrapString;
815 : 0 : refreshAttributes();
816 : 0 : emit changed();
817 : 0 : }
818 : :
819 : 0 : bool QgsLayoutItemAttributeTable::writePropertiesToElement( QDomElement &tableElem, QDomDocument &doc, const QgsReadWriteContext &context ) const
820 : : {
821 : 0 : if ( !QgsLayoutTable::writePropertiesToElement( tableElem, doc, context ) )
822 : 0 : return false;
823 : :
824 : 0 : tableElem.setAttribute( QStringLiteral( "source" ), QString::number( static_cast< int >( mSource ) ) );
825 : 0 : tableElem.setAttribute( QStringLiteral( "relationId" ), mRelationId );
826 : 0 : tableElem.setAttribute( QStringLiteral( "showUniqueRowsOnly" ), mShowUniqueRowsOnly );
827 : 0 : tableElem.setAttribute( QStringLiteral( "showOnlyVisibleFeatures" ), mShowOnlyVisibleFeatures );
828 : 0 : tableElem.setAttribute( QStringLiteral( "filterToAtlasIntersection" ), mFilterToAtlasIntersection );
829 : 0 : tableElem.setAttribute( QStringLiteral( "maxFeatures" ), mMaximumNumberOfFeatures );
830 : 0 : tableElem.setAttribute( QStringLiteral( "filterFeatures" ), mFilterFeatures ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
831 : 0 : tableElem.setAttribute( QStringLiteral( "featureFilter" ), mFeatureFilter );
832 : 0 : tableElem.setAttribute( QStringLiteral( "wrapString" ), mWrapString );
833 : 0 : tableElem.setAttribute( QStringLiteral( "useConditionalStyling" ), mUseConditionalStyling );
834 : :
835 : 0 : if ( mMap )
836 : : {
837 : 0 : tableElem.setAttribute( QStringLiteral( "mapUuid" ), mMap->uuid() );
838 : 0 : }
839 : :
840 : 0 : if ( mVectorLayer )
841 : : {
842 : 0 : tableElem.setAttribute( QStringLiteral( "vectorLayer" ), mVectorLayer.layerId );
843 : 0 : tableElem.setAttribute( QStringLiteral( "vectorLayerName" ), mVectorLayer.name );
844 : 0 : tableElem.setAttribute( QStringLiteral( "vectorLayerSource" ), mVectorLayer.source );
845 : 0 : tableElem.setAttribute( QStringLiteral( "vectorLayerProvider" ), mVectorLayer.provider );
846 : 0 : }
847 : 0 : return true;
848 : 0 : }
849 : :
850 : 0 : bool QgsLayoutItemAttributeTable::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context )
851 : : {
852 : 0 : if ( QgsVectorLayer *prevLayer = sourceLayer() )
853 : : {
854 : : //disconnect from previous layer
855 : 0 : disconnect( prevLayer, &QgsVectorLayer::layerModified, this, &QgsLayoutTable::refreshAttributes );
856 : 0 : }
857 : :
858 : 0 : if ( !QgsLayoutTable::readPropertiesFromElement( itemElem, doc, context ) )
859 : 0 : return false;
860 : :
861 : 0 : mSource = QgsLayoutItemAttributeTable::ContentSource( itemElem.attribute( QStringLiteral( "source" ), QStringLiteral( "0" ) ).toInt() );
862 : 0 : mRelationId = itemElem.attribute( QStringLiteral( "relationId" ), QString() );
863 : :
864 : 0 : if ( mSource == QgsLayoutItemAttributeTable::AtlasFeature )
865 : : {
866 : 0 : mCurrentAtlasLayer = mLayout->reportContext().layer();
867 : 0 : }
868 : :
869 : 0 : mShowUniqueRowsOnly = itemElem.attribute( QStringLiteral( "showUniqueRowsOnly" ), QStringLiteral( "0" ) ).toInt();
870 : 0 : mShowOnlyVisibleFeatures = itemElem.attribute( QStringLiteral( "showOnlyVisibleFeatures" ), QStringLiteral( "1" ) ).toInt();
871 : 0 : mFilterToAtlasIntersection = itemElem.attribute( QStringLiteral( "filterToAtlasIntersection" ), QStringLiteral( "0" ) ).toInt();
872 : 0 : mFilterFeatures = itemElem.attribute( QStringLiteral( "filterFeatures" ), QStringLiteral( "false" ) ) == QLatin1String( "true" );
873 : 0 : mFeatureFilter = itemElem.attribute( QStringLiteral( "featureFilter" ), QString() );
874 : 0 : mMaximumNumberOfFeatures = itemElem.attribute( QStringLiteral( "maxFeatures" ), QStringLiteral( "5" ) ).toInt();
875 : 0 : mWrapString = itemElem.attribute( QStringLiteral( "wrapString" ) );
876 : 0 : mUseConditionalStyling = itemElem.attribute( QStringLiteral( "useConditionalStyling" ), QStringLiteral( "0" ) ).toInt();
877 : :
878 : : //map
879 : 0 : mMapUuid = itemElem.attribute( QStringLiteral( "mapUuid" ) );
880 : 0 : if ( mMap )
881 : : {
882 : 0 : disconnect( mMap, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutTable::refreshAttributes );
883 : 0 : disconnect( mMap, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutTable::refreshAttributes );
884 : 0 : mMap = nullptr;
885 : 0 : }
886 : : // setting new mMap occurs in finalizeRestoreFromXml
887 : :
888 : : //vector layer
889 : 0 : QString layerId = itemElem.attribute( QStringLiteral( "vectorLayer" ) );
890 : 0 : QString layerName = itemElem.attribute( QStringLiteral( "vectorLayerName" ) );
891 : 0 : QString layerSource = itemElem.attribute( QStringLiteral( "vectorLayerSource" ) );
892 : 0 : QString layerProvider = itemElem.attribute( QStringLiteral( "vectorLayerProvider" ) );
893 : 0 : mVectorLayer = QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
894 : 0 : mVectorLayer.resolveWeakly( mLayout->project() );
895 : :
896 : : //connect to new layer
897 : 0 : if ( QgsVectorLayer *newLayer = sourceLayer() )
898 : 0 : connect( newLayer, &QgsVectorLayer::layerModified, this, &QgsLayoutTable::refreshAttributes );
899 : :
900 : 0 : refreshAttributes();
901 : :
902 : 0 : emit changed();
903 : 0 : return true;
904 : 0 : }
905 : :
906 : 0 : void QgsLayoutItemAttributeTable::setSource( const QgsLayoutItemAttributeTable::ContentSource source )
907 : : {
908 : 0 : if ( source == mSource )
909 : : {
910 : 0 : return;
911 : : }
912 : :
913 : 0 : QgsVectorLayer *prevLayer = sourceLayer();
914 : 0 : mSource = source;
915 : 0 : QgsVectorLayer *newLayer = sourceLayer();
916 : :
917 : 0 : if ( newLayer != prevLayer )
918 : : {
919 : : //disconnect from previous layer
920 : 0 : if ( prevLayer )
921 : : {
922 : 0 : disconnect( prevLayer, &QgsVectorLayer::layerModified, this, &QgsLayoutTable::refreshAttributes );
923 : 0 : }
924 : :
925 : : //connect to new layer
926 : 0 : connect( newLayer, &QgsVectorLayer::layerModified, this, &QgsLayoutTable::refreshAttributes );
927 : 0 : if ( mSource == QgsLayoutItemAttributeTable::AtlasFeature )
928 : : {
929 : 0 : mCurrentAtlasLayer = newLayer;
930 : 0 : }
931 : :
932 : : //layer has changed as a result of the source change, so reset column list
933 : 0 : resetColumns();
934 : 0 : }
935 : :
936 : 0 : refreshAttributes();
937 : 0 : emit changed();
938 : 0 : }
|