QGIS/src/core/composer/qgscomposition.cpp

2379 lines
68 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 "qgscomposerarrow.h"
#include "qgscomposerframe.h"
#include "qgscomposerhtml.h"
#include "qgscomposerlabel.h"
#include "qgscomposerlegend.h"
#include "qgscomposermap.h"
#include "qgscomposermousehandles.h"
#include "qgscomposeritemgroup.h"
#include "qgscomposerpicture.h"
#include "qgscomposerscalebar.h"
#include "qgscomposershape.h"
#include "qgscomposerlabel.h"
#include "qgscomposerattributetable.h"
#include "qgsaddremovemultiframecommand.h"
#include "qgscomposermultiframecommand.h"
#include "qgspaintenginehack.h"
#include "qgspaperitem.h"
#include "qgsproject.h"
#include "qgsgeometry.h"
#include "qgsvectorlayer.h"
#include "qgsvectordataprovider.h"
#include "qgsexpression.h"
#include "qgssymbolv2.h"
#include "qgssymbollayerv2utils.h"
#include <QDomDocument>
#include <QDomElement>
#include <QGraphicsRectItem>
#include <QPainter>
#include <QPrinter>
#include <QSettings>
#include <QDir>
QgsComposition::QgsComposition( QgsMapRenderer* mapRenderer )
: QGraphicsScene( 0 )
, mMapRenderer( mapRenderer )
, mPlotStyle( QgsComposition::Preview )
, mPageWidth( 297 )
, mPageHeight( 210 )
, mSpaceBetweenPages( 10 )
, mPageStyleSymbol( 0 )
, mPrintAsRaster( false )
, mGenerateWorldFile( false )
, mWorldFileMap( 0 )
, mUseAdvancedEffects( true )
, mSnapToGrid( false )
, mGridVisible( false )
, mSnapGridResolution( 0 )
, mSnapGridTolerance( 0 )
, mSnapGridOffsetX( 0 )
, mSnapGridOffsetY( 0 )
, mAlignmentSnap( true )
, mGuidesVisible( true )
, mSmartGuides( true )
, mAlignmentSnapTolerance( 0 )
, mSelectionHandles( 0 )
, mActiveItemCommand( 0 )
, mActiveMultiFrameCommand( 0 )
, mAtlasComposition( this )
, mAtlasMode( QgsComposition::AtlasOff )
, mPreventCursorChange( false )
{
setBackgroundBrush( QColor( 215, 215, 215 ) );
createDefaultPageStyleSymbol();
addPaperItem();
//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();
}
QgsComposition::QgsComposition()
: QGraphicsScene( 0 ),
mMapRenderer( 0 ),
mPlotStyle( QgsComposition::Preview ),
mPageWidth( 297 ),
mPageHeight( 210 ),
mSpaceBetweenPages( 10 ),
mPageStyleSymbol( 0 ),
mPrintAsRaster( false ),
mGenerateWorldFile( false ),
mWorldFileMap( 0 ),
mUseAdvancedEffects( true ),
mSnapToGrid( false ),
mGridVisible( false ),
mSnapGridResolution( 0 ),
mSnapGridTolerance( 0 ),
mSnapGridOffsetX( 0 ),
mSnapGridOffsetY( 0 ),
mAlignmentSnap( true ),
mGuidesVisible( true ),
mSmartGuides( true ),
mAlignmentSnapTolerance( 0 ),
mSelectionHandles( 0 ),
mActiveItemCommand( 0 ),
mActiveMultiFrameCommand( 0 ),
mAtlasComposition( this ),
mAtlasMode( QgsComposition::AtlasOff ),
mPreventCursorChange( false )
{
//load default composition settings
loadDefaults();
loadSettings();
}
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
clear();
delete mActiveItemCommand;
delete mActiveMultiFrameCommand;
delete mPageStyleSymbol;
}
void QgsComposition::loadDefaults()
{
QSettings settings;
mSnapGridResolution = settings.value( "/Composer/defaultSnapGridResolution", 10.0 ).toDouble();
mSnapGridTolerance = settings.value( "/Composer/defaultSnapGridTolerance", 2 ).toDouble();
mSnapGridOffsetX = settings.value( "/Composer/defaultSnapGridOffsetX", 0 ).toDouble();
mSnapGridOffsetY = settings.value( "/Composer/defaultSnapGridOffsetY", 0 ).toDouble();
mAlignmentSnapTolerance = settings.value( "/Composer/defaultSnapGuideTolerance", 2 ).toDouble();
}
void QgsComposition::setPaperSize( double width, double height )
{
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 );
}
emit paperSizeChanged();
}
double QgsComposition::paperHeight() const
{
return mPageHeight;
}
double QgsComposition::paperWidth() const
{
return mPageWidth;
}
void QgsComposition::setNumPages( int pages )
{
int currentPages = numPages();
int diff = pages - 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 the corresponding variable
QgsExpression::setSpecialColumn( "$numpages", QVariant(( int )numPages() ) );
emit nPagesChanged();
}
int QgsComposition::numPages() const
{
return mPages.size();
}
void QgsComposition::setPageStyleSymbol( QgsFillSymbolV2* symbol )
{
delete mPageStyleSymbol;
mPageStyleSymbol = symbol;
}
void QgsComposition::createDefaultPageStyleSymbol()
{
delete mPageStyleSymbol;
QgsStringMap properties;
properties.insert( "color", "white" );
properties.insert( "style", "solid" );
properties.insert( "style_border", "no" );
mPageStyleSymbol = QgsFillSymbolV2::createSimple( properties );
}
QPointF QgsComposition::positionOnPage( const 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( const 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( const QPointF & position )
{
return composerItemAt( position, 0 );
}
QgsComposerItem* QgsComposition::composerItemAt( const QPointF & position, const QgsComposerItem* belowItem )
{
//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 )
{
return composerItem;
}
else
{
if ( composerItem == belowItem )
{
//Target item is next in list
foundBelowItem = true;
}
}
}
}
return 0;
}
int QgsComposition::pageNumberAt( const 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()
{
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 )
{
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( 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 0;
}
const QgsComposerHtml* QgsComposition::getComposerHtmlByItem( QgsComposerItem *item ) const
{
// an html item will be a composer frame and if it is we can try to get
// its multiframe parent and then try to cast that to a composer html
const QgsComposerFrame* composerFrame =
dynamic_cast<const QgsComposerFrame *>( item );
if ( composerFrame )
{
const QgsComposerMultiFrame * mypMultiFrame = composerFrame->multiFrame();
const QgsComposerHtml* composerHtml =
dynamic_cast<const QgsComposerHtml *>( mypMultiFrame );
if ( composerHtml )
{
return composerHtml;
}
}
return 0;
}
const QgsComposerItem* QgsComposition::getComposerItemById( 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 0;
}
#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( 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 0;
}
void QgsComposition::setUseAdvancedEffects( 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 );
}
}
}
int QgsComposition::pixelFontSize( double pointSize ) const
{
//in QgsComposition, one unit = one mm
double sizeMillimeters = pointSize * 0.3527;
return qRound( sizeMillimeters ); //round to nearest mm
}
double QgsComposition::pointFontSize( int pixelSize ) const
{
double sizePoint = pixelSize / 0.3527;
return sizePoint;
}
bool QgsComposition::writeXML( QDomElement& composerElem, QDomDocument& doc )
{
if ( composerElem.isNull() )
{
return false;
}
QDomElement compositionElem = doc.createElement( "Composition" );
compositionElem.setAttribute( "paperWidth", QString::number( mPageWidth ) );
compositionElem.setAttribute( "paperHeight", QString::number( mPageHeight ) );
compositionElem.setAttribute( "numPages", mPages.size() );
QDomElement pageStyleElem = QgsSymbolLayerV2Utils::saveSymbol( QString(), mPageStyleSymbol, doc );
compositionElem.appendChild( pageStyleElem );
//snapping
if ( mSnapToGrid )
{
compositionElem.setAttribute( "snapping", "1" );
}
else
{
compositionElem.setAttribute( "snapping", "0" );
}
if ( mGridVisible )
{
compositionElem.setAttribute( "gridVisible", "1" );
}
else
{
compositionElem.setAttribute( "gridVisible", "0" );
}
compositionElem.setAttribute( "snapGridResolution", QString::number( mSnapGridResolution ) );
compositionElem.setAttribute( "snapGridTolerance", QString::number( mSnapGridTolerance ) );
compositionElem.setAttribute( "snapGridOffsetX", QString::number( mSnapGridOffsetX ) );
compositionElem.setAttribute( "snapGridOffsetY", QString::number( mSnapGridOffsetY ) );
//custom snap lines
QList< QGraphicsLineItem* >::const_iterator snapLineIt = mSnapLines.constBegin();
for ( ; snapLineIt != mSnapLines.constEnd(); ++snapLineIt )
{
QDomElement snapLineElem = doc.createElement( "SnapLine" );
QLineF line = ( *snapLineIt )->line();
snapLineElem.setAttribute( "x1", QString::number( line.x1() ) );
snapLineElem.setAttribute( "y1", QString::number( line.y1() ) );
snapLineElem.setAttribute( "x2", QString::number( line.x2() ) );
snapLineElem.setAttribute( "y2", QString::number( line.y2() ) );
compositionElem.appendChild( snapLineElem );
}
compositionElem.setAttribute( "printResolution", mPrintResolution );
compositionElem.setAttribute( "printAsRaster", mPrintAsRaster );
compositionElem.setAttribute( "generateWorldFile", mGenerateWorldFile ? 1 : 0 );
if ( mGenerateWorldFile && mWorldFileMap )
{
compositionElem.setAttribute( "worldFileMap", mWorldFileMap->id() );
}
compositionElem.setAttribute( "alignmentSnap", mAlignmentSnap ? 1 : 0 );
compositionElem.setAttribute( "guidesVisible", mGuidesVisible ? 1 : 0 );
compositionElem.setAttribute( "smartGuides", mSmartGuides ? 1 : 0 );
compositionElem.setAttribute( "alignmentSnapTolerance", mAlignmentSnapTolerance );
//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 );
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( "paperWidth" ).toDouble( &widthConversionOk );
mPageHeight = compositionElem.attribute( "paperHeight" ).toDouble( &heightConversionOk );
emit paperSizeChanged();
int numPages = compositionElem.attribute( "numPages", "1" ).toInt();
QDomElement pageStyleSymbolElem = compositionElem.firstChildElement( "symbol" );
if ( !pageStyleSymbolElem.isNull() )
{
delete mPageStyleSymbol;
mPageStyleSymbol = dynamic_cast<QgsFillSymbolV2*>( QgsSymbolLayerV2Utils::loadSymbol( pageStyleSymbolElem ) );
}
if ( widthConversionOk && heightConversionOk )
{
removePaperItems();
for ( int i = 0; i < numPages; ++i )
{
addPaperItem();
}
}
//snapping
if ( compositionElem.attribute( "snapping" ) == "0" )
{
mSnapToGrid = false;
}
else
{
mSnapToGrid = true;
}
if ( compositionElem.attribute( "gridVisible" ) == "0" )
{
mGridVisible = false;
}
else
{
mGridVisible = true;
}
mSnapGridResolution = compositionElem.attribute( "snapGridResolution" ).toDouble();
mSnapGridTolerance = compositionElem.attribute( "snapGridTolerance", "2.0" ).toDouble();
mSnapGridOffsetX = compositionElem.attribute( "snapGridOffsetX" ).toDouble();
mSnapGridOffsetY = compositionElem.attribute( "snapGridOffsetY" ).toDouble();
mAlignmentSnap = compositionElem.attribute( "alignmentSnap", "1" ).toInt() == 0 ? false : true;
mGuidesVisible = compositionElem.attribute( "guidesVisible", "1" ).toInt() == 0 ? false : true;
mSmartGuides = compositionElem.attribute( "smartGuides", "1" ).toInt() == 0 ? false : true;
mAlignmentSnapTolerance = compositionElem.attribute( "alignmentSnapTolerance", "2.0" ).toDouble();
//custom snap lines
QDomNodeList snapLineNodes = compositionElem.elementsByTagName( "SnapLine" );
for ( int i = 0; i < snapLineNodes.size(); ++i )
{
QDomElement snapLineElem = snapLineNodes.at( i ).toElement();
QGraphicsLineItem* snapItem = addSnapLine();
double x1 = snapLineElem.attribute( "x1" ).toDouble();
double y1 = snapLineElem.attribute( "y1" ).toDouble();
double x2 = snapLineElem.attribute( "x2" ).toDouble();
double y2 = snapLineElem.attribute( "y2" ).toDouble();
snapItem->setLine( x1, y1, x2, y2 );
}
mPrintAsRaster = compositionElem.attribute( "printAsRaster" ).toInt();
mPrintResolution = compositionElem.attribute( "printResolution", "300" ).toInt();
mGenerateWorldFile = compositionElem.attribute( "generateWorldFile", "0" ).toInt() == 1 ? true : false;
updatePaperItems();
return true;
}
bool QgsComposition::loadFromTemplate( const QDomDocument& doc, QMap<QString, QString>* substitutionMap, bool addUndoCommands )
{
deleteAndRemoveMultiFrames();
//delete all 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 );
if ( cItem )
{
removeItem( cItem );
emit itemRemoved( cItem );
delete cItem;
}
}
mItemZList.clear();
mPages.clear();
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 compositionElem = importDoc.documentElement().firstChildElement( "Composition" );
if ( compositionElem.isNull() )
{
return false;
}
bool ok = readXML( compositionElem, importDoc );
if ( !ok )
{
return false;
}
// remove all uuid attributes since we don't want duplicates UUIDS
QDomNodeList composerItemsNodes = importDoc.elementsByTagName( "ComposerItem" );
for ( int i = 0; i < composerItemsNodes.count(); ++i )
{
QDomNode composerItemNode = composerItemsNodes.at( i );
if ( composerItemNode.isElement() )
{
composerItemNode.toElement().setAttribute( "templateUuid", composerItemNode.toElement().attribute( "uuid" ) );
composerItemNode.toElement().removeAttribute( "uuid" );
}
}
//addItemsFromXML
addItemsFromXML( importDoc.documentElement(), importDoc, 0, addUndoCommands, 0 );
// read atlas parameters
QDomElement atlasElem = importDoc.documentElement().firstChildElement( "Atlas" );
atlasComposition().readXML( atlasElem, importDoc );
return true;
}
void QgsComposition::addItemsFromXML( const QDomElement& elem, const QDomDocument& doc, QMap< QgsComposerMap*, int >* mapsToRestore,
bool addUndoCommands, QPointF* pos, bool pasteInPlace )
{
QPointF* pasteInPlacePt = 0;
if ( pasteInPlace )
{
pasteInPlacePt = new QPointF( 0, pageNumberAt( *pos ) * ( mPageHeight + mSpaceBetweenPages ) );
}
QDomNodeList composerLabelList = elem.elementsByTagName( "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->setItemPosition( pos->x(), pos->y() );
}
}
addComposerLabel( newLabel );
if ( addUndoCommands )
{
pushAddRemoveCommand( newLabel, tr( "Label added" ) );
}
}
// map
QDomNodeList composerMapList = elem.elementsByTagName( "ComposerMap" );
for ( int i = 0; i < composerMapList.size(); ++i )
{
QDomElement currentComposerMapElem = composerMapList.at( i ).toElement();
QgsComposerMap* newMap = new QgsComposerMap( this );
newMap->readXML( currentComposerMapElem, doc );
newMap->assignFreeId();
if ( mapsToRestore )
{
mapsToRestore->insert( newMap, ( int )( newMap->previewMode() ) );
newMap->setPreviewMode( QgsComposerMap::Rectangle );
}
addComposerMap( newMap, false );
if ( pos )
{
if ( pasteInPlace )
{
newMap->setItemPosition( newMap->pos().x(), fmod( newMap->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
newMap->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
}
else
{
newMap->setItemPosition( pos->x(), pos->y() );
}
}
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 )
{
if (( *mit )->overviewFrameMapId() != -1 )
{
const QgsComposerMap* overviewMap = getComposerMapById(( *mit )->overviewFrameMapId() );
if ( overviewMap )
{
QObject::connect( overviewMap, SIGNAL( extentChanged() ), *mit, SLOT( overviewExtentChanged() ) );
}
}
}
// arrow
QDomNodeList composerArrowList = elem.elementsByTagName( "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->setItemPosition( pos->x(), pos->y() );
}
}
addComposerArrow( newArrow );
if ( addUndoCommands )
{
pushAddRemoveCommand( newArrow, tr( "Arrow added" ) );
}
}
// scalebar
QDomNodeList composerScaleBarList = elem.elementsByTagName( "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->setItemPosition( pos->x(), pos->y() );
}
}
addComposerScaleBar( newScaleBar );
if ( addUndoCommands )
{
pushAddRemoveCommand( newScaleBar, tr( "Scale bar added" ) );
}
}
// shape
QDomNodeList composerShapeList = elem.elementsByTagName( "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->setUseSymbolV2( true );
if ( pos )
{
if ( pasteInPlace )
{
newShape->setItemPosition( newShape->pos().x(), fmod( newShape->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
newShape->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
}
else
{
newShape->setItemPosition( pos->x(), pos->y() );
}
}
addComposerShape( newShape );
if ( addUndoCommands )
{
pushAddRemoveCommand( newShape, tr( "Shape added" ) );
}
}
// picture
QDomNodeList composerPictureList = elem.elementsByTagName( "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->setItemPosition( pos->x(), pos->y() );
}
}
addComposerPicture( newPicture );
if ( addUndoCommands )
{
pushAddRemoveCommand( newPicture, tr( "Picture added" ) );
}
}
// legend
QDomNodeList composerLegendList = elem.elementsByTagName( "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->setItemPosition( pos->x(), pos->y() );
}
}
addComposerLegend( newLegend );
if ( addUndoCommands )
{
pushAddRemoveCommand( newLegend, tr( "Legend added" ) );
}
}
// table
QDomNodeList composerTableList = elem.elementsByTagName( "ComposerAttributeTable" );
for ( int i = 0; i < composerTableList.size(); ++i )
{
QDomElement currentComposerTableElem = composerTableList.at( i ).toElement();
QgsComposerAttributeTable* newTable = new QgsComposerAttributeTable( this );
newTable->readXML( currentComposerTableElem, doc );
if ( pos )
{
if ( pasteInPlace )
{
newTable->setItemPosition( newTable->pos().x(), fmod( newTable->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
newTable->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
}
else
{
newTable->setItemPosition( pos->x(), pos->y() );
}
}
addComposerTable( newTable );
if ( addUndoCommands )
{
pushAddRemoveCommand( newTable, tr( "Table added" ) );
}
}
// html
QDomNodeList composerHtmlList = elem.elementsByTagName( "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 );
}
// groups (must be last as it references uuids of above items)
QDomNodeList groupList = elem.elementsByTagName( "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 );
}
}
void QgsComposition::addItemToZList( QgsComposerItem* item )
{
if ( !item )
{
return;
}
mItemZList.push_back( item );
item->setZValue( mItemZList.size() );
}
void QgsComposition::removeItemFromZList( QgsComposerItem* item )
{
if ( !item )
{
return;
}
mItemZList.removeAll( item );
}
void QgsComposition::raiseSelectedItems()
{
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
QList<QgsComposerItem*>::iterator it = selectedItems.begin();
for ( ; it != selectedItems.end(); ++it )
{
raiseItem( *it );
}
//update all positions
updateZValues();
update();
}
void QgsComposition::raiseItem( QgsComposerItem* item )
{
//search item
QMutableLinkedListIterator<QgsComposerItem*> it( mItemZList );
if ( it.findNext( item ) )
{
if ( it.hasNext() )
{
it.remove();
it.next();
it.insert( item );
}
}
}
QgsComposerItem* QgsComposition::getComposerItemAbove( QgsComposerItem* item )
{
//search item z list for selected item
QLinkedListIterator<QgsComposerItem*> it( mItemZList );
if ( it.findNext( item ) )
{
//return next item (list is sorted from lowest->highest items)
if ( it.hasNext() )
{
return it.next();
}
}
return 0;
}
QgsComposerItem* QgsComposition::getComposerItemBelow( QgsComposerItem* item )
{
//search item z list for selected item
QLinkedListIterator<QgsComposerItem*> it( mItemZList );
if ( it.findNext( item ) )
{
//move position to before selected item
it.previous();
//now find previous item, since list is sorted from lowest->highest items
if ( it.hasPrevious() )
{
return it.previous();
}
}
return 0;
}
void QgsComposition::selectNextByZOrder( ZValueDirection direction )
{
QgsComposerItem* previousSelectedItem = 0;
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
if ( selectedItems.size() > 0 )
{
previousSelectedItem = selectedItems.at( 0 );
}
if ( !previousSelectedItem )
{
return;
}
//select item with target z value
QgsComposerItem* selectedItem = 0;
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
clearSelection();
selectedItem->setSelected( true );
emit selectedItemChanged( selectedItem );
}
void QgsComposition::lowerSelectedItems()
{
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
QList<QgsComposerItem*>::iterator it = selectedItems.begin();
for ( ; it != selectedItems.end(); ++it )
{
lowerItem( *it );
}
//update all positions
updateZValues();
update();
}
void QgsComposition::lowerItem( QgsComposerItem* item )
{
//search item
QMutableLinkedListIterator<QgsComposerItem*> it( mItemZList );
if ( it.findNext( item ) )
{
it.previous();
if ( it.hasPrevious() )
{
it.remove();
it.previous();
it.insert( item );
}
}
}
void QgsComposition::moveSelectedItemsToTop()
{
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
QList<QgsComposerItem*>::iterator it = selectedItems.begin();
for ( ; it != selectedItems.end(); ++it )
{
moveItemToTop( *it );
}
//update all positions
updateZValues();
update();
}
void QgsComposition::moveItemToTop( QgsComposerItem* item )
{
//search item
QMutableLinkedListIterator<QgsComposerItem*> it( mItemZList );
if ( it.findNext( item ) )
{
it.remove();
}
mItemZList.push_back( item );
}
void QgsComposition::moveSelectedItemsToBottom()
{
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
QList<QgsComposerItem*>::iterator it = selectedItems.begin();
for ( ; it != selectedItems.end(); ++it )
{
moveItemToBottom( *it );
}
//update all positions
updateZValues();
update();
}
void QgsComposition::moveItemToBottom( QgsComposerItem* item )
{
//search item
QMutableLinkedListIterator<QgsComposerItem*> it( mItemZList );
if ( it.findNext( item ) )
{
it.remove();
}
mItemZList.push_front( 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, "", parentCommand );
subcommand->savePreviousState();
( *align_it )->setPos( minXCoordinate, ( *align_it )->pos().y() );
subcommand->saveAfterState();
}
mUndoStack.push( parentCommand );
}
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, "", parentCommand );
subcommand->savePreviousState();
( *align_it )->setPos( averageXCoord - ( *align_it )->rect().width() / 2.0, ( *align_it )->pos().y() );
subcommand->saveAfterState();
}
mUndoStack.push( parentCommand );
}
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, "", parentCommand );
subcommand->savePreviousState();
( *align_it )->setPos( maxXCoordinate - ( *align_it )->rect().width(), ( *align_it )->pos().y() );
subcommand->saveAfterState();
}
mUndoStack.push( parentCommand );
}
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, "", parentCommand );
subcommand->savePreviousState();
( *align_it )->setPos(( *align_it )->pos().x(), minYCoordinate );
subcommand->saveAfterState();
}
mUndoStack.push( parentCommand );
}
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, "", parentCommand );
subcommand->savePreviousState();
( *align_it )->setPos(( *align_it )->pos().x(), averageYCoord - ( *align_it )->rect().height() / 2 );
subcommand->saveAfterState();
}
mUndoStack.push( parentCommand );
}
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, "", parentCommand );
subcommand->savePreviousState();
( *align_it )->setPos(( *align_it )->pos().x(), maxYCoord - ( *align_it )->rect().height() );
subcommand->saveAfterState();
}
mUndoStack.push( parentCommand );
}
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, "", parentCommand );
subcommand->savePreviousState();
( *itemIter )->setPositionLock( true );
subcommand->saveAfterState();
}
clearSelection();
mUndoStack.push( parentCommand );
}
void QgsComposition::unlockAllItems()
{
//unlock all items in composer
QUndoCommand* parentCommand = new QUndoCommand( tr( "Items unlocked" ) );
//first, clear the selection
clearSelection();
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, "", parentCommand );
subcommand->savePreviousState();
mypItem->setPositionLock( false );
//select unlocked items, same behaviour as illustrator
mypItem->setSelected( true );
emit selectedItemChanged( mypItem );
subcommand->saveAfterState();
}
}
mUndoStack.push( parentCommand );
}
void QgsComposition::updateZValues()
{
int counter = 1;
QLinkedList<QgsComposerItem*>::iterator it = mItemZList.begin();
QgsComposerItem* currentItem = 0;
QUndoCommand* parentCommand = new QUndoCommand( tr( "Item z-order changed" ) );
for ( ; it != mItemZList.end(); ++it )
{
currentItem = *it;
if ( currentItem )
{
QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *it, "", parentCommand );
subcommand->savePreviousState();
currentItem->setZValue( counter );
subcommand->saveAfterState();
}
++counter;
}
mUndoStack.push( parentCommand );
}
void QgsComposition::sortZList()
{
if ( mItemZList.size() < 2 )
{
return;
}
QLinkedList<QgsComposerItem*>::const_iterator lIt = mItemZList.constBegin();
QLinkedList<QgsComposerItem*> sortedList;
for ( ; lIt != mItemZList.constEnd(); ++lIt )
{
QLinkedList<QgsComposerItem*>::iterator insertIt = sortedList.begin();
for ( ; insertIt != sortedList.end(); ++insertIt )
{
if (( *lIt )->zValue() < ( *insertIt )->zValue() )
{
break;
}
}
sortedList.insert( insertIt, ( *lIt ) );
}
mItemZList = sortedList;
}
QPointF QgsComposition::snapPointToGrid( const QPointF& scenePoint ) const
{
if ( !mSnapToGrid || mSnapGridResolution <= 0 )
{
return scenePoint;
}
//y offset to current page
int pageNr = ( 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 = ( int )(( scenePoint.x() - mSnapGridOffsetX ) / mSnapGridResolution + 0.5 );
int yRatio = ( int )(( yPage - mSnapGridOffsetY ) / mSnapGridResolution + 0.5 );
double xSnapped = xRatio * mSnapGridResolution + mSnapGridOffsetX;
double ySnapped = yRatio * mSnapGridResolution + mSnapGridOffsetY + yOffset;
if ( abs( xSnapped - scenePoint.x() ) > mSnapGridTolerance )
{
//snap distance is outside of tolerance
xSnapped = scenePoint.x();
}
if ( abs( ySnapped - scenePoint.y() ) > mSnapGridTolerance )
{
//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()
{
QList< QGraphicsLineItem* >::iterator it = mSnapLines.begin();
for ( ; it != mSnapLines.end(); ++it )
{
removeItem(( *it ) );
delete( *it );
}
mSnapLines.clear();
}
void QgsComposition::setSnapLinesVisible( bool visible )
{
mGuidesVisible = visible;
QList< QGraphicsLineItem* >::iterator it = mSnapLines.begin();
for ( ; it != mSnapLines.end(); ++it )
{
if ( visible )
{
( *it )->show();
}
else
{
( *it )->hide();
}
}
}
QGraphicsLineItem* QgsComposition::nearestSnapLine( bool horizontal, double x, double y, double tolerance,
QList< QPair< QgsComposerItem*, QgsComposerItem::ItemPositionMode> >& snappedItems )
{
double minSqrDist = DBL_MAX;
QGraphicsLineItem* item = 0;
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( bool b )
{
mSnapToGrid = b;
updatePaperItems();
}
void QgsComposition::setGridVisible( bool b )
{
mGridVisible = b;
updatePaperItems();
}
void QgsComposition::setSnapGridResolution( double r )
{
mSnapGridResolution = r;
updatePaperItems();
}
void QgsComposition::setSnapGridTolerance( double tolerance )
{
mSnapGridTolerance = tolerance;
}
void QgsComposition::setSnapGridOffsetX( double offset )
{
mSnapGridOffsetX = offset;
updatePaperItems();
}
void QgsComposition::setSnapGridOffsetY( 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( GridStyle s )
{
mGridStyle = s;
updatePaperItems();
}
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( "/Composer/gridStyle", "Dots" ).toString();
int gridRed, gridGreen, gridBlue, gridAlpha;
gridRed = s.value( "/Composer/gridRed", 190 ).toInt();
gridGreen = s.value( "/Composer/gridGreen", 190 ).toInt();
gridBlue = s.value( "/Composer/gridBlue", 190 ).toInt();
gridAlpha = s.value( "/Composer/gridAlpha", 100 ).toInt();
QColor gridColor = QColor( gridRed, gridGreen, gridBlue, gridAlpha );
mGridPen.setColor( gridColor );
mGridPen.setWidthF( 0 );
if ( gridStyleString == "Dots" )
{
mGridStyle = Dots;
}
else if ( gridStyleString == "Crosses" )
{
mGridStyle = Crosses;
}
else
{
mGridStyle = Solid;
}
}
void QgsComposition::beginCommand( QgsComposerItem* item, const QString& commandText, QgsComposerMergeCommand::Context c )
{
delete mActiveItemCommand;
if ( !item )
{
mActiveItemCommand = 0;
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 );
QgsProject::instance()->dirty( true );
}
else
{
delete mActiveItemCommand;
}
mActiveItemCommand = 0;
}
}
void QgsComposition::cancelCommand()
{
delete mActiveItemCommand;
mActiveItemCommand = 0;
}
void QgsComposition::beginMultiFrameCommand( QgsComposerMultiFrame* multiFrame, const QString& text )
{
delete mActiveMultiFrameCommand;
mActiveMultiFrameCommand = new QgsComposerMultiFrameCommand( multiFrame, text );
mActiveMultiFrameCommand->savePreviousState();
}
void QgsComposition::endMultiFrameCommand()
{
if ( mActiveMultiFrameCommand )
{
mActiveMultiFrameCommand->saveAfterState();
if ( mActiveMultiFrameCommand->containsChange() )
{
mUndoStack.push( mActiveMultiFrameCommand );
QgsProject::instance()->dirty( true );
}
else
{
delete mActiveMultiFrameCommand;
}
mActiveMultiFrameCommand = 0;
}
}
void QgsComposition::addMultiFrame( QgsComposerMultiFrame* multiFrame )
{
mMultiFrames.insert( multiFrame );
}
void QgsComposition::removeMultiFrame( QgsComposerMultiFrame* multiFrame )
{
mMultiFrames.remove( multiFrame );
}
void QgsComposition::addComposerArrow( QgsComposerArrow* arrow )
{
addItem( arrow );
emit composerArrowAdded( arrow );
clearSelection();
arrow->setSelected( true );
emit selectedItemChanged( arrow );
}
void QgsComposition::addComposerLabel( QgsComposerLabel* label )
{
addItem( label );
emit composerLabelAdded( label );
clearSelection();
label->setSelected( true );
emit selectedItemChanged( label );
}
void QgsComposition::addComposerMap( QgsComposerMap* map, bool setDefaultPreviewStyle )
{
addItem( map );
if ( setDefaultPreviewStyle )
{
//set default preview mode to cache. Must be done here between adding composer map to scene and emiting signal
map->setPreviewMode( QgsComposerMap::Cache );
}
if ( map->previewMode() != QgsComposerMap::Rectangle )
{
map->cache();
}
emit composerMapAdded( map );
clearSelection();
map->setSelected( true );
emit selectedItemChanged( map );
}
void QgsComposition::addComposerScaleBar( QgsComposerScaleBar* scaleBar )
{
addItem( scaleBar );
emit composerScaleBarAdded( scaleBar );
clearSelection();
scaleBar->setSelected( true );
emit selectedItemChanged( scaleBar );
}
void QgsComposition::addComposerLegend( QgsComposerLegend* legend )
{
//take first available map
QList<const QgsComposerMap*> mapItemList = composerMapItems();
if ( mapItemList.size() > 0 )
{
legend->setComposerMap( mapItemList.at( 0 ) );
}
addItem( legend );
emit composerLegendAdded( legend );
clearSelection();
legend->setSelected( true );
emit selectedItemChanged( legend );
}
void QgsComposition::addComposerPicture( QgsComposerPicture* picture )
{
addItem( picture );
emit composerPictureAdded( picture );
clearSelection();
picture->setSelected( true );
emit selectedItemChanged( picture );
}
void QgsComposition::addComposerShape( QgsComposerShape* shape )
{
addItem( shape );
emit composerShapeAdded( shape );
clearSelection();
shape->setSelected( true );
emit selectedItemChanged( shape );
}
void QgsComposition::addComposerTable( QgsComposerAttributeTable* table )
{
addItem( table );
emit composerTableAdded( table );
clearSelection();
table->setSelected( true );
emit selectedItemChanged( table );
}
void QgsComposition::addComposerHtmlFrame( QgsComposerHtml* html, QgsComposerFrame* frame )
{
addItem( frame );
emit composerHtmlFrameAdded( html, frame );
clearSelection();
frame->setSelected( true );
emit selectedItemChanged( frame );
}
void QgsComposition::removeComposerItem( QgsComposerItem* item, bool createCommand )
{
QgsComposerMap* map = dynamic_cast<QgsComposerMap *>( item );
if ( !map || !map->isDrawing() ) //don't delete a composer map while it draws
{
removeItem( item );
QgsComposerItemGroup* itemGroup = dynamic_cast<QgsComposerItemGroup*>( item );
if ( itemGroup )
{
//add add/remove item command for every item in the group
QUndoCommand* parentCommand = new QUndoCommand( tr( "Remove item group" ) );
QSet<QgsComposerItem*> groupedItems = itemGroup->items();
QSet<QgsComposerItem*>::iterator it = groupedItems.begin();
for ( ; it != groupedItems.end(); ++it )
{
QgsAddRemoveItemCommand* subcommand = new QgsAddRemoveItemCommand( QgsAddRemoveItemCommand::Removed, *it, this, "", parentCommand );
connectAddRemoveCommandSignals( subcommand );
emit itemRemoved( *it );
}
undoStack()->push( parentCommand );
emit itemRemoved( itemGroup );
delete itemGroup;
}
else
{
bool frameItem = ( item->type() == QgsComposerItem::ComposerFrame );
QgsComposerMultiFrame* multiFrame = 0;
if ( createCommand )
{
if ( frameItem ) //multiframe tracks item changes
{
multiFrame = static_cast<QgsComposerFrame*>( item )->multiFrame();
item->beginItemCommand( tr( "Frame deleted" ) );
emit itemRemoved( item );
item->endItemCommand();
}
else
{
emit itemRemoved( item );
pushAddRemoveCommand( item, tr( "Item deleted" ), QgsAddRemoveItemCommand::Removed );
}
}
else
{
emit itemRemoved( item );
}
//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;
}
}
}
}
}
}
void QgsComposition::pushAddRemoveCommand( QgsComposerItem* item, const QString& text, QgsAddRemoveItemCommand::State state )
{
QgsAddRemoveItemCommand* c = new QgsAddRemoveItemCommand( state, item, this, text );
connectAddRemoveCommandSignals( c );
undoStack()->push( c );
QgsProject::instance()->dirty( 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;
}
QgsComposerAttributeTable* table = dynamic_cast<QgsComposerAttributeTable*>( item );
if ( table )
{
emit composerTableAdded( table );
emit selectedItemChanged( table );
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 );
}
emit selectedItemChanged( frame );
return;
}
}
void QgsComposition::updatePaperItems()
{
QList< QgsPaperItem* >::iterator paperIt = mPages.begin();
for ( ; paperIt != mPages.end(); ++paperIt )
{
( *paperIt )->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 );
QgsExpression::setSpecialColumn( "$numpages", QVariant(( int )mPages.size() ) );
}
void QgsComposition::removePaperItems()
{
for ( int i = 0; i < mPages.size(); ++i )
{
delete mPages.at( i );
}
mPages.clear();
QgsExpression::setSpecialColumn( "$numpages", QVariant(( int )0 ) );
}
void QgsComposition::deleteAndRemoveMultiFrames()
{
QSet<QgsComposerMultiFrame*>::iterator multiFrameIt = mMultiFrames.begin();
for ( ; multiFrameIt != mMultiFrames.end(); ++multiFrameIt )
{
delete *multiFrameIt;
}
mMultiFrames.clear();
}
void QgsComposition::beginPrintAsPDF( QPrinter& printer, const QString& file )
{
printer.setOutputFormat( QPrinter::PdfFormat );
printer.setOutputFileName( file );
printer.setPaperSize( QSizeF( paperWidth(), paperHeight() ), QPrinter::Millimeter );
QgsPaintEngineHack::fixEngineFlags( printer.paintEngine() );
}
void QgsComposition::exportAsPDF( const QString& file )
{
QPrinter printer;
beginPrintAsPDF( printer, file );
print( printer );
}
void QgsComposition::doPrint( QPrinter& printer, QPainter& p )
{
//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;
if ( mPrintAsRaster )
{
for ( int i = fromPage; i <= toPage; ++i )
{
if ( i > fromPage )
{
printer.newPage();
}
QImage image = printPageAsRaster( i );
if ( !image.isNull() )
{
QRectF targetArea( 0, 0, image.width(), image.height() );
p.drawImage( targetArea, image, targetArea );
}
}
}
if ( !mPrintAsRaster )
{
for ( int i = fromPage; i <= toPage; ++i )
{
if ( i > fromPage )
{
printer.newPage();
}
renderPage( &p, i );
}
}
}
void QgsComposition::beginPrint( QPrinter &printer )
{
//set resolution based on composer setting
printer.setFullPage( true );
printer.setColorMode( QPrinter::Color );
//set user-defined resolution
printer.setResolution( printResolution() );
}
void QgsComposition::print( QPrinter &printer )
{
beginPrint( printer );
QPainter p( &printer );
doPrint( printer, p );
}
QImage QgsComposition::printPageAsRaster( int page )
{
//print out via QImage, code copied from on_mActionExportAsImage_activated
int width = ( int )( printResolution() * paperWidth() / 25.4 );
int height = ( int )( printResolution() * paperHeight() / 25.4 );
QImage image( QSize( width, height ), QImage::Format_ARGB32 );
if ( !image.isNull() )
{
image.setDotsPerMeterX( printResolution() / 25.4 * 1000 );
image.setDotsPerMeterY( printResolution() / 25.4 * 1000 );
image.fill( 0 );
QPainter imagePainter( &image );
renderPage( &imagePainter, page );
if ( !imagePainter.isActive() ) return QImage();
}
return image;
}
void QgsComposition::renderPage( QPainter* p, int page )
{
if ( mPages.size() <= page )
{
return;
}
QgsPaperItem* paperItem = mPages[page];
if ( !paperItem )
{
return;
}
QPaintDevice* paintDevice = p->device();
if ( !paintDevice )
{
return;
}
QRectF paperRect = QRectF( paperItem->pos().x(), paperItem->pos().y(), paperItem->rect().width(), paperItem->rect().height() );
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() ), paperRect );
//show background after rendering
setBackgroundBrush( QColor( 215, 215, 215 ) );
setSnapLinesVisible( true );
mPlotStyle = savedPlotStyle;
}
QString QgsComposition::encodeStringForXML( const QString& str )
{
QString modifiedStr( str );
modifiedStr.replace( "&", "&amp;" );
modifiedStr.replace( "\"", "&quot;" );
modifiedStr.replace( "'", "&apos;" );
modifiedStr.replace( "<", "&lt;" );
modifiedStr.replace( ">", "&gt;" );
return modifiedStr;
}
void QgsComposition::computeWorldFileParameters( double& a, double& b, double& c, double& d, double& e, double& f ) const
{
//
// Word file parameters : affine transformation parameters from pixel coordinates to map coordinates
if ( !mWorldFileMap )
{
return;
}
QRectF brect = mWorldFileMap->boundingRect();
QgsRectangle extent = mWorldFileMap->extent();
double alpha = mWorldFileMap->rotation() / 180 * M_PI;
double xr = extent.width() / brect.width();
double yr = extent.height() / brect.height();
double XC = extent.center().x();
double YC = extent.center().y();
// get the extent for the page
double xmin = extent.xMinimum() - mWorldFileMap->pos().x() * xr;
double ymax = extent.yMaximum() + mWorldFileMap->pos().y() * yr;
QgsRectangle paperExtent( xmin, ymax - paperHeight() * yr, xmin + paperWidth() * xr, ymax );
double X0 = paperExtent.xMinimum();
double Y0 = paperExtent.yMinimum();
int widthPx = ( int )( printResolution() * paperWidth() / 25.4 );
int heightPx = ( int )( printResolution() * paperHeight() / 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] = XC * ( 1 - cos( alpha ) ) + YC * sin( alpha );
r[3] = sin( alpha );
r[4] = cos( alpha );
r[5] = - XC * sin( alpha ) + YC * ( 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( QgsComposition::AtlasMode mode )
{
mAtlasMode = mode;
if ( mode == QgsComposition::AtlasOff )
{
mAtlasComposition.endRender();
}
else
{
bool atlasHasFeatures = mAtlasComposition.beginRender();
if ( ! atlasHasFeatures )
{
mAtlasMode = QgsComposition::AtlasOff;
return false;
}
}
if ( mAtlasComposition.composerMap() )
{
mAtlasComposition.composerMap()->toggleAtlasPreview();
}
update();
return true;
}
void QgsComposition::relativeResizeRect( QRectF& rectToResize, const QRectF& boundsBefore, const QRectF& boundsAfter )
{
//linearly scale rectToResize relative to the scaling from boundsBefore to boundsAfter
double left = relativePosition( rectToResize.left(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
double right = relativePosition( rectToResize.right(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
double top = relativePosition( rectToResize.top(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
double bottom = relativePosition( rectToResize.bottom(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
rectToResize.setRect( left, top, right - left, bottom - top );
}
double QgsComposition::relativePosition( double position, double beforeMin, double beforeMax, double afterMin, double afterMax )
{
//calculate parameters for linear scale between before and after ranges
double m = ( afterMax - afterMin ) / ( beforeMax - beforeMin );
double c = afterMin - ( beforeMin * m );
//return linearly scaled position
return m * position + c;
}