184 changes: 179 additions & 5 deletions src/core/composer/qgscomposition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@

QgsComposition::QgsComposition( QgsMapRenderer* mapRenderer ) :
QGraphicsScene( 0 ), mMapRenderer( mapRenderer ), mPlotStyle( QgsComposition::Preview ), mPageWidth( 297 ), mPageHeight( 210 ), mSpaceBetweenPages( 10 ), mPrintAsRaster( false ), mSelectionTolerance( 0.0 ),
mSnapToGrid( false ), mSnapGridResolution( 0.0 ), mSnapGridOffsetX( 0.0 ), mSnapGridOffsetY( 0.0 ), mActiveItemCommand( 0 ), mActiveMultiFrameCommand( 0 ),
mAtlasComposition( this )
mSnapToGrid( false ), mSnapGridResolution( 0.0 ), mSnapGridOffsetX( 0.0 ), mSnapGridOffsetY( 0.0 ), mAlignmentSnap( true ), mAlignmentSnapTolerance( 2 ),
mActiveItemCommand( 0 ), mActiveMultiFrameCommand( 0 ), mAtlasComposition( this )
{
setBackgroundBrush( Qt::gray );
addPaperItem();
Expand All @@ -61,8 +61,8 @@ QgsComposition::QgsComposition( QgsMapRenderer* mapRenderer ) :

QgsComposition::QgsComposition():
QGraphicsScene( 0 ), mMapRenderer( 0 ), mPlotStyle( QgsComposition::Preview ), mPageWidth( 297 ), mPageHeight( 210 ), mSpaceBetweenPages( 10 ), mPrintAsRaster( false ),
mSelectionTolerance( 0.0 ), mSnapToGrid( false ), mSnapGridResolution( 0.0 ), mSnapGridOffsetX( 0.0 ), mSnapGridOffsetY( 0.0 ), mActiveItemCommand( 0 ), mActiveMultiFrameCommand( 0 ),
mAtlasComposition( this )
mSelectionTolerance( 0.0 ), mSnapToGrid( false ), mSnapGridResolution( 0.0 ), mSnapGridOffsetX( 0.0 ), mSnapGridOffsetY( 0.0 ), mAlignmentSnap( true ),
mAlignmentSnapTolerance( 2 ), mActiveItemCommand( 0 ), mActiveMultiFrameCommand( 0 ), mAtlasComposition( this )
{
loadSettings();
}
Expand Down Expand Up @@ -303,6 +303,9 @@ bool QgsComposition::writeXML( QDomElement& composerElem, QDomDocument& doc )
compositionElem.setAttribute( "printResolution", mPrintResolution );
compositionElem.setAttribute( "printAsRaster", mPrintAsRaster );

compositionElem.setAttribute( "alignmentSnap", mAlignmentSnap ? 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();
Expand Down Expand Up @@ -365,8 +368,11 @@ bool QgsComposition::readXML( const QDomElement& compositionElem, const QDomDocu
mSnapGridResolution = compositionElem.attribute( "snapGridResolution" ).toDouble();
mSnapGridOffsetX = compositionElem.attribute( "snapGridOffsetX" ).toDouble();
mSnapGridOffsetY = compositionElem.attribute( "snapGridOffsetY" ).toDouble();
mPrintAsRaster = compositionElem.attribute( "printAsRaster" ).toInt();

mAlignmentSnap = compositionElem.attribute( "alignmentSnap", "1" ).toInt() == 0 ? false : true;
mAlignmentSnapTolerance = compositionElem.attribute( "alignmentSnapTolerance", "2.0" ).toDouble();

mPrintAsRaster = compositionElem.attribute( "printAsRaster" ).toInt();
mPrintResolution = compositionElem.attribute( "printResolution", "300" ).toInt();

updatePaperItems();
Expand Down Expand Up @@ -976,6 +982,88 @@ QPointF QgsComposition::snapPointToGrid( const QPointF& scenePoint ) const
return QPointF( xRatio * mSnapGridResolution + mSnapGridOffsetX, yRatio * mSnapGridResolution + mSnapGridOffsetY + yOffset );
}

QPointF QgsComposition::alignItem( const QgsComposerItem* item, double& alignX, double& alignY, double dx, double dy )
{
if ( !item )
{
return QPointF();
}

double left = item->transform().dx() + dx;
double right = left + item->rect().width();
double midH = ( left + right ) / 2.0;
double top = item->transform().dy() + dy;
double bottom = top + item->rect().height();
double midV = ( top + bottom ) / 2.0;

QMap<double, const QgsComposerItem* > xAlignCoordinates;
QMap<double, const QgsComposerItem* > yAlignCoordinates;
collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates, item );

//find nearest matches x
double xItemLeft = left; //new left coordinate of the item
double xAlignCoord = 0;
double smallestDiffX = DBL_MAX;

checkNearestItem( left, xAlignCoordinates, smallestDiffX, 0, xItemLeft, xAlignCoord );
checkNearestItem( midH, xAlignCoordinates, smallestDiffX, ( left - right ) / 2.0, xItemLeft, xAlignCoord );
checkNearestItem( right, xAlignCoordinates, smallestDiffX, left - right, xItemLeft, xAlignCoord );

//find nearest matches y
double yItemTop = top; //new top coordinate of the item
double yAlignCoord = 0;
double smallestDiffY = DBL_MAX;

checkNearestItem( top, yAlignCoordinates, smallestDiffY, 0, yItemTop, yAlignCoord );
checkNearestItem( midV, yAlignCoordinates, smallestDiffY, ( top - bottom ) / 2.0, yItemTop, yAlignCoord );
checkNearestItem( bottom, yAlignCoordinates, smallestDiffY, top - bottom, yItemTop, yAlignCoord );

double xCoord = ( smallestDiffX < 5 ) ? xItemLeft : item->transform().dx() + dx;
alignX = ( smallestDiffX < 5 ) ? xAlignCoord : -1;
double yCoord = ( smallestDiffY < 5 ) ? yItemTop : item->transform().dy() + dy;
alignY = ( smallestDiffY < 5 ) ? yAlignCoord : -1;
return QPointF( xCoord, yCoord );
}

QPointF QgsComposition::alignPos( const QPointF& pos, const QgsComposerItem* excludeItem, double& alignX, double& alignY )
{
QMap<double, const QgsComposerItem* > xAlignCoordinates;
QMap<double, const QgsComposerItem* > yAlignCoordinates;
collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates, excludeItem );

double nearestX = pos.x();
double nearestY = pos.y();
if ( !nearestItem( xAlignCoordinates, pos.x(), nearestX )
|| !nearestItem( yAlignCoordinates, pos.y(), nearestY ) )
{
alignX = -1;
alignY = -1;
return pos;
}

QPointF result( pos.x(), pos.y() );
if ( abs( nearestX - pos.x() ) < mAlignmentSnapTolerance )
{
result.setX( nearestX );
alignX = nearestX;
}
else
{
alignX = -1;
}

if ( abs( nearestY - pos.y() ) < mAlignmentSnapTolerance )
{
result.setY( nearestY );
alignY = nearestY;
}
else
{
alignY = -1;
}
return result;
}

int QgsComposition::boundingRectOfSelectedItems( QRectF& bRect )
{
QList<QgsComposerItem*> selectedItems = selectedComposerItems();
Expand Down Expand Up @@ -1634,3 +1722,89 @@ QString QgsComposition::encodeStringForXML( const QString& str )
return modifiedStr;
}

void QgsComposition::collectAlignCoordinates( QMap< double, const QgsComposerItem* >& alignCoordsX, QMap< double, const QgsComposerItem* >& alignCoordsY,
const QgsComposerItem* excludeItem )
{
alignCoordsX.clear();
alignCoordsY.clear();

QList<QGraphicsItem *> itemList = items();
QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
for ( ; itemIt != itemList.end(); ++itemIt )
{
const QgsComposerItem* currentItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
if ( excludeItem )
{
if ( !currentItem || currentItem == excludeItem || currentItem->type() == QgsComposerItem::ComposerPaper )
{
continue;
}
alignCoordsX.insert( currentItem->transform().dx(), currentItem );
alignCoordsX.insert( currentItem->transform().dx() + currentItem->rect().width(), currentItem );
alignCoordsX.insert( currentItem->transform().dx() + currentItem->rect().center().x(), currentItem );
alignCoordsY.insert( currentItem->transform().dy() + currentItem->rect().top(), currentItem );
alignCoordsY.insert( currentItem->transform().dy() + currentItem->rect().center().y(), currentItem );
alignCoordsY.insert( currentItem->transform().dy() + currentItem->rect().bottom(), currentItem );
}
}
}

void QgsComposition::checkNearestItem( double checkCoord, const QMap< double, const QgsComposerItem* >& alignCoords, double& smallestDiff,
double itemCoordOffset, double& itemCoord, double& alignCoord ) const
{
double currentCoord = 0;
if ( !nearestItem( alignCoords, checkCoord, currentCoord ) )
{
return;
}

double currentDiff = abs( checkCoord - currentCoord );
if ( currentDiff < mAlignmentSnapTolerance )
{
itemCoord = currentCoord + itemCoordOffset;
alignCoord = currentCoord;
smallestDiff = currentDiff;
}
}

bool QgsComposition::nearestItem( const QMap< double, const QgsComposerItem* >& coords, double value, double& nearestValue )
{
if ( coords.size() < 1 )
{
return false;
}

QMap< double, const QgsComposerItem* >::const_iterator it = coords.lowerBound( value );
if ( it == coords.constBegin() ) //value smaller than first map value
{
nearestValue = it.key();
return true;
}
else if ( it == coords.constEnd() ) //value larger than last map value
{
--it;
nearestValue = it.key();
return true;
}
else
{
//get smaller value and larger value and return the closer one
double upperVal = it.key();
--it;
double lowerVal = it.key();

double lowerDiff = value - lowerVal;
double upperDiff = upperVal - value;
if ( lowerDiff < upperDiff )
{
nearestValue = lowerVal;
return true;
}
else
{
nearestValue = upperVal;
return true;
}
}
}

37 changes: 37 additions & 0 deletions src/core/composer/qgscomposition.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ class CORE_EXPORT QgsComposition: public QGraphicsScene
void setGridStyle( GridStyle s );
GridStyle gridStyle() const {return mGridStyle;}

void setAlignmentSnap( bool s ) { mAlignmentSnap = s; }
bool alignmentSnap() const { return mAlignmentSnap; }

void setAlignmentSnapTolerance( double t ) { mAlignmentSnapTolerance = t; }
double alignmentSnapTolerance() const { return mAlignmentSnapTolerance; }

/**Returns pointer to undo/redo command storage*/
QUndoStack* undoStack() { return &mUndoStack; }

Expand Down Expand Up @@ -236,6 +242,22 @@ class CORE_EXPORT QgsComposition: public QGraphicsScene
/**Snaps a scene coordinate point to grid*/
QPointF snapPointToGrid( const QPointF& scenePoint ) const;

/**Snaps item position to align with other items (left / middle / right or top / middle / bottom
@param itemRectangle current item rectangle
@param alignX x-coordinate of align or -1 if not aligned to x
@param alignY y-coordinate of align or -1 if not aligned to y
@param dx item shift in x direction
@param dy item shift in y direction
@return new upper left point after the align*/
QPointF alignItem( const QgsComposerItem* item, double& alignX, double& alignY, double dx = 0, double dy = 0 );

/**Snaps position to align with the boundaries of other items
@param pos position to snap
@param alignX snapped x coordinate or -1 if not snapped
@param alignY snapped y coordinate or -1 if not snapped
@return snapped position or original position if no snap*/
QPointF alignPos( const QPointF& pos, const QgsComposerItem* excludeItem, double& alignX, double& alignY );

/**Allocates new item command and saves initial state in it
@param item target item
@param commandText descriptive command text
Expand Down Expand Up @@ -342,6 +364,10 @@ class CORE_EXPORT QgsComposition: public QGraphicsScene
QPen mGridPen;
GridStyle mGridStyle;

/**Parameters for alignment snap*/
bool mAlignmentSnap;
double mAlignmentSnapTolerance;

QUndoStack mUndoStack;

QgsComposerItemCommand* mActiveItemCommand;
Expand Down Expand Up @@ -371,6 +397,17 @@ class CORE_EXPORT QgsComposition: public QGraphicsScene

static QString encodeStringForXML( const QString& str );

//helper functions for item align
void collectAlignCoordinates( QMap< double, const QgsComposerItem* >& alignCoordsX,
QMap< double, const QgsComposerItem* >& alignCoordsY, const QgsComposerItem* excludeItem );

void checkNearestItem( double checkCoord, const QMap< double, const QgsComposerItem* >& alignCoords, double& smallestDiff,
double itemCoordOffset, double& itemCoord, double& alignCoord ) const;

/**Find nearest item in coordinate map to a double.
@return true if item found, false if coords is empty*/
static bool nearestItem( const QMap< double, const QgsComposerItem* >& coords, double value, double& nearestValue );

signals:
void paperSizeChanged();
void nPagesChanged();
Expand Down
142 changes: 72 additions & 70 deletions src/ui/qgscompositionwidgetbase.ui
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>352</width>
<height>570</height>
<height>511</height>
</rect>
</property>
<property name="sizePolicy">
Expand All @@ -19,13 +19,7 @@
<property name="windowTitle">
<string>Composition</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<property name="margin">
<number>0</number>
</property>
<property name="spacing">
<number>6</number>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QScrollArea" name="scrollArea">
<property name="frameShape">
Expand All @@ -42,14 +36,11 @@
<rect>
<x>0</x>
<y>0</y>
<width>352</width>
<height>570</height>
<width>344</width>
<height>503</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<property name="margin">
<number>3</number>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
Expand Down Expand Up @@ -229,19 +220,6 @@
</layout>
</widget>
</item>
<item row="2" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="mSnapGroupBox">
<property name="sizePolicy">
Expand All @@ -253,18 +231,48 @@
<property name="title">
<string>Snapping</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="mAlignmentSnapCheckBox">
<property name="text">
<string>Alignment snap</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QDoubleSpinBox" name="mAlignmentToleranceSpinBox">
<property name="prefix">
<string>Tolerance </string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="mSnapToGridCheckBox">
<property name="text">
<string>Snap to grid</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<item row="3" column="0" colspan="2">
<widget class="QDoubleSpinBox" name="mGridResolutionSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="prefix">
<string>Spacing </string>
</property>
<property name="suffix">
<string/>
</property>
<property name="maximum">
<double>9999.000000000000000</double>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QDoubleSpinBox" name="mOffsetXSpinBox">
Expand All @@ -285,7 +293,20 @@
</item>
</layout>
</item>
<item row="4" column="0">
<item row="5" column="0" colspan="2">
<widget class="QDoubleSpinBox" name="mPenWidthSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="prefix">
<string>Pen width </string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="mGridColorLabel">
<property name="text">
<string>Grid color</string>
Expand All @@ -298,7 +319,7 @@
</property>
</widget>
</item>
<item row="4" column="1">
<item row="6" column="1">
<widget class="QgsColorButton" name="mGridColorButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
Expand All @@ -311,7 +332,7 @@
</property>
</widget>
</item>
<item row="5" column="0">
<item row="7" column="0">
<widget class="QLabel" name="mGridStyleLabel">
<property name="text">
<string>Grid style</string>
Expand All @@ -324,51 +345,32 @@
</property>
</widget>
</item>
<item row="5" column="1">
<item row="7" column="1">
<widget class="QComboBox" name="mGridStyleComboBox"/>
</item>
<item row="6" column="0" colspan="2">
<item row="8" column="0" colspan="2">
<widget class="QDoubleSpinBox" name="mSelectionToleranceSpinBox">
<property name="prefix">
<string>Selection tolerance (mm) </string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QDoubleSpinBox" name="mGridResolutionSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="prefix">
<string>Spacing </string>
</property>
<property name="suffix">
<string/>
</property>
<property name="maximum">
<double>9999.000000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QDoubleSpinBox" name="mPenWidthSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="prefix">
<string>Pen width </string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
Expand Down