QGIS API Documentation  2.5.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
qgslegendrenderer.cpp
Go to the documentation of this file.
1 #include "qgslegendrenderer.h"
2 
4 #include "qgslegendmodel.h"
5 #include "qgsmaplayerregistry.h"
6 #include "qgssymbolv2.h"
7 #include "qgsvectorlayer.h"
8 
9 #include <QPainter>
10 
11 #define FONT_WORKAROUND_SCALE 10 //scale factor for upscaling fontsize and downscaling painter
12 
13 
15  : mLegendModel( legendModel )
16  , mSettings( settings )
17 {
18 }
19 
21 {
22  return paintAndDetermineSize( 0 );
23 }
24 
25 void QgsLegendRenderer::drawLegend( QPainter* painter )
26 {
27  paintAndDetermineSize( painter );
28 }
29 
30 
31 QSizeF QgsLegendRenderer::paintAndDetermineSize( QPainter* painter )
32 {
33  QSizeF size( 0, 0 );
34  QStandardItem* rootItem = mLegendModel->invisibleRootItem();
35  if ( !rootItem ) return size;
36 
37  QList<Atom> atomList = createAtomList( rootItem, mSettings.splitLayer() );
38 
39  setColumns( atomList );
40 
41  qreal maxColumnWidth = 0;
43  {
44  foreach ( Atom atom, atomList )
45  {
46  maxColumnWidth = qMax( atom.size.width(), maxColumnWidth );
47  }
48  }
49 
50  //calculate size of title
51  QSizeF titleSize = drawTitle();
52  //add title margin to size of title text
53  titleSize.rwidth() += mSettings.boxSpace() * 2.0;
55 
56  QPointF point( mSettings.boxSpace(), columnTop );
57  bool firstInColumn = true;
58  double columnMaxHeight = 0;
59  qreal columnWidth = 0;
60  int column = 0;
61  foreach ( Atom atom, atomList )
62  {
63  if ( atom.column > column )
64  {
65  // Switch to next column
67  {
68  point.rx() += mSettings.columnSpace() + maxColumnWidth;
69  }
70  else
71  {
72  point.rx() += mSettings.columnSpace() + columnWidth;
73  }
74  point.ry() = columnTop;
75  columnWidth = 0;
76  column++;
77  firstInColumn = true;
78  }
79  if ( !firstInColumn )
80  {
81  point.ry() += spaceAboveAtom( atom );
82  }
83 
84  QSizeF atomSize = drawAtom( atom, painter, point );
85  columnWidth = qMax( atomSize.width(), columnWidth );
86 
87  point.ry() += atom.size.height();
88  columnMaxHeight = qMax( point.y() - columnTop, columnMaxHeight );
89 
90  firstInColumn = false;
91  }
92  point.rx() += columnWidth + mSettings.boxSpace();
93 
94  size.rheight() = columnTop + columnMaxHeight + mSettings.boxSpace();
95  size.rwidth() = point.x();
96  if ( !mSettings.title().isEmpty() )
97  {
98  size.rwidth() = qMax( titleSize.width(), size.width() );
99  }
100 
101  // override the size if it was set by the user
102  if ( mLegendSize.isValid() )
103  {
104  qreal w = qMax( size.width(), mLegendSize.width() );
105  qreal h = qMax( size.height(), mLegendSize.height() );
106  size = QSizeF( w, h );
107  }
108 
109  // Now we have set the correct total item width and can draw the title centered
110  if ( !mSettings.title().isEmpty() )
111  {
112  if ( mSettings.titleAlignment() == Qt::AlignLeft )
113  {
114  point.rx() = mSettings.boxSpace();
115  }
116  else if ( mSettings.titleAlignment() == Qt::AlignHCenter )
117  {
118  point.rx() = size.width() / 2;
119  }
120  else
121  {
122  point.rx() = size.width() - mSettings.boxSpace();
123  }
124  point.ry() = mSettings.boxSpace();
125  drawTitle( painter, point, mSettings.titleAlignment(), size.width() );
126  }
127 
128  return size;
129 }
130 
131 
132 
133 
134 
135 QList<QgsLegendRenderer::Atom> QgsLegendRenderer::createAtomList( QStandardItem* rootItem, bool splitLayer )
136 {
137  QList<Atom> atoms;
138 
139  if ( !rootItem ) return atoms;
140 
141  Atom atom;
142 
143  for ( int i = 0; i < rootItem->rowCount(); i++ )
144  {
145  QStandardItem* currentLayerItem = rootItem->child( i );
146  QgsComposerLegendItem* currentLegendItem = dynamic_cast<QgsComposerLegendItem*>( currentLayerItem );
147  if ( !currentLegendItem ) continue;
148 
149  QgsComposerLegendItem::ItemType type = currentLegendItem->itemType();
150  if ( type == QgsComposerLegendItem::GroupItem )
151  {
152  // Group subitems
153  QList<Atom> groupAtoms = createAtomList( currentLayerItem, splitLayer );
154 
155  Nucleon nucleon;
156  nucleon.item = currentLegendItem;
157  nucleon.size = drawGroupItemTitle( dynamic_cast<QgsComposerGroupItem*>( currentLegendItem ) );
158 
159  if ( groupAtoms.size() > 0 )
160  {
161  // Add internal space between this group title and the next nucleon
162  groupAtoms[0].size.rheight() += spaceAboveAtom( groupAtoms[0] );
163  // Prepend this group title to the first atom
164  groupAtoms[0].nucleons.prepend( nucleon );
165  groupAtoms[0].size.rheight() += nucleon.size.height();
166  groupAtoms[0].size.rwidth() = qMax( nucleon.size.width(), groupAtoms[0].size.width() );
167  }
168  else
169  {
170  // no subitems, append new atom
171  Atom atom;
172  atom.nucleons.append( nucleon );
173  atom.size.rwidth() += nucleon.size.width();
174  atom.size.rheight() += nucleon.size.height();
175  atom.size.rwidth() = qMax( nucleon.size.width(), atom.size.width() );
176  groupAtoms.append( atom );
177  }
178  atoms.append( groupAtoms );
179  }
180  else if ( type == QgsComposerLegendItem::LayerItem )
181  {
182  Atom atom;
183 
184  if ( currentLegendItem->style() != QgsComposerLegendStyle::Hidden )
185  {
186  Nucleon nucleon;
187  nucleon.item = currentLegendItem;
188  nucleon.size = drawLayerItemTitle( dynamic_cast<QgsComposerLayerItem*>( currentLegendItem ) );
189  atom.nucleons.append( nucleon );
190  atom.size.rwidth() = nucleon.size.width();
191  atom.size.rheight() = nucleon.size.height();
192  }
193 
194  QList<Atom> layerAtoms;
195 
196  for ( int j = 0; j < currentLegendItem->rowCount(); j++ )
197  {
198  QgsComposerLegendItem * symbolItem = dynamic_cast<QgsComposerLegendItem*>( currentLegendItem->child( j, 0 ) );
199  if ( !symbolItem ) continue;
200 
201  Nucleon symbolNucleon = drawSymbolItem( symbolItem );
202 
203  if ( !mSettings.splitLayer() || j == 0 )
204  {
205  // append to layer atom
206  // the width is not correct at this moment, we must align all symbol labels
207  atom.size.rwidth() = qMax( symbolNucleon.size.width(), atom.size.width() );
208  // Add symbol space only if there is already title or another item above
209  if ( atom.nucleons.size() > 0 )
210  {
211  // TODO: for now we keep Symbol and SymbolLabel Top margin in sync
213  }
214  atom.size.rheight() += symbolNucleon.size.height();
215  atom.nucleons.append( symbolNucleon );
216  }
217  else
218  {
219  Atom symbolAtom;
220  symbolAtom.nucleons.append( symbolNucleon );
221  symbolAtom.size.rwidth() = symbolNucleon.size.width();
222  symbolAtom.size.rheight() = symbolNucleon.size.height();
223  layerAtoms.append( symbolAtom );
224  }
225  }
226  layerAtoms.prepend( atom );
227  atoms.append( layerAtoms );
228  }
229  }
230 
231  return atoms;
232 }
233 
234 
235 
236 void QgsLegendRenderer::setColumns( QList<Atom>& atomList )
237 {
238  if ( mSettings.columnCount() == 0 ) return;
239 
240  // Divide atoms to columns
241  double totalHeight = 0;
242  // bool first = true;
243  qreal maxAtomHeight = 0;
244  foreach ( Atom atom, atomList )
245  {
246  //if ( !first )
247  //{
248  totalHeight += spaceAboveAtom( atom );
249  //}
250  totalHeight += atom.size.height();
251  maxAtomHeight = qMax( atom.size.height(), maxAtomHeight );
252  // first = false;
253  }
254 
255  // We know height of each atom and we have to split them into columns
256  // minimizing max column height. It is sort of bin packing problem, NP-hard.
257  // We are using simple heuristic, brute fore appeared to be to slow,
258  // the number of combinations is N = n!/(k!*(n-k)!) where n = atomsCount-1
259  // and k = columnsCount-1
260 
261  double avgColumnHeight = totalHeight / mSettings.columnCount();
262  int currentColumn = 0;
263  int currentColumnAtomCount = 0; // number of atoms in current column
264  double currentColumnHeight = 0;
265  double maxColumnHeight = 0;
266  double closedColumnsHeight = 0;
267  // first = true; // first in column
268  for ( int i = 0; i < atomList.size(); i++ )
269  {
270  Atom atom = atomList[i];
271  double currentHeight = currentColumnHeight;
272  //if ( !first )
273  //{
274  currentHeight += spaceAboveAtom( atom );
275  //}
276  currentHeight += atom.size.height();
277 
278  // Recalc average height for remaining columns including current
279  avgColumnHeight = ( totalHeight - closedColumnsHeight ) / ( mSettings.columnCount() - currentColumn );
280  if (( currentHeight - avgColumnHeight ) > atom.size.height() / 2 // center of current atom is over average height
281  && currentColumnAtomCount > 0 // do not leave empty column
282  && currentHeight > maxAtomHeight // no sense to make smaller columns than max atom height
283  && currentHeight > maxColumnHeight // no sense to make smaller columns than max column already created
284  && currentColumn < mSettings.columnCount() - 1 ) // must not exceed max number of columns
285  {
286  // New column
287  currentColumn++;
288  currentColumnAtomCount = 0;
289  closedColumnsHeight += currentColumnHeight;
290  currentColumnHeight = atom.size.height();
291  }
292  else
293  {
294  currentColumnHeight = currentHeight;
295  }
296  atomList[i].column = currentColumn;
297  currentColumnAtomCount++;
298  maxColumnHeight = qMax( currentColumnHeight, maxColumnHeight );
299 
300  // first = false;
301  }
302 
303  // Alling labels of symbols for each layr/column to the same labelXOffset
304  QMap<QString, qreal> maxSymbolWidth;
305  for ( int i = 0; i < atomList.size(); i++ )
306  {
307  for ( int j = 0; j < atomList[i].nucleons.size(); j++ )
308  {
309  QgsComposerLegendItem* item = atomList[i].nucleons[j].item;
310  if ( !item ) continue;
314  {
315  QString key = QString( "%1-%2" ).arg(( qulonglong )item->parent() ).arg( atomList[i].column );
316  maxSymbolWidth[key] = qMax( atomList[i].nucleons[j].symbolSize.width(), maxSymbolWidth[key] );
317  }
318  }
319  }
320  for ( int i = 0; i < atomList.size(); i++ )
321  {
322  for ( int j = 0; j < atomList[i].nucleons.size(); j++ )
323  {
324  QgsComposerLegendItem* item = atomList[i].nucleons[j].item;
325  if ( !item ) continue;
329  {
330  QString key = QString( "%1-%2" ).arg(( qulonglong )item->parent() ).arg( atomList[i].column );
333  atomList[i].nucleons[j].labelXOffset = maxSymbolWidth[key] + space;
334  atomList[i].nucleons[j].size.rwidth() = maxSymbolWidth[key] + space + atomList[i].nucleons[j].labelSize.width();
335  }
336  }
337  }
338 }
339 
340 
341 
342 QSizeF QgsLegendRenderer::drawTitle( QPainter* painter, QPointF point, Qt::AlignmentFlag halignment, double legendWidth )
343 {
344  QSizeF size( 0, 0 );
345  if ( mSettings.title().isEmpty() )
346  {
347  return size;
348  }
349 
350  QStringList lines = splitStringForWrapping( mSettings.title() );
351  double y = point.y();
352 
353  if ( painter )
354  {
355  painter->setPen( mSettings.fontColor() );
356  }
357 
358  //calculate width and left pos of rectangle to draw text into
359  double textBoxWidth;
360  double textBoxLeft;
361  switch ( halignment )
362  {
363  case Qt::AlignHCenter:
364  textBoxWidth = ( qMin( point.x(), legendWidth - point.x() ) - mSettings.boxSpace() ) * 2.0;
365  textBoxLeft = point.x() - textBoxWidth / 2.;
366  break;
367  case Qt::AlignRight:
368  textBoxLeft = mSettings.boxSpace();
369  textBoxWidth = point.x() - mSettings.boxSpace();
370  break;
371  case Qt::AlignLeft:
372  default:
373  textBoxLeft = point.x();
374  textBoxWidth = legendWidth - point.x() - mSettings.boxSpace();
375  break;
376  }
377 
378  QFont titleFont = mSettings.style( QgsComposerLegendStyle::Title ).font();
379 
380  for ( QStringList::Iterator titlePart = lines.begin(); titlePart != lines.end(); ++titlePart )
381  {
382  //last word is not drawn if rectangle width is exactly text width, so add 1
383  //TODO - correctly calculate size of italicized text, since QFontMetrics does not
384  qreal width = textWidthMillimeters( titleFont, *titlePart ) + 1;
385  qreal height = fontAscentMillimeters( titleFont ) + fontDescentMillimeters( titleFont );
386 
387  QRectF r( textBoxLeft, y, textBoxWidth, height );
388 
389  if ( painter )
390  {
391  drawText( painter, r, *titlePart, titleFont, halignment, Qt::AlignVCenter, Qt::TextDontClip );
392  }
393 
394  //update max width of title
395  size.rwidth() = qMax( width, size.rwidth() );
396 
397  y += height;
398  if ( titlePart != lines.end() )
399  {
400  y += mSettings.lineSpacing();
401  }
402  }
403  size.rheight() = y - point.y();
404 
405  return size;
406 }
407 
408 
410 {
411  if ( atom.nucleons.size() == 0 ) return 0;
412 
413  Nucleon nucleon = atom.nucleons.first();
414 
415  QgsComposerLegendItem* item = nucleon.item;
416  if ( !item ) return 0;
417 
419  switch ( type )
420  {
422  return mSettings.style( item->style() ).margin( QgsComposerLegendStyle::Top );
423  break;
425  return mSettings.style( item->style() ).margin( QgsComposerLegendStyle::Top );
426  break;
429  // TODO: use Symbol or SymbolLabel Top margin
431  break;
432  default:
433  break;
434  }
435  return 0;
436 }
437 
438 
439 // Draw atom and expand its size (using actual nucleons labelXOffset)
440 QSizeF QgsLegendRenderer::drawAtom( Atom atom, QPainter* painter, QPointF point )
441 {
442  bool first = true;
443  QSizeF size = QSizeF( atom.size );
444  foreach ( Nucleon nucleon, atom.nucleons )
445  {
446  QgsComposerLegendItem* item = nucleon.item;
447  //QgsDebugMsg( "text: " + item->text() );
448  if ( !item ) continue;
450  if ( type == QgsComposerLegendItem::GroupItem )
451  {
452  QgsComposerGroupItem* groupItem = dynamic_cast<QgsComposerGroupItem*>( item );
453  if ( !groupItem ) continue;
454  if ( groupItem->style() != QgsComposerLegendStyle::Hidden )
455  {
456  if ( !first )
457  {
458  point.ry() += mSettings.style( groupItem->style() ).margin( QgsComposerLegendStyle::Top );
459  }
460  drawGroupItemTitle( groupItem, painter, point );
461  }
462  }
463  else if ( type == QgsComposerLegendItem::LayerItem )
464  {
465  QgsComposerLayerItem* layerItem = dynamic_cast<QgsComposerLayerItem*>( item );
466  if ( !layerItem ) continue;
467  if ( layerItem->style() != QgsComposerLegendStyle::Hidden )
468  {
469  if ( !first )
470  {
471  point.ry() += mSettings.style( layerItem->style() ).margin( QgsComposerLegendStyle::Top );
472  }
473  drawLayerItemTitle( layerItem, painter, point );
474  }
475  }
476  else if ( type == QgsComposerLegendItem::SymbologyV2Item ||
478  {
479  if ( !first )
480  {
482  }
483  double labelXOffset = nucleon.labelXOffset;
484  Nucleon symbolNucleon = drawSymbolItem( item, painter, point, labelXOffset );
485  // expand width, it may be wider because of labelXOffset
486  size.rwidth() = qMax( symbolNucleon.size.width(), size.width() );
487  }
488  point.ry() += nucleon.size.height();
489  first = false;
490  }
491  return size;
492 }
493 
494 
495 QStringList QgsLegendRenderer::splitStringForWrapping( QString stringToSplt )
496 {
497  QStringList list;
498  // If the string contains nothing then just return the string without spliting.
499  if ( mSettings.wrapChar().count() == 0 )
500  list << stringToSplt;
501  else
502  list = stringToSplt.split( mSettings.wrapChar() );
503  return list;
504 }
505 
506 
507 
508 void QgsLegendRenderer::drawText( QPainter* p, double x, double y, const QString& text, const QFont& font ) const
509 {
510  QFont textFont = scaledFontPixelSize( font );
511 
512  p->save();
513  p->setFont( textFont );
514  double scaleFactor = 1.0 / FONT_WORKAROUND_SCALE;
515  p->scale( scaleFactor, scaleFactor );
516  p->drawText( QPointF( x * FONT_WORKAROUND_SCALE, y * FONT_WORKAROUND_SCALE ), text );
517  p->restore();
518 }
519 
520 
521 void QgsLegendRenderer::drawText( QPainter* p, const QRectF& rect, const QString& text, const QFont& font, Qt::AlignmentFlag halignment, Qt::AlignmentFlag valignment, int flags ) const
522 {
523  QFont textFont = scaledFontPixelSize( font );
524 
525  QRectF scaledRect( rect.x() * FONT_WORKAROUND_SCALE, rect.y() * FONT_WORKAROUND_SCALE,
526  rect.width() * FONT_WORKAROUND_SCALE, rect.height() * FONT_WORKAROUND_SCALE );
527 
528  p->save();
529  p->setFont( textFont );
530  double scaleFactor = 1.0 / FONT_WORKAROUND_SCALE;
531  p->scale( scaleFactor, scaleFactor );
532  p->drawText( scaledRect, halignment | valignment | flags, text );
533  p->restore();
534 }
535 
536 
537 QFont QgsLegendRenderer::scaledFontPixelSize( const QFont& font ) const
538 {
539  QFont scaledFont = font;
540  double pixelSize = pixelFontSize( font.pointSizeF() ) * FONT_WORKAROUND_SCALE + 0.5;
541  scaledFont.setPixelSize( pixelSize );
542  return scaledFont;
543 }
544 
545 double QgsLegendRenderer::pixelFontSize( double pointSize ) const
546 {
547  return ( pointSize * 0.3527 );
548 }
549 
550 double QgsLegendRenderer::textWidthMillimeters( const QFont& font, const QString& text ) const
551 {
552  QFont metricsFont = scaledFontPixelSize( font );
553  QFontMetricsF fontMetrics( metricsFont );
554  return ( fontMetrics.width( text ) / FONT_WORKAROUND_SCALE );
555 }
556 
557 double QgsLegendRenderer::fontHeightCharacterMM( const QFont& font, const QChar& c ) const
558 {
559  QFont metricsFont = scaledFontPixelSize( font );
560  QFontMetricsF fontMetrics( metricsFont );
561  return ( fontMetrics.boundingRect( c ).height() / FONT_WORKAROUND_SCALE );
562 }
563 
564 double QgsLegendRenderer::fontAscentMillimeters( const QFont& font ) const
565 {
566  QFont metricsFont = scaledFontPixelSize( font );
567  QFontMetricsF fontMetrics( metricsFont );
568  return ( fontMetrics.ascent() / FONT_WORKAROUND_SCALE );
569 }
570 
571 double QgsLegendRenderer::fontDescentMillimeters( const QFont& font ) const
572 {
573  QFont metricsFont = scaledFontPixelSize( font );
574  QFontMetricsF fontMetrics( metricsFont );
575  return ( fontMetrics.descent() / FONT_WORKAROUND_SCALE );
576 }
577 
578 
579 QSizeF QgsLegendRenderer::drawGroupItemTitle( QgsComposerGroupItem* groupItem, QPainter* painter, QPointF point )
580 {
581  QSizeF size( 0, 0 );
582  if ( !groupItem ) return size;
583 
584  double y = point.y();
585 
586  if ( painter ) painter->setPen( mSettings.fontColor() );
587 
588  QFont groupFont = mSettings.style( groupItem->style() ).font();
589 
590  QStringList lines = splitStringForWrapping( groupItem->text() );
591  for ( QStringList::Iterator groupPart = lines.begin(); groupPart != lines.end(); ++groupPart )
592  {
593  y += fontAscentMillimeters( groupFont );
594  if ( painter ) drawText( painter, point.x(), y, *groupPart, groupFont );
595  qreal width = textWidthMillimeters( groupFont, *groupPart );
596  size.rwidth() = qMax( width, size.width() );
597  if ( groupPart != lines.end() )
598  {
599  y += mSettings.lineSpacing();
600  }
601  }
602  size.rheight() = y - point.y();
603  return size;
604 }
605 
606 
607 
608 
609 QSizeF QgsLegendRenderer::drawLayerItemTitle( QgsComposerLayerItem* layerItem, QPainter* painter, QPointF point )
610 {
611  QSizeF size( 0, 0 );
612  if ( !layerItem ) return size;
613 
614  //Let the user omit the layer title item by having an empty layer title string
615  if ( layerItem->text().isEmpty() ) return size;
616 
617  double y = point.y();
618 
619  if ( painter ) painter->setPen( mSettings.fontColor() );
620 
621  QFont layerFont = mSettings.style( layerItem->style() ).font();
622 
623  QStringList lines = splitStringForWrapping( layerItem->text() );
624  for ( QStringList::Iterator layerItemPart = lines.begin(); layerItemPart != lines.end(); ++layerItemPart )
625  {
626  y += fontAscentMillimeters( layerFont );
627  if ( painter ) drawText( painter, point.x(), y, *layerItemPart , layerFont );
628  qreal width = textWidthMillimeters( layerFont, *layerItemPart );
629  size.rwidth() = qMax( width, size.width() );
630  if ( layerItemPart != lines.end() )
631  {
632  y += mSettings.lineSpacing();
633  }
634  }
635  size.rheight() = y - point.y();
636 
637  return size;
638 }
639 
640 
641 
642 QgsLegendRenderer::Nucleon QgsLegendRenderer::drawSymbolItem( QgsComposerLegendItem* symbolItem, QPainter* painter, QPointF point, double labelXOffset )
643 {
644  QSizeF symbolSize( 0, 0 );
645  QSizeF labelSize( 0, 0 );
646  if ( !symbolItem ) return Nucleon();
647 
648  QFont symbolLabelFont = mSettings.style( QgsComposerLegendStyle::SymbolLabel ).font();
649 
650  double textHeight = fontHeightCharacterMM( symbolLabelFont, QChar( '0' ) );
651  // itemHeight here is not realy item height, it is only for symbol
652  // vertical alignment purpose, i.e. ok take single line height
653  // if there are more lines, thos run under the symbol
654  double itemHeight = qMax( mSettings.symbolSize().height(), textHeight );
655 
656  //real symbol height. Can be different from standard height in case of point symbols
657  double realSymbolHeight;
658 
659  QgsComposerLayerItem* layerItem = dynamic_cast<QgsComposerLayerItem*>( symbolItem->parent() );
660 
661  int opacity = 255;
662  if ( layerItem )
663  {
664  QgsMapLayer* currentLayer = QgsMapLayerRegistry::instance()->mapLayer( layerItem->layerID() );
665  if ( currentLayer )
666  {
667  //vector layer
668  QgsVectorLayer* vectorLayer = dynamic_cast<QgsVectorLayer*>( currentLayer );
669  if ( vectorLayer )
670  {
671  opacity = 255 - ( 255 * vectorLayer->layerTransparency() / 100 );
672  }
673  }
674  }
675 
676  QString text = symbolItem->text();
677 
678  QStringList lines = splitStringForWrapping( text );
679 
680  QgsSymbolV2* symbolNg = 0;
681  QgsComposerSymbolV2Item* symbolV2Item = dynamic_cast<QgsComposerSymbolV2Item*>( symbolItem );
682  if ( symbolV2Item )
683  {
684  symbolNg = symbolV2Item->symbolV2();
685  }
686  QgsComposerRasterSymbolItem* rasterItem = dynamic_cast<QgsComposerRasterSymbolItem*>( symbolItem );
687 
688  double x = point.x();
689  if ( symbolNg ) //item with symbol NG?
690  {
691  // must be called also with painter=0 to get real size
692  drawSymbolV2( painter, symbolNg, point.y() + ( itemHeight - mSettings.symbolSize().height() ) / 2, x, realSymbolHeight, opacity );
693  symbolSize.rwidth() = qMax( x - point.x(), mSettings.symbolSize().width() );
694  symbolSize.rheight() = qMax( realSymbolHeight, mSettings.symbolSize().height() );
695  }
696  else if ( rasterItem )
697  {
698  // manage WMS lengendGraphic
699  // 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 :(
700  QIcon symbolIcon = symbolItem->icon();
701  if ( !symbolIcon.isNull() && symbolItem->text().isEmpty() )
702  {
703  // find max size
704  QList<QSize> sizes = symbolIcon.availableSizes();
705  double maxWidth = 0;
706  double maxHeight = 0;
707  foreach ( QSize size, sizes )
708  {
709  if ( maxWidth < size.width() ) maxWidth = size.width();
710  if ( maxHeight < size.height() ) maxHeight = size.height();
711  }
712  QSize maxSize( maxWidth, maxHeight );
713 
714  // get and print legend
715  QImage legend = symbolIcon.pixmap( maxWidth, maxHeight ).toImage();
716  if ( painter )
717  {
718  painter->drawImage( QRectF( point.x(), point.y(), mSettings.wmsLegendSize().width(), mSettings.wmsLegendSize().height() ), legend, QRectF( 0, 0, maxWidth, maxHeight ) );
719  }
720  symbolSize.rwidth() = mSettings.wmsLegendSize().width();
721  symbolSize.rheight() = mSettings.wmsLegendSize().height();
722  }
723  else
724  {
725  if ( painter )
726  {
727  painter->setBrush( rasterItem->color() );
728  painter->drawRect( QRectF( point.x(), point.y() + ( itemHeight - mSettings.symbolSize().height() ) / 2, mSettings.symbolSize().width(), mSettings.symbolSize().height() ) );
729  }
730  symbolSize.rwidth() = mSettings.symbolSize().width();
731  symbolSize.rheight() = mSettings.symbolSize().height();
732  }
733  }
734  else //item with icon?
735  {
736  QIcon symbolIcon = symbolItem->icon();
737  if ( !symbolIcon.isNull() )
738  {
739  if ( painter ) symbolIcon.paint( painter, point.x(), point.y() + ( itemHeight - mSettings.symbolSize().height() ) / 2, mSettings.symbolSize().width(), mSettings.symbolSize().height() );
740  symbolSize.rwidth() = mSettings.symbolSize().width();
741  symbolSize.rheight() = mSettings.symbolSize().height();
742  }
743  }
744 
745  if ( painter ) painter->setPen( mSettings.fontColor() );
746 
747  //double labelX = point.x() + labelXOffset; // + mIconLabelSpace;
748  double labelX = point.x() + qMax(( double ) symbolSize.width(), labelXOffset );
749 
750  // Vertical alignment of label with symbol:
751  // a) label height < symbol height: label centerd with symbol
752  // b) label height > symbol height: label starts at top and runs under symbol
753 
754  labelSize.rheight() = lines.count() * textHeight + ( lines.count() - 1 ) * mSettings.lineSpacing();
755 
756  double labelY;
757  if ( labelSize.height() < symbolSize.height() )
758  {
759  labelY = point.y() + symbolSize.height() / 2 + textHeight / 2;
760  }
761  else
762  {
763  labelY = point.y() + textHeight;
764  }
765 
766  for ( QStringList::Iterator itemPart = lines.begin(); itemPart != lines.end(); ++itemPart )
767  {
768  if ( painter ) drawText( painter, labelX, labelY, *itemPart , symbolLabelFont );
769  labelSize.rwidth() = qMax( textWidthMillimeters( symbolLabelFont, *itemPart ), double( labelSize.width() ) );
770  if ( itemPart != lines.end() )
771  {
772  labelY += mSettings.lineSpacing() + textHeight;
773  }
774  }
775 
776  Nucleon nucleon;
777  nucleon.item = symbolItem;
778  nucleon.symbolSize = symbolSize;
779  nucleon.labelSize = labelSize;
780  //QgsDebugMsg( QString( "symbol height = %1 label height = %2").arg( symbolSize.height()).arg( labelSize.height() ));
781  double width = qMax(( double ) symbolSize.width(), labelXOffset ) + labelSize.width();
782  double height = qMax( symbolSize.height(), labelSize.height() );
783  nucleon.size = QSizeF( width, height );
784  return nucleon;
785 }
786 
787 
788 
789 void QgsLegendRenderer::drawSymbolV2( QPainter* p, QgsSymbolV2* s, double currentYCoord, double& currentXPosition, double& symbolHeight, int opacity ) const
790 {
791  if ( !s )
792  {
793  return;
794  }
795 
796  //setup painter scaling to dots so that raster symbology is drawn to scale
797  double dotsPerMM = 1.0;
798  if ( p )
799  {
800  QPaintDevice* paintDevice = p->device();
801  if ( !paintDevice )
802  {
803  return;
804  }
805  dotsPerMM = paintDevice->logicalDpiX() / 25.4;
806  }
807 
808  //consider relation to composer map for symbol sizes in mm
809  bool sizeInMapUnits = s->outputUnit() == QgsSymbolV2::MapUnit;
810  QgsMarkerSymbolV2* markerSymbol = dynamic_cast<QgsMarkerSymbolV2*>( s );
811 
812  //Consider symbol size for point markers
813  double height = mSettings.symbolSize().height();
814  double width = mSettings.symbolSize().width();
815  double size = 0;
816  //Center small marker symbols
817  double widthOffset = 0;
818  double heightOffset = 0;
819 
820  if ( markerSymbol )
821  {
822  size = markerSymbol->size();
823  height = size;
824  width = size;
825  if ( sizeInMapUnits )
826  {
827  height *= mSettings.mmPerMapUnit();
828  width *= mSettings.mmPerMapUnit();
829  markerSymbol->setSize( width );
830  }
831  if ( width < mSettings.symbolSize().width() )
832  {
833  widthOffset = ( mSettings.symbolSize().width() - width ) / 2.0;
834  }
835  if ( height < mSettings.symbolSize().height() )
836  {
837  heightOffset = ( mSettings.symbolSize().height() - height ) / 2.0;
838  }
839  }
840 
841  if ( p )
842  {
843  if ( markerSymbol && sizeInMapUnits )
844  {
846  }
847 
848  p->save();
849  p->setRenderHint( QPainter::Antialiasing );
850  if ( opacity != 255 && mSettings.useAdvancedEffects() )
851  {
852  //semi transparent layer, so need to draw symbol to an image (to flatten it first)
853  //create image which is same size as legend rect, in case symbol bleeds outside its alloted space
854  QImage tempImage = QImage( QSize( width * dotsPerMM, height * dotsPerMM ), QImage::Format_ARGB32 );
855  QPainter imagePainter( &tempImage );
856  tempImage.fill( Qt::transparent );
857  imagePainter.translate( dotsPerMM * ( currentXPosition + widthOffset ),
858  dotsPerMM * ( currentYCoord + heightOffset ) );
859  s->drawPreviewIcon( &imagePainter, QSize( width * dotsPerMM, height * dotsPerMM ) );
860  //reduce opacity of image
861  imagePainter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
862  imagePainter.fillRect( tempImage.rect(), QColor( 0, 0, 0, opacity ) );
863  //draw rendered symbol image
864  p->scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
865  p->drawImage( 0, 0, tempImage );
866  }
867  else
868  {
869  p->translate( currentXPosition + widthOffset, currentYCoord + heightOffset );
870  p->scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
871  s->drawPreviewIcon( p, QSize( width * dotsPerMM, height * dotsPerMM ) );
872  }
873  p->restore();
874 
875  if ( markerSymbol && sizeInMapUnits )
876  {
878  markerSymbol->setSize( size );
879  }
880  }
881  currentXPosition += width;
882  currentXPosition += 2 * widthOffset;
883  symbolHeight = height + 2 * heightOffset;
884 }
885 
QStringList splitStringForWrapping(QString stringToSplt)
Splits a string using the wrap char taking into account handling empty wrap char which means no wrapp...
QString wrapChar() const
QSizeF paintAndDetermineSize(QPainter *painter)
Base class for all map layer types.
Definition: qgsmaplayer.h:48
double boxSpace() const
QFont scaledFontPixelSize(const QFont &font) const
Returns a font where size is in pixel and font size is upscaled with FONT_WORKAROUND_SCALE.
A model that provides group, layer and classification items.
QSizeF drawLayerItemTitle(QgsComposerLayerItem *layerItem, QPainter *painter=0, QPointF point=QPointF())
Draws a layer item and all subitems.
double lineSpacing() const
QgsLegendModel * mLegendModel
int columnCount() const
void drawPreviewIcon(QPainter *painter, QSize size)
Atom is indivisible set (indivisible into more columns).
void drawLegend(QPainter *painter)
Draw the legend with given painter.
Nucleon is either group title, layer title or layer child item.
double columnSpace() const
QList< Nucleon > nucleons
double mmPerMapUnit() const
QgsComposerLegendStyle style(QgsComposerLegendStyle::Style s) const
Returns style.
double fontDescentMillimeters(const QFont &font) const
Returns the font descent in Millimeters (considers upscaling and downscaling with FONT_WORKAROUND_SCA...
QColor fontColor() const
QSizeF drawTitle(QPainter *painter=0, QPointF point=QPointF(), Qt::AlignmentFlag halignment=Qt::AlignLeft, double legendWidth=0)
Draws title in the legend using the title font and the specified alignment If no painter is specified...
int splitLayer() const
#define FONT_WORKAROUND_SCALE
The QgsLegendSettings class stores the appearance and layout settings for legend drawing with QgsLege...
QList< Atom > createAtomList(QStandardItem *rootItem, bool splitLayer)
Create list of atoms according to current layer splitting mode.
void setSize(double size)
double textWidthMillimeters(const QFont &font, const QString &text) const
Returns the font width in millimeters (considers upscaling and downscaling with FONT_WORKAROUND_SCALE...
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...
double fontHeightCharacterMM(const QFont &font, const QChar &c) const
Returns the font height of a character in millimeters.
double fontAscentMillimeters(const QFont &font) const
Returns the font ascent in Millimeters (considers upscaling and downscaling with FONT_WORKAROUND_SCAL...
QgsLegendRenderer(QgsLegendModel *legendModel, const QgsLegendSettings &settings)
Construct legend renderer.
QSizeF minimumSize()
Run the layout algorithm and determine the size required for legend.
Abstract base class for the legend item types.
int layerTransparency() const
Read transparency for layer.
Qt::AlignmentFlag titleAlignment() const
Returns the alignment of the legend title.
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...
QgsComposerLegendItem * item
bool useAdvancedEffects() const
static QgsMapLayerRegistry * instance()
Returns the instance pointer, creating the object on the first call.
double spaceAboveAtom(Atom atom)
void drawText(QPainter *p, double x, double y, const QString &text, const QFont &font) const
Draws Text.
QgsComposerLegendStyle::Style style() const
virtual ItemType itemType() const =0
QgsSymbolV2::OutputUnit outputUnit() const
Definition: qgssymbolv2.cpp:63
QgsLegendSettings mSettings
QString title() const
QgsMapLayer * mapLayer(QString theLayerId)
Retrieve a pointer to a loaded layer by id.
double pixelFontSize(double pointSize) const
Calculates font to from point size to pixel size.
QSizeF symbolSize() const
QSizeF wmsLegendSize() const
Represents a vector layer which manages a vector based data sets.
double size
Definition: qgssvgcache.cpp:77
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 setOutputUnit(QgsSymbolV2::OutputUnit u)
void setColumns(QList< Atom > &atomList)
Divide atoms to columns and set columns on atoms.
Nucleon drawSymbolItem(QgsComposerLegendItem *symbolItem, QPainter *painter=0, QPointF point=QPointF(), double labelXOffset=0.)
int equalColumnWidth() const