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