mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-15 00:04:00 -04:00
459 lines
15 KiB
C++
459 lines
15 KiB
C++
/***************************************************************************
|
|
qgscomposershape.cpp
|
|
----------------------
|
|
begin : November 2009
|
|
copyright : (C) 2009 by Marco Hugentobler
|
|
email : marco@hugis.net
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* 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 "qgscomposershape.h"
|
|
#include "qgscomposition.h"
|
|
#include "qgssymbol.h"
|
|
#include "qgssymbollayerutils.h"
|
|
#include "qgscomposermodel.h"
|
|
#include "qgsmapsettings.h"
|
|
#include "qgscomposerutils.h"
|
|
#include <QPainter>
|
|
|
|
QgsComposerShape::QgsComposerShape( QgsComposition* composition )
|
|
: QgsComposerItem( composition )
|
|
, mShape( Ellipse )
|
|
, mCornerRadius( 0 )
|
|
, mUseSymbol( false ) //default to not using symbol for shapes, to preserve 2.0 api
|
|
, mShapeStyleSymbol( nullptr )
|
|
, mMaxSymbolBleed( 0 )
|
|
{
|
|
setFrameEnabled( true );
|
|
createDefaultShapeStyleSymbol();
|
|
|
|
if ( mComposition )
|
|
{
|
|
//connect to atlas feature changes
|
|
//to update symbol style (in case of data-defined symbology)
|
|
connect( &mComposition->atlasComposition(), SIGNAL( featureChanged( QgsFeature* ) ), this, SLOT( repaint() ) );
|
|
}
|
|
}
|
|
|
|
QgsComposerShape::QgsComposerShape( qreal x, qreal y, qreal width, qreal height, QgsComposition* composition )
|
|
: QgsComposerItem( x, y, width, height, composition )
|
|
, mShape( Ellipse )
|
|
, mCornerRadius( 0 )
|
|
, mUseSymbol( false ) //default to not using Symbol for shapes, to preserve 2.0 api
|
|
, mShapeStyleSymbol( nullptr )
|
|
, mMaxSymbolBleed( 0 )
|
|
{
|
|
setSceneRect( QRectF( x, y, width, height ) );
|
|
setFrameEnabled( true );
|
|
createDefaultShapeStyleSymbol();
|
|
|
|
if ( mComposition )
|
|
{
|
|
//connect to atlas feature changes
|
|
//to update symbol style (in case of data-defined symbology)
|
|
connect( &mComposition->atlasComposition(), SIGNAL( featureChanged( QgsFeature* ) ), this, SLOT( repaint() ) );
|
|
}
|
|
}
|
|
|
|
QgsComposerShape::~QgsComposerShape()
|
|
{
|
|
delete mShapeStyleSymbol;
|
|
}
|
|
|
|
void QgsComposerShape::setUseSymbol( bool useSymbol )
|
|
{
|
|
mUseSymbol = useSymbol;
|
|
setFrameEnabled( !useSymbol );
|
|
}
|
|
|
|
void QgsComposerShape::setShapeStyleSymbol( QgsFillSymbol* symbol )
|
|
{
|
|
delete mShapeStyleSymbol;
|
|
mShapeStyleSymbol = static_cast<QgsFillSymbol*>( symbol->clone() );
|
|
refreshSymbol();
|
|
}
|
|
|
|
void QgsComposerShape::refreshSymbol()
|
|
{
|
|
mMaxSymbolBleed = QgsSymbolLayerUtils::estimateMaxSymbolBleed( mShapeStyleSymbol, QgsComposerUtils::createRenderContextForComposition( mComposition, nullptr ) );
|
|
updateBoundingRect();
|
|
|
|
update();
|
|
emit frameChanged();
|
|
}
|
|
|
|
void QgsComposerShape::createDefaultShapeStyleSymbol()
|
|
{
|
|
delete mShapeStyleSymbol;
|
|
QgsStringMap properties;
|
|
properties.insert( QStringLiteral( "color" ), QStringLiteral( "white" ) );
|
|
properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
|
|
properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) );
|
|
properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "black" ) );
|
|
properties.insert( QStringLiteral( "width_border" ), QStringLiteral( "0.3" ) );
|
|
properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
|
|
mShapeStyleSymbol = QgsFillSymbol::createSimple( properties );
|
|
|
|
mMaxSymbolBleed = QgsSymbolLayerUtils::estimateMaxSymbolBleed( mShapeStyleSymbol, QgsComposerUtils::createRenderContextForComposition( mComposition, nullptr ) );
|
|
updateBoundingRect();
|
|
|
|
emit frameChanged();
|
|
}
|
|
|
|
void QgsComposerShape::paint( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle, QWidget* pWidget )
|
|
{
|
|
Q_UNUSED( itemStyle );
|
|
Q_UNUSED( pWidget );
|
|
if ( !painter )
|
|
{
|
|
return;
|
|
}
|
|
if ( !shouldDrawItem() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
drawBackground( painter );
|
|
drawFrame( painter );
|
|
|
|
if ( isSelected() )
|
|
{
|
|
drawSelectionBoxes( painter );
|
|
}
|
|
}
|
|
|
|
|
|
void QgsComposerShape::drawShape( QPainter* p )
|
|
{
|
|
if ( mUseSymbol )
|
|
{
|
|
drawShapeUsingSymbol( p );
|
|
return;
|
|
}
|
|
|
|
//draw using QPainter brush and pen to keep 2.0 api compatibility
|
|
p->save();
|
|
p->setRenderHint( QPainter::Antialiasing );
|
|
|
|
switch ( mShape )
|
|
{
|
|
case Ellipse:
|
|
p->drawEllipse( QRectF( 0, 0, rect().width(), rect().height() ) );
|
|
break;
|
|
case Rectangle:
|
|
//if corner radius set, then draw a rounded rectangle
|
|
if ( mCornerRadius > 0 )
|
|
{
|
|
p->drawRoundedRect( QRectF( 0, 0, rect().width(), rect().height() ), mCornerRadius, mCornerRadius );
|
|
}
|
|
else
|
|
{
|
|
p->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
|
|
}
|
|
break;
|
|
case Triangle:
|
|
QPolygonF triangle;
|
|
triangle << QPointF( 0, rect().height() );
|
|
triangle << QPointF( rect().width(), rect().height() );
|
|
triangle << QPointF( rect().width() / 2.0, 0 );
|
|
p->drawPolygon( triangle );
|
|
break;
|
|
}
|
|
p->restore();
|
|
}
|
|
|
|
void QgsComposerShape::drawShapeUsingSymbol( QPainter* p )
|
|
{
|
|
p->save();
|
|
p->setRenderHint( QPainter::Antialiasing );
|
|
|
|
//setup painter scaling to dots so that raster symbology is drawn to scale
|
|
double dotsPerMM = p->device()->logicalDpiX() / 25.4;
|
|
|
|
//setup render context
|
|
QgsRenderContext context = QgsComposerUtils::createRenderContextForComposition( mComposition, p );
|
|
context.setForceVectorOutput( true );
|
|
QgsExpressionContext expressionContext = createExpressionContext();
|
|
context.setExpressionContext( expressionContext );
|
|
|
|
p->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
|
|
|
|
//generate polygon to draw
|
|
QList<QPolygonF> rings; //empty list
|
|
QPolygonF shapePolygon;
|
|
|
|
//shapes with curves must be enlarged before conversion to QPolygonF, or
|
|
//the curves are approximated too much and appear jaggy
|
|
QTransform t = QTransform::fromScale( 100, 100 );
|
|
//inverse transform used to scale created polygons back to expected size
|
|
QTransform ti = t.inverted();
|
|
|
|
switch ( mShape )
|
|
{
|
|
case Ellipse:
|
|
{
|
|
//create an ellipse
|
|
QPainterPath ellipsePath;
|
|
ellipsePath.addEllipse( QRectF( 0, 0, rect().width() * dotsPerMM, rect().height() * dotsPerMM ) );
|
|
QPolygonF ellipsePoly = ellipsePath.toFillPolygon( t );
|
|
shapePolygon = ti.map( ellipsePoly );
|
|
break;
|
|
}
|
|
case Rectangle:
|
|
{
|
|
//if corner radius set, then draw a rounded rectangle
|
|
if ( mCornerRadius > 0 )
|
|
{
|
|
QPainterPath roundedRectPath;
|
|
roundedRectPath.addRoundedRect( QRectF( 0, 0, rect().width() * dotsPerMM, rect().height() * dotsPerMM ), mCornerRadius * dotsPerMM, mCornerRadius * dotsPerMM );
|
|
QPolygonF roundedPoly = roundedRectPath.toFillPolygon( t );
|
|
shapePolygon = ti.map( roundedPoly );
|
|
}
|
|
else
|
|
{
|
|
shapePolygon = QPolygonF( QRectF( 0, 0, rect().width() * dotsPerMM, rect().height() * dotsPerMM ) );
|
|
}
|
|
break;
|
|
}
|
|
case Triangle:
|
|
{
|
|
shapePolygon << QPointF( 0, rect().height() * dotsPerMM );
|
|
shapePolygon << QPointF( rect().width() * dotsPerMM, rect().height() * dotsPerMM );
|
|
shapePolygon << QPointF( rect().width() / 2.0 * dotsPerMM, 0 );
|
|
shapePolygon << QPointF( 0, rect().height() * dotsPerMM );
|
|
break;
|
|
}
|
|
}
|
|
|
|
mShapeStyleSymbol->startRender( context );
|
|
mShapeStyleSymbol->renderPolygon( shapePolygon, &rings, nullptr, context );
|
|
mShapeStyleSymbol->stopRender( context );
|
|
|
|
p->restore();
|
|
}
|
|
|
|
|
|
void QgsComposerShape::drawFrame( QPainter* p )
|
|
{
|
|
if ( mFrame && p && !mUseSymbol )
|
|
{
|
|
p->setPen( pen() );
|
|
p->setBrush( Qt::NoBrush );
|
|
p->setRenderHint( QPainter::Antialiasing, true );
|
|
drawShape( p );
|
|
}
|
|
}
|
|
|
|
void QgsComposerShape::drawBackground( QPainter* p )
|
|
{
|
|
if ( p && ( mBackground || mUseSymbol ) )
|
|
{
|
|
p->setBrush( brush() );//this causes a problem in atlas generation
|
|
p->setPen( Qt::NoPen );
|
|
p->setRenderHint( QPainter::Antialiasing, true );
|
|
drawShape( p );
|
|
}
|
|
}
|
|
|
|
double QgsComposerShape::estimatedFrameBleed() const
|
|
{
|
|
return mMaxSymbolBleed;
|
|
}
|
|
|
|
bool QgsComposerShape::writeXml( QDomElement& elem, QDomDocument & doc ) const
|
|
{
|
|
QDomElement composerShapeElem = doc.createElement( QStringLiteral( "ComposerShape" ) );
|
|
composerShapeElem.setAttribute( QStringLiteral( "shapeType" ), mShape );
|
|
composerShapeElem.setAttribute( QStringLiteral( "cornerRadius" ), mCornerRadius );
|
|
|
|
QDomElement shapeStyleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mShapeStyleSymbol, doc );
|
|
composerShapeElem.appendChild( shapeStyleElem );
|
|
|
|
elem.appendChild( composerShapeElem );
|
|
return _writeXml( composerShapeElem, doc );
|
|
}
|
|
|
|
bool QgsComposerShape::readXml( const QDomElement& itemElem, const QDomDocument& doc )
|
|
{
|
|
mShape = QgsComposerShape::Shape( itemElem.attribute( QStringLiteral( "shapeType" ), QStringLiteral( "0" ) ).toInt() );
|
|
mCornerRadius = itemElem.attribute( QStringLiteral( "cornerRadius" ), QStringLiteral( "0" ) ).toDouble();
|
|
|
|
//restore general composer item properties
|
|
QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
|
|
if ( !composerItemList.isEmpty() )
|
|
{
|
|
QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
|
|
|
|
//rotation
|
|
if ( !qgsDoubleNear( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
|
|
{
|
|
//check for old (pre 2.1) rotation attribute
|
|
setItemRotation( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble() );
|
|
}
|
|
|
|
_readXml( composerItemElem, doc );
|
|
}
|
|
|
|
QDomElement shapeStyleSymbolElem = itemElem.firstChildElement( QStringLiteral( "symbol" ) );
|
|
if ( !shapeStyleSymbolElem.isNull() )
|
|
{
|
|
delete mShapeStyleSymbol;
|
|
mShapeStyleSymbol = QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( shapeStyleSymbolElem );
|
|
}
|
|
else
|
|
{
|
|
//upgrade project file from 2.0 to use symbol styling
|
|
delete mShapeStyleSymbol;
|
|
QgsStringMap properties;
|
|
properties.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( brush().color() ) );
|
|
if ( hasBackground() )
|
|
{
|
|
properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
|
|
}
|
|
else
|
|
{
|
|
properties.insert( QStringLiteral( "style" ), QStringLiteral( "no" ) );
|
|
}
|
|
if ( hasFrame() )
|
|
{
|
|
properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) );
|
|
}
|
|
else
|
|
{
|
|
properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "no" ) );
|
|
}
|
|
properties.insert( QStringLiteral( "color_border" ), QgsSymbolLayerUtils::encodeColor( pen().color() ) );
|
|
properties.insert( QStringLiteral( "width_border" ), QString::number( pen().widthF() ) );
|
|
|
|
//for pre 2.0 projects, shape color and outline were specified in a different element...
|
|
QDomNodeList outlineColorList = itemElem.elementsByTagName( QStringLiteral( "OutlineColor" ) );
|
|
if ( !outlineColorList.isEmpty() )
|
|
{
|
|
QDomElement frameColorElem = outlineColorList.at( 0 ).toElement();
|
|
bool redOk, greenOk, blueOk, alphaOk, widthOk;
|
|
int penRed, penGreen, penBlue, penAlpha;
|
|
double penWidth;
|
|
|
|
penWidth = itemElem.attribute( QStringLiteral( "outlineWidth" ) ).toDouble( &widthOk );
|
|
penRed = frameColorElem.attribute( QStringLiteral( "red" ) ).toDouble( &redOk );
|
|
penGreen = frameColorElem.attribute( QStringLiteral( "green" ) ).toDouble( &greenOk );
|
|
penBlue = frameColorElem.attribute( QStringLiteral( "blue" ) ).toDouble( &blueOk );
|
|
penAlpha = frameColorElem.attribute( QStringLiteral( "alpha" ) ).toDouble( &alphaOk );
|
|
|
|
if ( redOk && greenOk && blueOk && alphaOk && widthOk )
|
|
{
|
|
properties.insert( QStringLiteral( "color_border" ), QgsSymbolLayerUtils::encodeColor( QColor( penRed, penGreen, penBlue, penAlpha ) ) );
|
|
properties.insert( QStringLiteral( "width_border" ), QString::number( penWidth ) );
|
|
}
|
|
}
|
|
QDomNodeList fillColorList = itemElem.elementsByTagName( QStringLiteral( "FillColor" ) );
|
|
if ( !fillColorList.isEmpty() )
|
|
{
|
|
QDomElement fillColorElem = fillColorList.at( 0 ).toElement();
|
|
bool redOk, greenOk, blueOk, alphaOk;
|
|
int fillRed, fillGreen, fillBlue, fillAlpha;
|
|
|
|
fillRed = fillColorElem.attribute( QStringLiteral( "red" ) ).toDouble( &redOk );
|
|
fillGreen = fillColorElem.attribute( QStringLiteral( "green" ) ).toDouble( &greenOk );
|
|
fillBlue = fillColorElem.attribute( QStringLiteral( "blue" ) ).toDouble( &blueOk );
|
|
fillAlpha = fillColorElem.attribute( QStringLiteral( "alpha" ) ).toDouble( &alphaOk );
|
|
|
|
if ( redOk && greenOk && blueOk && alphaOk )
|
|
{
|
|
properties.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( QColor( fillRed, fillGreen, fillBlue, fillAlpha ) ) );
|
|
properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
|
|
}
|
|
}
|
|
if ( itemElem.hasAttribute( QStringLiteral( "transparentFill" ) ) )
|
|
{
|
|
//old style (pre 2.0) of specifying that shapes had no fill
|
|
bool hasOldTransparentFill = itemElem.attribute( QStringLiteral( "transparentFill" ), QStringLiteral( "0" ) ).toInt();
|
|
if ( hasOldTransparentFill )
|
|
{
|
|
properties.insert( QStringLiteral( "style" ), QStringLiteral( "no" ) );
|
|
}
|
|
}
|
|
|
|
mShapeStyleSymbol = QgsFillSymbol::createSimple( properties );
|
|
}
|
|
emit itemChanged();
|
|
return true;
|
|
}
|
|
|
|
void QgsComposerShape::setShapeType( QgsComposerShape::Shape s )
|
|
{
|
|
if ( s == mShape )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mShape = s;
|
|
|
|
if ( mComposition && id().isEmpty() )
|
|
{
|
|
//notify the model that the display name has changed
|
|
mComposition->itemsModel()->updateItemDisplayName( this );
|
|
}
|
|
}
|
|
|
|
void QgsComposerShape::setCornerRadius( double radius )
|
|
{
|
|
mCornerRadius = radius;
|
|
}
|
|
|
|
QRectF QgsComposerShape::boundingRect() const
|
|
{
|
|
return mCurrentRectangle;
|
|
}
|
|
|
|
void QgsComposerShape::updateBoundingRect()
|
|
{
|
|
QRectF rectangle = rect();
|
|
rectangle.adjust( -mMaxSymbolBleed, -mMaxSymbolBleed, mMaxSymbolBleed, mMaxSymbolBleed );
|
|
if ( rectangle != mCurrentRectangle )
|
|
{
|
|
prepareGeometryChange();
|
|
mCurrentRectangle = rectangle;
|
|
}
|
|
}
|
|
|
|
void QgsComposerShape::setSceneRect( const QRectF& rectangle )
|
|
{
|
|
// Reimplemented from QgsComposerItem as we need to call updateBoundingRect after the shape's size changes
|
|
|
|
//update rect for data defined size and position
|
|
QRectF evaluatedRect = evalItemRect( rectangle );
|
|
QgsComposerItem::setSceneRect( evaluatedRect );
|
|
|
|
updateBoundingRect();
|
|
update();
|
|
}
|
|
|
|
QString QgsComposerShape::displayName() const
|
|
{
|
|
if ( !id().isEmpty() )
|
|
{
|
|
return id();
|
|
}
|
|
|
|
switch ( mShape )
|
|
{
|
|
case Ellipse:
|
|
return tr( "<ellipse>" );
|
|
case Rectangle:
|
|
return tr( "<rectangle>" );
|
|
case Triangle:
|
|
return tr( "<triangle>" );
|
|
}
|
|
|
|
return tr( "<shape>" );
|
|
}
|