QGIS API Documentation  2.3.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 "qgslogger.h"
25 #include "qgsmaplayer.h"
26 #include "qgsmaplayerregistry.h"
27 #include "qgsmaprenderer.h"
28 #include "qgssymbolv2.h"
29 #include <QDomDocument>
30 #include <QDomElement>
31 #include <QPainter>
32 
34  : QgsComposerItem( composition )
35  , mTitle( tr( "Legend" ) )
36  , mFontColor( QColor( 0, 0, 0 ) )
37  , mBoxSpace( 2 )
38  , mColumnSpace( 2 )
39  , mTitleAlignment( Qt::AlignLeft )
40  , mColumnCount( 1 )
41  , mComposerMap( 0 )
42  , mSplitLayer( false )
43  , mEqualColumnWidth( false )
44 {
51  rstyle( QgsComposerLegendStyle::Title ).rfont().setPointSizeF( 16.0 );
52  rstyle( QgsComposerLegendStyle::Group ).rfont().setPointSizeF( 14.0 );
53  rstyle( QgsComposerLegendStyle::Subgroup ).rfont().setPointSizeF( 12.0 );
54  rstyle( QgsComposerLegendStyle::SymbolLabel ).rfont().setPointSizeF( 12.0 );
55 
56  mSymbolWidth = 7;
57  mSymbolHeight = 4;
58  mWmsLegendWidth = 50;
59  mWmsLegendHeight = 25;
60  mWrapChar = "";
61  mlineSpacing = 1.5;
62  adjustBoxSize();
63 
64  connect( &mLegendModel, SIGNAL( layersChanged() ), this, SLOT( synchronizeWithModel() ) );
65 }
66 
68 {
69 
70 }
71 
73 {
74 
75 }
76 
77 void QgsComposerLegend::paint( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle, QWidget* pWidget )
78 {
79  Q_UNUSED( itemStyle );
80  Q_UNUSED( pWidget );
81  paintAndDetermineSize( painter );
82 }
83 
84 QSizeF QgsComposerLegend::paintAndDetermineSize( QPainter* painter )
85 {
86  QSizeF size( 0, 0 );
87  QStandardItem* rootItem = mLegendModel.invisibleRootItem();
88  if ( !rootItem ) return size;
89 
90  if ( painter )
91  {
92  painter->save();
93  drawBackground( painter );
94  painter->setPen( QPen( QColor( 0, 0, 0 ) ) );
95  }
96 
97  QList<Atom> atomList = createAtomList( rootItem, mSplitLayer );
98 
99  setColumns( atomList );
100 
101  qreal maxColumnWidth = 0;
102  if ( mEqualColumnWidth )
103  {
104  foreach ( Atom atom, atomList )
105  {
106  maxColumnWidth = qMax( atom.size.width(), maxColumnWidth );
107  }
108  }
109 
110  //calculate size of title
111  QSizeF titleSize = drawTitle();
112  //add title margin to size of title text
113  titleSize.rwidth() += mBoxSpace * 2.0;
114  double columnTop = mBoxSpace + titleSize.height() + style( QgsComposerLegendStyle::Title ).margin( QgsComposerLegendStyle::Bottom );
115 
116  QPointF point( mBoxSpace, columnTop );
117  bool firstInColumn = true;
118  double columnMaxHeight = 0;
119  qreal columnWidth = 0;
120  int column = 0;
121  foreach ( Atom atom, atomList )
122  {
123  if ( atom.column > column )
124  {
125  // Switch to next column
126  if ( mEqualColumnWidth )
127  {
128  point.rx() += mColumnSpace + maxColumnWidth;
129  }
130  else
131  {
132  point.rx() += mColumnSpace + columnWidth;
133  }
134  point.ry() = columnTop;
135  columnWidth = 0;
136  column++;
137  firstInColumn = true;
138  }
139  if ( !firstInColumn )
140  {
141  point.ry() += spaceAboveAtom( atom );
142  }
143 
144  QSizeF atomSize = drawAtom( atom, painter, point );
145  columnWidth = qMax( atomSize.width(), columnWidth );
146 
147  point.ry() += atom.size.height();
148  columnMaxHeight = qMax( point.y() - columnTop, columnMaxHeight );
149 
150  firstInColumn = false;
151  }
152  point.rx() += columnWidth + mBoxSpace;
153 
154  size.rheight() = columnTop + columnMaxHeight + mBoxSpace;
155  size.rwidth() = point.x();
156  if ( !mTitle.isEmpty() )
157  {
158  size.rwidth() = qMax( titleSize.width(), size.width() );
159  }
160 
161  //adjust box if width or height is too small
162  if ( painter && size.height() > rect().height() )
163  {
164  setSceneRect( QRectF( pos().x(), pos().y(), rect().width(), size.height() ) );
165  }
166  if ( painter && size.width() > rect().width() )
167  {
168  setSceneRect( QRectF( pos().x(), pos().y(), size.width(), rect().height() ) );
169  }
170 
171  // Now we have set the correct total item width and can draw the title centered
172  if ( !mTitle.isEmpty() )
173  {
174  if ( mTitleAlignment == Qt::AlignLeft )
175  {
176  point.rx() = mBoxSpace;
177  }
178  else if ( mTitleAlignment == Qt::AlignHCenter )
179  {
180  point.rx() = rect().width() / 2;
181  }
182  else
183  {
184  point.rx() = rect().width() - mBoxSpace;
185  }
186  point.ry() = mBoxSpace;
187  drawTitle( painter, point, mTitleAlignment );
188  }
189 
190  if ( painter )
191  {
192  painter->restore();
193 
194  //draw frame and selection boxes if necessary
195  drawFrame( painter );
196  if ( isSelected() )
197  {
198  drawSelectionBoxes( painter );
199  }
200  }
201 
202  return size;
203 }
204 
205 QSizeF QgsComposerLegend::drawTitle( QPainter* painter, QPointF point, Qt::AlignmentFlag halignment )
206 {
207  QSizeF size( 0, 0 );
208  if ( mTitle.isEmpty() )
209  {
210  return size;
211  }
212 
213  QStringList lines = splitStringForWrapping( mTitle );
214  double y = point.y();
215 
216  if ( painter )
217  {
218  painter->setPen( mFontColor );
219  }
220 
221  //calculate width and left pos of rectangle to draw text into
222  double textBoxWidth;
223  double textBoxLeft;
224  switch ( halignment )
225  {
226  case Qt::AlignHCenter:
227  textBoxWidth = ( qMin( point.x(), rect().width() - point.x() ) - mBoxSpace ) * 2.0;
228  textBoxLeft = point.x() - textBoxWidth / 2.;
229  break;
230  case Qt::AlignRight:
231  textBoxLeft = mBoxSpace;
232  textBoxWidth = point.x() - mBoxSpace;
233  break;
234  case Qt::AlignLeft:
235  default:
236  textBoxLeft = point.x();
237  textBoxWidth = rect().width() - point.x() - mBoxSpace;
238  break;
239  }
240 
241  for ( QStringList::Iterator titlePart = lines.begin(); titlePart != lines.end(); ++titlePart )
242  {
243  //last word is not drawn if rectangle width is exactly text width, so add 1
244  //TODO - correctly calculate size of italicized text, since QFontMetrics does not
245  qreal width = textWidthMillimeters( styleFont( QgsComposerLegendStyle::Title ), *titlePart ) + 1;
247 
248  QRectF r( textBoxLeft, y, textBoxWidth, height );
249 
250  if ( painter )
251  {
252  drawText( painter, r, *titlePart, styleFont( QgsComposerLegendStyle::Title ), halignment, Qt::AlignVCenter );
253  }
254 
255  //update max width of title
256  size.rwidth() = qMax( width, size.rwidth() );
257 
258  y += height;
259  if ( titlePart != lines.end() )
260  {
261  y += mlineSpacing;
262  }
263  }
264  size.rheight() = y - point.y();
265 
266  return size;
267 }
268 
269 
270 QSizeF QgsComposerLegend::drawGroupItemTitle( QgsComposerGroupItem* groupItem, QPainter* painter, QPointF point )
271 {
272  QSizeF size( 0, 0 );
273  if ( !groupItem ) return size;
274 
275  double y = point.y();
276 
277  if ( painter ) painter->setPen( mFontColor );
278 
279  QStringList lines = splitStringForWrapping( groupItem->text() );
280  for ( QStringList::Iterator groupPart = lines.begin(); groupPart != lines.end(); ++groupPart )
281  {
282  y += fontAscentMillimeters( styleFont( groupItem->style() ) );
283  if ( painter ) drawText( painter, point.x(), y, *groupPart, styleFont( groupItem->style() ) );
284  qreal width = textWidthMillimeters( styleFont( groupItem->style() ), *groupPart );
285  size.rwidth() = qMax( width, size.width() );
286  if ( groupPart != lines.end() )
287  {
288  y += mlineSpacing;
289  }
290  }
291  size.rheight() = y - point.y();
292  return size;
293 }
294 
295 QSizeF QgsComposerLegend::drawLayerItemTitle( QgsComposerLayerItem* layerItem, QPainter* painter, QPointF point )
296 {
297  QSizeF size( 0, 0 );
298  if ( !layerItem ) return size;
299 
300  //Let the user omit the layer title item by having an empty layer title string
301  if ( layerItem->text().isEmpty() ) return size;
302 
303  double y = point.y();
304 
305  if ( painter ) painter->setPen( mFontColor );
306 
307  QStringList lines = splitStringForWrapping( layerItem->text() );
308  for ( QStringList::Iterator layerItemPart = lines.begin(); layerItemPart != lines.end(); ++layerItemPart )
309  {
310  y += fontAscentMillimeters( styleFont( layerItem->style() ) );
311  if ( painter ) drawText( painter, point.x(), y, *layerItemPart , styleFont( layerItem->style() ) );
312  qreal width = textWidthMillimeters( styleFont( layerItem->style() ), *layerItemPart );
313  size.rwidth() = qMax( width, size.width() );
314  if ( layerItemPart != lines.end() )
315  {
316  y += mlineSpacing;
317  }
318  }
319  size.rheight() = y - point.y();
320 
321  return size;
322 }
323 
325 {
326  QSizeF size = paintAndDetermineSize( 0 );
327  QgsDebugMsg( QString( "width = %1 height = %2" ).arg( size.width() ).arg( size.height() ) );
328  if ( size.isValid() )
329  {
330  setSceneRect( QRectF( pos().x(), pos().y(), size.width(), size.height() ) );
331  }
332 }
333 
334 QgsComposerLegend::Nucleon QgsComposerLegend::drawSymbolItem( QgsComposerLegendItem* symbolItem, QPainter* painter, QPointF point, double labelXOffset )
335 {
336  QSizeF symbolSize( 0, 0 );
337  QSizeF labelSize( 0, 0 );
338  if ( !symbolItem ) return Nucleon();
339 
340  double textHeight = fontHeightCharacterMM( styleFont( QgsComposerLegendStyle::SymbolLabel ), QChar( '0' ) );
341  // itemHeight here is not realy item height, it is only for symbol
342  // vertical alignment purpose, i.e. ok take single line height
343  // if there are more lines, thos run under the symbol
344  double itemHeight = qMax( mSymbolHeight, textHeight );
345 
346  //real symbol height. Can be different from standard height in case of point symbols
347  double realSymbolHeight;
348 
349 #if 0
350  QgsComposerLayerItem* layerItem = dynamic_cast<QgsComposerLayerItem*>( symbolItem->parent() );
351 
352  int opacity = 255;
353  if ( layerItem )
354  {
355  QgsMapLayer* currentLayer = QgsMapLayerRegistry::instance()->mapLayer( layerItem->layerID() );
356  if ( currentLayer )
357  {
358  opacity = currentLayer->getTransparency();
359  }
360  }
361 #endif
362 
363  QString text = symbolItem->text();
364 
365  QStringList lines = splitStringForWrapping( text );
366 
367  QgsSymbolV2* symbolNg = 0;
368  QgsComposerSymbolV2Item* symbolV2Item = dynamic_cast<QgsComposerSymbolV2Item*>( symbolItem );
369  if ( symbolV2Item )
370  {
371  symbolNg = symbolV2Item->symbolV2();
372  }
373  QgsComposerRasterSymbolItem* rasterItem = dynamic_cast<QgsComposerRasterSymbolItem*>( symbolItem );
374 
375  double x = point.x();
376  if ( symbolNg ) //item with symbol NG?
377  {
378  // must be called also with painter=0 to get real size
379  drawSymbolV2( painter, symbolNg, point.y() + ( itemHeight - mSymbolHeight ) / 2, x, realSymbolHeight );
380  symbolSize.rwidth() = qMax( x - point.x(), mSymbolWidth );
381  symbolSize.rheight() = qMax( realSymbolHeight, mSymbolHeight );
382  }
383  else if ( rasterItem )
384  {
385  // manage WMS lengendGraphic
386  // actual code recognise if it's a legend because it has an icon and it's text is empty => this is not good MV pattern implementation :(
387  QIcon symbolIcon = symbolItem->icon();
388  if ( !symbolIcon.isNull() && symbolItem->text().isEmpty() )
389  {
390  // find max size
391  QList<QSize> sizes = symbolIcon.availableSizes();
392  double maxWidth = 0;
393  double maxHeight = 0;
394  foreach ( QSize size, sizes )
395  {
396  if ( maxWidth < size.width() ) maxWidth = size.width();
397  if ( maxHeight < size.height() ) maxHeight = size.height();
398  }
399  QSize maxSize( maxWidth, maxHeight );
400 
401  // get and print legend
402  QImage legend = symbolIcon.pixmap( maxWidth, maxHeight ).toImage();
403  if ( painter )
404  {
405  painter->drawImage( QRectF( point.x(), point.y(), mWmsLegendWidth, mWmsLegendHeight ), legend, QRectF( 0, 0, maxWidth, maxHeight ) );
406  }
407  symbolSize.rwidth() = mWmsLegendWidth;
408  symbolSize.rheight() = mWmsLegendHeight;
409  }
410  else
411  {
412  if ( painter )
413  {
414  painter->setBrush( rasterItem->color() );
415  painter->drawRect( QRectF( point.x(), point.y() + ( itemHeight - mSymbolHeight ) / 2, mSymbolWidth, mSymbolHeight ) );
416  }
417  symbolSize.rwidth() = mSymbolWidth;
418  symbolSize.rheight() = mSymbolHeight;
419  }
420  }
421  else //item with icon?
422  {
423  QIcon symbolIcon = symbolItem->icon();
424  if ( !symbolIcon.isNull() )
425  {
426  if ( painter ) symbolIcon.paint( painter, point.x(), point.y() + ( itemHeight - mSymbolHeight ) / 2, mSymbolWidth, mSymbolHeight );
427  symbolSize.rwidth() = mSymbolWidth;
428  symbolSize.rheight() = mSymbolHeight;
429  }
430  }
431 
432  if ( painter ) painter->setPen( mFontColor );
433 
434  //double labelX = point.x() + labelXOffset; // + mIconLabelSpace;
435  double labelX = point.x() + qMax(( double ) symbolSize.width(), labelXOffset );
436 
437  // Vertical alignment of label with symbol:
438  // a) label height < symbol height: label centerd with symbol
439  // b) label height > symbol height: label starts at top and runs under symbol
440 
441  labelSize.rheight() = lines.count() * textHeight + ( lines.count() - 1 ) * mlineSpacing;
442 
443  double labelY;
444  if ( labelSize.height() < symbolSize.height() )
445  {
446  labelY = point.y() + symbolSize.height() / 2 + textHeight / 2;
447  }
448  else
449  {
450  labelY = point.y() + textHeight;
451  }
452 
453  for ( QStringList::Iterator itemPart = lines.begin(); itemPart != lines.end(); ++itemPart )
454  {
455  if ( painter ) drawText( painter, labelX, labelY, *itemPart , styleFont( QgsComposerLegendStyle::SymbolLabel ) );
456  labelSize.rwidth() = qMax( textWidthMillimeters( styleFont( QgsComposerLegendStyle::SymbolLabel ), *itemPart ), double( labelSize.width() ) );
457  if ( itemPart != lines.end() )
458  {
459  labelY += mlineSpacing + textHeight;
460  }
461  }
462 
463  Nucleon nucleon;
464  nucleon.item = symbolItem;
465  nucleon.symbolSize = symbolSize;
466  nucleon.labelSize = labelSize;
467  //QgsDebugMsg( QString( "symbol height = %1 label height = %2").arg( symbolSize.height()).arg( labelSize.height() ));
468  double width = qMax(( double ) symbolSize.width(), labelXOffset ) + labelSize.width();
469  double height = qMax( symbolSize.height(), labelSize.height() );
470  nucleon.size = QSizeF( width, height );
471  return nucleon;
472 }
473 
474 
475 void QgsComposerLegend::drawSymbolV2( QPainter* p, QgsSymbolV2* s, double currentYCoord, double& currentXPosition, double& symbolHeight ) const
476 {
477  if ( !s )
478  {
479  return;
480  }
481 
482  double rasterScaleFactor = 1.0;
483  if ( p )
484  {
485  QPaintDevice* paintDevice = p->device();
486  if ( !paintDevice )
487  {
488  return;
489  }
490  rasterScaleFactor = ( paintDevice->logicalDpiX() + paintDevice->logicalDpiY() ) / 2.0 / 25.4;
491  }
492 
493  //consider relation to composer map for symbol sizes in mm
494  bool sizeInMapUnits = s->outputUnit() == QgsSymbolV2::MapUnit;
495  double mmPerMapUnit = 1;
496  if ( mComposerMap )
497  {
498  mmPerMapUnit = mComposerMap->mapUnitsToMM();
499  }
500  QgsMarkerSymbolV2* markerSymbol = dynamic_cast<QgsMarkerSymbolV2*>( s );
501 
502  //Consider symbol size for point markers
503  double height = mSymbolHeight;
504  double width = mSymbolWidth;
505  double size = 0;
506  //Center small marker symbols
507  double widthOffset = 0;
508  double heightOffset = 0;
509 
510  if ( markerSymbol )
511  {
512  size = markerSymbol->size();
513  height = size;
514  width = size;
515  if ( mComposerMap && sizeInMapUnits )
516  {
517  height *= mmPerMapUnit;
518  width *= mmPerMapUnit;
519  markerSymbol->setSize( width );
520  }
521  if ( width < mSymbolWidth )
522  {
523  widthOffset = ( mSymbolWidth - width ) / 2.0;
524  }
525  if ( height < mSymbolHeight )
526  {
527  heightOffset = ( mSymbolHeight - height ) / 2.0;
528  }
529  }
530 
531  if ( p )
532  {
533  p->save();
534  p->translate( currentXPosition + widthOffset, currentYCoord + heightOffset );
535  p->scale( 1.0 / rasterScaleFactor, 1.0 / rasterScaleFactor );
536 
537  if ( markerSymbol && sizeInMapUnits )
538  {
540  }
541 
542  s->drawPreviewIcon( p, QSize( width * rasterScaleFactor, height * rasterScaleFactor ) );
543 
544  if ( markerSymbol && sizeInMapUnits )
545  {
547  markerSymbol->setSize( size );
548  }
549  p->restore();
550  }
551  currentXPosition += width;
552  currentXPosition += 2 * widthOffset;
553  symbolHeight = height + 2 * heightOffset;
554 }
555 
556 
558 {
559  //take layer list from map renderer (to have legend order)
560  if ( mComposition )
561  {
562  return mComposition->mapSettings().layers();
563  }
564  return QStringList();
565 }
566 
568 {
569  QgsDebugMsg( "Entered" );
570  adjustBoxSize();
571  update();
572 }
573 
575 {
576  rstyle( s ).setFont( f );
577 }
578 
580 {
581  rstyle( s ).setMargin( margin );
582 }
583 
585 {
586  rstyle( s ).setMargin( side, margin );
587 }
588 
590 {
592  adjustBoxSize();
593  update();
594 }
595 
596 bool QgsComposerLegend::writeXML( QDomElement& elem, QDomDocument & doc ) const
597 {
598  if ( elem.isNull() )
599  {
600  return false;
601  }
602 
603  QDomElement composerLegendElem = doc.createElement( "ComposerLegend" );
604  elem.appendChild( composerLegendElem );
605 
606  //write general properties
607  composerLegendElem.setAttribute( "title", mTitle );
608  composerLegendElem.setAttribute( "titleAlignment", QString::number(( int )mTitleAlignment ) );
609  composerLegendElem.setAttribute( "columnCount", QString::number( mColumnCount ) );
610  composerLegendElem.setAttribute( "splitLayer", QString::number( mSplitLayer ) );
611  composerLegendElem.setAttribute( "equalColumnWidth", QString::number( mEqualColumnWidth ) );
612 
613  composerLegendElem.setAttribute( "boxSpace", QString::number( mBoxSpace ) );
614  composerLegendElem.setAttribute( "columnSpace", QString::number( mColumnSpace ) );
615 
616  composerLegendElem.setAttribute( "symbolWidth", QString::number( mSymbolWidth ) );
617  composerLegendElem.setAttribute( "symbolHeight", QString::number( mSymbolHeight ) );
618  composerLegendElem.setAttribute( "wmsLegendWidth", QString::number( mWmsLegendWidth ) );
619  composerLegendElem.setAttribute( "wmsLegendHeight", QString::number( mWmsLegendHeight ) );
620  composerLegendElem.setAttribute( "wrapChar", mWrapChar );
621  composerLegendElem.setAttribute( "fontColor", mFontColor.name() );
622 
623  if ( mComposerMap )
624  {
625  composerLegendElem.setAttribute( "map", mComposerMap->id() );
626  }
627 
628  QDomElement composerLegendStyles = doc.createElement( "styles" );
629  composerLegendElem.appendChild( composerLegendStyles );
630 
631  style( QgsComposerLegendStyle::Title ).writeXML( "title", composerLegendStyles, doc );
632  style( QgsComposerLegendStyle::Group ).writeXML( "group", composerLegendStyles, doc );
633  style( QgsComposerLegendStyle::Subgroup ).writeXML( "subgroup", composerLegendStyles, doc );
634  style( QgsComposerLegendStyle::Symbol ).writeXML( "symbol", composerLegendStyles, doc );
635  style( QgsComposerLegendStyle::SymbolLabel ).writeXML( "symbolLabel", composerLegendStyles, doc );
636 
637  //write model properties
638  mLegendModel.writeXML( composerLegendElem, doc );
639 
640  return _writeXML( composerLegendElem, doc );
641 }
642 
643 bool QgsComposerLegend::readXML( const QDomElement& itemElem, const QDomDocument& doc )
644 {
645  if ( itemElem.isNull() )
646  {
647  return false;
648  }
649 
650  //read general properties
651  mTitle = itemElem.attribute( "title" );
652  if ( !itemElem.attribute( "titleAlignment" ).isEmpty() )
653  {
654  mTitleAlignment = ( Qt::AlignmentFlag )itemElem.attribute( "titleAlignment" ).toInt();
655  }
656  mColumnCount = itemElem.attribute( "columnCount", "1" ).toInt();
657  if ( mColumnCount < 1 ) mColumnCount = 1;
658  mSplitLayer = itemElem.attribute( "splitLayer", "0" ).toInt() == 1;
659  mEqualColumnWidth = itemElem.attribute( "equalColumnWidth", "0" ).toInt() == 1;
660 
661  QDomNodeList stylesNodeList = itemElem.elementsByTagName( "styles" );
662  if ( stylesNodeList.size() > 0 )
663  {
664  QDomNode stylesNode = stylesNodeList.at( 0 );
665  for ( int i = 0; i < stylesNode.childNodes().size(); i++ )
666  {
667  QDomElement styleElem = stylesNode.childNodes().at( i ).toElement();
669  style.readXML( styleElem, doc );
670  QString name = styleElem.attribute( "name" );
672  if ( name == "title" ) s = QgsComposerLegendStyle::Title;
673  else if ( name == "group" ) s = QgsComposerLegendStyle::Group;
674  else if ( name == "subgroup" ) s = QgsComposerLegendStyle::Subgroup;
675  else if ( name == "symbol" ) s = QgsComposerLegendStyle::Symbol;
676  else if ( name == "symbolLabel" ) s = QgsComposerLegendStyle::SymbolLabel;
677  else continue;
678  setStyle( s, style );
679  }
680  }
681 
682  //font color
683  mFontColor.setNamedColor( itemElem.attribute( "fontColor", "#000000" ) );
684 
685  //spaces
686  mBoxSpace = itemElem.attribute( "boxSpace", "2.0" ).toDouble();
687  mColumnSpace = itemElem.attribute( "columnSpace", "2.0" ).toDouble();
688 
689  mSymbolWidth = itemElem.attribute( "symbolWidth", "7.0" ).toDouble();
690  mSymbolHeight = itemElem.attribute( "symbolHeight", "14.0" ).toDouble();
691  mWmsLegendWidth = itemElem.attribute( "wmsLegendWidth", "50" ).toDouble();
692  mWmsLegendHeight = itemElem.attribute( "wmsLegendHeight", "25" ).toDouble();
693 
694  mWrapChar = itemElem.attribute( "wrapChar" );
695 
696  //composer map
697  if ( !itemElem.attribute( "map" ).isEmpty() )
698  {
699  mComposerMap = mComposition->getComposerMapById( itemElem.attribute( "map" ).toInt() );
700  }
701 
702  //read model properties
703  QDomNodeList modelNodeList = itemElem.elementsByTagName( "Model" );
704  if ( modelNodeList.size() > 0 )
705  {
706  QDomElement modelElem = modelNodeList.at( 0 ).toElement();
707  mLegendModel.readXML( modelElem, doc );
708  }
709 
710  //restore general composer item properties
711  QDomNodeList composerItemList = itemElem.elementsByTagName( "ComposerItem" );
712  if ( composerItemList.size() > 0 )
713  {
714  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
715  _readXML( composerItemElem, doc );
716  }
717 
718  // < 2.0 projects backward compatibility >>>>>
719  //title font
720  QString titleFontString = itemElem.attribute( "titleFont" );
721  if ( !titleFontString.isEmpty() )
722  {
723  rstyle( QgsComposerLegendStyle::Title ).rfont().fromString( titleFontString );
724  }
725  //group font
726  QString groupFontString = itemElem.attribute( "groupFont" );
727  if ( !groupFontString.isEmpty() )
728  {
729  rstyle( QgsComposerLegendStyle::Group ).rfont().fromString( groupFontString );
730  }
731 
732  //layer font
733  QString layerFontString = itemElem.attribute( "layerFont" );
734  if ( !layerFontString.isEmpty() )
735  {
736  rstyle( QgsComposerLegendStyle::Subgroup ).rfont().fromString( layerFontString );
737  }
738  //item font
739  QString itemFontString = itemElem.attribute( "itemFont" );
740  if ( !itemFontString.isEmpty() )
741  {
742  rstyle( QgsComposerLegendStyle::SymbolLabel ).rfont().fromString( itemFontString );
743  }
744 
745  if ( !itemElem.attribute( "groupSpace" ).isEmpty() )
746  {
747  rstyle( QgsComposerLegendStyle::Group ).setMargin( QgsComposerLegendStyle::Top, itemElem.attribute( "groupSpace", "3.0" ).toDouble() );
748  }
749  if ( !itemElem.attribute( "layerSpace" ).isEmpty() )
750  {
751  rstyle( QgsComposerLegendStyle::Subgroup ).setMargin( QgsComposerLegendStyle::Top, itemElem.attribute( "layerSpace", "3.0" ).toDouble() );
752  }
753  if ( !itemElem.attribute( "symbolSpace" ).isEmpty() )
754  {
755  rstyle( QgsComposerLegendStyle::Symbol ).setMargin( QgsComposerLegendStyle::Top, itemElem.attribute( "symbolSpace", "2.0" ).toDouble() );
756  rstyle( QgsComposerLegendStyle::SymbolLabel ).setMargin( QgsComposerLegendStyle::Top, itemElem.attribute( "symbolSpace", "2.0" ).toDouble() );
757  }
758  // <<<<<<< < 2.0 projects backward compatibility
759 
760  emit itemChanged();
761  return true;
762 }
763 
765 {
766  mComposerMap = map;
767  if ( map )
768  {
769  QObject::connect( map, SIGNAL( destroyed( QObject* ) ), this, SLOT( invalidateCurrentMap() ) );
770  }
771 }
772 
774 {
775  if ( mComposerMap )
776  {
777  disconnect( mComposerMap, SIGNAL( destroyed( QObject* ) ), this, SLOT( invalidateCurrentMap() ) );
778  }
779  mComposerMap = 0;
780 }
781 
782 QStringList QgsComposerLegend::splitStringForWrapping( QString stringToSplt )
783 {
784  QStringList list;
785  // If the string contains nothing then just return the string without spliting.
786  if ( mWrapChar.count() == 0 )
787  list << stringToSplt;
788  else
789  list = stringToSplt.split( mWrapChar );
790  return list;
791 }
792 
793 QList<QgsComposerLegend::Atom> QgsComposerLegend::createAtomList( QStandardItem* rootItem, bool splitLayer )
794 {
795  QList<Atom> atoms;
796 
797  if ( !rootItem ) return atoms;
798 
799  Atom atom;
800 
801  for ( int i = 0; i < rootItem->rowCount(); i++ )
802  {
803  QStandardItem* currentLayerItem = rootItem->child( i );
804  QgsComposerLegendItem* currentLegendItem = dynamic_cast<QgsComposerLegendItem*>( currentLayerItem );
805  if ( !currentLegendItem ) continue;
806 
807  QgsComposerLegendItem::ItemType type = currentLegendItem->itemType();
808  if ( type == QgsComposerLegendItem::GroupItem )
809  {
810  // Group subitems
811  QList<Atom> groupAtoms = createAtomList( currentLayerItem, splitLayer );
812 
813  Nucleon nucleon;
814  nucleon.item = currentLegendItem;
815  nucleon.size = drawGroupItemTitle( dynamic_cast<QgsComposerGroupItem*>( currentLegendItem ) );
816 
817  if ( groupAtoms.size() > 0 )
818  {
819  // Add internal space between this group title and the next nucleon
820  groupAtoms[0].size.rheight() += spaceAboveAtom( groupAtoms[0] );
821  // Prepend this group title to the first atom
822  groupAtoms[0].nucleons.prepend( nucleon );
823  groupAtoms[0].size.rheight() += nucleon.size.height();
824  groupAtoms[0].size.rwidth() = qMax( nucleon.size.width(), groupAtoms[0].size.width() );
825  }
826  else
827  {
828  // no subitems, append new atom
829  Atom atom;
830  atom.nucleons.append( nucleon );
831  atom.size.rwidth() += nucleon.size.width();
832  atom.size.rheight() += nucleon.size.height();
833  atom.size.rwidth() = qMax( nucleon.size.width(), atom.size.width() );
834  groupAtoms.append( atom );
835  }
836  atoms.append( groupAtoms );
837  }
838  else if ( type == QgsComposerLegendItem::LayerItem )
839  {
840  Atom atom;
841 
842  if ( currentLegendItem->style() != QgsComposerLegendStyle::Hidden )
843  {
844  Nucleon nucleon;
845  nucleon.item = currentLegendItem;
846  nucleon.size = drawLayerItemTitle( dynamic_cast<QgsComposerLayerItem*>( currentLegendItem ) );
847  atom.nucleons.append( nucleon );
848  atom.size.rwidth() = nucleon.size.width();
849  atom.size.rheight() = nucleon.size.height();
850  }
851 
852  QList<Atom> layerAtoms;
853 
854  for ( int j = 0; j < currentLegendItem->rowCount(); j++ )
855  {
856  QgsComposerLegendItem * symbolItem = dynamic_cast<QgsComposerLegendItem*>( currentLegendItem->child( j, 0 ) );
857  if ( !symbolItem ) continue;
858 
859  Nucleon symbolNucleon = drawSymbolItem( symbolItem );
860 
861  if ( !mSplitLayer || j == 0 )
862  {
863  // append to layer atom
864  // the width is not correct at this moment, we must align all symbol labels
865  atom.size.rwidth() = qMax( symbolNucleon.size.width(), atom.size.width() );
866  // Add symbol space only if there is already title or another item above
867  if ( atom.nucleons.size() > 0 )
868  {
869  // TODO: for now we keep Symbol and SymbolLabel Top margin in sync
871  }
872  atom.size.rheight() += symbolNucleon.size.height();
873  atom.nucleons.append( symbolNucleon );
874  }
875  else
876  {
877  Atom symbolAtom;
878  symbolAtom.nucleons.append( symbolNucleon );
879  symbolAtom.size.rwidth() = symbolNucleon.size.width();
880  symbolAtom.size.rheight() = symbolNucleon.size.height();
881  layerAtoms.append( symbolAtom );
882  }
883  }
884  layerAtoms.prepend( atom );
885  atoms.append( layerAtoms );
886  }
887  }
888 
889  return atoms;
890 }
891 
892 // Draw atom and expand its size (using actual nucleons labelXOffset)
893 QSizeF QgsComposerLegend::drawAtom( Atom atom, QPainter* painter, QPointF point )
894 {
895  bool first = true;
896  QSizeF size = QSizeF( atom.size );
897  foreach ( Nucleon nucleon, atom.nucleons )
898  {
899  QgsComposerLegendItem* item = nucleon.item;
900  //QgsDebugMsg( "text: " + item->text() );
901  if ( !item ) continue;
903  if ( type == QgsComposerLegendItem::GroupItem )
904  {
905  QgsComposerGroupItem* groupItem = dynamic_cast<QgsComposerGroupItem*>( item );
906  if ( !groupItem ) continue;
907  if ( groupItem->style() != QgsComposerLegendStyle::Hidden )
908  {
909  if ( !first )
910  {
911  point.ry() += style( groupItem->style() ).margin( QgsComposerLegendStyle::Top );
912  }
913  drawGroupItemTitle( groupItem, painter, point );
914  }
915  }
916  else if ( type == QgsComposerLegendItem::LayerItem )
917  {
918  QgsComposerLayerItem* layerItem = dynamic_cast<QgsComposerLayerItem*>( item );
919  if ( !layerItem ) continue;
920  if ( layerItem->style() != QgsComposerLegendStyle::Hidden )
921  {
922  if ( !first )
923  {
924  point.ry() += style( layerItem->style() ).margin( QgsComposerLegendStyle::Top );
925  }
926  drawLayerItemTitle( layerItem, painter, point );
927  }
928  }
929  else if ( type == QgsComposerLegendItem::SymbologyV2Item ||
931  {
932  if ( !first )
933  {
935  }
936  double labelXOffset = nucleon.labelXOffset;
937  Nucleon symbolNucleon = drawSymbolItem( item, painter, point, labelXOffset );
938  // expand width, it may be wider because of labelXOffset
939  size.rwidth() = qMax( symbolNucleon.size.width(), size.width() );
940  }
941  point.ry() += nucleon.size.height();
942  first = false;
943  }
944  return size;
945 }
946 
948 {
949  if ( atom.nucleons.size() == 0 ) return 0;
950 
951  Nucleon nucleon = atom.nucleons.first();
952 
953  QgsComposerLegendItem* item = nucleon.item;
954  if ( !item ) return 0;
955 
957  switch ( type )
958  {
960  return style( item->style() ).margin( QgsComposerLegendStyle::Top );
961  break;
963  return style( item->style() ).margin( QgsComposerLegendStyle::Top );
964  break;
967  // TODO: use Symbol or SymbolLabel Top margin
969  break;
970  default:
971  break;
972  }
973  return 0;
974 }
975 
976 void QgsComposerLegend::setColumns( QList<Atom>& atomList )
977 {
978  if ( mColumnCount == 0 ) return;
979 
980  // Divide atoms to columns
981  double totalHeight = 0;
982  // bool first = true;
983  qreal maxAtomHeight = 0;
984  foreach ( Atom atom, atomList )
985  {
986  //if ( !first )
987  //{
988  totalHeight += spaceAboveAtom( atom );
989  //}
990  totalHeight += atom.size.height();
991  maxAtomHeight = qMax( atom.size.height(), maxAtomHeight );
992  // first = false;
993  }
994 
995  // We know height of each atom and we have to split them into columns
996  // minimizing max column height. It is sort of bin packing problem, NP-hard.
997  // We are using simple heuristic, brute fore appeared to be to slow,
998  // the number of combinations is N = n!/(k!*(n-k)!) where n = atomsCount-1
999  // and k = columnsCount-1
1000 
1001  double avgColumnHeight = totalHeight / mColumnCount;
1002  int currentColumn = 0;
1003  int currentColumnAtomCount = 0; // number of atoms in current column
1004  double currentColumnHeight = 0;
1005  double maxColumnHeight = 0;
1006  double closedColumnsHeight = 0;
1007  // first = true; // first in column
1008  for ( int i = 0; i < atomList.size(); i++ )
1009  {
1010  Atom atom = atomList[i];
1011  double currentHeight = currentColumnHeight;
1012  //if ( !first )
1013  //{
1014  currentHeight += spaceAboveAtom( atom );
1015  //}
1016  currentHeight += atom.size.height();
1017 
1018  // Recalc average height for remaining columns including current
1019  avgColumnHeight = ( totalHeight - closedColumnsHeight ) / ( mColumnCount - currentColumn );
1020  if (( currentHeight - avgColumnHeight ) > atom.size.height() / 2 // center of current atom is over average height
1021  && currentColumnAtomCount > 0 // do not leave empty column
1022  && currentHeight > maxAtomHeight // no sense to make smaller columns than max atom height
1023  && currentHeight > maxColumnHeight // no sense to make smaller columns than max column already created
1024  && currentColumn < mColumnCount - 1 ) // must not exceed max number of columns
1025  {
1026  // New column
1027  currentColumn++;
1028  currentColumnAtomCount = 0;
1029  closedColumnsHeight += currentColumnHeight;
1030  currentColumnHeight = atom.size.height();
1031  }
1032  else
1033  {
1034  currentColumnHeight = currentHeight;
1035  }
1036  atomList[i].column = currentColumn;
1037  currentColumnAtomCount++;
1038  maxColumnHeight = qMax( currentColumnHeight, maxColumnHeight );
1039 
1040  // first = false;
1041  }
1042 
1043  // Alling labels of symbols for each layr/column to the same labelXOffset
1044  QMap<QString, qreal> maxSymbolWidth;
1045  for ( int i = 0; i < atomList.size(); i++ )
1046  {
1047  for ( int j = 0; j < atomList[i].nucleons.size(); j++ )
1048  {
1049  QgsComposerLegendItem* item = atomList[i].nucleons[j].item;
1050  if ( !item ) continue;
1054  {
1055  QString key = QString( "%1-%2" ).arg(( qulonglong )item->parent() ).arg( atomList[i].column );
1056  maxSymbolWidth[key] = qMax( atomList[i].nucleons[j].symbolSize.width(), maxSymbolWidth[key] );
1057  }
1058  }
1059  }
1060  for ( int i = 0; i < atomList.size(); i++ )
1061  {
1062  for ( int j = 0; j < atomList[i].nucleons.size(); j++ )
1063  {
1064  QgsComposerLegendItem* item = atomList[i].nucleons[j].item;
1065  if ( !item ) continue;
1069  {
1070  QString key = QString( "%1-%2" ).arg(( qulonglong )item->parent() ).arg( atomList[i].column );
1073  atomList[i].nucleons[j].labelXOffset = maxSymbolWidth[key] + space;
1074  atomList[i].nucleons[j].size.rwidth() = maxSymbolWidth[key] + space + atomList[i].nucleons[j].labelSize.width();
1075  }
1076  }
1077  }
1078 }
1079 
1080 
Base class for all map layer types.
Definition: qgsmaplayer.h:46
QgsComposerLegendStyle style(QgsComposerLegendStyle::Style s) const
Returns style.
double mWmsLegendWidth
Width of WMS legendGraphic pixmap.
double fontHeightCharacterMM(const QFont &font, const QChar &c) const
Returns the font height of a character in millimeters.
double mColumnSpace
Space between columns.
Qt::AlignmentFlag mTitleAlignment
Title alignment, one of Qt::AlignLeft, Qt::AlignHCenter, Qt::AlignRight)
void readXML(const QDomElement &elem, const QDomDocument &doc)
double mapUnitsToMM() const
Returns the conversion factor map units -> mm.
void setMargin(Side side, double margin)
double mWmsLegendHeight
Height of WMS legendGraphic pixmap.
#define QgsDebugMsg(str)
Definition: qgslogger.h:36
bool writeXML(QDomElement &elem, QDomDocument &doc) const
stores state in Dom node
void drawPreviewIcon(QPainter *painter, QSize size)
const QgsMapSettings & mapSettings() const
Return setting of QGIS map canvas.
Nucleon is either group title, layer title or layer child item.
A item that forms part of a map composition.
QgsLegendModel mLegendModel
virtual void drawFrame(QPainter *p)
Draw black frame around item.
int mColumnCount
Number of legend columns.
void updateLegend()
Updates the model and all legend entries.
double mlineSpacing
Spacing between lines when wrapped.
QStringList splitStringForWrapping(QString stringToSplt)
Splits a string using the wrap char taking into account handling empty wrap char which means no wrapp...
void adjustBoxSize()
Sets item box to the whole content.
QSizeF drawLayerItemTitle(QgsComposerLayerItem *layerItem, QPainter *painter=0, QPointF point=QPointF())
Draws a layer item and all subitems.
void itemChanged()
Used e.g.
bool _readXML(const QDomElement &itemElem, const QDomDocument &doc)
Reads parameter that are not subclass specific in document.
double fontDescentMillimeters(const QFont &font) const
Returns the font ascent in Millimeters (considers upscaling and downscaling with FONT_WORKAROUND_SCAL...
QSizeF drawGroupItemTitle(QgsComposerGroupItem *groupItem, QPainter *painter=0, QPointF point=QPointF())
Draws a group item and all subitems Returns list of sizes of layers and groups including this group...
QList< Atom > createAtomList(QStandardItem *rootItem, bool splitLayer)
Create list of atoms according to current layer splitting mode.
double mSymbolHeight
Height of symbol icon.
const QgsComposerMap * mComposerMap
Reference to map (because symbols are sometimes in map units)
void setLayerSet(const QStringList &layerIds, double scaleDenominator=-1, QString rule="")
virtual void drawSelectionBoxes(QPainter *p)
Draw selection boxes around item.
double textWidthMillimeters(const QFont &font, const QString &text) const
Returns the font width in millimeters (considers upscaling and downscaling with FONT_WORKAROUND_SCALE...
QStringList layerIdList() const
Helper function that lists ids of layers contained in map canvas.
void setComposerMap(const QgsComposerMap *map)
void synchronizeWithModel()
Data changed.
void setStyleFont(QgsComposerLegendStyle::Style s, const QFont &f)
Set style font.
void setSize(double size)
void drawText(QPainter *p, double x, double y, const QString &text, const QFont &font) const
Draws Text.
QSizeF drawTitle(QPainter *painter=0, QPointF point=QPointF(), Qt::AlignmentFlag halignment=Qt::AlignLeft)
Draws title in the legend using the title font and the specified alignment If no painter is specified...
bool writeXML(QDomElement &composerLegendElem, QDomDocument &doc) const
QgsComposition * mComposition
void setColumns(QList< Atom > &atomList)
Divide atoms to columns and set columns on atoms.
Graphics scene for map printing.
QFont styleFont(QgsComposerLegendStyle::Style s) const
Abstract base class for the legend item types.
Object representing map window.
QSizeF paintAndDetermineSize(QPainter *painter)
Paints the legend and calculates its size.
void invalidateCurrentMap()
Sets mCompositionMap to 0 if the map is deleted.
double rasterScaleFactor
Definition: qgssvgcache.cpp:80
bool readXML(const QDomElement &legendModelElem, const QDomDocument &doc)
QgsComposerLegendStyle & rstyle(QgsComposerLegendStyle::Style s)
Returns reference to modifiable style.
void writeXML(QString name, QDomElement &elem, QDomDocument &doc) const
int id() const
Get identification number.
Composer legend components style.
static QgsMapLayerRegistry * instance()
Returns the instance pointer, creating the object on the first call.
bool _writeXML(QDomElement &itemElem, QDomDocument &doc) const
Writes parameter that are not subclass specific in document.
void setStyleMargin(QgsComposerLegendStyle::Style s, double margin)
Set style margin.
qreal mBoxSpace
Space between item box and contents.
Nucleon drawSymbolItem(QgsComposerLegendItem *symbolItem, QPainter *painter=0, QPointF point=QPointF(), double labelXOffset=0.)
virtual void drawBackground(QPainter *p)
Draw background.
double spaceAboveAtom(Atom atom)
QgsComposerLegendStyle::Style style() const
virtual void setSceneRect(const QRectF &rectangle)
Sets this items bound in scene coordinates such that 1 item size units corresponds to 1 scene size un...
virtual ItemType itemType() const =0
QgsSymbolV2::OutputUnit outputUnit() const
Definition: qgssymbolv2.cpp:63
void setStyle(QgsComposerLegendStyle::Style s, const QgsComposerLegendStyle style)
bool mEqualColumnWidth
Use the same width (maximum) for all columns.
QSizeF drawAtom(Atom atom, QPainter *painter=0, QPointF point=QPointF())
Draw atom and return its actual size, the atom is drawn with the space above it so that first atoms i...
QStringList layers() const
QgsMapLayer * mapLayer(QString theLayerId)
Retrieve a pointer to a loaded layer by id.
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget)
Reimplementation of QCanvasItem::paint.
Atom is indivisible set (indivisible into more columns).
QgsComposerLegendItem * item
bool mSplitLayer
Allow splitting layers into multiple columns.
double size
Definition: qgssvgcache.cpp:77
const QgsComposerMap * getComposerMapById(int id) const
Returns the composer map with specified id.
virtual int type() const
return correct graphics item type.
double mSymbolWidth
Width of symbol icon.
bool readXML(const QDomElement &itemElem, const QDomDocument &doc)
sets state from Dom document
void setOutputUnit(QgsSymbolV2::OutputUnit u)
Definition: qgssymbolv2.cpp:86
double fontAscentMillimeters(const QFont &font) const
Returns the font ascent in Millimeters (considers upscaling and downscaling with FONT_WORKAROUND_SCAL...
void drawSymbolV2(QPainter *p, QgsSymbolV2 *s, double currentYCoord, double &currentXPosition, double &symbolHeight) const
Draws a symbol at the current y position and returns the new x position.
#define tr(sourceText)
void setFont(const QFont &font)