QGIS API Documentation  2.9.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgscomposerlegend.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscomposerlegend.cpp - description
3  ---------------------
4  begin : June 2008
5  copyright : (C) 2008 by Marco Hugentobler
6  email : marco dot hugentobler at karto dot baug dot ethz dot ch
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 #include <limits>
18 
19 #include "qgscomposerlegendstyle.h"
20 #include "qgscomposerlegend.h"
21 #include "qgscomposerlegenditem.h"
22 #include "qgscomposermap.h"
23 #include "qgscomposition.h"
24 #include "qgscomposermodel.h"
25 #include "qgsmaplayerregistry.h"
26 #include "qgslayertree.h"
27 #include "qgslayertreemodel.h"
28 #include "qgslegendrenderer.h"
29 #include "qgslogger.h"
30 #include "qgsproject.h"
31 #include <QDomDocument>
32 #include <QDomElement>
33 #include <QPainter>
34 
36  : QgsComposerItem( composition )
37  , mCustomLayerTree( 0 )
38  , mComposerMap( 0 )
39  , mLegendFilterByMap( false )
40 {
41  mLegendModel2 = new QgsLegendModelV2( QgsProject::instance()->layerTreeRoot() );
42 
43  adjustBoxSize();
44 
45  connect( &mLegendModel, SIGNAL( layersChanged() ), this, SLOT( synchronizeWithModel() ) );
46 }
47 
48 QgsComposerLegend::QgsComposerLegend()
49  : QgsComposerItem( 0 )
50  , mLegendModel2( 0 )
51  , mCustomLayerTree( 0 )
52  , mComposerMap( 0 )
53  , mLegendFilterByMap( false )
54 {
55 
56 }
57 
59 {
60  delete mLegendModel2;
61  delete mCustomLayerTree;
62 }
63 
64 void QgsComposerLegend::paint( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle, QWidget* pWidget )
65 {
66  Q_UNUSED( itemStyle );
67  Q_UNUSED( pWidget );
68 
69  if ( !painter )
70  return;
71 
72  if ( !shouldDrawItem() )
73  {
74  return;
75  }
76 
77  int dpi = painter->device()->logicalDpiX();
78  double dotsPerMM = dpi / 25.4;
79 
80  if ( mComposition )
81  {
83  mSettings.setDpi( dpi );
84  }
85  if ( mComposerMap )
86  {
87  mSettings.setMmPerMapUnit( mComposerMap->mapUnitsToMM() );
88 
89  // use a temporary QgsMapSettings to find out real map scale
90  QgsMapSettings ms = mComposerMap->composition()->mapSettings();
91  ms.setOutputSize( QSizeF( mComposerMap->rect().width() * dotsPerMM, mComposerMap->rect().height() * dotsPerMM ).toSize() );
92  ms.setExtent( *mComposerMap->currentMapExtent() );
93  ms.setOutputDpi( dpi );
94  mSettings.setMapScale( ms.scale() );
95  }
96 
97  drawBackground( painter );
98  painter->save();
99  //antialiasing on
100  painter->setRenderHint( QPainter::Antialiasing, true );
101  painter->setPen( QPen( QColor( 0, 0, 0 ) ) );
102 
103  QgsLegendRenderer legendRenderer( mLegendModel2, mSettings );
104  legendRenderer.setLegendSize( rect().size() );
105 
106  //adjust box if width or height is too small
107  QSizeF size = legendRenderer.minimumSize();
108  if ( size.height() > rect().height() || size.width() > rect().width() )
109  {
110  //need to resize box
111  QRectF targetRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
112  if ( size.height() > targetRect.height() )
113  targetRect.setHeight( size.height() );
114  if ( size.width() > rect().width() )
115  targetRect.setWidth( size.width() );
116 
117  //set new rect, respecting position mode and data defined size/position
118  setSceneRect( evalItemRect( targetRect, true ) );
119  }
120 
121  legendRenderer.drawLegend( painter );
122 
123  painter->restore();
124 
125  //draw frame and selection boxes if necessary
126  drawFrame( painter );
127  if ( isSelected() )
128  {
129  drawSelectionBoxes( painter );
130  }
131 }
132 
133 QSizeF QgsComposerLegend::paintAndDetermineSize( QPainter* painter )
134 {
135  QgsLegendRenderer legendRenderer( mLegendModel2, mSettings );
136  QSizeF size = legendRenderer.minimumSize();
137  if ( painter )
138  legendRenderer.drawLegend( painter );
139  return size;
140 }
141 
142 
144 {
145  QgsLegendRenderer legendRenderer( mLegendModel2, mSettings );
146  QSizeF size = legendRenderer.minimumSize();
147  QgsDebugMsg( QString( "width = %1 height = %2" ).arg( size.width() ).arg( size.height() ) );
148  if ( size.isValid() )
149  {
150  QRectF targetRect = QRectF( pos().x(), pos().y(), size.width(), size.height() );
151  //set new rect, respecting position mode and data defined size/position
152  setSceneRect( evalItemRect( targetRect, true ) );
153  }
154 }
155 
156 
157 void QgsComposerLegend::setCustomLayerTree( QgsLayerTreeGroup* rootGroup )
158 {
159  mLegendModel2->setRootGroup( rootGroup ? rootGroup : QgsProject::instance()->layerTreeRoot() );
160 
161  delete mCustomLayerTree;
162  mCustomLayerTree = rootGroup;
163 }
164 
165 
167 {
168  if ( autoUpdate == autoUpdateModel() )
169  return;
170 
171  setCustomLayerTree( autoUpdate ? 0 : QgsLayerTree::toGroup( QgsProject::instance()->layerTreeRoot()->clone() ) );
172  adjustBoxSize();
173 }
174 
176 {
177  return !mCustomLayerTree;
178 }
179 
181 {
182  mLegendFilterByMap = enabled;
183  updateFilterByMap();
184 }
185 
186 void QgsComposerLegend::setTitle( const QString& t )
187 {
188  mSettings.setTitle( t );
189 
190  if ( mComposition && id().isEmpty() )
191  {
192  //notify the model that the display name has changed
194  }
195 }
196 QString QgsComposerLegend::title() const { return mSettings.title(); }
197 
198 Qt::AlignmentFlag QgsComposerLegend::titleAlignment() const { return mSettings.titleAlignment(); }
199 void QgsComposerLegend::setTitleAlignment( Qt::AlignmentFlag alignment ) { mSettings.setTitleAlignment( alignment ); }
200 
204 
205 QFont QgsComposerLegend::styleFont( QgsComposerLegendStyle::Style s ) const { return mSettings.style( s ).font(); }
207 
210 
211 double QgsComposerLegend::boxSpace() const { return mSettings.boxSpace(); }
212 void QgsComposerLegend::setBoxSpace( double s ) { mSettings.setBoxSpace( s ); }
213 
214 double QgsComposerLegend::columnSpace() const { return mSettings.columnSpace(); }
215 void QgsComposerLegend::setColumnSpace( double s ) { mSettings.setColumnSpace( s ); }
216 
217 QColor QgsComposerLegend::fontColor() const { return mSettings.fontColor(); }
218 void QgsComposerLegend::setFontColor( const QColor& c ) { mSettings.setFontColor( c ); }
219 
220 double QgsComposerLegend::symbolWidth() const { return mSettings.symbolSize().width(); }
221 void QgsComposerLegend::setSymbolWidth( double w ) { mSettings.setSymbolSize( QSizeF( w, mSettings.symbolSize().height() ) ); }
222 
223 double QgsComposerLegend::symbolHeight() const { return mSettings.symbolSize().height(); }
224 void QgsComposerLegend::setSymbolHeight( double h ) { mSettings.setSymbolSize( QSizeF( mSettings.symbolSize().width(), h ) ); }
225 
226 double QgsComposerLegend::wmsLegendWidth() const { return mSettings.wmsLegendSize().width(); }
227 void QgsComposerLegend::setWmsLegendWidth( double w ) { mSettings.setWmsLegendSize( QSizeF( w, mSettings.wmsLegendSize().height() ) ); }
228 
229 double QgsComposerLegend::wmsLegendHeight() const {return mSettings.wmsLegendSize().height(); }
230 void QgsComposerLegend::setWmsLegendHeight( double h ) { mSettings.setWmsLegendSize( QSizeF( mSettings.wmsLegendSize().width(), h ) ); }
231 
232 void QgsComposerLegend::setWrapChar( const QString& t ) { mSettings.setWrapChar( t ); }
233 QString QgsComposerLegend::wrapChar() const {return mSettings.wrapChar(); }
234 
235 int QgsComposerLegend::columnCount() const { return mSettings.columnCount(); }
236 void QgsComposerLegend::setColumnCount( int c ) { mSettings.setColumnCount( c ); }
237 
238 int QgsComposerLegend::splitLayer() const { return mSettings.splitLayer(); }
239 void QgsComposerLegend::setSplitLayer( bool s ) { mSettings.setSplitLayer( s ); }
240 
241 int QgsComposerLegend::equalColumnWidth() const { return mSettings.equalColumnWidth(); }
243 
244 
246 {
247  QgsDebugMsg( "Entered" );
248  adjustBoxSize();
249  update();
250 }
251 
253 {
254  // take layer list from map renderer (to have legend order)
255  mLegendModel.setLayerSet( mComposition ? mComposition->mapSettings().layers() : QStringList() );
256  adjustBoxSize();
257  update();
258 }
259 
260 bool QgsComposerLegend::writeXML( QDomElement& elem, QDomDocument & doc ) const
261 {
262  if ( elem.isNull() )
263  {
264  return false;
265  }
266 
267  QDomElement composerLegendElem = doc.createElement( "ComposerLegend" );
268  elem.appendChild( composerLegendElem );
269 
270  //write general properties
271  composerLegendElem.setAttribute( "title", mSettings.title() );
272  composerLegendElem.setAttribute( "titleAlignment", QString::number(( int ) mSettings.titleAlignment() ) );
273  composerLegendElem.setAttribute( "columnCount", QString::number( mSettings.columnCount() ) );
274  composerLegendElem.setAttribute( "splitLayer", QString::number( mSettings.splitLayer() ) );
275  composerLegendElem.setAttribute( "equalColumnWidth", QString::number( mSettings.equalColumnWidth() ) );
276 
277  composerLegendElem.setAttribute( "boxSpace", QString::number( mSettings.boxSpace() ) );
278  composerLegendElem.setAttribute( "columnSpace", QString::number( mSettings.columnSpace() ) );
279 
280  composerLegendElem.setAttribute( "symbolWidth", QString::number( mSettings.symbolSize().width() ) );
281  composerLegendElem.setAttribute( "symbolHeight", QString::number( mSettings.symbolSize().height() ) );
282  composerLegendElem.setAttribute( "wmsLegendWidth", QString::number( mSettings.wmsLegendSize().width() ) );
283  composerLegendElem.setAttribute( "wmsLegendHeight", QString::number( mSettings.wmsLegendSize().height() ) );
284  composerLegendElem.setAttribute( "wrapChar", mSettings.wrapChar() );
285  composerLegendElem.setAttribute( "fontColor", mSettings.fontColor().name() );
286 
287  if ( mComposerMap )
288  {
289  composerLegendElem.setAttribute( "map", mComposerMap->id() );
290  }
291 
292  QDomElement composerLegendStyles = doc.createElement( "styles" );
293  composerLegendElem.appendChild( composerLegendStyles );
294 
295  style( QgsComposerLegendStyle::Title ).writeXML( "title", composerLegendStyles, doc );
296  style( QgsComposerLegendStyle::Group ).writeXML( "group", composerLegendStyles, doc );
297  style( QgsComposerLegendStyle::Subgroup ).writeXML( "subgroup", composerLegendStyles, doc );
298  style( QgsComposerLegendStyle::Symbol ).writeXML( "symbol", composerLegendStyles, doc );
299  style( QgsComposerLegendStyle::SymbolLabel ).writeXML( "symbolLabel", composerLegendStyles, doc );
300 
301  if ( mCustomLayerTree )
302  {
303  // if not using auto-update - store the custom layer tree
304  mCustomLayerTree->writeXML( composerLegendElem );
305  }
306 
307  if ( mLegendFilterByMap )
308  composerLegendElem.setAttribute( "legendFilterByMap", "1" );
309 
310  return _writeXML( composerLegendElem, doc );
311 }
312 
313 static void _readOldLegendGroup( QDomElement& elem, QgsLayerTreeGroup* parentGroup )
314 {
315  QDomElement itemElem = elem.firstChildElement();
316 
317  while ( !itemElem.isNull() )
318  {
319 
320  if ( itemElem.tagName() == "LayerItem" )
321  {
322  QString layerId = itemElem.attribute( "layerId" );
323  if ( QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( layerId ) )
324  {
325  QgsLayerTreeLayer* nodeLayer = parentGroup->addLayer( layer );
326  QString userText = itemElem.attribute( "userText" );
327  if ( !userText.isEmpty() )
328  nodeLayer->setCustomProperty( "legend/title-label", userText );
329  QString style = itemElem.attribute( "style" );
330  if ( !style.isEmpty() )
331  nodeLayer->setCustomProperty( "legend/title-style", style );
332  QString showFeatureCount = itemElem.attribute( "showFeatureCount" );
333  if ( showFeatureCount.toInt() )
334  nodeLayer->setCustomProperty( "showFeatureCount", 1 );
335 
336  // support for individual legend items (user text, order) not implemented yet
337  }
338  }
339  else if ( itemElem.tagName() == "GroupItem" )
340  {
341  QgsLayerTreeGroup* nodeGroup = parentGroup->addGroup( itemElem.attribute( "userText" ) );
342  QString style = itemElem.attribute( "style" );
343  if ( !style.isEmpty() )
344  nodeGroup->setCustomProperty( "legend/title-style", style );
345 
346  _readOldLegendGroup( itemElem, nodeGroup );
347  }
348 
349  itemElem = itemElem.nextSiblingElement();
350  }
351 }
352 
353 bool QgsComposerLegend::readXML( const QDomElement& itemElem, const QDomDocument& doc )
354 {
355  if ( itemElem.isNull() )
356  {
357  return false;
358  }
359 
360  //read general properties
361  mSettings.setTitle( itemElem.attribute( "title" ) );
362  if ( !itemElem.attribute( "titleAlignment" ).isEmpty() )
363  {
364  mSettings.setTitleAlignment(( Qt::AlignmentFlag )itemElem.attribute( "titleAlignment" ).toInt() );
365  }
366  int colCount = itemElem.attribute( "columnCount", "1" ).toInt();
367  if ( colCount < 1 ) colCount = 1;
368  mSettings.setColumnCount( colCount );
369  mSettings.setSplitLayer( itemElem.attribute( "splitLayer", "0" ).toInt() == 1 );
370  mSettings.setEqualColumnWidth( itemElem.attribute( "equalColumnWidth", "0" ).toInt() == 1 );
371 
372  QDomNodeList stylesNodeList = itemElem.elementsByTagName( "styles" );
373  if ( stylesNodeList.size() > 0 )
374  {
375  QDomNode stylesNode = stylesNodeList.at( 0 );
376  for ( int i = 0; i < stylesNode.childNodes().size(); i++ )
377  {
378  QDomElement styleElem = stylesNode.childNodes().at( i ).toElement();
380  style.readXML( styleElem, doc );
381  QString name = styleElem.attribute( "name" );
383  if ( name == "title" ) s = QgsComposerLegendStyle::Title;
384  else if ( name == "group" ) s = QgsComposerLegendStyle::Group;
385  else if ( name == "subgroup" ) s = QgsComposerLegendStyle::Subgroup;
386  else if ( name == "symbol" ) s = QgsComposerLegendStyle::Symbol;
387  else if ( name == "symbolLabel" ) s = QgsComposerLegendStyle::SymbolLabel;
388  else continue;
389  setStyle( s, style );
390  }
391  }
392 
393  //font color
394  QColor fontClr;
395  fontClr.setNamedColor( itemElem.attribute( "fontColor", "#000000" ) );
396  mSettings.setFontColor( fontClr );
397 
398  //spaces
399  mSettings.setBoxSpace( itemElem.attribute( "boxSpace", "2.0" ).toDouble() );
400  mSettings.setColumnSpace( itemElem.attribute( "columnSpace", "2.0" ).toDouble() );
401 
402  mSettings.setSymbolSize( QSizeF( itemElem.attribute( "symbolWidth", "7.0" ).toDouble(), itemElem.attribute( "symbolHeight", "14.0" ).toDouble() ) );
403  mSettings.setWmsLegendSize( QSizeF( itemElem.attribute( "wmsLegendWidth", "50" ).toDouble(), itemElem.attribute( "wmsLegendHeight", "25" ).toDouble() ) );
404 
405  mSettings.setWrapChar( itemElem.attribute( "wrapChar" ) );
406 
407  //composer map
408  mLegendFilterByMap = itemElem.attribute( "legendFilterByMap", "0" ).toInt();
409  if ( !itemElem.attribute( "map" ).isEmpty() )
410  {
411  setComposerMap( mComposition->getComposerMapById( itemElem.attribute( "map" ).toInt() ) );
412  }
413 
414  QDomElement oldLegendModelElem = itemElem.firstChildElement( "Model" );
415  if ( !oldLegendModelElem.isNull() )
416  {
417  // QGIS <= 2.4
418  QgsLayerTreeGroup* nodeRoot = new QgsLayerTreeGroup();
419  _readOldLegendGroup( oldLegendModelElem, nodeRoot );
420  setCustomLayerTree( nodeRoot );
421  }
422  else
423  {
424  // QGIS >= 2.6
425  QDomElement layerTreeElem = itemElem.firstChildElement( "layer-tree-group" );
426  setCustomLayerTree( QgsLayerTreeGroup::readXML( layerTreeElem ) );
427  }
428 
429  //restore general composer item properties
430  QDomNodeList composerItemList = itemElem.elementsByTagName( "ComposerItem" );
431  if ( composerItemList.size() > 0 )
432  {
433  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
434  _readXML( composerItemElem, doc );
435  }
436 
437  // < 2.0 projects backward compatibility >>>>>
438  //title font
439  QString titleFontString = itemElem.attribute( "titleFont" );
440  if ( !titleFontString.isEmpty() )
441  {
442  rstyle( QgsComposerLegendStyle::Title ).rfont().fromString( titleFontString );
443  }
444  //group font
445  QString groupFontString = itemElem.attribute( "groupFont" );
446  if ( !groupFontString.isEmpty() )
447  {
448  rstyle( QgsComposerLegendStyle::Group ).rfont().fromString( groupFontString );
449  }
450 
451  //layer font
452  QString layerFontString = itemElem.attribute( "layerFont" );
453  if ( !layerFontString.isEmpty() )
454  {
455  rstyle( QgsComposerLegendStyle::Subgroup ).rfont().fromString( layerFontString );
456  }
457  //item font
458  QString itemFontString = itemElem.attribute( "itemFont" );
459  if ( !itemFontString.isEmpty() )
460  {
461  rstyle( QgsComposerLegendStyle::SymbolLabel ).rfont().fromString( itemFontString );
462  }
463 
464  if ( !itemElem.attribute( "groupSpace" ).isEmpty() )
465  {
466  rstyle( QgsComposerLegendStyle::Group ).setMargin( QgsComposerLegendStyle::Top, itemElem.attribute( "groupSpace", "3.0" ).toDouble() );
467  }
468  if ( !itemElem.attribute( "layerSpace" ).isEmpty() )
469  {
470  rstyle( QgsComposerLegendStyle::Subgroup ).setMargin( QgsComposerLegendStyle::Top, itemElem.attribute( "layerSpace", "3.0" ).toDouble() );
471  }
472  if ( !itemElem.attribute( "symbolSpace" ).isEmpty() )
473  {
474  rstyle( QgsComposerLegendStyle::Symbol ).setMargin( QgsComposerLegendStyle::Top, itemElem.attribute( "symbolSpace", "2.0" ).toDouble() );
475  rstyle( QgsComposerLegendStyle::SymbolLabel ).setMargin( QgsComposerLegendStyle::Top, itemElem.attribute( "symbolSpace", "2.0" ).toDouble() );
476  }
477  // <<<<<<< < 2.0 projects backward compatibility
478 
479  emit itemChanged();
480  return true;
481 }
482 
484 {
485  if ( !id().isEmpty() )
486  {
487  return id();
488  }
489 
490  //if no id, default to portion of title text
491  QString text = mSettings.title();
492  if ( text.isEmpty() )
493  {
494  return tr( "<legend>" );
495  }
496  if ( text.length() > 25 )
497  {
498  return QString( tr( "%1..." ) ).arg( text.left( 25 ) );
499  }
500  else
501  {
502  return text;
503  }
504 }
505 
507 {
508  if ( mComposerMap )
509  {
510  disconnect( mComposerMap, SIGNAL( destroyed( QObject* ) ), this, SLOT( invalidateCurrentMap() ) );
511  disconnect( mComposerMap, SIGNAL( itemChanged() ), this, SLOT( updateFilterByMap() ) );
512  disconnect( mComposerMap, SIGNAL( extentChanged() ), this, SLOT( updateFilterByMap() ) );
513  }
514 
515  mComposerMap = map;
516 
517  if ( map )
518  {
519  QObject::connect( map, SIGNAL( destroyed( QObject* ) ), this, SLOT( invalidateCurrentMap() ) );
520  QObject::connect( map, SIGNAL( itemChanged() ), this, SLOT( updateFilterByMap() ) );
521  QObject::connect( map, SIGNAL( extentChanged() ), this, SLOT( updateFilterByMap() ) );
522  }
523 
524  updateFilterByMap();
525 }
526 
528 {
529  setComposerMap( 0 );
530 }
531 
532 void QgsComposerLegend::updateFilterByMap()
533 {
534  if ( isRemoved() )
535  return;
536 
537  if ( mComposerMap && mLegendFilterByMap )
538  {
539  int dpi = mComposition->printResolution();
540 
541  QgsRectangle requestRectangle;
542  mComposerMap->requestedExtent( requestRectangle );
543 
544  QSizeF theSize( requestRectangle.width(), requestRectangle.height() );
545  theSize *= mComposerMap->mapUnitsToMM() * dpi / 25.4;
546 
547  QgsMapSettings ms = mComposerMap->mapSettings( requestRectangle, theSize, dpi );
548 
549  mLegendModel2->setLegendFilterByMap( &ms );
550  }
551  else
552  mLegendModel2->setLegendFilterByMap( 0 );
553 
554  adjustBoxSize();
555  update();
556 }
557 
558 // -------------------------------------------------------------------------
560 #include "qgsvectorlayer.h"
561 
563  : QgsLayerTreeModel( rootNode, parent )
564 {
567 }
568 
569 QVariant QgsLegendModelV2::data( const QModelIndex& index, int role ) const
570 {
571  // handle custom layer node labels
572  if ( QgsLayerTreeNode* node = index2node( index ) )
573  {
574  if ( QgsLayerTree::isLayer( node ) && ( role == Qt::DisplayRole || role == Qt::EditRole ) && !node->customProperty( "legend/title-label" ).isNull() )
575  {
576  QgsLayerTreeLayer* nodeLayer = QgsLayerTree::toLayer( node );
577  QString name = node->customProperty( "legend/title-label" ).toString();
578  if ( nodeLayer->customProperty( "showFeatureCount", 0 ).toInt() && role == Qt::DisplayRole )
579  {
580  QgsVectorLayer* vlayer = qobject_cast<QgsVectorLayer*>( nodeLayer->layer() );
581  if ( vlayer && vlayer->pendingFeatureCount() >= 0 )
582  name += QString( " [%1]" ).arg( vlayer->pendingFeatureCount() );
583  }
584  return name;
585  }
586  }
587 
588  return QgsLayerTreeModel::data( index, role );
589 }
590 
591 Qt::ItemFlags QgsLegendModelV2::flags( const QModelIndex& index ) const
592 {
593  // make the legend nodes selectable even if they are not by default
594  if ( index2legendNode( index ) )
595  return QgsLayerTreeModel::flags( index ) | Qt::ItemIsSelectable;
596 
597  return QgsLayerTreeModel::flags( index );
598 }