/*************************************************************************** qgslayoutitempage.cpp --------------------- begin : July 2017 copyright : (C) 2017 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 "qgslayoutitempage.h" #include "qgslayout.h" #include "qgslayoututils.h" #include "qgspagesizeregistry.h" #include "qgssymbollayerutils.h" #include "qgslayoutitemundocommand.h" #include "qgslayoutpagecollection.h" #include "qgslayoutundostack.h" #include #include QgsLayoutItemPage::QgsLayoutItemPage( QgsLayout *layout ) : QgsLayoutItem( layout, false ) { setFlag( QGraphicsItem::ItemIsSelectable, false ); setFlag( QGraphicsItem::ItemIsMovable, false ); setZValue( QgsLayout::ZPage ); connect( this, &QgsLayoutItem::sizePositionChanged, this, [ = ] { mBoundingRect = QRectF(); prepareGeometryChange(); } ); QFont font; QFontMetrics fm( font ); mMaximumShadowWidth = fm.width( QStringLiteral( "X" ) ); mGrid.reset( new QgsLayoutItemPageGrid( pos().x(), pos().y(), rect().width(), rect().height(), mLayout ) ); mGrid->setParentItem( this ); } QgsLayoutItemPage *QgsLayoutItemPage::create( QgsLayout *layout ) { return new QgsLayoutItemPage( layout ); } int QgsLayoutItemPage::type() const { return QgsLayoutItemRegistry::LayoutPage; } void QgsLayoutItemPage::setPageSize( const QgsLayoutSize &size ) { attemptResize( size ); } bool QgsLayoutItemPage::setPageSize( const QString &size, Orientation orientation ) { QgsPageSize newSize; if ( QgsApplication::pageSizeRegistry()->decodePageSize( size, newSize ) ) { switch ( orientation ) { case Portrait: break; // nothing to do case Landscape: { // flip height and width double x = newSize.size.width(); newSize.size.setWidth( newSize.size.height() ); newSize.size.setHeight( x ); break; } } setPageSize( newSize.size ); return true; } else { return false; } } QgsLayoutSize QgsLayoutItemPage::pageSize() const { return sizeWithUnits(); } QgsLayoutItemPage::Orientation QgsLayoutItemPage::orientation() const { if ( sizeWithUnits().width() >= sizeWithUnits().height() ) return Landscape; else return Portrait; } QgsLayoutItemPage::Orientation QgsLayoutItemPage::decodePageOrientation( const QString &string, bool *ok ) { if ( ok ) *ok = false; QString trimmedString = string.trimmed(); if ( trimmedString.compare( QStringLiteral( "portrait" ), Qt::CaseInsensitive ) == 0 ) { if ( ok ) *ok = true; return Portrait; } else if ( trimmedString.compare( QStringLiteral( "landscape" ), Qt::CaseInsensitive ) == 0 ) { if ( ok ) *ok = true; return Landscape; } return Landscape; } QRectF QgsLayoutItemPage::boundingRect() const { if ( mBoundingRect.isNull() ) { double shadowWidth = mLayout->pageCollection()->pageShadowWidth(); mBoundingRect = rect(); mBoundingRect.adjust( 0, 0, shadowWidth, shadowWidth ); } return mBoundingRect; } void QgsLayoutItemPage::attemptResize( const QgsLayoutSize &size, bool includesFrame ) { QgsLayoutItem::attemptResize( size, includesFrame ); //update size of attached grid to reflect new page size and position mGrid->setRect( 0, 0, rect().width(), rect().height() ); mLayout->guides().update(); } ///@cond PRIVATE class QgsLayoutItemPageUndoCommand: public QgsLayoutItemUndoCommand { public: QgsLayoutItemPageUndoCommand( QgsLayoutItemPage *page, const QString &text, int id = 0, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr ) : QgsLayoutItemUndoCommand( page, text, id, parent ) {} void restoreState( QDomDocument &stateDoc ) override { QgsLayoutItemUndoCommand::restoreState( stateDoc ); layout()->pageCollection()->reflow(); } protected: QgsLayoutItem *recreateItem( int, QgsLayout *layout ) override { QgsLayoutItemPage *page = new QgsLayoutItemPage( layout ); layout->pageCollection()->addPage( page ); return page; } }; ///@endcond QgsAbstractLayoutUndoCommand *QgsLayoutItemPage::createCommand( const QString &text, int id, QUndoCommand *parent ) { return new QgsLayoutItemPageUndoCommand( this, text, id, parent ); } void QgsLayoutItemPage::redraw() { QgsLayoutItem::redraw(); mGrid->update(); } void QgsLayoutItemPage::draw( QgsRenderContext &context, const QStyleOptionGraphicsItem * ) { if ( !context.painter() || !mLayout || !mLayout->renderContext().pagesVisible() ) { return; } double scale = context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ); QgsExpressionContext expressionContext = createExpressionContext(); context.setExpressionContext( expressionContext ); QPainter *painter = context.painter(); painter->save(); if ( mLayout->renderContext().isPreviewRender() ) { //if in preview mode, draw page border and shadow so that it's //still possible to tell where pages with a transparent style begin and end painter->setRenderHint( QPainter::Antialiasing, false ); QRectF pageRect = QRectF( 0, 0, scale * rect().width(), scale * rect().height() ); //shadow painter->setBrush( QBrush( QColor( 150, 150, 150 ) ) ); painter->setPen( Qt::NoPen ); painter->drawRect( pageRect.translated( std::min( scale * mLayout->pageCollection()->pageShadowWidth(), mMaximumShadowWidth ), std::min( scale * mLayout->pageCollection()->pageShadowWidth(), mMaximumShadowWidth ) ) ); //page area painter->setBrush( QColor( 215, 215, 215 ) ); QPen pagePen = QPen( QColor( 100, 100, 100 ), 0 ); pagePen.setJoinStyle( Qt::MiterJoin ); pagePen.setCosmetic( true ); painter->setPen( pagePen ); painter->drawRect( pageRect ); } std::unique_ptr< QgsFillSymbol > symbol( mLayout->pageCollection()->pageStyleSymbol()->clone() ); symbol->startRender( context ); //get max bleed from symbol double maxBleedPixels = QgsSymbolLayerUtils::estimateMaxSymbolBleed( symbol.get(), context ); //Now subtract 1 pixel to prevent semi-transparent borders at edge of solid page caused by //anti-aliased painting. This may cause a pixel to be cropped from certain edge lines/symbols, //but that can be counteracted by adding a dummy transparent line symbol layer with a wider line width if ( !mLayout->renderContext().isPreviewRender() || !qgsDoubleNear( maxBleedPixels, 0.0 ) ) { maxBleedPixels = std::floor( maxBleedPixels - 2 ); } // round up QPolygonF pagePolygon = QPolygonF( QRectF( maxBleedPixels, maxBleedPixels, std::ceil( rect().width() * scale ) - 2 * maxBleedPixels, std::ceil( rect().height() * scale ) - 2 * maxBleedPixels ) ); QList rings; //empty list symbol->renderPolygon( pagePolygon, &rings, nullptr, context ); symbol->stopRender( context ); painter->restore(); } void QgsLayoutItemPage::drawFrame( QgsRenderContext & ) {} void QgsLayoutItemPage::drawBackground( QgsRenderContext & ) {} // // 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; if ( !mLayout->renderContext().isPreviewRender() ) return; const QgsLayoutRenderContext &context = mLayout->renderContext(); const QgsLayoutGridSettings &grid = mLayout->gridSettings(); if ( !context.gridVisible() || grid.resolution().length() <= 0 ) return; QPointF gridOffset = mLayout->convertToLayoutUnits( grid.offset() ); double gridResolution = mLayout->convertToLayoutUnits( grid.resolution() ); 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 ( grid.style() ) { case QgsLayoutGridSettings::StyleLines: { painter->setPen( grid.pen() ); //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 QgsLayoutGridSettings::StyleDots: case QgsLayoutGridSettings::StyleCrosses: { QPen gridPen = grid.pen(); painter->setPen( gridPen ); painter->setBrush( QBrush( gridPen.color() ) ); double halfCrossLength = 1; if ( grid.style() == QgsLayoutGridSettings::StyleDots ) { //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