QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgscomposerattributetablev2.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  QgsComposerAttributeTableV2.cpp
3  -----------------------------
4  begin : September 2014
5  copyright : (C) 2014 by Marco Hugentobler
6  email : marco at hugis dot net
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 
19 #include "qgscomposertablecolumn.h"
20 #include "qgscomposermap.h"
21 #include "qgscomposerutils.h"
22 #include "qgsmaplayerregistry.h"
23 #include "qgsvectorlayer.h"
24 #include "qgscomposerframe.h"
25 #include "qgsatlascomposition.h"
26 #include "qgsproject.h"
27 #include "qgsrelationmanager.h"
28 #include "qgsgeometry.h"
29 
30 //QgsComposerAttributeTableCompareV2
31 
33  : mCurrentSortColumn( 0 ), mAscending( true )
34 {
35 }
36 
37 
39 {
40  QVariant v1 = m1[mCurrentSortColumn];
41  QVariant v2 = m2[mCurrentSortColumn];
42 
43  bool less = false;
44 
45  //sort null values first
46  if ( v1.isNull() && v2.isNull() )
47  {
48  less = false;
49  }
50  else if ( v1.isNull() )
51  {
52  less = true;
53  }
54  else if ( v2.isNull() )
55  {
56  less = false;
57  }
58  else
59  {
60  //otherwise sort by converting to corresponding type and comparing
61  switch ( v1.type() )
62  {
63  case QVariant::Int:
64  case QVariant::UInt:
65  case QVariant::LongLong:
66  case QVariant::ULongLong:
67  less = v1.toLongLong() < v2.toLongLong();
68  break;
69 
70  case QVariant::Double:
71  less = v1.toDouble() < v2.toDouble();
72  break;
73 
74  case QVariant::Date:
75  less = v1.toDate() < v2.toDate();
76  break;
77 
78  case QVariant::DateTime:
79  less = v1.toDateTime() < v2.toDateTime();
80  break;
81 
82  case QVariant::Time:
83  less = v1.toTime() < v2.toTime();
84  break;
85 
86  default:
87  //use locale aware compare for strings
88  less = v1.toString().localeAwareCompare( v2.toString() ) < 0;
89  break;
90  }
91  }
92 
93  return ( mAscending ? less : !less );
94 }
95 
96 //
97 // QgsComposerAttributeTableV2
98 //
99 
101  : QgsComposerTableV2( composition, createUndoCommands )
102  , mSource( LayerAttributes )
103  , mVectorLayer( 0 )
104  , mCurrentAtlasLayer( 0 )
105  , mComposerMap( 0 )
106  , mMaximumNumberOfFeatures( 30 )
107  , mShowUniqueRowsOnly( false )
108  , mShowOnlyVisibleFeatures( false )
109  , mFilterToAtlasIntersection( false )
110  , mFilterFeatures( false )
111  , mFeatureFilter( "" )
112 {
113  //set first vector layer from layer registry as default one
114  QMap<QString, QgsMapLayer*> layerMap = QgsMapLayerRegistry::instance()->mapLayers();
115  QMap<QString, QgsMapLayer*>::const_iterator mapIt = layerMap.constBegin();
116  for ( ; mapIt != layerMap.constEnd(); ++mapIt )
117  {
118  QgsVectorLayer* vl = dynamic_cast<QgsVectorLayer*>( mapIt.value() );
119  if ( vl )
120  {
121  mVectorLayer = vl;
122  break;
123  }
124  }
125  if ( mVectorLayer )
126  {
127  resetColumns();
128  //listen for modifications to layer and refresh table when they occur
129  connect( mVectorLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
130  }
131  connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWillBeRemoved( QString ) ), this, SLOT( removeLayer( const QString& ) ) );
132 
133  if ( mComposition )
134  {
135  //refresh table attributes when composition is refreshed
136  connect( mComposition, SIGNAL( refreshItemsTriggered() ), this, SLOT( refreshAttributes() ) );
137 
138  //connect to atlas feature changes to update table rows
139  connect( &mComposition->atlasComposition(), SIGNAL( featureChanged( QgsFeature* ) ), this, SLOT( refreshAttributes() ) );
140 
141  //atlas coverage layer change = regenerate columns
142  connect( &mComposition->atlasComposition(), SIGNAL( coverageLayerChanged( QgsVectorLayer* ) ), this, SLOT( atlasLayerChanged( QgsVectorLayer* ) ) );
143  }
145 }
146 
148 {
149 }
150 
152 {
153  return tr( "<attribute table>" );
154 }
155 
157 {
158  if ( layer == mVectorLayer )
159  {
160  //no change
161  return;
162  }
163 
164  QgsVectorLayer* prevLayer = sourceLayer();
165  mVectorLayer = layer;
166 
167  if ( mSource == QgsComposerAttributeTableV2::LayerAttributes && layer != prevLayer )
168  {
169  if ( prevLayer )
170  {
171  //disconnect from previous layer
172  disconnect( prevLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
173  }
174 
175  //rebuild column list to match all columns from layer
176  resetColumns();
177 
178  //listen for modifications to layer and refresh table when they occur
179  connect( mVectorLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
180  }
181 
183  emit changed();
184 }
185 
186 void QgsComposerAttributeTableV2::setRelationId( const QString relationId )
187 {
188  if ( relationId == mRelationId )
189  {
190  //no change
191  return;
192  }
193 
194  QgsVectorLayer* prevLayer = sourceLayer();
195  mRelationId = relationId;
196  QgsRelation relation = QgsProject::instance()->relationManager()->relation( mRelationId );
197  QgsVectorLayer* newLayer = relation.referencingLayer();
198 
199  if ( mSource == QgsComposerAttributeTableV2::RelationChildren && newLayer != prevLayer )
200  {
201  if ( prevLayer )
202  {
203  //disconnect from previous layer
204  disconnect( prevLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
205  }
206 
207  //rebuild column list to match all columns from layer
208  resetColumns();
209 
210  //listen for modifications to layer and refresh table when they occur
211  connect( newLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
212  }
213 
215  emit changed();
216 }
217 
218 void QgsComposerAttributeTableV2::atlasLayerChanged( QgsVectorLayer *layer )
219 {
220  if ( mSource != QgsComposerAttributeTableV2::AtlasFeature || layer == mCurrentAtlasLayer )
221  {
222  //nothing to do
223  return;
224  }
225 
226  //atlas feature mode, atlas layer changed, so we need to reset columns
227  if ( mCurrentAtlasLayer )
228  {
229  //disconnect from previous layer
230  disconnect( mCurrentAtlasLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
231  }
232 
233  mCurrentAtlasLayer = layer;
234 
235  //rebuild column list to match all columns from layer
236  resetColumns();
238 
239  //listen for modifications to layer and refresh table when they occur
240  connect( layer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
241 }
242 
244 {
246  if ( !source )
247  {
248  return;
249  }
250 
251  //remove existing columns
252  qDeleteAll( mColumns );
253  mColumns.clear();
254 
255  //rebuild columns list from vector layer fields
256  const QgsFields& fields = source->pendingFields();
257  for ( int idx = 0; idx < fields.count(); ++idx )
258  {
259  QString currentAlias = source->attributeDisplayName( idx );
261  col->setAttribute( fields[idx].name() );
262  col->setHeading( currentAlias );
263  mColumns.append( col );
264  }
265 }
266 
268 {
269  if ( map == mComposerMap )
270  {
271  //no change
272  return;
273  }
274 
275  if ( mComposerMap )
276  {
277  //disconnect from previous map
278  disconnect( mComposerMap, SIGNAL( extentChanged() ), this, SLOT( refreshAttributes() ) );
279  }
280  mComposerMap = map;
281  if ( mComposerMap )
282  {
283  //listen out for extent changes in linked map
284  connect( mComposerMap, SIGNAL( extentChanged() ), this, SLOT( refreshAttributes() ) );
285  }
287  emit changed();
288 }
289 
291 {
292  if ( features == mMaximumNumberOfFeatures )
293  {
294  return;
295  }
296 
297  mMaximumNumberOfFeatures = features;
299  emit changed();
300 }
301 
303 {
304  if ( uniqueOnly == mShowUniqueRowsOnly )
305  {
306  return;
307  }
308 
309  mShowUniqueRowsOnly = uniqueOnly;
311  emit changed();
312 }
313 
315 {
316  if ( visibleOnly == mShowOnlyVisibleFeatures )
317  {
318  return;
319  }
320 
321  mShowOnlyVisibleFeatures = visibleOnly;
323  emit changed();
324 }
325 
327 {
328  if ( filterToAtlas == mFilterToAtlasIntersection )
329  {
330  return;
331  }
332 
333  mFilterToAtlasIntersection = filterToAtlas;
335  emit changed();
336 }
337 
339 {
340  if ( filter == mFilterFeatures )
341  {
342  return;
343  }
344 
345  mFilterFeatures = filter;
347  emit changed();
348 }
349 
350 void QgsComposerAttributeTableV2::setFeatureFilter( const QString& expression )
351 {
352  if ( expression == mFeatureFilter )
353  {
354  return;
355  }
356 
357  mFeatureFilter = expression;
359  emit changed();
360 }
361 
362 void QgsComposerAttributeTableV2::setDisplayAttributes( const QSet<int>& attr, bool refresh )
363 {
365  if ( !source )
366  {
367  return;
368  }
369 
370  //rebuild columns list, taking only attributes with index in supplied QSet
371  qDeleteAll( mColumns );
372  mColumns.clear();
373 
374  const QgsFields& fields = source->pendingFields();
375 
376  if ( !attr.empty() )
377  {
378  QSet<int>::const_iterator attIt = attr.constBegin();
379  for ( ; attIt != attr.constEnd(); ++attIt )
380  {
381  int attrIdx = ( *attIt );
382  if ( !fields.exists( attrIdx ) )
383  {
384  continue;
385  }
386  QString currentAlias = source->attributeDisplayName( attrIdx );
388  col->setAttribute( fields[attrIdx].name() );
389  col->setHeading( currentAlias );
390  mColumns.append( col );
391  }
392  }
393  else
394  {
395  //resetting, so add all attributes to columns
396  for ( int idx = 0; idx < fields.count(); ++idx )
397  {
398  QString currentAlias = source->attributeDisplayName( idx );
400  col->setAttribute( fields[idx].name() );
401  col->setHeading( currentAlias );
402  mColumns.append( col );
403  }
404  }
405 
406  if ( refresh )
407  {
409  }
410 }
411 
412 void QgsComposerAttributeTableV2::restoreFieldAliasMap( const QMap<int, QString>& map )
413 {
415  if ( !source )
416  {
417  return;
418  }
419 
420  QList<QgsComposerTableColumn*>::const_iterator columnIt = mColumns.constBegin();
421  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
422  {
423  int attrIdx = source->fieldNameIndex(( *columnIt )->attribute() );
424  if ( map.contains( attrIdx ) )
425  {
426  ( *columnIt )->setHeading( map.value( attrIdx ) );
427  }
428  else
429  {
430  ( *columnIt )->setHeading( source->attributeDisplayName( attrIdx ) );
431  }
432  }
433 }
434 
436 {
437  contents.clear();
438 
441  {
442  //source mode requires atlas, but atlas disabled
443  return false;
444  }
445 
446  QgsVectorLayer* layer = sourceLayer();
447 
448  if ( !layer )
449  {
450  //no source layer
451  return false;
452  }
453 
454  //prepare filter expression
455  QScopedPointer<QgsExpression> filterExpression;
456  bool activeFilter = false;
457  if ( mFilterFeatures && !mFeatureFilter.isEmpty() )
458  {
459  filterExpression.reset( new QgsExpression( mFeatureFilter ) );
460  if ( !filterExpression->hasParserError() )
461  {
462  activeFilter = true;
463  }
464  }
465 
466  QgsRectangle selectionRect;
467  if ( mComposerMap && mShowOnlyVisibleFeatures )
468  {
469  selectionRect = *mComposerMap->currentMapExtent();
470  if ( layer && mComposition->mapSettings().hasCrsTransformEnabled() )
471  {
472  //transform back to layer CRS
473  QgsCoordinateTransform coordTransform( layer->crs(), mComposition->mapSettings().destinationCrs() );
474  try
475  {
476  selectionRect = coordTransform.transformBoundingBox( selectionRect, QgsCoordinateTransform::ReverseTransform );
477  }
478  catch ( QgsCsException &cse )
479  {
480  Q_UNUSED( cse );
481  return false;
482  }
483  }
484  }
485 
486  QgsFeatureRequest req;
487 
489  {
490  QgsRelation relation = QgsProject::instance()->relationManager()->relation( mRelationId );
492  if ( atlasFeature )
493  {
494  req = relation.getRelatedFeaturesRequest( *atlasFeature );
495  }
496  else
497  {
498  //no atlas feature, so empty table
499  return true;
500  }
501  }
502 
503  if ( !selectionRect.isEmpty() )
504  req.setFilterRect( selectionRect );
505 
506  req.setFlags( mShowOnlyVisibleFeatures ? QgsFeatureRequest::ExactIntersect : QgsFeatureRequest::NoFlags );
507 
510  {
511  //source mode is current atlas feature
513  if ( atlasFeature )
514  {
515  req.setFilterFid( atlasFeature->id() );
516  }
517  else
518  {
519  //no atlas feature, so empty table
520  return true;
521  }
522  }
523 
524  QgsFeature f;
525  int counter = 0;
526  QgsFeatureIterator fit = layer->getFeatures( req );
527 
528  while ( fit.nextFeature( f ) && counter < mMaximumNumberOfFeatures )
529  {
530  //check feature against filter
531  if ( activeFilter && !filterExpression.isNull() )
532  {
533  QVariant result = filterExpression->evaluate( &f, layer->pendingFields() );
534  // skip this feature if the filter evaluation is false
535  if ( !result.toBool() )
536  {
537  continue;
538  }
539  }
540  //check against atlas feature intersection
541  if ( mFilterToAtlasIntersection )
542  {
543  if ( !f.geometry() || ! mComposition->atlasComposition().enabled() )
544  {
545  continue;
546  }
548  if ( !atlasFeature || !atlasFeature->geometry() ||
549  !f.geometry()->intersects( atlasFeature->geometry() ) )
550  {
551  //feature falls outside current atlas feature
552  continue;
553  }
554  }
555 
556  QgsComposerTableRow currentRow;
557 
558  QList<QgsComposerTableColumn*>::const_iterator columnIt = mColumns.constBegin();
559  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
560  {
561  int idx = layer->fieldNameIndex(( *columnIt )->attribute() );
562  if ( idx != -1 )
563  {
564  currentRow << f.attributes()[idx];
565  }
566  else
567  {
568  // Lets assume it's an expression
569  QgsExpression* expression = new QgsExpression(( *columnIt )->attribute() );
570  expression->setCurrentRowNumber( counter + 1 );
571  expression->prepare( layer->pendingFields() );
572  QVariant value = expression->evaluate( f );
573  currentRow << value;
574  }
575  }
576 
577  if ( !mShowUniqueRowsOnly || !contentsContainsRow( contents, currentRow ) )
578  {
579  contents << currentRow;
580  ++counter;
581  }
582  }
583 
584  //sort the list, starting with the last attribute
586  QList< QPair<int, bool> > sortColumns = sortAttributes();
587  for ( int i = sortColumns.size() - 1; i >= 0; --i )
588  {
589  c.setSortColumn( sortColumns.at( i ).first );
590  c.setAscending( sortColumns.at( i ).second );
591  qStableSort( contents.begin(), contents.end(), c );
592  }
593 
595  return true;
596 }
597 
599 {
600  switch ( mSource )
601  {
605  return mVectorLayer;
607  {
608  QgsRelation relation = QgsProject::instance()->relationManager()->relation( mRelationId );
609  return relation.referencingLayer();
610  }
611  }
612  return 0;
613 }
614 
615 void QgsComposerAttributeTableV2::removeLayer( QString layerId )
616 {
617  if ( mVectorLayer && mSource == QgsComposerAttributeTableV2::LayerAttributes )
618  {
619  if ( layerId == mVectorLayer->id() )
620  {
621  mVectorLayer = 0;
622  //remove existing columns
623  qDeleteAll( mColumns );
624  mColumns.clear();
625  }
626  }
627 }
628 
629 static bool columnsBySortRank( QPair<int, QgsComposerTableColumn* > a, QPair<int, QgsComposerTableColumn* > b )
630 {
631  return a.second->sortByRank() < b.second->sortByRank();
632 }
633 
634 QList<QPair<int, bool> > QgsComposerAttributeTableV2::sortAttributes() const
635 {
636  //generate list of all sorted columns
637  QList< QPair<int, QgsComposerTableColumn* > > sortedColumns;
638  QList<QgsComposerTableColumn*>::const_iterator columnIt = mColumns.constBegin();
639  int idx = 0;
640  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
641  {
642  if (( *columnIt )->sortByRank() > 0 )
643  {
644  sortedColumns.append( qMakePair( idx, *columnIt ) );
645  }
646  idx++;
647  }
648 
649  //sort columns by rank
650  qSort( sortedColumns.begin(), sortedColumns.end(), columnsBySortRank );
651 
652  //generate list of column index, bool for sort direction (to match 2.0 api)
653  QList<QPair<int, bool> > attributesBySortRank;
654  QList< QPair<int, QgsComposerTableColumn* > >::const_iterator sortedColumnIt = sortedColumns.constBegin();
655  for ( ; sortedColumnIt != sortedColumns.constEnd(); ++sortedColumnIt )
656  {
657 
658  attributesBySortRank.append( qMakePair(( *sortedColumnIt ).first,
659  ( *sortedColumnIt ).second->sortOrder() == Qt::AscendingOrder ) );
660  }
661  return attributesBySortRank;
662 }
663 
664 bool QgsComposerAttributeTableV2::writeXML( QDomElement& elem, QDomDocument & doc, bool ignoreFrames ) const
665 {
666  QDomElement composerTableElem = doc.createElement( "ComposerAttributeTableV2" );
667  composerTableElem.setAttribute( "source", QString::number(( int )mSource ) );
668  composerTableElem.setAttribute( "relationId", mRelationId );
669  composerTableElem.setAttribute( "showUniqueRowsOnly", mShowUniqueRowsOnly );
670  composerTableElem.setAttribute( "showOnlyVisibleFeatures", mShowOnlyVisibleFeatures );
671  composerTableElem.setAttribute( "filterToAtlasIntersection", mFilterToAtlasIntersection );
672  composerTableElem.setAttribute( "maxFeatures", mMaximumNumberOfFeatures );
673  composerTableElem.setAttribute( "filterFeatures", mFilterFeatures ? "true" : "false" );
674  composerTableElem.setAttribute( "featureFilter", mFeatureFilter );
675 
676  if ( mComposerMap )
677  {
678  composerTableElem.setAttribute( "composerMap", mComposerMap->id() );
679  }
680  else
681  {
682  composerTableElem.setAttribute( "composerMap", -1 );
683  }
684  if ( mVectorLayer )
685  {
686  composerTableElem.setAttribute( "vectorLayer", mVectorLayer->id() );
687  }
688 
689  bool ok = QgsComposerTableV2::writeXML( composerTableElem, doc, ignoreFrames );
690 
691  elem.appendChild( composerTableElem );
692 
693  return ok;
694 }
695 
696 bool QgsComposerAttributeTableV2::readXML( const QDomElement& itemElem, const QDomDocument& doc, bool ignoreFrames )
697 {
698  if ( itemElem.isNull() )
699  {
700  return false;
701  }
702 
703  //read general table properties
704  if ( !QgsComposerTableV2::readXML( itemElem, doc, ignoreFrames ) )
705  {
706  return false;
707  }
708 
709  QgsVectorLayer* prevLayer = sourceLayer();
710  if ( prevLayer )
711  {
712  //disconnect from previous layer
713  disconnect( prevLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
714  }
715 
716  mSource = QgsComposerAttributeTableV2::ContentSource( itemElem.attribute( "source", "0" ).toInt() );
717  mRelationId = itemElem.attribute( "relationId", "" );
718 
720  {
721  mCurrentAtlasLayer = mComposition->atlasComposition().coverageLayer();
722  }
723 
724  mShowUniqueRowsOnly = itemElem.attribute( "showUniqueRowsOnly", "0" ).toInt();
725  mShowOnlyVisibleFeatures = itemElem.attribute( "showOnlyVisibleFeatures", "1" ).toInt();
726  mFilterToAtlasIntersection = itemElem.attribute( "filterToAtlasIntersection", "0" ).toInt();
727  mFilterFeatures = itemElem.attribute( "filterFeatures", "false" ) == "true" ? true : false;
728  mFeatureFilter = itemElem.attribute( "featureFilter", "" );
729  mMaximumNumberOfFeatures = itemElem.attribute( "maxFeatures", "5" ).toInt();
730 
731  //composer map
732  int composerMapId = itemElem.attribute( "composerMap", "-1" ).toInt();
733  if ( composerMapId == -1 )
734  {
735  mComposerMap = 0;
736  }
737 
738  if ( composition() )
739  {
740  mComposerMap = composition()->getComposerMapById( composerMapId );
741  }
742  else
743  {
744  mComposerMap = 0;
745  }
746 
747  if ( mComposerMap )
748  {
749  //if we have found a valid map item, listen out to extent changes on it and refresh the table
750  connect( mComposerMap, SIGNAL( extentChanged() ), this, SLOT( refreshAttributes() ) );
751  }
752 
753  //vector layer
754  QString layerId = itemElem.attribute( "vectorLayer", "not_existing" );
755  if ( layerId == "not_existing" )
756  {
757  mVectorLayer = 0;
758  }
759  else
760  {
762  if ( ml )
763  {
764  mVectorLayer = dynamic_cast<QgsVectorLayer*>( ml );
765  }
766  }
767 
768  //connect to new layer
769  connect( sourceLayer(), SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
770 
772 
773  emit changed();
774  return true;
775 }
776 
777 void QgsComposerAttributeTableV2::addFrame( QgsComposerFrame *frame, bool recalcFrameSizes )
778 {
779  mFrameItems.push_back( frame );
780  connect( frame, SIGNAL( sizeChanged() ), this, SLOT( recalculateFrameSizes() ) );
781  if ( mComposition )
782  {
783  mComposition->addComposerTableFrame( this, frame );
784  }
785 
786  if ( recalcFrameSizes )
787  {
789  }
790 }
791 
793 {
794  if ( source == mSource )
795  {
796  return;
797  }
798 
799  QgsVectorLayer* prevLayer = sourceLayer();
800  mSource = source;
801  QgsVectorLayer* newLayer = sourceLayer();
802 
803  if ( newLayer != prevLayer )
804  {
805  //disconnect from previous layer
806  if ( prevLayer )
807  {
808  disconnect( prevLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
809  }
810 
811  //connect to new layer
812  connect( newLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
814  {
815  mCurrentAtlasLayer = newLayer;
816  }
817 
818  //layer has changed as a result of the source change, so reset column list
819  resetColumns();
820  }
821 
823  emit changed();
824 }