Skip to content

Commit

Permalink
[FEATURE][layouts] Add "hollow" scalebar style
Browse files Browse the repository at this point in the history
This matches the "hollow" scalebar style from ArcMap, and is a style
used in South African mapping standards

Fixes #20390

Sponsored by SLYR
  • Loading branch information
nyalldawson committed Mar 22, 2020
1 parent e7cc830 commit 1887846
Show file tree
Hide file tree
Showing 16 changed files with 461 additions and 3 deletions.
Expand Up @@ -615,6 +615,15 @@ Sets the cap ``style`` used when drawing the lines in the scalebar.
Applies the default scalebar settings to the scale bar.

.. seealso:: :py:func:`applyDefaultSize`
%End

bool applyDefaultRendererSettings( QgsScaleBarRenderer *renderer );
%Docstring
Applies any default settings relating to the specified ``renderer`` to the item.

Returns ``True`` if settings were applied.

.. versionadded:: 3.14
%End

QgsUnitTypes::DistanceUnit guessUnits() const;
Expand Down
@@ -0,0 +1,53 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/scalebar/qgshollowscalebarrenderer.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/



class QgsHollowScaleBarRenderer: QgsScaleBarRenderer
{
%Docstring
Scalebar style that draws a single box with alternating color for the segments, with horizontal lines through
alternating segments. AKA "South African" style.

.. versionadded:: 3.14
%End

%TypeHeaderCode
#include "qgshollowscalebarrenderer.h"
%End
public:

QgsHollowScaleBarRenderer();
%Docstring
Constructor for QgsHollowScaleBarRenderer.
%End

virtual QString id() const;

virtual QString visibleName() const;

virtual int sortKey() const;

virtual QgsHollowScaleBarRenderer *clone() const /Factory/;


virtual void draw( QgsRenderContext &context,
const QgsScaleBarSettings &settings,
const QgsScaleBarRenderer::ScaleBarContext &scaleContext ) const;
virtual bool applyDefaultSettings( QgsScaleBarSettings &settings ) const;


};

/************************************************************************
* This file has been generated automatically from *
* *
* src/core/scalebar/qgshollowscalebarrenderer.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
Expand Up @@ -98,6 +98,15 @@ Calculates the required box size (in millimeters) for a scalebar using the speci
%Docstring
Calculates the required box size (in millimeters) for a scalebar using the specified ``settings`` and ``scaleContext``.

.. versionadded:: 3.14
%End

virtual bool applyDefaultSettings( QgsScaleBarSettings &settings ) const;
%Docstring
Applies any default settings relating to the scalebar to the passed ``settings`` object.

Returns ``True`` if settings were applied.

.. versionadded:: 3.14
%End

Expand Down
1 change: 1 addition & 0 deletions python/core/core_auto.sip
Expand Up @@ -486,6 +486,7 @@
%Include auto_generated/raster/qgssinglebandgrayrenderer.sip
%Include auto_generated/raster/qgssinglebandpseudocolorrenderer.sip
%Include auto_generated/scalebar/qgsdoubleboxscalebarrenderer.sip
%Include auto_generated/scalebar/qgshollowscalebarrenderer.sip
%Include auto_generated/scalebar/qgsnumericscalebarrenderer.sip
%Include auto_generated/scalebar/qgsscalebarrenderer.sip
%Include auto_generated/scalebar/qgsscalebarrendererregistry.sip
Expand Down
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -181,6 +181,7 @@ SET(QGIS_CORE_SRCS
providers/ogr/qgsogrtransaction.cpp

scalebar/qgsdoubleboxscalebarrenderer.cpp
scalebar/qgshollowscalebarrenderer.cpp
scalebar/qgsnumericscalebarrenderer.cpp
scalebar/qgsscalebarrenderer.cpp
scalebar/qgsscalebarrendererregistry.cpp
Expand Down Expand Up @@ -1272,6 +1273,7 @@ SET(QGIS_CORE_HDRS
raster/qgssinglebandpseudocolorrenderer.h

scalebar/qgsdoubleboxscalebarrenderer.h
scalebar/qgshollowscalebarrenderer.h
scalebar/qgsnumericscalebarrenderer.h
scalebar/qgsscalebarrenderer.h
scalebar/qgsscalebarrendererregistry.h
Expand Down
5 changes: 5 additions & 0 deletions src/core/layout/qgslayoutitemscalebar.cpp
Expand Up @@ -469,6 +469,11 @@ void QgsLayoutItemScaleBar::applyDefaultSettings()
emit changed();
}

bool QgsLayoutItemScaleBar::applyDefaultRendererSettings( QgsScaleBarRenderer *renderer )
{
return renderer->applyDefaultSettings( mSettings );
}

QgsUnitTypes::DistanceUnit QgsLayoutItemScaleBar::guessUnits() const
{
if ( !mMap )
Expand Down
9 changes: 9 additions & 0 deletions src/core/layout/qgslayoutitemscalebar.h
Expand Up @@ -498,6 +498,15 @@ class CORE_EXPORT QgsLayoutItemScaleBar: public QgsLayoutItem
*/
void applyDefaultSettings();

/**
* Applies any default settings relating to the specified \a renderer to the item.
*
* Returns TRUE if settings were applied.
*
* \since QGIS 3.14
*/
bool applyDefaultRendererSettings( QgsScaleBarRenderer *renderer );

/**
* Attempts to guess the most reasonable unit choice for the scalebar, given
* the current linked map's scale.
Expand Down
183 changes: 183 additions & 0 deletions src/core/scalebar/qgshollowscalebarrenderer.cpp
@@ -0,0 +1,183 @@
/***************************************************************************
qgshollowscalebarrenderer.cpp
--------------------------------
begin : March 2020
copyright : (C) 2020 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgshollowscalebarrenderer.h"
#include "qgsscalebarsettings.h"
#include "qgslayoututils.h"
#include "qgssymbol.h"
#include "qgsfillsymbollayer.h"
#include <QList>
#include <QPainter>

QString QgsHollowScaleBarRenderer::id() const
{
return QStringLiteral( "hollow" );
}

QString QgsHollowScaleBarRenderer::visibleName() const
{
return QObject::tr( "Hollow" );
}

int QgsHollowScaleBarRenderer::sortKey() const
{
return 8;
}

QgsHollowScaleBarRenderer *QgsHollowScaleBarRenderer::clone() const
{
return new QgsHollowScaleBarRenderer( *this );
}

void QgsHollowScaleBarRenderer::draw( QgsRenderContext &context, const QgsScaleBarSettings &settings, const ScaleBarContext &scaleContext ) const
{
if ( !context.painter() )
{
return;
}
QPainter *painter = context.painter();

const double scaledLabelBarSpace = context.convertToPainterUnits( settings.labelBarSpace(), QgsUnitTypes::RenderMillimeters );
const double scaledBoxContentSpace = context.convertToPainterUnits( settings.boxContentSpace(), QgsUnitTypes::RenderMillimeters );
const QFontMetricsF fontMetrics = QgsTextRenderer::fontMetrics( context, settings.textFormat() );
const double barTopPosition = scaledBoxContentSpace + ( settings.labelVerticalPlacement() == QgsScaleBarSettings::LabelAboveSegment ? fontMetrics.ascent() + scaledLabelBarSpace : 0 );
const double barHeight = context.convertToPainterUnits( settings.height(), QgsUnitTypes::RenderMillimeters );

painter->save();
if ( context.flags() & QgsRenderContext::Antialiasing )
painter->setRenderHint( QPainter::Antialiasing, true );

std::unique_ptr< QgsLineSymbol > lineSymbol( settings.lineSymbol()->clone() );
lineSymbol->startRender( context );

std::unique_ptr< QgsFillSymbol > fillSymbol1( settings.fillSymbol1()->clone() );
fillSymbol1->startRender( context );

std::unique_ptr< QgsFillSymbol > fillSymbol2( settings.fillSymbol2()->clone() );
fillSymbol2->startRender( context );

painter->setPen( Qt::NoPen );
painter->setBrush( Qt::NoBrush );

bool useColor = true; //alternate brush color/white
const double xOffset = firstLabelXOffset( settings, context, scaleContext );

const QList<double> positions = segmentPositions( context, scaleContext, settings );
const QList<double> widths = segmentWidths( scaleContext, settings );

// draw the fill
double minX = 0;
double maxX = 0;
QgsFillSymbol *currentSymbol = nullptr;
for ( int i = 0; i < positions.size(); ++i )
{
if ( useColor ) //alternating colors
{
currentSymbol = fillSymbol1.get();
}
else //secondary fill
{
currentSymbol = fillSymbol2.get();
}

const double thisX = context.convertToPainterUnits( positions.at( i ), QgsUnitTypes::RenderMillimeters ) + xOffset;
const double thisWidth = context.convertToPainterUnits( widths.at( i ), QgsUnitTypes::RenderMillimeters );

if ( i == 0 )
minX = thisX;
if ( i == positions.size() - 1 )
maxX = thisX + thisWidth;

QRectF segmentRect( thisX, barTopPosition, thisWidth, barHeight );
currentSymbol->renderPolygon( QPolygonF()
<< segmentRect.topLeft()
<< segmentRect.topRight()
<< segmentRect.bottomRight()
<< segmentRect.bottomLeft()
<< segmentRect.topLeft(), nullptr, nullptr, context );
useColor = !useColor;
}

// and then the lines
// note that we do this layer-by-layer, to avoid ugliness where the lines touch the outer rect
for ( int layer = 0; layer < lineSymbol->symbolLayerCount(); ++layer )
{
// horizontal lines
bool drawLine = false;
for ( int i = 0; i < positions.size(); ++i )
{
drawLine = !drawLine;
if ( !drawLine )
continue;

const double lineX = context.convertToPainterUnits( positions.at( i ), QgsUnitTypes::RenderMillimeters ) + xOffset;
const double lineLength = context.convertToPainterUnits( widths.at( i ), QgsUnitTypes::RenderMillimeters );
lineSymbol->renderPolyline( QPolygonF()
<< QPointF( lineX, barTopPosition + barHeight / 2.0 )
<< QPointF( lineX + lineLength, barTopPosition + barHeight / 2.0 ),
nullptr, context, layer );
}

// vertical lines
for ( int i = 1; i < positions.size(); ++i )
{
const double lineX = context.convertToPainterUnits( positions.at( i ), QgsUnitTypes::RenderMillimeters ) + xOffset;
lineSymbol->renderPolyline( QPolygonF()
<< QPointF( lineX, barTopPosition )
<< QPointF( lineX, barTopPosition + barHeight ),
nullptr, context, layer );
}

// outside line
lineSymbol->renderPolyline( QPolygonF()
<< QPointF( minX, barTopPosition )
<< QPointF( maxX, barTopPosition )
<< QPointF( maxX, barTopPosition + barHeight )
<< QPointF( minX, barTopPosition + barHeight )
<< QPointF( minX, barTopPosition ),
nullptr, context, layer );
}

lineSymbol->stopRender( context );
fillSymbol1->stopRender( context );
fillSymbol2->stopRender( context );
painter->restore();

//draw labels using the default method
drawDefaultLabels( context, settings, scaleContext );
}

bool QgsHollowScaleBarRenderer::applyDefaultSettings( QgsScaleBarSettings &settings ) const
{
// null the fill symbols by default
std::unique_ptr< QgsFillSymbol > fillSymbol = qgis::make_unique< QgsFillSymbol >();
std::unique_ptr< QgsSimpleFillSymbolLayer > fillSymbolLayer = qgis::make_unique< QgsSimpleFillSymbolLayer >();
fillSymbolLayer->setColor( QColor( 0, 0, 0 ) );
fillSymbolLayer->setBrushStyle( Qt::NoBrush );
fillSymbolLayer->setStrokeStyle( Qt::NoPen );
fillSymbol->changeSymbolLayer( 0, fillSymbolLayer->clone() );
settings.setFillSymbol1( fillSymbol.release() );

fillSymbol = qgis::make_unique< QgsFillSymbol >();
fillSymbolLayer->setColor( QColor( 255, 255, 255 ) );
fillSymbol->changeSymbolLayer( 0, fillSymbolLayer.release() );
settings.setFillSymbol2( fillSymbol.release() );

return true;
}



52 changes: 52 additions & 0 deletions src/core/scalebar/qgshollowscalebarrenderer.h
@@ -0,0 +1,52 @@
/***************************************************************************
qgshollowscalebarrenderer.h
------------------------------
begin : March 2020
copyright : (C) 2020 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSHOLLOWSCALEBARRENDERER_H
#define QGSHOLLOWSCALEBARRENDERER_H

#include "qgis_core.h"
#include "qgsscalebarrenderer.h"
#include <QString>

/**
* \class QgsHollowScaleBarRenderer
* \ingroup core
* Scalebar style that draws a single box with alternating color for the segments, with horizontal lines through
* alternating segments. AKA "South African" style.
* \since QGIS 3.14
*/
class CORE_EXPORT QgsHollowScaleBarRenderer: public QgsScaleBarRenderer
{
public:

/**
* Constructor for QgsHollowScaleBarRenderer.
*/
QgsHollowScaleBarRenderer() = default;

QString id() const override;
QString visibleName() const override;
int sortKey() const override;
QgsHollowScaleBarRenderer *clone() const override SIP_FACTORY;

void draw( QgsRenderContext &context,
const QgsScaleBarSettings &settings,
const QgsScaleBarRenderer::ScaleBarContext &scaleContext ) const override;
bool applyDefaultSettings( QgsScaleBarSettings &settings ) const override;

};

#endif // QGSHOLLOWSCALEBARRENDERER_H
2 changes: 1 addition & 1 deletion src/core/scalebar/qgsnumericscalebarrenderer.cpp
Expand Up @@ -33,7 +33,7 @@ QString QgsNumericScaleBarRenderer::visibleName() const

int QgsNumericScaleBarRenderer::sortKey() const
{
return 8;
return 9;
}

QgsNumericScaleBarRenderer *QgsNumericScaleBarRenderer::clone() const
Expand Down
5 changes: 5 additions & 0 deletions src/core/scalebar/qgsscalebarrenderer.cpp
Expand Up @@ -263,6 +263,11 @@ QSizeF QgsScaleBarRenderer::calculateBoxSize( QgsRenderContext &context, const Q
return QSizeF( width, height );
}

bool QgsScaleBarRenderer::applyDefaultSettings( QgsScaleBarSettings & ) const
{
return false;
}

QString QgsScaleBarRenderer::firstLabelString( const QgsScaleBarSettings &settings ) const
{
if ( settings.numberOfSegmentsLeft() > 0 )
Expand Down

0 comments on commit 1887846

Please sign in to comment.