mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-16 00:03:12 -04:00
This allows the CRS for map items to differ from the canvas/project CRS. It also allows different map items to have different CRS, eg an overview map can be set to a different CRS to the main map. An unfortunate side effect of this change and the ongoing work to separate compositions from canvas is that datum transforms are no longer supported in composer. This cannot be fixed until the datum transform store is rewritten to not depend on canvas (ie, it's also broken for upcoming multi-canvas work)
3610 lines
110 KiB
C++
3610 lines
110 KiB
C++
/***************************************************************************
|
|
qgscomposition.cpp
|
|
-------------------
|
|
begin : January 2005
|
|
copyright : (C) 2005 by Radim Blazek
|
|
email : blazek@itc.it
|
|
***************************************************************************/
|
|
/***************************************************************************
|
|
* *
|
|
* 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 "qgscomposition.h"
|
|
#include "qgscomposerutils.h"
|
|
#include "qgscomposerarrow.h"
|
|
#include "qgscomposerpolygon.h"
|
|
#include "qgscomposerpolyline.h"
|
|
#include "qgscomposerframe.h"
|
|
#include "qgscomposerhtml.h"
|
|
#include "qgscomposerlabel.h"
|
|
#include "qgscomposerlegend.h"
|
|
#include "qgscomposermap.h"
|
|
#include "qgscomposermapoverview.h"
|
|
#include "qgscomposermousehandles.h"
|
|
#include "qgscomposeritemgroup.h"
|
|
#include "qgscomposerpicture.h"
|
|
#include "qgscomposerscalebar.h"
|
|
#include "qgscomposershape.h"
|
|
#include "qgscomposermodel.h"
|
|
#include "qgscomposerattributetablev2.h"
|
|
#include "qgsaddremovemultiframecommand.h"
|
|
#include "qgscomposermultiframecommand.h"
|
|
#include "qgsgroupungroupitemscommand.h"
|
|
#include "qgsmapsettings.h"
|
|
#include "qgspaintenginehack.h"
|
|
#include "qgspaperitem.h"
|
|
#include "qgsproject.h"
|
|
#include "qgsgeometry.h"
|
|
#include "qgsvectorlayer.h"
|
|
#include "qgsvectordataprovider.h"
|
|
#include "qgsexpression.h"
|
|
#include "qgssymbol.h"
|
|
#include "qgssymbollayerutils.h"
|
|
#include "qgsdatadefined.h"
|
|
#include "qgslogger.h"
|
|
|
|
#include <QDomDocument>
|
|
#include <QDomElement>
|
|
#include <QGraphicsRectItem>
|
|
#include <QGraphicsView>
|
|
#include <QPainter>
|
|
#include <QPrinter>
|
|
#include <QSettings>
|
|
#include <QDir>
|
|
|
|
#include <limits>
|
|
#include "gdal.h"
|
|
#include "cpl_conv.h"
|
|
|
|
QgsComposition::QgsComposition( const QgsMapSettings& mapSettings, QgsProject* project )
|
|
: QGraphicsScene( nullptr )
|
|
, mMapSettings( mapSettings )
|
|
, mProject( project )
|
|
, mAtlasComposition( this )
|
|
{
|
|
init();
|
|
}
|
|
|
|
void QgsComposition::init()
|
|
{
|
|
// these members should be ideally in constructor's initialization list, but now we have two constructors...
|
|
mPlotStyle = QgsComposition::Preview;
|
|
mPageWidth = 297;
|
|
mPageHeight = 210;
|
|
mSpaceBetweenPages = 10;
|
|
mPageStyleSymbol = nullptr;
|
|
mPrintAsRaster = false;
|
|
mGenerateWorldFile = false;
|
|
mUseAdvancedEffects = true;
|
|
mSnapToGrid = false;
|
|
mGridVisible = false;
|
|
mPagesVisible = true;
|
|
mSnapGridResolution = 0;
|
|
mSnapGridOffsetX = 0;
|
|
mSnapGridOffsetY = 0;
|
|
mAlignmentSnap = true;
|
|
mGuidesVisible = true;
|
|
mSmartGuides = true;
|
|
mSnapTolerance = 0;
|
|
mBoundingBoxesVisible = true;
|
|
mSelectionHandles = nullptr;
|
|
mActiveItemCommand = nullptr;
|
|
mActiveMultiFrameCommand = nullptr;
|
|
mAtlasMode = QgsComposition::AtlasOff;
|
|
mPreventCursorChange = false;
|
|
mItemsModel = nullptr;
|
|
mUndoStack = new QUndoStack();
|
|
|
|
mResizeToContentsMarginTop = 0;
|
|
mResizeToContentsMarginRight = 0;
|
|
mResizeToContentsMarginBottom = 0;
|
|
mResizeToContentsMarginLeft = 0;
|
|
|
|
//data defined strings
|
|
mDataDefinedNames.insert( QgsComposerObject::PresetPaperSize, QStringLiteral( "dataDefinedPaperSize" ) );
|
|
mDataDefinedNames.insert( QgsComposerObject::PaperWidth, QStringLiteral( "dataDefinedPaperWidth" ) );
|
|
mDataDefinedNames.insert( QgsComposerObject::PaperHeight, QStringLiteral( "dataDefinedPaperHeight" ) );
|
|
mDataDefinedNames.insert( QgsComposerObject::NumPages, QStringLiteral( "dataDefinedNumPages" ) );
|
|
mDataDefinedNames.insert( QgsComposerObject::PaperOrientation, QStringLiteral( "dataDefinedPaperOrientation" ) );
|
|
|
|
//connect to atlas toggling on/off and coverage layer and feature changes
|
|
//to update data defined values
|
|
connect( &mAtlasComposition, SIGNAL( toggled( bool ) ), this, SLOT( refreshDataDefinedProperty() ) );
|
|
connect( &mAtlasComposition, SIGNAL( coverageLayerChanged( QgsVectorLayer* ) ), this, SLOT( refreshDataDefinedProperty() ) );
|
|
connect( &mAtlasComposition, SIGNAL( featureChanged( QgsFeature* ) ), this, SLOT( refreshDataDefinedProperty() ) );
|
|
//also, refreshing composition triggers a recalculation of data defined properties
|
|
connect( this, SIGNAL( refreshItemsTriggered() ), this, SLOT( refreshDataDefinedProperty() ) );
|
|
//toggling atlas or changing coverage layer requires data defined expressions to be reprepared
|
|
connect( &mAtlasComposition, SIGNAL( toggled( bool ) ), this, SLOT( prepareAllDataDefinedExpressions() ) );
|
|
connect( &mAtlasComposition, SIGNAL( coverageLayerChanged( QgsVectorLayer* ) ), this, SLOT( prepareAllDataDefinedExpressions() ) );
|
|
|
|
setBackgroundBrush( QColor( 215, 215, 215 ) );
|
|
createDefaultPageStyleSymbol();
|
|
|
|
addPaperItem();
|
|
|
|
updateBounds();
|
|
|
|
//add mouse selection handles to composition, and initially hide
|
|
mSelectionHandles = new QgsComposerMouseHandles( this );
|
|
addItem( mSelectionHandles );
|
|
mSelectionHandles->hide();
|
|
mSelectionHandles->setZValue( 500 );
|
|
|
|
mPrintResolution = 300; //hardcoded default
|
|
|
|
//load default composition settings
|
|
loadDefaults();
|
|
loadSettings();
|
|
|
|
mItemsModel = new QgsComposerModel( this );
|
|
}
|
|
|
|
|
|
QgsComposition::~QgsComposition()
|
|
{
|
|
removePaperItems();
|
|
deleteAndRemoveMultiFrames();
|
|
|
|
// make sure that all composer items are removed before
|
|
// this class is deconstructed - to avoid segfaults
|
|
// when composer items access in destructor composition that isn't valid anymore
|
|
QList<QGraphicsItem*> itemList = items();
|
|
qDeleteAll( itemList );
|
|
|
|
// clear pointers to QgsDataDefined objects
|
|
qDeleteAll( mDataDefinedProperties );
|
|
mDataDefinedProperties.clear();
|
|
|
|
//order is important here - we need to delete model last so that all items have already
|
|
//been deleted. Deleting the undo stack will also delete any items which have been
|
|
//removed from the scene, so this needs to be done before deleting the model
|
|
delete mUndoStack;
|
|
|
|
delete mActiveItemCommand;
|
|
delete mActiveMultiFrameCommand;
|
|
delete mPageStyleSymbol;
|
|
delete mItemsModel;
|
|
}
|
|
|
|
QgsProject* QgsComposition::project() const
|
|
{
|
|
return mProject;
|
|
}
|
|
|
|
void QgsComposition::loadDefaults()
|
|
{
|
|
QSettings settings;
|
|
mSnapGridResolution = settings.value( QStringLiteral( "/Composer/defaultSnapGridResolution" ), 10.0 ).toDouble();
|
|
mSnapGridOffsetX = settings.value( QStringLiteral( "/Composer/defaultSnapGridOffsetX" ), 0 ).toDouble();
|
|
mSnapGridOffsetY = settings.value( QStringLiteral( "/Composer/defaultSnapGridOffsetY" ), 0 ).toDouble();
|
|
mSnapTolerance = settings.value( QStringLiteral( "/Composer/defaultSnapTolerancePixels" ), 5 ).toInt();
|
|
}
|
|
|
|
void QgsComposition::updateBounds()
|
|
{
|
|
setSceneRect( compositionBounds( false, 0.05 ) );
|
|
}
|
|
|
|
void QgsComposition::refreshItems()
|
|
{
|
|
emit refreshItemsTriggered();
|
|
//force a redraw on all maps
|
|
QList<QgsComposerMap*> maps;
|
|
composerItems( maps );
|
|
QList<QgsComposerMap*>::iterator mapIt = maps.begin();
|
|
for ( ; mapIt != maps.end(); ++mapIt )
|
|
{
|
|
( *mapIt )->cache();
|
|
( *mapIt )->update();
|
|
}
|
|
}
|
|
|
|
void QgsComposition::setSelectedItem( QgsComposerItem *item )
|
|
{
|
|
setAllDeselected();
|
|
if ( item )
|
|
{
|
|
item->setSelected( true );
|
|
emit selectedItemChanged( item );
|
|
}
|
|
}
|
|
|
|
void QgsComposition::setAllDeselected()
|
|
{
|
|
//we can't use QGraphicsScene::clearSelection, as that emits no signals
|
|
//and we don't know which items are being deselected
|
|
//accordingly, we can't inform the composition model of selection changes
|
|
//instead, do the clear selection manually...
|
|
QList<QGraphicsItem *> selectedItemList = selectedItems();
|
|
QList<QGraphicsItem *>::iterator itemIter = selectedItemList.begin();
|
|
|
|
for ( ; itemIter != selectedItemList.end(); ++itemIter )
|
|
{
|
|
QgsComposerItem* composerItem = dynamic_cast<QgsComposerItem *>( *itemIter );
|
|
if ( composerItem )
|
|
{
|
|
composerItem->setSelected( false );
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgsComposition::refreshDataDefinedProperty( const QgsComposerObject::DataDefinedProperty property, const QgsExpressionContext* context )
|
|
{
|
|
QgsExpressionContext scopedContext = createExpressionContext();
|
|
const QgsExpressionContext* evalContext = context ? context : &scopedContext;
|
|
|
|
|
|
//updates data defined properties and redraws composition to match
|
|
if ( property == QgsComposerObject::NumPages || property == QgsComposerObject::AllProperties )
|
|
{
|
|
setNumPages( numPages() );
|
|
}
|
|
if ( property == QgsComposerObject::PaperWidth || property == QgsComposerObject::PaperHeight ||
|
|
property == QgsComposerObject::PaperOrientation || property == QgsComposerObject::PresetPaperSize ||
|
|
property == QgsComposerObject::AllProperties )
|
|
{
|
|
refreshPageSize( evalContext );
|
|
}
|
|
}
|
|
|
|
QRectF QgsComposition::compositionBounds( bool ignorePages, double margin ) const
|
|
{
|
|
//start with an empty rectangle
|
|
QRectF bounds;
|
|
|
|
//add all QgsComposerItems and QgsPaperItems which are in the composition
|
|
QList<QGraphicsItem *> itemList = items();
|
|
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
|
|
for ( ; itemIt != itemList.end(); ++itemIt )
|
|
{
|
|
const QgsComposerItem* composerItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
|
|
const QgsPaperItem* paperItem = dynamic_cast<const QgsPaperItem*>( *itemIt );
|
|
if (( composerItem && ( !paperItem || !ignorePages ) ) )
|
|
{
|
|
//expand bounds with current item's bounds
|
|
if ( bounds.isValid() )
|
|
bounds = bounds.united(( *itemIt )->sceneBoundingRect() );
|
|
else
|
|
bounds = ( *itemIt )->sceneBoundingRect();
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
QRectF QgsComposition::pageItemBounds( int pageNumber, bool visibleOnly ) const
|
|
{
|
|
//start with an empty rectangle
|
|
QRectF bounds;
|
|
|
|
//add all QgsComposerItems on page
|
|
QList<QGraphicsItem *> itemList = items();
|
|
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
|
|
for ( ; itemIt != itemList.end(); ++itemIt )
|
|
{
|
|
const QgsComposerItem* composerItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
|
|
const QgsPaperItem* paperItem = dynamic_cast<const QgsPaperItem*>( *itemIt );
|
|
if ( composerItem && !paperItem && itemPageNumber( composerItem ) == pageNumber )
|
|
{
|
|
if ( visibleOnly && !composerItem->isVisible() )
|
|
continue;
|
|
|
|
//expand bounds with current item's bounds
|
|
if ( bounds.isValid() )
|
|
bounds = bounds.united(( *itemIt )->sceneBoundingRect() );
|
|
else
|
|
bounds = ( *itemIt )->sceneBoundingRect();
|
|
}
|
|
}
|
|
|
|
return bounds;
|
|
}
|
|
|
|
void QgsComposition::setPaperSize( const double width, const double height, bool keepRelativeItemPosition )
|
|
{
|
|
if ( qgsDoubleNear( width, mPageWidth ) && qgsDoubleNear( height, mPageHeight ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( keepRelativeItemPosition )
|
|
{
|
|
//update item positions
|
|
QList<QGraphicsItem *> itemList = items();
|
|
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
|
|
for ( ; itemIt != itemList.end(); ++itemIt )
|
|
{
|
|
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();
|
|
double totalHeight = ( height + spaceBetweenPages() ) * ( numPages() - 1 ) + height;
|
|
for ( ; guideIt != guides->end(); ++guideIt )
|
|
{
|
|
QLineF line = ( *guideIt )->line();
|
|
if ( qgsDoubleNear( line.dx(), 0. ) )
|
|
{
|
|
//vertical line, change height of line
|
|
( *guideIt )->setLine( line.x1(), 0, line.x1(), totalHeight );
|
|
}
|
|
else
|
|
{
|
|
//horizontal line
|
|
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() );
|
|
}
|
|
}
|
|
}
|
|
|
|
mPageWidth = width;
|
|
mPageHeight = height;
|
|
double currentY = 0;
|
|
for ( int i = 0; i < mPages.size(); ++i )
|
|
{
|
|
mPages.at( i )->setSceneRect( QRectF( 0, currentY, width, height ) );
|
|
currentY += ( height + mSpaceBetweenPages );
|
|
}
|
|
mProject->setDirty( true );
|
|
updateBounds();
|
|
emit paperSizeChanged();
|
|
}
|
|
|
|
double QgsComposition::paperHeight() const
|
|
{
|
|
return mPageHeight;
|
|
}
|
|
|
|
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 ( qgsDoubleNear( line.dx(), 0.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();
|
|
int desiredPages = pages;
|
|
|
|
//data defined num pages set?
|
|
QVariant exprVal;
|
|
QgsExpressionContext context = createExpressionContext();
|
|
if ( dataDefinedEvaluate( QgsComposerObject::NumPages, exprVal, context, &mDataDefinedProperties ) )
|
|
{
|
|
bool ok = false;
|
|
int pagesD = exprVal.toInt( &ok );
|
|
QgsDebugMsg( QString( "exprVal NumPages:%1" ).arg( pagesD ) );
|
|
if ( ok )
|
|
{
|
|
desiredPages = pagesD;
|
|
}
|
|
}
|
|
|
|
int diff = desiredPages - currentPages;
|
|
if ( diff >= 0 )
|
|
{
|
|
for ( int i = 0; i < diff; ++i )
|
|
{
|
|
addPaperItem();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
diff = -diff;
|
|
for ( int i = 0; i < diff; ++i )
|
|
{
|
|
delete mPages.last();
|
|
mPages.removeLast();
|
|
}
|
|
}
|
|
|
|
//update vertical guide height
|
|
QList< QGraphicsLineItem* >* guides = snapLines();
|
|
QList< QGraphicsLineItem* >::iterator guideIt = guides->begin();
|
|
double totalHeight = ( mPageHeight + spaceBetweenPages() ) * ( pages - 1 ) + mPageHeight;
|
|
for ( ; guideIt != guides->end(); ++guideIt )
|
|
{
|
|
QLineF line = ( *guideIt )->line();
|
|
if ( qgsDoubleNear( line.dx(), 0.0 ) )
|
|
{
|
|
//vertical line, change height of line
|
|
( *guideIt )->setLine( line.x1(), 0, line.x1(), totalHeight );
|
|
}
|
|
}
|
|
|
|
mProject->setDirty( true );
|
|
updateBounds();
|
|
|
|
emit nPagesChanged();
|
|
}
|
|
|
|
int QgsComposition::numPages() const
|
|
{
|
|
return mPages.size();
|
|
}
|
|
|
|
bool QgsComposition::pageIsEmpty( const int page ) const
|
|
{
|
|
//get all items on page
|
|
QList<QgsComposerItem*> items;
|
|
//composerItemsOnPage uses 0-based page numbering
|
|
composerItemsOnPage( items, page - 1 );
|
|
|
|
//loop through and check for non-paper items
|
|
QList<QgsComposerItem*>::const_iterator itemIt = items.constBegin();
|
|
for ( ; itemIt != items.constEnd(); ++itemIt )
|
|
{
|
|
//is item a paper item?
|
|
QgsPaperItem* paper = dynamic_cast<QgsPaperItem*>( *itemIt );
|
|
if ( !paper )
|
|
{
|
|
//item is not a paper item, so we have other items on the page
|
|
return false;
|
|
}
|
|
}
|
|
//no non-paper items
|
|
return true;
|
|
}
|
|
|
|
bool QgsComposition::shouldExportPage( const int page ) const
|
|
{
|
|
if ( page > numPages() || page < 1 )
|
|
{
|
|
//page number out of range
|
|
return false;
|
|
}
|
|
|
|
//check all frame items on page
|
|
QList<QgsComposerFrame*> frames;
|
|
//composerItemsOnPage uses 0 based page numbering
|
|
composerItemsOnPage( frames, page - 1 );
|
|
QList<QgsComposerFrame*>::const_iterator frameIt = frames.constBegin();
|
|
for ( ; frameIt != frames.constEnd(); ++frameIt )
|
|
{
|
|
if (( *frameIt )->hidePageIfEmpty() && ( *frameIt )->isEmpty() )
|
|
{
|
|
//frame is set to hide page if empty, and frame is empty, so we don't want to export this page
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void QgsComposition::setPageStyleSymbol( QgsFillSymbol* symbol )
|
|
{
|
|
delete mPageStyleSymbol;
|
|
mPageStyleSymbol = static_cast<QgsFillSymbol*>( symbol->clone() );
|
|
mProject->setDirty( true );
|
|
}
|
|
|
|
void QgsComposition::createDefaultPageStyleSymbol()
|
|
{
|
|
delete mPageStyleSymbol;
|
|
QgsStringMap properties;
|
|
properties.insert( QStringLiteral( "color" ), QStringLiteral( "white" ) );
|
|
properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
|
|
properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "no" ) );
|
|
properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
|
|
mPageStyleSymbol = QgsFillSymbol::createSimple( properties );
|
|
}
|
|
|
|
QPointF QgsComposition::positionOnPage( QPointF position ) const
|
|
{
|
|
double y;
|
|
if ( position.y() > ( mPages.size() - 1 ) * ( paperHeight() + spaceBetweenPages() ) )
|
|
{
|
|
//y coordinate is greater then the end of the last page, so return distance between
|
|
//top of last page and y coordinate
|
|
y = position.y() - ( mPages.size() - 1 ) * ( paperHeight() + spaceBetweenPages() );
|
|
}
|
|
else
|
|
{
|
|
//y coordinate is less then the end of the last page
|
|
y = fmod( position.y(), ( paperHeight() + spaceBetweenPages() ) );
|
|
}
|
|
return QPointF( position.x(), y );
|
|
}
|
|
|
|
int QgsComposition::pageNumberForPoint( QPointF position ) const
|
|
{
|
|
int pageNumber = qFloor( position.y() / ( paperHeight() + spaceBetweenPages() ) ) + 1;
|
|
pageNumber = pageNumber < 1 ? 1 : pageNumber;
|
|
pageNumber = pageNumber > mPages.size() ? mPages.size() : pageNumber;
|
|
return pageNumber;
|
|
}
|
|
|
|
void QgsComposition::setStatusMessage( const QString & message )
|
|
{
|
|
emit statusMsgChanged( message );
|
|
}
|
|
|
|
QgsComposerItem* QgsComposition::composerItemAt( QPointF position, bool ignoreLocked ) const
|
|
{
|
|
return composerItemAt( position, nullptr, ignoreLocked );
|
|
}
|
|
|
|
QgsComposerItem* QgsComposition::composerItemAt( QPointF position, const QgsComposerItem* belowItem, const bool ignoreLocked ) const
|
|
{
|
|
//get a list of items which intersect the specified position, in descending z order
|
|
QList<QGraphicsItem*> itemList;
|
|
itemList = items( position, Qt::IntersectsItemShape, Qt::DescendingOrder );
|
|
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
|
|
|
|
bool foundBelowItem = false;
|
|
for ( ; itemIt != itemList.end(); ++itemIt )
|
|
{
|
|
QgsComposerItem* composerItem = dynamic_cast<QgsComposerItem *>( *itemIt );
|
|
QgsPaperItem* paperItem = dynamic_cast<QgsPaperItem*>( *itemIt );
|
|
if ( composerItem && !paperItem )
|
|
{
|
|
// If we are not checking for a an item below a specified item, or if we've
|
|
// already found that item, then we've found our target
|
|
if (( ! belowItem || foundBelowItem ) && ( !ignoreLocked || !composerItem->positionLock() ) )
|
|
{
|
|
return composerItem;
|
|
}
|
|
else
|
|
{
|
|
if ( composerItem == belowItem )
|
|
{
|
|
//Target item is next in list
|
|
foundBelowItem = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int QgsComposition::pageNumberAt( QPointF position ) const
|
|
{
|
|
return position.y() / ( paperHeight() + spaceBetweenPages() );
|
|
}
|
|
|
|
int QgsComposition::itemPageNumber( const QgsComposerItem* item ) const
|
|
{
|
|
return pageNumberAt( QPointF( item->pos().x(), item->pos().y() ) );
|
|
}
|
|
|
|
QList<QgsComposerItem*> QgsComposition::selectedComposerItems( const bool includeLockedItems )
|
|
{
|
|
QList<QgsComposerItem*> composerItemList;
|
|
|
|
QList<QGraphicsItem *> graphicsItemList = selectedItems();
|
|
QList<QGraphicsItem *>::iterator itemIter = graphicsItemList.begin();
|
|
|
|
for ( ; itemIter != graphicsItemList.end(); ++itemIter )
|
|
{
|
|
QgsComposerItem* composerItem = dynamic_cast<QgsComposerItem *>( *itemIter );
|
|
if ( composerItem && ( includeLockedItems || !composerItem->positionLock() ) )
|
|
{
|
|
composerItemList.push_back( composerItem );
|
|
}
|
|
}
|
|
|
|
return composerItemList;
|
|
}
|
|
|
|
QList<const QgsComposerMap*> QgsComposition::composerMapItems() const
|
|
{
|
|
QList<const QgsComposerMap*> resultList;
|
|
|
|
QList<QGraphicsItem *> itemList = items();
|
|
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
|
|
for ( ; itemIt != itemList.end(); ++itemIt )
|
|
{
|
|
const QgsComposerMap* composerMap = dynamic_cast<const QgsComposerMap *>( *itemIt );
|
|
if ( composerMap )
|
|
{
|
|
resultList.push_back( composerMap );
|
|
}
|
|
}
|
|
|
|
return resultList;
|
|
}
|
|
|
|
const QgsComposerMap* QgsComposition::getComposerMapById( const int id ) const
|
|
{
|
|
QList<QGraphicsItem *> itemList = items();
|
|
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
|
|
for ( ; itemIt != itemList.end(); ++itemIt )
|
|
{
|
|
const QgsComposerMap* composerMap = dynamic_cast<const QgsComposerMap *>( *itemIt );
|
|
if ( composerMap )
|
|
{
|
|
if ( composerMap->id() == id )
|
|
{
|
|
return composerMap;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const QgsComposerItem* QgsComposition::getComposerItemById( const QString& theId ) const
|
|
{
|
|
QList<QGraphicsItem *> itemList = items();
|
|
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
|
|
for ( ; itemIt != itemList.end(); ++itemIt )
|
|
{
|
|
const QgsComposerItem* mypItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
|
|
if ( mypItem )
|
|
{
|
|
if ( mypItem->id() == theId )
|
|
{
|
|
return mypItem;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
#if 0
|
|
const QgsComposerItem* QgsComposition::getComposerItemByUuid( QString theUuid, bool inAllComposers ) const
|
|
{
|
|
//This does not work since it seems impossible to get the QgisApp::instance() from here... Is there a workaround ?
|
|
QSet<QgsComposer*> composers = QSet<QgsComposer*>();
|
|
|
|
if ( inAllComposers )
|
|
{
|
|
composers = QgisApp::instance()->printComposers();
|
|
}
|
|
else
|
|
{
|
|
composers.insert( this )
|
|
}
|
|
|
|
QSet<QgsComposer*>::const_iterator it = composers.constBegin();
|
|
for ( ; it != composers.constEnd(); ++it )
|
|
{
|
|
QList<QGraphicsItem *> itemList = ( *it )->items();
|
|
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
|
|
for ( ; itemIt != itemList.end(); ++itemIt )
|
|
{
|
|
const QgsComposerItem* mypItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
|
|
if ( mypItem )
|
|
{
|
|
if ( mypItem->uuid() == theUuid )
|
|
{
|
|
return mypItem;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
const QgsComposerItem* QgsComposition::getComposerItemByUuid( const QString& theUuid ) const
|
|
{
|
|
QList<QGraphicsItem *> itemList = items();
|
|
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
|
|
for ( ; itemIt != itemList.end(); ++itemIt )
|
|
{
|
|
const QgsComposerItem* mypItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
|
|
if ( mypItem )
|
|
{
|
|
if ( mypItem->uuid() == theUuid )
|
|
{
|
|
return mypItem;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void QgsComposition::setPrintResolution( const int dpi )
|
|
{
|
|
mPrintResolution = dpi;
|
|
emit printResolutionChanged();
|
|
mProject->setDirty( true );
|
|
}
|
|
|
|
QgsComposerMap* QgsComposition::referenceMap() const
|
|
{
|
|
// prefer explicitly set reference map
|
|
if ( QgsComposerMap* map = dynamic_cast< QgsComposerMap* >( const_cast< QgsComposerItem* >( getComposerItemByUuid( mWorldFileMapId ) ) ) )
|
|
return map;
|
|
|
|
// else try to find largest map
|
|
QList< const QgsComposerMap* > maps = composerMapItems();
|
|
const QgsComposerMap* largestMap = nullptr;
|
|
double largestMapArea = 0;
|
|
Q_FOREACH ( const QgsComposerMap* map, maps )
|
|
{
|
|
double area = map->rect().width() * map->rect().height();
|
|
if ( area > largestMapArea )
|
|
{
|
|
largestMapArea = area;
|
|
largestMap = map;
|
|
}
|
|
}
|
|
return const_cast< QgsComposerMap* >( largestMap );
|
|
}
|
|
|
|
void QgsComposition::setReferenceMap( QgsComposerMap* map )
|
|
{
|
|
mWorldFileMapId = map ? map->uuid() : QString();
|
|
mProject->setDirty( true );
|
|
}
|
|
|
|
void QgsComposition::setUseAdvancedEffects( const bool effectsEnabled )
|
|
{
|
|
mUseAdvancedEffects = effectsEnabled;
|
|
|
|
//toggle effects for all composer items
|
|
QList<QGraphicsItem*> itemList = items();
|
|
QList<QGraphicsItem*>::const_iterator itemIt = itemList.constBegin();
|
|
for ( ; itemIt != itemList.constEnd(); ++itemIt )
|
|
{
|
|
QgsComposerItem* composerItem = dynamic_cast<QgsComposerItem*>( *itemIt );
|
|
if ( composerItem )
|
|
{
|
|
composerItem->setEffectsEnabled( effectsEnabled );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool QgsComposition::writeXml( QDomElement& composerElem, QDomDocument& doc )
|
|
{
|
|
if ( composerElem.isNull() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QDomElement compositionElem = doc.createElement( QStringLiteral( "Composition" ) );
|
|
compositionElem.setAttribute( QStringLiteral( "paperWidth" ), QString::number( mPageWidth ) );
|
|
compositionElem.setAttribute( QStringLiteral( "paperHeight" ), QString::number( mPageHeight ) );
|
|
compositionElem.setAttribute( QStringLiteral( "numPages" ), mPages.size() );
|
|
|
|
QDomElement pageStyleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mPageStyleSymbol, doc );
|
|
compositionElem.appendChild( pageStyleElem );
|
|
|
|
//snapping
|
|
if ( mSnapToGrid )
|
|
{
|
|
compositionElem.setAttribute( QStringLiteral( "snapping" ), QStringLiteral( "1" ) );
|
|
}
|
|
else
|
|
{
|
|
compositionElem.setAttribute( QStringLiteral( "snapping" ), QStringLiteral( "0" ) );
|
|
}
|
|
if ( mGridVisible )
|
|
{
|
|
compositionElem.setAttribute( QStringLiteral( "gridVisible" ), QStringLiteral( "1" ) );
|
|
}
|
|
else
|
|
{
|
|
compositionElem.setAttribute( QStringLiteral( "gridVisible" ), QStringLiteral( "0" ) );
|
|
}
|
|
compositionElem.setAttribute( QStringLiteral( "snapGridResolution" ), QString::number( mSnapGridResolution ) );
|
|
compositionElem.setAttribute( QStringLiteral( "snapGridOffsetX" ), QString::number( mSnapGridOffsetX ) );
|
|
compositionElem.setAttribute( QStringLiteral( "snapGridOffsetY" ), QString::number( mSnapGridOffsetY ) );
|
|
|
|
compositionElem.setAttribute( QStringLiteral( "showPages" ), mPagesVisible );
|
|
|
|
//custom snap lines
|
|
QList< QGraphicsLineItem* >::const_iterator snapLineIt = mSnapLines.constBegin();
|
|
for ( ; snapLineIt != mSnapLines.constEnd(); ++snapLineIt )
|
|
{
|
|
QDomElement snapLineElem = doc.createElement( QStringLiteral( "SnapLine" ) );
|
|
QLineF line = ( *snapLineIt )->line();
|
|
snapLineElem.setAttribute( QStringLiteral( "x1" ), QString::number( line.x1() ) );
|
|
snapLineElem.setAttribute( QStringLiteral( "y1" ), QString::number( line.y1() ) );
|
|
snapLineElem.setAttribute( QStringLiteral( "x2" ), QString::number( line.x2() ) );
|
|
snapLineElem.setAttribute( QStringLiteral( "y2" ), QString::number( line.y2() ) );
|
|
compositionElem.appendChild( snapLineElem );
|
|
}
|
|
|
|
compositionElem.setAttribute( QStringLiteral( "printResolution" ), mPrintResolution );
|
|
compositionElem.setAttribute( QStringLiteral( "printAsRaster" ), mPrintAsRaster );
|
|
|
|
compositionElem.setAttribute( QStringLiteral( "generateWorldFile" ), mGenerateWorldFile ? 1 : 0 );
|
|
compositionElem.setAttribute( QStringLiteral( "worldFileMap" ), mWorldFileMapId );
|
|
|
|
compositionElem.setAttribute( QStringLiteral( "alignmentSnap" ), mAlignmentSnap ? 1 : 0 );
|
|
compositionElem.setAttribute( QStringLiteral( "guidesVisible" ), mGuidesVisible ? 1 : 0 );
|
|
compositionElem.setAttribute( QStringLiteral( "smartGuides" ), mSmartGuides ? 1 : 0 );
|
|
compositionElem.setAttribute( QStringLiteral( "snapTolerancePixels" ), mSnapTolerance );
|
|
|
|
compositionElem.setAttribute( QStringLiteral( "resizeToContentsMarginTop" ), mResizeToContentsMarginTop );
|
|
compositionElem.setAttribute( QStringLiteral( "resizeToContentsMarginRight" ), mResizeToContentsMarginRight );
|
|
compositionElem.setAttribute( QStringLiteral( "resizeToContentsMarginBottom" ), mResizeToContentsMarginBottom );
|
|
compositionElem.setAttribute( QStringLiteral( "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();
|
|
for ( ; itemIt != itemList.constEnd(); ++itemIt )
|
|
{
|
|
const QgsComposerItem* composerItem = dynamic_cast<const QgsComposerItem*>( *itemIt );
|
|
if ( composerItem )
|
|
{
|
|
if ( composerItem->type() != QgsComposerItem::ComposerPaper && composerItem->type() != QgsComposerItem::ComposerFrame )
|
|
{
|
|
composerItem->writeXml( compositionElem, doc );
|
|
}
|
|
}
|
|
}
|
|
|
|
//save multiframes
|
|
QSet<QgsComposerMultiFrame*>::const_iterator multiFrameIt = mMultiFrames.constBegin();
|
|
for ( ; multiFrameIt != mMultiFrames.constEnd(); ++multiFrameIt )
|
|
{
|
|
( *multiFrameIt )->writeXml( compositionElem, doc );
|
|
}
|
|
composerElem.appendChild( compositionElem );
|
|
|
|
//data defined properties
|
|
QgsComposerUtils::writeDataDefinedPropertyMap( compositionElem, doc, &mDataDefinedNames, &mDataDefinedProperties );
|
|
|
|
//custom properties
|
|
mCustomProperties.writeXml( compositionElem, doc );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QgsComposition::readXml( const QDomElement& compositionElem, const QDomDocument& doc )
|
|
{
|
|
Q_UNUSED( doc );
|
|
if ( compositionElem.isNull() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//create pages
|
|
bool widthConversionOk, heightConversionOk;
|
|
mPageWidth = compositionElem.attribute( QStringLiteral( "paperWidth" ) ).toDouble( &widthConversionOk );
|
|
mPageHeight = compositionElem.attribute( QStringLiteral( "paperHeight" ) ).toDouble( &heightConversionOk );
|
|
emit paperSizeChanged();
|
|
int numPages = compositionElem.attribute( QStringLiteral( "numPages" ), QStringLiteral( "1" ) ).toInt();
|
|
|
|
QDomElement pageStyleSymbolElem = compositionElem.firstChildElement( QStringLiteral( "symbol" ) );
|
|
if ( !pageStyleSymbolElem.isNull() )
|
|
{
|
|
delete mPageStyleSymbol;
|
|
mPageStyleSymbol = QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( pageStyleSymbolElem );
|
|
}
|
|
|
|
if ( widthConversionOk && heightConversionOk )
|
|
{
|
|
removePaperItems();
|
|
for ( int i = 0; i < numPages; ++i )
|
|
{
|
|
addPaperItem();
|
|
}
|
|
}
|
|
|
|
//snapping
|
|
mSnapToGrid = compositionElem.attribute( QStringLiteral( "snapping" ), QStringLiteral( "0" ) ).toInt() == 0 ? false : true;
|
|
mGridVisible = compositionElem.attribute( QStringLiteral( "gridVisible" ), QStringLiteral( "0" ) ).toInt() == 0 ? false : true;
|
|
|
|
mSnapGridResolution = compositionElem.attribute( QStringLiteral( "snapGridResolution" ) ).toDouble();
|
|
mSnapGridOffsetX = compositionElem.attribute( QStringLiteral( "snapGridOffsetX" ) ).toDouble();
|
|
mSnapGridOffsetY = compositionElem.attribute( QStringLiteral( "snapGridOffsetY" ) ).toDouble();
|
|
|
|
mAlignmentSnap = compositionElem.attribute( QStringLiteral( "alignmentSnap" ), QStringLiteral( "1" ) ).toInt() == 0 ? false : true;
|
|
mGuidesVisible = compositionElem.attribute( QStringLiteral( "guidesVisible" ), QStringLiteral( "1" ) ).toInt() == 0 ? false : true;
|
|
mSmartGuides = compositionElem.attribute( QStringLiteral( "smartGuides" ), QStringLiteral( "1" ) ).toInt() == 0 ? false : true;
|
|
mSnapTolerance = compositionElem.attribute( QStringLiteral( "snapTolerancePixels" ), QStringLiteral( "10" ) ).toInt();
|
|
|
|
mResizeToContentsMarginTop = compositionElem.attribute( QStringLiteral( "resizeToContentsMarginTop" ), QStringLiteral( "0" ) ).toDouble();
|
|
mResizeToContentsMarginRight = compositionElem.attribute( QStringLiteral( "resizeToContentsMarginRight" ), QStringLiteral( "0" ) ).toDouble();
|
|
mResizeToContentsMarginBottom = compositionElem.attribute( QStringLiteral( "resizeToContentsMarginBottom" ), QStringLiteral( "0" ) ).toDouble();
|
|
mResizeToContentsMarginLeft = compositionElem.attribute( QStringLiteral( "resizeToContentsMarginLeft" ), QStringLiteral( "0" ) ).toDouble();
|
|
|
|
//custom snap lines
|
|
QDomNodeList snapLineNodes = compositionElem.elementsByTagName( QStringLiteral( "SnapLine" ) );
|
|
for ( int i = 0; i < snapLineNodes.size(); ++i )
|
|
{
|
|
QDomElement snapLineElem = snapLineNodes.at( i ).toElement();
|
|
QGraphicsLineItem* snapItem = addSnapLine();
|
|
double x1 = snapLineElem.attribute( QStringLiteral( "x1" ) ).toDouble();
|
|
double y1 = snapLineElem.attribute( QStringLiteral( "y1" ) ).toDouble();
|
|
double x2 = snapLineElem.attribute( QStringLiteral( "x2" ) ).toDouble();
|
|
double y2 = snapLineElem.attribute( QStringLiteral( "y2" ) ).toDouble();
|
|
snapItem->setLine( x1, y1, x2, y2 );
|
|
}
|
|
|
|
mPagesVisible = ( compositionElem.attribute( QStringLiteral( "showPages" ), QStringLiteral( "1" ) ) != QLatin1String( "0" ) );
|
|
mPrintAsRaster = compositionElem.attribute( QStringLiteral( "printAsRaster" ) ).toInt();
|
|
mPrintResolution = compositionElem.attribute( QStringLiteral( "printResolution" ), QStringLiteral( "300" ) ).toInt();
|
|
|
|
mGenerateWorldFile = compositionElem.attribute( QStringLiteral( "generateWorldFile" ), QStringLiteral( "0" ) ).toInt() == 1 ? true : false;
|
|
mWorldFileMapId = compositionElem.attribute( QStringLiteral( "worldFileMap" ) );
|
|
|
|
//data defined properties
|
|
QgsComposerUtils::readDataDefinedPropertyMap( compositionElem, &mDataDefinedNames, &mDataDefinedProperties );
|
|
|
|
//custom properties
|
|
mCustomProperties.readXml( compositionElem );
|
|
|
|
updatePaperItems();
|
|
|
|
updateBounds();
|
|
|
|
emit variablesChanged();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QgsComposition::loadFromTemplate( const QDomDocument& doc, QMap<QString, QString>* substitutionMap, bool addUndoCommands, const bool clearComposition )
|
|
{
|
|
if ( clearComposition )
|
|
{
|
|
deleteAndRemoveMultiFrames();
|
|
|
|
//delete all non paper items and emit itemRemoved signal
|
|
QList<QGraphicsItem *> itemList = items();
|
|
QList<QGraphicsItem *>::iterator itemIter = itemList.begin();
|
|
for ( ; itemIter != itemList.end(); ++itemIter )
|
|
{
|
|
QgsComposerItem* cItem = dynamic_cast<QgsComposerItem*>( *itemIter );
|
|
QgsPaperItem* pItem = dynamic_cast<QgsPaperItem*>( *itemIter );
|
|
if ( cItem && !pItem )
|
|
{
|
|
removeItem( cItem );
|
|
emit itemRemoved( cItem );
|
|
delete cItem;
|
|
}
|
|
}
|
|
mItemsModel->clear();
|
|
|
|
removePaperItems();
|
|
mUndoStack->clear();
|
|
}
|
|
|
|
QDomDocument importDoc;
|
|
if ( substitutionMap )
|
|
{
|
|
QString xmlString = doc.toString();
|
|
QMap<QString, QString>::const_iterator sIt = substitutionMap->constBegin();
|
|
for ( ; sIt != substitutionMap->constEnd(); ++sIt )
|
|
{
|
|
xmlString = xmlString.replace( '[' + sIt.key() + ']', encodeStringForXml( sIt.value() ) );
|
|
}
|
|
|
|
QString errorMsg;
|
|
int errorLine, errorColumn;
|
|
if ( !importDoc.setContent( xmlString, &errorMsg, &errorLine, &errorColumn ) )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
importDoc = doc;
|
|
}
|
|
|
|
//read general settings
|
|
QDomElement atlasElem;
|
|
if ( clearComposition )
|
|
{
|
|
QDomElement compositionElem = importDoc.documentElement().firstChildElement( QStringLiteral( "Composition" ) );
|
|
if ( compositionElem.isNull() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool ok = readXml( compositionElem, importDoc );
|
|
if ( !ok )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// read atlas parameters - must be done before adding items
|
|
atlasElem = importDoc.documentElement().firstChildElement( QStringLiteral( "Atlas" ) );
|
|
atlasComposition().readXml( atlasElem, importDoc );
|
|
}
|
|
|
|
// remove all uuid attributes since we don't want duplicates UUIDS
|
|
QDomNodeList composerItemsNodes = importDoc.elementsByTagName( QStringLiteral( "ComposerItem" ) );
|
|
for ( int i = 0; i < composerItemsNodes.count(); ++i )
|
|
{
|
|
QDomNode composerItemNode = composerItemsNodes.at( i );
|
|
if ( composerItemNode.isElement() )
|
|
{
|
|
composerItemNode.toElement().setAttribute( QStringLiteral( "templateUuid" ), composerItemNode.toElement().attribute( QStringLiteral( "uuid" ) ) );
|
|
composerItemNode.toElement().removeAttribute( QStringLiteral( "uuid" ) );
|
|
}
|
|
}
|
|
|
|
//addItemsFromXML
|
|
addItemsFromXml( importDoc.documentElement(), importDoc, nullptr, addUndoCommands, nullptr );
|
|
|
|
return true;
|
|
}
|
|
|
|
QPointF QgsComposition::minPointFromXml( const QDomElement& elem ) const
|
|
{
|
|
double minX = std::numeric_limits<double>::max();
|
|
double minY = std::numeric_limits<double>::max();
|
|
QDomNodeList composerItemList = elem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
|
|
for ( int i = 0; i < composerItemList.size(); ++i )
|
|
{
|
|
QDomElement currentComposerItemElem = composerItemList.at( i ).toElement();
|
|
double x, y;
|
|
bool xOk, yOk;
|
|
x = currentComposerItemElem.attribute( QStringLiteral( "x" ) ).toDouble( &xOk );
|
|
y = currentComposerItemElem.attribute( QStringLiteral( "y" ) ).toDouble( &yOk );
|
|
if ( !xOk || !yOk )
|
|
{
|
|
continue;
|
|
}
|
|
minX = qMin( minX, x );
|
|
minY = qMin( minY, y );
|
|
}
|
|
if ( minX < std::numeric_limits<double>::max() )
|
|
{
|
|
return QPointF( minX, minY );
|
|
}
|
|
else
|
|
{
|
|
return QPointF( 0, 0 );
|
|
}
|
|
}
|
|
|
|
void QgsComposition::addItemsFromXml( const QDomElement& elem, const QDomDocument& doc, QMap< QgsComposerMap*, int >* mapsToRestore,
|
|
bool addUndoCommands, QPointF* pos, bool pasteInPlace )
|
|
{
|
|
QPointF* pasteInPlacePt = nullptr;
|
|
|
|
//if we are adding items to a composition which already contains items, we need to make sure
|
|
//these items are placed at the top of the composition and that zValues are not duplicated
|
|
//so, calculate an offset which needs to be added to the zValue of created items
|
|
int zOrderOffset = mItemsModel->zOrderListSize();
|
|
|
|
QPointF pasteShiftPos;
|
|
QgsComposerItem* lastPastedItem = nullptr;
|
|
if ( pos )
|
|
{
|
|
//If we are placing items relative to a certain point, then calculate how much we need
|
|
//to shift the items by so that they are placed at this point
|
|
//First, calculate the minimum position from the xml
|
|
QPointF minItemPos = minPointFromXml( elem );
|
|
//next, calculate how much each item needs to be shifted from its original position
|
|
//so that it's placed at the correct relative position
|
|
pasteShiftPos = *pos - minItemPos;
|
|
|
|
//since we are pasting items, clear the existing selection
|
|
setAllDeselected();
|
|
|
|
if ( pasteInPlace )
|
|
{
|
|
pasteInPlacePt = new QPointF( 0, pageNumberAt( *pos ) * ( mPageHeight + mSpaceBetweenPages ) );
|
|
}
|
|
}
|
|
QDomNodeList composerLabelList = elem.elementsByTagName( QStringLiteral( "ComposerLabel" ) );
|
|
for ( int i = 0; i < composerLabelList.size(); ++i )
|
|
{
|
|
QDomElement currentComposerLabelElem = composerLabelList.at( i ).toElement();
|
|
QgsComposerLabel* newLabel = new QgsComposerLabel( this );
|
|
newLabel->readXml( currentComposerLabelElem, doc );
|
|
if ( pos )
|
|
{
|
|
if ( pasteInPlacePt )
|
|
{
|
|
newLabel->setItemPosition( newLabel->pos().x(), fmod( newLabel->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
|
|
newLabel->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
|
|
}
|
|
else
|
|
{
|
|
newLabel->move( pasteShiftPos.x(), pasteShiftPos.y() );
|
|
}
|
|
newLabel->setSelected( true );
|
|
lastPastedItem = newLabel;
|
|
}
|
|
addComposerLabel( newLabel );
|
|
newLabel->setZValue( newLabel->zValue() + zOrderOffset );
|
|
if ( addUndoCommands )
|
|
{
|
|
pushAddRemoveCommand( newLabel, tr( "Label added" ) );
|
|
}
|
|
}
|
|
// map
|
|
QDomNodeList composerMapList = elem.elementsByTagName( QStringLiteral( "ComposerMap" ) );
|
|
for ( int i = 0; i < composerMapList.size(); ++i )
|
|
{
|
|
QDomElement currentComposerMapElem = composerMapList.at( i ).toElement();
|
|
QgsComposerMap* newMap = new QgsComposerMap( this );
|
|
|
|
if ( mapsToRestore )
|
|
{
|
|
newMap->setUpdatesEnabled( false );
|
|
}
|
|
|
|
newMap->readXml( currentComposerMapElem, doc );
|
|
newMap->assignFreeId();
|
|
|
|
if ( mapsToRestore )
|
|
{
|
|
mapsToRestore->insert( newMap, static_cast< int >( newMap->previewMode() ) );
|
|
newMap->setPreviewMode( QgsComposerMap::Rectangle );
|
|
newMap->setUpdatesEnabled( true );
|
|
}
|
|
addComposerMap( newMap, false );
|
|
newMap->setZValue( newMap->zValue() + zOrderOffset );
|
|
if ( pos )
|
|
{
|
|
if ( pasteInPlace )
|
|
{
|
|
newMap->setItemPosition( newMap->pos().x(), fmod( newMap->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
|
|
newMap->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
|
|
}
|
|
else
|
|
{
|
|
newMap->move( pasteShiftPos.x(), pasteShiftPos.y() );
|
|
}
|
|
newMap->setSelected( true );
|
|
lastPastedItem = newMap;
|
|
}
|
|
|
|
if ( addUndoCommands )
|
|
{
|
|
pushAddRemoveCommand( newMap, tr( "Map added" ) );
|
|
}
|
|
}
|
|
//now that all map items have been created, re-connect overview map signals
|
|
QList<QgsComposerMap*> maps;
|
|
composerItems( maps );
|
|
for ( QList<QgsComposerMap*>::iterator mit = maps.begin(); mit != maps.end(); ++mit )
|
|
{
|
|
QgsComposerMap* map = ( *mit );
|
|
if ( map )
|
|
{
|
|
QList<QgsComposerMapOverview* > overviews = map->overviews()->asList();
|
|
QList<QgsComposerMapOverview* >::iterator overviewIt = overviews.begin();
|
|
for ( ; overviewIt != overviews.end(); ++overviewIt )
|
|
{
|
|
( *overviewIt )->connectSignals();
|
|
}
|
|
}
|
|
}
|
|
|
|
// arrow
|
|
QDomNodeList composerArrowList = elem.elementsByTagName( QStringLiteral( "ComposerArrow" ) );
|
|
for ( int i = 0; i < composerArrowList.size(); ++i )
|
|
{
|
|
QDomElement currentComposerArrowElem = composerArrowList.at( i ).toElement();
|
|
QgsComposerArrow* newArrow = new QgsComposerArrow( this );
|
|
newArrow->readXml( currentComposerArrowElem, doc );
|
|
if ( pos )
|
|
{
|
|
if ( pasteInPlace )
|
|
{
|
|
newArrow->setItemPosition( newArrow->pos().x(), fmod( newArrow->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
|
|
newArrow->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
|
|
}
|
|
else
|
|
{
|
|
newArrow->move( pasteShiftPos.x(), pasteShiftPos.y() );
|
|
}
|
|
newArrow->setSelected( true );
|
|
lastPastedItem = newArrow;
|
|
}
|
|
addComposerArrow( newArrow );
|
|
newArrow->setZValue( newArrow->zValue() + zOrderOffset );
|
|
if ( addUndoCommands )
|
|
{
|
|
pushAddRemoveCommand( newArrow, tr( "Arrow added" ) );
|
|
}
|
|
}
|
|
// scalebar
|
|
QDomNodeList composerScaleBarList = elem.elementsByTagName( QStringLiteral( "ComposerScaleBar" ) );
|
|
for ( int i = 0; i < composerScaleBarList.size(); ++i )
|
|
{
|
|
QDomElement currentComposerScaleBarElem = composerScaleBarList.at( i ).toElement();
|
|
QgsComposerScaleBar* newScaleBar = new QgsComposerScaleBar( this );
|
|
newScaleBar->readXml( currentComposerScaleBarElem, doc );
|
|
if ( pos )
|
|
{
|
|
if ( pasteInPlace )
|
|
{
|
|
newScaleBar->setItemPosition( newScaleBar->pos().x(), fmod( newScaleBar->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
|
|
newScaleBar->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
|
|
}
|
|
else
|
|
{
|
|
newScaleBar->move( pasteShiftPos.x(), pasteShiftPos.y() );
|
|
}
|
|
newScaleBar->setSelected( true );
|
|
lastPastedItem = newScaleBar;
|
|
}
|
|
addComposerScaleBar( newScaleBar );
|
|
newScaleBar->setZValue( newScaleBar->zValue() + zOrderOffset );
|
|
if ( addUndoCommands )
|
|
{
|
|
pushAddRemoveCommand( newScaleBar, tr( "Scale bar added" ) );
|
|
}
|
|
}
|
|
// shape
|
|
QDomNodeList composerShapeList = elem.elementsByTagName( QStringLiteral( "ComposerShape" ) );
|
|
for ( int i = 0; i < composerShapeList.size(); ++i )
|
|
{
|
|
QDomElement currentComposerShapeElem = composerShapeList.at( i ).toElement();
|
|
QgsComposerShape* newShape = new QgsComposerShape( this );
|
|
newShape->readXml( currentComposerShapeElem, doc );
|
|
//new shapes should default to symbol v2
|
|
newShape->setUseSymbol( true );
|
|
if ( pos )
|
|
{
|
|
if ( pasteInPlace )
|
|
{
|
|
newShape->setItemPosition( newShape->pos().x(), fmod( newShape->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
|
|
newShape->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
|
|
}
|
|
else
|
|
{
|
|
newShape->move( pasteShiftPos.x(), pasteShiftPos.y() );
|
|
}
|
|
newShape->setSelected( true );
|
|
lastPastedItem = newShape;
|
|
}
|
|
addComposerShape( newShape );
|
|
newShape->setZValue( newShape->zValue() + zOrderOffset );
|
|
if ( addUndoCommands )
|
|
{
|
|
pushAddRemoveCommand( newShape, tr( "Shape added" ) );
|
|
}
|
|
}
|
|
|
|
// polygon
|
|
QDomNodeList composerPolygonList = elem.elementsByTagName( QStringLiteral( "ComposerPolygon" ) );
|
|
for ( int i = 0; i < composerPolygonList.size(); ++i )
|
|
{
|
|
QDomElement currentComposerPolygonElem = composerPolygonList.at( i ).toElement();
|
|
QgsComposerPolygon* newPolygon = new QgsComposerPolygon( this );
|
|
newPolygon->readXml( currentComposerPolygonElem, doc );
|
|
|
|
if ( pos )
|
|
{
|
|
if ( pasteInPlace )
|
|
{
|
|
newPolygon->setItemPosition( newPolygon->pos().x(), fmod( newPolygon->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
|
|
newPolygon->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
|
|
}
|
|
else
|
|
{
|
|
newPolygon->move( pasteShiftPos.x(), pasteShiftPos.y() );
|
|
}
|
|
newPolygon->setSelected( true );
|
|
lastPastedItem = newPolygon;
|
|
}
|
|
|
|
addComposerPolygon( newPolygon );
|
|
newPolygon->setZValue( newPolygon->zValue() + zOrderOffset );
|
|
if ( addUndoCommands )
|
|
{
|
|
pushAddRemoveCommand( newPolygon, tr( "Polygon added" ) );
|
|
}
|
|
}
|
|
|
|
// polyline
|
|
QDomNodeList addComposerPolylineList = elem.elementsByTagName( QStringLiteral( "ComposerPolyline" ) );
|
|
for ( int i = 0; i < addComposerPolylineList.size(); ++i )
|
|
{
|
|
QDomElement currentComposerPolylineElem = addComposerPolylineList.at( i ).toElement();
|
|
QgsComposerPolyline* newPolyline = new QgsComposerPolyline( this );
|
|
newPolyline->readXml( currentComposerPolylineElem, doc );
|
|
|
|
if ( pos )
|
|
{
|
|
if ( pasteInPlace )
|
|
{
|
|
newPolyline->setItemPosition( newPolyline->pos().x(), fmod( newPolyline->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
|
|
newPolyline->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
|
|
}
|
|
else
|
|
{
|
|
newPolyline->move( pasteShiftPos.x(), pasteShiftPos.y() );
|
|
}
|
|
newPolyline->setSelected( true );
|
|
lastPastedItem = newPolyline;
|
|
}
|
|
|
|
addComposerPolyline( newPolyline );
|
|
newPolyline->setZValue( newPolyline->zValue() + zOrderOffset );
|
|
if ( addUndoCommands )
|
|
{
|
|
pushAddRemoveCommand( newPolyline, tr( "Polyline added" ) );
|
|
}
|
|
}
|
|
|
|
// picture
|
|
QDomNodeList composerPictureList = elem.elementsByTagName( QStringLiteral( "ComposerPicture" ) );
|
|
for ( int i = 0; i < composerPictureList.size(); ++i )
|
|
{
|
|
QDomElement currentComposerPictureElem = composerPictureList.at( i ).toElement();
|
|
QgsComposerPicture* newPicture = new QgsComposerPicture( this );
|
|
newPicture->readXml( currentComposerPictureElem, doc );
|
|
if ( pos )
|
|
{
|
|
if ( pasteInPlace )
|
|
{
|
|
newPicture->setItemPosition( newPicture->pos().x(), fmod( newPicture->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
|
|
newPicture->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
|
|
}
|
|
else
|
|
{
|
|
newPicture->move( pasteShiftPos.x(), pasteShiftPos.y() );
|
|
}
|
|
newPicture->setSelected( true );
|
|
lastPastedItem = newPicture;
|
|
}
|
|
addComposerPicture( newPicture );
|
|
newPicture->setZValue( newPicture->zValue() + zOrderOffset );
|
|
if ( addUndoCommands )
|
|
{
|
|
pushAddRemoveCommand( newPicture, tr( "Picture added" ) );
|
|
}
|
|
}
|
|
// legend
|
|
QDomNodeList composerLegendList = elem.elementsByTagName( QStringLiteral( "ComposerLegend" ) );
|
|
for ( int i = 0; i < composerLegendList.size(); ++i )
|
|
{
|
|
QDomElement currentComposerLegendElem = composerLegendList.at( i ).toElement();
|
|
QgsComposerLegend* newLegend = new QgsComposerLegend( this );
|
|
newLegend->readXml( currentComposerLegendElem, doc );
|
|
if ( pos )
|
|
{
|
|
if ( pasteInPlace )
|
|
{
|
|
newLegend->setItemPosition( newLegend->pos().x(), fmod( newLegend->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
|
|
newLegend->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
|
|
}
|
|
else
|
|
{
|
|
newLegend->move( pasteShiftPos.x(), pasteShiftPos.y() );
|
|
}
|
|
newLegend->setSelected( true );
|
|
lastPastedItem = newLegend;
|
|
}
|
|
addComposerLegend( newLegend );
|
|
newLegend->setZValue( newLegend->zValue() + zOrderOffset );
|
|
if ( addUndoCommands )
|
|
{
|
|
pushAddRemoveCommand( newLegend, tr( "Legend added" ) );
|
|
}
|
|
}
|
|
|
|
// html
|
|
//TODO - fix this. pasting multiframe frame items has no effect
|
|
QDomNodeList composerHtmlList = elem.elementsByTagName( QStringLiteral( "ComposerHtml" ) );
|
|
for ( int i = 0; i < composerHtmlList.size(); ++i )
|
|
{
|
|
QDomElement currentHtmlElem = composerHtmlList.at( i ).toElement();
|
|
QgsComposerHtml* newHtml = new QgsComposerHtml( this, false );
|
|
newHtml->readXml( currentHtmlElem, doc );
|
|
newHtml->setCreateUndoCommands( true );
|
|
this->addMultiFrame( newHtml );
|
|
|
|
//offset z values for frames
|
|
//TODO - fix this after fixing html item paste
|
|
/*for ( int frameIdx = 0; frameIdx < newHtml->frameCount(); ++frameIdx )
|
|
{
|
|
QgsComposerFrame * frame = newHtml->frame( frameIdx );
|
|
frame->setZValue( frame->zValue() + zOrderOffset );
|
|
}*/
|
|
}
|
|
QDomNodeList composerAttributeTableV2List = elem.elementsByTagName( QStringLiteral( "ComposerAttributeTableV2" ) );
|
|
for ( int i = 0; i < composerAttributeTableV2List.size(); ++i )
|
|
{
|
|
QDomElement currentTableElem = composerAttributeTableV2List.at( i ).toElement();
|
|
QgsComposerAttributeTableV2* newTable = new QgsComposerAttributeTableV2( this, false );
|
|
newTable->readXml( currentTableElem, doc );
|
|
newTable->setCreateUndoCommands( true );
|
|
this->addMultiFrame( newTable );
|
|
|
|
//offset z values for frames
|
|
//TODO - fix this after fixing html item paste
|
|
/*for ( int frameIdx = 0; frameIdx < newHtml->frameCount(); ++frameIdx )
|
|
{
|
|
QgsComposerFrame * frame = newHtml->frame( frameIdx );
|
|
frame->setZValue( frame->zValue() + zOrderOffset );
|
|
}*/
|
|
}
|
|
|
|
// groups (must be last as it references uuids of above items)
|
|
//TODO - pasted groups lose group properties, since the uuids of group items
|
|
//changes
|
|
QDomNodeList groupList = elem.elementsByTagName( QStringLiteral( "ComposerItemGroup" ) );
|
|
for ( int i = 0; i < groupList.size(); ++i )
|
|
{
|
|
QDomElement groupElem = groupList.at( i ).toElement();
|
|
QgsComposerItemGroup *newGroup = new QgsComposerItemGroup( this );
|
|
newGroup->readXml( groupElem, doc );
|
|
addItem( newGroup );
|
|
if ( addUndoCommands )
|
|
{
|
|
pushAddRemoveCommand( newGroup, tr( "Group added" ) );
|
|
}
|
|
}
|
|
|
|
//Since this function adds items grouped by type, and each item is added to end of
|
|
//z order list in turn, it will now be inconsistent with the actual order of items in the scene.
|
|
//Make sure z order list matches the actual order of items in the scene.
|
|
mItemsModel->rebuildZList();
|
|
|
|
if ( lastPastedItem )
|
|
{
|
|
emit selectedItemChanged( lastPastedItem );
|
|
}
|
|
|
|
delete pasteInPlacePt;
|
|
pasteInPlacePt = nullptr;
|
|
|
|
}
|
|
|
|
void QgsComposition::addItemToZList( QgsComposerItem* item )
|
|
{
|
|
if ( !item )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//model handles changes to z list
|
|
mItemsModel->addItemAtTop( item );
|
|
}
|
|
|
|
void QgsComposition::removeItemFromZList( QgsComposerItem* item )
|
|
{
|
|
if ( !item )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//model handles changes to z list
|
|
mItemsModel->removeItem( item );
|
|
}
|
|
|
|
void QgsComposition::raiseSelectedItems()
|
|
{
|
|
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
|
|
QList<QgsComposerItem*>::iterator it = selectedItems.begin();
|
|
bool itemsRaised = false;
|
|
for ( ; it != selectedItems.end(); ++it )
|
|
{
|
|
itemsRaised = itemsRaised | raiseItem( *it );
|
|
}
|
|
|
|
if ( !itemsRaised )
|
|
{
|
|
//no change
|
|
return;
|
|
}
|
|
|
|
//update all positions
|
|
updateZValues();
|
|
update();
|
|
}
|
|
|
|
bool QgsComposition::raiseItem( QgsComposerItem* item )
|
|
{
|
|
//model handles reordering items
|
|
return mItemsModel->reorderItemUp( item );
|
|
}
|
|
|
|
QgsComposerItem* QgsComposition::getComposerItemAbove( QgsComposerItem* item ) const
|
|
{
|
|
return mItemsModel->getComposerItemAbove( item );
|
|
}
|
|
|
|
QgsComposerItem* QgsComposition::getComposerItemBelow( QgsComposerItem* item ) const
|
|
{
|
|
return mItemsModel->getComposerItemBelow( item );
|
|
}
|
|
|
|
void QgsComposition::selectNextByZOrder( ZValueDirection direction )
|
|
{
|
|
QgsComposerItem* previousSelectedItem = nullptr;
|
|
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
|
|
if ( !selectedItems.isEmpty() )
|
|
{
|
|
previousSelectedItem = selectedItems.at( 0 );
|
|
}
|
|
|
|
if ( !previousSelectedItem )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//select item with target z value
|
|
QgsComposerItem* selectedItem = nullptr;
|
|
switch ( direction )
|
|
{
|
|
case QgsComposition::ZValueBelow:
|
|
selectedItem = getComposerItemBelow( previousSelectedItem );
|
|
break;
|
|
case QgsComposition::ZValueAbove:
|
|
selectedItem = getComposerItemAbove( previousSelectedItem );
|
|
break;
|
|
}
|
|
|
|
if ( !selectedItem )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//ok, found a good target item
|
|
setAllDeselected();
|
|
selectedItem->setSelected( true );
|
|
emit selectedItemChanged( selectedItem );
|
|
}
|
|
|
|
void QgsComposition::lowerSelectedItems()
|
|
{
|
|
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
|
|
QList<QgsComposerItem*>::iterator it = selectedItems.begin();
|
|
bool itemsLowered = false;
|
|
for ( ; it != selectedItems.end(); ++it )
|
|
{
|
|
itemsLowered = itemsLowered | lowerItem( *it );
|
|
}
|
|
|
|
if ( !itemsLowered )
|
|
{
|
|
//no change
|
|
return;
|
|
}
|
|
|
|
//update all positions
|
|
updateZValues();
|
|
update();
|
|
}
|
|
|
|
bool QgsComposition::lowerItem( QgsComposerItem* item )
|
|
{
|
|
//model handles reordering items
|
|
return mItemsModel->reorderItemDown( item );
|
|
}
|
|
|
|
void QgsComposition::moveSelectedItemsToTop()
|
|
{
|
|
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
|
|
QList<QgsComposerItem*>::iterator it = selectedItems.begin();
|
|
bool itemsRaised = false;
|
|
for ( ; it != selectedItems.end(); ++it )
|
|
{
|
|
itemsRaised = itemsRaised | moveItemToTop( *it );
|
|
}
|
|
|
|
if ( !itemsRaised )
|
|
{
|
|
//no change
|
|
return;
|
|
}
|
|
|
|
//update all positions
|
|
updateZValues();
|
|
update();
|
|
}
|
|
|
|
bool QgsComposition::moveItemToTop( QgsComposerItem* item )
|
|
{
|
|
//model handles reordering items
|
|
return mItemsModel->reorderItemToTop( item );
|
|
}
|
|
|
|
void QgsComposition::moveSelectedItemsToBottom()
|
|
{
|
|
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
|
|
QList<QgsComposerItem*>::iterator it = selectedItems.begin();
|
|
bool itemsLowered = false;
|
|
for ( ; it != selectedItems.end(); ++it )
|
|
{
|
|
itemsLowered = itemsLowered | moveItemToBottom( *it );
|
|
}
|
|
|
|
if ( !itemsLowered )
|
|
{
|
|
//no change
|
|
return;
|
|
}
|
|
|
|
//update all positions
|
|
updateZValues();
|
|
update();
|
|
}
|
|
|
|
bool QgsComposition::moveItemToBottom( QgsComposerItem* item )
|
|
{
|
|
//model handles reordering items
|
|
return mItemsModel->reorderItemToBottom( item );
|
|
}
|
|
|
|
void QgsComposition::alignSelectedItemsLeft()
|
|
{
|
|
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
|
|
if ( selectedItems.size() < 2 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QRectF selectedItemBBox;
|
|
if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
double minXCoordinate = selectedItemBBox.left();
|
|
|
|
//align items left to minimum x coordinate
|
|
QUndoCommand* parentCommand = new QUndoCommand( tr( "Aligned items left" ) );
|
|
QList<QgsComposerItem*>::iterator align_it = selectedItems.begin();
|
|
for ( ; align_it != selectedItems.end(); ++align_it )
|
|
{
|
|
QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
|
|
subcommand->savePreviousState();
|
|
( *align_it )->setPos( minXCoordinate, ( *align_it )->pos().y() );
|
|
subcommand->saveAfterState();
|
|
}
|
|
mUndoStack->push( parentCommand );
|
|
mProject->setDirty( true );
|
|
}
|
|
|
|
void QgsComposition::alignSelectedItemsHCenter()
|
|
{
|
|
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
|
|
if ( selectedItems.size() < 2 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QRectF selectedItemBBox;
|
|
if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
double averageXCoord = ( selectedItemBBox.left() + selectedItemBBox.right() ) / 2.0;
|
|
|
|
//place items
|
|
QUndoCommand* parentCommand = new QUndoCommand( tr( "Aligned items horizontal center" ) );
|
|
QList<QgsComposerItem*>::iterator align_it = selectedItems.begin();
|
|
for ( ; align_it != selectedItems.end(); ++align_it )
|
|
{
|
|
QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
|
|
subcommand->savePreviousState();
|
|
( *align_it )->setPos( averageXCoord - ( *align_it )->rect().width() / 2.0, ( *align_it )->pos().y() );
|
|
subcommand->saveAfterState();
|
|
}
|
|
mUndoStack->push( parentCommand );
|
|
mProject->setDirty( true );
|
|
}
|
|
|
|
void QgsComposition::alignSelectedItemsRight()
|
|
{
|
|
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
|
|
if ( selectedItems.size() < 2 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QRectF selectedItemBBox;
|
|
if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
double maxXCoordinate = selectedItemBBox.right();
|
|
|
|
//align items right to maximum x coordinate
|
|
QUndoCommand* parentCommand = new QUndoCommand( tr( "Aligned items right" ) );
|
|
QList<QgsComposerItem*>::iterator align_it = selectedItems.begin();
|
|
for ( ; align_it != selectedItems.end(); ++align_it )
|
|
{
|
|
QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
|
|
subcommand->savePreviousState();
|
|
( *align_it )->setPos( maxXCoordinate - ( *align_it )->rect().width(), ( *align_it )->pos().y() );
|
|
subcommand->saveAfterState();
|
|
}
|
|
mUndoStack->push( parentCommand );
|
|
mProject->setDirty( true );
|
|
}
|
|
|
|
void QgsComposition::alignSelectedItemsTop()
|
|
{
|
|
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
|
|
if ( selectedItems.size() < 2 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QRectF selectedItemBBox;
|
|
if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
double minYCoordinate = selectedItemBBox.top();
|
|
|
|
QUndoCommand* parentCommand = new QUndoCommand( tr( "Aligned items top" ) );
|
|
QList<QgsComposerItem*>::iterator align_it = selectedItems.begin();
|
|
for ( ; align_it != selectedItems.end(); ++align_it )
|
|
{
|
|
QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
|
|
subcommand->savePreviousState();
|
|
( *align_it )->setPos(( *align_it )->pos().x(), minYCoordinate );
|
|
subcommand->saveAfterState();
|
|
}
|
|
mUndoStack->push( parentCommand );
|
|
mProject->setDirty( true );
|
|
}
|
|
|
|
void QgsComposition::alignSelectedItemsVCenter()
|
|
{
|
|
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
|
|
if ( selectedItems.size() < 2 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QRectF selectedItemBBox;
|
|
if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
double averageYCoord = ( selectedItemBBox.top() + selectedItemBBox.bottom() ) / 2.0;
|
|
QUndoCommand* parentCommand = new QUndoCommand( tr( "Aligned items vertical center" ) );
|
|
QList<QgsComposerItem*>::iterator align_it = selectedItems.begin();
|
|
for ( ; align_it != selectedItems.end(); ++align_it )
|
|
{
|
|
QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
|
|
subcommand->savePreviousState();
|
|
( *align_it )->setPos(( *align_it )->pos().x(), averageYCoord - ( *align_it )->rect().height() / 2 );
|
|
subcommand->saveAfterState();
|
|
}
|
|
mUndoStack->push( parentCommand );
|
|
mProject->setDirty( true );
|
|
}
|
|
|
|
void QgsComposition::alignSelectedItemsBottom()
|
|
{
|
|
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
|
|
if ( selectedItems.size() < 2 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QRectF selectedItemBBox;
|
|
if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
double maxYCoord = selectedItemBBox.bottom();
|
|
QUndoCommand* parentCommand = new QUndoCommand( tr( "Aligned items bottom" ) );
|
|
QList<QgsComposerItem*>::iterator align_it = selectedItems.begin();
|
|
for ( ; align_it != selectedItems.end(); ++align_it )
|
|
{
|
|
QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
|
|
subcommand->savePreviousState();
|
|
( *align_it )->setPos(( *align_it )->pos().x(), maxYCoord - ( *align_it )->rect().height() );
|
|
subcommand->saveAfterState();
|
|
}
|
|
mUndoStack->push( parentCommand );
|
|
mProject->setDirty( true );
|
|
}
|
|
|
|
void QgsComposition::lockSelectedItems()
|
|
{
|
|
QUndoCommand* parentCommand = new QUndoCommand( tr( "Items locked" ) );
|
|
QList<QgsComposerItem*> selectionList = selectedComposerItems();
|
|
QList<QgsComposerItem*>::iterator itemIter = selectionList.begin();
|
|
for ( ; itemIter != selectionList.end(); ++itemIter )
|
|
{
|
|
QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *itemIter, QLatin1String( "" ), parentCommand );
|
|
subcommand->savePreviousState();
|
|
( *itemIter )->setPositionLock( true );
|
|
subcommand->saveAfterState();
|
|
}
|
|
|
|
setAllDeselected();
|
|
mUndoStack->push( parentCommand );
|
|
mProject->setDirty( true );
|
|
}
|
|
|
|
void QgsComposition::unlockAllItems()
|
|
{
|
|
//unlock all items in composer
|
|
|
|
QUndoCommand* parentCommand = new QUndoCommand( tr( "Items unlocked" ) );
|
|
|
|
//first, clear the selection
|
|
setAllDeselected();
|
|
|
|
QList<QGraphicsItem *> itemList = items();
|
|
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
|
|
for ( ; itemIt != itemList.end(); ++itemIt )
|
|
{
|
|
QgsComposerItem* mypItem = dynamic_cast<QgsComposerItem *>( *itemIt );
|
|
if ( mypItem && mypItem->positionLock() )
|
|
{
|
|
QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( mypItem, QLatin1String( "" ), parentCommand );
|
|
subcommand->savePreviousState();
|
|
mypItem->setPositionLock( false );
|
|
//select unlocked items, same behavior as illustrator
|
|
mypItem->setSelected( true );
|
|
emit selectedItemChanged( mypItem );
|
|
subcommand->saveAfterState();
|
|
}
|
|
}
|
|
mUndoStack->push( parentCommand );
|
|
mProject->setDirty( true );
|
|
}
|
|
|
|
QgsComposerItemGroup *QgsComposition::groupItems( QList<QgsComposerItem *> items )
|
|
{
|
|
if ( items.size() < 2 )
|
|
{
|
|
//not enough items for a group
|
|
return nullptr;
|
|
}
|
|
|
|
QgsComposerItemGroup* itemGroup = new QgsComposerItemGroup( this );
|
|
QgsDebugMsg( QString( "itemgroup created with %1 items (%2 to be added)" ) .arg( itemGroup->items().size() ).arg( items.size() ) );
|
|
|
|
QList<QgsComposerItem*>::iterator itemIter = items.begin();
|
|
for ( ; itemIter != items.end(); ++itemIter )
|
|
{
|
|
itemGroup->addItem( *itemIter );
|
|
QgsDebugMsg( QString( "itemgroup now has %1" )
|
|
.arg( itemGroup->items().size() ) );
|
|
}
|
|
|
|
addItem( itemGroup );
|
|
|
|
QgsGroupUngroupItemsCommand* c = new QgsGroupUngroupItemsCommand( QgsGroupUngroupItemsCommand::Grouped, itemGroup, this, tr( "Items grouped" ) );
|
|
QObject::connect( c, SIGNAL( itemRemoved( QgsComposerItem* ) ), this, SIGNAL( itemRemoved( QgsComposerItem* ) ) );
|
|
QObject::connect( c, SIGNAL( itemAdded( QgsComposerItem* ) ), this, SLOT( sendItemAddedSignal( QgsComposerItem* ) ) );
|
|
|
|
undoStack()->push( c );
|
|
mProject->setDirty( true );
|
|
//QgsDebugMsg( QString( "itemgroup after pushAddRemove has %1" ) .arg( itemGroup->items().size() ) );
|
|
|
|
emit composerItemGroupAdded( itemGroup );
|
|
|
|
return itemGroup;
|
|
}
|
|
|
|
QList<QgsComposerItem *> QgsComposition::ungroupItems( QgsComposerItemGroup* group )
|
|
{
|
|
QList<QgsComposerItem *> ungroupedItems;
|
|
if ( !group )
|
|
{
|
|
return ungroupedItems;
|
|
}
|
|
|
|
// group ownership transferred to QgsGroupUngroupItemsCommand
|
|
// Call this before removing group items so it can keep note
|
|
// of contents
|
|
QgsGroupUngroupItemsCommand* c = new QgsGroupUngroupItemsCommand( QgsGroupUngroupItemsCommand::Ungrouped, group, this, tr( "Items ungrouped" ) );
|
|
QObject::connect( c, SIGNAL( itemRemoved( QgsComposerItem* ) ), this, SIGNAL( itemRemoved( QgsComposerItem* ) ) );
|
|
QObject::connect( c, SIGNAL( itemAdded( QgsComposerItem* ) ), this, SLOT( sendItemAddedSignal( QgsComposerItem* ) ) );
|
|
|
|
undoStack()->push( c );
|
|
mProject->setDirty( true );
|
|
|
|
|
|
QSet<QgsComposerItem*> groupedItems = group->items();
|
|
QSet<QgsComposerItem*>::iterator itemIt = groupedItems.begin();
|
|
for ( ; itemIt != groupedItems.end(); ++itemIt )
|
|
{
|
|
ungroupedItems << ( *itemIt );
|
|
}
|
|
|
|
group->removeItems();
|
|
|
|
// note: emits itemRemoved
|
|
removeComposerItem( group, false, false );
|
|
|
|
return ungroupedItems;
|
|
}
|
|
|
|
void QgsComposition::updateZValues( const bool addUndoCommands )
|
|
{
|
|
int counter = mItemsModel->zOrderListSize();
|
|
QList<QgsComposerItem*>::const_iterator it = mItemsModel->zOrderList()->constBegin();
|
|
QgsComposerItem* currentItem = nullptr;
|
|
|
|
QUndoCommand* parentCommand = nullptr;
|
|
if ( addUndoCommands )
|
|
{
|
|
parentCommand = new QUndoCommand( tr( "Item z-order changed" ) );
|
|
}
|
|
for ( ; it != mItemsModel->zOrderList()->constEnd(); ++it )
|
|
{
|
|
currentItem = *it;
|
|
if ( currentItem )
|
|
{
|
|
QgsComposerItemCommand* subcommand = nullptr;
|
|
if ( addUndoCommands )
|
|
{
|
|
subcommand = new QgsComposerItemCommand( *it, QLatin1String( "" ), parentCommand );
|
|
subcommand->savePreviousState();
|
|
}
|
|
currentItem->setZValue( counter );
|
|
if ( addUndoCommands )
|
|
{
|
|
subcommand->saveAfterState();
|
|
}
|
|
}
|
|
--counter;
|
|
}
|
|
if ( addUndoCommands )
|
|
{
|
|
mUndoStack->push( parentCommand );
|
|
mProject->setDirty( true );
|
|
}
|
|
}
|
|
|
|
void QgsComposition::refreshZList()
|
|
{
|
|
//model handles changes to item z order list
|
|
mItemsModel->rebuildZList();
|
|
|
|
//Finally, rebuild the zValue of all items to remove any duplicate zValues and make sure there's
|
|
//no missing zValues.
|
|
updateZValues( false );
|
|
}
|
|
|
|
QPointF QgsComposition::snapPointToGrid( QPointF scenePoint ) const
|
|
{
|
|
if ( !mSnapToGrid || mSnapGridResolution <= 0 || !graphicsView() )
|
|
{
|
|
return scenePoint;
|
|
}
|
|
|
|
//y offset to current page
|
|
int pageNr = static_cast< int >( scenePoint.y() / ( mPageHeight + mSpaceBetweenPages ) );
|
|
double yOffset = pageNr * ( mPageHeight + mSpaceBetweenPages );
|
|
double yPage = scenePoint.y() - yOffset; //y-coordinate relative to current page
|
|
|
|
//snap x coordinate
|
|
int xRatio = static_cast< int >(( scenePoint.x() - mSnapGridOffsetX ) / mSnapGridResolution + 0.5 );
|
|
int yRatio = static_cast< int >(( yPage - mSnapGridOffsetY ) / mSnapGridResolution + 0.5 );
|
|
|
|
double xSnapped = xRatio * mSnapGridResolution + mSnapGridOffsetX;
|
|
double ySnapped = yRatio * mSnapGridResolution + mSnapGridOffsetY + yOffset;
|
|
|
|
//convert snap tolerance from pixels to mm
|
|
double viewScaleFactor = graphicsView()->transform().m11();
|
|
double alignThreshold = mSnapTolerance / viewScaleFactor;
|
|
|
|
if ( fabs( xSnapped - scenePoint.x() ) > alignThreshold )
|
|
{
|
|
//snap distance is outside of tolerance
|
|
xSnapped = scenePoint.x();
|
|
}
|
|
if ( fabs( ySnapped - scenePoint.y() ) > alignThreshold )
|
|
{
|
|
//snap distance is outside of tolerance
|
|
ySnapped = scenePoint.y();
|
|
}
|
|
|
|
return QPointF( xSnapped, ySnapped );
|
|
}
|
|
|
|
QGraphicsLineItem* QgsComposition::addSnapLine()
|
|
{
|
|
QGraphicsLineItem* item = new QGraphicsLineItem();
|
|
QPen linePen( Qt::SolidLine );
|
|
linePen.setColor( Qt::red );
|
|
// use a pen width of 0, since this activates a cosmetic pen
|
|
// which doesn't scale with the composer and keeps a constant size
|
|
linePen.setWidthF( 0 );
|
|
item->setPen( linePen );
|
|
item->setZValue( 100 );
|
|
item->setVisible( mGuidesVisible );
|
|
addItem( item );
|
|
mSnapLines.push_back( item );
|
|
return item;
|
|
}
|
|
|
|
void QgsComposition::removeSnapLine( QGraphicsLineItem* line )
|
|
{
|
|
removeItem( line );
|
|
mSnapLines.removeAll( line );
|
|
delete line;
|
|
}
|
|
|
|
void QgsComposition::clearSnapLines()
|
|
{
|
|
Q_FOREACH ( QGraphicsLineItem* line, mSnapLines )
|
|
{
|
|
removeItem( line );
|
|
delete( line );
|
|
}
|
|
mSnapLines.clear();
|
|
}
|
|
|
|
void QgsComposition::setSnapLinesVisible( const bool visible )
|
|
{
|
|
mGuidesVisible = visible;
|
|
Q_FOREACH ( QGraphicsLineItem* line, mSnapLines )
|
|
{
|
|
line->setVisible( visible );
|
|
}
|
|
}
|
|
|
|
void QgsComposition::setPagesVisible( bool visible )
|
|
{
|
|
mPagesVisible = visible;
|
|
update();
|
|
}
|
|
|
|
QGraphicsLineItem* QgsComposition::nearestSnapLine( const bool horizontal, const double x, const double y, const double tolerance,
|
|
QList< QPair< QgsComposerItem*, QgsComposerItem::ItemPositionMode> >& snappedItems ) const
|
|
{
|
|
double minSqrDist = DBL_MAX;
|
|
QGraphicsLineItem* item = nullptr;
|
|
double currentXCoord = 0;
|
|
double currentYCoord = 0;
|
|
double currentSqrDist = 0;
|
|
double sqrTolerance = tolerance * tolerance;
|
|
|
|
snappedItems.clear();
|
|
|
|
QList< QGraphicsLineItem* >::const_iterator it = mSnapLines.constBegin();
|
|
for ( ; it != mSnapLines.constEnd(); ++it )
|
|
{
|
|
bool itemHorizontal = qgsDoubleNear(( *it )->line().y2() - ( *it )->line().y1(), 0 );
|
|
if ( horizontal && itemHorizontal )
|
|
{
|
|
currentYCoord = ( *it )->line().y1();
|
|
currentSqrDist = ( y - currentYCoord ) * ( y - currentYCoord );
|
|
}
|
|
else if ( !horizontal && !itemHorizontal )
|
|
{
|
|
currentXCoord = ( *it )->line().x1();
|
|
currentSqrDist = ( x - currentXCoord ) * ( x - currentXCoord );
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( currentSqrDist < minSqrDist && currentSqrDist < sqrTolerance )
|
|
{
|
|
item = *it;
|
|
minSqrDist = currentSqrDist;
|
|
}
|
|
}
|
|
|
|
double itemTolerance = 0.0000001;
|
|
if ( item )
|
|
{
|
|
//go through all the items to find items snapped to this snap line
|
|
QList<QGraphicsItem *> itemList = items();
|
|
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
|
|
for ( ; itemIt != itemList.end(); ++itemIt )
|
|
{
|
|
QgsComposerItem* currentItem = dynamic_cast<QgsComposerItem*>( *itemIt );
|
|
if ( !currentItem || currentItem->type() == QgsComposerItem::ComposerPaper )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( horizontal )
|
|
{
|
|
if ( qgsDoubleNear( currentYCoord, currentItem->pos().y() + currentItem->rect().top(), itemTolerance ) )
|
|
{
|
|
snappedItems.append( qMakePair( currentItem, QgsComposerItem::UpperMiddle ) );
|
|
}
|
|
else if ( qgsDoubleNear( currentYCoord, currentItem->pos().y() + currentItem->rect().center().y(), itemTolerance ) )
|
|
{
|
|
snappedItems.append( qMakePair( currentItem, QgsComposerItem::Middle ) );
|
|
}
|
|
else if ( qgsDoubleNear( currentYCoord, currentItem->pos().y() + currentItem->rect().bottom(), itemTolerance ) )
|
|
{
|
|
snappedItems.append( qMakePair( currentItem, QgsComposerItem::LowerMiddle ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( qgsDoubleNear( currentXCoord, currentItem->pos().x(), itemTolerance ) )
|
|
{
|
|
snappedItems.append( qMakePair( currentItem, QgsComposerItem::MiddleLeft ) );
|
|
}
|
|
else if ( qgsDoubleNear( currentXCoord, currentItem->pos().x() + currentItem->rect().center().x(), itemTolerance ) )
|
|
{
|
|
snappedItems.append( qMakePair( currentItem, QgsComposerItem::Middle ) );
|
|
}
|
|
else if ( qgsDoubleNear( currentXCoord, currentItem->pos().x() + currentItem->rect().width(), itemTolerance ) )
|
|
{
|
|
snappedItems.append( qMakePair( currentItem, QgsComposerItem::MiddleRight ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
int QgsComposition::boundingRectOfSelectedItems( QRectF& bRect )
|
|
{
|
|
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
|
|
if ( selectedItems.size() < 1 )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
//set the box to the first item
|
|
QgsComposerItem* currentItem = selectedItems.at( 0 );
|
|
double minX = currentItem->pos().x();
|
|
double minY = currentItem->pos().y();
|
|
double maxX = minX + currentItem->rect().width();
|
|
double maxY = minY + currentItem->rect().height();
|
|
|
|
double currentMinX, currentMinY, currentMaxX, currentMaxY;
|
|
|
|
for ( int i = 1; i < selectedItems.size(); ++i )
|
|
{
|
|
currentItem = selectedItems.at( i );
|
|
currentMinX = currentItem->pos().x();
|
|
currentMinY = currentItem->pos().y();
|
|
currentMaxX = currentMinX + currentItem->rect().width();
|
|
currentMaxY = currentMinY + currentItem->rect().height();
|
|
|
|
if ( currentMinX < minX )
|
|
minX = currentMinX;
|
|
if ( currentMaxX > maxX )
|
|
maxX = currentMaxX;
|
|
if ( currentMinY < minY )
|
|
minY = currentMinY;
|
|
if ( currentMaxY > maxY )
|
|
maxY = currentMaxY;
|
|
}
|
|
|
|
bRect.setTopLeft( QPointF( minX, minY ) );
|
|
bRect.setBottomRight( QPointF( maxX, maxY ) );
|
|
return 0;
|
|
}
|
|
|
|
void QgsComposition::setSnapToGridEnabled( const bool b )
|
|
{
|
|
mSnapToGrid = b;
|
|
updatePaperItems();
|
|
}
|
|
|
|
void QgsComposition::setGridVisible( const bool b )
|
|
{
|
|
mGridVisible = b;
|
|
updatePaperItems();
|
|
}
|
|
|
|
void QgsComposition::setSnapGridResolution( const double r )
|
|
{
|
|
mSnapGridResolution = r;
|
|
updatePaperItems();
|
|
}
|
|
|
|
void QgsComposition::setSnapGridOffsetX( const double offset )
|
|
{
|
|
mSnapGridOffsetX = offset;
|
|
updatePaperItems();
|
|
}
|
|
|
|
void QgsComposition::setSnapGridOffsetY( const double offset )
|
|
{
|
|
mSnapGridOffsetY = offset;
|
|
updatePaperItems();
|
|
}
|
|
|
|
void QgsComposition::setGridPen( const QPen& p )
|
|
{
|
|
mGridPen = p;
|
|
//make sure grid is drawn using a zero-width cosmetic pen
|
|
mGridPen.setWidthF( 0 );
|
|
updatePaperItems();
|
|
}
|
|
|
|
void QgsComposition::setGridStyle( const GridStyle s )
|
|
{
|
|
mGridStyle = s;
|
|
updatePaperItems();
|
|
}
|
|
|
|
void QgsComposition::setBoundingBoxesVisible( const bool boundsVisible )
|
|
{
|
|
mBoundingBoxesVisible = boundsVisible;
|
|
|
|
if ( mSelectionHandles )
|
|
{
|
|
mSelectionHandles->update();
|
|
}
|
|
}
|
|
|
|
void QgsComposition::updateSettings()
|
|
{
|
|
//load new composer setting values
|
|
loadSettings();
|
|
//update any paper items to reflect new settings
|
|
updatePaperItems();
|
|
}
|
|
|
|
void QgsComposition::loadSettings()
|
|
{
|
|
//read grid style, grid color and pen width from settings
|
|
QSettings s;
|
|
|
|
QString gridStyleString;
|
|
gridStyleString = s.value( QStringLiteral( "/Composer/gridStyle" ), "Dots" ).toString();
|
|
|
|
int gridRed, gridGreen, gridBlue, gridAlpha;
|
|
gridRed = s.value( QStringLiteral( "/Composer/gridRed" ), 190 ).toInt();
|
|
gridGreen = s.value( QStringLiteral( "/Composer/gridGreen" ), 190 ).toInt();
|
|
gridBlue = s.value( QStringLiteral( "/Composer/gridBlue" ), 190 ).toInt();
|
|
gridAlpha = s.value( QStringLiteral( "/Composer/gridAlpha" ), 100 ).toInt();
|
|
QColor gridColor = QColor( gridRed, gridGreen, gridBlue, gridAlpha );
|
|
|
|
mGridPen.setColor( gridColor );
|
|
mGridPen.setWidthF( 0 );
|
|
|
|
if ( gridStyleString == QLatin1String( "Dots" ) )
|
|
{
|
|
mGridStyle = Dots;
|
|
}
|
|
else if ( gridStyleString == QLatin1String( "Crosses" ) )
|
|
{
|
|
mGridStyle = Crosses;
|
|
}
|
|
else
|
|
{
|
|
mGridStyle = Solid;
|
|
}
|
|
}
|
|
|
|
void QgsComposition::beginCommand( QgsComposerItem* item, const QString& commandText, const QgsComposerMergeCommand::Context c )
|
|
{
|
|
delete mActiveItemCommand;
|
|
if ( !item )
|
|
{
|
|
mActiveItemCommand = nullptr;
|
|
return;
|
|
}
|
|
|
|
if ( c == QgsComposerMergeCommand::Unknown )
|
|
{
|
|
mActiveItemCommand = new QgsComposerItemCommand( item, commandText );
|
|
}
|
|
else
|
|
{
|
|
mActiveItemCommand = new QgsComposerMergeCommand( c, item, commandText );
|
|
}
|
|
mActiveItemCommand->savePreviousState();
|
|
}
|
|
|
|
void QgsComposition::endCommand()
|
|
{
|
|
if ( mActiveItemCommand )
|
|
{
|
|
mActiveItemCommand->saveAfterState();
|
|
if ( mActiveItemCommand->containsChange() ) //protect against empty commands
|
|
{
|
|
mUndoStack->push( mActiveItemCommand );
|
|
mProject->setDirty( true );
|
|
}
|
|
else
|
|
{
|
|
delete mActiveItemCommand;
|
|
}
|
|
mActiveItemCommand = nullptr;
|
|
}
|
|
}
|
|
|
|
void QgsComposition::cancelCommand()
|
|
{
|
|
delete mActiveItemCommand;
|
|
mActiveItemCommand = nullptr;
|
|
}
|
|
|
|
void QgsComposition::beginMultiFrameCommand( QgsComposerMultiFrame* multiFrame, const QString& text, const QgsComposerMultiFrameMergeCommand::Context c )
|
|
{
|
|
delete mActiveMultiFrameCommand;
|
|
|
|
if ( !multiFrame )
|
|
{
|
|
mActiveMultiFrameCommand = nullptr;
|
|
return;
|
|
}
|
|
|
|
if ( c == QgsComposerMultiFrameMergeCommand::Unknown )
|
|
{
|
|
mActiveMultiFrameCommand = new QgsComposerMultiFrameCommand( multiFrame, text );
|
|
}
|
|
else
|
|
{
|
|
mActiveMultiFrameCommand = new QgsComposerMultiFrameMergeCommand( c, multiFrame, text );
|
|
}
|
|
mActiveMultiFrameCommand->savePreviousState();
|
|
}
|
|
|
|
void QgsComposition::endMultiFrameCommand()
|
|
{
|
|
if ( mActiveMultiFrameCommand )
|
|
{
|
|
mActiveMultiFrameCommand->saveAfterState();
|
|
if ( mActiveMultiFrameCommand->containsChange() )
|
|
{
|
|
mUndoStack->push( mActiveMultiFrameCommand );
|
|
mProject->setDirty( true );
|
|
}
|
|
else
|
|
{
|
|
delete mActiveMultiFrameCommand;
|
|
}
|
|
mActiveMultiFrameCommand = nullptr;
|
|
}
|
|
}
|
|
|
|
void QgsComposition::cancelMultiFrameCommand()
|
|
{
|
|
delete mActiveMultiFrameCommand;
|
|
mActiveMultiFrameCommand = nullptr;
|
|
}
|
|
|
|
void QgsComposition::addMultiFrame( QgsComposerMultiFrame* multiFrame )
|
|
{
|
|
mMultiFrames.insert( multiFrame );
|
|
|
|
updateBounds();
|
|
}
|
|
|
|
void QgsComposition::removeMultiFrame( QgsComposerMultiFrame* multiFrame )
|
|
{
|
|
mMultiFrames.remove( multiFrame );
|
|
|
|
updateBounds();
|
|
}
|
|
|
|
void QgsComposition::addComposerArrow( QgsComposerArrow* arrow )
|
|
{
|
|
addItem( arrow );
|
|
|
|
updateBounds();
|
|
connect( arrow, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
|
|
|
|
emit composerArrowAdded( arrow );
|
|
}
|
|
|
|
void QgsComposition::addComposerPolygon( QgsComposerPolygon *polygon )
|
|
{
|
|
addItem( polygon );
|
|
|
|
updateBounds();
|
|
connect( polygon, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
|
|
|
|
emit composerPolygonAdded( polygon );
|
|
}
|
|
|
|
void QgsComposition::addComposerPolyline( QgsComposerPolyline *polyline )
|
|
{
|
|
addItem( polyline );
|
|
|
|
updateBounds();
|
|
connect( polyline, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
|
|
|
|
emit composerPolylineAdded( polyline );
|
|
}
|
|
|
|
void QgsComposition::addComposerLabel( QgsComposerLabel* label )
|
|
{
|
|
addItem( label );
|
|
|
|
updateBounds();
|
|
connect( label, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
|
|
|
|
emit composerLabelAdded( label );
|
|
}
|
|
|
|
void QgsComposition::addComposerMap( QgsComposerMap* map, const bool setDefaultPreviewStyle )
|
|
{
|
|
addItem( map );
|
|
if ( setDefaultPreviewStyle )
|
|
{
|
|
//set default preview mode to cache. Must be done here between adding composer map to scene and emitting signal
|
|
map->setPreviewMode( QgsComposerMap::Cache );
|
|
}
|
|
|
|
if ( map->previewMode() != QgsComposerMap::Rectangle )
|
|
{
|
|
map->cache();
|
|
}
|
|
|
|
updateBounds();
|
|
connect( map, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
|
|
|
|
emit composerMapAdded( map );
|
|
}
|
|
|
|
void QgsComposition::addComposerScaleBar( QgsComposerScaleBar* scaleBar )
|
|
{
|
|
addItem( scaleBar );
|
|
|
|
updateBounds();
|
|
connect( scaleBar, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
|
|
|
|
emit composerScaleBarAdded( scaleBar );
|
|
}
|
|
|
|
void QgsComposition::addComposerLegend( QgsComposerLegend* legend )
|
|
{
|
|
addItem( legend );
|
|
|
|
updateBounds();
|
|
connect( legend, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
|
|
|
|
emit composerLegendAdded( legend );
|
|
}
|
|
|
|
void QgsComposition::addComposerPicture( QgsComposerPicture* picture )
|
|
{
|
|
addItem( picture );
|
|
|
|
updateBounds();
|
|
connect( picture, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
|
|
|
|
emit composerPictureAdded( picture );
|
|
}
|
|
|
|
void QgsComposition::addComposerShape( QgsComposerShape* shape )
|
|
{
|
|
addItem( shape );
|
|
|
|
updateBounds();
|
|
connect( shape, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
|
|
|
|
emit composerShapeAdded( shape );
|
|
}
|
|
|
|
void QgsComposition::addComposerHtmlFrame( QgsComposerHtml* html, QgsComposerFrame* frame )
|
|
{
|
|
addItem( frame );
|
|
|
|
updateBounds();
|
|
connect( frame, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
|
|
|
|
emit composerHtmlFrameAdded( html, frame );
|
|
}
|
|
|
|
void QgsComposition::addComposerTableFrame( QgsComposerAttributeTableV2 *table, QgsComposerFrame *frame )
|
|
{
|
|
addItem( frame );
|
|
|
|
updateBounds();
|
|
connect( frame, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
|
|
|
|
emit composerTableFrameAdded( table, frame );
|
|
}
|
|
|
|
/* public */
|
|
void QgsComposition::removeComposerItem( QgsComposerItem* item, const bool createCommand, const bool removeGroupItems )
|
|
{
|
|
QgsComposerMap* map = dynamic_cast<QgsComposerMap *>( item );
|
|
|
|
if ( !map || !map->isDrawing() ) //don't delete a composer map while it draws
|
|
{
|
|
mItemsModel->setItemRemoved( item );
|
|
removeItem( item );
|
|
emit itemRemoved( item );
|
|
|
|
QgsDebugMsg( QString( "removeComposerItem called, createCommand:%1 removeGroupItems:%2" )
|
|
.arg( createCommand ).arg( removeGroupItems ) );
|
|
|
|
QgsComposerItemGroup* itemGroup = dynamic_cast<QgsComposerItemGroup*>( item );
|
|
if ( itemGroup && removeGroupItems )
|
|
{
|
|
QgsDebugMsg( QString( "itemGroup && removeGroupItems" ) );
|
|
|
|
// Takes ownership of itemGroup
|
|
QgsAddRemoveItemCommand* parentCommand = new QgsAddRemoveItemCommand(
|
|
QgsAddRemoveItemCommand::Removed, itemGroup, this,
|
|
tr( "Remove item group" ) );
|
|
connectAddRemoveCommandSignals( parentCommand );
|
|
|
|
//add add/remove item command for every item in the group
|
|
QSet<QgsComposerItem*> groupedItems = itemGroup->items();
|
|
QgsDebugMsg( QString( "itemGroup contains %1 items" ) .arg( groupedItems.size() ) );
|
|
QSet<QgsComposerItem*>::iterator it = groupedItems.begin();
|
|
for ( ; it != groupedItems.end(); ++it )
|
|
{
|
|
mItemsModel->setItemRemoved( *it );
|
|
removeItem( *it );
|
|
QgsAddRemoveItemCommand* subcommand = new QgsAddRemoveItemCommand( QgsAddRemoveItemCommand::Removed, *it, this, QLatin1String( "" ), parentCommand );
|
|
connectAddRemoveCommandSignals( subcommand );
|
|
emit itemRemoved( *it );
|
|
}
|
|
|
|
undoStack()->push( parentCommand );
|
|
}
|
|
else
|
|
{
|
|
bool frameItem = ( item->type() == QgsComposerItem::ComposerFrame );
|
|
QgsComposerMultiFrame* multiFrame = nullptr;
|
|
if ( createCommand )
|
|
{
|
|
if ( frameItem ) //multiframe tracks item changes
|
|
{
|
|
multiFrame = static_cast<QgsComposerFrame*>( item )->multiFrame();
|
|
item->beginItemCommand( tr( "Frame deleted" ) );
|
|
item->endItemCommand();
|
|
}
|
|
else
|
|
{
|
|
pushAddRemoveCommand( item, tr( "Item deleted" ), QgsAddRemoveItemCommand::Removed );
|
|
}
|
|
}
|
|
|
|
//check if there are frames left. If not, remove the multi frame
|
|
if ( frameItem && multiFrame )
|
|
{
|
|
if ( multiFrame->frameCount() < 1 )
|
|
{
|
|
removeMultiFrame( multiFrame );
|
|
if ( createCommand )
|
|
{
|
|
QgsAddRemoveMultiFrameCommand* command = new QgsAddRemoveMultiFrameCommand( QgsAddRemoveMultiFrameCommand::Removed,
|
|
multiFrame, this, tr( "Multiframe removed" ) );
|
|
undoStack()->push( command );
|
|
}
|
|
else
|
|
{
|
|
delete multiFrame;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
updateBounds();
|
|
}
|
|
|
|
void QgsComposition::pushAddRemoveCommand( QgsComposerItem* item, const QString& text, const QgsAddRemoveItemCommand::State state )
|
|
{
|
|
QgsAddRemoveItemCommand* c = new QgsAddRemoveItemCommand( state, item, this, text );
|
|
connectAddRemoveCommandSignals( c );
|
|
undoStack()->push( c );
|
|
mProject->setDirty( true );
|
|
}
|
|
|
|
void QgsComposition::connectAddRemoveCommandSignals( QgsAddRemoveItemCommand* c )
|
|
{
|
|
if ( !c )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QObject::connect( c, SIGNAL( itemRemoved( QgsComposerItem* ) ), this, SIGNAL( itemRemoved( QgsComposerItem* ) ) );
|
|
QObject::connect( c, SIGNAL( itemAdded( QgsComposerItem* ) ), this, SLOT( sendItemAddedSignal( QgsComposerItem* ) ) );
|
|
}
|
|
|
|
void QgsComposition::sendItemAddedSignal( QgsComposerItem* item )
|
|
{
|
|
//cast and send proper signal
|
|
item->setSelected( true );
|
|
QgsComposerArrow* arrow = dynamic_cast<QgsComposerArrow*>( item );
|
|
if ( arrow )
|
|
{
|
|
emit composerArrowAdded( arrow );
|
|
emit selectedItemChanged( arrow );
|
|
return;
|
|
}
|
|
QgsComposerLabel* label = dynamic_cast<QgsComposerLabel*>( item );
|
|
if ( label )
|
|
{
|
|
emit composerLabelAdded( label );
|
|
emit selectedItemChanged( label );
|
|
return;
|
|
}
|
|
QgsComposerMap* map = dynamic_cast<QgsComposerMap*>( item );
|
|
if ( map )
|
|
{
|
|
emit composerMapAdded( map );
|
|
emit selectedItemChanged( map );
|
|
return;
|
|
}
|
|
QgsComposerScaleBar* scalebar = dynamic_cast<QgsComposerScaleBar*>( item );
|
|
if ( scalebar )
|
|
{
|
|
emit composerScaleBarAdded( scalebar );
|
|
emit selectedItemChanged( scalebar );
|
|
return;
|
|
}
|
|
QgsComposerLegend* legend = dynamic_cast<QgsComposerLegend*>( item );
|
|
if ( legend )
|
|
{
|
|
emit composerLegendAdded( legend );
|
|
emit selectedItemChanged( legend );
|
|
return;
|
|
}
|
|
QgsComposerPicture* picture = dynamic_cast<QgsComposerPicture*>( item );
|
|
if ( picture )
|
|
{
|
|
emit composerPictureAdded( picture );
|
|
emit selectedItemChanged( picture );
|
|
return;
|
|
}
|
|
QgsComposerShape* shape = dynamic_cast<QgsComposerShape*>( item );
|
|
if ( shape )
|
|
{
|
|
emit composerShapeAdded( shape );
|
|
emit selectedItemChanged( shape );
|
|
return;
|
|
}
|
|
QgsComposerPolygon* polygon = dynamic_cast<QgsComposerPolygon*>( item );
|
|
if ( polygon )
|
|
{
|
|
emit composerPolygonAdded( polygon );
|
|
emit selectedItemChanged( polygon );
|
|
return;
|
|
}
|
|
QgsComposerPolyline* polyline = dynamic_cast<QgsComposerPolyline*>( item );
|
|
if ( polyline )
|
|
{
|
|
emit composerPolylineAdded( polyline );
|
|
emit selectedItemChanged( polyline );
|
|
return;
|
|
}
|
|
|
|
QgsComposerFrame* frame = dynamic_cast<QgsComposerFrame*>( item );
|
|
if ( frame )
|
|
{
|
|
//emit composerFrameAdded( multiframe, frame, );
|
|
QgsComposerMultiFrame* mf = frame->multiFrame();
|
|
QgsComposerHtml* html = dynamic_cast<QgsComposerHtml*>( mf );
|
|
if ( html )
|
|
{
|
|
emit composerHtmlFrameAdded( html, frame );
|
|
}
|
|
QgsComposerAttributeTableV2* table = dynamic_cast<QgsComposerAttributeTableV2*>( mf );
|
|
if ( table )
|
|
{
|
|
emit composerTableFrameAdded( table, frame );
|
|
}
|
|
emit selectedItemChanged( frame );
|
|
return;
|
|
}
|
|
QgsComposerItemGroup* group = dynamic_cast<QgsComposerItemGroup*>( item );
|
|
if ( group )
|
|
{
|
|
emit composerItemGroupAdded( group );
|
|
}
|
|
}
|
|
|
|
void QgsComposition::updatePaperItems()
|
|
{
|
|
Q_FOREACH ( QgsPaperItem* page, mPages )
|
|
{
|
|
page->update();
|
|
}
|
|
}
|
|
|
|
void QgsComposition::addPaperItem()
|
|
{
|
|
double paperHeight = this->paperHeight();
|
|
double paperWidth = this->paperWidth();
|
|
double currentY = paperHeight * mPages.size() + mPages.size() * mSpaceBetweenPages; //add 10mm visible space between pages
|
|
QgsPaperItem* paperItem = new QgsPaperItem( 0, currentY, paperWidth, paperHeight, this ); //default size A4
|
|
paperItem->setBrush( Qt::white );
|
|
addItem( paperItem );
|
|
paperItem->setZValue( 0 );
|
|
mPages.push_back( paperItem );
|
|
}
|
|
|
|
void QgsComposition::removePaperItems()
|
|
{
|
|
qDeleteAll( mPages );
|
|
mPages.clear();
|
|
}
|
|
|
|
void QgsComposition::deleteAndRemoveMultiFrames()
|
|
{
|
|
qDeleteAll( mMultiFrames );
|
|
mMultiFrames.clear();
|
|
}
|
|
|
|
#ifndef QT_NO_PRINTER
|
|
|
|
void QgsComposition::beginPrintAsPDF( QPrinter& printer, const QString& file )
|
|
{
|
|
printer.setOutputFileName( file );
|
|
// setOutputFormat should come after setOutputFileName, which auto-sets format to QPrinter::PdfFormat.
|
|
// [LS] This should be QPrinter::NativeFormat for Mac, otherwise fonts are not embed-able
|
|
// and text is not searchable; however, there are several bugs with <= Qt 4.8.5, 5.1.1, 5.2.0:
|
|
// https://bugreports.qt-project.org/browse/QTBUG-10094 - PDF font embedding fails
|
|
// https://bugreports.qt-project.org/browse/QTBUG-33583 - PDF output converts text to outline
|
|
// Also an issue with PDF paper size using QPrinter::NativeFormat on Mac (always outputs portrait letter-size)
|
|
printer.setOutputFormat( QPrinter::PdfFormat );
|
|
|
|
refreshPageSize();
|
|
//must set orientation to portrait before setting paper size, otherwise size will be flipped
|
|
//for landscape sized outputs (#11352)
|
|
printer.setOrientation( QPrinter::Portrait );
|
|
printer.setPaperSize( QSizeF( paperWidth(), paperHeight() ), QPrinter::Millimeter );
|
|
|
|
// TODO: add option for this in Composer
|
|
// May not work on Windows or non-X11 Linux. Works fine on Mac using QPrinter::NativeFormat
|
|
//printer.setFontEmbeddingEnabled( true );
|
|
|
|
QgsPaintEngineHack::fixEngineFlags( printer.paintEngine() );
|
|
}
|
|
|
|
bool QgsComposition::exportAsPDF( const QString& file )
|
|
{
|
|
QPrinter printer;
|
|
beginPrintAsPDF( printer, file );
|
|
return print( printer );
|
|
}
|
|
|
|
#endif
|
|
|
|
void QgsComposition::georeferenceOutput( const QString& file, QgsComposerMap* map,
|
|
const QRectF& exportRegion, double dpi ) const
|
|
{
|
|
if ( !map )
|
|
map = referenceMap();
|
|
|
|
if ( !map )
|
|
return; // no reference map
|
|
|
|
if ( dpi < 0 )
|
|
dpi = printResolution();
|
|
|
|
double* t = computeGeoTransform( map, exportRegion, dpi );
|
|
if ( !t )
|
|
return;
|
|
|
|
// important - we need to manually specify the DPI in advance, as GDAL will otherwise
|
|
// assume a DPI of 150
|
|
CPLSetConfigOption( "GDAL_PDF_DPI", QString::number( dpi ).toLocal8Bit().constData() );
|
|
GDALDatasetH outputDS = GDALOpen( file.toLocal8Bit().constData(), GA_Update );
|
|
if ( outputDS )
|
|
{
|
|
GDALSetGeoTransform( outputDS, t );
|
|
#if 0
|
|
//TODO - metadata can be set here, e.g.:
|
|
GDALSetMetadataItem( outputDS, "AUTHOR", "me", nullptr );
|
|
#endif
|
|
GDALSetProjection( outputDS, map->crs().toWkt().toLocal8Bit().constData() );
|
|
GDALClose( outputDS );
|
|
}
|
|
CPLSetConfigOption( "GDAL_PDF_DPI", nullptr );
|
|
delete[] t;
|
|
}
|
|
|
|
#ifndef QT_NO_PRINTER
|
|
|
|
void QgsComposition::doPrint( QPrinter& printer, QPainter& p, bool startNewPage )
|
|
{
|
|
if ( ddPageSizeActive() )
|
|
{
|
|
//set the page size again so that data defined page size takes effect
|
|
refreshPageSize();
|
|
//must set orientation to portrait before setting paper size, otherwise size will be flipped
|
|
//for landscape sized outputs (#11352)
|
|
printer.setOrientation( QPrinter::Portrait );
|
|
printer.setPaperSize( QSizeF( paperWidth(), paperHeight() ), QPrinter::Millimeter );
|
|
}
|
|
|
|
//QgsComposition starts page numbering at 0
|
|
int fromPage = ( printer.fromPage() < 1 ) ? 0 : printer.fromPage() - 1;
|
|
int toPage = ( printer.toPage() < 1 ) ? numPages() - 1 : printer.toPage() - 1;
|
|
|
|
bool pageExported = false;
|
|
if ( mPrintAsRaster )
|
|
{
|
|
for ( int i = fromPage; i <= toPage; ++i )
|
|
{
|
|
if ( !shouldExportPage( i + 1 ) )
|
|
{
|
|
continue;
|
|
}
|
|
if (( pageExported && i > fromPage ) || startNewPage )
|
|
{
|
|
printer.newPage();
|
|
}
|
|
|
|
QImage image = printPageAsRaster( i );
|
|
if ( !image.isNull() )
|
|
{
|
|
QRectF targetArea( 0, 0, image.width(), image.height() );
|
|
p.drawImage( targetArea, image, targetArea );
|
|
}
|
|
pageExported = true;
|
|
}
|
|
}
|
|
|
|
if ( !mPrintAsRaster )
|
|
{
|
|
for ( int i = fromPage; i <= toPage; ++i )
|
|
{
|
|
if ( !shouldExportPage( i + 1 ) )
|
|
{
|
|
continue;
|
|
}
|
|
if (( pageExported && i > fromPage ) || startNewPage )
|
|
{
|
|
printer.newPage();
|
|
}
|
|
renderPage( &p, i );
|
|
pageExported = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgsComposition::beginPrint( QPrinter &printer, const bool evaluateDDPageSize )
|
|
{
|
|
//set resolution based on composer setting
|
|
printer.setFullPage( true );
|
|
printer.setColorMode( QPrinter::Color );
|
|
|
|
//set user-defined resolution
|
|
printer.setResolution( printResolution() );
|
|
|
|
if ( evaluateDDPageSize && ddPageSizeActive() )
|
|
{
|
|
//set data defined page size
|
|
refreshPageSize();
|
|
//must set orientation to portrait before setting paper size, otherwise size will be flipped
|
|
//for landscape sized outputs (#11352)
|
|
printer.setOrientation( QPrinter::Portrait );
|
|
printer.setPaperSize( QSizeF( paperWidth(), paperHeight() ), QPrinter::Millimeter );
|
|
}
|
|
}
|
|
|
|
bool QgsComposition::print( QPrinter &printer, const bool evaluateDDPageSize )
|
|
{
|
|
beginPrint( printer, evaluateDDPageSize );
|
|
QPainter p;
|
|
bool ready = p.begin( &printer );
|
|
if ( !ready )
|
|
{
|
|
//error beginning print
|
|
return false;
|
|
}
|
|
doPrint( printer, p );
|
|
p.end();
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
QImage QgsComposition::printPageAsRaster( int page, QSize imageSize, int dpi )
|
|
{
|
|
int resolution = mPrintResolution;
|
|
if ( imageSize.isValid() )
|
|
{
|
|
//output size in pixels specified, calculate resolution using average of
|
|
//derived x/y dpi
|
|
resolution = ( imageSize.width() / mPageWidth
|
|
+ imageSize.height() / mPageHeight ) / 2.0 * 25.4;
|
|
}
|
|
else if ( dpi > 0 )
|
|
{
|
|
//dpi overridden by function parameters
|
|
resolution = dpi;
|
|
}
|
|
|
|
int width = imageSize.isValid() ? imageSize.width()
|
|
: static_cast< int >( resolution * mPageWidth / 25.4 );
|
|
int height = imageSize.isValid() ? imageSize.height()
|
|
: static_cast< int >( resolution * mPageHeight / 25.4 );
|
|
|
|
QImage image( QSize( width, height ), QImage::Format_ARGB32 );
|
|
if ( !image.isNull() )
|
|
{
|
|
image.setDotsPerMeterX( resolution / 25.4 * 1000 );
|
|
image.setDotsPerMeterY( resolution / 25.4 * 1000 );
|
|
image.fill( 0 );
|
|
QPainter imagePainter( &image );
|
|
renderPage( &imagePainter, page );
|
|
if ( !imagePainter.isActive() ) return QImage();
|
|
}
|
|
return image;
|
|
}
|
|
|
|
QImage QgsComposition::renderRectAsRaster( const QRectF& rect, QSize imageSize, int dpi )
|
|
{
|
|
int resolution = mPrintResolution;
|
|
if ( imageSize.isValid() )
|
|
{
|
|
//output size in pixels specified, calculate resolution using average of
|
|
//derived x/y dpi
|
|
resolution = ( imageSize.width() / rect.width()
|
|
+ imageSize.height() / rect.height() ) / 2.0 * 25.4;
|
|
}
|
|
else if ( dpi > 0 )
|
|
{
|
|
//dpi overridden by function parameters
|
|
resolution = dpi;
|
|
}
|
|
|
|
int width = imageSize.isValid() ? imageSize.width()
|
|
: static_cast< int >( resolution * rect.width() / 25.4 );
|
|
int height = imageSize.isValid() ? imageSize.height()
|
|
: static_cast< int >( resolution * rect.height() / 25.4 );
|
|
|
|
QImage image( QSize( width, height ), QImage::Format_ARGB32 );
|
|
if ( !image.isNull() )
|
|
{
|
|
image.setDotsPerMeterX( resolution / 25.4 * 1000 );
|
|
image.setDotsPerMeterY( resolution / 25.4 * 1000 );
|
|
image.fill( Qt::transparent );
|
|
QPainter imagePainter( &image );
|
|
renderRect( &imagePainter, rect );
|
|
if ( !imagePainter.isActive() ) return QImage();
|
|
}
|
|
return image;
|
|
}
|
|
|
|
void QgsComposition::renderPage( QPainter* p, int page )
|
|
{
|
|
if ( mPages.size() <= page )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QgsPaperItem* paperItem = mPages.at( page );
|
|
if ( !paperItem )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QRectF paperRect = QRectF( paperItem->pos().x(), paperItem->pos().y(), paperItem->rect().width(), paperItem->rect().height() );
|
|
renderRect( p, paperRect );
|
|
}
|
|
|
|
void QgsComposition::renderRect( QPainter* p, const QRectF& rect )
|
|
{
|
|
QPaintDevice* paintDevice = p->device();
|
|
if ( !paintDevice )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QgsComposition::PlotStyle savedPlotStyle = mPlotStyle;
|
|
mPlotStyle = QgsComposition::Print;
|
|
|
|
setSnapLinesVisible( false );
|
|
//hide background before rendering
|
|
setBackgroundBrush( Qt::NoBrush );
|
|
render( p, QRectF( 0, 0, paintDevice->width(), paintDevice->height() ), rect );
|
|
//show background after rendering
|
|
setBackgroundBrush( QColor( 215, 215, 215 ) );
|
|
setSnapLinesVisible( true );
|
|
|
|
mPlotStyle = savedPlotStyle;
|
|
}
|
|
|
|
double* QgsComposition::computeGeoTransform( const QgsComposerMap* map, const QRectF& region , double dpi ) const
|
|
{
|
|
if ( !map )
|
|
map = referenceMap();
|
|
|
|
if ( !map )
|
|
return nullptr;
|
|
|
|
if ( dpi < 0 )
|
|
dpi = printResolution();
|
|
|
|
// calculate region of composition to export (in mm)
|
|
QRectF exportRegion = region;
|
|
if ( !exportRegion.isValid() )
|
|
{
|
|
int pageNumber = map->page() - 1;
|
|
double pageY = pageNumber * ( mPageHeight + mSpaceBetweenPages );
|
|
exportRegion = QRectF( 0, pageY, mPageWidth, mPageHeight );
|
|
}
|
|
|
|
// map rectangle (in mm)
|
|
QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
|
|
|
|
// destination width/height in mm
|
|
double outputHeightMM = exportRegion.height();
|
|
double outputWidthMM = exportRegion.width();
|
|
|
|
// map properties
|
|
QgsRectangle mapExtent = *map->currentMapExtent();
|
|
double mapXCenter = mapExtent.center().x();
|
|
double mapYCenter = mapExtent.center().y();
|
|
double alpha = - map->mapRotation() / 180 * M_PI;
|
|
double sinAlpha = sin( alpha );
|
|
double cosAlpha = cos( alpha );
|
|
|
|
// get the extent (in map units) for the exported region
|
|
QPointF mapItemPos = map->pos();
|
|
//adjust item position so it is relative to export region
|
|
mapItemPos.rx() -= exportRegion.left();
|
|
mapItemPos.ry() -= exportRegion.top();
|
|
|
|
// calculate extent of entire page in map units
|
|
double xRatio = mapExtent.width() / mapItemSceneRect.width();
|
|
double yRatio = mapExtent.height() / mapItemSceneRect.height();
|
|
double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
|
|
double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
|
|
QgsRectangle paperExtent( xmin, ymax - outputHeightMM * yRatio, xmin + outputWidthMM * xRatio, ymax );
|
|
|
|
// calculate origin of page
|
|
double X0 = paperExtent.xMinimum();
|
|
double Y0 = paperExtent.yMaximum();
|
|
|
|
if ( !qgsDoubleNear( alpha, 0.0 ) )
|
|
{
|
|
// translate origin to account for map rotation
|
|
double X1 = X0 - mapXCenter;
|
|
double Y1 = Y0 - mapYCenter;
|
|
double X2 = X1 * cosAlpha + Y1 * sinAlpha;
|
|
double Y2 = -X1 * sinAlpha + Y1 * cosAlpha;
|
|
X0 = X2 + mapXCenter;
|
|
Y0 = Y2 + mapYCenter;
|
|
}
|
|
|
|
// calculate scaling of pixels
|
|
int pageWidthPixels = static_cast< int >( dpi * outputWidthMM / 25.4 );
|
|
int pageHeightPixels = static_cast< int >( dpi * outputHeightMM / 25.4 );
|
|
double pixelWidthScale = paperExtent.width() / pageWidthPixels;
|
|
double pixelHeightScale = paperExtent.height() / pageHeightPixels;
|
|
|
|
// transform matrix
|
|
double* t = new double[6];
|
|
t[0] = X0;
|
|
t[1] = cosAlpha * pixelWidthScale;
|
|
t[2] = -sinAlpha * pixelWidthScale;
|
|
t[3] = Y0;
|
|
t[4] = -sinAlpha * pixelHeightScale;
|
|
t[5] = -cosAlpha * pixelHeightScale;
|
|
|
|
return t;
|
|
}
|
|
|
|
QString QgsComposition::encodeStringForXml( const QString& str )
|
|
{
|
|
QString modifiedStr( str );
|
|
modifiedStr.replace( '&', QLatin1String( "&" ) );
|
|
modifiedStr.replace( '\"', QLatin1String( """ ) );
|
|
modifiedStr.replace( '\'', QLatin1String( "'" ) );
|
|
modifiedStr.replace( '<', QLatin1String( "<" ) );
|
|
modifiedStr.replace( '>', QLatin1String( ">" ) );
|
|
return modifiedStr;
|
|
}
|
|
|
|
QGraphicsView *QgsComposition::graphicsView() const
|
|
{
|
|
//try to find current view attached to composition
|
|
QList<QGraphicsView*> viewList = views();
|
|
if ( !viewList.isEmpty() )
|
|
{
|
|
return viewList.at( 0 );
|
|
}
|
|
|
|
//no view attached to composition
|
|
return nullptr;
|
|
}
|
|
|
|
void QgsComposition::computeWorldFileParameters( double& a, double& b, double& c, double& d, double& e, double& f ) const
|
|
{
|
|
const QgsComposerMap* map = referenceMap();
|
|
if ( !map )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int pageNumber = map->page() - 1;
|
|
double pageY = pageNumber * ( mPageHeight + mSpaceBetweenPages );
|
|
QRectF pageRect( 0, pageY, mPageWidth, mPageHeight );
|
|
computeWorldFileParameters( pageRect, a, b, c, d, e, f );
|
|
}
|
|
|
|
void QgsComposition::computeWorldFileParameters( const QRectF& exportRegion, double& a, double& b, double& c, double& d, double& e, double& f ) const
|
|
{
|
|
// World file parameters : affine transformation parameters from pixel coordinates to map coordinates
|
|
QgsComposerMap* map = referenceMap();
|
|
if ( !map )
|
|
{
|
|
return;
|
|
}
|
|
|
|
double destinationHeight = exportRegion.height();
|
|
double destinationWidth = exportRegion.width();
|
|
|
|
QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
|
|
QgsRectangle mapExtent = *map->currentMapExtent();
|
|
|
|
double alpha = map->mapRotation() / 180 * M_PI;
|
|
|
|
double xRatio = mapExtent.width() / mapItemSceneRect.width();
|
|
double yRatio = mapExtent.height() / mapItemSceneRect.height();
|
|
|
|
double xCenter = mapExtent.center().x();
|
|
double yCenter = mapExtent.center().y();
|
|
|
|
// get the extent (in map units) for the region
|
|
QPointF mapItemPos = map->pos();
|
|
//adjust item position so it is relative to export region
|
|
mapItemPos.rx() -= exportRegion.left();
|
|
mapItemPos.ry() -= exportRegion.top();
|
|
|
|
double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
|
|
double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
|
|
QgsRectangle paperExtent( xmin, ymax - destinationHeight * yRatio, xmin + destinationWidth * xRatio, ymax );
|
|
|
|
double X0 = paperExtent.xMinimum();
|
|
double Y0 = paperExtent.yMinimum();
|
|
|
|
int widthPx = static_cast< int >( printResolution() * destinationWidth / 25.4 );
|
|
int heightPx = static_cast< int >( printResolution() * destinationHeight / 25.4 );
|
|
|
|
double Ww = paperExtent.width() / widthPx;
|
|
double Hh = paperExtent.height() / heightPx;
|
|
|
|
// scaling matrix
|
|
double s[6];
|
|
s[0] = Ww;
|
|
s[1] = 0;
|
|
s[2] = X0;
|
|
s[3] = 0;
|
|
s[4] = -Hh;
|
|
s[5] = Y0 + paperExtent.height();
|
|
|
|
// rotation matrix
|
|
double r[6];
|
|
r[0] = cos( alpha );
|
|
r[1] = -sin( alpha );
|
|
r[2] = xCenter * ( 1 - cos( alpha ) ) + yCenter * sin( alpha );
|
|
r[3] = sin( alpha );
|
|
r[4] = cos( alpha );
|
|
r[5] = - xCenter * sin( alpha ) + yCenter * ( 1 - cos( alpha ) );
|
|
|
|
// result = rotation x scaling = rotation(scaling(X))
|
|
a = r[0] * s[0] + r[1] * s[3];
|
|
b = r[0] * s[1] + r[1] * s[4];
|
|
c = r[0] * s[2] + r[1] * s[5] + r[2];
|
|
d = r[3] * s[0] + r[4] * s[3];
|
|
e = r[3] * s[1] + r[4] * s[4];
|
|
f = r[3] * s[2] + r[4] * s[5] + r[5];
|
|
}
|
|
|
|
bool QgsComposition::setAtlasMode( const AtlasMode mode )
|
|
{
|
|
mAtlasMode = mode;
|
|
|
|
if ( mode == QgsComposition::AtlasOff )
|
|
{
|
|
mAtlasComposition.endRender();
|
|
}
|
|
else
|
|
{
|
|
bool atlasHasFeatures = mAtlasComposition.beginRender();
|
|
if ( ! atlasHasFeatures )
|
|
{
|
|
mAtlasMode = QgsComposition::AtlasOff;
|
|
mAtlasComposition.endRender();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
update();
|
|
return true;
|
|
}
|
|
|
|
bool QgsComposition::ddPageSizeActive() const
|
|
{
|
|
//check if any data defined page settings are active
|
|
return dataDefinedActive( QgsComposerObject::PresetPaperSize, &mDataDefinedProperties ) ||
|
|
dataDefinedActive( QgsComposerObject::PaperWidth, &mDataDefinedProperties ) ||
|
|
dataDefinedActive( QgsComposerObject::PaperHeight, &mDataDefinedProperties ) ||
|
|
dataDefinedActive( QgsComposerObject::PaperOrientation, &mDataDefinedProperties );
|
|
}
|
|
|
|
void QgsComposition::refreshPageSize( const QgsExpressionContext* context )
|
|
{
|
|
QgsExpressionContext scopedContext = createExpressionContext();
|
|
const QgsExpressionContext* evalContext = context ? context : &scopedContext;
|
|
|
|
double pageWidth = mPageWidth;
|
|
double pageHeight = mPageHeight;
|
|
|
|
QVariant exprVal;
|
|
//in order of precedence - first consider predefined page size
|
|
if ( dataDefinedEvaluate( QgsComposerObject::PresetPaperSize, exprVal, *evalContext, &mDataDefinedProperties ) )
|
|
{
|
|
QString presetString = exprVal.toString().trimmed();
|
|
QgsDebugMsg( QString( "exprVal Paper Preset size :%1" ).arg( presetString ) );
|
|
double widthD = 0;
|
|
double heightD = 0;
|
|
if ( QgsComposerUtils::decodePresetPaperSize( presetString, widthD, heightD ) )
|
|
{
|
|
pageWidth = widthD;
|
|
pageHeight = heightD;
|
|
}
|
|
}
|
|
|
|
//which is overwritten by data defined width/height
|
|
if ( dataDefinedEvaluate( QgsComposerObject::PaperWidth, exprVal, *evalContext, &mDataDefinedProperties ) )
|
|
{
|
|
bool ok;
|
|
double widthD = exprVal.toDouble( &ok );
|
|
QgsDebugMsg( QString( "exprVal Paper Width:%1" ).arg( widthD ) );
|
|
if ( ok )
|
|
{
|
|
pageWidth = widthD;
|
|
}
|
|
}
|
|
if ( dataDefinedEvaluate( QgsComposerObject::PaperHeight, exprVal, *evalContext, &mDataDefinedProperties ) )
|
|
{
|
|
bool ok;
|
|
double heightD = exprVal.toDouble( &ok );
|
|
QgsDebugMsg( QString( "exprVal Paper Height:%1" ).arg( heightD ) );
|
|
if ( ok )
|
|
{
|
|
pageHeight = heightD;
|
|
}
|
|
}
|
|
|
|
//which is finally overwritten by data defined orientation
|
|
if ( dataDefinedEvaluate( QgsComposerObject::PaperOrientation, exprVal, *evalContext, &mDataDefinedProperties ) )
|
|
{
|
|
bool ok;
|
|
QString orientationString = exprVal.toString().trimmed();
|
|
QgsComposition::PaperOrientation orientation = QgsComposerUtils::decodePaperOrientation( orientationString, ok );
|
|
QgsDebugMsg( QString( "exprVal Paper Orientation:%1" ).arg( orientationString ) );
|
|
if ( ok )
|
|
{
|
|
double heightD, widthD;
|
|
if ( orientation == QgsComposition::Portrait )
|
|
{
|
|
heightD = qMax( pageHeight, pageWidth );
|
|
widthD = qMin( pageHeight, pageWidth );
|
|
}
|
|
else
|
|
{
|
|
heightD = qMin( pageHeight, pageWidth );
|
|
widthD = qMax( pageHeight, pageWidth );
|
|
}
|
|
pageWidth = widthD;
|
|
pageHeight = heightD;
|
|
}
|
|
}
|
|
|
|
setPaperSize( pageWidth, pageHeight );
|
|
}
|
|
|
|
QgsDataDefined *QgsComposition::dataDefinedProperty( const QgsComposerObject::DataDefinedProperty property )
|
|
{
|
|
if ( property == QgsComposerObject::AllProperties || property == QgsComposerObject::NoProperty )
|
|
{
|
|
//invalid property
|
|
return nullptr;
|
|
}
|
|
|
|
//find matching QgsDataDefined for property
|
|
QMap< QgsComposerObject::DataDefinedProperty, QgsDataDefined* >::const_iterator it = mDataDefinedProperties.constFind( property );
|
|
if ( it != mDataDefinedProperties.constEnd() )
|
|
{
|
|
return it.value();
|
|
}
|
|
|
|
//not found
|
|
return nullptr;
|
|
}
|
|
|
|
void QgsComposition::setDataDefinedProperty( const QgsComposerObject::DataDefinedProperty property, bool active, bool useExpression, const QString &expression, const QString &field )
|
|
{
|
|
if ( property == QgsComposerObject::AllProperties || property == QgsComposerObject::NoProperty )
|
|
{
|
|
//invalid property
|
|
return;
|
|
}
|
|
|
|
bool defaultVals = ( !active && !useExpression && expression.isEmpty() && field.isEmpty() );
|
|
|
|
if ( mDataDefinedProperties.contains( property ) )
|
|
{
|
|
QMap< QgsComposerObject::DataDefinedProperty, QgsDataDefined* >::const_iterator it = mDataDefinedProperties.constFind( property );
|
|
if ( it != mDataDefinedProperties.constEnd() )
|
|
{
|
|
QgsDataDefined* dd = it.value();
|
|
dd->setActive( active );
|
|
dd->setExpressionString( expression );
|
|
dd->setField( field );
|
|
dd->setUseExpression( useExpression );
|
|
}
|
|
}
|
|
else if ( !defaultVals )
|
|
{
|
|
QgsDataDefined* dd = new QgsDataDefined( active, useExpression, expression, field );
|
|
mDataDefinedProperties.insert( property, dd );
|
|
}
|
|
}
|
|
|
|
void QgsComposition::setCustomProperty( const QString& key, const QVariant& value )
|
|
{
|
|
mCustomProperties.setValue( key, value );
|
|
|
|
if ( key.startsWith( QLatin1String( "variable" ) ) )
|
|
emit variablesChanged();
|
|
}
|
|
|
|
QVariant QgsComposition::customProperty( const QString& key, const QVariant& defaultValue ) const
|
|
{
|
|
return mCustomProperties.value( key, defaultValue );
|
|
}
|
|
|
|
void QgsComposition::removeCustomProperty( const QString& key )
|
|
{
|
|
mCustomProperties.remove( key );
|
|
}
|
|
|
|
QStringList QgsComposition::customProperties() const
|
|
{
|
|
return mCustomProperties.keys();
|
|
}
|
|
|
|
bool QgsComposition::dataDefinedEvaluate( QgsComposerObject::DataDefinedProperty property, QVariant &expressionValue,
|
|
const QgsExpressionContext& context,
|
|
QMap<QgsComposerObject::DataDefinedProperty, QgsDataDefined *> *dataDefinedProperties )
|
|
{
|
|
if ( property == QgsComposerObject::NoProperty || property == QgsComposerObject::AllProperties )
|
|
{
|
|
//invalid property
|
|
return false;
|
|
}
|
|
|
|
//null passed-around QVariant
|
|
expressionValue.clear();
|
|
|
|
//get fields and feature from atlas
|
|
QgsFeature currentFeature;
|
|
QgsFields layerFields;
|
|
bool useFeature = false;
|
|
if ( mAtlasComposition.enabled() )
|
|
{
|
|
QgsVectorLayer* atlasLayer = mAtlasComposition.coverageLayer();
|
|
if ( atlasLayer )
|
|
{
|
|
layerFields = atlasLayer->fields();
|
|
}
|
|
if ( mAtlasMode != QgsComposition::AtlasOff )
|
|
{
|
|
useFeature = true;
|
|
currentFeature = mAtlasComposition.feature();
|
|
}
|
|
}
|
|
|
|
//evaluate data defined property using current atlas context
|
|
QVariant result = dataDefinedValue( property, useFeature ? ¤tFeature : nullptr, layerFields, context, dataDefinedProperties );
|
|
|
|
if ( result.isValid() )
|
|
{
|
|
expressionValue = result;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool QgsComposition::dataDefinedActive( const QgsComposerObject::DataDefinedProperty property, const QMap<QgsComposerObject::DataDefinedProperty, QgsDataDefined *> *dataDefinedProperties ) const
|
|
{
|
|
if ( property == QgsComposerObject::AllProperties || property == QgsComposerObject::NoProperty )
|
|
{
|
|
//invalid property
|
|
return false;
|
|
}
|
|
if ( !dataDefinedProperties->contains( property ) )
|
|
{
|
|
//missing property
|
|
return false;
|
|
}
|
|
|
|
QgsDataDefined* dd = nullptr;
|
|
QMap< QgsComposerObject::DataDefinedProperty, QgsDataDefined* >::const_iterator it = dataDefinedProperties->find( property );
|
|
if ( it != dataDefinedProperties->constEnd() )
|
|
{
|
|
dd = it.value();
|
|
}
|
|
|
|
if ( !dd )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//found the data defined property, return whether it is active
|
|
return dd->isActive();
|
|
}
|
|
|
|
QVariant QgsComposition::dataDefinedValue( QgsComposerObject::DataDefinedProperty property, const QgsFeature *feature, const QgsFields& fields, const QgsExpressionContext& context, QMap<QgsComposerObject::DataDefinedProperty, QgsDataDefined *> *dataDefinedProperties ) const
|
|
{
|
|
if ( property == QgsComposerObject::AllProperties || property == QgsComposerObject::NoProperty )
|
|
{
|
|
//invalid property
|
|
return QVariant();
|
|
}
|
|
if ( !dataDefinedProperties->contains( property ) )
|
|
{
|
|
//missing property
|
|
return QVariant();
|
|
}
|
|
|
|
QgsDataDefined* dd = nullptr;
|
|
QMap< QgsComposerObject::DataDefinedProperty, QgsDataDefined* >::const_iterator it = dataDefinedProperties->find( property );
|
|
if ( it != dataDefinedProperties->constEnd() )
|
|
{
|
|
dd = it.value();
|
|
}
|
|
|
|
if ( !dd )
|
|
{
|
|
return QVariant();
|
|
}
|
|
|
|
if ( !dd->isActive() )
|
|
{
|
|
return QVariant();
|
|
}
|
|
|
|
QVariant result = QVariant();
|
|
bool useExpression = dd->useExpression();
|
|
QString field = dd->field();
|
|
|
|
if ( !dd->expressionIsPrepared() )
|
|
{
|
|
prepareDataDefinedExpression( dd, dataDefinedProperties, context );
|
|
}
|
|
|
|
if ( useExpression && dd->expressionIsPrepared() )
|
|
{
|
|
QgsExpression* expr = dd->expression();
|
|
|
|
result = expr->evaluate( &context );
|
|
if ( expr->hasEvalError() )
|
|
{
|
|
QgsDebugMsgLevel( QString( "Evaluate error:" ) + expr->evalErrorString(), 4 );
|
|
return QVariant();
|
|
}
|
|
}
|
|
else if ( !useExpression && !field.isEmpty() )
|
|
{
|
|
if ( !feature )
|
|
{
|
|
return QVariant();
|
|
}
|
|
// use direct attribute access instead of evaluating "field" expression (much faster)
|
|
int indx = fields.indexFromName( field );
|
|
if ( indx != -1 )
|
|
{
|
|
result = feature->attribute( indx );
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void QgsComposition::prepareDataDefinedExpression( QgsDataDefined *dd, QMap<QgsComposerObject::DataDefinedProperty, QgsDataDefined *> *dataDefinedProperties,
|
|
const QgsExpressionContext& context ) const
|
|
{
|
|
//if specific QgsDataDefined passed, prepare it
|
|
//otherwise prepare all QgsDataDefineds
|
|
if ( dd )
|
|
{
|
|
dd->prepareExpression( context );
|
|
}
|
|
else
|
|
{
|
|
QMap< QgsComposerObject::DataDefinedProperty, QgsDataDefined* >::const_iterator it = dataDefinedProperties->constBegin();
|
|
for ( ; it != dataDefinedProperties->constEnd(); ++it )
|
|
{
|
|
it.value()->prepareExpression( context );
|
|
}
|
|
}
|
|
}
|
|
|
|
QgsExpressionContext QgsComposition::createExpressionContext() const
|
|
{
|
|
QgsExpressionContext context = QgsExpressionContext();
|
|
context.appendScope( QgsExpressionContextUtils::globalScope() );
|
|
context.appendScope( QgsExpressionContextUtils::projectScope( mProject ) );
|
|
context.appendScope( QgsExpressionContextUtils::compositionScope( this ) );
|
|
if ( mAtlasComposition.enabled() )
|
|
{
|
|
context.appendScope( QgsExpressionContextUtils::atlasScope( &mAtlasComposition ) );
|
|
}
|
|
return context;
|
|
}
|
|
|
|
void QgsComposition::prepareAllDataDefinedExpressions()
|
|
{
|
|
QgsExpressionContext context = createExpressionContext();
|
|
prepareDataDefinedExpression( nullptr, &mDataDefinedProperties, context );
|
|
}
|
|
|