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