[FEATURE][composer] Add styling support for composer shapes

This commit is contained in:
Nyall Dawson 2013-01-12 16:21:00 +11:00
parent 0c6d9ef90c
commit 860e23afc7
11 changed files with 330 additions and 43 deletions

View File

@ -39,18 +39,22 @@ class QgsComposerShape: QgsComposerItem
QgsComposerShape::Shape shapeType() const;
void setShapeType( QgsComposerShape::Shape s );
/**Sets this items bound in scene coordinates such that 1 item size units
corresponds to 1 scene size unit. Also, the shape is scaled*/
void setSceneRect( const QRectF& rectangle );
/**Sets radius for rounded rectangle corners. Added in v2.1 */
void setCornerRadius( double radius );
/**Returns the radius for rounded rectangle corners*/
double cornerRadius() const;
/**Sets the QgsFillSymbolV2 used to draw the shape. Must also call setUseSymbolV2( true ) to
* enable drawing with a symbol.
* Note: added in version 2.1*/
void setShapeStyleSymbol( QgsFillSymbolV2* symbol );
/**Returns the QgsFillSymbolV2 used to draw the shape.
* Note: added in version 2.1*/
QgsFillSymbolV2* shapeStyleSymbol();
public slots:
/**Sets item rotation and resizes item bounds such that the shape always has the same size*/
virtual void setItemRotation( double r );
/**Controls whether the shape should be drawn using a QgsFillSymbolV2.
* Note: Added in v2.1 */
void setUseSymbolV2( bool useSymbolV2 );
protected:
/* reimplement drawFrame, since it's not a rect, but a custom shape */

View File

@ -18,6 +18,10 @@
#include "qgscomposershapewidget.h"
#include "qgscomposershape.h"
#include "qgscomposeritemwidget.h"
#include "qgscomposition.h"
#include "qgsstylev2.h"
#include "qgssymbolv2selectordialog.h"
#include "qgssymbollayerv2utils.h"
#include <QColorDialog>
QgsComposerShapeWidget::QgsComposerShapeWidget( QgsComposerShape* composerShape ): QWidget( 0 ), mComposerShape( composerShape )
@ -54,6 +58,7 @@ void QgsComposerShapeWidget::blockAllSignals( bool block )
{
mShapeComboBox->blockSignals( block );
mCornerRadiusSpinBox->blockSignals( block );
mShapeStyleButton->blockSignals( block );
}
void QgsComposerShapeWidget::setGuiElementValues()
@ -65,6 +70,8 @@ void QgsComposerShapeWidget::setGuiElementValues()
blockAllSignals( true );
updateShapeStyle();
mCornerRadiusSpinBox->setValue( mComposerShape->cornerRadius() );
if ( mComposerShape->shapeType() == QgsComposerShape::Ellipse )
{
@ -85,6 +92,37 @@ void QgsComposerShapeWidget::setGuiElementValues()
blockAllSignals( false );
}
void QgsComposerShapeWidget::on_mShapeStyleButton_clicked()
{
if ( !mComposerShape )
{
return;
}
QgsVectorLayer* coverageLayer = 0;
// use the atlas coverage layer, if any
if ( mComposerShape->composition()->atlasComposition().enabled() )
{
coverageLayer = mComposerShape->composition()->atlasComposition().coverageLayer();
}
QgsSymbolV2SelectorDialog d( mComposerShape->shapeStyleSymbol(), QgsStyleV2::defaultStyle(), coverageLayer );
if ( d.exec() == QDialog::Accepted )
{
updateShapeStyle();
}
}
void QgsComposerShapeWidget::updateShapeStyle()
{
if ( mComposerShape )
{
QIcon icon = QgsSymbolLayerV2Utils::symbolPreviewIcon( mComposerShape->shapeStyleSymbol(), mShapeStyleButton->iconSize() );
mShapeStyleButton->setIcon( icon );
}
}
void QgsComposerShapeWidget::on_mCornerRadiusSpinBox_valueChanged( double val )
{
if ( mComposerShape )

View File

@ -39,10 +39,13 @@ class QgsComposerShapeWidget: public QWidget, private Ui::QgsComposerShapeWidget
private slots:
void on_mShapeComboBox_currentIndexChanged( const QString& text );
void on_mCornerRadiusSpinBox_valueChanged( double val );
void on_mShapeStyleButton_clicked();
/**Sets the GUI elements to the currentValues of mComposerShape*/
void setGuiElementValues();
void updateShapeStyle();
/**Enables or disables the rounded radius spin box based on shape type*/
void toggleRadiusSpin( const QString& shapeText );
};

View File

@ -24,6 +24,7 @@
#include "qgsexpression.h"
#include "qgsgeometry.h"
#include "qgscomposerlabel.h"
#include "qgscomposershape.h"
#include "qgspaperitem.h"
#include "qgsmaplayerregistry.h"
@ -375,6 +376,14 @@ void QgsAtlasComposition::prepareForFeature( int featureI )
( *lit )->setExpressionContext( &mCurrentFeature, mCoverageLayer );
}
// update shapes (in case they use data defined symbology with atlas properties)
QList<QgsComposerShape*> shapes;
mComposition->composerItems( shapes );
for ( QList<QgsComposerShape*>::iterator lit = shapes.begin(); lit != shapes.end(); ++lit )
{
( *lit )->update();
}
// update page background (in case it uses data defined symbology with atlas properties)
QList<QgsPaperItem*> pages;
mComposition->composerItems( pages );

View File

@ -16,24 +16,61 @@
***************************************************************************/
#include "qgscomposershape.h"
#include "qgscomposition.h"
#include "qgssymbolv2.h"
#include "qgssymbollayerv2utils.h"
#include <QPainter>
QgsComposerShape::QgsComposerShape( QgsComposition* composition ): QgsComposerItem( composition ), mShape( Ellipse ), mCornerRadius( 0 )
QgsComposerShape::QgsComposerShape( QgsComposition* composition ): QgsComposerItem( composition ),
mShape( Ellipse ),
mCornerRadius( 0 ),
mUseSymbolV2( false ), //default to not using SymbolV2 for shapes, to preserve 2.0 api
mShapeStyleSymbol( 0 )
{
setFrameEnabled( true );
createDefaultShapeStyleSymbol();
}
QgsComposerShape::QgsComposerShape( qreal x, qreal y, qreal width, qreal height, QgsComposition* composition ): QgsComposerItem( x, y, width, height, composition ),
QgsComposerShape::QgsComposerShape( qreal x, qreal y, qreal width, qreal height, QgsComposition* composition ):
QgsComposerItem( x, y, width, height, composition ),
mShape( Ellipse ),
mCornerRadius( 0 )
mCornerRadius( 0 ),
mUseSymbolV2( false ), //default to not using SymbolV2 for shapes, to preserve 2.0 api
mShapeStyleSymbol( 0 )
{
setSceneRect( QRectF( x, y, width, height ) );
setFrameEnabled( true );
createDefaultShapeStyleSymbol();
}
QgsComposerShape::~QgsComposerShape()
{
delete mShapeStyleSymbol;
}
void QgsComposerShape::setUseSymbolV2( bool useSymbolV2 )
{
mUseSymbolV2 = useSymbolV2;
setFrameEnabled( !useSymbolV2 );
}
void QgsComposerShape::setShapeStyleSymbol( QgsFillSymbolV2* symbol )
{
delete mShapeStyleSymbol;
mShapeStyleSymbol = symbol;
update();
}
void QgsComposerShape::createDefaultShapeStyleSymbol()
{
delete mShapeStyleSymbol;
QgsStringMap properties;
properties.insert( "color", "white" );
properties.insert( "style", "solid" );
properties.insert( "style_border", "solid" );
properties.insert( "color_border", "black" );
properties.insert( "width_border", "0.3" );
mShapeStyleSymbol = QgsFillSymbolV2::createSimple( properties );
}
void QgsComposerShape::paint( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle, QWidget* pWidget )
@ -56,7 +93,13 @@ void QgsComposerShape::paint( QPainter* painter, const QStyleOptionGraphicsItem*
void QgsComposerShape::drawShape( QPainter* p )
{
if ( mUseSymbolV2 )
{
drawShapeUsingSymbol( p );
return;
}
//draw using QPainter brush and pen to keep 2.0 api compatibility
p->save();
p->setRenderHint( QPainter::Antialiasing );
@ -85,13 +128,101 @@ void QgsComposerShape::drawShape( QPainter* p )
break;
}
p->restore();
}
void QgsComposerShape::drawShapeUsingSymbol( QPainter* p )
{
p->save();
p->setRenderHint( QPainter::Antialiasing );
QgsRenderContext context;
context.setPainter( p );
context.setScaleFactor( 1.0 );
if ( mComposition->plotStyle() == QgsComposition::Preview )
{
context.setRasterScaleFactor( horizontalViewScaleFactor() );
}
else
{
context.setRasterScaleFactor( mComposition->printResolution() / 25.4 );
}
//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(), rect().height() ) );
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(), rect().height() ), mCornerRadius, mCornerRadius );
QPolygonF roundedPoly = roundedRectPath.toFillPolygon( t );
shapePolygon = ti.map( roundedPoly );
}
else
{
shapePolygon = QPolygonF( QRectF( 0, 0, rect().width(), rect().height() ) );
}
break;
}
case Triangle:
{
shapePolygon << QPointF( 0, rect().height() );
shapePolygon << QPointF( rect().width() , rect().height() );
shapePolygon << QPointF( rect().width() / 2.0, 0 );
shapePolygon << QPointF( 0, rect().height() );
break;
}
}
mShapeStyleSymbol->startRender( context );
double maxBleed = QgsSymbolLayerV2Utils::estimateMaxSymbolBleed( mShapeStyleSymbol );
//even though we aren't going to use it to draw the shape, set the pen width as 2 * symbol bleed
//so that the item is fully rendered within it's scene rect
//(QGraphicsRectItem considers the pen width when calculating an item's scene rect)
setPen( QPen( QBrush( Qt::NoBrush ), maxBleed * 2.0 ) );
//need to render using atlas feature properties?
if ( mComposition->atlasComposition().enabled() && mComposition->atlasMode() != QgsComposition::AtlasOff )
{
//using an atlas, so render using current atlas feature
//since there may be data defined symbols using atlas feature properties
mShapeStyleSymbol->renderPolygon( shapePolygon, &rings, mComposition->atlasComposition().currentFeature(), context );
}
else
{
mShapeStyleSymbol->renderPolygon( shapePolygon, &rings, 0, context );
}
mShapeStyleSymbol->stopRender( context );
p->restore();
}
void QgsComposerShape::drawFrame( QPainter* p )
{
if ( mFrame && p )
if ( mFrame && p && !mUseSymbolV2 )
{
p->setPen( pen() );
p->setBrush( Qt::NoBrush );
@ -102,7 +233,7 @@ void QgsComposerShape::drawFrame( QPainter* p )
void QgsComposerShape::drawBackground( QPainter* p )
{
if ( mBackground && p )
if ( p && ( mBackground || mUseSymbolV2 ) )
{
p->setBrush( brush() );//this causes a problem in atlas generation
p->setPen( Qt::NoPen );
@ -117,6 +248,10 @@ bool QgsComposerShape::writeXML( QDomElement& elem, QDomDocument & doc ) const
QDomElement composerShapeElem = doc.createElement( "ComposerShape" );
composerShapeElem.setAttribute( "shapeType", mShape );
composerShapeElem.setAttribute( "cornerRadius", mCornerRadius );
QDomElement shapeStyleElem = QgsSymbolLayerV2Utils::saveSymbol( QString(), mShapeStyleSymbol, doc );
composerShapeElem.appendChild( shapeStyleElem );
elem.appendChild( composerShapeElem );
return _writeXML( composerShapeElem, doc );
}
@ -141,6 +276,39 @@ bool QgsComposerShape::readXML( const QDomElement& itemElem, const QDomDocument&
_readXML( composerItemElem, doc );
}
QDomElement shapeStyleSymbolElem = itemElem.firstChildElement( "symbol" );
if ( !shapeStyleSymbolElem.isNull() )
{
delete mShapeStyleSymbol;
mShapeStyleSymbol = dynamic_cast<QgsFillSymbolV2*>( QgsSymbolLayerV2Utils::loadSymbol( shapeStyleSymbolElem ) );
}
else
{
//upgrade project file from 2.0 to use symbolV2 styling
delete mShapeStyleSymbol;
QgsStringMap properties;
properties.insert( "color", QgsSymbolLayerV2Utils::encodeColor( brush().color() ) );
if ( hasBackground() )
{
properties.insert( "style", "solid" );
}
else
{
properties.insert( "style", "no" );
}
if ( hasFrame() )
{
properties.insert( "style_border", "solid" );
}
else
{
properties.insert( "style_border", "no" );
}
properties.insert( "color_border", QgsSymbolLayerV2Utils::encodeColor( pen().color() ) );
properties.insert( "width_border", QString::number( pen().widthF() ) );
mShapeStyleSymbol = QgsFillSymbolV2::createSimple( properties );
}
emit itemChanged();
return true;
}

View File

@ -22,6 +22,8 @@
#include <QBrush>
#include <QPen>
class QgsFillSymbolV2;
/**A composer items that draws common shapes (ellipse, triangle, rectangle)*/
class CORE_EXPORT QgsComposerShape: public QgsComposerItem
{
@ -66,22 +68,42 @@ class CORE_EXPORT QgsComposerShape: public QgsComposerItem
/**Returns the radius for rounded rectangle corners*/
double cornerRadius() const { return mCornerRadius; };
/**Sets the QgsFillSymbolV2 used to draw the shape. Must also call setUseSymbolV2( true ) to
* enable drawing with a symbol.
* Note: added in version 2.1*/
void setShapeStyleSymbol( QgsFillSymbolV2* symbol );
/**Returns the QgsFillSymbolV2 used to draw the shape.
* Note: added in version 2.1*/
QgsFillSymbolV2* shapeStyleSymbol() { return mShapeStyleSymbol; }
/**Controls whether the shape should be drawn using a QgsFillSymbolV2.
* Note: Added in v2.1 */
void setUseSymbolV2( bool useSymbolV2 );
protected:
/* reimplement drawFrame, since it's not a rect, but a custom shape */
virtual void drawFrame( QPainter* p );
/* reimplement drawBackground, since it's not a rect, but a custom shape */
virtual void drawBackground( QPainter* p );
private:
/**Ellipse, rectangle or triangle*/
Shape mShape;
double mCornerRadius;
bool mUseSymbolV2;
QgsFillSymbolV2* mShapeStyleSymbol;
/* draws the custom shape */
void drawShape( QPainter* p );
/* draws the custom shape using symbol v2*/
void drawShapeUsingSymbol( QPainter* p );
/* creates the default shape symbol */
void createDefaultShapeStyleSymbol();
/**Returns a point on the line from startPoint to directionPoint that is a certain distance away from the starting point*/
QPointF pointOnLineWithDistance( const QPointF& startPoint, const QPointF& directionPoint, double distance ) const;

View File

@ -868,6 +868,8 @@ void QgsComposition::addItemsFromXML( const QDomElement& elem, const QDomDocumen
QDomElement currentComposerShapeElem = composerShapeList.at( i ).toElement();
QgsComposerShape* newShape = new QgsComposerShape( this );
newShape->readXML( currentComposerShapeElem, doc );
//new shapes should default to symbol v2
newShape->setUseSymbolV2( true );
if ( pos )
{
if ( pasteInPlace )

View File

@ -399,6 +399,8 @@ void QgsComposerView::addShape( Tool currentTool )
{
QgsComposerShape* composerShape = new QgsComposerShape( mRubberBandItem->transform().dx(), mRubberBandItem->transform().dy(), mRubberBandItem->rect().width(), mRubberBandItem->rect().height(), composition() );
composerShape->setShapeType( shape );
//new shapes use symbol v2 by default
composerShape->setUseSymbolV2( true );
composition()->addComposerShape( composerShape );
removeRubberBand();
emit actionFinished();

View File

@ -17,7 +17,16 @@
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
@ -63,35 +72,39 @@
<bool>false</bool>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<property name="labelAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<item row="2" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Corner radius</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="mCornerRadiusSpinBox">
<property name="suffix">
<string> mm</string>
</property>
<property name="maximum">
<double>999.000000000000000</double>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QComboBox" name="mShapeComboBox"/>
</item>
</layout>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Corner radius</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="mCornerRadiusSpinBox">
<property name="suffix">
<string> mm</string>
</property>
<property name="maximum">
<double>999.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Style</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="mShapeStyleButton">
<property name="text">
<string>Change...</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QComboBox" name="mShapeComboBox"/>
</item>
</layout>
</widget>

View File

@ -19,6 +19,9 @@
#include "qgscomposition.h"
#include "qgscompositionchecker.h"
#include "qgscomposershape.h"
#include "qgssymbolv2.h"
#include "qgssinglesymbolrendererv2.h"
#include "qgsfillsymbollayerv2.h"
#include <QObject>
#include <QtTest>
#include <QColor>
@ -36,10 +39,13 @@ class TestQgsComposerShapes: public QObject
void triangle(); //test if triange shape is functioning
void ellipse(); //test if ellipse shape is functioning
void roundedRectangle(); //test if rounded rectangle shape is functioning
void symbolV2(); //test is styling shapes via symbolv2 is working
private:
QgsComposition* mComposition;
QgsComposerShape* mComposerShape;
QgsSimpleFillSymbolLayerV2* mSimpleFill;
QgsFillSymbolV2* mFillSymbol;
QString mReport;
};
@ -55,6 +61,11 @@ void TestQgsComposerShapes::initTestCase()
mComposerShape->setBackgroundColor( QColor::fromRgb( 255, 150, 0 ) );
mComposition->addComposerShape( mComposerShape );
//setup simple fill
mSimpleFill = new QgsSimpleFillSymbolLayerV2();
mFillSymbol = new QgsFillSymbolV2();
mFillSymbol->changeSymbolLayer( 0, mSimpleFill );
mReport = "<h1>Composer Shape Tests</h1>\n";
}
@ -116,5 +127,20 @@ void TestQgsComposerShapes::roundedRectangle()
mComposerShape->setCornerRadius( 0 );
}
void TestQgsComposerShapes::symbolV2()
{
mComposerShape->setShapeType( QgsComposerShape::Rectangle );
mSimpleFill->setColor( Qt::green );
mSimpleFill->setBorderColor( Qt::yellow );
mSimpleFill->setBorderWidth( 6 );
mComposerShape->setShapeStyleSymbol( mFillSymbol );
mComposerShape->setUseSymbolV2( true );
QgsCompositionChecker checker( "composershapes_symbolv2", mComposition );
QVERIFY( checker.testComposition( mReport ) );
}
QTEST_MAIN( TestQgsComposerShapes )
#include "moc_testqgscomposershapes.cxx"

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB