QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgslayertreemodellegendnode.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayertreemodellegendnode.cpp
3  --------------------------------------
4  Date : August 2014
5  Copyright : (C) 2014 by Martin Dobias
6  Email : wonder dot sk at gmail dot com
7 
8  QgsWMSLegendNode : Sandro Santilli < strk at keybit dot net >
9 
10  ***************************************************************************
11  * *
12  * This program is free software; you can redistribute it and/or modify *
13  * it under the terms of the GNU General Public License as published by *
14  * the Free Software Foundation; either version 2 of the License, or *
15  * (at your option) any later version. *
16  * *
17  ***************************************************************************/
18 
20 
21 #include "qgslayertree.h"
22 #include "qgslayertreemodel.h"
23 #include "qgslegendsettings.h"
24 #include "qgsrasterlayer.h"
25 #include "qgsrendererv2.h"
26 #include "qgssymbollayerv2utils.h"
27 #include "qgsvectorlayer.h"
28 
29 
30 
32  : QObject( parent )
33  , mLayerNode( nodeL )
34  , mEmbeddedInParent( false )
35 {
36 }
37 
39 {
40 }
41 
43 {
44  return qobject_cast<QgsLayerTreeModel*>( parent() );
45 }
46 
48 {
49  return Qt::ItemIsEnabled;
50 }
51 
52 bool QgsLayerTreeModelLegendNode::setData( const QVariant& value, int role )
53 {
54  Q_UNUSED( value );
55  Q_UNUSED( role );
56  return false;
57 }
58 
59 
61 {
62  QFont symbolLabelFont = settings.style( QgsComposerLegendStyle::SymbolLabel ).font();
63 
64  double textHeight = settings.fontHeightCharacterMM( symbolLabelFont, QChar( '0' ) );
65  // itemHeight here is not realy item height, it is only for symbol
66  // vertical alignment purpose, i.e. ok take single line height
67  // if there are more lines, thos run under the symbol
68  double itemHeight = qMax(( double ) settings.symbolSize().height(), textHeight );
69 
70  ItemMetrics im;
71  im.symbolSize = drawSymbol( settings, ctx, itemHeight );
72  im.labelSize = drawSymbolText( settings, ctx, im.symbolSize );
73  return im;
74 }
75 
76 
77 QSizeF QgsLayerTreeModelLegendNode::drawSymbol( const QgsLegendSettings& settings, ItemContext* ctx, double itemHeight ) const
78 {
79  QIcon symbolIcon = data( Qt::DecorationRole ).value<QIcon>();
80  if ( symbolIcon.isNull() )
81  return QSizeF();
82 
83  if ( ctx )
84  symbolIcon.paint( ctx->painter, ctx->point.x(), ctx->point.y() + ( itemHeight - settings.symbolSize().height() ) / 2,
85  settings.symbolSize().width(), settings.symbolSize().height() );
86  return settings.symbolSize();
87 }
88 
89 
90 QSizeF QgsLayerTreeModelLegendNode::drawSymbolText( const QgsLegendSettings& settings, ItemContext* ctx, const QSizeF& symbolSize ) const
91 {
92  QSizeF labelSize( 0, 0 );
93 
94  QFont symbolLabelFont = settings.style( QgsComposerLegendStyle::SymbolLabel ).font();
95  double textHeight = settings.fontHeightCharacterMM( symbolLabelFont, QChar( '0' ) );
96 
97  QStringList lines = settings.splitStringForWrapping( data( Qt::DisplayRole ).toString() );
98 
99  labelSize.rheight() = lines.count() * textHeight + ( lines.count() - 1 ) * settings.lineSpacing();
100 
101  double labelX, labelY;
102  if ( ctx )
103  {
104  ctx->painter->setPen( settings.fontColor() );
105 
106  labelX = ctx->point.x() + qMax(( double ) symbolSize.width(), ctx->labelXOffset );
107  labelY = ctx->point.y();
108 
109  // Vertical alignment of label with symbol
110  if ( labelSize.height() < symbolSize.height() )
111  labelY += symbolSize.height() / 2 + textHeight / 2; // label centered with symbol
112  else
113  labelY += textHeight; // label starts at top and runs under symbol
114  }
115 
116  for ( QStringList::Iterator itemPart = lines.begin(); itemPart != lines.end(); ++itemPart )
117  {
118  labelSize.rwidth() = qMax( settings.textWidthMillimeters( symbolLabelFont, *itemPart ), double( labelSize.width() ) );
119 
120  if ( ctx )
121  {
122  settings.drawText( ctx->painter, labelX, labelY, *itemPart, symbolLabelFont );
123  if ( itemPart != lines.end() )
124  labelY += settings.lineSpacing() + textHeight;
125  }
126  }
127 
128  return labelSize;
129 }
130 
131 // -------------------------------------------------------------------------
132 
133 
135  : QgsLayerTreeModelLegendNode( nodeLayer, parent )
136  , mItem( item )
137  , mSymbolUsesMapUnits( false )
138 {
139  updateLabel();
140 
141  if ( mItem.symbol() )
142  mSymbolUsesMapUnits = ( mItem.symbol()->outputUnit() != QgsSymbolV2::MM );
143 }
144 
146 {
147 }
148 
149 Qt::ItemFlags QgsSymbolV2LegendNode::flags() const
150 {
151  if ( mItem.isCheckable() )
152  return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
153  else
154  return Qt::ItemIsEnabled;
155 }
156 
157 
158 QVariant QgsSymbolV2LegendNode::data( int role ) const
159 {
160  if ( role == Qt::DisplayRole )
161  {
162  return mLabel;
163  }
164  else if ( role == Qt::EditRole )
165  {
166  return mUserLabel.isEmpty() ? mItem.label() : mUserLabel;
167  }
168  else if ( role == Qt::DecorationRole )
169  {
170  QSize iconSize( 16, 16 ); // TODO: configurable
171  const int indentSize = 20;
172  if ( mPixmap.isNull() )
173  {
174  QPixmap pix;
175  if ( mItem.symbol() )
176  {
177  double scale = 0.0;
178  double mupp = 0.0;
179  int dpi = 0;
180  if ( model() )
181  model()->legendMapViewData( &mupp, &dpi, &scale );
182  bool validData = mupp != 0 && dpi != 0 && scale != 0;
183 
184  // setup temporary render context
185  QgsRenderContext context;
186  context.setScaleFactor( dpi / 25.4 );
187  context.setRendererScale( scale );
188  context.setMapToPixel( QgsMapToPixel( mupp ) ); // hope it's ok to leave out other params
189 
190  pix = QgsSymbolLayerV2Utils::symbolPreviewPixmap( mItem.symbol(), iconSize, validData ? &context : 0 );
191  }
192  else
193  {
194  pix = QPixmap( iconSize );
195  pix.fill( Qt::transparent );
196  }
197 
198  if ( mItem.level() == 0 || ( model() && model()->testFlag( QgsLayerTreeModel::ShowLegendAsTree ) ) )
199  mPixmap = pix;
200  else
201  {
202  // ident the symbol icon to make it look like a tree structure
203  QPixmap pix2( pix.width() + mItem.level() * indentSize, pix.height() );
204  pix2.fill( Qt::transparent );
205  QPainter p( &pix2 );
206  p.drawPixmap( mItem.level() * indentSize, 0, pix );
207  p.end();
208  mPixmap = pix2;
209  }
210  }
211  return mPixmap;
212  }
213  else if ( role == Qt::CheckStateRole )
214  {
215  if ( !mItem.isCheckable() )
216  return QVariant();
217 
218  QgsVectorLayer* vlayer = qobject_cast<QgsVectorLayer*>( mLayerNode->layer() );
219  if ( !vlayer || !vlayer->rendererV2() )
220  return QVariant();
221 
222  return vlayer->rendererV2()->legendSymbolItemChecked( mItem.ruleKey() ) ? Qt::Checked : Qt::Unchecked;
223  }
224  else if ( role == RuleKeyRole )
225  {
226  return mItem.ruleKey();
227  }
228  else if ( role == SymbolV2LegacyRuleKeyRole )
229  {
230  return QVariant::fromValue<void*>( mItem.legacyRuleKey() );
231  }
232  else if ( role == ParentRuleKeyRole )
233  {
234  return mItem.parentRuleKey();
235  }
236 
237  return QVariant();
238 }
239 
240 bool QgsSymbolV2LegendNode::setData( const QVariant& value, int role )
241 {
242  if ( role != Qt::CheckStateRole )
243  return false;
244 
245  if ( !mItem.isCheckable() )
246  return false;
247 
248  QgsVectorLayer* vlayer = qobject_cast<QgsVectorLayer*>( mLayerNode->layer() );
249  if ( !vlayer || !vlayer->rendererV2() )
250  return false;
251 
252  vlayer->rendererV2()->checkLegendSymbolItem( mItem.ruleKey(), value == Qt::Checked );
253 
254  emit dataChanged();
255 
256  vlayer->triggerRepaint();
257 
258  return true;
259 }
260 
261 
262 
263 QSizeF QgsSymbolV2LegendNode::drawSymbol( const QgsLegendSettings& settings, ItemContext* ctx, double itemHeight ) const
264 {
265  QgsSymbolV2* s = mItem.symbol();
266  if ( !s )
267  {
268  return QSizeF();
269  }
270 
271  // setup temporary render context
272  QgsRenderContext context;
273  context.setScaleFactor( settings.dpi() / 25.4 );
274  context.setRendererScale( settings.mapScale() );
275  context.setMapToPixel( QgsMapToPixel( 1 / ( settings.mmPerMapUnit() * context.scaleFactor() ) ) ); // hope it's ok to leave out other params
276  context.setForceVectorOutput( true );
277  context.setPainter( ctx ? ctx->painter : 0 );
278 
279  //Consider symbol size for point markers
280  double height = settings.symbolSize().height();
281  double width = settings.symbolSize().width();
282  double size = 0;
283  //Center small marker symbols
284  double widthOffset = 0;
285  double heightOffset = 0;
286 
287  if ( QgsMarkerSymbolV2* markerSymbol = dynamic_cast<QgsMarkerSymbolV2*>( s ) )
288  {
289  // allow marker symbol to occupy bigger area if necessary
290  size = markerSymbol->size() * QgsSymbolLayerV2Utils::lineWidthScaleFactor( context, s->outputUnit(), s->mapUnitScale() ) / context.scaleFactor();
291  height = size;
292  width = size;
293  if ( width < settings.symbolSize().width() )
294  {
295  widthOffset = ( settings.symbolSize().width() - width ) / 2.0;
296  }
297  if ( height < settings.symbolSize().height() )
298  {
299  heightOffset = ( settings.symbolSize().height() - height ) / 2.0;
300  }
301  }
302 
303  if ( ctx )
304  {
305  double currentXPosition = ctx->point.x();
306  double currentYCoord = ctx->point.y() + ( itemHeight - settings.symbolSize().height() ) / 2;
307  QPainter* p = ctx->painter;
308 
309  //setup painter scaling to dots so that raster symbology is drawn to scale
310  double dotsPerMM = context.scaleFactor();
311 
312  int opacity = 255;
313  if ( QgsVectorLayer* vectorLayer = dynamic_cast<QgsVectorLayer*>( layerNode()->layer() ) )
314  opacity = 255 - ( 255 * vectorLayer->layerTransparency() / 100 );
315 
316  p->save();
317  p->setRenderHint( QPainter::Antialiasing );
318  p->translate( currentXPosition + widthOffset, currentYCoord + heightOffset );
319  p->scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
320  if ( opacity != 255 && settings.useAdvancedEffects() )
321  {
322  //semi transparent layer, so need to draw symbol to an image (to flatten it first)
323  //create image which is same size as legend rect, in case symbol bleeds outside its alloted space
324  QSize tempImageSize( width * dotsPerMM, height * dotsPerMM );
325  QImage tempImage = QImage( tempImageSize, QImage::Format_ARGB32 );
326  tempImage.fill( Qt::transparent );
327  QPainter imagePainter( &tempImage );
328  context.setPainter( &imagePainter );
329  s->drawPreviewIcon( &imagePainter, tempImageSize, &context );
330  context.setPainter( ctx->painter );
331  //reduce opacity of image
332  imagePainter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
333  imagePainter.fillRect( tempImage.rect(), QColor( 0, 0, 0, opacity ) );
334  imagePainter.end();
335  //draw rendered symbol image
336  p->drawImage( 0, 0, tempImage );
337  }
338  else
339  {
340  s->drawPreviewIcon( p, QSize( width * dotsPerMM, height * dotsPerMM ), &context );
341  }
342  p->restore();
343  }
344 
345  return QSizeF( qMax( width + 2 * widthOffset, ( double ) settings.symbolSize().width() ),
346  qMax( height + 2 * heightOffset, ( double ) settings.symbolSize().height() ) );
347 }
348 
349 
351 {
353  updateLabel();
354 }
355 
356 
358 {
359  if ( mSymbolUsesMapUnits )
360  {
361  mPixmap = QPixmap();
362  emit dataChanged();
363  }
364 }
365 
366 
367 void QgsSymbolV2LegendNode::updateLabel()
368 {
369  bool showFeatureCount = mLayerNode->customProperty( "showFeatureCount", 0 ).toBool();
370  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( mLayerNode->layer() );
371 
372  if ( mEmbeddedInParent )
373  {
374  QString layerName = mLayerNode->layerName();
375  if ( !mLayerNode->customProperty( "legend/title-label" ).isNull() )
376  layerName = mLayerNode->customProperty( "legend/title-label" ).toString();
377 
378  mLabel = mUserLabel.isEmpty() ? layerName : mUserLabel;
379  if ( showFeatureCount && vl && vl->pendingFeatureCount() >= 0 )
380  mLabel += QString( " [%1]" ).arg( vl->pendingFeatureCount() );
381  }
382  else
383  {
384  mLabel = mUserLabel.isEmpty() ? mItem.label() : mUserLabel;
385  if ( showFeatureCount && vl && mItem.legacyRuleKey() )
386  mLabel += QString( " [%1]" ).arg( vl->featureCount( mItem.legacyRuleKey() ) );
387  }
388 }
389 
390 
391 
392 // -------------------------------------------------------------------------
393 
394 
395 QgsSimpleLegendNode::QgsSimpleLegendNode( QgsLayerTreeLayer* nodeLayer, const QString& label, const QIcon& icon, QObject* parent )
396  : QgsLayerTreeModelLegendNode( nodeLayer, parent )
397  , mLabel( label )
398  , mIcon( icon )
399 {
400 }
401 
402 QVariant QgsSimpleLegendNode::data( int role ) const
403 {
404  if ( role == Qt::DisplayRole || role == Qt::EditRole )
405  return mLabel;
406  else if ( role == Qt::DecorationRole )
407  return mIcon;
408  else
409  return QVariant();
410 }
411 
412 
413 // -------------------------------------------------------------------------
414 
415 QgsImageLegendNode::QgsImageLegendNode( QgsLayerTreeLayer* nodeLayer, const QImage& img, QObject* parent )
416  : QgsLayerTreeModelLegendNode( nodeLayer, parent )
417  , mImage( img )
418 {
419 }
420 
421 QVariant QgsImageLegendNode::data( int role ) const
422 {
423  if ( role == Qt::DecorationRole )
424  {
425  return QPixmap::fromImage( mImage );
426  }
427  else if ( role == Qt::SizeHintRole )
428  {
429  return mImage.size();
430  }
431  return QVariant();
432 }
433 
434 QSizeF QgsImageLegendNode::drawSymbol( const QgsLegendSettings& settings, ItemContext* ctx, double itemHeight ) const
435 {
436  Q_UNUSED( itemHeight );
437 
438  if ( ctx )
439  {
440  ctx->painter->drawImage( QRectF( ctx->point.x(), ctx->point.y(), settings.wmsLegendSize().width(), settings.wmsLegendSize().height() ),
441  mImage, QRectF( 0, 0, mImage.width(), mImage.height() ) );
442  }
443  return settings.wmsLegendSize();
444 }
445 
446 // -------------------------------------------------------------------------
447 
448 QgsRasterSymbolLegendNode::QgsRasterSymbolLegendNode( QgsLayerTreeLayer* nodeLayer, const QColor& color, const QString& label, QObject* parent )
449  : QgsLayerTreeModelLegendNode( nodeLayer, parent )
450  , mColor( color )
451  , mLabel( label )
452 {
453 }
454 
455 QVariant QgsRasterSymbolLegendNode::data( int role ) const
456 {
457  if ( role == Qt::DecorationRole )
458  {
459  QSize iconSize( 16, 16 ); // TODO: configurable?
460  QPixmap pix( iconSize );
461  pix.fill( mColor );
462  return QIcon( pix );
463  }
464  else if ( role == Qt::DisplayRole || role == Qt::EditRole )
465  return mUserLabel.isEmpty() ? mLabel : mUserLabel;
466  else
467  return QVariant();
468 }
469 
470 
471 QSizeF QgsRasterSymbolLegendNode::drawSymbol( const QgsLegendSettings& settings, ItemContext* ctx, double itemHeight ) const
472 {
473  if ( ctx )
474  {
475  QColor itemColor = mColor;
476  if ( QgsRasterLayer* rasterLayer = dynamic_cast<QgsRasterLayer*>( layerNode()->layer() ) )
477  {
478  if ( QgsRasterRenderer* rasterRenderer = rasterLayer->renderer() )
479  itemColor.setAlpha( rasterRenderer->opacity() * 255.0 );
480  }
481 
482  ctx->painter->setBrush( itemColor );
483  ctx->painter->drawRect( QRectF( ctx->point.x(), ctx->point.y() + ( itemHeight - settings.symbolSize().height() ) / 2,
484  settings.symbolSize().width(), settings.symbolSize().height() ) );
485  }
486  return settings.symbolSize();
487 }
488 
489 // -------------------------------------------------------------------------
490 
492  : QgsLayerTreeModelLegendNode( nodeLayer, parent )
493  , mValid( false )
494 {
495 }
496 
497 const QImage& QgsWMSLegendNode::getLegendGraphic() const
498 {
499  if ( ! mValid && ! mFetcher )
500  {
501  // or maybe in presence of a downloader we should just delete it
502  // and start a new one ?
503 
504  QgsRasterLayer* layer = qobject_cast<QgsRasterLayer*>( mLayerNode->layer() );
505  const QgsLayerTreeModel* mod = model();
506  if ( ! mod ) return mImage;
507  const QgsMapSettings* ms = mod->legendFilterByMap();
508 
509  QgsRasterDataProvider* prov = layer->dataProvider();
510 
511  Q_ASSERT( ! mFetcher );
512  mFetcher.reset( prov->getLegendGraphicFetcher( ms ) );
513  if ( mFetcher )
514  {
515  connect( mFetcher.data(), SIGNAL( finish( const QImage& ) ), this, SLOT( getLegendGraphicFinished( const QImage& ) ) );
516  connect( mFetcher.data(), SIGNAL( error( const QString& ) ), this, SLOT( getLegendGraphicErrored( const QString& ) ) );
517  connect( mFetcher.data(), SIGNAL( progress( qint64, qint64 ) ), this, SLOT( getLegendGraphicProgress( qint64, qint64 ) ) );
518  mFetcher->start();
519  } // else QgsDebugMsg("XXX No legend supported ?");
520 
521  }
522 
523  return mImage;
524 }
525 
526 QVariant QgsWMSLegendNode::data( int role ) const
527 {
528  //QgsDebugMsg( QString("XXX data called with role %1 -- mImage size is %2x%3").arg(role).arg(mImage.width()).arg(mImage.height()) );
529 
530  if ( role == Qt::DecorationRole )
531  {
532  return QPixmap::fromImage( getLegendGraphic() );
533  }
534  else if ( role == Qt::SizeHintRole )
535  {
536  return getLegendGraphic().size();
537  }
538  return QVariant();
539 }
540 
541 QSizeF QgsWMSLegendNode::drawSymbol( const QgsLegendSettings& settings, ItemContext* ctx, double itemHeight ) const
542 {
543  Q_UNUSED( itemHeight );
544 
545  if ( ctx )
546  {
547  ctx->painter->drawImage( QRectF( ctx->point, settings.wmsLegendSize() ),
548  mImage,
549  QRectF( QPointF( 0, 0 ), mImage.size() ) );
550  }
551  return settings.wmsLegendSize();
552 }
553 
554 /* private */
555 QImage QgsWMSLegendNode::renderMessage( const QString& msg ) const
556 {
557  const int fontHeight = 10;
558  const int margin = fontHeight / 2;
559  const int nlines = 1;
560 
561  const int w = 512, h = fontHeight * nlines + margin * ( nlines + 1 );
562  QImage theImage( w, h, QImage::Format_ARGB32_Premultiplied );
563  QPainter painter;
564  painter.begin( &theImage );
565  painter.setPen( QColor( 255, 0, 0 ) );
566  painter.setFont( QFont( "Chicago", fontHeight ) );
567  painter.fillRect( 0, 0, w, h, QColor( 255, 255, 255 ) );
568  painter.drawText( 0, margin + fontHeight, msg );
569  //painter.drawText(0,2*(margin+fontHeight),QString("retrying in 5 seconds..."));
570  painter.end();
571 
572  return theImage;
573 }
574 
575 void QgsWMSLegendNode::getLegendGraphicProgress( qint64 cur, qint64 tot )
576 {
577  QString msg = QString( "Downloading... %1/%2" ).arg( cur ).arg( tot );
578  //QgsDebugMsg ( QString("XXX %1").arg(msg) );
579  mImage = renderMessage( msg );
580  emit dataChanged();
581 }
582 
583 void QgsWMSLegendNode::getLegendGraphicErrored( const QString& msg )
584 {
585  if ( ! mFetcher ) return; // must be coming after finish
586 
587  mImage = renderMessage( msg );
588  //QgsDebugMsg( QString("XXX emitting dataChanged after writing an image of %1x%2").arg(mImage.width()).arg(mImage.height()) );
589 
590  emit dataChanged();
591 
592  mFetcher.reset();
593 
594  mValid = true; // we consider it valid anyway
595  // ... but remove validity after 5 seconds
596  //QTimer::singleShot(5000, this, SLOT(invalidateMapBasedData()));
597 }
598 
599 void QgsWMSLegendNode::getLegendGraphicFinished( const QImage& theImage )
600 {
601  if ( ! mFetcher ) return; // must be coming after error
602 
603  //QgsDebugMsg( QString("XXX legend graphic finished, image is %1x%2").arg(theImage.width()).arg(theImage.height()) );
604  if ( ! theImage.isNull() )
605  {
606  if ( theImage != mImage )
607  {
608  mImage = theImage;
609  //QgsDebugMsg( QString("XXX emitting dataChanged") );
610  emit dataChanged();
611  }
612  mValid = true; // only if not null I guess
613  }
614  mFetcher.reset();
615 }
616 
618 {
619  //QgsDebugMsg( QString("XXX invalidateMapBasedData called") );
620  // TODO: do this only if this extent != prev extent ?
621  mValid = false;
622  emit dataChanged();
623 }