QGIS API Documentation  2.0.1-Dufour
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgspallabeling.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgspallabeling.cpp
3  Smart labeling for vector layers
4  -------------------
5  begin : June 2009
6  copyright : (C) Martin Dobias
7  email : wonder dot sk at gmail dot com
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 
18 #include "qgspallabeling.h"
19 
20 #include <list>
21 
22 #include <pal/pal.h>
23 #include <pal/feature.h>
24 #include <pal/layer.h>
25 #include <pal/palgeometry.h>
26 #include <pal/palexception.h>
27 #include <pal/problem.h>
28 #include <pal/labelposition.h>
29 
30 #include <geos_c.h>
31 
32 #include <cmath>
33 
34 #include <QApplication>
35 #include <QByteArray>
36 #include <QString>
37 #include <QFontMetrics>
38 #include <QTime>
39 #include <QPainter>
40 
41 #include "diagram/qgsdiagram.h"
42 #include "qgsdiagramrendererv2.h"
43 #include "qgsfontutils.h"
44 #include "qgslabelsearchtree.h"
45 #include "qgsexpression.h"
46 #include "qgsdatadefined.h"
47 
48 #include <qgslogger.h>
49 #include <qgsvectorlayer.h>
50 #include <qgsmaplayerregistry.h>
51 #include <qgsvectordataprovider.h>
52 #include <qgsgeometry.h>
53 #include <qgsmaprenderer.h>
54 #include <qgsmarkersymbollayerv2.h>
55 #include <qgsproject.h>
56 #include "qgssymbolv2.h"
57 #include "qgssymbollayerv2utils.h"
58 #include <QMessageBox>
59 
60 using namespace pal;
61 
62 
63 class QgsPalGeometry : public PalGeometry
64 {
65  public:
66  QgsPalGeometry( QgsFeatureId id, QString text, GEOSGeometry* g,
67  qreal ltrSpacing = 0.0, qreal wordSpacing = 0.0, bool curvedLabeling = false )
68  : mG( g )
69  , mText( text )
70  , mId( id )
71  , mInfo( NULL )
72  , mIsDiagram( false )
73  , mIsPinned( false )
74  , mFontMetrics( NULL )
75  , mLetterSpacing( ltrSpacing )
76  , mWordSpacing( wordSpacing )
77  , mCurvedLabeling( curvedLabeling )
78  {
79  mStrId = FID_TO_STRING( id ).toAscii();
80  mDefinedFont = QFont();
81  }
82 
84  {
85  if ( mG )
86  GEOSGeom_destroy( mG );
87  delete mInfo;
88  delete mFontMetrics;
89  }
90 
91  // getGeosGeometry + releaseGeosGeometry is called twice: once when adding, second time when labeling
92 
93  const GEOSGeometry* getGeosGeometry()
94  {
95  return mG;
96  }
97  void releaseGeosGeometry( const GEOSGeometry* /*geom*/ )
98  {
99  // nothing here - we'll delete the geometry in destructor
100  }
101 
102  const char* strId() { return mStrId.data(); }
103  QString text() { return mText; }
104 
105  pal::LabelInfo* info( QFontMetricsF* fm, const QgsMapToPixel* xform, double fontScale, double maxinangle, double maxoutangle )
106  {
107  if ( mInfo )
108  return mInfo;
109 
110  mFontMetrics = new QFontMetricsF( *fm ); // duplicate metrics for when drawing label
111 
112  // max angle between curved label characters (20.0/-20.0 was default in QGIS <= 1.8)
113  if ( maxinangle < 20.0 )
114  maxinangle = 20.0;
115  if ( 60.0 < maxinangle )
116  maxinangle = 60.0;
117  if ( maxoutangle > -20.0 )
118  maxoutangle = -20.0;
119  if ( -95.0 > maxoutangle )
120  maxoutangle = -95.0;
121 
122  // create label info!
123  QgsPoint ptZero = xform->toMapCoordinates( 0, 0 );
124  QgsPoint ptSize = xform->toMapCoordinatesF( 0.0, -fm->height() / fontScale );
125 
126  // mLetterSpacing/mWordSpacing = 0.0 is default for non-curved labels
127  // (non-curved spacings handled by Qt in QgsPalLayerSettings/QgsPalLabeling)
128  qreal charWidth;
129  qreal wordSpaceFix;
130  mInfo = new pal::LabelInfo( mText.count(), ptSize.y() - ptZero.y(), maxinangle, maxoutangle );
131  for ( int i = 0; i < mText.count(); i++ )
132  {
133  mInfo->char_info[i].chr = mText[i].unicode();
134 
135  // reconstruct how Qt creates word spacing, then adjust per individual stored character
136  // this will allow PAL to create each candidate width = character width + correct spacing
137  charWidth = fm->width( mText[i] );
138  if ( mCurvedLabeling )
139  {
140  wordSpaceFix = qreal( 0.0 );
141  if ( mText[i] == QString( " " )[0] )
142  {
143  // word spacing only gets added once at end of consecutive run of spaces, see QTextEngine::shapeText()
144  int nxt = i + 1;
145  wordSpaceFix = ( nxt < mText.count() && mText[nxt] != QString( " " )[0] ) ? mWordSpacing : qreal( 0.0 );
146  }
147  if ( fm->width( QString( mText[i] ) ) - fm->width( mText[i] ) - mLetterSpacing != qreal( 0.0 ) )
148  {
149  // word spacing applied when it shouldn't be
150  wordSpaceFix -= mWordSpacing;
151  }
152  charWidth = fm->width( QString( mText[i] ) ) + wordSpaceFix;
153  }
154 
155  ptSize = xform->toMapCoordinatesF((( double ) charWidth ) / fontScale , 0.0 );
156  mInfo->char_info[i].width = ptSize.x() - ptZero.x();
157  }
158  return mInfo;
159  }
160 
161  const QMap< QgsPalLayerSettings::DataDefinedProperties, QVariant >& dataDefinedValues() const { return mDataDefinedValues; }
162  void addDataDefinedValue( QgsPalLayerSettings::DataDefinedProperties p, QVariant v ) { mDataDefinedValues.insert( p, v ); }
163 
164  void setIsDiagram( bool d ) { mIsDiagram = d; }
165  bool isDiagram() const { return mIsDiagram; }
166 
167  void setIsPinned( bool f ) { mIsPinned = f; }
168  bool isPinned() const { return mIsPinned; }
169 
170  void setDefinedFont( QFont f ) { mDefinedFont = QFont( f ); }
171  QFont definedFont() { return mDefinedFont; }
172 
173  QFontMetricsF* getLabelFontMetrics() { return mFontMetrics; }
174 
175  void setDiagramAttributes( const QgsAttributes& attrs ) { mDiagramAttributes = attrs; }
176  const QgsAttributes& diagramAttributes() { return mDiagramAttributes; }
177 
178  protected:
179  GEOSGeometry* mG;
180  QString mText;
181  QByteArray mStrId;
183  LabelInfo* mInfo;
185  bool mIsPinned;
187  QFontMetricsF* mFontMetrics;
188  qreal mLetterSpacing; // for use with curved labels
189  qreal mWordSpacing; // for use with curved labels
190  bool mCurvedLabeling; // whether the geometry is to be used for curved labeling placement
192  QMap< QgsPalLayerSettings::DataDefinedProperties, QVariant > mDataDefinedValues;
193 
196 };
197 
198 // -------------
199 
201  : palLayer( NULL )
202  , mCurFeat( 0 )
203  , mCurFields( 0 )
204  , ct( NULL )
205  , extentGeom( NULL )
206  , mFeaturesToLabel( 0 )
207  , mFeatsSendingToPal( 0 )
208  , mFeatsRegPal( 0 )
209  , expression( NULL )
210 {
211  enabled = false;
212 
213  // text style
214  textFont = QApplication::font();
215  textNamedStyle = QString( "" );
216  fontSizeInMapUnits = false;
217  textColor = Qt::black;
218  textTransp = 0;
219  blendMode = QPainter::CompositionMode_SourceOver;
220  previewBkgrdColor = Qt::white;
221  // font processing info
222  mTextFontFound = true;
223  mTextFontFamily = QApplication::font().family();
224 
225  // text formatting
226  wrapChar = "";
227  multilineHeight = 1.0;
229  addDirectionSymbol = false;
230  leftDirectionSymbol = QString( "<" );
231  rightDirectionSymbol = QString( ">" );
232  reverseDirectionSymbol = false;
234  formatNumbers = false;
235  decimals = 3;
236  plusSign = false;
237 
238  // text buffer
239  bufferDraw = false;
240  bufferSize = 1.0;
241  bufferSizeInMapUnits = false;
242  bufferColor = Qt::white;
243  bufferTransp = 0;
244  bufferNoFill = false;
245  bufferJoinStyle = Qt::BevelJoin;
246  bufferBlendMode = QPainter::CompositionMode_SourceOver;
247 
248  // shape background
249  shapeDraw = false;
251  shapeSVGFile = QString();
253  shapeSize = QPointF( 0.0, 0.0 );
254  shapeSizeUnits = MM;
256  shapeRotation = 0.0;
257  shapeOffset = QPointF( 0.0, 0.0 );
259  shapeRadii = QPointF( 0.0, 0.0 );
261  shapeFillColor = Qt::white;
262  shapeBorderColor = Qt::darkGray;
263  shapeBorderWidth = 0.0;
265  shapeJoinStyle = Qt::BevelJoin;
266  shapeTransparency = 0;
267  shapeBlendMode = QPainter::CompositionMode_SourceOver;
268 
269  // drop shadow
270  shadowDraw = false;
272  shadowOffsetAngle = 135;
273  shadowOffsetDist = 1.0;
275  shadowOffsetGlobal = true;
276  shadowRadius = 1.5;
278  shadowRadiusAlphaOnly = false;
279  shadowTransparency = 30;
280  shadowScale = 100;
281  shadowColor = Qt::black;
282  shadowBlendMode = QPainter::CompositionMode_Multiply;
283 
284  // placement
286  placementFlags = 0;
287  centroidWhole = false;
289  xOffset = 0;
290  yOffset = 0;
291  labelOffsetInMapUnits = true;
292  dist = 0;
293  distInMapUnits = false;
294  angleOffset = 0;
295  preserveRotation = true;
296  maxCurvedCharAngleIn = 20.0;
297  maxCurvedCharAngleOut = -20.0;
298  priority = 5;
299 
300  // rendering
301  scaleVisibility = false;
302  scaleMin = 1;
303  scaleMax = 10000000;
304  fontLimitPixelSize = false;
305  fontMinPixelSize = 0; //trigger to turn it on by default for map unit labels
306  fontMaxPixelSize = 10000;
307  displayAll = false;
309 
310  labelPerPart = false;
311  mergeLines = false;
312  minFeatureSize = 0.0;
313  limitNumLabels = false;
314  maxNumLabels = 2000;
315  obstacle = true;
316 
317  // scale factors
318  vectorScaleFactor = 1.0;
319  rasterCompressFactor = 1.0;
320 
321  // data defined string and old-style index values
322  // NOTE: in QPair use -1 for second value (other values are for old-style layer properties migration)
323 
324  // text style
325  mDataDefinedNames.insert( Size, QPair<QString, int>( "Size", 0 ) );
326  mDataDefinedNames.insert( Bold, QPair<QString, int>( "Bold", 1 ) );
327  mDataDefinedNames.insert( Italic, QPair<QString, int>( "Italic", 2 ) );
328  mDataDefinedNames.insert( Underline, QPair<QString, int>( "Underline", 3 ) );
329  mDataDefinedNames.insert( Color, QPair<QString, int>( "Color", 4 ) );
330  mDataDefinedNames.insert( Strikeout, QPair<QString, int>( "Strikeout", 5 ) );
331  mDataDefinedNames.insert( Family, QPair<QString, int>( "Family", 6 ) );
332  mDataDefinedNames.insert( FontStyle, QPair<QString, int>( "FontStyle", -1 ) );
333  mDataDefinedNames.insert( FontSizeUnit, QPair<QString, int>( "FontSizeUnit", -1 ) );
334  mDataDefinedNames.insert( FontTransp, QPair<QString, int>( "FontTransp", 18 ) );
335  mDataDefinedNames.insert( FontCase, QPair<QString, int>( "FontCase", -1 ) );
336  mDataDefinedNames.insert( FontLetterSpacing, QPair<QString, int>( "FontLetterSpacing", -1 ) );
337  mDataDefinedNames.insert( FontWordSpacing, QPair<QString, int>( "FontWordSpacing", -1 ) );
338  mDataDefinedNames.insert( FontBlendMode, QPair<QString, int>( "FontBlendMode", -1 ) );
339 
340  // text formatting
341  mDataDefinedNames.insert( MultiLineWrapChar, QPair<QString, int>( "MultiLineWrapChar", -1 ) );
342  mDataDefinedNames.insert( MultiLineHeight, QPair<QString, int>( "MultiLineHeight", -1 ) );
343  mDataDefinedNames.insert( MultiLineAlignment, QPair<QString, int>( "MultiLineAlignment", -1 ) );
344  mDataDefinedNames.insert( DirSymbDraw, QPair<QString, int>( "DirSymbDraw", -1 ) );
345  mDataDefinedNames.insert( DirSymbLeft, QPair<QString, int>( "DirSymbLeft", -1 ) );
346  mDataDefinedNames.insert( DirSymbRight, QPair<QString, int>( "DirSymbRight", -1 ) );
347  mDataDefinedNames.insert( DirSymbPlacement, QPair<QString, int>( "DirSymbPlacement", -1 ) );
348  mDataDefinedNames.insert( DirSymbReverse, QPair<QString, int>( "DirSymbReverse", -1 ) );
349  mDataDefinedNames.insert( NumFormat, QPair<QString, int>( "NumFormat", -1 ) );
350  mDataDefinedNames.insert( NumDecimals, QPair<QString, int>( "NumDecimals", -1 ) );
351  mDataDefinedNames.insert( NumPlusSign, QPair<QString, int>( "NumPlusSign", -1 ) );
352 
353  // text buffer
354  mDataDefinedNames.insert( BufferDraw, QPair<QString, int>( "BufferDraw", -1 ) );
355  mDataDefinedNames.insert( BufferSize, QPair<QString, int>( "BufferSize", 7 ) );
356  mDataDefinedNames.insert( BufferUnit, QPair<QString, int>( "BufferUnit", -1 ) );
357  mDataDefinedNames.insert( BufferColor, QPair<QString, int>( "BufferColor", 8 ) );
358  mDataDefinedNames.insert( BufferTransp, QPair<QString, int>( "BufferTransp", 19 ) );
359  mDataDefinedNames.insert( BufferJoinStyle, QPair<QString, int>( "BufferJoinStyle", -1 ) );
360  mDataDefinedNames.insert( BufferBlendMode, QPair<QString, int>( "BufferBlendMode", -1 ) );
361 
362  // background
363  mDataDefinedNames.insert( ShapeDraw, QPair<QString, int>( "ShapeDraw", -1 ) );
364  mDataDefinedNames.insert( ShapeKind, QPair<QString, int>( "ShapeKind", -1 ) );
365  mDataDefinedNames.insert( ShapeSVGFile, QPair<QString, int>( "ShapeSVGFile", -1 ) );
366  mDataDefinedNames.insert( ShapeSizeType, QPair<QString, int>( "ShapeSizeType", -1 ) );
367  mDataDefinedNames.insert( ShapeSizeX, QPair<QString, int>( "ShapeSizeX", -1 ) );
368  mDataDefinedNames.insert( ShapeSizeY, QPair<QString, int>( "ShapeSizeY", -1 ) );
369  mDataDefinedNames.insert( ShapeSizeUnits, QPair<QString, int>( "ShapeSizeUnits", -1 ) );
370  mDataDefinedNames.insert( ShapeRotationType, QPair<QString, int>( "ShapeRotationType", -1 ) );
371  mDataDefinedNames.insert( ShapeRotation, QPair<QString, int>( "ShapeRotation", -1 ) );
372  mDataDefinedNames.insert( ShapeOffset, QPair<QString, int>( "ShapeOffset", -1 ) );
373  mDataDefinedNames.insert( ShapeOffsetUnits, QPair<QString, int>( "ShapeOffsetUnits", -1 ) );
374  mDataDefinedNames.insert( ShapeRadii, QPair<QString, int>( "ShapeRadii", -1 ) );
375  mDataDefinedNames.insert( ShapeRadiiUnits, QPair<QString, int>( "ShapeRadiiUnits", -1 ) );
376  mDataDefinedNames.insert( ShapeTransparency, QPair<QString, int>( "ShapeTransparency", -1 ) );
377  mDataDefinedNames.insert( ShapeBlendMode, QPair<QString, int>( "ShapeBlendMode", -1 ) );
378  mDataDefinedNames.insert( ShapeFillColor, QPair<QString, int>( "ShapeFillColor", -1 ) );
379  mDataDefinedNames.insert( ShapeBorderColor, QPair<QString, int>( "ShapeBorderColor", -1 ) );
380  mDataDefinedNames.insert( ShapeBorderWidth, QPair<QString, int>( "ShapeBorderWidth", -1 ) );
381  mDataDefinedNames.insert( ShapeBorderWidthUnits, QPair<QString, int>( "ShapeBorderWidthUnits", -1 ) );
382  mDataDefinedNames.insert( ShapeJoinStyle, QPair<QString, int>( "ShapeJoinStyle", -1 ) );
383 
384  // drop shadow
385  mDataDefinedNames.insert( ShadowDraw, QPair<QString, int>( "ShadowDraw", -1 ) );
386  mDataDefinedNames.insert( ShadowUnder, QPair<QString, int>( "ShadowUnder", -1 ) );
387  mDataDefinedNames.insert( ShadowOffsetAngle, QPair<QString, int>( "ShadowOffsetAngle", -1 ) );
388  mDataDefinedNames.insert( ShadowOffsetDist, QPair<QString, int>( "ShadowOffsetDist", -1 ) );
389  mDataDefinedNames.insert( ShadowOffsetUnits, QPair<QString, int>( "ShadowOffsetUnits", -1 ) );
390  mDataDefinedNames.insert( ShadowRadius, QPair<QString, int>( "ShadowRadius", -1 ) );
391  mDataDefinedNames.insert( ShadowRadiusUnits, QPair<QString, int>( "ShadowRadiusUnits", -1 ) );
392  mDataDefinedNames.insert( ShadowTransparency, QPair<QString, int>( "ShadowTransparency", -1 ) );
393  mDataDefinedNames.insert( ShadowScale, QPair<QString, int>( "ShadowScale", -1 ) );
394  mDataDefinedNames.insert( ShadowColor, QPair<QString, int>( "ShadowColor", -1 ) );
395  mDataDefinedNames.insert( ShadowBlendMode, QPair<QString, int>( "ShadowBlendMode", -1 ) );
396 
397  // placement
398  mDataDefinedNames.insert( CentroidWhole, QPair<QString, int>( "CentroidWhole", -1 ) );
399  mDataDefinedNames.insert( OffsetQuad, QPair<QString, int>( "OffsetQuad", -1 ) );
400  mDataDefinedNames.insert( OffsetXY, QPair<QString, int>( "OffsetXY", -1 ) );
401  mDataDefinedNames.insert( OffsetUnits, QPair<QString, int>( "OffsetUnits", -1 ) );
402  mDataDefinedNames.insert( LabelDistance, QPair<QString, int>( "LabelDistance", 13 ) );
403  mDataDefinedNames.insert( DistanceUnits, QPair<QString, int>( "DistanceUnits", -1 ) );
404  mDataDefinedNames.insert( OffsetRotation, QPair<QString, int>( "OffsetRotation", -1 ) );
405  mDataDefinedNames.insert( CurvedCharAngleInOut, QPair<QString, int>( "CurvedCharAngleInOut", -1 ) );
406  // (data defined only)
407  mDataDefinedNames.insert( PositionX, QPair<QString, int>( "PositionX", 9 ) );
408  mDataDefinedNames.insert( PositionY, QPair<QString, int>( "PositionY", 10 ) );
409  mDataDefinedNames.insert( Hali, QPair<QString, int>( "Hali", 11 ) );
410  mDataDefinedNames.insert( Vali, QPair<QString, int>( "Vali", 12 ) );
411  mDataDefinedNames.insert( Rotation, QPair<QString, int>( "Rotation", 14 ) );
412 
413  //rendering
414  mDataDefinedNames.insert( ScaleVisibility, QPair<QString, int>( "ScaleVisibility", -1 ) );
415  mDataDefinedNames.insert( MinScale, QPair<QString, int>( "MinScale", 16 ) );
416  mDataDefinedNames.insert( MaxScale, QPair<QString, int>( "MaxScale", 17 ) );
417  mDataDefinedNames.insert( FontLimitPixel, QPair<QString, int>( "FontLimitPixel", -1 ) );
418  mDataDefinedNames.insert( FontMinPixel, QPair<QString, int>( "FontMinPixel", -1 ) );
419  mDataDefinedNames.insert( FontMaxPixel, QPair<QString, int>( "FontMaxPixel", -1 ) );
420  // (data defined only)
421  mDataDefinedNames.insert( Show, QPair<QString, int>( "Show", 15 ) );
422  mDataDefinedNames.insert( AlwaysShow, QPair<QString, int>( "AlwaysShow", 20 ) );
423 
424  // temp stuff for when drawing label components (don't copy)
425  showingShadowRects = false;
426 }
427 
429 {
430  // copy only permanent stuff
431 
432  enabled = s.enabled;
433 
434  // text style
435  fieldName = s.fieldName;
437  textFont = s.textFont;
440  textColor = s.textColor;
442  blendMode = s.blendMode;
444  // font processing info
447 
448  // text formatting
449  wrapChar = s.wrapChar;
458  decimals = s.decimals;
459  plusSign = s.plusSign;
460 
461  // text buffer
470 
471  // placement
472  placement = s.placement;
476  xOffset = s.xOffset;
477  yOffset = s.yOffset;
479  dist = s.dist;
485  priority = s.priority;
486 
487  // rendering
489  scaleMin = s.scaleMin;
490  scaleMax = s.scaleMax;
496 
502  obstacle = s.obstacle;
503 
504  // shape background
505  shapeDraw = s.shapeDraw;
506  shapeType = s.shapeType;
509  shapeSize = s.shapeSize;
524 
525  // drop shadow
539 
540  // data defined
543 
544  // scale factors
547 
548  ct = NULL;
549  extentGeom = NULL;
550  expression = NULL;
551 }
552 
553 
555 {
556  // pal layer is deleted internally in PAL
557 
558  delete ct;
559  delete expression;
560  delete extentGeom;
561 
562  // clear pointers to QgsDataDefined objects
563  dataDefinedProperties.clear();
564 }
565 
567 {
568  if ( expression == NULL )
569  {
571  }
572  return expression;
573 }
574 
575 static QColor _readColor( QgsVectorLayer* layer, QString property, QColor defaultColor = Qt::black, bool withAlpha = true )
576 {
577  int r = layer->customProperty( property + "R", QVariant( defaultColor.red() ) ).toInt();
578  int g = layer->customProperty( property + "G", QVariant( defaultColor.green() ) ).toInt();
579  int b = layer->customProperty( property + "B", QVariant( defaultColor.blue() ) ).toInt();
580  int a = withAlpha ? layer->customProperty( property + "A", QVariant( defaultColor.alpha() ) ).toInt() : 255;
581  return QColor( r, g, b, a );
582 }
583 
584 static void _writeColor( QgsVectorLayer* layer, QString property, QColor color, bool withAlpha = true )
585 {
586  layer->setCustomProperty( property + "R", color.red() );
587  layer->setCustomProperty( property + "G", color.green() );
588  layer->setCustomProperty( property + "B", color.blue() );
589  if ( withAlpha )
590  layer->setCustomProperty( property + "A", color.alpha() );
591 }
592 
593 static QgsPalLayerSettings::SizeUnit _decodeUnits( const QString& str )
594 {
595  if ( str.compare( "Point", Qt::CaseInsensitive ) == 0
596  || str.compare( "Points", Qt::CaseInsensitive ) == 0 ) return QgsPalLayerSettings::Points;
597  if ( str.compare( "MapUnit", Qt::CaseInsensitive ) == 0
598  || str.compare( "MapUnits", Qt::CaseInsensitive ) == 0 ) return QgsPalLayerSettings::MapUnits;
599  if ( str.compare( "Percent", Qt::CaseInsensitive ) == 0 ) return QgsPalLayerSettings::Percent;
600  return QgsPalLayerSettings::MM; // "MM"
601 }
602 
603 static QPainter::CompositionMode _decodeBlendMode( const QString& str )
604 {
605  if ( str.compare( "Lighten", Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Lighten;
606  if ( str.compare( "Screen", Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Screen;
607  if ( str.compare( "Dodge", Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_ColorDodge;
608  if ( str.compare( "Addition", Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Plus;
609  if ( str.compare( "Darken", Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Darken;
610  if ( str.compare( "Multiply", Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Multiply;
611  if ( str.compare( "Burn", Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_ColorBurn;
612  if ( str.compare( "Overlay", Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Overlay;
613  if ( str.compare( "SoftLight", Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_SoftLight;
614  if ( str.compare( "HardLight", Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_HardLight;
615  if ( str.compare( "Difference", Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Difference;
616  if ( str.compare( "Subtract", Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Exclusion;
617  return QPainter::CompositionMode_SourceOver; // "Normal"
618 }
619 
620 static Qt::PenJoinStyle _decodePenJoinStyle( const QString& str )
621 {
622  if ( str.compare( "Miter", Qt::CaseInsensitive ) == 0 ) return Qt::MiterJoin;
623  if ( str.compare( "Round", Qt::CaseInsensitive ) == 0 ) return Qt::RoundJoin;
624  return Qt::BevelJoin; // "Bevel"
625 }
626 
628  QMap < QgsPalLayerSettings::DataDefinedProperties, QgsDataDefined* > & propertyMap )
629 {
630  if ( !layer )
631  {
632  return;
633  }
634 
635  QMapIterator<QgsPalLayerSettings::DataDefinedProperties, QPair<QString, int> > i( mDataDefinedNames );
636  while ( i.hasNext() )
637  {
638  i.next();
639  readDataDefinedProperty( layer, i.key(), propertyMap );
640  }
641 }
642 
644  const QMap < QgsPalLayerSettings::DataDefinedProperties, QgsDataDefined* > & propertyMap )
645 {
646  if ( !layer )
647  {
648  return;
649  }
650 
651  QMapIterator<QgsPalLayerSettings::DataDefinedProperties, QPair<QString, int> > i( mDataDefinedNames );
652  while ( i.hasNext() )
653  {
654  i.next();
655  QString newPropertyName = "labeling/dataDefined/" + i.value().first;
656  QVariant propertyValue = QVariant();
657 
658  QMap< QgsPalLayerSettings::DataDefinedProperties, QgsDataDefined* >::const_iterator it = propertyMap.find( i.key() );
659  if ( it != propertyMap.constEnd() )
660  {
661  QgsDataDefined* dd = it.value();
662  if ( dd )
663  {
664  bool active = dd->isActive();
665  bool useExpr = dd->useExpression();
666  QString expr = dd->expressionString();
667  QString field = dd->field();
668 
669  bool defaultVals = ( !active && !useExpr && expr.isEmpty() && field.isEmpty() );
670 
671  if ( !defaultVals )
672  {
673  // TODO: update this when project settings for labeling are migrated to better XML layout
674  QStringList values;
675  values << ( active ? "1" : "0" );
676  values << ( useExpr ? "1" : "0" );
677  values << expr;
678  values << field;
679  if ( !values.isEmpty() )
680  {
681  propertyValue = QVariant( values.join( "~~" ) );
682  }
683  }
684  }
685  }
686 
687  if ( propertyValue.isValid() )
688  {
689  layer->setCustomProperty( newPropertyName, propertyValue );
690  }
691  else
692  {
693  // remove unused properties
694  layer->removeCustomProperty( newPropertyName );
695  }
696 
697  if ( layer->customProperty( newPropertyName, QVariant() ).isValid() && i.value().second > -1 )
698  {
699  // remove old-style field index-based property, if still present
700  layer->removeCustomProperty( QString( "labeling/dataDefinedProperty" ) + QString::number( i.value().second ) );
701  }
702  }
703 }
704 
707  QMap < QgsPalLayerSettings::DataDefinedProperties, QgsDataDefined* > & propertyMap )
708 {
709  QString newPropertyName = "labeling/dataDefined/" + mDataDefinedNames.value( p ).first;
710  QVariant newPropertyField = layer->customProperty( newPropertyName, QVariant() );
711 
712  QString ddString = QString();
713  if ( newPropertyField.isValid() )
714  {
715  ddString = newPropertyField.toString();
716  }
717  else // maybe working with old-style field index-based property (< QGIS 2.0)
718  {
719  int oldIndx = mDataDefinedNames.value( p ).second;
720 
721  if ( oldIndx < 0 ) // something went wrong and we are working with new-style
722  {
723  return;
724  }
725 
726  QString oldPropertyName = "labeling/dataDefinedProperty" + QString::number( oldIndx );
727  QVariant oldPropertyField = layer->customProperty( oldPropertyName, QVariant() );
728 
729  if ( !oldPropertyField.isValid() )
730  {
731  return;
732  }
733 
734  // switch from old-style field index- to name-based properties
735  bool conversionOk;
736  int indx = oldPropertyField.toInt( &conversionOk );
737 
738  if ( conversionOk )
739  {
740  // Fix to migrate from old-style vector api, where returned QMap keys possibly
741  // had 'holes' in sequence of field indices, e.g. 0,2,3
742  // QgsAttrPalIndexNameHash provides a means of access field name in sequences from
743  // providers that procuded holes (e.g. PostGIS skipped geom column), otherwise it is empty
744  QgsAttrPalIndexNameHash oldIndicesToNames = layer->dataProvider()->palAttributeIndexNames();
745 
746  if ( !oldIndicesToNames.isEmpty() )
747  {
748  ddString = oldIndicesToNames.value( indx );
749  }
750  else
751  {
752  QgsFields fields = layer->dataProvider()->fields();
753  if ( indx < fields.size() ) // in case field count has changed
754  {
755  ddString = fields.at( indx ).name();
756  }
757  }
758  }
759 
760  if ( !ddString.isEmpty() )
761  {
762  //upgrade any existing property to field name-based
763  layer->setCustomProperty( newPropertyName, QVariant( updateDataDefinedString( ddString ) ) );
764 
765  // fix for buffer drawing triggered off of just its data defined size in the past (<2.0)
766  if ( oldIndx == 7 ) // old bufferSize enum
767  {
768  bufferDraw = true;
769  layer->setCustomProperty( "labeling/bufferDraw", true );
770  }
771 
772  // fix for scale visibility limits triggered off of just its data defined values in the past (<2.0)
773  if ( oldIndx == 16 || oldIndx == 17 ) // old minScale and maxScale enums
774  {
775  scaleVisibility = true;
776  layer->setCustomProperty( "labeling/scaleVisibility", true );
777  }
778  }
779 
780  // remove old-style field index-based property
781  layer->removeCustomProperty( oldPropertyName );
782  }
783 
784  if ( !ddString.isEmpty() && ddString != QString( "0~~0~~~~" ) )
785  {
786  // TODO: update this when project settings for labeling are migrated to better XML layout
787  QString newStyleString = updateDataDefinedString( ddString );
788  QStringList ddv = newStyleString.split( "~~" );
789 
790  QgsDataDefined* dd = new QgsDataDefined( ddv.at( 0 ).toInt(), ddv.at( 1 ).toInt(), ddv.at( 2 ), ddv.at( 3 ) );
791  propertyMap.insert( p, dd );
792  }
793  else
794  {
795  // remove unused properties
796  layer->removeCustomProperty( newPropertyName );
797  }
798 }
799 
801 {
802  if ( layer->customProperty( "labeling" ).toString() != QString( "pal" ) )
803  return; // there's no information available
804 
805  // NOTE: set defaults for newly added properties, for backwards compatibility
806 
807  enabled = layer->customProperty( "labeling/enabled" ).toBool();
808 
809  // text style
810  fieldName = layer->customProperty( "labeling/fieldName" ).toString();
811  isExpression = layer->customProperty( "labeling/isExpression" ).toBool();
812  QFont appFont = QApplication::font();
813  mTextFontFamily = layer->customProperty( "labeling/fontFamily", QVariant( appFont.family() ) ).toString();
814  QString fontFamily = mTextFontFamily;
816  {
817  // trigger to notify user about font family substitution (signal emitted in QgsVectorLayer::prepareLabelingAndDiagrams)
818  mTextFontFound = false;
819 
820  // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
821  // currently only defaults to matching algorithm for resolving [foundry], if a font of similar family is found (default for QFont)
822 
823  // for now, do not use matching algorithm for substitution if family not found, substitute default instead
824  fontFamily = appFont.family();
825  }
826 
827  double fontSize = layer->customProperty( "labeling/fontSize" ).toDouble();
828  fontSizeInMapUnits = layer->customProperty( "labeling/fontSizeInMapUnits" ).toBool();
829  int fontWeight = layer->customProperty( "labeling/fontWeight" ).toInt();
830  bool fontItalic = layer->customProperty( "labeling/fontItalic" ).toBool();
831  textFont = QFont( fontFamily, fontSize, fontWeight, fontItalic );
832  textFont.setPointSizeF( fontSize ); //double precision needed because of map units
833  textNamedStyle = layer->customProperty( "labeling/namedStyle", QVariant( "" ) ).toString();
834  QgsFontUtils::updateFontViaStyle( textFont, textNamedStyle ); // must come after textFont.setPointSizeF()
835  textFont.setCapitalization(( QFont::Capitalization )layer->customProperty( "labeling/fontCapitals", QVariant( 0 ) ).toUInt() );
836  textFont.setUnderline( layer->customProperty( "labeling/fontUnderline" ).toBool() );
837  textFont.setStrikeOut( layer->customProperty( "labeling/fontStrikeout" ).toBool() );
838  textFont.setLetterSpacing( QFont::AbsoluteSpacing, layer->customProperty( "labeling/fontLetterSpacing", QVariant( 0.0 ) ).toDouble() );
839  textFont.setWordSpacing( layer->customProperty( "labeling/fontWordSpacing", QVariant( 0.0 ) ).toDouble() );
840  textColor = _readColor( layer, "labeling/textColor", Qt::black, false );
841  textTransp = layer->customProperty( "labeling/textTransp" ).toInt();
843  ( QgsMapRenderer::BlendMode )layer->customProperty( "labeling/blendMode", QVariant( QgsMapRenderer::BlendNormal ) ).toUInt() );
844  previewBkgrdColor = QColor( layer->customProperty( "labeling/previewBkgrdColor", QVariant( "#ffffff" ) ).toString() );
845 
846 
847  // text formatting
848  wrapChar = layer->customProperty( "labeling/wrapChar" ).toString();
849  multilineHeight = layer->customProperty( "labeling/multilineHeight", QVariant( 1.0 ) ).toDouble();
850  multilineAlign = ( MultiLineAlign )layer->customProperty( "labeling/multilineAlign", QVariant( MultiLeft ) ).toUInt();
851  addDirectionSymbol = layer->customProperty( "labeling/addDirectionSymbol" ).toBool();
852  leftDirectionSymbol = layer->customProperty( "labeling/leftDirectionSymbol", QVariant( "<" ) ).toString();
853  rightDirectionSymbol = layer->customProperty( "labeling/rightDirectionSymbol", QVariant( ">" ) ).toString();
854  reverseDirectionSymbol = layer->customProperty( "labeling/reverseDirectionSymbol" ).toBool();
855  placeDirectionSymbol = ( DirectionSymbols )layer->customProperty( "labeling/placeDirectionSymbol", QVariant( SymbolLeftRight ) ).toUInt();
856  formatNumbers = layer->customProperty( "labeling/formatNumbers" ).toBool();
857  decimals = layer->customProperty( "labeling/decimals" ).toInt();
858  plusSign = layer->customProperty( "labeling/plussign" ).toInt();
859 
860  // text buffer
861  double bufSize = layer->customProperty( "labeling/bufferSize", QVariant( 0.0 ) ).toDouble();
862 
863  // fix for buffer being keyed off of just its size in the past (<2.0)
864  QVariant drawBuffer = layer->customProperty( "labeling/bufferDraw", QVariant() );
865  if ( drawBuffer.isValid() )
866  {
867  bufferDraw = drawBuffer.toBool();
868  bufferSize = bufSize;
869  }
870  else if ( bufSize != 0.0 )
871  {
872  bufferDraw = true;
873  bufferSize = bufSize;
874  }
875  else
876  {
877  // keep bufferSize at new 1.0 default
878  bufferDraw = false;
879  }
880 
881  bufferSizeInMapUnits = layer->customProperty( "labeling/bufferSizeInMapUnits" ).toBool();
882  bufferColor = _readColor( layer, "labeling/bufferColor", Qt::white, false );
883  bufferTransp = layer->customProperty( "labeling/bufferTransp" ).toInt();
885  ( QgsMapRenderer::BlendMode )layer->customProperty( "labeling/bufferBlendMode", QVariant( QgsMapRenderer::BlendNormal ) ).toUInt() );
886  bufferJoinStyle = ( Qt::PenJoinStyle )layer->customProperty( "labeling/bufferJoinStyle", QVariant( Qt::BevelJoin ) ).toUInt();
887  bufferNoFill = layer->customProperty( "labeling/bufferNoFill", QVariant( false ) ).toBool();
888 
889  // background
890  shapeDraw = layer->customProperty( "labeling/shapeDraw", QVariant( false ) ).toBool();
891  shapeType = ( ShapeType )layer->customProperty( "labeling/shapeType", QVariant( ShapeRectangle ) ).toUInt();
892  shapeSVGFile = layer->customProperty( "labeling/shapeSVGFile", QVariant( "" ) ).toString();
893  shapeSizeType = ( SizeType )layer->customProperty( "labeling/shapeSizeType", QVariant( SizeBuffer ) ).toUInt();
894  shapeSize = QPointF( layer->customProperty( "labeling/shapeSizeX", QVariant( 0.0 ) ).toDouble(),
895  layer->customProperty( "labeling/shapeSizeY", QVariant( 0.0 ) ).toDouble() );
896  shapeSizeUnits = ( SizeUnit )layer->customProperty( "labeling/shapeSizeUnits", QVariant( MM ) ).toUInt();
897  shapeRotationType = ( RotationType )layer->customProperty( "labeling/shapeRotationType", QVariant( RotationSync ) ).toUInt();
898  shapeRotation = layer->customProperty( "labeling/shapeRotation", QVariant( 0.0 ) ).toDouble();
899  shapeOffset = QPointF( layer->customProperty( "labeling/shapeOffsetX", QVariant( 0.0 ) ).toDouble(),
900  layer->customProperty( "labeling/shapeOffsetY", QVariant( 0.0 ) ).toDouble() );
901  shapeOffsetUnits = ( SizeUnit )layer->customProperty( "labeling/shapeOffsetUnits", QVariant( MM ) ).toUInt();
902  shapeRadii = QPointF( layer->customProperty( "labeling/shapeRadiiX", QVariant( 0.0 ) ).toDouble(),
903  layer->customProperty( "labeling/shapeRadiiY", QVariant( 0.0 ) ).toDouble() );
904  shapeRadiiUnits = ( SizeUnit )layer->customProperty( "labeling/shapeRadiiUnits", QVariant( MM ) ).toUInt();
905  shapeFillColor = _readColor( layer, "labeling/shapeFillColor", Qt::white, true );
906  shapeBorderColor = _readColor( layer, "labeling/shapeBorderColor", Qt::darkGray, true );
907  shapeBorderWidth = layer->customProperty( "labeling/shapeBorderWidth", QVariant( .0 ) ).toDouble();
908  shapeBorderWidthUnits = ( SizeUnit )layer->customProperty( "labeling/shapeBorderWidthUnits", QVariant( MM ) ).toUInt();
909  shapeJoinStyle = ( Qt::PenJoinStyle )layer->customProperty( "labeling/shapeJoinStyle", QVariant( Qt::BevelJoin ) ).toUInt();
910  shapeTransparency = layer->customProperty( "labeling/shapeTransparency", QVariant( 0 ) ).toInt();
912  ( QgsMapRenderer::BlendMode )layer->customProperty( "labeling/shapeBlendMode", QVariant( QgsMapRenderer::BlendNormal ) ).toUInt() );
913 
914  // drop shadow
915  shadowDraw = layer->customProperty( "labeling/shadowDraw", QVariant( false ) ).toBool();
916  shadowUnder = ( ShadowType )layer->customProperty( "labeling/shadowUnder", QVariant( ShadowLowest ) ).toUInt();//ShadowLowest;
917  shadowOffsetAngle = layer->customProperty( "labeling/shadowOffsetAngle", QVariant( 135 ) ).toInt();
918  shadowOffsetDist = layer->customProperty( "labeling/shadowOffsetDist", QVariant( 1.0 ) ).toDouble();
919  shadowOffsetUnits = ( SizeUnit )layer->customProperty( "labeling/shadowOffsetUnits", QVariant( MM ) ).toUInt();
920  shadowOffsetGlobal = layer->customProperty( "labeling/shadowOffsetGlobal", QVariant( true ) ).toBool();
921  shadowRadius = layer->customProperty( "labeling/shadowRadius", QVariant( 1.5 ) ).toDouble();
922  shadowRadiusUnits = ( SizeUnit )layer->customProperty( "labeling/shadowRadiusUnits", QVariant( MM ) ).toUInt();
923  shadowRadiusAlphaOnly = layer->customProperty( "labeling/shadowRadiusAlphaOnly", QVariant( false ) ).toBool();
924  shadowTransparency = layer->customProperty( "labeling/shadowTransparency", QVariant( 30 ) ).toInt();
925  shadowScale = layer->customProperty( "labeling/shadowScale", QVariant( 100 ) ).toInt();
926  shadowColor = _readColor( layer, "labeling/shadowColor", Qt::black, false );
928  ( QgsMapRenderer::BlendMode )layer->customProperty( "labeling/shadowBlendMode", QVariant( QgsMapRenderer::BlendMultiply ) ).toUInt() );
929 
930  // placement
931  placement = ( Placement )layer->customProperty( "labeling/placement" ).toInt();
932  placementFlags = layer->customProperty( "labeling/placementFlags" ).toUInt();
933  centroidWhole = layer->customProperty( "labeling/centroidWhole", QVariant( false ) ).toBool();
934  dist = layer->customProperty( "labeling/dist" ).toDouble();
935  distInMapUnits = layer->customProperty( "labeling/distInMapUnits" ).toBool();
936  quadOffset = ( QuadrantPosition )layer->customProperty( "labeling/quadOffset", QVariant( QuadrantOver ) ).toUInt();
937  xOffset = layer->customProperty( "labeling/xOffset", QVariant( 0.0 ) ).toDouble();
938  yOffset = layer->customProperty( "labeling/yOffset", QVariant( 0.0 ) ).toDouble();
939  labelOffsetInMapUnits = layer->customProperty( "labeling/labelOffsetInMapUnits", QVariant( true ) ).toBool();
940  angleOffset = layer->customProperty( "labeling/angleOffset", QVariant( 0.0 ) ).toDouble();
941  preserveRotation = layer->customProperty( "labeling/preserveRotation", QVariant( true ) ).toBool();
942  maxCurvedCharAngleIn = layer->customProperty( "labeling/maxCurvedCharAngleIn", QVariant( 20.0 ) ).toDouble();
943  maxCurvedCharAngleOut = layer->customProperty( "labeling/maxCurvedCharAngleOut", QVariant( -20.0 ) ).toDouble();
944  priority = layer->customProperty( "labeling/priority" ).toInt();
945 
946  // rendering
947  int scalemn = layer->customProperty( "labeling/scaleMin", QVariant( 0 ) ).toInt();
948  int scalemx = layer->customProperty( "labeling/scaleMax", QVariant( 0 ) ).toInt();
949 
950  // fix for scale visibility limits being keyed off of just its values in the past (<2.0)
951  QVariant scalevis = layer->customProperty( "labeling/scaleVisibility", QVariant() );
952  if ( scalevis.isValid() )
953  {
954  scaleVisibility = scalevis.toBool();
955  scaleMin = scalemn;
956  scaleMax = scalemx;
957  }
958  else if ( scalemn > 0 || scalemx > 0 )
959  {
960  scaleVisibility = true;
961  scaleMin = scalemn;
962  scaleMax = scalemx;
963  }
964  else
965  {
966  // keep scaleMin and scaleMax at new 1.0 defaults (1 and 10000000, were 0 and 0)
967  scaleVisibility = false;
968  }
969 
970 
971  fontLimitPixelSize = layer->customProperty( "labeling/fontLimitPixelSize", QVariant( false ) ).toBool();
972  fontMinPixelSize = layer->customProperty( "labeling/fontMinPixelSize", QVariant( 0 ) ).toInt();
973  fontMaxPixelSize = layer->customProperty( "labeling/fontMaxPixelSize", QVariant( 10000 ) ).toInt();
974  displayAll = layer->customProperty( "labeling/displayAll", QVariant( false ) ).toBool();
975  upsidedownLabels = ( UpsideDownLabels )layer->customProperty( "labeling/upsidedownLabels", QVariant( Upright ) ).toUInt();
976 
977  labelPerPart = layer->customProperty( "labeling/labelPerPart" ).toBool();
978  mergeLines = layer->customProperty( "labeling/mergeLines" ).toBool();
979  minFeatureSize = layer->customProperty( "labeling/minFeatureSize" ).toDouble();
980  limitNumLabels = layer->customProperty( "labeling/limitNumLabels", QVariant( false ) ).toBool();
981  maxNumLabels = layer->customProperty( "labeling/maxNumLabels", QVariant( 2000 ) ).toInt();
982  obstacle = layer->customProperty( "labeling/obstacle", QVariant( true ) ).toBool();
983 
985 }
986 
988 {
989  // this is a mark that labeling information is present
990  layer->setCustomProperty( "labeling", "pal" );
991 
992  layer->setCustomProperty( "labeling/enabled", enabled );
993 
994  // text style
995  layer->setCustomProperty( "labeling/fieldName", fieldName );
996  layer->setCustomProperty( "labeling/isExpression", isExpression );
997  layer->setCustomProperty( "labeling/fontFamily", textFont.family() );
998  layer->setCustomProperty( "labeling/namedStyle", textNamedStyle );
999  layer->setCustomProperty( "labeling/fontSize", textFont.pointSizeF() );
1000  layer->setCustomProperty( "labeling/fontSizeInMapUnits", fontSizeInMapUnits );
1001  layer->setCustomProperty( "labeling/fontWeight", textFont.weight() );
1002  layer->setCustomProperty( "labeling/fontItalic", textFont.italic() );
1003  layer->setCustomProperty( "labeling/fontBold", textFont.bold() );
1004  layer->setCustomProperty( "labeling/fontStrikeout", textFont.strikeOut() );
1005  layer->setCustomProperty( "labeling/fontUnderline", textFont.underline() );
1006  _writeColor( layer, "labeling/textColor", textColor );
1007  layer->setCustomProperty( "labeling/fontCapitals", ( unsigned int )textFont.capitalization() );
1008  layer->setCustomProperty( "labeling/fontLetterSpacing", textFont.letterSpacing() );
1009  layer->setCustomProperty( "labeling/fontWordSpacing", textFont.wordSpacing() );
1010  layer->setCustomProperty( "labeling/textTransp", textTransp );
1011  layer->setCustomProperty( "labeling/blendMode", QgsMapRenderer::getBlendModeEnum( blendMode ) );
1012  layer->setCustomProperty( "labeling/previewBkgrdColor", previewBkgrdColor.name() );
1013 
1014  // text formatting
1015  layer->setCustomProperty( "labeling/wrapChar", wrapChar );
1016  layer->setCustomProperty( "labeling/multilineHeight", multilineHeight );
1017  layer->setCustomProperty( "labeling/multilineAlign", ( unsigned int )multilineAlign );
1018  layer->setCustomProperty( "labeling/addDirectionSymbol", addDirectionSymbol );
1019  layer->setCustomProperty( "labeling/leftDirectionSymbol", leftDirectionSymbol );
1020  layer->setCustomProperty( "labeling/rightDirectionSymbol", rightDirectionSymbol );
1021  layer->setCustomProperty( "labeling/reverseDirectionSymbol", reverseDirectionSymbol );
1022  layer->setCustomProperty( "labeling/placeDirectionSymbol", ( unsigned int )placeDirectionSymbol );
1023  layer->setCustomProperty( "labeling/formatNumbers", formatNumbers );
1024  layer->setCustomProperty( "labeling/decimals", decimals );
1025  layer->setCustomProperty( "labeling/plussign", plusSign );
1026 
1027  // text buffer
1028  layer->setCustomProperty( "labeling/bufferDraw", bufferDraw );
1029  layer->setCustomProperty( "labeling/bufferSize", bufferSize );
1030  layer->setCustomProperty( "labeling/bufferSizeInMapUnits", bufferSizeInMapUnits );
1031  _writeColor( layer, "labeling/bufferColor", bufferColor );
1032  layer->setCustomProperty( "labeling/bufferNoFill", bufferNoFill );
1033  layer->setCustomProperty( "labeling/bufferTransp", bufferTransp );
1034  layer->setCustomProperty( "labeling/bufferJoinStyle", ( unsigned int )bufferJoinStyle );
1035  layer->setCustomProperty( "labeling/bufferBlendMode", QgsMapRenderer::getBlendModeEnum( bufferBlendMode ) );
1036 
1037  // background
1038  layer->setCustomProperty( "labeling/shapeDraw", shapeDraw );
1039  layer->setCustomProperty( "labeling/shapeType", ( unsigned int )shapeType );
1040  layer->setCustomProperty( "labeling/shapeSVGFile", shapeSVGFile );
1041  layer->setCustomProperty( "labeling/shapeSizeType", ( unsigned int )shapeSizeType );
1042  layer->setCustomProperty( "labeling/shapeSizeX", shapeSize.x() );
1043  layer->setCustomProperty( "labeling/shapeSizeY", shapeSize.y() );
1044  layer->setCustomProperty( "labeling/shapeSizeUnits", ( unsigned int )shapeSizeUnits );
1045  layer->setCustomProperty( "labeling/shapeRotationType", ( unsigned int )shapeRotationType );
1046  layer->setCustomProperty( "labeling/shapeRotation", shapeRotation );
1047  layer->setCustomProperty( "labeling/shapeOffsetX", shapeOffset.x() );
1048  layer->setCustomProperty( "labeling/shapeOffsetY", shapeOffset.y() );
1049  layer->setCustomProperty( "labeling/shapeOffsetUnits", ( unsigned int )shapeOffsetUnits );
1050  layer->setCustomProperty( "labeling/shapeRadiiX", shapeRadii.x() );
1051  layer->setCustomProperty( "labeling/shapeRadiiY", shapeRadii.y() );
1052  layer->setCustomProperty( "labeling/shapeRadiiUnits", ( unsigned int )shapeRadiiUnits );
1053  _writeColor( layer, "labeling/shapeFillColor", shapeFillColor, true );
1054  _writeColor( layer, "labeling/shapeBorderColor", shapeBorderColor, true );
1055  layer->setCustomProperty( "labeling/shapeBorderWidth", shapeBorderWidth );
1056  layer->setCustomProperty( "labeling/shapeBorderWidthUnits", ( unsigned int )shapeBorderWidthUnits );
1057  layer->setCustomProperty( "labeling/shapeJoinStyle", ( unsigned int )shapeJoinStyle );
1058  layer->setCustomProperty( "labeling/shapeTransparency", shapeTransparency );
1059  layer->setCustomProperty( "labeling/shapeBlendMode", QgsMapRenderer::getBlendModeEnum( shapeBlendMode ) );
1060 
1061  // drop shadow
1062  layer->setCustomProperty( "labeling/shadowDraw", shadowDraw );
1063  layer->setCustomProperty( "labeling/shadowUnder", ( unsigned int )shadowUnder );
1064  layer->setCustomProperty( "labeling/shadowOffsetAngle", shadowOffsetAngle );
1065  layer->setCustomProperty( "labeling/shadowOffsetDist", shadowOffsetDist );
1066  layer->setCustomProperty( "labeling/shadowOffsetUnits", ( unsigned int )shadowOffsetUnits );
1067  layer->setCustomProperty( "labeling/shadowOffsetGlobal", shadowOffsetGlobal );
1068  layer->setCustomProperty( "labeling/shadowRadius", shadowRadius );
1069  layer->setCustomProperty( "labeling/shadowRadiusUnits", ( unsigned int )shadowRadiusUnits );
1070  layer->setCustomProperty( "labeling/shadowRadiusAlphaOnly", shadowRadiusAlphaOnly );
1071  layer->setCustomProperty( "labeling/shadowTransparency", shadowTransparency );
1072  layer->setCustomProperty( "labeling/shadowScale", shadowScale );
1073  _writeColor( layer, "labeling/shadowColor", shadowColor, false );
1074  layer->setCustomProperty( "labeling/shadowBlendMode", QgsMapRenderer::getBlendModeEnum( shadowBlendMode ) );
1075 
1076  // placement
1077  layer->setCustomProperty( "labeling/placement", placement );
1078  layer->setCustomProperty( "labeling/placementFlags", ( unsigned int )placementFlags );
1079  layer->setCustomProperty( "labeling/centroidWhole", centroidWhole );
1080  layer->setCustomProperty( "labeling/dist", dist );
1081  layer->setCustomProperty( "labeling/distInMapUnits", distInMapUnits );
1082  layer->setCustomProperty( "labeling/quadOffset", ( unsigned int )quadOffset );
1083  layer->setCustomProperty( "labeling/xOffset", xOffset );
1084  layer->setCustomProperty( "labeling/yOffset", yOffset );
1085  layer->setCustomProperty( "labeling/labelOffsetInMapUnits", labelOffsetInMapUnits );
1086  layer->setCustomProperty( "labeling/angleOffset", angleOffset );
1087  layer->setCustomProperty( "labeling/preserveRotation", preserveRotation );
1088  layer->setCustomProperty( "labeling/maxCurvedCharAngleIn", maxCurvedCharAngleIn );
1089  layer->setCustomProperty( "labeling/maxCurvedCharAngleOut", maxCurvedCharAngleOut );
1090  layer->setCustomProperty( "labeling/priority", priority );
1091 
1092  // rendering
1093  layer->setCustomProperty( "labeling/scaleVisibility", scaleVisibility );
1094  layer->setCustomProperty( "labeling/scaleMin", scaleMin );
1095  layer->setCustomProperty( "labeling/scaleMax", scaleMax );
1096  layer->setCustomProperty( "labeling/fontLimitPixelSize", fontLimitPixelSize );
1097  layer->setCustomProperty( "labeling/fontMinPixelSize", fontMinPixelSize );
1098  layer->setCustomProperty( "labeling/fontMaxPixelSize", fontMaxPixelSize );
1099  layer->setCustomProperty( "labeling/displayAll", displayAll );
1100  layer->setCustomProperty( "labeling/upsidedownLabels", ( unsigned int )upsidedownLabels );
1101 
1102  layer->setCustomProperty( "labeling/labelPerPart", labelPerPart );
1103  layer->setCustomProperty( "labeling/mergeLines", mergeLines );
1104  layer->setCustomProperty( "labeling/minFeatureSize", minFeatureSize );
1105  layer->setCustomProperty( "labeling/limitNumLabels", limitNumLabels );
1106  layer->setCustomProperty( "labeling/maxNumLabels", maxNumLabels );
1107  layer->setCustomProperty( "labeling/obstacle", obstacle );
1108 
1110 }
1111 
1113  bool active, bool useExpr, const QString& expr, const QString& field )
1114 {
1115  bool defaultVals = ( !active && !useExpr && expr.isEmpty() && field.isEmpty() );
1116 
1117  if ( dataDefinedProperties.contains( p ) )
1118  {
1119  QMap< QgsPalLayerSettings::DataDefinedProperties, QgsDataDefined* >::const_iterator it = dataDefinedProperties.find( p );
1120  if ( it != dataDefinedProperties.constEnd() )
1121  {
1122  QgsDataDefined* dd = it.value();
1123  dd->setActive( active );
1124  dd->setUseExpression( useExpr );
1125  dd->setExpressionString( expr );
1126  dd->setField( field );
1127  }
1128  }
1129  else if ( !defaultVals )
1130  {
1131  QgsDataDefined* dd = new QgsDataDefined( active, useExpr, expr, field );
1132  dataDefinedProperties.insert( p, dd );
1133  }
1134 }
1135 
1137 {
1138  QMap< DataDefinedProperties, QgsDataDefined* >::iterator it = dataDefinedProperties.find( p );
1139  if ( it != dataDefinedProperties.end() )
1140  {
1141  delete( it.value() );
1142  dataDefinedProperties.erase( it );
1143  }
1144 }
1145 
1146 QString QgsPalLayerSettings::updateDataDefinedString( const QString& value )
1147 {
1148  // TODO: update or remove this when project settings for labeling are migrated to better XML layout
1149  QString newValue = value;
1150  if ( !value.isEmpty() && !value.contains( "~~" ) )
1151  {
1152  QStringList values;
1153  values << "1"; // all old-style values are active if not empty
1154  values << "0";
1155  values << "";
1156  values << value; // all old-style values are only field names
1157  newValue = values.join( "~~" );
1158  }
1159 
1160  return newValue;
1161 }
1162 
1164 {
1165  QMap< QgsPalLayerSettings::DataDefinedProperties, QgsDataDefined* >::const_iterator it = dataDefinedProperties.find( p );
1166  if ( it != dataDefinedProperties.constEnd() )
1167  {
1168  return it.value();
1169  }
1170  return 0;
1171 }
1172 
1174 {
1175  QMap<QString, QString> map;
1176  QMap< QgsPalLayerSettings::DataDefinedProperties, QgsDataDefined* >::const_iterator it = dataDefinedProperties.find( p );
1177  if ( it != dataDefinedProperties.constEnd() )
1178  {
1179  return it.value()->toMap();
1180  }
1181  return map;
1182 }
1183 
1185 {
1186  if ( !dataDefinedProperties.contains( p ) )
1187  {
1188  return QVariant();
1189  }
1190 
1191  QgsDataDefined* dd = 0;
1192  QMap< DataDefinedProperties, QgsDataDefined* >::const_iterator it = dataDefinedProperties.find( p );
1193  if ( it != dataDefinedProperties.constEnd() )
1194  {
1195  dd = it.value();
1196  }
1197 
1198  if ( !dd )
1199  {
1200  return QVariant();
1201  }
1202 
1203  if ( !dd->isActive() )
1204  {
1205  return QVariant();
1206  }
1207 
1208  QVariant result = QVariant();
1209  bool useExpression = dd->useExpression();
1210  QString field = dd->field();
1211 
1212  //QgsDebugMsgLevel( QString( "isActive:" ) + isActive ? "1" : "0", 4 );
1213  //QgsDebugMsgLevel( QString( "useExpression:" ) + useExpression ? "1" : "0", 4 );
1214  //QgsDebugMsgLevel( QString( "expression:" ) + dd->expressionString(), 4 );
1215  //QgsDebugMsgLevel( QString( "field:" ) + field, 4 );
1216 
1217  if ( useExpression && dd->expressionIsPrepared() )
1218  {
1219  QgsExpression* expr = dd->expression();
1220  //QgsDebugMsgLevel( QString( "expr columns:" ) + expr->referencedColumns().join( "," ), 4 );
1221 
1222  result = expr->evaluate( &f );
1223  if ( expr->hasEvalError() )
1224  {
1225  QgsDebugMsgLevel( QString( "Evaluate error:" ) + expr->evalErrorString(), 4 );
1226  return QVariant();
1227  }
1228  }
1229  else if ( !useExpression && !field.isEmpty() )
1230  {
1231  // use direct attribute access instead of evaluating "field" expression (much faster)
1232  int indx = fields.indexFromName( field );
1233  if ( indx != -1 )
1234  {
1235  result = f.attribute( indx );
1236  }
1237  }
1238  return result;
1239 }
1240 
1242 {
1243  // null passed-around QVariant
1244  exprVal.clear();
1245 
1246  QVariant result = dataDefinedValue( p, *mCurFeat, *mCurFields );
1247 
1248  if ( result.isValid() ) // filter NULL values? i.e. && !result.isNull()
1249  {
1250  //QgsDebugMsgLevel( QString( "result type:" ) + QString( result.typeName() ), 4 );
1251  //QgsDebugMsgLevel( QString( "result string:" ) + result.toString(), 4 );
1252  exprVal = result;
1253  return true;
1254  }
1255 
1256  return false;
1257 }
1258 
1260 {
1261  bool isActive = false;
1262  QMap< DataDefinedProperties, QgsDataDefined* >::const_iterator it = dataDefinedProperties.find( p );
1263  if ( it != dataDefinedProperties.constEnd() )
1264  {
1265  isActive = it.value()->isActive();
1266  }
1267 
1268  return isActive;
1269 }
1270 
1272 {
1273  bool useExpression = false;
1274  QMap< DataDefinedProperties, QgsDataDefined* >::const_iterator it = dataDefinedProperties.find( p );
1275  if ( it != dataDefinedProperties.constEnd() )
1276  {
1277  useExpression = it.value()->useExpression();
1278  }
1279 
1280  return useExpression;
1281 }
1282 
1283 bool QgsPalLayerSettings::checkMinimumSizeMM( const QgsRenderContext& ct, QgsGeometry* geom, double minSize ) const
1284 {
1285  if ( minSize <= 0 )
1286  {
1287  return true;
1288  }
1289 
1290  if ( !geom )
1291  {
1292  return false;
1293  }
1294 
1295  QGis::GeometryType featureType = geom->type();
1296  if ( featureType == QGis::Point ) //minimum size does not apply to point features
1297  {
1298  return true;
1299  }
1300 
1301  double mapUnitsPerMM = ct.mapToPixel().mapUnitsPerPixel() * ct.scaleFactor();
1302  if ( featureType == QGis::Line )
1303  {
1304  double length = geom->length();
1305  if ( length >= 0.0 )
1306  {
1307  return ( length >= ( minSize * mapUnitsPerMM ) );
1308  }
1309  }
1310  else if ( featureType == QGis::Polygon )
1311  {
1312  double area = geom->area();
1313  if ( area >= 0.0 )
1314  {
1315  return ( sqrt( area ) >= ( minSize * mapUnitsPerMM ) );
1316  }
1317  }
1318  return true; //should never be reached. Return true in this case to label such geometries anyway.
1319 }
1320 
1321 void QgsPalLayerSettings::calculateLabelSize( const QFontMetricsF* fm, QString text, double& labelX, double& labelY, QgsFeature* f )
1322 {
1323  if ( !fm || !f )
1324  {
1325  return;
1326  }
1327 
1328  QString wrapchr = wrapChar;
1329  double multilineH = multilineHeight;
1330 
1331  bool addDirSymb = addDirectionSymbol;
1332  QString leftDirSymb = leftDirectionSymbol;
1333  QString rightDirSymb = rightDirectionSymbol;
1335 
1336  if ( f == mCurFeat ) // called internally, use any stored data defined values
1337  {
1339  {
1340  wrapchr = dataDefinedValues.value( QgsPalLayerSettings::MultiLineWrapChar ).toString();
1341  }
1342 
1344  {
1345  multilineH = dataDefinedValues.value( QgsPalLayerSettings::MultiLineHeight ).toDouble();
1346  }
1347 
1349  {
1350  addDirSymb = dataDefinedValues.value( QgsPalLayerSettings::DirSymbDraw ).toBool();
1351  }
1352 
1353  if ( addDirSymb )
1354  {
1355 
1357  {
1358  leftDirSymb = dataDefinedValues.value( QgsPalLayerSettings::DirSymbLeft ).toString();
1359  }
1361  {
1362  rightDirSymb = dataDefinedValues.value( QgsPalLayerSettings::DirSymbRight ).toString();
1363  }
1364 
1366  {
1368  }
1369 
1370  }
1371 
1372  }
1373  else // called externally with passed-in feature, evaluate data defined
1374  {
1375  QVariant exprVal = QVariant();
1376 
1378  if ( exprVal.isValid() )
1379  {
1380  wrapchr = exprVal.toString();
1381  }
1382  exprVal.clear();
1384  if ( exprVal.isValid() )
1385  {
1386  bool ok;
1387  double size = exprVal.toDouble( &ok );
1388  if ( ok )
1389  {
1390  multilineH = size;
1391  }
1392  }
1393 
1394  exprVal.clear();
1396  if ( exprVal.isValid() )
1397  {
1398  addDirSymb = exprVal.toBool();
1399  }
1400 
1401  if ( addDirSymb ) // don't do extra evaluations if not adding a direction symbol
1402  {
1403  exprVal.clear();
1405  if ( exprVal.isValid() )
1406  {
1407  leftDirSymb = exprVal.toString();
1408  }
1409  exprVal.clear();
1411  if ( exprVal.isValid() )
1412  {
1413  rightDirSymb = exprVal.toString();
1414  }
1415  exprVal.clear();
1417  if ( exprVal.isValid() )
1418  {
1419  bool ok;
1420  int enmint = exprVal.toInt( &ok );
1421  if ( ok )
1422  {
1423  placeDirSymb = ( QgsPalLayerSettings::DirectionSymbols )enmint;
1424  }
1425  }
1426  }
1427 
1428  }
1429 
1430  if ( wrapchr.isEmpty() )
1431  {
1432  wrapchr = QString( "\n" ); // default to new line delimiter
1433  }
1434 
1435  //consider the space needed for the direction symbol
1436  if ( addDirSymb && placement == QgsPalLayerSettings::Line
1437  && ( !leftDirSymb.isEmpty() || !rightDirSymb.isEmpty() ) )
1438  {
1439  QString dirSym = leftDirSymb;
1440 
1441  if ( fm->width( rightDirSymb ) > fm->width( dirSym ) )
1442  dirSym = rightDirSymb;
1443 
1444  if ( placeDirSymb == QgsPalLayerSettings::SymbolLeftRight )
1445  {
1446  text.append( dirSym );
1447  }
1448  else
1449  {
1450  text.prepend( dirSym + wrapchr ); // SymbolAbove or SymbolBelow
1451  }
1452  }
1453 
1454  double w = 0.0, h = 0.0;
1455  QStringList multiLineSplit = text.split( wrapchr );
1456  int lines = multiLineSplit.size();
1457 
1458  double labelHeight = fm->ascent() + fm->descent(); // ignore +1 for baseline
1459 
1460  h += fm->height() + ( double )(( lines - 1 ) * labelHeight * multilineH );
1461  h /= rasterCompressFactor;
1462 
1463  for ( int i = 0; i < lines; ++i )
1464  {
1465  double width = fm->width( multiLineSplit.at( i ) );
1466  if ( width > w )
1467  {
1468  w = width;
1469  }
1470  }
1471  w /= rasterCompressFactor;
1472  QgsPoint ptSize = xform->toMapCoordinatesF( w, h );
1473 
1474  labelX = qAbs( ptSize.x() - ptZero.x() );
1475  labelY = qAbs( ptSize.y() - ptZero.y() );
1476 }
1477 
1479 {
1480  Q_UNUSED( layer );
1481 
1482  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
1483  mCurFeat = &f;
1484 // mCurFields = &layer->pendingFields();
1485 
1486  // store data defined-derived values for later adding to QgsPalGeometry for use during rendering
1487  dataDefinedValues.clear();
1488 
1489 
1490  // data defined show label? defaults to show label if not 0
1492  {
1493  QgsDebugMsgLevel( QString( "exprVal Show:%1" ).arg( exprVal.toBool() ? "true" : "false" ), 4 );
1494  if ( !exprVal.toBool() )
1495  {
1496  return;
1497  }
1498  }
1499 
1500  // data defined scale visibility?
1501  bool useScaleVisibility = scaleVisibility;
1503  {
1504  QgsDebugMsgLevel( QString( "exprVal ScaleVisibility:%1" ).arg( exprVal.toBool() ? "true" : "false" ), 4 );
1505  useScaleVisibility = exprVal.toBool();
1506  }
1507 
1508  if ( useScaleVisibility )
1509  {
1510  // data defined min scale?
1511  double minScale = scaleMin;
1513  {
1514  QgsDebugMsgLevel( QString( "exprVal MinScale:%1" ).arg( exprVal.toDouble() ), 4 );
1515  bool conversionOk;
1516  double mins = exprVal.toDouble( &conversionOk );
1517  if ( conversionOk )
1518  {
1519  minScale = mins;
1520  }
1521  }
1522 
1523  // scales closer than 1:1
1524  if ( minScale < 0 )
1525  {
1526  minScale = 1 / qAbs( minScale );
1527  }
1528 
1529  if ( minScale != 0 && context.rendererScale() < minScale )
1530  {
1531  return;
1532  }
1533 
1534  // data defined max scale?
1535  double maxScale = scaleMax;
1537  {
1538  QgsDebugMsgLevel( QString( "exprVal MaxScale:%1" ).arg( exprVal.toDouble() ), 4 );
1539  bool conversionOk;
1540  double maxs = exprVal.toDouble( &conversionOk );
1541  if ( conversionOk )
1542  {
1543  maxScale = maxs;
1544  }
1545  }
1546 
1547  // scales closer than 1:1
1548  if ( maxScale < 0 )
1549  {
1550  maxScale = 1 / qAbs( maxScale );
1551  }
1552 
1553  if ( maxScale != 0 && context.rendererScale() > maxScale )
1554  {
1555  return;
1556  }
1557  }
1558 
1559  QFont labelFont = textFont;
1560  // labelFont will be added to label's QgsPalGeometry for use during label painting
1561 
1562  // data defined font units?
1565  {
1566  QString units = exprVal.toString().trimmed();
1567  QgsDebugMsgLevel( QString( "exprVal Font units:%1" ).arg( units ), 4 );
1568  if ( !units.isEmpty() )
1569  {
1570  fontunits = _decodeUnits( units );
1571  }
1572  }
1573 
1574  //data defined label size?
1575  double fontSize = labelFont.pointSizeF(); // font size doesn't have its own class data member
1577  {
1578  QgsDebugMsgLevel( QString( "exprVal Size:%1" ).arg( exprVal.toDouble() ), 4 );
1579  bool ok;
1580  double size = exprVal.toDouble( &ok );
1581  if ( ok )
1582  {
1583  fontSize = size;
1584  }
1585  }
1586  if ( fontSize <= 0.0 )
1587  {
1588  return;
1589  }
1590 
1591  int fontPixelSize = sizeToPixel( fontSize, context, fontunits, true );
1592  // don't try to show font sizes less than 1 pixel (Qt complains)
1593  if ( fontPixelSize < 1 )
1594  {
1595  return;
1596  }
1597  labelFont.setPixelSize( fontPixelSize );
1598 
1599  // NOTE: labelFont now always has pixelSize set, so pointSize or pointSizeF might return -1
1600 
1601  // defined 'minimum/maximum pixel font size'?
1602  if ( fontunits == QgsPalLayerSettings::MapUnits )
1603  {
1604  bool useFontLimitPixelSize = fontLimitPixelSize;
1606  {
1607  QgsDebugMsgLevel( QString( "exprVal FontLimitPixel:%1" ).arg( exprVal.toBool() ? "true" : "false" ), 4 );
1608  useFontLimitPixelSize = exprVal.toBool();
1609  }
1610 
1611  if ( useFontLimitPixelSize )
1612  {
1613  int fontMinPixel = fontMinPixelSize;
1615  {
1616  bool ok;
1617  int sizeInt = exprVal.toInt( &ok );
1618  QgsDebugMsgLevel( QString( "exprVal FontMinPixel:%1" ).arg( sizeInt ), 4 );
1619  if ( ok )
1620  {
1621  fontMinPixel = sizeInt;
1622  }
1623  }
1624 
1625  int fontMaxPixel = fontMaxPixelSize;
1627  {
1628  bool ok;
1629  int sizeInt = exprVal.toInt( &ok );
1630  QgsDebugMsgLevel( QString( "exprVal FontMaxPixel:%1" ).arg( sizeInt ), 4 );
1631  if ( ok )
1632  {
1633  fontMaxPixel = sizeInt;
1634  }
1635  }
1636 
1637  if ( fontMinPixel > labelFont.pixelSize() || labelFont.pixelSize() > fontMaxPixel )
1638  {
1639  return;
1640  }
1641  }
1642  }
1643 
1644  // NOTE: the following parsing functions calculate and store any data defined values for later use in QgsPalLabeling::drawLabeling
1645  // this is done to provide clarity, and because such parsing is not directly related to PAL feature registration calculations
1646 
1647  // calculate rest of font attributes and store any data defined values
1648  // this is done here for later use in making label backgrounds part of collision management (when implemented)
1649  parseTextStyle( labelFont, fontunits, context );
1651  parseTextBuffer();
1653  parseDropShadow();
1654 
1655  QString labelText;
1656 
1657  // Check to see if we are a expression string.
1658  if ( isExpression )
1659  {
1661  if ( exp->hasParserError() )
1662  {
1663  QgsDebugMsgLevel( QString( "Expression parser error:%1" ).arg( exp->parserErrorString() ), 4 );
1664  return;
1665  }
1666  exp->setScale( context.rendererScale() );
1667 // QVariant result = exp->evaluate( &f, layer->pendingFields() );
1668  QVariant result = exp->evaluate( &f ); // expression prepared in QgsPalLabeling::prepareLayer()
1669  if ( exp->hasEvalError() )
1670  {
1671  QgsDebugMsgLevel( QString( "Expression parser eval error:%1" ).arg( exp->evalErrorString() ), 4 );
1672  return;
1673  }
1674  labelText = result.toString();
1675  }
1676  else
1677  {
1678  labelText = f.attribute( fieldIndex ).toString();
1679  }
1680 
1681  // data defined format numbers?
1682  bool formatnum = formatNumbers;
1684  {
1685  formatnum = exprVal.toBool();
1686  QgsDebugMsgLevel( QString( "exprVal NumFormat:%1" ).arg( formatnum ? "true" : "false" ), 4 );
1687  }
1688 
1689  // format number if label text is coercible to a number
1690  if ( formatnum )
1691  {
1692  // data defined decimal places?
1693  int decimalPlaces = decimals;
1695  {
1696  bool ok;
1697  int dInt = exprVal.toInt( &ok );
1698  QgsDebugMsgLevel( QString( "exprVal NumDecimals:%1" ).arg( dInt ), 4 );
1699  if ( ok && dInt > 0 ) // needs to be positive
1700  {
1701  decimalPlaces = dInt;
1702  }
1703  }
1704 
1705  // data defined plus sign?
1706  bool signPlus = plusSign;
1708  {
1709  signPlus = exprVal.toBool();
1710  QgsDebugMsgLevel( QString( "exprVal NumPlusSign:%1" ).arg( signPlus ? "true" : "false" ), 4 );
1711  }
1712 
1713  QVariant textV = QVariant( labelText );
1714  bool ok;
1715  double d = textV.toDouble( &ok );
1716  if ( ok )
1717  {
1718  QString numberFormat;
1719  if ( d > 0 && signPlus )
1720  {
1721  numberFormat.append( "+" );
1722  }
1723  numberFormat.append( "%1" );
1724  labelText = numberFormat.arg( d, 0, 'f', decimalPlaces );
1725  }
1726  }
1727 
1728 
1729  // NOTE: this should come AFTER any option that affects font metrics
1730  QFontMetricsF* labelFontMetrics = new QFontMetricsF( labelFont );
1731  double labelX, labelY; // will receive label size
1732  calculateLabelSize( labelFontMetrics, labelText, labelX, labelY, mCurFeat );
1733 
1734 
1735  // maximum angle between curved label characters (hardcoded defaults used in QGIS <2.0)
1736  //
1737  double maxcharanglein = 20.0; // range 20.0-60.0
1738  double maxcharangleout = -20.0; // range 20.0-95.0
1739 
1741  {
1742  maxcharanglein = maxCurvedCharAngleIn;
1743  maxcharangleout = maxCurvedCharAngleOut;
1744 
1745  //data defined maximum angle between curved label characters?
1747  {
1748  QString ptstr = exprVal.toString().trimmed();
1749  QgsDebugMsgLevel( QString( "exprVal CurvedCharAngleInOut:%1" ).arg( ptstr ), 4 );
1750 
1751  if ( !ptstr.isEmpty() )
1752  {
1753  QPointF maxcharanglePt = QgsSymbolLayerV2Utils::decodePoint( ptstr );
1754  maxcharanglein = qBound( 20.0, ( double )maxcharanglePt.x(), 60.0 );
1755  maxcharangleout = qBound( 20.0, ( double )maxcharanglePt.y(), 95.0 );
1756  }
1757  }
1758  // make sure maxcharangleout is always negative
1759  maxcharangleout = -( qAbs( maxcharangleout ) );
1760  }
1761 
1762  QgsGeometry* geom = f.geometry();
1763  if ( !geom )
1764  {
1765  return;
1766  }
1767 
1768  if ( ct ) // reproject the geometry if necessary
1769  {
1770  try
1771  {
1772  geom->transform( *ct );
1773  }
1774  catch ( QgsCsException &cse )
1775  {
1776  Q_UNUSED( cse );
1777  QgsDebugMsgLevel( QString( "Ignoring feature %1 due transformation exception" ).arg( f.id() ), 4 );
1778  return;
1779  }
1780  }
1781 
1782  if ( !checkMinimumSizeMM( context, geom, minFeatureSize ) )
1783  {
1784  return;
1785  }
1786 
1787  // whether we're going to create a centroid for polygon
1788  bool centroidPoly = (( placement == QgsPalLayerSettings::AroundPoint
1790  && geom->type() == QGis::Polygon );
1791 
1792  // data defined centroid whole or clipped?
1793  bool wholeCentroid = centroidWhole;
1795  {
1796  QString str = exprVal.toString().trimmed();
1797  QgsDebugMsgLevel( QString( "exprVal CentroidWhole:%1" ).arg( str ), 4 );
1798 
1799  if ( !str.isEmpty() )
1800  {
1801  if ( str.compare( "Visible", Qt::CaseInsensitive ) == 0 )
1802  {
1803  wholeCentroid = false;
1804  }
1805  else if ( str.compare( "Whole", Qt::CaseInsensitive ) == 0 )
1806  {
1807  wholeCentroid = true;
1808  }
1809  }
1810  }
1811 
1812  // CLIP the geometry if it is bigger than the extent
1813  // don't clip if centroid is requested for whole feature
1814  bool do_clip = false;
1815  if ( !centroidPoly || ( centroidPoly && !wholeCentroid ) )
1816  {
1817  do_clip = !extentGeom->contains( geom );
1818  if ( do_clip )
1819  {
1820  geom = geom->intersection( extentGeom ); // creates new geometry
1821  if ( !geom )
1822  {
1823  return;
1824  }
1825  }
1826  }
1827 
1828  const GEOSGeometry* geos_geom = geom->asGeos();
1829 
1830  if ( geos_geom == NULL )
1831  return; // invalid geometry
1832 
1833  // likelihood exists label will be registered with PAL and may be drawn
1834  // check if max number of features to label (already registered with PAL) has been reached
1835  // Debug output at end of QgsPalLabeling::drawLabeling(), when deleting temp geometries
1836  if ( limitNumLabels )
1837  {
1838  if ( !maxNumLabels )
1839  {
1840  return;
1841  }
1842  mFeatsRegPal = palLayer->getNbFeatures();
1843  if ( mFeatsRegPal >= maxNumLabels )
1844  {
1845  return;
1846  }
1847 
1848  int divNum = ( int )(( mFeaturesToLabel / maxNumLabels ) + 0.5 );
1849  if ( divNum && ( mFeatsRegPal == ( int )( mFeatsSendingToPal / divNum ) ) )
1850  {
1851  mFeatsSendingToPal += 1;
1852  if ( divNum && mFeatsSendingToPal % divNum )
1853  {
1854  return;
1855  }
1856  }
1857  }
1858 
1859  GEOSGeometry* geos_geom_clone = GEOSGeom_clone( geos_geom );
1860 
1861  //data defined position / alignment / rotation?
1862  bool dataDefinedPosition = false;
1863  bool labelIsPinned = false;
1864  bool layerDefinedRotation = false;
1865  bool dataDefinedRotation = false;
1866  double xPos = 0.0, yPos = 0.0, angle = 0.0;
1867  bool ddXPos = false, ddYPos = false;
1868  double quadOffsetX = 0.0, quadOffsetY = 0.0;
1869  double offsetX = 0.0, offsetY = 0.0;
1870 
1871  //data defined quadrant offset?
1872  QuadrantPosition quadOff = quadOffset;
1874  {
1875  bool ok;
1876  int quadInt = exprVal.toInt( &ok );
1877  QgsDebugMsgLevel( QString( "exprVal OffsetQuad:%1" ).arg( quadInt ), 4 );
1878  if ( ok && 0 <= quadInt && quadInt <= 8 )
1879  {
1880  quadOff = ( QuadrantPosition )quadInt;
1881  }
1882  }
1883 
1884  // adjust quadrant offset of labels
1885  switch ( quadOff )
1886  {
1887  case QuadrantAboveLeft:
1888  quadOffsetX = -1.0;
1889  quadOffsetY = 1.0;
1890  break;
1891  case QuadrantAbove:
1892  quadOffsetX = 0.0;
1893  quadOffsetY = 1.0;
1894  break;
1895  case QuadrantAboveRight:
1896  quadOffsetX = 1.0;
1897  quadOffsetY = 1.0;
1898  break;
1899  case QuadrantLeft:
1900  quadOffsetX = -1.0;
1901  quadOffsetY = 0.0;
1902  break;
1903  case QuadrantRight:
1904  quadOffsetX = 1.0;
1905  quadOffsetY = 0.0;
1906  break;
1907  case QuadrantBelowLeft:
1908  quadOffsetX = -1.0;
1909  quadOffsetY = -1.0;
1910  break;
1911  case QuadrantBelow:
1912  quadOffsetX = 0.0;
1913  quadOffsetY = -1.0;
1914  break;
1915  case QuadrantBelowRight:
1916  quadOffsetX = 1.0;
1917  quadOffsetY = -1.0;
1918  break;
1919  case QuadrantOver:
1920  default:
1921  break;
1922  }
1923 
1924  //data defined label offset?
1925  double xOff = xOffset;
1926  double yOff = yOffset;
1928  {
1929  QString ptstr = exprVal.toString().trimmed();
1930  QgsDebugMsgLevel( QString( "exprVal OffsetXY:%1" ).arg( ptstr ), 4 );
1931 
1932  if ( !ptstr.isEmpty() )
1933  {
1934  QPointF ddOffPt = QgsSymbolLayerV2Utils::decodePoint( ptstr );
1935  xOff = ddOffPt.x();
1936  yOff = ddOffPt.y();
1937  }
1938  }
1939 
1940  // data defined label offset units?
1941  bool offinmapunits = labelOffsetInMapUnits;
1943  {
1944  QString units = exprVal.toString().trimmed();
1945  QgsDebugMsgLevel( QString( "exprVal OffsetUnits:%1" ).arg( units ), 4 );
1946  if ( !units.isEmpty() )
1947  {
1948  offinmapunits = ( _decodeUnits( units ) == QgsPalLayerSettings::MapUnits );
1949  }
1950  }
1951 
1952  // adjust offset of labels to match chosen unit and map scale
1953  // offsets match those of symbology: -x = left, -y = up
1954  double mapUntsPerMM = context.mapToPixel().mapUnitsPerPixel() * context.scaleFactor();
1955  if ( xOff != 0 )
1956  {
1957  offsetX = xOff; // must be positive to match symbology offset direction
1958  if ( !offinmapunits )
1959  {
1960  offsetX *= mapUntsPerMM; //convert offset from mm to map units
1961  }
1962  }
1963  if ( yOff != 0 )
1964  {
1965  offsetY = -yOff; // must be negative to match symbology offset direction
1966  if ( !offinmapunits )
1967  {
1968  offsetY *= mapUntsPerMM; //convert offset from mm to map units
1969  }
1970  }
1971 
1972  // layer defined rotation?
1973  // only rotate non-pinned OverPoint placements until other placements are supported in pal::Feature
1975  {
1976  layerDefinedRotation = true;
1977  angle = angleOffset * M_PI / 180; // convert to radians
1978  }
1979 
1980  //data defined rotation?
1982  {
1983  bool ok;
1984  double rotD = exprVal.toDouble( &ok );
1985  QgsDebugMsgLevel( QString( "exprVal Rotation:%1" ).arg( rotD ), 4 );
1986  if ( ok )
1987  {
1988  dataDefinedRotation = true;
1989  angle = rotD * M_PI / 180.0;
1990  }
1991  }
1992 
1994  {
1995  xPos = exprVal.toDouble( &ddXPos );
1996  QgsDebugMsgLevel( QString( "exprVal PositionX:%1" ).arg( xPos ), 4 );
1997 
1999  {
2000  //data defined position. But field values could be NULL -> positions will be generated by PAL
2001  yPos = exprVal.toDouble( &ddYPos );
2002  QgsDebugMsgLevel( QString( "exprVal PositionY:%1" ).arg( yPos ), 4 );
2003 
2004  if ( ddXPos && ddYPos )
2005  {
2006  dataDefinedPosition = true;
2007  labelIsPinned = true;
2008  // layer rotation set, but don't rotate pinned labels unless data defined
2009  if ( layerDefinedRotation && !dataDefinedRotation )
2010  {
2011  angle = 0.0;
2012  }
2013 
2014  //x/y shift in case of alignment
2015  double xdiff = 0.0;
2016  double ydiff = 0.0;
2017 
2018  //horizontal alignment
2020  {
2021  QString haliString = exprVal.toString();
2022  QgsDebugMsgLevel( QString( "exprVal Hali:%1" ).arg( haliString ), 4 );
2023  if ( haliString.compare( "Center", Qt::CaseInsensitive ) == 0 )
2024  {
2025  xdiff -= labelX / 2.0;
2026  }
2027  else if ( haliString.compare( "Right", Qt::CaseInsensitive ) == 0 )
2028  {
2029  xdiff -= labelX;
2030  }
2031  }
2032 
2033  //vertical alignment
2035  {
2036  QString valiString = exprVal.toString();
2037  QgsDebugMsgLevel( QString( "exprVal Vali:%1" ).arg( valiString ), 4 );
2038 
2039  if ( valiString.compare( "Bottom", Qt::CaseInsensitive ) != 0 )
2040  {
2041  if ( valiString.compare( "Top", Qt::CaseInsensitive ) == 0 )
2042  {
2043  ydiff -= labelY;
2044  }
2045  else
2046  {
2047  double descentRatio = labelFontMetrics->descent() / labelFontMetrics->height();
2048  if ( valiString.compare( "Base", Qt::CaseInsensitive ) == 0 )
2049  {
2050  ydiff -= labelY * descentRatio;
2051  }
2052  else //'Cap' or 'Half'
2053  {
2054  double capHeightRatio = ( labelFontMetrics->boundingRect( 'H' ).height() + 1 + labelFontMetrics->descent() ) / labelFontMetrics->height();
2055  ydiff -= labelY * capHeightRatio;
2056  if ( valiString.compare( "Half", Qt::CaseInsensitive ) == 0 )
2057  {
2058  ydiff += labelY * ( capHeightRatio - descentRatio ) / 2.0;
2059  }
2060  }
2061  }
2062  }
2063  }
2064 
2065  if ( dataDefinedRotation )
2066  {
2067  //adjust xdiff and ydiff because the hali/vali point needs to be the rotation center
2068  double xd = xdiff * cos( angle ) - ydiff * sin( angle );
2069  double yd = xdiff * sin( angle ) + ydiff * cos( angle );
2070  xdiff = xd;
2071  ydiff = yd;
2072  }
2073 
2074  //project xPos and yPos from layer to map CRS
2075  double z = 0;
2076  if ( ct )
2077  {
2078  try
2079  {
2080  ct->transformInPlace( xPos, yPos, z );
2081  }
2082  catch ( QgsCsException &e )
2083  {
2084  Q_UNUSED( e );
2085  QgsDebugMsgLevel( QString( "Ignoring feature %1 due transformation exception on data-defined position" ).arg( f.id() ), 4 );
2086  return;
2087  }
2088  }
2089 
2090  xPos += xdiff;
2091  yPos += ydiff;
2092  }
2093  else
2094  {
2095  // only rotate non-pinned OverPoint placements until other placements are supported in pal::Feature
2096  if ( dataDefinedRotation && placement != QgsPalLayerSettings::OverPoint )
2097  {
2098  angle = 0.0;
2099  }
2100  }
2101  }
2102  }
2103 
2104  // data defined always show?
2105  bool alwaysShow = false;
2107  {
2108  alwaysShow = exprVal.toBool();
2109  }
2110 
2111  QgsPalGeometry* lbl = new QgsPalGeometry(
2112  f.id(),
2113  labelText,
2114  geos_geom_clone,
2115  labelFont.letterSpacing(),
2116  labelFont.wordSpacing(),
2118 
2119  // record the created geometry - it will be deleted at the end.
2120  geometries.append( lbl );
2121 
2122  // store the label's calculated font for later use during painting
2123 #if QT_VERSION >= 0x040800
2124  QgsDebugMsgLevel( QString( "PAL font stored definedFont: %1, Style: %2" ).arg( labelFont.toString() ).arg( labelFont.styleName() ), 4 );
2125 #endif
2126  lbl->setDefinedFont( labelFont );
2127 
2128  // feature to the layer
2129  try
2130  {
2131  if ( !palLayer->registerFeature( lbl->strId(), lbl, labelX, labelY, labelText.toUtf8().constData(),
2132  xPos, yPos, dataDefinedPosition, angle, dataDefinedRotation,
2133  quadOffsetX, quadOffsetY, offsetX, offsetY, alwaysShow ) )
2134  return;
2135  }
2136  catch ( std::exception &e )
2137  {
2138  Q_UNUSED( e );
2139  QgsDebugMsgLevel( QString( "Ignoring feature %1 due PAL exception:" ).arg( f.id() ) + QString::fromLatin1( e.what() ), 4 );
2140  return;
2141  }
2142 
2143  // TODO: only for placement which needs character info
2144  pal::Feature* feat = palLayer->getFeature( lbl->strId() );
2145  // account for any data defined font metrics adjustments
2146  feat->setLabelInfo( lbl->info( labelFontMetrics, xform, rasterCompressFactor, maxcharanglein, maxcharangleout ) );
2147  delete labelFontMetrics;
2148 
2149  // TODO: allow layer-wide feature dist in PAL...?
2150 
2151  // data defined label-feature distance?
2152  double distance = dist;
2154  {
2155  bool ok;
2156  double distD = exprVal.toDouble( &ok );
2157  if ( ok )
2158  {
2159  distance = distD;
2160  }
2161  }
2162 
2163  // data defined label-feature distance units?
2164  bool distinmapunit = distInMapUnits;
2166  {
2167  QString units = exprVal.toString().trimmed();
2168  QgsDebugMsgLevel( QString( "exprVal DistanceUnits:%1" ).arg( units ), 4 );
2169  if ( !units.isEmpty() )
2170  {
2171  distinmapunit = ( _decodeUnits( units ) == QgsPalLayerSettings::MapUnits );
2172  }
2173  }
2174 
2175  if ( distance != 0 )
2176  {
2177  if ( distinmapunit ) //convert distance from mm/map units to pixels
2178  {
2179  distance /= context.mapToPixel().mapUnitsPerPixel();
2180  }
2181  else //mm
2182  {
2183  distance *= vectorScaleFactor;
2184  }
2185  feat->setDistLabel( qAbs( ptOne.x() - ptZero.x() )* distance );
2186  }
2187 
2188  //add parameters for data defined labeling to QgsPalGeometry
2189  QMap< DataDefinedProperties, QVariant >::const_iterator dIt = dataDefinedValues.constBegin();
2190  for ( ; dIt != dataDefinedValues.constEnd(); ++dIt )
2191  {
2192  lbl->addDataDefinedValue( dIt.key(), dIt.value() );
2193  }
2194 
2195  // set geometry's pinned property
2196  lbl->setIsPinned( labelIsPinned );
2197 }
2198 
2199 bool QgsPalLayerSettings::dataDefinedValEval( const QString& valType,
2201  QVariant& exprVal )
2202 {
2203  if ( dataDefinedEvaluate( p, exprVal ) )
2204  {
2205  QString dbgStr = QString( "exprVal %1:" ).arg( mDataDefinedNames.value( p ).first ) + "%1";
2206 
2207  if ( valType == QString( "bool" ) )
2208  {
2209  bool bol = exprVal.toBool();
2210  QgsDebugMsgLevel( dbgStr.arg( bol ? "true" : "false" ), 4 );
2211  dataDefinedValues.insert( p, QVariant( bol ) );
2212  return true;
2213  }
2214  if ( valType == QString( "int" ) )
2215  {
2216  bool ok;
2217  int size = exprVal.toInt( &ok );
2218  QgsDebugMsgLevel( dbgStr.arg( size ), 4 );
2219 
2220  if ( ok )
2221  {
2222  dataDefinedValues.insert( p, QVariant( size ) );
2223  return true;
2224  }
2225  }
2226  if ( valType == QString( "intpos" ) )
2227  {
2228  bool ok;
2229  int size = exprVal.toInt( &ok );
2230  QgsDebugMsgLevel( dbgStr.arg( size ), 4 );
2231 
2232  if ( ok && size > 0 )
2233  {
2234  dataDefinedValues.insert( p, QVariant( size ) );
2235  return true;
2236  }
2237  }
2238  if ( valType == QString( "double" ) )
2239  {
2240  bool ok;
2241  double size = exprVal.toDouble( &ok );
2242  QgsDebugMsgLevel( dbgStr.arg( size ), 4 );
2243 
2244  if ( ok )
2245  {
2246  dataDefinedValues.insert( p, QVariant( size ) );
2247  return true;
2248  }
2249  }
2250  if ( valType == QString( "doublepos" ) )
2251  {
2252  bool ok;
2253  double size = exprVal.toDouble( &ok );
2254  QgsDebugMsgLevel( dbgStr.arg( size ), 4 );
2255 
2256  if ( ok && size > 0.0 )
2257  {
2258  dataDefinedValues.insert( p, QVariant( size ) );
2259  return true;
2260  }
2261  }
2262  if ( valType == QString( "rotation180" ) )
2263  {
2264  bool ok;
2265  double rot = exprVal.toDouble( &ok );
2266  QgsDebugMsgLevel( dbgStr.arg( rot ), 4 );
2267  if ( ok )
2268  {
2269  if ( rot < -180.0 && rot >= -360 )
2270  {
2271  rot += 360;
2272  }
2273  if ( rot > 180.0 && rot <= 360 )
2274  {
2275  rot -= 360;
2276  }
2277  if ( rot >= -180 && rot <= 180 )
2278  {
2279  dataDefinedValues.insert( p, QVariant( rot ) );
2280  return true;
2281  }
2282  }
2283  }
2284  if ( valType == QString( "transp" ) )
2285  {
2286  bool ok;
2287  int size = exprVal.toInt( &ok );
2288  QgsDebugMsgLevel( dbgStr.arg( size ), 4 );
2289  if ( ok && size >= 0 && size <= 100 )
2290  {
2291  dataDefinedValues.insert( p, QVariant( size ) );
2292  return true;
2293  }
2294  }
2295  if ( valType == QString( "string" ) )
2296  {
2297  QString str = exprVal.toString(); // don't trim whitespace
2298  QgsDebugMsgLevel( dbgStr.arg( str ), 4 );
2299 
2300  dataDefinedValues.insert( p, QVariant( str ) ); // let it stay empty if it is
2301  return true;
2302  }
2303  if ( valType == QString( "units" ) )
2304  {
2305  QString unitstr = exprVal.toString().trimmed();
2306  QgsDebugMsgLevel( dbgStr.arg( unitstr ), 4 );
2307 
2308  if ( !unitstr.isEmpty() )
2309  {
2310  dataDefinedValues.insert( p, QVariant(( int )_decodeUnits( unitstr ) ) );
2311  return true;
2312  }
2313  }
2314  if ( valType == QString( "color" ) )
2315  {
2316  QString colorstr = exprVal.toString().trimmed();
2317  QgsDebugMsgLevel( dbgStr.arg( colorstr ), 4 );
2318  QColor color = QgsSymbolLayerV2Utils::decodeColor( colorstr );
2319 
2320  if ( color.isValid() )
2321  {
2322  dataDefinedValues.insert( p, QVariant( color ) );
2323  return true;
2324  }
2325  }
2326  if ( valType == QString( "joinstyle" ) )
2327  {
2328  QString joinstr = exprVal.toString().trimmed();
2329  QgsDebugMsgLevel( dbgStr.arg( joinstr ), 4 );
2330 
2331  if ( !joinstr.isEmpty() )
2332  {
2333  dataDefinedValues.insert( p, QVariant(( int )_decodePenJoinStyle( joinstr ) ) );
2334  return true;
2335  }
2336  }
2337  if ( valType == QString( "blendmode" ) )
2338  {
2339  QString blendstr = exprVal.toString().trimmed();
2340  QgsDebugMsgLevel( dbgStr.arg( blendstr ), 4 );
2341 
2342  if ( !blendstr.isEmpty() )
2343  {
2344  dataDefinedValues.insert( p, QVariant(( int )_decodeBlendMode( blendstr ) ) );
2345  return true;
2346  }
2347  }
2348  if ( valType == QString( "pointf" ) )
2349  {
2350  QString ptstr = exprVal.toString().trimmed();
2351  QgsDebugMsgLevel( dbgStr.arg( ptstr ), 4 );
2352 
2353  if ( !ptstr.isEmpty() )
2354  {
2355  dataDefinedValues.insert( p, QVariant( QgsSymbolLayerV2Utils::decodePoint( ptstr ) ) );
2356  return true;
2357  }
2358  }
2359  }
2360  return false;
2361 }
2362 
2365  const QgsRenderContext& context )
2366 {
2367  // NOTE: labelFont already has pixelSize set, so pointSize or pointSizeF might return -1
2368 
2369  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
2370 
2371  // Two ways to generate new data defined font:
2372  // 1) Family + [bold] + [italic] (named style is ignored and font is built off of base family)
2373  // 2) Family + named style (bold or italic is ignored)
2374 
2375  // data defined font family?
2376  QString ddFontFamily( "" );
2378  {
2379  QString family = exprVal.toString().trimmed();
2380  QgsDebugMsgLevel( QString( "exprVal Font family:%1" ).arg( family ), 4 );
2381 
2382  if ( labelFont.family() != family )
2383  {
2384  // testing for ddFontFamily in QFontDatabase.families() may be slow to do for every feature
2385  // (i.e. don't use QgsFontUtils::fontFamilyMatchOnSystem( family ) here)
2386  if ( QgsFontUtils::fontFamilyOnSystem( family ) )
2387  {
2388  ddFontFamily = family;
2389  }
2390  }
2391  }
2392 
2393  // data defined named font style?
2394  QString ddFontStyle( "" );
2396  {
2397  QString fontstyle = exprVal.toString().trimmed();
2398  QgsDebugMsgLevel( QString( "exprVal Font style:%1" ).arg( fontstyle ), 4 );
2399  ddFontStyle = fontstyle;
2400  }
2401 
2402  // data defined bold font style?
2403  bool ddBold = false;
2405  {
2406  bool bold = exprVal.toBool();
2407  QgsDebugMsgLevel( QString( "exprVal Font bold:%1" ).arg( bold ? "true" : "false" ), 4 );
2408  ddBold = bold;
2409  }
2410 
2411  // data defined italic font style?
2412  bool ddItalic = false;
2414  {
2415  bool italic = exprVal.toBool();
2416  QgsDebugMsgLevel( QString( "exprVal Font italic:%1" ).arg( italic ? "true" : "false" ), 4 );
2417  ddItalic = italic;
2418  }
2419 
2420  // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
2421  // (currently defaults to what has been read in from layer settings)
2422  QFont newFont;
2423  QFont appFont = QApplication::font();
2424  bool newFontBuilt = false;
2425  if ( ddBold || ddItalic )
2426  {
2427  // new font needs built, since existing style needs removed
2428  newFont = QFont( !ddFontFamily.isEmpty() ? ddFontFamily : labelFont.family() );
2429  newFontBuilt = true;
2430  newFont.setBold( ddBold );
2431  newFont.setItalic( ddItalic );
2432  }
2433  else if ( !ddFontStyle.isEmpty()
2434  && ddFontStyle.compare( "Ignore", Qt::CaseInsensitive ) != 0 )
2435  {
2436  if ( !ddFontFamily.isEmpty() )
2437  {
2438  // both family and style are different, build font from database
2439  QFont styledfont = mFontDB.font( ddFontFamily, ddFontStyle, appFont.pointSize() );
2440  if ( appFont != styledfont )
2441  {
2442  newFont = styledfont;
2443  newFontBuilt = true;
2444  }
2445  }
2446 
2447  // update the font face style
2448  QgsFontUtils::updateFontViaStyle( newFontBuilt ? newFont : labelFont, ddFontStyle );
2449  }
2450  else if ( !ddFontFamily.isEmpty() )
2451  {
2452  if ( ddFontStyle.compare( "Ignore", Qt::CaseInsensitive ) != 0 )
2453  {
2454  // just family is different, build font from database
2455  QFont styledfont = mFontDB.font( ddFontFamily, textNamedStyle, appFont.pointSize() );
2456  if ( appFont != styledfont )
2457  {
2458  newFont = styledfont;
2459  newFontBuilt = true;
2460  }
2461  }
2462  else
2463  {
2464  newFont = QFont( ddFontFamily );
2465  newFontBuilt = true;
2466  }
2467  }
2468 
2469  if ( newFontBuilt )
2470  {
2471  // copy over existing font settings
2472  //newFont = newFont.resolve( labelFont ); // should work, but let's be sure what's being copied
2473  newFont.setPixelSize( labelFont.pixelSize() );
2474  newFont.setCapitalization( labelFont.capitalization() );
2475  newFont.setUnderline( labelFont.underline() );
2476  newFont.setStrikeOut( labelFont.strikeOut() );
2477  newFont.setWordSpacing( labelFont.wordSpacing() );
2478  newFont.setLetterSpacing( QFont::AbsoluteSpacing, labelFont.letterSpacing() );
2479 
2480  labelFont = newFont;
2481  }
2482 
2483  // data defined word spacing?
2484  double wordspace = labelFont.wordSpacing();
2486  {
2487  bool ok;
2488  double wspacing = exprVal.toDouble( &ok );
2489  QgsDebugMsgLevel( QString( "exprVal FontWordSpacing:%1" ).arg( wspacing ), 4 );
2490  if ( ok )
2491  {
2492  wordspace = wspacing;
2493  }
2494  }
2495  labelFont.setWordSpacing( sizeToPixel( wordspace, context, fontunits, false ) );
2496 
2497  // data defined letter spacing?
2498  double letterspace = labelFont.letterSpacing();
2500  {
2501  bool ok;
2502  double lspacing = exprVal.toDouble( &ok );
2503  QgsDebugMsgLevel( QString( "exprVal FontLetterSpacing:%1" ).arg( lspacing ), 4 );
2504  if ( ok )
2505  {
2506  letterspace = lspacing;
2507  }
2508  }
2509  labelFont.setLetterSpacing( QFont::AbsoluteSpacing, sizeToPixel( letterspace, context, fontunits, false ) );
2510 
2511  // data defined font capitalization?
2512  QFont::Capitalization fontcaps = labelFont.capitalization();
2514  {
2515  QString fcase = exprVal.toString().trimmed();
2516  QgsDebugMsgLevel( QString( "exprVal FontCase:%1" ).arg( fcase ), 4 );
2517 
2518  if ( !fcase.isEmpty() )
2519  {
2520  if ( fcase.compare( "NoChange", Qt::CaseInsensitive ) == 0 )
2521  {
2522  fontcaps = QFont::MixedCase;
2523  }
2524  else if ( fcase.compare( "Upper", Qt::CaseInsensitive ) == 0 )
2525  {
2526  fontcaps = QFont::AllUppercase;
2527  }
2528  else if ( fcase.compare( "Lower", Qt::CaseInsensitive ) == 0 )
2529  {
2530  fontcaps = QFont::AllLowercase;
2531  }
2532  else if ( fcase.compare( "Capitalize", Qt::CaseInsensitive ) == 0 )
2533  {
2534  fontcaps = QFont::Capitalize;
2535  }
2536 
2537  if ( fontcaps != labelFont.capitalization() )
2538  {
2539  labelFont.setCapitalization( fontcaps );
2540  }
2541  }
2542  }
2543 
2544  // data defined strikeout font style?
2546  {
2547  bool strikeout = exprVal.toBool();
2548  QgsDebugMsgLevel( QString( "exprVal Font strikeout:%1" ).arg( strikeout ? "true" : "false" ), 4 );
2549  labelFont.setStrikeOut( strikeout );
2550  }
2551 
2552  // data defined underline font style?
2554  {
2555  bool underline = exprVal.toBool();
2556  QgsDebugMsgLevel( QString( "exprVal Font underline:%1" ).arg( underline ? "true" : "false" ), 4 );
2557  labelFont.setUnderline( underline );
2558  }
2559 
2560  // pass the rest on to QgsPalLabeling::drawLabeling
2561 
2562  // data defined font color?
2563  dataDefinedValEval( "color", QgsPalLayerSettings::Color, exprVal );
2564 
2565  // data defined font transparency?
2567 
2568  // data defined font blend mode?
2569  dataDefinedValEval( "blendmode", QgsPalLayerSettings::FontBlendMode, exprVal );
2570 
2571 }
2572 
2574 {
2575  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
2576 
2577  // data defined draw buffer?
2578  bool drawBuffer = bufferDraw;
2579  if ( dataDefinedValEval( "bool", QgsPalLayerSettings::BufferDraw, exprVal ) )
2580  {
2581  drawBuffer = exprVal.toBool();
2582  }
2583 
2584  if ( !drawBuffer )
2585  {
2586  return;
2587  }
2588 
2589  // data defined buffer size?
2590  double bufrSize = bufferSize;
2591  if ( dataDefinedValEval( "doublepos", QgsPalLayerSettings::BufferSize, exprVal ) )
2592  {
2593  bufrSize = exprVal.toDouble();
2594  }
2595 
2596  // data defined buffer transparency?
2597  int bufTransp = bufferTransp;
2598  if ( dataDefinedValEval( "transp", QgsPalLayerSettings::BufferTransp, exprVal ) )
2599  {
2600  bufTransp = exprVal.toInt();
2601  }
2602 
2603  drawBuffer = ( drawBuffer && bufrSize > 0.0 && bufTransp < 100 );
2604 
2605  if ( !drawBuffer )
2606  {
2607  dataDefinedValues.insert( QgsPalLayerSettings::BufferDraw, QVariant( false ) ); // trigger value
2610  return; // don't bother evaluating values that won't be used
2611  }
2612 
2613  // data defined buffer units?
2615 
2616  // data defined buffer color?
2618 
2619  // data defined buffer pen join style?
2621 
2622  // data defined buffer blend mode?
2624 }
2625 
2627 {
2628  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
2629 
2630  // data defined multiline wrap character?
2631  QString wrapchr = wrapChar;
2632  if ( dataDefinedValEval( "string", QgsPalLayerSettings::MultiLineWrapChar, exprVal ) )
2633  {
2634  wrapchr = exprVal.toString();
2635  }
2636 
2637  // data defined multiline height?
2639 
2640  // data defined multiline text align?
2642  {
2643  QString str = exprVal.toString().trimmed();
2644  QgsDebugMsgLevel( QString( "exprVal MultiLineAlignment:%1" ).arg( str ), 4 );
2645 
2646  if ( !str.isEmpty() )
2647  {
2648  // "Left"
2650 
2651  if ( str.compare( "Center", Qt::CaseInsensitive ) == 0 )
2652  {
2654  }
2655  else if ( str.compare( "Right", Qt::CaseInsensitive ) == 0 )
2656  {
2657  aligntype = QgsPalLayerSettings::MultiRight;
2658  }
2659  dataDefinedValues.insert( QgsPalLayerSettings::MultiLineAlignment, QVariant(( int )aligntype ) );
2660  }
2661  }
2662 
2663  // data defined direction symbol?
2664  bool drawDirSymb = addDirectionSymbol;
2665  if ( dataDefinedValEval( "bool", QgsPalLayerSettings::DirSymbDraw, exprVal ) )
2666  {
2667  drawDirSymb = exprVal.toBool();
2668  }
2669 
2670  if ( drawDirSymb )
2671  {
2672  // data defined direction left symbol?
2674 
2675  // data defined direction right symbol?
2677 
2678  // data defined direction symbol placement?
2680  {
2681  QString str = exprVal.toString().trimmed();
2682  QgsDebugMsgLevel( QString( "exprVal DirSymbPlacement:%1" ).arg( str ), 4 );
2683 
2684  if ( !str.isEmpty() )
2685  {
2686  // "LeftRight"
2688 
2689  if ( str.compare( "Above", Qt::CaseInsensitive ) == 0 )
2690  {
2692  }
2693  else if ( str.compare( "Below", Qt::CaseInsensitive ) == 0 )
2694  {
2696  }
2697  dataDefinedValues.insert( QgsPalLayerSettings::DirSymbPlacement, QVariant(( int )placetype ) );
2698  }
2699  }
2700 
2701  // data defined direction symbol reversed?
2703  }
2704 
2705  // formatting for numbers is inline with generation of base label text and not passed to label painting
2706 }
2707 
2709 {
2710  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
2711 
2712  // data defined draw shape?
2713  bool drawShape = shapeDraw;
2714  if ( dataDefinedValEval( "bool", QgsPalLayerSettings::ShapeDraw, exprVal ) )
2715  {
2716  drawShape = exprVal.toBool();
2717  }
2718 
2719  if ( !drawShape )
2720  {
2721  return;
2722  }
2723 
2724  // data defined shape transparency?
2725  int shapeTransp = shapeTransparency;
2726  if ( dataDefinedValEval( "transp", QgsPalLayerSettings::ShapeTransparency, exprVal ) )
2727  {
2728  shapeTransp = exprVal.toInt();
2729  }
2730 
2731  drawShape = ( drawShape && shapeTransp < 100 ); // size is not taken into account (could be)
2732 
2733  if ( !drawShape )
2734  {
2735  dataDefinedValues.insert( QgsPalLayerSettings::ShapeDraw, QVariant( false ) ); // trigger value
2737  return; // don't bother evaluating values that won't be used
2738  }
2739 
2740  // data defined shape kind?
2743  {
2744  QString skind = exprVal.toString().trimmed();
2745  QgsDebugMsgLevel( QString( "exprVal ShapeKind:%1" ).arg( skind ), 4 );
2746 
2747  if ( !skind.isEmpty() )
2748  {
2749  // "Rectangle"
2751 
2752  if ( skind.compare( "Square", Qt::CaseInsensitive ) == 0 )
2753  {
2755  }
2756  else if ( skind.compare( "Ellipse", Qt::CaseInsensitive ) == 0 )
2757  {
2759  }
2760  else if ( skind.compare( "Circle", Qt::CaseInsensitive ) == 0 )
2761  {
2763  }
2764  else if ( skind.compare( "SVG", Qt::CaseInsensitive ) == 0 )
2765  {
2767  }
2768  shapeKind = shpkind;
2769  dataDefinedValues.insert( QgsPalLayerSettings::ShapeKind, QVariant(( int )shpkind ) );
2770  }
2771  }
2772 
2773  // data defined shape SVG path?
2774  QString svgPath = shapeSVGFile;
2776  {
2777  QString svgfile = exprVal.toString().trimmed();
2778  QgsDebugMsgLevel( QString( "exprVal ShapeSVGFile:%1" ).arg( svgfile ), 4 );
2779 
2780  // '' empty paths are allowed
2781  svgPath = svgfile;
2782  dataDefinedValues.insert( QgsPalLayerSettings::ShapeSVGFile, QVariant( svgfile ) );
2783  }
2784 
2785  // data defined shape size type?
2788  {
2789  QString stype = exprVal.toString().trimmed();
2790  QgsDebugMsgLevel( QString( "exprVal ShapeSizeType:%1" ).arg( stype ), 4 );
2791 
2792  if ( !stype.isEmpty() )
2793  {
2794  // "Buffer"
2796 
2797  if ( stype.compare( "Fixed", Qt::CaseInsensitive ) == 0 )
2798  {
2800  }
2801  shpSizeType = sizType;
2802  dataDefinedValues.insert( QgsPalLayerSettings::ShapeSizeType, QVariant(( int )sizType ) );
2803  }
2804  }
2805 
2806  // data defined shape size X? (SVGs only use X for sizing)
2807  double ddShpSizeX = shapeSize.x();
2808  if ( dataDefinedValEval( "double", QgsPalLayerSettings::ShapeSizeX, exprVal ) )
2809  {
2810  ddShpSizeX = exprVal.toDouble();
2811  }
2812 
2813  // data defined shape size Y?
2814  double ddShpSizeY = shapeSize.y();
2815  if ( dataDefinedValEval( "double", QgsPalLayerSettings::ShapeSizeY, exprVal ) )
2816  {
2817  ddShpSizeY = exprVal.toDouble();
2818  }
2819 
2820  // don't continue under certain circumstances (e.g. size is fixed)
2821  bool skip = false;
2822  if ( shapeKind == QgsPalLayerSettings::ShapeSVG
2823  && ( svgPath.isEmpty()
2824  || ( !svgPath.isEmpty()
2825  && shpSizeType == QgsPalLayerSettings::SizeFixed
2826  && ddShpSizeX == 0.0 ) ) )
2827  {
2828  skip = true;
2829  }
2830  if ( shapeKind != QgsPalLayerSettings::ShapeSVG
2831  && shpSizeType == QgsPalLayerSettings::SizeFixed
2832  && ( ddShpSizeX == 0.0 || ddShpSizeY == 0.0 ) )
2833  {
2834  skip = true;
2835  }
2836 
2837  if ( skip )
2838  {
2839  dataDefinedValues.insert( QgsPalLayerSettings::ShapeDraw, QVariant( false ) ); // trigger value
2845  return; // don't bother evaluating values that won't be used
2846  }
2847 
2848  // data defined shape size units?
2850 
2851  // data defined shape rotation type?
2853  {
2854  QString rotstr = exprVal.toString().trimmed();
2855  QgsDebugMsgLevel( QString( "exprVal ShapeRotationType:%1" ).arg( rotstr ), 4 );
2856 
2857  if ( !rotstr.isEmpty() )
2858  {
2859  // "Sync"
2861 
2862  if ( rotstr.compare( "Offset", Qt::CaseInsensitive ) == 0 )
2863  {
2865  }
2866  else if ( rotstr.compare( "Fixed", Qt::CaseInsensitive ) == 0 )
2867  {
2869  }
2870  dataDefinedValues.insert( QgsPalLayerSettings::ShapeRotationType, QVariant(( int )rottype ) );
2871  }
2872  }
2873 
2874  // data defined shape rotation?
2875  dataDefinedValEval( "rotation180", QgsPalLayerSettings::ShapeRotation, exprVal );
2876 
2877  // data defined shape offset?
2879 
2880  // data defined shape offset units?
2882 
2883  // data defined shape radii?
2885 
2886  // data defined shape radii units?
2888 
2889  // data defined shape blend mode?
2891 
2892  // data defined shape fill color?
2894 
2895  // data defined shape border color?
2897 
2898  // data defined shape border width?
2900 
2901  // data defined shape border width units?
2903 
2904  // data defined shape join style?
2906 
2907 }
2908 
2910 {
2911  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
2912 
2913  // data defined draw shadow?
2914  bool drawShadow = shadowDraw;
2915  if ( dataDefinedValEval( "bool", QgsPalLayerSettings::ShadowDraw, exprVal ) )
2916  {
2917  drawShadow = exprVal.toBool();
2918  }
2919 
2920  if ( !drawShadow )
2921  {
2922  return;
2923  }
2924 
2925  // data defined shadow transparency?
2926  int shadowTransp = shadowTransparency;
2927  if ( dataDefinedValEval( "transp", QgsPalLayerSettings::ShadowTransparency, exprVal ) )
2928  {
2929  shadowTransp = exprVal.toInt();
2930  }
2931 
2932  // data defined shadow offset distance?
2933  double shadowOffDist = shadowOffsetDist;
2934  if ( dataDefinedValEval( "doublepos", QgsPalLayerSettings::ShadowOffsetDist, exprVal ) )
2935  {
2936  shadowOffDist = exprVal.toDouble();
2937  }
2938 
2939  // data defined shadow offset distance?
2940  double shadowRad = shadowRadius;
2941  if ( dataDefinedValEval( "doublepos", QgsPalLayerSettings::ShadowRadius, exprVal ) )
2942  {
2943  shadowRad = exprVal.toDouble();
2944  }
2945 
2946  drawShadow = ( drawShadow && shadowTransp < 100 && !( shadowOffDist == 0.0 && shadowRad == 0.0 ) );
2947 
2948  if ( !drawShadow )
2949  {
2950  dataDefinedValues.insert( QgsPalLayerSettings::ShadowDraw, QVariant( false ) ); // trigger value
2954  return; // don't bother evaluating values that won't be used
2955  }
2956 
2957  // data defined shadow under type?
2959  {
2960  QString str = exprVal.toString().trimmed();
2961  QgsDebugMsgLevel( QString( "exprVal ShadowUnder:%1" ).arg( str ), 4 );
2962 
2963  if ( !str.isEmpty() )
2964  {
2965  // "Lowest"
2967 
2968  if ( str.compare( "Text", Qt::CaseInsensitive ) == 0 )
2969  {
2971  }
2972  else if ( str.compare( "Buffer", Qt::CaseInsensitive ) == 0 )
2973  {
2975  }
2976  else if ( str.compare( "Background", Qt::CaseInsensitive ) == 0 )
2977  {
2979  }
2980  dataDefinedValues.insert( QgsPalLayerSettings::ShadowUnder, QVariant(( int )shdwtype ) );
2981  }
2982  }
2983 
2984  // data defined shadow offset angle?
2986 
2987  // data defined shadow offset units?
2989 
2990  // data defined shadow radius?
2992 
2993  // data defined shadow radius units?
2995 
2996  // data defined shadow scale? ( gui bounds to 0-2000, no upper bound here )
2998 
2999  // data defined shadow color?
3001 
3002  // data defined shadow blend mode?
3004 }
3005 
3006 int QgsPalLayerSettings::sizeToPixel( double size, const QgsRenderContext& c, SizeUnit unit, bool rasterfactor ) const
3007 {
3008  return ( int )( scaleToPixelContext( size, c, unit, rasterfactor ) + 0.5 );
3009 }
3010 
3011 double QgsPalLayerSettings::scaleToPixelContext( double size, const QgsRenderContext& c, SizeUnit unit, bool rasterfactor ) const
3012 {
3013  // if render context is that of device (i.e. not a scaled map), just return size
3014  double mapUnitsPerPixel = c.mapToPixel().mapUnitsPerPixel();
3015 
3016  if ( unit == MapUnits && mapUnitsPerPixel > 0.0 )
3017  {
3018  size = size / mapUnitsPerPixel * ( rasterfactor ? c.rasterScaleFactor() : 1 );
3019  }
3020  else // e.g. in points or mm
3021  {
3022  double ptsTomm = ( unit == Points ? 0.352778 : 1 );
3023  size *= ptsTomm * c.scaleFactor() * ( rasterfactor ? c.rasterScaleFactor() : 1 );
3024  }
3025  return size;
3026 }
3027 
3028 // -------------
3029 
3031  : mMapRenderer( NULL ), mPal( NULL )
3032 {
3033 
3034  // find out engine defaults
3035  Pal p;
3036  mCandPoint = p.getPointP();
3037  mCandLine = p.getLineP();
3038  mCandPolygon = p.getPolyP();
3039 
3040  switch ( p.getSearch() )
3041  {
3042  case CHAIN: mSearch = Chain; break;
3043  case POPMUSIC_TABU: mSearch = Popmusic_Tabu; break;
3044  case POPMUSIC_CHAIN: mSearch = Popmusic_Chain; break;
3045  case POPMUSIC_TABU_CHAIN: mSearch = Popmusic_Tabu_Chain; break;
3046  case FALP: mSearch = Falp; break;
3047  }
3048 
3049  mShowingCandidates = false;
3050  mShowingShadowRects = false;
3051  mShowingAllLabels = false;
3052 
3054 }
3055 
3057 {
3058  // make sure we've freed everything
3059  exit();
3060 
3062 
3063  delete mLabelSearchTree;
3064  mLabelSearchTree = NULL;
3065 }
3066 
3068 {
3069  // don't do QgsPalLayerSettings::readFromLayer( layer ) if not needed
3070  bool enabled = false;
3071  if ( layer->customProperty( "labeling" ).toString() == QString( "pal" ) )
3072  enabled = layer->customProperty( "labeling/enabled", QVariant( false ) ).toBool();
3073 
3074  return enabled;
3075 }
3076 
3078 {
3079  QHash<QgsVectorLayer*, QgsPalLayerSettings>::iterator lit;
3080  for ( lit = mActiveLayers.begin(); lit != mActiveLayers.end(); ++lit )
3081  {
3082  clearActiveLayer( lit.key() );
3083  }
3084  mActiveLayers.clear();
3085 }
3086 
3088 {
3090 
3091  // delete all QgsDataDefined objects (which also deletes their QgsExpression object)
3092  QMap< QgsPalLayerSettings::DataDefinedProperties, QgsDataDefined* >::iterator it = lyr.dataDefinedProperties.begin();
3093  for ( ; it != lyr.dataDefinedProperties.constEnd(); ++it )
3094  {
3095  delete( it.value() );
3096  it.value() = 0;
3097  }
3098  lyr.dataDefinedProperties.clear();
3099 }
3100 
3101 int QgsPalLabeling::prepareLayer( QgsVectorLayer* layer, QSet<int>& attrIndices, QgsRenderContext& ctx )
3102 {
3103  Q_ASSERT( mMapRenderer != NULL );
3104 
3105  if ( !willUseLayer( layer ) )
3106  {
3107  return 0;
3108  }
3109 
3110  QgsDebugMsgLevel( "PREPARE LAYER " + layer->id(), 4 );
3111 
3112  // start with a temporary settings class, find out labeling info
3113  QgsPalLayerSettings lyrTmp;
3114  lyrTmp.readFromLayer( layer );
3115 
3116  if ( lyrTmp.fieldName.isEmpty() )
3117  {
3118  return 0;
3119  }
3120 
3121  int fldIndex = -1;
3122  if ( lyrTmp.isExpression )
3123  {
3124  QgsExpression exp( lyrTmp.fieldName );
3125  if ( exp.hasEvalError() )
3126  {
3127  QgsDebugMsgLevel( "Prepare error:" + exp.evalErrorString(), 4 );
3128  return 0;
3129  }
3130  }
3131  else
3132  {
3133  // If we aren't an expression, we check to see if we can find the column.
3134  fldIndex = layer->fieldNameIndex( lyrTmp.fieldName );
3135  if ( fldIndex == -1 )
3136  {
3137  return 0;
3138  }
3139  }
3140 
3141  // add layer settings to the pallabeling hashtable: <QgsVectorLayer*, QgsPalLayerSettings>
3142  mActiveLayers.insert( layer, lyrTmp );
3143  // start using the reference to the layer in hashtable instead of local instance
3145 
3146  lyr.mCurFields = &( layer->pendingFields() );
3147 
3148  // add field indices for label's text, from expression or field
3149  if ( lyr.isExpression )
3150  {
3151  // prepare expression for use in QgsPalLayerSettings::registerFeature()
3152  QgsExpression* exp = lyr.getLabelExpression();
3153  exp->prepare( layer->pendingFields() );
3154  if ( exp->hasEvalError() )
3155  {
3156  QgsDebugMsgLevel( "Prepare error:" + exp->evalErrorString(), 4 );
3157  }
3158  foreach ( QString name, exp->referencedColumns() )
3159  {
3160  QgsDebugMsgLevel( "REFERENCED COLUMN = " + name, 4 );
3161  attrIndices.insert( layer->fieldNameIndex( name ) );
3162  }
3163  }
3164  else
3165  {
3166  if ( fldIndex != -1 )
3167  {
3168  attrIndices.insert( fldIndex );
3169  }
3170  }
3171 
3172  // add field indices of data defined expression or field
3173  QMap< QgsPalLayerSettings::DataDefinedProperties, QgsDataDefined* >::const_iterator dIt = lyr.dataDefinedProperties.constBegin();
3174  for ( ; dIt != lyr.dataDefinedProperties.constEnd(); ++dIt )
3175  {
3176  QgsDataDefined* dd = dIt.value();
3177  if ( !dd->isActive() )
3178  {
3179  continue;
3180  }
3181 
3182  // NOTE: the following also prepares any expressions for later use
3183 
3184  // store parameters for data defined expressions
3185  QMap<QString, QVariant> exprParams;
3186  exprParams.insert( "scale", ctx.rendererScale() );
3187 
3188  dd->setExpressionParams( exprParams );
3189 
3190  // this will return columns for expressions or field name, depending upon what is set to be used
3191  QStringList cols = dd->referencedColumns( layer ); // <-- prepares any expressions, too
3192 
3193  //QgsDebugMsgLevel( QString( "Data defined referenced columns:" ) + cols.join( "," ), 4 );
3194  foreach ( QString name, cols )
3195  {
3196  attrIndices.insert( layer->fieldNameIndex( name ) );
3197  }
3198  }
3199 
3200  // how to place the labels
3201  Arrangement arrangement;
3202  switch ( lyr.placement )
3203  {
3204  case QgsPalLayerSettings::AroundPoint: arrangement = P_POINT; break;
3205  case QgsPalLayerSettings::OverPoint: arrangement = P_POINT_OVER; break;
3206  case QgsPalLayerSettings::Line: arrangement = P_LINE; break;
3207  case QgsPalLayerSettings::Curved: arrangement = P_CURVED; break;
3208  case QgsPalLayerSettings::Horizontal: arrangement = P_HORIZ; break;
3209  case QgsPalLayerSettings::Free: arrangement = P_FREE; break;
3210  default: Q_ASSERT( "unsupported placement" && 0 ); return 0;
3211  }
3212 
3213  // create the pal layer
3214  double priority = 1 - lyr.priority / 10.0; // convert 0..10 --> 1..0
3215  double min_scale = -1, max_scale = -1;
3216 
3217  // handled in QgsPalLayerSettings::registerFeature now
3218  //if ( lyr.scaleVisibility && !lyr.dataDefinedIsActive( QgsPalLayerSettings::ScaleVisibility ) )
3219  //{
3220  // min_scale = lyr.scaleMin;
3221  // max_scale = lyr.scaleMax;
3222  //}
3223 
3224  Layer* l = mPal->addLayer( layer->id().toUtf8().data(),
3225  min_scale, max_scale, arrangement,
3226  METER, priority, lyr.obstacle, true, true,
3227  lyr.displayAll );
3228 
3229  if ( lyr.placementFlags )
3230  l->setArrangementFlags( lyr.placementFlags );
3231 
3232  // set label mode (label per feature is the default)
3233  l->setLabelMode( lyr.labelPerPart ? Layer::LabelPerFeaturePart : Layer::LabelPerFeature );
3234 
3235  // set whether adjacent lines should be merged
3236  l->setMergeConnectedLines( lyr.mergeLines );
3237 
3238  // set how to show upside-down labels
3239  Layer::UpsideDownLabels upsdnlabels;
3240  switch ( lyr.upsidedownLabels )
3241  {
3242  case QgsPalLayerSettings::Upright: upsdnlabels = Layer::Upright; break;
3243  case QgsPalLayerSettings::ShowDefined: upsdnlabels = Layer::ShowDefined; break;
3244  case QgsPalLayerSettings::ShowAll: upsdnlabels = Layer::ShowAll; break;
3245  default: Q_ASSERT( "unsupported upside-down label setting" && 0 ); return 0;
3246  }
3247  l->setUpsidedownLabels( upsdnlabels );
3248 
3249 // // fix for font size in map units causing font to show pointsize at small map scales
3250 // int pixelFontSize = lyr.sizeToPixel( lyr.textFont.pointSizeF(), ctx,
3251 // lyr.fontSizeInMapUnits ? QgsPalLayerSettings::MapUnits : QgsPalLayerSettings::Points,
3252 // true );
3253 
3254 // if ( pixelFontSize < 1 )
3255 // {
3256 // lyr.textFont.setPointSize( 1 );
3257 // lyr.textFont.setPixelSize( 1 );
3258 // }
3259 // else
3260 // {
3261 // lyr.textFont.setPixelSize( pixelFontSize );
3262 // }
3263 
3264 // // scale spacing sizes if using map units
3265 // if ( lyr.fontSizeInMapUnits )
3266 // {
3267 // double spacingPixelSize;
3268 // if ( lyr.textFont.wordSpacing() != 0 )
3269 // {
3270 // spacingPixelSize = lyr.textFont.wordSpacing() / ctx.mapToPixel().mapUnitsPerPixel() * ctx.rasterScaleFactor();
3271 // lyr.textFont.setWordSpacing( spacingPixelSize );
3272 // }
3273 
3274 // if ( lyr.textFont.letterSpacing() != 0 )
3275 // {
3276 // spacingPixelSize = lyr.textFont.letterSpacing() / ctx.mapToPixel().mapUnitsPerPixel() * ctx.rasterScaleFactor();
3277 // lyr.textFont.setLetterSpacing( QFont::AbsoluteSpacing, spacingPixelSize );
3278 // }
3279 // }
3280 
3281  //raster and vector scale factors
3282  lyr.vectorScaleFactor = ctx.scaleFactor();
3284 
3285  // save the pal layer to our layer context (with some additional info)
3286  lyr.palLayer = l;
3287  lyr.fieldIndex = fldIndex;
3288 
3291  lyr.ct = new QgsCoordinateTransform( layer->crs(), mMapRenderer->destinationCrs() );
3292  else
3293  lyr.ct = NULL;
3294  lyr.ptZero = lyr.xform->toMapCoordinates( 0, 0 );
3295  lyr.ptOne = lyr.xform->toMapCoordinates( 1, 0 );
3296 
3297  // rect for clipping
3299 
3300  lyr.mFeatsSendingToPal = 0;
3301 
3302  return 1; // init successful
3303 }
3304 
3306 {
3307  Layer* l = mPal->addLayer( layer->id().append( "d" ).toUtf8().data(), -1, -1, pal::Arrangement( s->placement ), METER, s->priority, s->obstacle, true, true );
3308  l->setArrangementFlags( s->placementFlags );
3309 
3310  s->palLayer = l;
3312  s->ct = new QgsCoordinateTransform( layer->crs(), mMapRenderer->destinationCrs() );
3313  else
3314  s->ct = NULL;
3316  mActiveDiagramLayers.insert( layer, *s );
3317  return 1;
3318 }
3319 
3321 {
3323  lyr.registerFeature( layer, f, context );
3324 }
3325 
3327 {
3328  //get diagram layer settings, diagram renderer
3329  QHash<QgsVectorLayer*, QgsDiagramLayerSettings>::iterator layerIt = mActiveDiagramLayers.find( layer );
3330  if ( layerIt == mActiveDiagramLayers.constEnd() )
3331  {
3332  return;
3333  }
3334 
3335  //convert geom to geos
3336  QgsGeometry* geom = feat.geometry();
3337 
3338  if ( layerIt.value().ct && !willUseLayer( layer ) ) // reproject the geometry if feature not already transformed for labeling
3339  {
3340  geom->transform( *( layerIt.value().ct ) );
3341  }
3342 
3343  const GEOSGeometry* geos_geom = geom->asGeos();
3344  if ( geos_geom == 0 )
3345  {
3346  return; // invalid geometry
3347  }
3348 
3349  //create PALGeometry with diagram = true
3350  QgsPalGeometry* lbl = new QgsPalGeometry( feat.id(), "", GEOSGeom_clone( geos_geom ) );
3351  lbl->setIsDiagram( true );
3352 
3353  // record the created geometry - it will be deleted at the end.
3354  layerIt.value().geometries.append( lbl );
3355 
3356  double diagramWidth = 0;
3357  double diagramHeight = 0;
3358  QgsDiagramRendererV2* dr = layerIt.value().renderer;
3359  if ( dr )
3360  {
3361  QSizeF diagSize = dr->sizeMapUnits( feat.attributes(), context );
3362  if ( diagSize.isValid() )
3363  {
3364  diagramWidth = diagSize.width();
3365  diagramHeight = diagSize.height();
3366  }
3367 
3368  //append the diagram attributes to lbl
3369  lbl->setDiagramAttributes( feat.attributes() );
3370  }
3371 
3372  // feature to the layer
3373  int ddColX = layerIt.value().xPosColumn;
3374  int ddColY = layerIt.value().yPosColumn;
3375  double ddPosX = 0.0;
3376  double ddPosY = 0.0;
3377  bool ddPos = ( ddColX >= 0 && ddColY >= 0 );
3378  if ( ddPos )
3379  {
3380  bool posXOk, posYOk;
3381  //data defined diagram position is always centered
3382  ddPosX = feat.attribute( ddColX ).toDouble( &posXOk ) - diagramWidth / 2.0;
3383  ddPosY = feat.attribute( ddColY ).toDouble( &posYOk ) - diagramHeight / 2.0;
3384  if ( !posXOk || !posYOk )
3385  {
3386  ddPos = false;
3387  }
3388  else
3389  {
3390  const QgsCoordinateTransform* ct = layerIt.value().ct;
3391  if ( ct )
3392  {
3393  double z = 0;
3394  ct->transformInPlace( ddPosX, ddPosY, z );
3395  }
3396  }
3397  }
3398 
3399  try
3400  {
3401  if ( !layerIt.value().palLayer->registerFeature( lbl->strId(), lbl, diagramWidth, diagramHeight, "", ddPosX, ddPosY, ddPos ) )
3402  {
3403  return;
3404  }
3405  }
3406  catch ( std::exception &e )
3407  {
3408  Q_UNUSED( e );
3409  QgsDebugMsgLevel( QString( "Ignoring feature %1 due PAL exception:" ).arg( feat.id() ) + QString::fromLatin1( e.what() ), 4 );
3410  return;
3411  }
3412 
3413  pal::Feature* palFeat = layerIt.value().palLayer->getFeature( lbl->strId() );
3414  QgsPoint ptZero = layerIt.value().xform->toMapCoordinates( 0, 0 );
3415  QgsPoint ptOne = layerIt.value().xform->toMapCoordinates( 1, 0 );
3416  palFeat->setDistLabel( qAbs( ptOne.x() - ptZero.x() ) * layerIt.value().dist );
3417 }
3418 
3419 
3421 {
3422  mMapRenderer = mr;
3423 
3424  // delete if exists already
3425  if ( mPal )
3426  delete mPal;
3427 
3428  mPal = new Pal;
3429 
3430  SearchMethod s;
3431  switch ( mSearch )
3432  {
3433  default:
3434  case Chain: s = CHAIN; break;
3435  case Popmusic_Tabu: s = POPMUSIC_TABU; break;
3436  case Popmusic_Chain: s = POPMUSIC_CHAIN; break;
3437  case Popmusic_Tabu_Chain: s = POPMUSIC_TABU_CHAIN; break;
3438  case Falp: s = FALP; break;
3439  }
3440  mPal->setSearch( s );
3441 
3442  // set number of candidates generated per feature
3443  mPal->setPointP( mCandPoint );
3444  mPal->setLineP( mCandLine );
3445  mPal->setPolyP( mCandPolygon );
3446 
3447  clearActiveLayers(); // free any previous QgsDataDefined objects
3448  mActiveDiagramLayers.clear();
3449 }
3450 
3452 {
3453  delete mPal;
3454  mPal = NULL;
3455  mMapRenderer = NULL;
3456 }
3457 
3458 QgsPalLayerSettings& QgsPalLabeling::layer( const QString& layerName )
3459 {
3460  QHash<QgsVectorLayer*, QgsPalLayerSettings>::iterator lit;
3461  for ( lit = mActiveLayers.begin(); lit != mActiveLayers.end(); ++lit )
3462  {
3463  if ( lit.key() && lit.key()->id() == layerName )
3464  {
3465  return lit.value();
3466  }
3467  }
3468  return mInvalidLayerSettings;
3469 }
3470 
3472  const QMap< QgsPalLayerSettings::DataDefinedProperties, QVariant >& ddValues )
3473 {
3474  //font color
3475  if ( ddValues.contains( QgsPalLayerSettings::Color ) )
3476  {
3477  QVariant ddColor = ddValues.value( QgsPalLayerSettings::Color );
3478  tmpLyr.textColor = ddColor.value<QColor>();
3479  }
3480 
3481  //font transparency
3482  if ( ddValues.contains( QgsPalLayerSettings::FontTransp ) )
3483  {
3484  tmpLyr.textTransp = ddValues.value( QgsPalLayerSettings::FontTransp ).toInt();
3485  }
3486 
3487  tmpLyr.textColor.setAlphaF(( 100.0 - ( double )( tmpLyr.textTransp ) ) / 100.0 );
3488 
3489  //font blend mode
3490  if ( ddValues.contains( QgsPalLayerSettings::FontBlendMode ) )
3491  {
3492  tmpLyr.blendMode = ( QPainter::CompositionMode )ddValues.value( QgsPalLayerSettings::FontBlendMode ).toInt();
3493  }
3494 }
3495 
3497  const QMap< QgsPalLayerSettings::DataDefinedProperties, QVariant >& ddValues )
3498 {
3499  if ( ddValues.contains( QgsPalLayerSettings::MultiLineWrapChar ) )
3500  {
3501  tmpLyr.wrapChar = ddValues.value( QgsPalLayerSettings::MultiLineWrapChar ).toString();
3502  }
3503 
3504  if ( !tmpLyr.wrapChar.isEmpty() )
3505  {
3506 
3507  if ( ddValues.contains( QgsPalLayerSettings::MultiLineHeight ) )
3508  {
3509  tmpLyr.multilineHeight = ddValues.value( QgsPalLayerSettings::MultiLineHeight ).toDouble();
3510  }
3511 
3512  if ( ddValues.contains( QgsPalLayerSettings::MultiLineAlignment ) )
3513  {
3515  }
3516 
3517  }
3518 
3519  if ( ddValues.contains( QgsPalLayerSettings::DirSymbDraw ) )
3520  {
3521  tmpLyr.addDirectionSymbol = ddValues.value( QgsPalLayerSettings::DirSymbDraw ).toBool();
3522  }
3523 
3524  if ( tmpLyr.addDirectionSymbol )
3525  {
3526 
3527  if ( ddValues.contains( QgsPalLayerSettings::DirSymbLeft ) )
3528  {
3529  tmpLyr.leftDirectionSymbol = ddValues.value( QgsPalLayerSettings::DirSymbLeft ).toString();
3530  }
3531  if ( ddValues.contains( QgsPalLayerSettings::DirSymbRight ) )
3532  {
3533  tmpLyr.rightDirectionSymbol = ddValues.value( QgsPalLayerSettings::DirSymbRight ).toString();
3534  }
3535 
3536  if ( ddValues.contains( QgsPalLayerSettings::DirSymbPlacement ) )
3537  {
3539  }
3540 
3541  if ( ddValues.contains( QgsPalLayerSettings::DirSymbReverse ) )
3542  {
3543  tmpLyr.reverseDirectionSymbol = ddValues.value( QgsPalLayerSettings::DirSymbReverse ).toBool();
3544  }
3545 
3546  }
3547 }
3548 
3550  const QMap< QgsPalLayerSettings::DataDefinedProperties, QVariant >& ddValues )
3551 {
3552  //buffer draw
3553  if ( ddValues.contains( QgsPalLayerSettings::BufferDraw ) )
3554  {
3555  tmpLyr.bufferDraw = ddValues.value( QgsPalLayerSettings::BufferDraw ).toBool();
3556  }
3557 
3558  if ( !tmpLyr.bufferDraw )
3559  {
3560  // tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
3561  return; // don't continue looking for unused values
3562  }
3563 
3564  //buffer size
3565  if ( ddValues.contains( QgsPalLayerSettings::BufferSize ) )
3566  {
3567  tmpLyr.bufferSize = ddValues.value( QgsPalLayerSettings::BufferSize ).toDouble();
3568  }
3569 
3570  //buffer transparency
3571  if ( ddValues.contains( QgsPalLayerSettings::BufferTransp ) )
3572  {
3573  tmpLyr.bufferTransp = ddValues.value( QgsPalLayerSettings::BufferTransp ).toInt();
3574  }
3575 
3576  //buffer size units
3577  if ( ddValues.contains( QgsPalLayerSettings::BufferUnit ) )
3578  {
3580  tmpLyr.bufferSizeInMapUnits = ( bufunit == QgsPalLayerSettings::MapUnits );
3581  }
3582 
3583  //buffer color
3584  if ( ddValues.contains( QgsPalLayerSettings::BufferColor ) )
3585  {
3586  QVariant ddColor = ddValues.value( QgsPalLayerSettings::BufferColor );
3587  tmpLyr.bufferColor = ddColor.value<QColor>();
3588  }
3589 
3590  // apply any transparency
3591  tmpLyr.bufferColor.setAlphaF(( 100.0 - ( double )( tmpLyr.bufferTransp ) ) / 100.0 );
3592 
3593  //buffer pen join style
3594  if ( ddValues.contains( QgsPalLayerSettings::BufferJoinStyle ) )
3595  {
3596  tmpLyr.bufferJoinStyle = ( Qt::PenJoinStyle )ddValues.value( QgsPalLayerSettings::BufferJoinStyle ).toInt();
3597  }
3598 
3599  //buffer blend mode
3600  if ( ddValues.contains( QgsPalLayerSettings::BufferBlendMode ) )
3601  {
3602  tmpLyr.bufferBlendMode = ( QPainter::CompositionMode )ddValues.value( QgsPalLayerSettings::BufferBlendMode ).toInt();
3603  }
3604 }
3605 
3607  const QMap< QgsPalLayerSettings::DataDefinedProperties, QVariant >& ddValues )
3608 {
3609  //shape draw
3610  if ( ddValues.contains( QgsPalLayerSettings::ShapeDraw ) )
3611  {
3612  tmpLyr.shapeDraw = ddValues.value( QgsPalLayerSettings::ShapeDraw ).toBool();
3613  }
3614 
3615  if ( !tmpLyr.shapeDraw )
3616  {
3617  return; // don't continue looking for unused values
3618  }
3619 
3620  if ( ddValues.contains( QgsPalLayerSettings::ShapeKind ) )
3621  {
3622  tmpLyr.shapeType = ( QgsPalLayerSettings::ShapeType )ddValues.value( QgsPalLayerSettings::ShapeKind ).toInt();
3623  }
3624 
3625  if ( ddValues.contains( QgsPalLayerSettings::ShapeSVGFile ) )
3626  {
3627  tmpLyr.shapeSVGFile = ddValues.value( QgsPalLayerSettings::ShapeSVGFile ).toString();
3628  }
3629 
3630  if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeType ) )
3631  {
3633  }
3634 
3635  if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeX ) )
3636  {
3637  tmpLyr.shapeSize.setX( ddValues.value( QgsPalLayerSettings::ShapeSizeX ).toDouble() );
3638  }
3639  if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeY ) )
3640  {
3641  tmpLyr.shapeSize.setY( ddValues.value( QgsPalLayerSettings::ShapeSizeY ).toDouble() );
3642  }
3643 
3644  if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeUnits ) )
3645  {
3647  }
3648 
3649  if ( ddValues.contains( QgsPalLayerSettings::ShapeRotationType ) )
3650  {
3652  }
3653 
3654  if ( ddValues.contains( QgsPalLayerSettings::ShapeRotation ) )
3655  {
3656  tmpLyr.shapeRotation = ddValues.value( QgsPalLayerSettings::ShapeRotation ).toDouble();
3657  }
3658 
3659  if ( ddValues.contains( QgsPalLayerSettings::ShapeOffset ) )
3660  {
3661  tmpLyr.shapeOffset = ddValues.value( QgsPalLayerSettings::ShapeOffset ).toPointF();
3662  }
3663 
3664  if ( ddValues.contains( QgsPalLayerSettings::ShapeOffsetUnits ) )
3665  {
3667  }
3668 
3669  if ( ddValues.contains( QgsPalLayerSettings::ShapeRadii ) )
3670  {
3671  tmpLyr.shapeRadii = ddValues.value( QgsPalLayerSettings::ShapeRadii ).toPointF();
3672  }
3673 
3674  if ( ddValues.contains( QgsPalLayerSettings::ShapeRadiiUnits ) )
3675  {
3677  }
3678 
3679  if ( ddValues.contains( QgsPalLayerSettings::ShapeTransparency ) )
3680  {
3681  tmpLyr.shapeTransparency = ddValues.value( QgsPalLayerSettings::ShapeTransparency ).toInt();
3682  }
3683 
3684  if ( ddValues.contains( QgsPalLayerSettings::ShapeBlendMode ) )
3685  {
3686  tmpLyr.shapeBlendMode = ( QPainter::CompositionMode )ddValues.value( QgsPalLayerSettings::ShapeBlendMode ).toInt();
3687  }
3688 
3689  if ( ddValues.contains( QgsPalLayerSettings::ShapeFillColor ) )
3690  {
3691  QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShapeFillColor );
3692  tmpLyr.shapeFillColor = ddColor.value<QColor>();
3693  }
3694 
3695  if ( ddValues.contains( QgsPalLayerSettings::ShapeBorderColor ) )
3696  {
3697  QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShapeBorderColor );
3698  tmpLyr.shapeBorderColor = ddColor.value<QColor>();
3699  }
3700 
3701  if ( ddValues.contains( QgsPalLayerSettings::ShapeBorderWidth ) )
3702  {
3703  tmpLyr.shapeBorderWidth = ddValues.value( QgsPalLayerSettings::ShapeBorderWidth ).toDouble();
3704  }
3705 
3706  if ( ddValues.contains( QgsPalLayerSettings::ShapeBorderWidthUnits ) )
3707  {
3709  }
3710 
3711  if ( ddValues.contains( QgsPalLayerSettings::ShapeJoinStyle ) )
3712  {
3713  tmpLyr.shapeJoinStyle = ( Qt::PenJoinStyle )ddValues.value( QgsPalLayerSettings::ShapeJoinStyle ).toInt();
3714  }
3715 }
3716 
3718  const QMap< QgsPalLayerSettings::DataDefinedProperties, QVariant >& ddValues )
3719 {
3720  //shadow draw
3721  if ( ddValues.contains( QgsPalLayerSettings::ShadowDraw ) )
3722  {
3723  tmpLyr.shadowDraw = ddValues.value( QgsPalLayerSettings::ShadowDraw ).toBool();
3724  }
3725 
3726  if ( !tmpLyr.shadowDraw )
3727  {
3728  return; // don't continue looking for unused values
3729  }
3730 
3731  if ( ddValues.contains( QgsPalLayerSettings::ShadowUnder ) )
3732  {
3734  }
3735 
3736  if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetAngle ) )
3737  {
3738  tmpLyr.shadowOffsetAngle = ddValues.value( QgsPalLayerSettings::ShadowOffsetAngle ).toInt();
3739  }
3740 
3741  if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetDist ) )
3742  {
3743  tmpLyr.shadowOffsetDist = ddValues.value( QgsPalLayerSettings::ShadowOffsetDist ).toDouble();
3744  }
3745 
3746  if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetUnits ) )
3747  {
3749  }
3750 
3751  if ( ddValues.contains( QgsPalLayerSettings::ShadowRadius ) )
3752  {
3753  tmpLyr.shadowRadius = ddValues.value( QgsPalLayerSettings::ShadowRadius ).toDouble();
3754  }
3755 
3756  if ( ddValues.contains( QgsPalLayerSettings::ShadowRadiusUnits ) )
3757  {
3759  }
3760 
3761  if ( ddValues.contains( QgsPalLayerSettings::ShadowTransparency ) )
3762  {
3763  tmpLyr.shadowTransparency = ddValues.value( QgsPalLayerSettings::ShadowTransparency ).toInt();
3764  }
3765 
3766  if ( ddValues.contains( QgsPalLayerSettings::ShadowScale ) )
3767  {
3768  tmpLyr.shadowScale = ddValues.value( QgsPalLayerSettings::ShadowScale ).toInt();
3769  }
3770 
3771  if ( ddValues.contains( QgsPalLayerSettings::ShadowColor ) )
3772  {
3773  QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShadowColor );
3774  tmpLyr.shadowColor = ddColor.value<QColor>();
3775  }
3776 
3777  if ( ddValues.contains( QgsPalLayerSettings::ShadowBlendMode ) )
3778  {
3779  tmpLyr.shadowBlendMode = ( QPainter::CompositionMode )ddValues.value( QgsPalLayerSettings::ShadowBlendMode ).toInt();
3780  }
3781 }
3782 
3784 {
3785  Q_ASSERT( mMapRenderer != NULL );
3786  QPainter* painter = context.painter();
3787  QgsRectangle extent = context.extent();
3788 
3789  if ( mLabelSearchTree )
3790  {
3792  }
3793 
3794  QTime t;
3795  t.start();
3796 
3797  // do the labeling itself
3798  double scale = mMapRenderer->scale(); // scale denominator
3799  QgsRectangle r = extent;
3800  double bbox[] = { r.xMinimum(), r.yMinimum(), r.xMaximum(), r.yMaximum() };
3801 
3802  std::list<LabelPosition*>* labels;
3803  pal::Problem* problem;
3804  try
3805  {
3806  problem = mPal->extractProblem( scale, bbox );
3807  }
3808  catch ( std::exception& e )
3809  {
3810  Q_UNUSED( e );
3811  QgsDebugMsgLevel( "PAL EXCEPTION :-( " + QString::fromLatin1( e.what() ), 4 );
3812  //mActiveLayers.clear(); // clean up
3813  return;
3814  }
3815 
3817 
3818  // draw rectangles with all candidates
3819  // this is done before actual solution of the problem
3820  // before number of candidates gets reduced
3821  mCandidates.clear();
3822  if ( mShowingCandidates && problem )
3823  {
3824  painter->setPen( QColor( 0, 0, 0, 64 ) );
3825  painter->setBrush( Qt::NoBrush );
3826  for ( int i = 0; i < problem->getNumFeatures(); i++ )
3827  {
3828  for ( int j = 0; j < problem->getFeatureCandidateCount( i ); j++ )
3829  {
3830  pal::LabelPosition* lp = problem->getFeatureCandidate( i, j );
3831 
3832  drawLabelCandidateRect( lp, painter, xform );
3833  }
3834  }
3835  }
3836 
3837  // find the solution
3838  labels = mPal->solveProblem( problem, mShowingAllLabels );
3839 
3840  QgsDebugMsgLevel( QString( "LABELING work: %1 ms ... labels# %2" ).arg( t.elapsed() ).arg( labels->size() ), 4 );
3841  t.restart();
3842 
3843  painter->setRenderHint( QPainter::Antialiasing );
3844 
3845  // draw the labels
3846  std::list<LabelPosition*>::iterator it = labels->begin();
3847  for ( ; it != labels->end(); ++it )
3848  {
3849  QgsPalGeometry* palGeometry = dynamic_cast< QgsPalGeometry* >(( *it )->getFeaturePart()->getUserGeometry() );
3850  if ( !palGeometry )
3851  {
3852  continue;
3853  }
3854 
3855  //layer names
3856  QString layerName = QString::fromUtf8(( *it )->getLayerName() );
3857  if ( palGeometry->isDiagram() )
3858  {
3859  //render diagram
3860  QHash<QgsVectorLayer*, QgsDiagramLayerSettings>::iterator dit = mActiveDiagramLayers.begin();
3861  for ( dit = mActiveDiagramLayers.begin(); dit != mActiveDiagramLayers.end(); ++dit )
3862  {
3863  if ( dit.key() && dit.key()->id().append( "d" ) == layerName )
3864  {
3865  QgsPoint outPt = xform->transform(( *it )->getX(), ( *it )->getY() );
3866  dit.value().renderer->renderDiagram( palGeometry->diagramAttributes(), context, QPointF( outPt.x(), outPt.y() ) );
3867  }
3868  }
3869 
3870  //insert into label search tree to manipulate position interactively
3871  if ( mLabelSearchTree )
3872  {
3873  //for diagrams, remove the additional 'd' at the end of the layer id
3874  QString layerId = layerName;
3875  layerId.chop( 1 );
3876  mLabelSearchTree->insertLabel( *it, QString( palGeometry->strId() ).toInt(), QString( "" ), layerId, QFont(), true, false );
3877  }
3878  continue;
3879  }
3880 
3881  const QgsPalLayerSettings& lyr = layer( layerName );
3882 
3883  // Copy to temp, editable layer settings
3884  // these settings will be changed by any data defined values, then used for rendering label components
3885  // settings may be adjusted during rendering of components
3886  QgsPalLayerSettings tmpLyr( lyr );
3887 
3888  // apply any previously applied data defined settings for the label
3889  const QMap< QgsPalLayerSettings::DataDefinedProperties, QVariant >& ddValues = palGeometry->dataDefinedValues();
3890 
3891  //font
3892  QFont dFont = palGeometry->definedFont();
3893  // following debug is >= Qt 4.8 only ( because of QFont::styleName() )
3894 #if QT_VERSION >= 0x040800
3895  QgsDebugMsgLevel( QString( "PAL font tmpLyr: %1, Style: %2" ).arg( tmpLyr.textFont.toString() ).arg( QFontInfo( tmpLyr.textFont ).styleName() ), 4 );
3896  QgsDebugMsgLevel( QString( "PAL font definedFont: %1, Style: %2" ).arg( dFont.toString() ).arg( dFont.styleName() ), 4 );
3897 #endif
3898  tmpLyr.textFont = dFont;
3899 
3900  // update tmpLyr with any data defined text style values
3901  dataDefinedTextStyle( tmpLyr, ddValues );
3902 
3903  // update tmpLyr with any data defined text buffer values
3904  dataDefinedTextBuffer( tmpLyr, ddValues );
3905 
3906  // update tmpLyr with any data defined text formatting values
3907  dataDefinedTextFormatting( tmpLyr, ddValues );
3908 
3909  // update tmpLyr with any data defined shape background values
3910  dataDefinedShapeBackground( tmpLyr, ddValues );
3911 
3912  // update tmpLyr with any data defined drop shadow values
3913  dataDefinedDropShadow( tmpLyr, ddValues );
3914 
3915 
3917 
3918  // Render the components of a label in reverse order
3919  // (backgrounds -> text)
3920 
3921  if ( tmpLyr.shadowDraw && tmpLyr.shadowUnder == QgsPalLayerSettings::ShadowLowest )
3922  {
3923  if ( tmpLyr.shapeDraw )
3924  {
3926  }
3927  else if ( tmpLyr.bufferDraw )
3928  {
3930  }
3931  else
3932  {
3934  }
3935  }
3936 
3937  if ( tmpLyr.shapeDraw )
3938  {
3939  drawLabel( *it, context, tmpLyr, LabelShape );
3940  }
3941 
3942  if ( tmpLyr.bufferDraw )
3943  {
3944  drawLabel( *it, context, tmpLyr, LabelBuffer );
3945  }
3946 
3947  drawLabel( *it, context, tmpLyr, LabelText );
3948 
3949  if ( mLabelSearchTree )
3950  {
3951  QString labeltext = (( QgsPalGeometry* )( *it )->getFeaturePart()->getUserGeometry() )->text();
3952  mLabelSearchTree->insertLabel( *it, QString( palGeometry->strId() ).toInt(), layerName, labeltext, dFont, false, palGeometry->isPinned() );
3953  }
3954  }
3955 
3956  // Reset composition mode for further drawing operations
3957  painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
3958 
3959  QgsDebugMsgLevel( QString( "LABELING draw: %1 ms" ).arg( t.elapsed() ), 4 );
3960 
3961  delete problem;
3962  delete labels;
3963 
3964  // delete all allocated geometries for features
3965  QHash<QgsVectorLayer*, QgsPalLayerSettings>::iterator lit;
3966  for ( lit = mActiveLayers.begin(); lit != mActiveLayers.end(); ++lit )
3967  {
3968  QgsPalLayerSettings& lyr = lit.value();
3969  for ( QList<QgsPalGeometry*>::iterator git = lyr.geometries.begin(); git != lyr.geometries.end(); ++git )
3970  delete *git;
3971  if ( lyr.limitNumLabels )
3972  {
3973  QgsDebugMsgLevel( QString( "mFeaturesToLabel: %1" ).arg( lyr.mFeaturesToLabel ), 4 );
3974  QgsDebugMsgLevel( QString( "maxNumLabels: %1" ).arg( lyr.maxNumLabels ), 4 );
3975  QgsDebugMsgLevel( QString( "mFeatsSendingToPal: %1" ).arg( lyr.mFeatsSendingToPal ), 4 );
3976  QgsDebugMsgLevel( QString( "mFeatsRegPal: %1" ).arg( lyr.geometries.count() ), 4 );
3977  }
3978  lyr.geometries.clear();
3979  }
3980 
3981  //delete all allocated geometries for diagrams
3982  QHash<QgsVectorLayer*, QgsDiagramLayerSettings>::iterator dIt = mActiveDiagramLayers.begin();
3983  for ( ; dIt != mActiveDiagramLayers.end(); ++dIt )
3984  {
3985  QgsDiagramLayerSettings& dls = dIt.value();
3986  for ( QList<QgsPalGeometry*>::iterator git = dls.geometries.begin(); git != dls.geometries.end(); ++git )
3987  {
3988  delete *git;
3989  }
3990  dls.geometries.clear();
3991  }
3992 }
3993 
3994 QList<QgsLabelPosition> QgsPalLabeling::labelsAtPosition( const QgsPoint& p )
3995 {
3996  QList<QgsLabelPosition> positions;
3997 
3998  QList<QgsLabelPosition*> positionPointers;
3999  if ( mLabelSearchTree )
4000  {
4001  mLabelSearchTree->label( p, positionPointers );
4002  QList<QgsLabelPosition*>::const_iterator pointerIt = positionPointers.constBegin();
4003  for ( ; pointerIt != positionPointers.constEnd(); ++pointerIt )
4004  {
4005  positions.push_back( QgsLabelPosition( **pointerIt ) );
4006  }
4007  }
4008 
4009  return positions;
4010 }
4011 
4012 QList<QgsLabelPosition> QgsPalLabeling::labelsWithinRect( const QgsRectangle& r )
4013 {
4014  QList<QgsLabelPosition> positions;
4015 
4016  QList<QgsLabelPosition*> positionPointers;
4017  if ( mLabelSearchTree )
4018  {
4019  mLabelSearchTree->labelsInRect( r, positionPointers );
4020  QList<QgsLabelPosition*>::const_iterator pointerIt = positionPointers.constBegin();
4021  for ( ; pointerIt != positionPointers.constEnd(); ++pointerIt )
4022  {
4023  positions.push_back( QgsLabelPosition( **pointerIt ) );
4024  }
4025  }
4026 
4027  return positions;
4028 }
4029 
4030 void QgsPalLabeling::numCandidatePositions( int& candPoint, int& candLine, int& candPolygon )
4031 {
4032  candPoint = mCandPoint;
4033  candLine = mCandLine;
4034  candPolygon = mCandPolygon;
4035 }
4036 
4037 void QgsPalLabeling::setNumCandidatePositions( int candPoint, int candLine, int candPolygon )
4038 {
4039  mCandPoint = candPoint;
4040  mCandLine = candLine;
4041  mCandPolygon = candPolygon;
4042 }
4043 
4045 {
4046  mSearch = s;
4047 }
4048 
4050 {
4051  return mSearch;
4052 }
4053 
4054 void QgsPalLabeling::drawLabelCandidateRect( pal::LabelPosition* lp, QPainter* painter, const QgsMapToPixel* xform )
4055 {
4056  QgsPoint outPt = xform->transform( lp->getX(), lp->getY() );
4057  QgsPoint outPt2 = xform->transform( lp->getX() + lp->getWidth(), lp->getY() + lp->getHeight() );
4058 
4059  painter->save();
4060  painter->translate( QPointF( outPt.x(), outPt.y() ) );
4061  painter->rotate( -lp->getAlpha() * 180 / M_PI );
4062  QRectF rect( 0, 0, outPt2.x() - outPt.x(), outPt2.y() - outPt.y() );
4063  painter->drawRect( rect );
4064  painter->restore();
4065 
4066  // save the rect
4067  rect.moveTo( outPt.x(), outPt.y() );
4068  mCandidates.append( QgsLabelCandidate( rect, lp->getCost() * 1000 ) );
4069 
4070  // show all parts of the multipart label
4071  if ( lp->getNextPart() )
4072  drawLabelCandidateRect( lp->getNextPart(), painter, xform );
4073 }
4074 
4075 void QgsPalLabeling::drawLabel( pal::LabelPosition* label, QgsRenderContext& context, QgsPalLayerSettings& tmpLyr, DrawLabelType drawType )
4076 {
4077  // NOTE: this is repeatedly called for multi-part labels
4078  QPainter* painter = context.painter();
4079  const QgsMapToPixel* xform = &context.mapToPixel();
4080 
4081  QgsLabelComponent component;
4082 
4083  // account for print output or image saving @ specific dpi
4084  if ( !qgsDoubleNear( context.rasterScaleFactor(), 1.0, 0.1 ) )
4085  {
4086  // find relative dpi scaling for local painter
4087  QPicture localPict;
4088  QPainter localp;
4089  localp.begin( &localPict );
4090 
4091  double localdpi = ( localp.device()->logicalDpiX() + localp.device()->logicalDpiY() ) / 2;
4092  double contextdpi = ( painter->device()->logicalDpiX() + painter->device()->logicalDpiY() ) / 2;
4093  component.setDpiRatio( localdpi / contextdpi );
4094 
4095  localp.end();
4096  }
4097 
4098  QgsPoint outPt = xform->transform( label->getX(), label->getY() );
4099 // QgsPoint outPt2 = xform->transform( label->getX() + label->getWidth(), label->getY() + label->getHeight() );
4100 // QRectF labelRect( 0, 0, outPt2.x() - outPt.x(), outPt2.y() - outPt.y() );
4101 
4102  component.setOrigin( outPt );
4103  component.setRotation( label->getAlpha() );
4104 
4105  if ( drawType == QgsPalLabeling::LabelShape )
4106  {
4107  // get rotated label's center point
4108  QgsPoint centerPt( outPt );
4109  QgsPoint outPt2 = xform->transform( label->getX() + label->getWidth() / 2,
4110  label->getY() + label->getHeight() / 2 );
4111 
4112  double xc = outPt2.x() - outPt.x();
4113  double yc = outPt2.y() - outPt.y();
4114 
4115  double angle = -label->getAlpha();
4116  double xd = xc * cos( angle ) - yc * sin( angle );
4117  double yd = xc * sin( angle ) + yc * cos( angle );
4118 
4119  centerPt.setX( centerPt.x() + xd );
4120  centerPt.setY( centerPt.y() + yd );
4121 
4122  component.setCenter( centerPt );
4123  component.setSize( QgsPoint( label->getWidth(), label->getHeight() ) );
4124 
4125  drawLabelBackground( context, component, tmpLyr );
4126  }
4127 
4128  if ( drawType == QgsPalLabeling::LabelBuffer
4129  || drawType == QgsPalLabeling::LabelText )
4130  {
4131 
4132  // TODO: optimize access :)
4133  QString text = (( QgsPalGeometry* )label->getFeaturePart()->getUserGeometry() )->text();
4134  QString txt = ( label->getPartId() == -1 ? text : QString( text[label->getPartId()] ) );
4135  QFontMetricsF* labelfm = (( QgsPalGeometry* )label->getFeaturePart()->getUserGeometry() )->getLabelFontMetrics();
4136 
4137  QString wrapchr = !tmpLyr.wrapChar.isEmpty() ? tmpLyr.wrapChar : QString( "\n" );
4138 
4139  //add the direction symbol if needed
4140  if ( !txt.isEmpty() && tmpLyr.placement == QgsPalLayerSettings::Line &&
4141  tmpLyr.addDirectionSymbol )
4142  {
4143  bool prependSymb = false;
4144  QString symb = tmpLyr.rightDirectionSymbol;
4145 
4146  if ( label->getReversed() )
4147  {
4148  prependSymb = true;
4149  symb = tmpLyr.leftDirectionSymbol;
4150  }
4151 
4152  if ( tmpLyr.reverseDirectionSymbol )
4153  {
4154  if ( symb == tmpLyr.rightDirectionSymbol )
4155  {
4156  prependSymb = true;
4157  symb = tmpLyr.leftDirectionSymbol;
4158  }
4159  else
4160  {
4161  prependSymb = false;
4162  symb = tmpLyr.rightDirectionSymbol;
4163  }
4164  }
4165 
4167  {
4168  prependSymb = true;
4169  symb = symb + wrapchr;
4170  }
4172  {
4173  prependSymb = false;
4174  symb = wrapchr + symb;
4175  }
4176 
4177  if ( prependSymb )
4178  {
4179  txt.prepend( symb );
4180  }
4181  else
4182  {
4183  txt.append( symb );
4184  }
4185  }
4186 
4187  //QgsDebugMsgLevel( "drawLabel " + txt, 4 );
4188 
4189  QStringList multiLineList = txt.split( wrapchr );
4190  int lines = multiLineList.size();
4191 
4192  double labelWidest = 0.0;
4193  for ( int i = 0; i < lines; ++i )
4194  {
4195  double labelWidth = labelfm->width( multiLineList.at( i ) );
4196  if ( labelWidth > labelWidest )
4197  {
4198  labelWidest = labelWidth;
4199  }
4200  }
4201 
4202  double labelHeight = labelfm->ascent() + labelfm->descent(); // ignore +1 for baseline
4203  // double labelHighest = labelfm->height() + ( double )(( lines - 1 ) * labelHeight * tmpLyr.multilineHeight );
4204 
4205  // needed to move bottom of text's descender to within bottom edge of label
4206  double ascentOffset = 0.25 * labelfm->ascent(); // labelfm->descent() is not enough
4207 
4208  for ( int i = 0; i < lines; ++i )
4209  {
4210  painter->save();
4211  painter->translate( QPointF( outPt.x(), outPt.y() ) );
4212  painter->rotate( -label->getAlpha() * 180 / M_PI );
4213 
4214  // scale down painter: the font size has been multiplied by raster scale factor
4215  // to workaround a Qt font scaling bug with small font sizes
4216  painter->scale( 1.0 / tmpLyr.rasterCompressFactor, 1.0 / tmpLyr.rasterCompressFactor );
4217 
4218  // figure x offset for horizontal alignment of multiple lines
4219  double xMultiLineOffset = 0.0;
4220  double labelWidth = labelfm->width( multiLineList.at( i ) );
4221  if ( lines > 1 && tmpLyr.multilineAlign != QgsPalLayerSettings::MultiLeft )
4222  {
4223  double labelWidthDiff = labelWidest - labelWidth;
4225  {
4226  labelWidthDiff /= 2;
4227  }
4228  xMultiLineOffset = labelWidthDiff;
4229  //QgsDebugMsgLevel( QString( "xMultiLineOffset: %1" ).arg( xMultiLineOffset ), 4 );
4230  }
4231 
4232  double yMultiLineOffset = ( lines - 1 - i ) * labelHeight * tmpLyr.multilineHeight;
4233  painter->translate( QPointF( xMultiLineOffset, - ascentOffset - yMultiLineOffset ) );
4234 
4235  component.setText( multiLineList.at( i ) );
4236  component.setSize( QgsPoint( labelWidth, labelHeight ) );
4237  component.setOffset( QgsPoint( 0.0, -ascentOffset ) );
4238  component.setRotation( -component.rotation() * 180 / M_PI );
4239  component.setRotationOffset( 0.0 );
4240 
4241  if ( drawType == QgsPalLabeling::LabelBuffer )
4242  {
4243  // draw label's buffer
4244  drawLabelBuffer( context, component, tmpLyr );
4245  }
4246  else
4247  {
4248  // draw label's text, QPainterPath method
4249  QPainterPath path;
4250  path.addText( 0, 0, tmpLyr.textFont, component.text() );
4251 
4252  // store text's drawing in QPicture for drop shadow call
4253  QPicture textPict;
4254  QPainter textp;
4255  textp.begin( &textPict );
4256  textp.setPen( Qt::NoPen );
4257  textp.setBrush( tmpLyr.textColor );
4258  textp.drawPath( path );
4259  textp.end();
4260 
4261  if ( tmpLyr.shadowDraw && tmpLyr.shadowUnder == QgsPalLayerSettings::ShadowText )
4262  {
4263  component.setPicture( &textPict );
4264  component.setPictureBuffer( 0.0 ); // no pen width to deal with
4265  component.setOrigin( QgsPoint( 0.0, 0.0 ) );
4266 
4267  drawLabelShadow( context, component, tmpLyr );
4268  }
4269 
4270  // paint the text
4271  if ( context.useAdvancedEffects() )
4272  {
4273  painter->setCompositionMode( tmpLyr.blendMode );
4274  }
4275 // painter->setPen( Qt::NoPen );
4276 // painter->setBrush( tmpLyr.textColor );
4277 // painter->drawPath( path );
4278 
4279  // scale for any print output or image saving @ specific dpi
4280  painter->scale( component.dpiRatio(), component.dpiRatio() );
4281  painter->drawPicture( 0, 0, textPict );
4282 
4283  // regular text draw, for testing optimization
4284 // painter->setFont( tmpLyr.textFont );
4285 // painter->setPen( tmpLyr.textColor );
4286 // painter->drawText( 0, 0, multiLineList.at( i ) );
4287 
4288  }
4289  painter->restore();
4290  }
4291  }
4292 
4293  // NOTE: this used to be within above multi-line loop block, at end. (a mistake since 2010? [LS])
4294  if ( label->getNextPart() )
4295  drawLabel( label->getNextPart(), context, tmpLyr, drawType );
4296 }
4297 
4299  QgsLabelComponent component,
4300  const QgsPalLayerSettings& tmpLyr )
4301 {
4302  QPainter* p = context.painter();
4303 
4304  double penSize = tmpLyr.scaleToPixelContext( tmpLyr.bufferSize, context,
4306 
4307  QPainterPath path;
4308  path.addText( 0, 0, tmpLyr.textFont, component.text() );
4309  QPen pen( tmpLyr.bufferColor );
4310  pen.setWidthF( penSize );
4311  pen.setJoinStyle( tmpLyr.bufferJoinStyle );
4312  QColor tmpColor( tmpLyr.bufferColor );
4313  // honor pref for whether to fill buffer interior
4314  if ( tmpLyr.bufferNoFill )
4315  {
4316  tmpColor.setAlpha( 0 );
4317  }
4318 
4319  // store buffer's drawing in QPicture for drop shadow call
4320  QPicture buffPict;
4321  QPainter buffp;
4322  buffp.begin( &buffPict );
4323  buffp.setPen( pen );
4324  buffp.setBrush( tmpColor );
4325  buffp.drawPath( path );
4326  buffp.end();
4327 
4328  if ( tmpLyr.shadowDraw && tmpLyr.shadowUnder == QgsPalLayerSettings::ShadowBuffer )
4329  {
4330  component.setOrigin( QgsPoint( 0.0, 0.0 ) );
4331  component.setPicture( &buffPict );
4332  component.setPictureBuffer( penSize / 2.0 );
4333 
4334  drawLabelShadow( context, component, tmpLyr );
4335  }
4336 
4337  p->save();
4338  if ( context.useAdvancedEffects() )
4339  {
4340  p->setCompositionMode( tmpLyr.bufferBlendMode );
4341  }
4342 // p->setPen( pen );
4343 // p->setBrush( tmpColor );
4344 // p->drawPath( path );
4345 
4346  // scale for any print output or image saving @ specific dpi
4347  p->scale( component.dpiRatio(), component.dpiRatio() );
4348  p->drawPicture( 0, 0, buffPict );
4349  p->restore();
4350 }
4351 
4353  QgsLabelComponent component,
4354  const QgsPalLayerSettings& tmpLyr )
4355 {
4356  QPainter* p = context.painter();
4357  double labelWidth = component.size().x(), labelHeight = component.size().y();
4358  //QgsDebugMsgLevel( QString( "Background label rotation: %1" ).arg( component.rotation() ), 4 );
4359 
4360  // shared calculations between shapes and SVG
4361 
4362  // configure angles, set component rotation and rotationOffset
4364  {
4365  component.setRotation( -( component.rotation() * 180 / M_PI ) ); // RotationSync
4366  component.setRotationOffset(
4368  }
4369  else // RotationFixed
4370  {
4371  component.setRotation( 0.0 ); // don't use label's rotation
4372  component.setRotationOffset( tmpLyr.shapeRotation );
4373  }
4374 
4375  // mm to map units conversion factor
4376  double mmToMapUnits = context.mapToPixel().mapUnitsPerPixel() * context.scaleFactor();
4377 
4378  // TODO: the following label-buffered generated shapes and SVG symbols should be moved into marker symbology classes
4379 
4380  if ( tmpLyr.shapeType == QgsPalLayerSettings::ShapeSVG )
4381  {
4382  // all calculations done in shapeSizeUnits, which are then passed to symbology class for painting
4383 
4384  if ( tmpLyr.shapeSVGFile.isEmpty() )
4385  return;
4386 
4387  double sizeOut = 0.0;
4388  // only one size used for SVG sizing/scaling (no use of shapeSize.y() or Y field in gui)
4390  {
4391  sizeOut = tmpLyr.shapeSize.x();
4392  }
4393  else if ( tmpLyr.shapeSizeType == QgsPalLayerSettings::SizeBuffer )
4394  {
4395  // add buffer to greatest dimension of label
4396  if ( labelWidth >= labelHeight )
4397  sizeOut = labelWidth;
4398  else if ( labelHeight > labelWidth )
4399  sizeOut = labelHeight;
4400 
4401  // label size in map units, convert to shapeSizeUnits, if different
4402  if ( tmpLyr.shapeSizeUnits == QgsPalLayerSettings::MM )
4403  {
4404  sizeOut /= mmToMapUnits;
4405  }
4406 
4407  // add buffer
4408  sizeOut += tmpLyr.shapeSize.x() * 2;
4409  }
4410 
4411  // don't bother rendering symbols smaller than 1x1 pixels in size
4412  // TODO: add option to not show any svgs under/over a certian size
4413  if ( tmpLyr.scaleToPixelContext( sizeOut, context, tmpLyr.shapeSizeUnits ) < 1.0 )
4414  return;
4415 
4416  QgsStringMap map; // for SVG symbology marker
4417  map["name"] = QgsSymbolLayerV2Utils::symbolNameToPath( tmpLyr.shapeSVGFile.trimmed() );
4418  map["size"] = QString::number( sizeOut );
4419  map["size_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit(
4421  map["angle"] = QString::number( 0.0 ); // angle is handled by this local painter
4422 
4423  // offset is handled by this local painter
4424  // TODO: see why the marker renderer doesn't seem to translate offset *after* applying rotation
4425  //map["offset"] = QgsSymbolLayerV2Utils::encodePoint( tmpLyr.shapeOffset );
4426  //map["offset_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit(
4427  // tmpLyr.shapeOffsetUnits == QgsPalLayerSettings::MapUnits ? QgsSymbolV2::MapUnit : QgsSymbolV2::MM );
4428 
4429  map["fill"] = tmpLyr.shapeFillColor.name();
4430  map["outline"] = tmpLyr.shapeBorderColor.name();
4431  map["outline-width"] = QString::number( tmpLyr.shapeBorderWidth );
4432 
4433  // TODO: fix overriding SVG symbol's border width/units in QgsSvgCache
4434  // currently broken, fall back to symbol's
4435  //map["outline_width_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit(
4436  // tmpLyr.shapeBorderWidthUnits == QgsPalLayerSettings::MapUnits ? QgsSymbolV2::MapUnit : QgsSymbolV2::MM );
4437 
4438  if ( tmpLyr.shadowDraw && tmpLyr.shadowUnder == QgsPalLayerSettings::ShadowShape )
4439  {
4440  // configure SVG shadow specs
4441  QgsStringMap shdwmap( map );
4442  shdwmap["fill"] = tmpLyr.shadowColor.name();
4443  shdwmap["outline"] = tmpLyr.shadowColor.name();
4444  shdwmap["size"] = QString::number( sizeOut * tmpLyr.rasterCompressFactor );
4445 
4446  // store SVG's drawing in QPicture for drop shadow call
4447  QPicture svgPict;
4448  QPainter svgp;
4449  svgp.begin( &svgPict );
4450 
4451  // draw shadow symbol
4452 
4453  // clone current render context map unit/mm conversion factors, but not
4454  // other map canvas parameters, then substitute this painter for use in symbology painting
4455  // NOTE: this is because the shadow needs to be scaled correctly for output to map canvas,
4456  // but will be created relative to the SVG's computed size, not the current map canvas
4457  QgsRenderContext shdwContext;
4458  shdwContext.setMapToPixel( context.mapToPixel() );
4459  shdwContext.setScaleFactor( context.scaleFactor() );
4460  shdwContext.setPainter( &svgp );
4461 
4462  QgsSymbolLayerV2* symShdwL = QgsSvgMarkerSymbolLayerV2::create( shdwmap );
4463  QgsSvgMarkerSymbolLayerV2* svgShdwM = static_cast<QgsSvgMarkerSymbolLayerV2*>( symShdwL );
4464  QgsSymbolV2RenderContext svgShdwContext( shdwContext, QgsSymbolV2::Mixed,
4465  ( 100.0 - ( double )( tmpLyr.shapeTransparency ) ) / 100.0 );
4466 
4467  double svgSize = tmpLyr.scaleToPixelContext( sizeOut, context, tmpLyr.shapeSizeUnits, true );
4468  svgShdwM->renderPoint( QPointF( svgSize / 2, -svgSize / 2 ), svgShdwContext );
4469  svgp.end();
4470 
4471  component.setPicture( &svgPict );
4472  // TODO: when SVG symbol's border width/units is fixed in QgsSvgCache, adjust for it here
4473  component.setPictureBuffer( 0.0 );
4474 
4475  component.setSize( QgsPoint( svgSize, svgSize ) );
4476  component.setOffset( QgsPoint( 0.0, 0.0 ) );
4477 
4478  // rotate about origin center of SVG
4479  p->save();
4480  p->translate( component.center().x(), component.center().y() );
4481  p->rotate( component.rotation() );
4482  p->scale( 1.0 / tmpLyr.rasterCompressFactor, 1.0 / tmpLyr.rasterCompressFactor );
4483  double xoff = tmpLyr.scaleToPixelContext( tmpLyr.shapeOffset.x(), context, tmpLyr.shapeOffsetUnits, true );
4484  double yoff = tmpLyr.scaleToPixelContext( tmpLyr.shapeOffset.y(), context, tmpLyr.shapeOffsetUnits, true );
4485  p->translate( QPointF( xoff, yoff ) );
4486  p->rotate( component.rotationOffset() );
4487  p->translate( -svgSize / 2, svgSize / 2 );
4488 
4489  drawLabelShadow( context, component, tmpLyr );
4490  p->restore();
4491 
4492  delete svgShdwM;
4493  svgShdwM = 0;
4494  }
4495 
4496  // draw the actual symbol
4498  QgsSvgMarkerSymbolLayerV2* svgM = static_cast<QgsSvgMarkerSymbolLayerV2*>( symL );
4499  QgsSymbolV2RenderContext svgContext( context, QgsSymbolV2::Mixed,
4500  ( 100.0 - ( double )( tmpLyr.shapeTransparency ) ) / 100.0 );
4501 
4502  p->save();
4503  if ( context.useAdvancedEffects() )
4504  {
4505  p->setCompositionMode( tmpLyr.shapeBlendMode );
4506  }
4507  p->translate( component.center().x(), component.center().y() );
4508  p->rotate( component.rotation() );
4509  double xoff = tmpLyr.scaleToPixelContext( tmpLyr.shapeOffset.x(), context, tmpLyr.shapeOffsetUnits );
4510  double yoff = tmpLyr.scaleToPixelContext( tmpLyr.shapeOffset.y(), context, tmpLyr.shapeOffsetUnits );
4511  p->translate( QPointF( xoff, yoff ) );
4512  p->rotate( component.rotationOffset() );
4513  svgM->renderPoint( QPointF( 0, 0 ), svgContext );
4514  p->setCompositionMode( QPainter::CompositionMode_SourceOver ); // just to be sure
4515  p->restore();
4516 
4517  delete svgM;
4518  svgM = 0;
4519 
4520  }
4521  else // Generated Shapes
4522  {
4523  // all calculations done in shapeSizeUnits
4524 
4525  double w = labelWidth / ( tmpLyr.shapeSizeUnits == QgsPalLayerSettings::MM ? mmToMapUnits : 1 );
4526  double h = labelHeight / ( tmpLyr.shapeSizeUnits == QgsPalLayerSettings::MM ? mmToMapUnits : 1 );
4527 
4528  double xsize = tmpLyr.shapeSize.x();
4529  double ysize = tmpLyr.shapeSize.y();
4530 
4532  {
4533  w = xsize;
4534  h = ysize;
4535  }
4536  else if ( tmpLyr.shapeSizeType == QgsPalLayerSettings::SizeBuffer )
4537  {
4539  {
4540  if ( w > h )
4541  h = w;
4542  else if ( h > w )
4543  w = h;
4544  }
4545  else if ( tmpLyr.shapeType == QgsPalLayerSettings::ShapeCircle )
4546  {
4547  // start with label bound by circle
4548  h = sqrt( pow( w, 2 ) + pow( h, 2 ) );
4549  w = h;
4550  }
4551  else if ( tmpLyr.shapeType == QgsPalLayerSettings::ShapeEllipse )
4552  {
4553  // start with label bound by ellipse
4554  h = h / sqrt( 2.0 ) * 2;
4555  w = w / sqrt( 2.0 ) * 2;
4556  }
4557 
4558  w += xsize * 2;
4559  h += ysize * 2;
4560  }
4561 
4562  // convert everything over to map pixels from here on
4563  w = tmpLyr.scaleToPixelContext( w, context, tmpLyr.shapeSizeUnits, true );
4564  h = tmpLyr.scaleToPixelContext( h, context, tmpLyr.shapeSizeUnits, true );
4565 
4566  // offsets match those of symbology: -x = left, -y = up
4567  QRectF rect( -w / 2.0, - h / 2.0, w, h );
4568 
4569  if ( rect.isNull() )
4570  return;
4571 
4572  p->save();
4573  p->translate( QPointF( component.center().x(), component.center().y() ) );
4574  p->rotate( component.rotation() );
4575  double xoff = tmpLyr.scaleToPixelContext( tmpLyr.shapeOffset.x(), context, tmpLyr.shapeOffsetUnits, true );
4576  double yoff = tmpLyr.scaleToPixelContext( tmpLyr.shapeOffset.y(), context, tmpLyr.shapeOffsetUnits, true );
4577  p->translate( QPointF( xoff, yoff ) );
4578  p->rotate( component.rotationOffset() );
4579 
4580  double penSize = tmpLyr.scaleToPixelContext( tmpLyr.shapeBorderWidth, context, tmpLyr.shapeBorderWidthUnits, true );
4581 
4582  QPen pen;
4583  if ( tmpLyr.shapeBorderWidth > 0 )
4584  {
4585  pen.setColor( tmpLyr.shapeBorderColor );
4586  pen.setWidthF( penSize );
4588  pen.setJoinStyle( tmpLyr.shapeJoinStyle );
4589  }
4590  else
4591  {
4592  pen = Qt::NoPen;
4593  }
4594 
4595  // store painting in QPicture for shadow drawing
4596  QPicture shapePict;
4597  QPainter shapep;
4598  shapep.begin( &shapePict );
4599  shapep.setPen( pen );
4600  shapep.setBrush( tmpLyr.shapeFillColor );
4601 
4604  {
4606  {
4607  shapep.drawRoundedRect( rect, tmpLyr.shapeRadii.x(), tmpLyr.shapeRadii.y(), Qt::RelativeSize );
4608  }
4609  else
4610  {
4611  double xRadius = tmpLyr.scaleToPixelContext( tmpLyr.shapeRadii.x(), context, tmpLyr.shapeRadiiUnits, true );
4612  double yRadius = tmpLyr.scaleToPixelContext( tmpLyr.shapeRadii.y(), context, tmpLyr.shapeRadiiUnits, true );
4613  shapep.drawRoundedRect( rect, xRadius, yRadius );
4614  }
4615  }
4616  else if ( tmpLyr.shapeType == QgsPalLayerSettings::ShapeEllipse
4618  {
4619  shapep.drawEllipse( rect );
4620  }
4621  shapep.end();
4622 
4623  p->scale( 1.0 / tmpLyr.rasterCompressFactor, 1.0 / tmpLyr.rasterCompressFactor );
4624 
4625  if ( tmpLyr.shadowDraw && tmpLyr.shadowUnder == QgsPalLayerSettings::ShadowShape )
4626  {
4627  component.setPicture( &shapePict );
4628  component.setPictureBuffer( penSize / 2.0 );
4629 
4630  component.setSize( QgsPoint( rect.width(), rect.height() ) );
4631  component.setOffset( QgsPoint( rect.width() / 2, -rect.height() / 2 ) );
4632  drawLabelShadow( context, component, tmpLyr );
4633  }
4634 
4635  p->setOpacity(( 100.0 - ( double )( tmpLyr.shapeTransparency ) ) / 100.0 );
4636  if ( context.useAdvancedEffects() )
4637  {
4638  p->setCompositionMode( tmpLyr.shapeBlendMode );
4639  }
4640 
4641  // scale for any print output or image saving @ specific dpi
4642  p->scale( component.dpiRatio(), component.dpiRatio() );
4643  p->drawPicture( 0, 0, shapePict );
4644  p->restore();
4645  }
4646 }
4647 
4649  QgsLabelComponent component,
4650  const QgsPalLayerSettings& tmpLyr )
4651 {
4652  // incoming component sizes should be multiplied by rasterCompressFactor, as
4653  // this allows shadows to be created at paint device dpi (e.g. high resolution),
4654  // then scale device painter by 1.0 / rasterCompressFactor for output
4655 
4656  QPainter* p = context.painter();
4657  double componentWidth = component.size().x(), componentHeight = component.size().y();
4658  double xOffset = component.offset().x(), yOffset = component.offset().y();
4659  double pictbuffer = component.pictureBuffer();
4660 
4661  // generate pixmap representation of label component drawing
4662  bool mapUnits = ( tmpLyr.shadowRadiusUnits == QgsPalLayerSettings::MapUnits );
4663  double radius = tmpLyr.scaleToPixelContext( tmpLyr.shadowRadius , context, tmpLyr.shadowRadiusUnits, !mapUnits );
4664  radius /= ( mapUnits ? tmpLyr.vectorScaleFactor / component.dpiRatio() : 1 );
4665  radius = ( int )( radius + 0.5 );
4666 
4667  // TODO: add labeling gui option to adjust blurBufferClippingScale to minimize pixels, or
4668  // to ensure shadow isn't clipped too tight. (Or, find a better method of buffering)
4669  double blurBufferClippingScale = 3.75;
4670  int blurbuffer = ( radius > 17 ? 16 : radius ) * blurBufferClippingScale;
4671 
4672  QImage blurImg( componentWidth + ( pictbuffer * 2.0 ) + ( blurbuffer * 2.0 ),
4673  componentHeight + ( pictbuffer * 2.0 ) + ( blurbuffer * 2.0 ),
4674  QImage::Format_ARGB32_Premultiplied );
4675 
4676  // TODO: add labeling gui option to not show any shadows under/over a certian size
4677  // keep very small QImages from causing paint device issues, i.e. must be at least > 1
4678  int minBlurImgSize = 1;
4679  // max limitation on QgsSvgCache is 10,000 for screen, which will probably be reasonable for future caching here, too
4680  // 4 x QgsSvgCache limit for output to print/image at higher dpi
4681  // TODO: should it be higher, scale with dpi, or have no limit? Needs testing with very large labels rendered at high dpi output
4682  int maxBlurImgSize = 40000;
4683  if ( blurImg.isNull()
4684  || ( blurImg.width() < minBlurImgSize || blurImg.height() < minBlurImgSize )
4685  || ( blurImg.width() > maxBlurImgSize || blurImg.height() > maxBlurImgSize ) )
4686  return;
4687 
4688  blurImg.fill( QColor( Qt::transparent ).rgba() );
4689  QPainter pictp;
4690  if ( !pictp.begin( &blurImg ) )
4691  return;
4692  pictp.setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform );
4693  QPointF imgOffset( blurbuffer + pictbuffer + xOffset,
4694  blurbuffer + pictbuffer + componentHeight + yOffset );
4695 
4696  pictp.drawPicture( imgOffset,
4697  *component.picture() );
4698 
4699  // overlay shadow color
4700  pictp.setCompositionMode( QPainter::CompositionMode_SourceIn );
4701  pictp.fillRect( blurImg.rect(), tmpLyr.shadowColor );
4702  pictp.end();
4703 
4704  // blur the QImage in-place
4705  if ( tmpLyr.shadowRadius > 0.0 && radius > 0 )
4706  {
4707  QgsSymbolLayerV2Utils::blurImageInPlace( blurImg, blurImg.rect(), radius, tmpLyr.shadowRadiusAlphaOnly );
4708  }
4709 
4710  if ( tmpLyr.showingShadowRects ) // engine setting, not per layer
4711  {
4712  // debug rect for QImage shadow registration and clipping visualization
4713  QPainter picti;
4714  picti.begin( &blurImg );
4715  picti.setBrush( Qt::Dense7Pattern );
4716  QPen imgPen( QColor( 0, 0, 255, 255 ) );
4717  imgPen.setWidth( 1 );
4718  picti.setPen( imgPen );
4719  picti.setOpacity( 0.1 );
4720  picti.drawRect( 0, 0, blurImg.width(), blurImg.height() );
4721  picti.end();
4722  }
4723 
4724  double offsetDist = tmpLyr.scaleToPixelContext( tmpLyr.shadowOffsetDist, context, tmpLyr.shadowOffsetUnits, true );
4725  double angleRad = tmpLyr.shadowOffsetAngle * M_PI / 180; // to radians
4726  if ( tmpLyr.shadowOffsetGlobal )
4727  {
4728  // TODO: check for differences in rotation origin and cw/ccw direction,
4729  // when this shadow function is used for something other than labels
4730 
4731  // it's 0-->cw-->360 for labels
4732  //QgsDebugMsgLevel( QString( "Shadow aggregated label rotation (degrees): %1" ).arg( component.rotation() + component.rotationOffset() ), 4 );
4733  angleRad -= ( component.rotation() * M_PI / 180 + component.rotationOffset() * M_PI / 180 );
4734  }
4735 
4736  QPointF transPt( -offsetDist * cos( angleRad + M_PI / 2 ),
4737  -offsetDist * sin( angleRad + M_PI / 2 ) );
4738 
4739  p->save();
4740  p->setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform );
4741  if ( context.useAdvancedEffects() )
4742  {
4743  p->setCompositionMode( tmpLyr.shadowBlendMode );
4744  }
4745  p->setOpacity(( 100.0 - ( double )( tmpLyr.shadowTransparency ) ) / 100.0 );
4746 
4747  double scale = ( double )tmpLyr.shadowScale / 100.0;
4748  // TODO: scale from center/center, left/center or left/top, instead of default left/bottom?
4749  p->scale( scale, scale );
4750  if ( component.useOrigin() )
4751  {
4752  p->translate( component.origin().x(), component.origin().y() );
4753  }
4754  p->translate( transPt );
4755  p->translate( -imgOffset.x(),
4756  -imgOffset.y() );
4757  p->drawImage( 0, 0, blurImg );
4758  p->restore();
4759 
4760  // debug rects
4761  if ( tmpLyr.showingShadowRects ) // engine setting, not per layer
4762  {
4763  // draw debug rect for QImage painting registration
4764  p->save();
4765  p->setBrush( Qt::NoBrush );
4766  QPen imgPen( QColor( 255, 0, 0, 10 ) );
4767  imgPen.setWidth( 2 );
4768  imgPen.setStyle( Qt::DashLine );
4769  p->setPen( imgPen );
4770  p->scale( scale, scale );
4771  if ( component.useOrigin() )
4772  {
4773  p->translate( component.origin().x(), component.origin().y() );
4774  }
4775  p->translate( transPt );
4776  p->translate( -imgOffset.x(),
4777  -imgOffset.y() );
4778  p->drawRect( 0, 0, blurImg.width(), blurImg.height() );
4779  p->restore();
4780 
4781  // draw debug rect for passed in component dimensions
4782  p->save();
4783  p->setBrush( Qt::NoBrush );
4784  QPen componentRectPen( QColor( 0, 255, 0, 70 ) );
4785  componentRectPen.setWidth( 1 );
4786  if ( component.useOrigin() )
4787  {
4788  p->translate( component.origin().x(), component.origin().y() );
4789  }
4790  p->setPen( componentRectPen );
4791  p->drawRect( QRect( -xOffset, -componentHeight - yOffset, componentWidth, componentHeight ) );
4792  p->restore();
4793  }
4794 }
4795 
4797 {
4798  // start with engine defaults for new project, or project that has no saved settings
4799  Pal p;
4800  bool saved = false;
4802  "PAL", "/SearchMethod", ( int )p.getSearch(), &saved ) );
4804  "PAL", "/CandidatesPoint", p.getPointP(), &saved );
4806  "PAL", "/CandidatesLine", p.getLineP(), &saved );
4808  "PAL", "/CandidatesPolygon", p.getPolyP(), &saved );
4810  "PAL", "/ShowingCandidates", false, &saved );
4812  "PAL", "/ShowingShadowRects", false, &saved );
4814  "PAL", "/ShowingAllLabels", false, &saved );
4815  mSavedWithProject = saved;
4816 }
4817 
4819 {
4820  QgsProject::instance()->writeEntry( "PAL", "/SearchMethod", ( int )mSearch );
4821  QgsProject::instance()->writeEntry( "PAL", "/CandidatesPoint", mCandPoint );
4822  QgsProject::instance()->writeEntry( "PAL", "/CandidatesLine", mCandLine );
4823  QgsProject::instance()->writeEntry( "PAL", "/CandidatesPolygon", mCandPolygon );
4824  QgsProject::instance()->writeEntry( "PAL", "/ShowingCandidates", mShowingCandidates );
4825  QgsProject::instance()->writeEntry( "PAL", "/ShowingShadowRects", mShowingShadowRects );
4826  QgsProject::instance()->writeEntry( "PAL", "/ShowingAllLabels", mShowingAllLabels );
4827  mSavedWithProject = true;
4828 }
4829 
4831 {
4832  QgsProject::instance()->removeEntry( "PAL", "/SearchMethod" );
4833  QgsProject::instance()->removeEntry( "PAL", "/CandidatesPoint" );
4834  QgsProject::instance()->removeEntry( "PAL", "/CandidatesLine" );
4835  QgsProject::instance()->removeEntry( "PAL", "/CandidatesPolygon" );
4836  QgsProject::instance()->removeEntry( "PAL", "/ShowingCandidates" );
4837  QgsProject::instance()->removeEntry( "PAL", "/ShowingShadowRects" );
4838  QgsProject::instance()->removeEntry( "PAL", "/ShowingAllLabels" );
4839  mSavedWithProject = false;
4840 }
4841 
4843 {
4844  QgsPalLabeling* lbl = new QgsPalLabeling();
4848  return lbl;
4849 }