mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
Add ability for QgsLayoutSnapper to snap to grid
This commit is contained in:
parent
361dd312bc
commit
5be237fdd9
@ -7,6 +7,7 @@
|
||||
************************************************************************/
|
||||
|
||||
|
||||
|
||||
class QgsLayoutSnapper
|
||||
{
|
||||
%Docstring
|
||||
@ -27,7 +28,23 @@ class QgsLayoutSnapper
|
||||
GridCrosses
|
||||
};
|
||||
|
||||
QgsLayoutSnapper();
|
||||
QgsLayoutSnapper( QgsLayout *layout );
|
||||
%Docstring
|
||||
Constructor for QgsLayoutSnapper, attached to the specified ``layout``.
|
||||
%End
|
||||
|
||||
void setSnapTolerance( const int snapTolerance );
|
||||
%Docstring
|
||||
Sets the snap ``tolerance`` (in pixels) to use when snapping.
|
||||
.. seealso:: snapTolerance()
|
||||
%End
|
||||
|
||||
int snapTolerance() const;
|
||||
%Docstring
|
||||
Returns the snap tolerance (in pixels) to use when snapping.
|
||||
.. seealso:: setSnapTolerance()
|
||||
:rtype: int
|
||||
%End
|
||||
|
||||
void setGridResolution( const QgsLayoutMeasurement &resolution );
|
||||
%Docstring
|
||||
@ -89,6 +106,45 @@ class QgsLayoutSnapper
|
||||
:rtype: GridStyle
|
||||
%End
|
||||
|
||||
bool snapToGrid() const;
|
||||
%Docstring
|
||||
Returns true if snapping to grid is enabled.
|
||||
.. seealso:: setSnapToGrid()
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
void setSnapToGrid( bool enabled );
|
||||
%Docstring
|
||||
Sets whether snapping to grid is ``enabled``.
|
||||
.. seealso:: snapToGrid()
|
||||
%End
|
||||
|
||||
QPointF snapPoint( QPointF point, double scaleFactor, bool &snapped /Out/ ) const;
|
||||
%Docstring
|
||||
Snaps a layout coordinate ``point``. If ``point`` was snapped, ``snapped`` will be set to true.
|
||||
|
||||
The ``scaleFactor`` argument should be set to the transformation from
|
||||
scalar transform from layout coordinates to pixels, i.e. the
|
||||
graphics view transform().m11() value.
|
||||
|
||||
This method considers snapping to the grid, snap lines, etc.
|
||||
:rtype: QPointF
|
||||
%End
|
||||
|
||||
QPointF snapPointToGrid( QPointF point, double scaleFactor, bool &snapped /Out/ ) const;
|
||||
%Docstring
|
||||
Snaps a layout coordinate ``point`` to the grid. If ``point``
|
||||
was snapped, ``snapped`` will be set to true.
|
||||
|
||||
The ``scaleFactor`` argument should be set to the transformation from
|
||||
scalar transform from layout coordinates to pixels, i.e. the
|
||||
graphics view transform().m11() value.
|
||||
|
||||
If snapToGrid() is disabled, this method will return the point
|
||||
unchanged.
|
||||
:rtype: QPointF
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
|
@ -20,6 +20,7 @@
|
||||
QgsLayout::QgsLayout( QgsProject *project )
|
||||
: QGraphicsScene()
|
||||
, mProject( project )
|
||||
, mSnapper( QgsLayoutSnapper( this ) )
|
||||
, mPageCollection( new QgsLayoutPageCollection( this ) )
|
||||
{
|
||||
// just to make sure - this should be the default, but maybe it'll change in some future Qt version...
|
||||
|
@ -15,10 +15,76 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgslayoutsnapper.h"
|
||||
#include "qgslayout.h"
|
||||
|
||||
QgsLayoutSnapper::QgsLayoutSnapper()
|
||||
: mGridResolution( QgsLayoutMeasurement( 10 ) )
|
||||
QgsLayoutSnapper::QgsLayoutSnapper( QgsLayout *layout )
|
||||
: mLayout( layout )
|
||||
, mGridResolution( QgsLayoutMeasurement( 10 ) )
|
||||
{
|
||||
mGridPen = QPen( QColor( 190, 190, 190, 100 ), 0 );
|
||||
mGridPen.setCosmetic( true );
|
||||
}
|
||||
|
||||
QPointF QgsLayoutSnapper::snapPoint( QPointF point, double scaleFactor, bool &snapped ) const
|
||||
{
|
||||
snapped = false;
|
||||
|
||||
// highest priority - grid
|
||||
bool snappedToGrid = false;
|
||||
QPointF res = snapPointToGrid( point, scaleFactor, snappedToGrid );
|
||||
if ( snappedToGrid )
|
||||
{
|
||||
snapped = true;
|
||||
return res;
|
||||
}
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
QPointF QgsLayoutSnapper::snapPointToGrid( QPointF point, double scaleFactor, bool &snapped ) const
|
||||
{
|
||||
snapped = false;
|
||||
if ( !mLayout || !mSnapToGrid || mGridResolution.length() <= 0 )
|
||||
{
|
||||
return point;
|
||||
}
|
||||
|
||||
//calculate y offset to current page
|
||||
QPointF pagePoint = mLayout->pageCollection()->positionOnPage( point );
|
||||
|
||||
double yPage = pagePoint.y(); //y-coordinate relative to current page
|
||||
double yAtTopOfPage = mLayout->pageCollection()->page( mLayout->pageCollection()->pageNumberForPoint( point ) )->pos().y();
|
||||
|
||||
//snap x coordinate
|
||||
double gridRes = mLayout->convertToLayoutUnits( mGridResolution );
|
||||
QPointF gridOffset = mLayout->convertToLayoutUnits( mGridOffset );
|
||||
int xRatio = static_cast< int >( ( point.x() - gridOffset.x() ) / gridRes + 0.5 ); //NOLINT
|
||||
int yRatio = static_cast< int >( ( yPage - gridOffset.y() ) / gridRes + 0.5 ); //NOLINT
|
||||
|
||||
double xSnapped = xRatio * gridRes + gridOffset.x();
|
||||
double ySnapped = yRatio * gridRes + gridOffset.y() + yAtTopOfPage;
|
||||
|
||||
//convert snap tolerance from pixels to layout units
|
||||
double alignThreshold = mTolerance / scaleFactor;
|
||||
|
||||
if ( fabs( xSnapped - point.x() ) > alignThreshold )
|
||||
{
|
||||
//snap distance is outside of tolerance
|
||||
xSnapped = point.x();
|
||||
}
|
||||
else
|
||||
{
|
||||
snapped = true;
|
||||
}
|
||||
if ( fabs( ySnapped - point.y() ) > alignThreshold )
|
||||
{
|
||||
//snap distance is outside of tolerance
|
||||
ySnapped = point.y();
|
||||
}
|
||||
else
|
||||
{
|
||||
snapped = true;
|
||||
}
|
||||
|
||||
return QPointF( xSnapped, ySnapped );
|
||||
}
|
||||
|
@ -21,6 +21,8 @@
|
||||
#include "qgslayoutpoint.h"
|
||||
#include <QPen>
|
||||
|
||||
class QgsLayout;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* \class QgsLayoutSnapper
|
||||
@ -41,7 +43,22 @@ class CORE_EXPORT QgsLayoutSnapper
|
||||
GridCrosses //! Crosses
|
||||
};
|
||||
|
||||
QgsLayoutSnapper();
|
||||
/**
|
||||
* Constructor for QgsLayoutSnapper, attached to the specified \a layout.
|
||||
*/
|
||||
QgsLayoutSnapper( QgsLayout *layout );
|
||||
|
||||
/**
|
||||
* Sets the snap \a tolerance (in pixels) to use when snapping.
|
||||
* \see snapTolerance()
|
||||
*/
|
||||
void setSnapTolerance( const int snapTolerance ) { mTolerance = snapTolerance; }
|
||||
|
||||
/**
|
||||
* Returns the snap tolerance (in pixels) to use when snapping.
|
||||
* \see setSnapTolerance()
|
||||
*/
|
||||
int snapTolerance() const { return mTolerance; }
|
||||
|
||||
/**
|
||||
* Sets the page/snap grid \a resolution.
|
||||
@ -99,13 +116,55 @@ class CORE_EXPORT QgsLayoutSnapper
|
||||
*/
|
||||
GridStyle gridStyle() const { return mGridStyle; }
|
||||
|
||||
/**
|
||||
* Returns true if snapping to grid is enabled.
|
||||
* \see setSnapToGrid()
|
||||
*/
|
||||
bool snapToGrid() const { return mSnapToGrid; }
|
||||
|
||||
/**
|
||||
* Sets whether snapping to grid is \a enabled.
|
||||
* \see snapToGrid()
|
||||
*/
|
||||
void setSnapToGrid( bool enabled ) { mSnapToGrid = enabled; }
|
||||
|
||||
/**
|
||||
* Snaps a layout coordinate \a point. If \a point was snapped, \a snapped will be set to true.
|
||||
*
|
||||
* The \a scaleFactor argument should be set to the transformation from
|
||||
* scalar transform from layout coordinates to pixels, i.e. the
|
||||
* graphics view transform().m11() value.
|
||||
*
|
||||
* This method considers snapping to the grid, snap lines, etc.
|
||||
*/
|
||||
QPointF snapPoint( QPointF point, double scaleFactor, bool &snapped SIP_OUT ) const;
|
||||
|
||||
/**
|
||||
* Snaps a layout coordinate \a point to the grid. If \a point
|
||||
* was snapped, \a snapped will be set to true.
|
||||
*
|
||||
* The \a scaleFactor argument should be set to the transformation from
|
||||
* scalar transform from layout coordinates to pixels, i.e. the
|
||||
* graphics view transform().m11() value.
|
||||
*
|
||||
* If snapToGrid() is disabled, this method will return the point
|
||||
* unchanged.
|
||||
*/
|
||||
QPointF snapPointToGrid( QPointF point, double scaleFactor, bool &snapped SIP_OUT ) const;
|
||||
|
||||
private:
|
||||
|
||||
QgsLayout *mLayout = nullptr;
|
||||
|
||||
int mTolerance = 5;
|
||||
|
||||
QgsLayoutMeasurement mGridResolution;
|
||||
QgsLayoutPoint mGridOffset;
|
||||
QPen mGridPen;
|
||||
GridStyle mGridStyle = GridLines;
|
||||
|
||||
bool mSnapToGrid = false;
|
||||
|
||||
};
|
||||
|
||||
#endif //QGSLAYOUTSNAPPER_H
|
||||
|
@ -19,8 +19,9 @@ from qgis.core import (QgsProject,
|
||||
QgsLayoutSnapper,
|
||||
QgsLayoutMeasurement,
|
||||
QgsUnitTypes,
|
||||
QgsLayoutPoint)
|
||||
from qgis.PyQt.QtCore import QRectF
|
||||
QgsLayoutPoint,
|
||||
QgsLayoutItemPage)
|
||||
from qgis.PyQt.QtCore import QRectF, QPointF
|
||||
from qgis.PyQt.QtGui import (QTransform,
|
||||
QPen,
|
||||
QColor)
|
||||
@ -33,7 +34,9 @@ start_app()
|
||||
class TestQgsLayoutSnapper(unittest.TestCase):
|
||||
|
||||
def testGettersSetters(self):
|
||||
s = QgsLayoutSnapper()
|
||||
p = QgsProject()
|
||||
l = QgsLayout(p)
|
||||
s = QgsLayoutSnapper(l)
|
||||
s.setGridResolution(QgsLayoutMeasurement(5, QgsUnitTypes.LayoutPoints))
|
||||
self.assertEqual(s.gridResolution().length(), 5.0)
|
||||
self.assertEqual(s.gridResolution().units(), QgsUnitTypes.LayoutPoints)
|
||||
@ -49,6 +52,95 @@ class TestQgsLayoutSnapper(unittest.TestCase):
|
||||
s.setGridStyle(QgsLayoutSnapper.GridDots)
|
||||
self.assertEqual(s.gridStyle(), QgsLayoutSnapper.GridDots)
|
||||
|
||||
s.setSnapToGrid(False)
|
||||
self.assertFalse(s.snapToGrid())
|
||||
s.setSnapToGrid(True)
|
||||
self.assertTrue(s.snapToGrid())
|
||||
|
||||
s.setSnapTolerance(15)
|
||||
self.assertEqual(s.snapTolerance(), 15)
|
||||
|
||||
def testSnapPointToGrid(self):
|
||||
p = QgsProject()
|
||||
l = QgsLayout(p)
|
||||
# need a page to snap to grid
|
||||
page = QgsLayoutItemPage(l)
|
||||
page.setPageSize('A4')
|
||||
l.pageCollection().addPage(page)
|
||||
s = QgsLayoutSnapper(l)
|
||||
|
||||
s.setGridResolution(QgsLayoutMeasurement(5, QgsUnitTypes.LayoutMillimeters))
|
||||
s.setSnapToGrid(True)
|
||||
s.setSnapTolerance(1)
|
||||
|
||||
point, snapped = s.snapPointToGrid(QPointF(1, 1), 1)
|
||||
self.assertTrue(snapped)
|
||||
self.assertEqual(point, QPointF(0, 0))
|
||||
|
||||
point, snapped = s.snapPointToGrid(QPointF(9, 1), 1)
|
||||
self.assertTrue(snapped)
|
||||
self.assertEqual(point, QPointF(10, 0))
|
||||
|
||||
point, snapped = s.snapPointToGrid(QPointF(1, 11), 1)
|
||||
self.assertTrue(snapped)
|
||||
self.assertEqual(point, QPointF(0, 10))
|
||||
|
||||
point, snapped = s.snapPointToGrid(QPointF(13, 11), 1)
|
||||
self.assertTrue(snapped)
|
||||
self.assertEqual(point, QPointF(13, 10))
|
||||
|
||||
point, snapped = s.snapPointToGrid(QPointF(11, 13), 1)
|
||||
self.assertTrue(snapped)
|
||||
self.assertEqual(point, QPointF(10, 13))
|
||||
|
||||
point, snapped = s.snapPointToGrid(QPointF(13, 23), 1)
|
||||
self.assertFalse(snapped)
|
||||
self.assertEqual(point, QPointF(13, 23))
|
||||
|
||||
# grid disabled
|
||||
s.setSnapToGrid(False)
|
||||
point, snapped = s.snapPointToGrid(QPointF(1, 1), 1)
|
||||
self.assertFalse(snapped)
|
||||
self.assertEqual(point, QPointF(1, 1))
|
||||
s.setSnapToGrid(True)
|
||||
|
||||
# with different pixel scale
|
||||
point, snapped = s.snapPointToGrid(QPointF(0.5, 0.5), 1)
|
||||
self.assertTrue(snapped)
|
||||
self.assertEqual(point, QPointF(0, 0))
|
||||
point, snapped = s.snapPointToGrid(QPointF(0.5, 0.5), 3)
|
||||
self.assertFalse(snapped)
|
||||
self.assertEqual(point, QPointF(0.5, 0.5))
|
||||
|
||||
# with offset grid
|
||||
s.setGridOffset(QgsLayoutPoint(2, 0))
|
||||
point, snapped = s.snapPointToGrid(QPointF(13, 23), 1)
|
||||
self.assertTrue(snapped)
|
||||
self.assertEqual(point, QPointF(12, 23))
|
||||
|
||||
|
||||
def testSnapPoint(self):
|
||||
p = QgsProject()
|
||||
l = QgsLayout(p)
|
||||
page = QgsLayoutItemPage(l)
|
||||
page.setPageSize('A4')
|
||||
l.pageCollection().addPage(page)
|
||||
s = QgsLayoutSnapper(l)
|
||||
|
||||
# first test snapping to grid
|
||||
s.setGridResolution(QgsLayoutMeasurement(5, QgsUnitTypes.LayoutMillimeters))
|
||||
s.setSnapToGrid(True)
|
||||
s.setSnapTolerance(1)
|
||||
|
||||
point, snapped = s.snapPoint(QPointF(1, 1), 1)
|
||||
self.assertTrue(snapped)
|
||||
self.assertEqual(point, QPointF(0, 0))
|
||||
|
||||
s.setSnapToGrid(False)
|
||||
point, snapped = s.snapPoint(QPointF(1, 1), 1)
|
||||
self.assertFalse(snapped)
|
||||
self.assertEqual(point, QPointF(1, 1))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
Loading…
x
Reference in New Issue
Block a user