diff --git a/python/core/composer/qgscomposershape.sip b/python/core/composer/qgscomposershape.sip index 4318f56300b..f1853320196 100644 --- a/python/core/composer/qgscomposershape.sip +++ b/python/core/composer/qgscomposershape.sip @@ -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 */ diff --git a/src/app/composer/qgscomposershapewidget.cpp b/src/app/composer/qgscomposershapewidget.cpp index bfb4568c58e..bb195e07fa8 100644 --- a/src/app/composer/qgscomposershapewidget.cpp +++ b/src/app/composer/qgscomposershapewidget.cpp @@ -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 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 ) diff --git a/src/app/composer/qgscomposershapewidget.h b/src/app/composer/qgscomposershapewidget.h index 1d165f8d63f..5f57f6a143d 100644 --- a/src/app/composer/qgscomposershapewidget.h +++ b/src/app/composer/qgscomposershapewidget.h @@ -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 ); }; diff --git a/src/core/composer/qgsatlascomposition.cpp b/src/core/composer/qgsatlascomposition.cpp index 52b5e3e36e6..9ce2a69a919 100644 --- a/src/core/composer/qgsatlascomposition.cpp +++ b/src/core/composer/qgsatlascomposition.cpp @@ -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 shapes; + mComposition->composerItems( shapes ); + for ( QList::iterator lit = shapes.begin(); lit != shapes.end(); ++lit ) + { + ( *lit )->update(); + } + // update page background (in case it uses data defined symbology with atlas properties) QList pages; mComposition->composerItems( pages ); diff --git a/src/core/composer/qgscomposershape.cpp b/src/core/composer/qgscomposershape.cpp index 6a422a81567..de0a9d7b853 100644 --- a/src/core/composer/qgscomposershape.cpp +++ b/src/core/composer/qgscomposershape.cpp @@ -16,24 +16,61 @@ ***************************************************************************/ #include "qgscomposershape.h" +#include "qgscomposition.h" +#include "qgssymbolv2.h" +#include "qgssymbollayerv2utils.h" #include -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 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( 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; } diff --git a/src/core/composer/qgscomposershape.h b/src/core/composer/qgscomposershape.h index af6777bab80..6f3bd286cb0 100644 --- a/src/core/composer/qgscomposershape.h +++ b/src/core/composer/qgscomposershape.h @@ -22,6 +22,8 @@ #include #include +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; diff --git a/src/core/composer/qgscomposition.cpp b/src/core/composer/qgscomposition.cpp index 67d59804fa5..ff41594bfaf 100644 --- a/src/core/composer/qgscomposition.cpp +++ b/src/core/composer/qgscomposition.cpp @@ -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 ) diff --git a/src/gui/qgscomposerview.cpp b/src/gui/qgscomposerview.cpp index 72cfec49358..2b8eacd1839 100644 --- a/src/gui/qgscomposerview.cpp +++ b/src/gui/qgscomposerview.cpp @@ -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(); diff --git a/src/ui/qgscomposershapewidgetbase.ui b/src/ui/qgscomposershapewidgetbase.ui index e6df931f3f7..6abacc6e959 100644 --- a/src/ui/qgscomposershapewidgetbase.ui +++ b/src/ui/qgscomposershapewidgetbase.ui @@ -17,7 +17,16 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 @@ -63,35 +72,39 @@ false - - QFormLayout::AllNonFixedFieldsGrow - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Corner radius - - - - - - - mm - - - 999.000000000000000 - - - - - - - + + + + Corner radius + + + + + + + mm + + + 999.000000000000000 + + + + + + + Style + + + + + + + Change... + + + + + diff --git a/tests/src/core/testqgscomposershapes.cpp b/tests/src/core/testqgscomposershapes.cpp index e1ab203e7b8..638e3fafc52 100644 --- a/tests/src/core/testqgscomposershapes.cpp +++ b/tests/src/core/testqgscomposershapes.cpp @@ -19,6 +19,9 @@ #include "qgscomposition.h" #include "qgscompositionchecker.h" #include "qgscomposershape.h" +#include "qgssymbolv2.h" +#include "qgssinglesymbolrendererv2.h" +#include "qgsfillsymbollayerv2.h" #include #include #include @@ -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 = "

Composer Shape Tests

\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" diff --git a/tests/testdata/control_images/expected_composershapes_symbolv2/expected_composershapes_symbolv2.png b/tests/testdata/control_images/expected_composershapes_symbolv2/expected_composershapes_symbolv2.png new file mode 100644 index 00000000000..aadd64de891 Binary files /dev/null and b/tests/testdata/control_images/expected_composershapes_symbolv2/expected_composershapes_symbolv2.png differ