Skip to content

Commit

Permalink
[FEATURE][composer] Add fit page to contents option
Browse files Browse the repository at this point in the history
New option in the composition panel, with optional extra margins
to add

Sponsored by NIWA
  • Loading branch information
nyalldawson committed Sep 25, 2015
1 parent 2215d48 commit 2f34300
Show file tree
Hide file tree
Showing 11 changed files with 646 additions and 153 deletions.
60 changes: 52 additions & 8 deletions python/core/composer/qgscomposition.sip
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,16 @@ class QgsComposition : QGraphicsScene

~QgsComposition();

/** Changes size of paper item. Also moves all items so that they retain
* their same relative position to the top left corner of their current page.
* @param width page width in mm
* @param height page height in mm
* @see paperHeight
* @see paperWidth
*/
void setPaperSize( const double width, const double height );
/** Changes size of paper item.
* @param width page width in mm
* @param height page height in mm
* @param keepRelativeItemPosition if true, all items and guides will be moved so that they retain
* their same relative position to the top left corner of their current page.
* @see paperHeight
* @see paperWidth
*/
void setPaperSize( double width, double height,
bool keepRelativeItemPosition = true );

/** Height of paper item
* @returns height in mm
Expand All @@ -77,6 +79,48 @@ class QgsComposition : QGraphicsScene
*/
double paperWidth() const;

/** Resizes the composition page to fit the current contents of the composition.
* Calling this method resets the number of pages to 1, with the size set to the
* minimum size required to fit all existing composer items. Items will also be
* repositioned so that the new top-left bounds of the composition is at the point
* (marginLeft, marginTop). An optional margin can be specified.
* @param marginTop top margin (millimeters)
* @param marginRight right margin (millimeters)
* @param marginBottom bottom margin (millimeters)
* @param marginLeft left margin (millimeters)
* @note added in QGIS 2.12
* @see setResizeToContentsMargins()
* @see resizeToContentsMargins()
*/
void resizePageToContents( double marginTop = 0.0, double marginRight = 0.0,
double marginBottom = 0.0, double marginLeft = 0.0 );

/** Sets the resize to contents margins. These margins are saved in the composition
* so that they can be restored with the composer.
* @param marginTop top margin (millimeters)
* @param marginRight right margin (millimeters)
* @param marginBottom bottom margin (millimeters)
* @param marginLeft left margin (millimeters)
* @note added in QGIS 2.12
* @see resizePageToContents()
* @see resizeToContentsMargins()
*/
void setResizeToContentsMargins( double marginTop, double marginRight,
double marginBottom, double marginLeft );

/** Returns the resize to contents margins. These margins are saved in the composition
* so that they can be restored with the composer.
* @param marginTop reference for top margin (millimeters)
* @param marginRight reference for right margin (millimeters)
* @param marginBottom reference for bottom margin (millimeters)
* @param marginLeft reference for left margin (millimeters)
* @note added in QGIS 2.12
* @see resizePageToContents()
* @see setResizeToContentsMargins()
*/
void resizeToContentsMargins( double& marginTop /Out/, double& marginRight /Out/,
double& marginBottom /Out/, double& marginLeft /Out/ ) const;

/** Returns the vertical space between pages in a composer view
* @returns space between pages in mm
*/
Expand Down
39 changes: 39 additions & 0 deletions src/app/composer/qgscompositionwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ QgsCompositionWidget::QgsCompositionWidget( QWidget* parent, QgsComposition* c )
//read printout resolution from composition
mResolutionSpinBox->setValue( mComposition->printResolution() );

double topMargin = 0;
double rightMargin = 0;
double bottomMargin = 0;
double leftMargin = 0;
mComposition->resizeToContentsMargins( topMargin, rightMargin, bottomMargin, leftMargin );
mTopMarginSpinBox->setValue( topMargin );
mRightMarginSpinBox->setValue( rightMargin );
mBottomMarginSpinBox->setValue( bottomMargin );
mLeftMarginSpinBox->setValue( leftMargin );

//print as raster
mPrintAsRasterCheckBox->setChecked( mComposition->printAsRaster() );

Expand Down Expand Up @@ -105,6 +115,11 @@ QgsCompositionWidget::QgsCompositionWidget( QWidget* parent, QgsComposition* c )
}
}

connect( mTopMarginSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( resizeMarginsChanged() ) );
connect( mRightMarginSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( resizeMarginsChanged() ) );
connect( mBottomMarginSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( resizeMarginsChanged() ) );
connect( mLeftMarginSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( resizeMarginsChanged() ) );

connect( mPaperSizeDDBtn, SIGNAL( dataDefinedChanged( const QString& ) ), this, SLOT( updateDataDefinedProperty() ) );
connect( mPaperSizeDDBtn, SIGNAL( dataDefinedActivated( bool ) ), this, SLOT( updateDataDefinedProperty() ) );
connect( mPaperSizeDDBtn, SIGNAL( dataDefinedActivated( bool ) ), mPaperSizeComboBox, SLOT( setDisabled( bool ) ) );
Expand Down Expand Up @@ -195,6 +210,17 @@ void QgsCompositionWidget::variablesChanged()
QgsExpressionContextUtils::setCompositionVariables( mComposition, mVariableEditor->variablesInActiveScope() );
}

void QgsCompositionWidget::resizeMarginsChanged()
{
if ( !mComposition )
return;

mComposition->setResizeToContentsMargins( mTopMarginSpinBox->value(),
mRightMarginSpinBox->value(),
mBottomMarginSpinBox->value(),
mLeftMarginSpinBox->value() );
}

void QgsCompositionWidget::setDataDefinedProperty( const QgsDataDefinedButton* ddBtn, QgsComposerObject::DataDefinedProperty property )
{
if ( !mComposition )
Expand Down Expand Up @@ -567,6 +593,19 @@ void QgsCompositionWidget::on_mPageStyleButton_clicked()
delete newSymbol;
}

void QgsCompositionWidget::on_mResizePageButton_clicked()
{
if ( !mComposition )
{
return;
}

mComposition->resizePageToContents( mTopMarginSpinBox->value(),
mRightMarginSpinBox->value(),
mBottomMarginSpinBox->value(),
mLeftMarginSpinBox->value() );
}

void QgsCompositionWidget::updatePageStyle()
{
if ( mComposition )
Expand Down
3 changes: 3 additions & 0 deletions src/app/composer/qgscompositionwidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class QgsCompositionWidget: public QWidget, private Ui::QgsCompositionWidgetBase
void on_mPaperHeightDoubleSpinBox_editingFinished();
void on_mNumPagesSpinBox_valueChanged( int value );
void on_mPageStyleButton_clicked();
void on_mResizePageButton_clicked();
void on_mResolutionSpinBox_valueChanged( const int value );
void on_mPrintAsRasterCheckBox_toggled( bool state );
void on_mGenerateWorldFileCheckBox_toggled( bool state );
Expand Down Expand Up @@ -85,6 +86,8 @@ class QgsCompositionWidget: public QWidget, private Ui::QgsCompositionWidgetBase

void variablesChanged();

void resizeMarginsChanged();

private:
QgsComposition* mComposition;
QMap<QString, QgsCompositionPaper> mPaperMap;
Expand Down
133 changes: 113 additions & 20 deletions src/core/composer/qgscomposition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ void QgsComposition::init()
mItemsModel = 0;
mUndoStack = new QUndoStack();

mResizeToContentsMarginTop = 0;
mResizeToContentsMarginRight = 0;
mResizeToContentsMarginBottom = 0;
mResizeToContentsMarginLeft = 0;

//data defined strings
mDataDefinedNames.insert( QgsComposerObject::PresetPaperSize, QString( "dataDefinedPaperSize" ) );
mDataDefinedNames.insert( QgsComposerObject::PaperWidth, QString( "dataDefinedPaperWidth" ) );
Expand Down Expand Up @@ -220,7 +225,7 @@ void QgsComposition::loadDefaults()

void QgsComposition::updateBounds()
{
setSceneRect( compositionBounds() );
setSceneRect( compositionBounds( false, 0.05 ) );
}

void QgsComposition::refreshItems()
Expand Down Expand Up @@ -289,10 +294,10 @@ void QgsComposition::refreshDataDefinedProperty( const QgsComposerObject::DataDe
}
}

QRectF QgsComposition::compositionBounds() const
QRectF QgsComposition::compositionBounds( bool ignorePages, double margin ) const
{
//start with an empty rectangle
QRectF bounds = QRectF( 0, 0, 0, 0 );
QRectF bounds;

//add all QgsComposerItems and QgsPaperItems which are in the composition
QList<QGraphicsItem *> itemList = items();
Expand All @@ -301,37 +306,47 @@ QRectF QgsComposition::compositionBounds() const
{
const QgsComposerItem* composerItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
const QgsPaperItem* paperItem = dynamic_cast<const QgsPaperItem*>( *itemIt );
if (( composerItem || paperItem ) )
if (( composerItem && ( !paperItem || !ignorePages ) ) )
{
//expand bounds with current item's bounds
bounds = bounds.united(( *itemIt )->sceneBoundingRect() );
if ( bounds.isValid() )
bounds = bounds.united(( *itemIt )->sceneBoundingRect() );
else
bounds = ( *itemIt )->sceneBoundingRect();
}
}

//finally, expand bounds out by 5% page size to give a bit of a margin
bounds.adjust( -mPageWidth * 0.05, -mPageWidth * 0.05, mPageWidth * 0.05, mPageWidth * 0.05 );
if ( bounds.isValid() && margin > 0.0 )
{
//finally, expand bounds out by specified margin of page size
bounds.adjust( -mPageWidth * margin, -mPageWidth * margin, mPageWidth * margin, mPageWidth * margin );
}

return bounds;
}

void QgsComposition::setPaperSize( const double width, const double height )
void QgsComposition::setPaperSize( const double width, const double height, bool keepRelativeItemPosition )
{
if ( width == mPageWidth && height == mPageHeight )
{
return;
}

//update item positions
QList<QGraphicsItem *> itemList = items();
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
for ( ; itemIt != itemList.end(); ++itemIt )
if ( keepRelativeItemPosition )
{
QgsComposerItem* composerItem = dynamic_cast<QgsComposerItem *>( *itemIt );
if ( composerItem )
//update item positions
QList<QGraphicsItem *> itemList = items();
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
for ( ; itemIt != itemList.end(); ++itemIt )
{
composerItem->updatePagePos( width, height );
QgsComposerItem* composerItem = dynamic_cast<QgsComposerItem *>( *itemIt );
if ( composerItem )
{
composerItem->updatePagePos( width, height );
}
}
}

//update guide positions and size
QList< QGraphicsLineItem* >* guides = snapLines();
QList< QGraphicsLineItem* >::iterator guideIt = guides->begin();
Expand All @@ -347,11 +362,19 @@ void QgsComposition::setPaperSize( const double width, const double height )
else
{
//horizontal line
//move to new vertical position and change width of line
QPointF curPagePos = positionOnPage( line.p1() );
int curPage = pageNumberForPoint( line.p1() ) - 1;
double newY = curPage * ( height + spaceBetweenPages() ) + curPagePos.y();
( *guideIt )->setLine( 0, newY, width, newY );
if ( keepRelativeItemPosition )
{
//move to new vertical position and change width of line
QPointF curPagePos = positionOnPage( line.p1() );
int curPage = pageNumberForPoint( line.p1() ) - 1;
double newY = curPage * ( height + spaceBetweenPages() ) + curPagePos.y();
( *guideIt )->setLine( 0, newY, width, newY );
}
else
{
//just resize guide to new page size
( *guideIt )->setLine( 0, line.y1(), width, line.y1() );
}
}
}

Expand All @@ -378,6 +401,66 @@ double QgsComposition::paperWidth() const
return mPageWidth;
}

void QgsComposition::resizePageToContents( double marginTop, double marginRight, double marginBottom, double marginLeft )
{
//calculate current bounds
QRectF bounds = compositionBounds( true, 0.0 );

setNumPages( 1 );
double newWidth = bounds.width() + marginLeft + marginRight;
double newHeight = bounds.height() + marginTop + marginBottom;
setPaperSize( newWidth, newHeight, false );

//also move all items so that top-left of bounds is at marginLeft, marginTop
double diffX = marginLeft - bounds.left();
double diffY = marginTop - bounds.top();

QList<QGraphicsItem *> itemList = items();
Q_FOREACH ( QGraphicsItem* item, itemList )
{
QgsComposerItem* composerItem = dynamic_cast<QgsComposerItem *>( item );
if ( composerItem )
{
const QgsPaperItem* paperItem = dynamic_cast<const QgsPaperItem*>( item );

if ( !paperItem )
composerItem->move( diffX, diffY );
}
}

//also move guides
Q_FOREACH ( QGraphicsLineItem* guide, mSnapLines )
{
QLineF line = guide->line();
if ( line.dx() == 0 )
{
//vertical line
guide->setLine( line.x1() + diffX, 0, line.x1() + diffX, newHeight );
}
else
{
//horizontal line
guide->setLine( 0, line.y1() + diffY, newWidth, line.y1() + diffY );
}
}
}

void QgsComposition::setResizeToContentsMargins( double marginTop, double marginRight, double marginBottom, double marginLeft )
{
mResizeToContentsMarginTop = marginTop;
mResizeToContentsMarginRight = marginRight;
mResizeToContentsMarginBottom = marginBottom;
mResizeToContentsMarginLeft = marginLeft;
}

void QgsComposition::resizeToContentsMargins( double& marginTop, double& marginRight, double& marginBottom, double& marginLeft ) const
{
marginTop = mResizeToContentsMarginTop;
marginRight = mResizeToContentsMarginRight;
marginBottom = mResizeToContentsMarginBottom;
marginLeft = mResizeToContentsMarginLeft;
}

void QgsComposition::setNumPages( const int pages )
{
int currentPages = numPages();
Expand Down Expand Up @@ -830,6 +913,11 @@ bool QgsComposition::writeXML( QDomElement& composerElem, QDomDocument& doc )
compositionElem.setAttribute( "smartGuides", mSmartGuides ? 1 : 0 );
compositionElem.setAttribute( "snapTolerancePixels", mSnapTolerance );

compositionElem.setAttribute( "resizeToContentsMarginTop", mResizeToContentsMarginTop );
compositionElem.setAttribute( "resizeToContentsMarginRight", mResizeToContentsMarginRight );
compositionElem.setAttribute( "resizeToContentsMarginBottom", mResizeToContentsMarginBottom );
compositionElem.setAttribute( "resizeToContentsMarginLeft", mResizeToContentsMarginLeft );

//save items except paper items and frame items (they are saved with the corresponding multiframe)
QList<QGraphicsItem*> itemList = items();
QList<QGraphicsItem*>::const_iterator itemIt = itemList.constBegin();
Expand Down Expand Up @@ -906,6 +994,11 @@ bool QgsComposition::readXML( const QDomElement& compositionElem, const QDomDocu
mSmartGuides = compositionElem.attribute( "smartGuides", "1" ).toInt() == 0 ? false : true;
mSnapTolerance = compositionElem.attribute( "snapTolerancePixels", "10" ).toInt();

mResizeToContentsMarginTop = compositionElem.attribute( "resizeToContentsMarginTop", "0" ).toDouble();
mResizeToContentsMarginRight = compositionElem.attribute( "resizeToContentsMarginRight", "0" ).toDouble();
mResizeToContentsMarginBottom = compositionElem.attribute( "resizeToContentsMarginBottom", "0" ).toDouble();
mResizeToContentsMarginLeft = compositionElem.attribute( "resizeToContentsMarginLeft", "0" ).toDouble();

//custom snap lines
QDomNodeList snapLineNodes = compositionElem.elementsByTagName( "SnapLine" );
for ( int i = 0; i < snapLineNodes.size(); ++i )
Expand Down
Loading

0 comments on commit 2f34300

Please sign in to comment.