Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsscalebarrenderer.cpp
3 : : -----------------------
4 : : begin : June 2008
5 : : copyright : (C) 2008 by Marco Hugentobler
6 : : email : marco.hugentobler@karto.baug.ethz.ch
7 : : ***************************************************************************/
8 : : /***************************************************************************
9 : : * *
10 : : * This program is free software; you can redistribute it and/or modify *
11 : : * it under the terms of the GNU General Public License as published by *
12 : : * the Free Software Foundation; either version 2 of the License, or *
13 : : * (at your option) any later version. *
14 : : * *
15 : : ***************************************************************************/
16 : :
17 : : #include "qgsscalebarrenderer.h"
18 : : #include "qgsscalebarsettings.h"
19 : : #include "qgslayoututils.h"
20 : : #include "qgstextrenderer.h"
21 : : #include "qgsexpressioncontextutils.h"
22 : : #include "qgsnumericformat.h"
23 : : #include "qgssymbol.h"
24 : : #include "qgssymbollayerutils.h"
25 : : #include <QFontMetricsF>
26 : : #include <QPainter>
27 : :
28 : 0 : void QgsScaleBarRenderer::drawDefaultLabels( QgsRenderContext &context, const QgsScaleBarSettings &settings, const ScaleBarContext &scaleContext ) const
29 : : {
30 : 0 : if ( !context.painter() )
31 : : {
32 : 0 : return;
33 : : }
34 : :
35 : 0 : QPainter *painter = context.painter();
36 : :
37 : 0 : painter->save();
38 : :
39 : 0 : QgsTextFormat format = settings.textFormat();
40 : :
41 : 0 : QgsExpressionContextScope *scaleScope = new QgsExpressionContextScope( QStringLiteral( "scalebar_text" ) );
42 : 0 : QgsExpressionContextScopePopper scopePopper( context.expressionContext(), scaleScope );
43 : :
44 : 0 : QString firstLabel = firstLabelString( settings );
45 : 0 : QFontMetricsF fontMetrics = QgsTextRenderer::fontMetrics( context, format );
46 : 0 : double xOffset = fontMetrics.horizontalAdvance( firstLabel ) / 2.0;
47 : :
48 : 0 : double scaledBoxContentSpace = context.convertToPainterUnits( settings.boxContentSpace(), QgsUnitTypes::RenderMillimeters );
49 : 0 : double scaledLabelBarSpace = context.convertToPainterUnits( settings.labelBarSpace(), QgsUnitTypes::RenderMillimeters );
50 : : double scaledHeight;
51 : 0 : if ( ( scaleContext.flags & Flag::FlagUsesSubdivisionsHeight ) && ( settings.numberOfSubdivisions() > 1 ) && ( settings.subdivisionsHeight() > settings.height() ) )
52 : : {
53 : 0 : scaledHeight = context.convertToPainterUnits( settings.subdivisionsHeight(), QgsUnitTypes::RenderMillimeters );
54 : 0 : }
55 : : else
56 : : {
57 : 0 : scaledHeight = context.convertToPainterUnits( settings.height(), QgsUnitTypes::RenderMillimeters );
58 : : }
59 : :
60 : 0 : double currentLabelNumber = 0.0;
61 : :
62 : 0 : int nSegmentsLeft = settings.numberOfSegmentsLeft();
63 : 0 : int segmentCounter = 0;
64 : :
65 : 0 : QString currentNumericLabel;
66 : 0 : QList<double> positions = segmentPositions( context, scaleContext, settings );
67 : :
68 : 0 : bool drawZero = true;
69 : 0 : switch ( settings.labelHorizontalPlacement() )
70 : : {
71 : : case QgsScaleBarSettings::LabelCenteredSegment:
72 : 0 : drawZero = false;
73 : 0 : break;
74 : : case QgsScaleBarSettings::LabelCenteredEdge:
75 : 0 : drawZero = true;
76 : 0 : break;
77 : : }
78 : :
79 : 0 : QgsNumericFormatContext numericContext;
80 : :
81 : 0 : for ( int i = 0; i < positions.size(); ++i )
82 : : {
83 : 0 : if ( segmentCounter == 0 && nSegmentsLeft > 0 )
84 : : {
85 : : //label first left segment
86 : 0 : currentNumericLabel = firstLabel;
87 : 0 : }
88 : 0 : else if ( segmentCounter != 0 && segmentCounter == nSegmentsLeft ) //reset label number to 0 if there are left segments
89 : : {
90 : 0 : currentLabelNumber = 0.0;
91 : 0 : }
92 : :
93 : 0 : if ( segmentCounter >= nSegmentsLeft )
94 : : {
95 : 0 : currentNumericLabel = settings.numericFormat()->formatDouble( currentLabelNumber / settings.mapUnitsPerScaleBarUnit(), numericContext );
96 : 0 : }
97 : :
98 : : //don't draw label for intermediate left segments or the zero label when it needs to be skipped
99 : 0 : if ( ( segmentCounter == 0 || segmentCounter >= nSegmentsLeft ) && ( currentNumericLabel != QLatin1String( "0" ) || drawZero ) )
100 : : {
101 : 0 : scaleScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "scale_value" ), currentNumericLabel, true, false ) );
102 : 0 : QPointF pos;
103 : 0 : if ( settings.labelHorizontalPlacement() == QgsScaleBarSettings::LabelCenteredSegment )
104 : : {
105 : 0 : if ( segmentCounter == 0 )
106 : : {
107 : : // if the segment counter is zero with a non zero label, this is the left-of-zero label
108 : 0 : pos.setX( context.convertToPainterUnits( positions.at( i ) + ( scaleContext.segmentWidth / 2 ), QgsUnitTypes::RenderMillimeters ) );
109 : 0 : }
110 : : else
111 : : {
112 : 0 : pos.setX( context.convertToPainterUnits( positions.at( i ) - ( scaleContext.segmentWidth / 2 ), QgsUnitTypes::RenderMillimeters ) );
113 : : }
114 : 0 : }
115 : : else
116 : : {
117 : 0 : pos.setX( context.convertToPainterUnits( positions.at( i ), QgsUnitTypes::RenderMillimeters ) + xOffset );
118 : : }
119 : 0 : pos.setY( fontMetrics.ascent() + scaledBoxContentSpace + ( settings.labelVerticalPlacement() == QgsScaleBarSettings::LabelBelowSegment ? scaledHeight + scaledLabelBarSpace : 0 ) );
120 : 0 : QgsTextRenderer::drawText( pos, 0, QgsTextRenderer::AlignCenter, QStringList() << currentNumericLabel, context, format );
121 : 0 : }
122 : :
123 : 0 : if ( segmentCounter >= nSegmentsLeft )
124 : : {
125 : 0 : currentLabelNumber += settings.unitsPerSegment();
126 : 0 : }
127 : 0 : ++segmentCounter;
128 : 0 : }
129 : :
130 : : //also draw the last label
131 : 0 : if ( !positions.isEmpty() )
132 : : {
133 : : // note: this label is NOT centered over the end of the bar - rather the numeric portion
134 : : // of it is, without considering the unit label suffix. That's drawn at the end after
135 : : // horizontally centering just the numeric portion.
136 : 0 : currentNumericLabel = settings.numericFormat()->formatDouble( currentLabelNumber / settings.mapUnitsPerScaleBarUnit(), numericContext );
137 : 0 : scaleScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "scale_value" ), currentNumericLabel, true, false ) );
138 : 0 : QPointF pos;
139 : 0 : pos.setY( fontMetrics.ascent() + scaledBoxContentSpace + ( settings.labelVerticalPlacement() == QgsScaleBarSettings::LabelBelowSegment ? scaledHeight + scaledLabelBarSpace : 0 ) );
140 : 0 : if ( settings.labelHorizontalPlacement() == QgsScaleBarSettings::LabelCenteredSegment )
141 : : {
142 : 0 : pos.setX( context.convertToPainterUnits( positions.at( positions.size() - 1 ) + ( scaleContext.segmentWidth / 2 ), QgsUnitTypes::RenderMillimeters ) + xOffset );
143 : 0 : QgsTextRenderer::drawText( pos, 0, QgsTextRenderer::AlignCenter, QStringList() << ( currentNumericLabel + ' ' + settings.unitLabel() ), context, format );
144 : 0 : }
145 : : else
146 : : {
147 : 0 : pos.setX( context.convertToPainterUnits( positions.at( positions.size() - 1 ) + scaleContext.segmentWidth, QgsUnitTypes::RenderMillimeters ) + xOffset
148 : 0 : - fontMetrics.horizontalAdvance( currentNumericLabel ) / 2.0 );
149 : 0 : QgsTextRenderer::drawText( pos, 0, QgsTextRenderer::AlignLeft, QStringList() << ( currentNumericLabel + ' ' + settings.unitLabel() ), context, format );
150 : : }
151 : 0 : }
152 : :
153 : 0 : painter->restore();
154 : 0 : }
155 : :
156 : 0 : QgsScaleBarRenderer::Flags QgsScaleBarRenderer::flags() const
157 : : {
158 : 0 : return QgsScaleBarRenderer::Flags();
159 : : }
160 : :
161 : 0 : int QgsScaleBarRenderer::sortKey() const
162 : : {
163 : 0 : return 100;
164 : : }
165 : :
166 : 0 : QSizeF QgsScaleBarRenderer::calculateBoxSize( const QgsScaleBarSettings &settings,
167 : : const QgsScaleBarRenderer::ScaleBarContext &scaleContext ) const
168 : : {
169 : 0 : QFont font = settings.textFormat().toQFont();
170 : :
171 : : //consider centered first label
172 : 0 : double firstLabelWidth = QgsLayoutUtils::textWidthMM( font, firstLabelString( settings ) );
173 : 0 : if ( settings.labelHorizontalPlacement() == QgsScaleBarSettings::LabelCenteredSegment )
174 : : {
175 : 0 : if ( firstLabelWidth > scaleContext.segmentWidth )
176 : : {
177 : 0 : firstLabelWidth = ( firstLabelWidth - scaleContext.segmentWidth ) / 2;
178 : 0 : }
179 : : else
180 : : {
181 : 0 : firstLabelWidth = 0.0;
182 : : }
183 : 0 : }
184 : : else
185 : : {
186 : 0 : firstLabelWidth = firstLabelWidth / 2;
187 : : }
188 : :
189 : : //consider last number and label
190 : 0 : double largestLabelNumber = settings.numberOfSegments() * settings.unitsPerSegment() / settings.mapUnitsPerScaleBarUnit();
191 : 0 : QString largestNumberLabel = settings.numericFormat()->formatDouble( largestLabelNumber, QgsNumericFormatContext() );
192 : 0 : QString largestLabel = largestNumberLabel + ' ' + settings.unitLabel();
193 : : double largestLabelWidth;
194 : 0 : if ( settings.labelHorizontalPlacement() == QgsScaleBarSettings::LabelCenteredSegment )
195 : : {
196 : 0 : largestLabelWidth = QgsLayoutUtils::textWidthMM( font, largestLabel );
197 : 0 : if ( largestLabelWidth > scaleContext.segmentWidth )
198 : : {
199 : 0 : largestLabelWidth = ( largestLabelWidth - scaleContext.segmentWidth ) / 2;
200 : 0 : }
201 : : else
202 : : {
203 : 0 : largestLabelWidth = 0.0;
204 : : }
205 : 0 : }
206 : : else
207 : : {
208 : 0 : largestLabelWidth = QgsLayoutUtils::textWidthMM( font, largestLabel ) - QgsLayoutUtils::textWidthMM( font, largestNumberLabel ) / 2;
209 : : }
210 : :
211 : 0 : double totalBarLength = scaleContext.segmentWidth * ( settings.numberOfSegments() + ( settings.numberOfSegmentsLeft() > 0 ? 1 : 0 ) );
212 : :
213 : : // this whole method is deprecated, so we can still call the deprecated settings.pen() getter
214 : : Q_NOWARN_DEPRECATED_PUSH
215 : 0 : double width = firstLabelWidth + totalBarLength + 2 * settings.pen().widthF() + largestLabelWidth + 2 * settings.boxContentSpace();
216 : : Q_NOWARN_DEPRECATED_POP
217 : :
218 : 0 : double height = settings.height() + settings.labelBarSpace() + 2 * settings.boxContentSpace() + QgsLayoutUtils::fontAscentMM( font );
219 : :
220 : 0 : return QSizeF( width, height );
221 : 0 : }
222 : :
223 : 0 : QSizeF QgsScaleBarRenderer::calculateBoxSize( QgsRenderContext &context, const QgsScaleBarSettings &settings, const QgsScaleBarRenderer::ScaleBarContext &scaleContext ) const
224 : : {
225 : 0 : const double painterToMm = 1.0 / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
226 : : //consider centered first label
227 : 0 : double firstLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << firstLabelString( settings ) ) * painterToMm;
228 : :
229 : 0 : if ( settings.labelHorizontalPlacement() == QgsScaleBarSettings::LabelCenteredSegment )
230 : : {
231 : 0 : if ( firstLabelWidth > scaleContext.segmentWidth )
232 : : {
233 : 0 : firstLabelWidth = ( firstLabelWidth - scaleContext.segmentWidth ) / 2;
234 : 0 : }
235 : : else
236 : : {
237 : 0 : firstLabelWidth = 0.0;
238 : : }
239 : 0 : }
240 : : else
241 : : {
242 : 0 : firstLabelWidth = firstLabelWidth / 2;
243 : : }
244 : :
245 : : //consider last number and label
246 : 0 : double largestLabelNumber = settings.numberOfSegments() * settings.unitsPerSegment() / settings.mapUnitsPerScaleBarUnit();
247 : 0 : QString largestNumberLabel = settings.numericFormat()->formatDouble( largestLabelNumber, QgsNumericFormatContext() );
248 : 0 : QString largestLabel = largestNumberLabel + ' ' + settings.unitLabel();
249 : : double largestLabelWidth;
250 : 0 : if ( settings.labelHorizontalPlacement() == QgsScaleBarSettings::LabelCenteredSegment )
251 : : {
252 : 0 : largestLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << largestLabel ) * painterToMm;
253 : :
254 : 0 : if ( largestLabelWidth > scaleContext.segmentWidth )
255 : : {
256 : 0 : largestLabelWidth = ( largestLabelWidth - scaleContext.segmentWidth ) / 2;
257 : 0 : }
258 : : else
259 : : {
260 : 0 : largestLabelWidth = 0.0;
261 : : }
262 : 0 : }
263 : : else
264 : : {
265 : 0 : largestLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << largestLabel ) * painterToMm
266 : 0 : - QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << largestNumberLabel ) * painterToMm / 2;
267 : : }
268 : :
269 : 0 : double totalBarLength = scaleContext.segmentWidth * ( settings.numberOfSegments() + ( settings.numberOfSegmentsLeft() > 0 ? 1 : 0 ) );
270 : :
271 : 0 : double lineWidth = QgsSymbolLayerUtils::estimateMaxSymbolBleed( settings.lineSymbol(), context ) * 2;
272 : : // need to convert to mm
273 : 0 : lineWidth /= context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
274 : :
275 : 0 : double width = firstLabelWidth + totalBarLength + 2 * lineWidth + largestLabelWidth + 2 * settings.boxContentSpace();
276 : : double height;
277 : 0 : if ( ( scaleContext.flags & Flag::FlagUsesSubdivisionsHeight ) && ( settings.numberOfSubdivisions() > 1 ) && ( settings.subdivisionsHeight() > settings.height() ) )
278 : : {
279 : 0 : height = settings.subdivisionsHeight();
280 : 0 : }
281 : : else
282 : : {
283 : 0 : height = settings.height();
284 : : }
285 : :
286 : : // TODO -- we technically should check the height of ALL labels here and take the maximum
287 : 0 : height += settings.labelBarSpace() + 2 * settings.boxContentSpace() + QgsTextRenderer::textHeight( context, settings.textFormat(), QStringList() << largestLabel ) * painterToMm;
288 : :
289 : 0 : return QSizeF( width, height );
290 : 0 : }
291 : :
292 : 0 : bool QgsScaleBarRenderer::applyDefaultSettings( QgsScaleBarSettings & ) const
293 : : {
294 : 0 : return false;
295 : : }
296 : :
297 : 0 : QString QgsScaleBarRenderer::firstLabelString( const QgsScaleBarSettings &settings ) const
298 : : {
299 : 0 : if ( settings.numberOfSegmentsLeft() > 0 )
300 : : {
301 : 0 : return settings.numericFormat()->formatDouble( settings.unitsPerSegment() / settings.mapUnitsPerScaleBarUnit(), QgsNumericFormatContext() );
302 : : }
303 : : else
304 : : {
305 : 0 : return settings.numericFormat()->formatDouble( 0, QgsNumericFormatContext() );
306 : : }
307 : 0 : }
308 : :
309 : 0 : double QgsScaleBarRenderer::firstLabelXOffset( const QgsScaleBarSettings &settings ) const
310 : : {
311 : 0 : QString firstLabel = firstLabelString( settings );
312 : : Q_NOWARN_DEPRECATED_PUSH
313 : 0 : return QgsLayoutUtils::textWidthMM( settings.font(), firstLabel ) / 2.0;
314 : : Q_NOWARN_DEPRECATED_POP
315 : 0 : }
316 : :
317 : 0 : double QgsScaleBarRenderer::firstLabelXOffset( const QgsScaleBarSettings &settings, const QgsRenderContext &context, const ScaleBarContext &scaleContext ) const
318 : : {
319 : 0 : QString firstLabel = firstLabelString( settings );
320 : 0 : double firstLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << firstLabel );
321 : 0 : if ( settings.labelHorizontalPlacement() == QgsScaleBarSettings::LabelCenteredSegment )
322 : : {
323 : 0 : if ( firstLabelWidth > scaleContext.segmentWidth )
324 : : {
325 : 0 : firstLabelWidth = ( firstLabelWidth - scaleContext.segmentWidth ) / 2;
326 : 0 : }
327 : : else
328 : : {
329 : 0 : firstLabelWidth = 0.0;
330 : : }
331 : 0 : }
332 : : else
333 : : {
334 : 0 : firstLabelWidth = firstLabelWidth / 2;
335 : : }
336 : 0 : return firstLabelWidth;
337 : 0 : }
338 : :
339 : 0 : QList<double> QgsScaleBarRenderer::segmentPositions( const ScaleBarContext &scaleContext, const QgsScaleBarSettings &settings ) const
340 : : {
341 : 0 : QList<double> positions;
342 : :
343 : : // this whole method is deprecated, so calling a deprecated function is fine
344 : : Q_NOWARN_DEPRECATED_PUSH
345 : 0 : double currentXCoord = settings.pen().widthF() + settings.boxContentSpace();
346 : : Q_NOWARN_DEPRECATED_POP
347 : :
348 : : //left segments
349 : 0 : double leftSegmentSize = scaleContext.segmentWidth / settings.numberOfSegmentsLeft();
350 : 0 : positions.reserve( settings.numberOfSegmentsLeft() + settings.numberOfSegments() );
351 : 0 : for ( int i = 0; i < settings.numberOfSegmentsLeft(); ++i )
352 : : {
353 : 0 : positions << currentXCoord;
354 : 0 : currentXCoord += leftSegmentSize;
355 : 0 : }
356 : :
357 : : //right segments
358 : 0 : for ( int i = 0; i < settings.numberOfSegments(); ++i )
359 : : {
360 : 0 : positions << currentXCoord;
361 : 0 : currentXCoord += scaleContext.segmentWidth;
362 : 0 : }
363 : 0 : return positions;
364 : 0 : }
365 : :
366 : 0 : QList<double> QgsScaleBarRenderer::segmentPositions( QgsRenderContext &context, const QgsScaleBarRenderer::ScaleBarContext &scaleContext, const QgsScaleBarSettings &settings ) const
367 : : {
368 : 0 : QList<double> positions;
369 : :
370 : 0 : double lineWidth = QgsSymbolLayerUtils::estimateMaxSymbolBleed( settings.lineSymbol(), context ) * 2.0;
371 : : // need to convert to mm
372 : 0 : lineWidth /= context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
373 : :
374 : 0 : double currentXCoord = lineWidth + settings.boxContentSpace();
375 : :
376 : : //left segments
377 : 0 : double leftSegmentSize = scaleContext.segmentWidth / settings.numberOfSegmentsLeft();
378 : 0 : positions.reserve( settings.numberOfSegmentsLeft() + settings.numberOfSegments() );
379 : 0 : for ( int i = 0; i < settings.numberOfSegmentsLeft(); ++i )
380 : : {
381 : 0 : positions << currentXCoord;
382 : 0 : currentXCoord += leftSegmentSize;
383 : 0 : }
384 : :
385 : : //right segments
386 : 0 : for ( int i = 0; i < settings.numberOfSegments(); ++i )
387 : : {
388 : 0 : positions << currentXCoord;
389 : 0 : currentXCoord += scaleContext.segmentWidth;
390 : 0 : }
391 : 0 : return positions;
392 : 0 : }
393 : :
394 : 0 : QList<double> QgsScaleBarRenderer::segmentWidths( const ScaleBarContext &scaleContext, const QgsScaleBarSettings &settings ) const
395 : : {
396 : 0 : QList<double> widths;
397 : 0 : widths.reserve( settings.numberOfSegmentsLeft() + settings.numberOfSegments() );
398 : :
399 : : //left segments
400 : 0 : if ( settings.numberOfSegmentsLeft() > 0 )
401 : : {
402 : 0 : double leftSegmentSize = scaleContext.segmentWidth / settings.numberOfSegmentsLeft();
403 : 0 : for ( int i = 0; i < settings.numberOfSegmentsLeft(); ++i )
404 : : {
405 : 0 : widths << leftSegmentSize;
406 : 0 : }
407 : 0 : }
408 : :
409 : : //right segments
410 : 0 : for ( int i = 0; i < settings.numberOfSegments(); ++i )
411 : : {
412 : 0 : widths << scaleContext.segmentWidth;
413 : 0 : }
414 : :
415 : 0 : return widths;
416 : 0 : }
|