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