From b42c055e97fadf4ff3acd3dc6c7a8e7397e94dff Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 24 Jul 2017 07:57:25 +1000 Subject: [PATCH] Restore drawing of page grids --- python/core/layout/qgslayout.sip | 2 + python/core/layout/qgslayoutcontext.sip | 73 +++++++++++++++ python/core/layout/qgslayoutitempage.sip | 5 + src/core/layout/qgslayout.h | 2 + src/core/layout/qgslayoutcontext.cpp | 11 ++- src/core/layout/qgslayoutcontext.h | 74 +++++++++++++++ src/core/layout/qgslayoutitempage.cpp | 111 +++++++++++++++++++++++ src/core/layout/qgslayoutitempage.h | 28 ++++++ tests/src/core/testqgslayoutpage.cpp | 34 +++++++ 9 files changed, 339 insertions(+), 1 deletion(-) diff --git a/python/core/layout/qgslayout.sip b/python/core/layout/qgslayout.sip index 29e013baa73..3fe046e497a 100644 --- a/python/core/layout/qgslayout.sip +++ b/python/core/layout/qgslayout.sip @@ -23,7 +23,9 @@ class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator enum ZValues { ZPage, + ZGrid, ZMapTool, + }; QgsLayout( QgsProject *project ); diff --git a/python/core/layout/qgslayoutcontext.sip b/python/core/layout/qgslayoutcontext.sip index a1e8ea16cda..3c7f40fbcc8 100644 --- a/python/core/layout/qgslayoutcontext.sip +++ b/python/core/layout/qgslayoutcontext.sip @@ -30,6 +30,13 @@ class QgsLayoutContext typedef QFlags Flags; + enum GridStyle + { + GridLines, + GridDots, + GridCrosses + }; + QgsLayoutContext(); void setFlags( const QgsLayoutContext::Flags flags ); @@ -125,6 +132,72 @@ class QgsLayoutContext :rtype: QgsLayoutMeasurementConverter %End + bool gridVisible() const; +%Docstring + Returns true if the page grid should be drawn. + :rtype: bool +%End + + void setGridResolution( const QgsLayoutMeasurement &resolution ); +%Docstring + Sets the page/snap grid ``resolution``. +.. seealso:: gridResolution() +.. seealso:: setGridOffset() +%End + + QgsLayoutMeasurement gridResolution() const; +%Docstring + Returns the page/snap grid resolution. +.. seealso:: setGridResolution() +.. seealso:: gridOffset() + :rtype: QgsLayoutMeasurement +%End + + void setGridOffset( const QgsLayoutPoint offset ); +%Docstring + Sets the ``offset`` of the page/snap grid. +.. seealso:: gridOffset() +.. seealso:: setGridResolution() +%End + + QgsLayoutPoint gridOffset() const; +%Docstring + Returns the offset of the page/snap grid. +.. seealso:: setGridOffset() +.. seealso:: gridResolution() + :rtype: QgsLayoutPoint +%End + + void setGridPen( const QPen &pen ); +%Docstring + Sets the ``pen`` used for drawing page/snap grids. +.. seealso:: gridPen() +.. seealso:: setGridStyle() +%End + + QPen gridPen() const; +%Docstring + Returns the pen used for drawing page/snap grids. +.. seealso:: setGridPen() +.. seealso:: gridStyle() + :rtype: QPen +%End + + void setGridStyle( const GridStyle style ); +%Docstring + Sets the ``style`` used for drawing the page/snap grids. +.. seealso:: gridStyle() +.. seealso:: setGridPen() +%End + + GridStyle gridStyle() const; +%Docstring + Returns the style used for drawing the page/snap grids. +.. seealso:: setGridStyle() +.. seealso:: gridPen() + :rtype: GridStyle +%End + }; diff --git a/python/core/layout/qgslayoutitempage.sip b/python/core/layout/qgslayoutitempage.sip index d29e964617b..e1ef47a6242 100644 --- a/python/core/layout/qgslayoutitempage.sip +++ b/python/core/layout/qgslayoutitempage.sip @@ -8,6 +8,8 @@ + + class QgsLayoutItemPage : QgsLayoutItem { %Docstring @@ -75,6 +77,9 @@ class QgsLayoutItemPage : QgsLayoutItem :rtype: QgsLayoutItemPage.Orientation %End + virtual void attemptResize( const QgsLayoutSize &size ); + + protected: virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 ); diff --git a/src/core/layout/qgslayout.h b/src/core/layout/qgslayout.h index 4e580325031..d7f397c8ac1 100644 --- a/src/core/layout/qgslayout.h +++ b/src/core/layout/qgslayout.h @@ -40,7 +40,9 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext enum ZValues { ZPage = 0, //!< Z-value for page (paper) items + ZGrid = 9999, //!< Z-value for page grids ZMapTool = 10000, //!< Z-value for temporary map tool items + }; /** diff --git a/src/core/layout/qgslayoutcontext.cpp b/src/core/layout/qgslayoutcontext.cpp index 183955bd60e..cc8bdb2d479 100644 --- a/src/core/layout/qgslayoutcontext.cpp +++ b/src/core/layout/qgslayoutcontext.cpp @@ -20,7 +20,11 @@ QgsLayoutContext::QgsLayoutContext() : mFlags( FlagAntialiasing | FlagUseAdvancedEffects ) -{} + , mGridResolution( QgsLayoutMeasurement( 10 ) ) +{ + mGridPen = QPen( QColor( 190, 190, 190, 100 ), 0 ); + mGridPen.setCosmetic( true ); +} void QgsLayoutContext::setFlags( const QgsLayoutContext::Flags flags ) { @@ -77,3 +81,8 @@ double QgsLayoutContext::dpi() const { return mMeasurementConverter.dpi(); } + +bool QgsLayoutContext::gridVisible() const +{ + return true; +} diff --git a/src/core/layout/qgslayoutcontext.h b/src/core/layout/qgslayoutcontext.h index 4aa5f43eaa5..b8adbc65469 100644 --- a/src/core/layout/qgslayoutcontext.h +++ b/src/core/layout/qgslayoutcontext.h @@ -46,6 +46,14 @@ class CORE_EXPORT QgsLayoutContext }; Q_DECLARE_FLAGS( Flags, Flag ) + //! Style for drawing the page/snapping grid + enum GridStyle + { + GridLines, //! Solid lines + GridDots, //! Dots + GridCrosses //! Crosses + }; + QgsLayoutContext(); /** @@ -139,6 +147,67 @@ class CORE_EXPORT QgsLayoutContext */ QgsLayoutMeasurementConverter &measurementConverter() { return mMeasurementConverter; } + /** + * Returns true if the page grid should be drawn. + */ + bool gridVisible() const; + + /** + * Sets the page/snap grid \a resolution. + * \see gridResolution() + * \see setGridOffset() + */ + void setGridResolution( const QgsLayoutMeasurement &resolution ) { mGridResolution = resolution; } + + /** + * Returns the page/snap grid resolution. + * \see setGridResolution() + * \see gridOffset() + */ + QgsLayoutMeasurement gridResolution() const { return mGridResolution;} + + /** + * Sets the \a offset of the page/snap grid. + * \see gridOffset() + * \see setGridResolution() + */ + void setGridOffset( const QgsLayoutPoint offset ) { mGridOffset = offset; } + + /** + * Returns the offset of the page/snap grid. + * \see setGridOffset() + * \see gridResolution() + */ + QgsLayoutPoint gridOffset() const { return mGridOffset; } + + /** + * Sets the \a pen used for drawing page/snap grids. + * \see gridPen() + * \see setGridStyle() + */ + void setGridPen( const QPen &pen ) { mGridPen = pen; } + + /** + * Returns the pen used for drawing page/snap grids. + * \see setGridPen() + * \see gridStyle() + */ + QPen gridPen() const { return mGridPen; } + + /** + * Sets the \a style used for drawing the page/snap grids. + * \see gridStyle() + * \see setGridPen() + */ + void setGridStyle( const GridStyle style ) { mGridStyle = style; } + + /** + * Returns the style used for drawing the page/snap grids. + * \see setGridStyle() + * \see gridPen() + */ + GridStyle gridStyle() const { return mGridStyle; } + private: Flags mFlags = 0; @@ -148,6 +217,11 @@ class CORE_EXPORT QgsLayoutContext QgsLayoutMeasurementConverter mMeasurementConverter; + QgsLayoutMeasurement mGridResolution; + QgsLayoutPoint mGridOffset; + QPen mGridPen; + GridStyle mGridStyle = GridLines; + }; #endif //QGSLAYOUTCONTEXT_H diff --git a/src/core/layout/qgslayoutitempage.cpp b/src/core/layout/qgslayoutitempage.cpp index 946fd0a90b9..1b78ec050c9 100644 --- a/src/core/layout/qgslayoutitempage.cpp +++ b/src/core/layout/qgslayoutitempage.cpp @@ -20,6 +20,7 @@ #include "qgspagesizeregistry.h" #include "qgssymbollayerutils.h" #include +#include QgsLayoutItemPage::QgsLayoutItemPage( QgsLayout *layout ) : QgsLayoutItem( layout ) @@ -36,6 +37,9 @@ QgsLayoutItemPage::QgsLayoutItemPage( QgsLayout *layout ) QFont font; QFontMetrics fm( font ); mMaximumShadowWidth = fm.width( "X" ); + + mGrid.reset( new QgsLayoutItemPageGrid( pos().x(), pos().y(), rect().width(), rect().height(), mLayout ) ); + mGrid->setParentItem( this ); } void QgsLayoutItemPage::setPageSize( const QgsLayoutSize &size ) @@ -106,6 +110,13 @@ QgsLayoutItemPage::Orientation QgsLayoutItemPage::decodePageOrientation( const Q return Landscape; } +void QgsLayoutItemPage::attemptResize( const QgsLayoutSize &size ) +{ + QgsLayoutItem::attemptResize( size ); + //update size of attached grid to reflect new page size and position + mGrid->setRect( 0, 0, rect().width(), rect().height() ); +} + void QgsLayoutItemPage::draw( QgsRenderContext &context, const QStyleOptionGraphicsItem * ) { if ( !context.painter() || !mLayout /*|| !mLayout->pagesVisible() */ ) @@ -166,3 +177,103 @@ void QgsLayoutItemPage::draw( QgsRenderContext &context, const QStyleOptionGraph painter->restore(); } + + +// +// QgsLayoutItemPageGrid +// +///@cond PRIVATE + +QgsLayoutItemPageGrid::QgsLayoutItemPageGrid( double x, double y, double width, double height, QgsLayout *layout ) + : QGraphicsRectItem( 0, 0, width, height ) + , mLayout( layout ) +{ + // needed to access current view transform during paint operations + setFlags( flags() | QGraphicsItem::ItemUsesExtendedStyleOption ); + setCacheMode( QGraphicsItem::DeviceCoordinateCache ); + setFlag( QGraphicsItem::ItemIsSelectable, false ); + setFlag( QGraphicsItem::ItemIsMovable, false ); + setZValue( QgsLayout::ZGrid ); + setPos( x, y ); +} + +void QgsLayoutItemPageGrid::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ) +{ + Q_UNUSED( pWidget ); + + //draw grid + if ( !mLayout ) + return; + + const QgsLayoutContext &context = mLayout->context(); + + if ( !context.gridVisible() || context.gridResolution().length() <= 0 ) + return; + + QPointF gridOffset = mLayout->convertToLayoutUnits( context.gridOffset() ); + double gridResolution = mLayout->convertToLayoutUnits( context.gridResolution() ); + int gridMultiplyX = static_cast< int >( gridOffset.x() / gridResolution ); + int gridMultiplyY = static_cast< int >( gridOffset.y() / gridResolution ); + double currentXCoord = gridOffset.x() - gridMultiplyX * gridResolution; + double currentYCoord; + double minYCoord = gridOffset.y() - gridMultiplyY * gridResolution; + + painter->save(); + //turn of antialiasing so grid is nice and sharp + painter->setRenderHint( QPainter::Antialiasing, false ); + + switch ( context.gridStyle() ) + { + case QgsLayoutContext::GridLines: + { + painter->setPen( context.gridPen() ); + + //draw vertical lines + for ( ; currentXCoord <= rect().width(); currentXCoord += gridResolution ) + { + painter->drawLine( QPointF( currentXCoord, 0 ), QPointF( currentXCoord, rect().height() ) ); + } + + //draw horizontal lines + currentYCoord = minYCoord; + for ( ; currentYCoord <= rect().height(); currentYCoord += gridResolution ) + { + painter->drawLine( QPointF( 0, currentYCoord ), QPointF( rect().width(), currentYCoord ) ); + } + break; + } + + case QgsLayoutContext::GridDots: + case QgsLayoutContext::GridCrosses: + { + QPen gridPen = context.gridPen(); + painter->setPen( gridPen ); + painter->setBrush( QBrush( gridPen.color() ) ); + double halfCrossLength = 1; + if ( context.gridStyle() == QgsLayoutContext::GridDots ) + { + //dots are actually drawn as tiny crosses a few pixels across + //set halfCrossLength to equivalent of 1 pixel + halfCrossLength = 1 / itemStyle->matrix.m11(); + } + else + { + halfCrossLength = gridResolution / 6; + } + + for ( ; currentXCoord <= rect().width(); currentXCoord += gridResolution ) + { + currentYCoord = minYCoord; + for ( ; currentYCoord <= rect().height(); currentYCoord += gridResolution ) + { + painter->drawLine( QPointF( currentXCoord - halfCrossLength, currentYCoord ), QPointF( currentXCoord + halfCrossLength, currentYCoord ) ); + painter->drawLine( QPointF( currentXCoord, currentYCoord - halfCrossLength ), QPointF( currentXCoord, currentYCoord + halfCrossLength ) ); + } + } + break; + } + } + painter->restore(); +} + +///@endcond diff --git a/src/core/layout/qgslayoutitempage.h b/src/core/layout/qgslayoutitempage.h index b0934f7ac2d..4ac180465a4 100644 --- a/src/core/layout/qgslayoutitempage.h +++ b/src/core/layout/qgslayoutitempage.h @@ -22,6 +22,29 @@ #include "qgslayoutitemregistry.h" #include "qgis_sip.h" + +///@cond PRIVATE +#ifndef SIP_RUN + +/** + * \ingroup core + * Item representing a grid. This is drawn separately to the underlying page item since the grid needs to be + * drawn above all other layout items, while the paper item is drawn below all others. + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsLayoutItemPageGrid: public QGraphicsRectItem +{ + public: + QgsLayoutItemPageGrid( double x, double y, double width, double height, QgsLayout *layout ); + + void paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ) override; + + private: + QgsLayout *mLayout = nullptr; +}; +#endif +///@endcond + /** * \ingroup core * \class QgsLayoutItemPage @@ -85,6 +108,8 @@ class CORE_EXPORT QgsLayoutItemPage : public QgsLayoutItem */ static QgsLayoutItemPage::Orientation decodePageOrientation( const QString &string, bool *ok SIP_OUT = nullptr ); + void attemptResize( const QgsLayoutSize &size ) override; + protected: void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ) override; @@ -93,6 +118,9 @@ class CORE_EXPORT QgsLayoutItemPage : public QgsLayoutItem double mMaximumShadowWidth = -1; + std::unique_ptr< QgsLayoutItemPageGrid > mGrid; + + friend class TestQgsLayoutPage; }; #endif //QGSLAYOUTITEMPAGE_H diff --git a/tests/src/core/testqgslayoutpage.cpp b/tests/src/core/testqgslayoutpage.cpp index 10cd46d3742..13e78d20d02 100644 --- a/tests/src/core/testqgslayoutpage.cpp +++ b/tests/src/core/testqgslayoutpage.cpp @@ -35,6 +35,7 @@ class TestQgsLayoutPage : public QObject void itemType(); void pageSize(); void decodePageOrientation(); + void grid(); private: QString mReport; @@ -125,5 +126,38 @@ void TestQgsLayoutPage::decodePageOrientation() QVERIFY( !ok ); } +void TestQgsLayoutPage::grid() +{ + // test that grid follows page around + QgsProject p; + QgsLayout l( &p ); + QgsLayoutItemPage *page = new QgsLayoutItemPage( &l ); + + // should have a grid + QVERIFY( page->mGrid.get() ); + + // grid is parented to page, so while the grid should resize to match + // page size, it should always report pos() of 0,0 (origin of page) + page->attemptMove( QgsLayoutPoint( 5, 15 ) ); + page->attemptResize( QgsLayoutSize( 100, 200 ) ); + QCOMPARE( page->mGrid->rect().width(), 100.0 ); + QCOMPARE( page->mGrid->rect().height(), 200.0 ); + QCOMPARE( page->mGrid->pos().x(), 0.0 ); + QCOMPARE( page->mGrid->pos().y(), 0.0 ); + + page->attemptMove( QgsLayoutPoint( 25, 35 ) ); + QCOMPARE( page->mGrid->rect().width(), 100.0 ); + QCOMPARE( page->mGrid->rect().height(), 200.0 ); + QCOMPARE( page->mGrid->pos().x(), 0.0 ); + QCOMPARE( page->mGrid->pos().y(), 0.0 ); + + page->attemptResize( QgsLayoutSize( 150, 250 ) ); + QCOMPARE( page->mGrid->rect().width(), 150.0 ); + QCOMPARE( page->mGrid->rect().height(), 250.0 ); + QCOMPARE( page->mGrid->pos().x(), 0.0 ); + QCOMPARE( page->mGrid->pos().y(), 0.0 ); + +} + QGSTEST_MAIN( TestQgsLayoutPage ) #include "testqgslayoutpage.moc"