QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgscomposerattributetable.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscomposerattributetable.cpp
3  -----------------------------
4  begin : April 2010
5  copyright : (C) 2010 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 
25 //QgsComposerAttributeTableCompare
26 
28  : mCurrentSortColumn( 0 ), mAscending( true )
29 {
30 }
31 
32 
34 {
35  QVariant v1 = m1[mCurrentSortColumn];
36  QVariant v2 = m2[mCurrentSortColumn];
37 
38  bool less = false;
39 
40  //sort null values first
41  if ( v1.isNull() && v2.isNull() )
42  {
43  less = false;
44  }
45  else if ( v1.isNull() )
46  {
47  less = true;
48  }
49  else if ( v2.isNull() )
50  {
51  less = false;
52  }
53  else
54  {
55  //otherwise sort by converting to corresponding type and comparing
56  switch ( v1.type() )
57  {
58  case QVariant::Int:
59  case QVariant::UInt:
60  case QVariant::LongLong:
61  case QVariant::ULongLong:
62  less = v1.toLongLong() < v2.toLongLong();
63  break;
64 
65  case QVariant::Double:
66  less = v1.toDouble() < v2.toDouble();
67  break;
68 
69  case QVariant::Date:
70  less = v1.toDate() < v2.toDate();
71  break;
72 
73  case QVariant::DateTime:
74  less = v1.toDateTime() < v2.toDateTime();
75  break;
76 
77  case QVariant::Time:
78  less = v1.toTime() < v2.toTime();
79  break;
80 
81  default:
82  //use locale aware compare for strings
83  less = v1.toString().localeAwareCompare( v2.toString() ) < 0;
84  break;
85  }
86  }
87 
88  return ( mAscending ? less : !less );
89 }
90 
91 
92 //QgsComposerAttributeTable
93 
95  : QgsComposerTable( composition )
96  , mVectorLayer( 0 )
97  , mComposerMap( 0 )
98  , mMaximumNumberOfFeatures( 5 )
99  , mShowOnlyVisibleFeatures( false )
100  , mFilterFeatures( false )
101  , mFeatureFilter( "" )
102 {
103  //set first vector layer from layer registry as default one
104  QMap<QString, QgsMapLayer*> layerMap = QgsMapLayerRegistry::instance()->mapLayers();
105  QMap<QString, QgsMapLayer*>::const_iterator mapIt = layerMap.constBegin();
106  for ( ; mapIt != layerMap.constEnd(); ++mapIt )
107  {
108  QgsVectorLayer* vl = dynamic_cast<QgsVectorLayer*>( mapIt.value() );
109  if ( vl )
110  {
111  mVectorLayer = vl;
112  break;
113  }
114  }
115  if ( mVectorLayer )
116  {
117  resetColumns();
118  }
119  connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWillBeRemoved( QString ) ), this, SLOT( removeLayer( const QString& ) ) );
120 
121  if ( mComposition )
122  {
123  //refresh table attributes when composition is refreshed
124  connect( mComposition, SIGNAL( refreshItemsTriggered() ), this, SLOT( refreshAttributes() ) );
125 
126  //connect to atlas feature changes to update table rows
127  connect( &mComposition->atlasComposition(), SIGNAL( featureChanged( QgsFeature* ) ), this, SLOT( refreshAttributes() ) );
128  }
129 }
130 
132 {
133 }
134 
135 void QgsComposerAttributeTable::paint( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle, QWidget* pWidget )
136 {
137  if ( mComposerMap && mComposerMap->isDrawing() )
138  {
139  return;
140  }
141  if ( !shouldDrawItem() )
142  {
143  return;
144  }
145  QgsComposerTable::paint( painter, itemStyle, pWidget );
146 }
147 
149 {
150  if ( layer == mVectorLayer )
151  {
152  //no change
153  return;
154  }
155 
156  if ( mVectorLayer )
157  {
158  //disconnect from previous layer
159  QObject::disconnect( mVectorLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
160  }
161 
162  mVectorLayer = layer;
163 
164  //rebuild column list to match all columns from layer
165  resetColumns();
167 
168  //listen for modifications to layer and refresh table when they occur
169  QObject::connect( mVectorLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
170 }
171 
173 {
174  if ( !mVectorLayer )
175  {
176  return;
177  }
178 
179  //remove existing columns
180  qDeleteAll( mColumns );
181  mColumns.clear();
182 
183  //rebuild columns list from vector layer fields
184  const QgsFields& fields = mVectorLayer->pendingFields();
185  for ( int idx = 0; idx < fields.count(); ++idx )
186  {
187  QString currentAlias = mVectorLayer->attributeDisplayName( idx );
189  col->setAttribute( fields[idx].name() );
190  col->setHeading( currentAlias );
191  mColumns.append( col );
192  }
193 }
194 
196 {
197  if ( map == mComposerMap )
198  {
199  //no change
200  return;
201  }
202 
203  if ( mComposerMap )
204  {
205  //disconnect from previous map
206  QObject::disconnect( mComposerMap, SIGNAL( extentChanged() ), this, SLOT( refreshAttributes() ) );
207  }
208  mComposerMap = map;
209  if ( mComposerMap )
210  {
211  //listen out for extent changes in linked map
212  QObject::connect( mComposerMap, SIGNAL( extentChanged() ), this, SLOT( refreshAttributes() ) );
213  }
215 }
216 
218 {
219  if ( features == mMaximumNumberOfFeatures )
220  {
221  return;
222  }
223 
224  mMaximumNumberOfFeatures = features;
226 }
227 
229 {
230  if ( visibleOnly == mShowOnlyVisibleFeatures )
231  {
232  return;
233  }
234 
235  mShowOnlyVisibleFeatures = visibleOnly;
237 }
238 
240 {
241  if ( filter == mFilterFeatures )
242  {
243  return;
244  }
245 
246  mFilterFeatures = filter;
248 }
249 
250 void QgsComposerAttributeTable::setFeatureFilter( const QString& expression )
251 {
252  if ( expression == mFeatureFilter )
253  {
254  return;
255  }
256 
257  mFeatureFilter = expression;
259 }
260 
262 {
263  return fieldsToDisplay().toSet();
264 }
265 
266 void QgsComposerAttributeTable::setDisplayAttributes( const QSet<int>& attr, bool refresh )
267 {
268  if ( !mVectorLayer )
269  {
270  return;
271  }
272 
273  //rebuild columns list, taking only attributes with index in supplied QSet
274  qDeleteAll( mColumns );
275  mColumns.clear();
276 
277  const QgsFields& fields = mVectorLayer->pendingFields();
278 
279  if ( !attr.empty() )
280  {
281  QSet<int>::const_iterator attIt = attr.constBegin();
282  for ( ; attIt != attr.constEnd(); ++attIt )
283  {
284  int attrIdx = ( *attIt );
285  if ( !fields.exists( attrIdx ) )
286  {
287  continue;
288  }
289  QString currentAlias = mVectorLayer->attributeDisplayName( attrIdx );
291  col->setAttribute( fields[attrIdx].name() );
292  col->setHeading( currentAlias );
293  mColumns.append( col );
294  }
295  }
296  else
297  {
298  //resetting, so add all attributes to columns
299  for ( int idx = 0; idx < fields.count(); ++idx )
300  {
301  QString currentAlias = mVectorLayer->attributeDisplayName( idx );
303  col->setAttribute( fields[idx].name() );
304  col->setHeading( currentAlias );
305  mColumns.append( col );
306  }
307  }
308 
309  if ( refresh )
310  {
312  }
313 }
314 
316 {
317  QMap<int, QString> fieldAliasMap;
318 
319  QList<QgsComposerTableColumn*>::const_iterator columnIt = mColumns.constBegin();
320  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
321  {
322  int attrIdx = mVectorLayer->fieldNameIndex(( *columnIt )->attribute() );
323  fieldAliasMap.insert( attrIdx, ( *columnIt )->heading() );
324  }
325  return fieldAliasMap;
326 }
327 
328 void QgsComposerAttributeTable::restoreFieldAliasMap( const QMap<int, QString>& map )
329 {
330  if ( !mVectorLayer )
331  {
332  return;
333  }
334 
335  QList<QgsComposerTableColumn*>::const_iterator columnIt = mColumns.constBegin();
336  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
337  {
338  int attrIdx = mVectorLayer->fieldNameIndex(( *columnIt )->attribute() );
339  if ( map.contains( attrIdx ) )
340  {
341  ( *columnIt )->setHeading( map.value( attrIdx ) );
342  }
343  else
344  {
345  ( *columnIt )->setHeading( mVectorLayer->attributeDisplayName( attrIdx ) );
346  }
347  }
348 }
349 
350 
351 void QgsComposerAttributeTable::setFieldAliasMap( const QMap<int, QString>& map )
352 {
353  restoreFieldAliasMap( map );
355 }
356 
357 QList<int> QgsComposerAttributeTable::fieldsToDisplay() const
358 {
359  //kept for api compatibility with 2.0 only, can be removed after next api break
360  QList<int> fields;
361 
362  QList<QgsComposerTableColumn*>::const_iterator columnIt = mColumns.constBegin();
363  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
364  {
365  int idx = mVectorLayer->fieldNameIndex(( *columnIt )->attribute() );
366  fields.append( idx );
367  }
368  return fields;
369 }
370 
371 bool QgsComposerAttributeTable::getFeatureAttributes( QList<QgsAttributeMap> &attributeMaps )
372 {
373  if ( !mVectorLayer )
374  {
375  return false;
376  }
377 
378  attributeMaps.clear();
379 
380  //prepare filter expression
381  QScopedPointer<QgsExpression> filterExpression;
382  bool activeFilter = false;
383  if ( mFilterFeatures && !mFeatureFilter.isEmpty() )
384  {
385  filterExpression.reset( new QgsExpression( mFeatureFilter ) );
386  if ( !filterExpression->hasParserError() )
387  {
388  activeFilter = true;
389  }
390  }
391 
392  QgsRectangle selectionRect;
393  if ( mComposerMap && mShowOnlyVisibleFeatures )
394  {
395  selectionRect = *mComposerMap->currentMapExtent();
396  if ( mVectorLayer && mComposition->mapSettings().hasCrsTransformEnabled() )
397  {
398  //transform back to layer CRS
399  QgsCoordinateTransform coordTransform( mVectorLayer->crs(), mComposition->mapSettings().destinationCrs() );
400  try
401  {
402  selectionRect = coordTransform.transformBoundingBox( selectionRect, QgsCoordinateTransform::ReverseTransform );
403  }
404  catch ( QgsCsException &cse )
405  {
406  Q_UNUSED( cse );
407  return false;
408  }
409  }
410  }
411 
412  QgsFeatureRequest req;
413  if ( !selectionRect.isEmpty() )
414  req.setFilterRect( selectionRect );
415 
416  req.setFlags( mShowOnlyVisibleFeatures ? QgsFeatureRequest::ExactIntersect : QgsFeatureRequest::NoFlags );
417 
418  QgsFeature f;
419  int counter = 0;
420  QgsFeatureIterator fit = mVectorLayer->getFeatures( req );
421 
422  while ( fit.nextFeature( f ) && counter < mMaximumNumberOfFeatures )
423  {
424  //check feature against filter
425  if ( activeFilter && !filterExpression.isNull() )
426  {
427  QVariant result = filterExpression->evaluate( &f, mVectorLayer->pendingFields() );
428  // skip this feature if the filter evaluation is false
429  if ( !result.toBool() )
430  {
431  continue;
432  }
433  }
434 
435  attributeMaps.push_back( QgsAttributeMap() );
436 
437  QList<QgsComposerTableColumn*>::const_iterator columnIt = mColumns.constBegin();
438  int i = 0;
439  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
440  {
441  int idx = mVectorLayer->fieldNameIndex(( *columnIt )->attribute() );
442  if ( idx != -1 )
443  {
444  attributeMaps.last().insert( i, f.attributes()[idx] );
445  }
446  else
447  {
448  // Lets assume it's an expression
449  QgsExpression* expression = new QgsExpression(( *columnIt )->attribute() );
450  expression->setCurrentRowNumber( counter + 1 );
451  expression->prepare( mVectorLayer->pendingFields() );
452  QVariant value = expression->evaluate( f );
453  attributeMaps.last().insert( i, value.toString() );
454  }
455 
456  i++;
457  }
458  ++counter;
459  }
460 
461  //sort the list, starting with the last attribute
463  QList< QPair<int, bool> > sortColumns = sortAttributes();
464  for ( int i = sortColumns.size() - 1; i >= 0; --i )
465  {
466  c.setSortColumn( sortColumns.at( i ).first );
467  c.setAscending( sortColumns.at( i ).second );
468  qStableSort( attributeMaps.begin(), attributeMaps.end(), c );
469  }
470 
472  return true;
473 }
474 
475 void QgsComposerAttributeTable::removeLayer( QString layerId )
476 {
477  if ( mVectorLayer )
478  {
479  if ( layerId == mVectorLayer->id() )
480  {
481  mVectorLayer = 0;
482  //remove existing columns
483  qDeleteAll( mColumns );
484  mColumns.clear();
485  }
486  }
487 }
488 
489 void QgsComposerAttributeTable::setSceneRect( const QRectF& rectangle )
490 {
491  //update rect for data defined size and position
492  QRectF evaluatedRect = evalItemRect( rectangle );
493 
494  QgsComposerItem::setSceneRect( evaluatedRect );
495 
496  //refresh table attributes, since number of features has likely changed
498 }
499 
500 void QgsComposerAttributeTable::setSortAttributes( const QList<QPair<int, bool> > att )
501 {
502  //first, clear existing sort by ranks
503  QList<QgsComposerTableColumn*>::iterator columnIt = mColumns.begin();
504  for ( ; columnIt != mColumns.end(); ++columnIt )
505  {
506  ( *columnIt )->setSortByRank( 0 );
507  }
508 
509  //now, update sort rank of specified columns
510  QList< QPair<int, bool > >::const_iterator sortedColumnIt = att.constBegin();
511  int rank = 1;
512  for ( ; sortedColumnIt != att.constEnd(); ++sortedColumnIt )
513  {
514  if (( *sortedColumnIt ).first >= mColumns.length() )
515  {
516  continue;
517  }
518  mColumns[( *sortedColumnIt ).first ]->setSortByRank( rank );
519  mColumns[( *sortedColumnIt ).first ]->setSortOrder(( *sortedColumnIt ).second ? Qt::AscendingOrder : Qt::DescendingOrder );
520  rank++;
521  }
522 
524 }
525 
526 static bool columnsBySortRank( QPair<int, QgsComposerTableColumn* > a, QPair<int, QgsComposerTableColumn* > b )
527 {
528  return a.second->sortByRank() < b.second->sortByRank();
529 }
530 
531 QList<QPair<int, bool> > QgsComposerAttributeTable::sortAttributes() const
532 {
533  //generate list of all sorted columns
534  QList< QPair<int, QgsComposerTableColumn* > > sortedColumns;
535  QList<QgsComposerTableColumn*>::const_iterator columnIt = mColumns.constBegin();
536  int idx = 0;
537  for ( ; columnIt != mColumns.constEnd(); ++columnIt )
538  {
539  if (( *columnIt )->sortByRank() > 0 )
540  {
541  sortedColumns.append( qMakePair( idx, *columnIt ) );
542  }
543  idx++;
544  }
545 
546  //sort columns by rank
547  qSort( sortedColumns.begin(), sortedColumns.end(), columnsBySortRank );
548 
549  //generate list of column index, bool for sort direction (to match 2.0 api)
550  QList<QPair<int, bool> > attributesBySortRank;
551  QList< QPair<int, QgsComposerTableColumn* > >::const_iterator sortedColumnIt = sortedColumns.constBegin();
552  for ( ; sortedColumnIt != sortedColumns.constEnd(); ++sortedColumnIt )
553  {
554 
555  attributesBySortRank.append( qMakePair(( *sortedColumnIt ).first,
556  ( *sortedColumnIt ).second->sortOrder() == Qt::AscendingOrder ) );
557  }
558  return attributesBySortRank;
559 }
560 
561 bool QgsComposerAttributeTable::writeXML( QDomElement& elem, QDomDocument & doc ) const
562 {
563  QDomElement composerTableElem = doc.createElement( "ComposerAttributeTable" );
564  composerTableElem.setAttribute( "showOnlyVisibleFeatures", mShowOnlyVisibleFeatures );
565  composerTableElem.setAttribute( "maxFeatures", mMaximumNumberOfFeatures );
566  composerTableElem.setAttribute( "filterFeatures", mFilterFeatures ? "true" : "false" );
567  composerTableElem.setAttribute( "featureFilter", mFeatureFilter );
568 
569  if ( mComposerMap )
570  {
571  composerTableElem.setAttribute( "composerMap", mComposerMap->id() );
572  }
573  else
574  {
575  composerTableElem.setAttribute( "composerMap", -1 );
576  }
577  if ( mVectorLayer )
578  {
579  composerTableElem.setAttribute( "vectorLayer", mVectorLayer->id() );
580  }
581 
582  elem.appendChild( composerTableElem );
583  bool ok = tableWriteXML( composerTableElem, doc );
584  return ok;
585 }
586 
587 bool QgsComposerAttributeTable::readXML( const QDomElement& itemElem, const QDomDocument& doc )
588 {
589  if ( itemElem.isNull() )
590  {
591  return false;
592  }
593 
594  //read general table properties
595  if ( !tableReadXML( itemElem, doc ) )
596  {
597  return false;
598  }
599 
600  mShowOnlyVisibleFeatures = itemElem.attribute( "showOnlyVisibleFeatures", "1" ).toInt();
601  mFilterFeatures = itemElem.attribute( "filterFeatures", "false" ) == "true" ? true : false;
602  mFeatureFilter = itemElem.attribute( "featureFilter", "" );
603 
604  //composer map
605  int composerMapId = itemElem.attribute( "composerMap", "-1" ).toInt();
606  if ( composerMapId == -1 )
607  {
608  mComposerMap = 0;
609  }
610 
611  if ( composition() )
612  {
613  mComposerMap = composition()->getComposerMapById( composerMapId );
614  }
615  else
616  {
617  mComposerMap = 0;
618  }
619 
620  if ( mComposerMap )
621  {
622  //if we have found a valid map item, listen out to extent changes on it and refresh the table
623  QObject::connect( mComposerMap, SIGNAL( extentChanged() ), this, SLOT( refreshAttributes() ) );
624  }
625 
626  //vector layer
627  QString layerId = itemElem.attribute( "vectorLayer", "not_existing" );
628  if ( layerId == "not_existing" )
629  {
630  mVectorLayer = 0;
631  }
632  else
633  {
635  if ( ml )
636  {
637  mVectorLayer = dynamic_cast<QgsVectorLayer*>( ml );
638  if ( mVectorLayer )
639  {
640  //if we have found a valid vector layer, listen for modifications on it and refresh the table
641  QObject::connect( mVectorLayer, SIGNAL( layerModified() ), this, SLOT( refreshAttributes() ) );
642  }
643  }
644  }
645 
646  //restore display attribute map. This is required to upgrade pre 2.4 projects.
647  QSet<int> displayAttributes;
648  QDomNodeList displayAttributeList = itemElem.elementsByTagName( "displayAttributes" );
649  if ( displayAttributeList.size() > 0 )
650  {
651  QDomElement displayAttributesElem = displayAttributeList.at( 0 ).toElement();
652  QDomNodeList attributeEntryList = displayAttributesElem.elementsByTagName( "attributeEntry" );
653  for ( int i = 0; i < attributeEntryList.size(); ++i )
654  {
655  QDomElement attributeEntryElem = attributeEntryList.at( i ).toElement();
656  int index = attributeEntryElem.attribute( "index", "-1" ).toInt();
657  if ( index != -1 )
658  {
659  displayAttributes.insert( index );
660  }
661  }
662  setDisplayAttributes( displayAttributes, false );
663  }
664 
665  //restore alias map. This is required to upgrade pre 2.4 projects.
666  QMap<int, QString> fieldAliasMap;
667  QDomNodeList aliasMapNodeList = itemElem.elementsByTagName( "attributeAliasMap" );
668  if ( aliasMapNodeList.size() > 0 )
669  {
670  QDomElement attributeAliasMapElem = aliasMapNodeList.at( 0 ).toElement();
671  QDomNodeList aliasMepEntryList = attributeAliasMapElem.elementsByTagName( "aliasEntry" );
672  for ( int i = 0; i < aliasMepEntryList.size(); ++i )
673  {
674  QDomElement aliasEntryElem = aliasMepEntryList.at( i ).toElement();
675  int key = aliasEntryElem.attribute( "key", "-1" ).toInt();
676  QString value = aliasEntryElem.attribute( "value", "" );
677  fieldAliasMap.insert( key, value );
678  }
679  restoreFieldAliasMap( fieldAliasMap );
680  }
681 
682  //restore sort columns. This is required to upgrade pre 2.4 projects.
683  QDomElement sortColumnsElem = itemElem.firstChildElement( "sortColumns" );
684  if ( !sortColumnsElem.isNull() && mVectorLayer )
685  {
686  QDomNodeList columns = sortColumnsElem.elementsByTagName( "column" );
687  const QgsFields& fields = mVectorLayer->pendingFields();
688 
689  for ( int i = 0; i < columns.size(); ++i )
690  {
691  QDomElement columnElem = columns.at( i ).toElement();
692  int attribute = columnElem.attribute( "index" ).toInt();
693  Qt::SortOrder order = columnElem.attribute( "ascending" ) == "true" ? Qt::AscendingOrder : Qt::DescendingOrder;
694  //find corresponding column
695  QList<QgsComposerTableColumn*>::iterator columnIt = mColumns.begin();
696  for ( ; columnIt != mColumns.end(); ++columnIt )
697  {
698  if (( *columnIt )->attribute() == fields[attribute].name() )
699  {
700  ( *columnIt )->setSortByRank( i + 1 );
701  ( *columnIt )->setSortOrder( order );
702  break;
703  }
704  }
705  }
706  }
707 
708  //must be done here because tableReadXML->setSceneRect changes mMaximumNumberOfFeatures
709  mMaximumNumberOfFeatures = itemElem.attribute( "maxFeatures", "5" ).toInt();
710 
712 
713  emit itemChanged();
714  return true;
715 }