1 /***************************************************************************
2                             qgsticksscalebarrenderer.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 "qgsticksscalebarrenderer.h"
18 #include "qgsscalebarsettings.h"
19 #include "qgslayoututils.h"
20 #include "qgssymbol.h"
21 #include "qgslinesymbol.h"
22 #include "qgstextrenderer.h"
23 #include <QPainter>
24 
QgsTicksScaleBarRenderer(QgsTicksScaleBarRenderer::TickPosition position)25 QgsTicksScaleBarRenderer::QgsTicksScaleBarRenderer( QgsTicksScaleBarRenderer::TickPosition position )
26   : mTickPosition( position )
27 {
28 
29 }
30 
id() const31 QString QgsTicksScaleBarRenderer::id() const
32 {
33   switch ( mTickPosition )
34   {
35     case TicksUp:
36       return QStringLiteral( "Line Ticks Up" );
37     case TicksDown:
38       return QStringLiteral( "Line Ticks Down" );
39     case TicksMiddle:
40       return QStringLiteral( "Line Ticks Middle" );
41   }
42   return QString();  // to make gcc happy
43 }
44 
visibleName() const45 QString QgsTicksScaleBarRenderer::visibleName() const
46 {
47   switch ( mTickPosition )
48   {
49     case TicksUp:
50       return QObject::tr( "Line Ticks Up" );
51     case TicksDown:
52       return QObject::tr( "Line Ticks Down" );
53     case TicksMiddle:
54       return QObject::tr( "Line Ticks Middle" );
55   }
56   return QString();  // to make gcc happy
57 
58 }
59 
sortKey() const60 int QgsTicksScaleBarRenderer::sortKey() const
61 {
62   switch ( mTickPosition )
63   {
64     case TicksUp:
65       return 5;
66     case TicksDown:
67       return 4;
68     case TicksMiddle:
69       return 3;
70   }
71   return 6;
72 }
73 
flags() const74 QgsScaleBarRenderer::Flags QgsTicksScaleBarRenderer::flags() const
75 {
76   return Flag::FlagUsesLineSymbol |
77          Flag::FlagUsesDivisionSymbol |
78          Flag::FlagUsesSubdivisionSymbol |
79          Flag::FlagRespectsUnits |
80          Flag::FlagRespectsMapUnitsPerScaleBarUnit |
81          Flag::FlagUsesUnitLabel |
82          Flag::FlagUsesSegments |
83          Flag::FlagUsesLabelBarSpace |
84          Flag::FlagUsesLabelVerticalPlacement |
85          Flag::FlagUsesLabelHorizontalPlacement |
86          Flag::FlagUsesSubdivisions |
87          Flag::FlagUsesSubdivisionsHeight;
88 }
89 
clone() const90 QgsTicksScaleBarRenderer *QgsTicksScaleBarRenderer::clone() const
91 {
92   return new QgsTicksScaleBarRenderer( * this );
93 }
94 
draw(QgsRenderContext & context,const QgsScaleBarSettings & settings,const ScaleBarContext & scaleContext) const95 void QgsTicksScaleBarRenderer::draw( QgsRenderContext &context, const QgsScaleBarSettings &settings, const ScaleBarContext &scaleContext ) const
96 {
97   if ( !context.painter() )
98     return;
99 
100   QPainter *painter = context.painter();
101 
102   const double scaledLabelBarSpace = context.convertToPainterUnits( settings.labelBarSpace(), QgsUnitTypes::RenderMillimeters );
103   const double scaledBoxContentSpace = context.convertToPainterUnits( settings.boxContentSpace(), QgsUnitTypes::RenderMillimeters );
104   const QFontMetricsF fontMetrics = QgsTextRenderer::fontMetrics( context, settings.textFormat() );
105   const double barTopPosition = scaledBoxContentSpace + ( settings.labelVerticalPlacement() == QgsScaleBarSettings::LabelAboveSegment ? fontMetrics.ascent() + scaledLabelBarSpace : 0 );
106   const double scaledHeight = context.convertToPainterUnits( settings.height(), QgsUnitTypes::RenderMillimeters );
107   const double scaledSubdivisionsHeight = context.convertToPainterUnits( settings.subdivisionsHeight(), QgsUnitTypes::RenderMillimeters );
108   const double scaledMaxHeight = ( ( settings.numberOfSubdivisions() > 1 ) && ( scaledSubdivisionsHeight > scaledHeight ) ) ? scaledSubdivisionsHeight : scaledHeight;
109   const double middlePosition = barTopPosition + scaledMaxHeight / 2.0;
110   const double bottomPosition = barTopPosition + scaledMaxHeight;
111 
112   const double xOffset = firstLabelXOffset( settings, context, scaleContext );
113 
114   painter->save();
115   context.setPainterFlagsUsingContext( painter );
116 
117   std::unique_ptr< QgsLineSymbol > symbol( settings.lineSymbol()->clone() );
118   symbol->startRender( context );
119 
120   std::unique_ptr< QgsLineSymbol > divisionSymbol( settings.divisionLineSymbol()->clone() );
121   divisionSymbol->startRender( context );
122 
123   std::unique_ptr< QgsLineSymbol > subdivisionSymbol( settings.subdivisionLineSymbol()->clone() );
124   subdivisionSymbol->startRender( context );
125 
126   const QList<double> positions = segmentPositions( context, scaleContext, settings );
127 
128   // vertical positions
129   double verticalPos = 0.0;
130   QList<double> subTickPositionsY;
131   QList<double> tickPositionsY;
132   switch ( mTickPosition )
133   {
134     case TicksDown:
135       verticalPos = barTopPosition;
136       subTickPositionsY << verticalPos;
137       subTickPositionsY << verticalPos + scaledSubdivisionsHeight;
138       tickPositionsY << verticalPos;
139       tickPositionsY << verticalPos + scaledHeight;
140       break;
141     case TicksMiddle:
142       verticalPos = middlePosition;
143       subTickPositionsY << verticalPos + scaledSubdivisionsHeight / 2.0;
144       subTickPositionsY << verticalPos - scaledSubdivisionsHeight / 2.0;
145       tickPositionsY << verticalPos + scaledHeight / 2.0;
146       tickPositionsY << verticalPos - scaledHeight / 2.0;
147       break;
148     case TicksUp:
149       verticalPos = bottomPosition;
150       subTickPositionsY << verticalPos;
151       subTickPositionsY << verticalPos - scaledSubdivisionsHeight;
152       tickPositionsY << verticalPos;
153       tickPositionsY << verticalPos - scaledHeight;
154       break;
155   }
156 
157   int symbolLayerCount = symbol->symbolLayerCount();
158   symbolLayerCount = std::max( symbolLayerCount, divisionSymbol->symbolLayerCount() );
159   symbolLayerCount = std::max( symbolLayerCount, subdivisionSymbol->symbolLayerCount() );
160 
161   // we render the bar symbol-layer-by-symbol-layer, to avoid ugliness where the lines overlap in multi-layer symbols
162   for ( int layer = 0; layer < symbolLayerCount; ++ layer )
163   {
164     const bool drawDivisionsForThisSymbolLayer = layer < divisionSymbol->symbolLayerCount();
165     const bool drawSubdivisionsForThisSymbolLayer = layer < subdivisionSymbol->symbolLayerCount();
166     const bool drawLineForThisSymbolLayer = layer < symbol->symbolLayerCount();
167 
168     if ( drawDivisionsForThisSymbolLayer )
169     {
170       // first draw the vertical lines for segments
171       for ( int i = 0; i < positions.size(); ++i )
172       {
173         const double thisX = context.convertToPainterUnits( positions.at( i ), QgsUnitTypes::RenderMillimeters ) + xOffset;
174         divisionSymbol->renderPolyline( QPolygonF() << QPointF( thisX, tickPositionsY.at( 0 ) )
175                                         << QPointF( thisX, tickPositionsY.at( 1 ) ), nullptr, context, layer );
176       }
177     }
178 
179     // draw the vertical lines for right subdivisions
180     if ( drawSubdivisionsForThisSymbolLayer )
181     {
182       for ( int i = settings.numberOfSegmentsLeft(); i < positions.size(); ++i )
183       {
184         for ( int j = 1; j < settings.numberOfSubdivisions(); ++j )
185         {
186           const double thisSubX = context.convertToPainterUnits( positions.at( i ) + j * scaleContext.segmentWidth / settings.numberOfSubdivisions(), QgsUnitTypes::RenderMillimeters ) + xOffset;
187           subdivisionSymbol->renderPolyline( QPolygonF() << QPointF( thisSubX, subTickPositionsY.at( 0 ) )
188                                              << QPointF( thisSubX, subTickPositionsY.at( 1 ) ), nullptr, context, layer );
189         }
190       }
191     }
192 
193     //draw last tick and horizontal line
194     if ( !positions.isEmpty() )
195     {
196       const double lastTickPositionX = context.convertToPainterUnits( positions.at( positions.size() - 1 ) + scaleContext.segmentWidth, QgsUnitTypes::RenderMillimeters ) + xOffset;
197 
198       //last vertical line
199       if ( drawDivisionsForThisSymbolLayer )
200       {
201         divisionSymbol->renderPolyline( QPolygonF() << QPointF( lastTickPositionX, tickPositionsY.at( 0 ) )
202                                         << QPointF( lastTickPositionX, tickPositionsY.at( 1 ) ),
203                                         nullptr, context, layer );
204       }
205 
206       //horizontal line
207       if ( drawLineForThisSymbolLayer )
208       {
209         symbol->renderPolyline( QPolygonF() << QPointF( xOffset + context.convertToPainterUnits( positions.at( 0 ), QgsUnitTypes::RenderMillimeters ), verticalPos )
210                                 << QPointF( lastTickPositionX, verticalPos ), nullptr, context, layer );
211       }
212     }
213   }
214 
215   symbol->stopRender( context );
216   divisionSymbol->stopRender( context );
217   subdivisionSymbol->stopRender( context );
218 
219   painter->restore();
220 
221   //draw labels using the default method
222   drawDefaultLabels( context, settings, scaleContext );
223 }
224 
225 
226