diff --git a/python/core/core.sip b/python/core/core.sip index a174e465062d..5b24cc0662a4 100644 --- a/python/core/core.sip +++ b/python/core/core.sip @@ -93,6 +93,7 @@ %Include qgsmapsettings.sip %Include qgsmaptopixel.sip %Include qgsmapunitscale.sip +%Include qgsmargins.sip %Include qgsmessagelog.sip %Include qgsmessageoutput.sip %Include qgsmimedatautils.sip diff --git a/python/core/qgsmargins.sip b/python/core/qgsmargins.sip new file mode 100644 index 000000000000..cdbbc7539d4e --- /dev/null +++ b/python/core/qgsmargins.sip @@ -0,0 +1,47 @@ +class QgsMargins +{ +%TypeHeaderCode +#include +%End + + public: + + QgsMargins(); + QgsMargins( double left, double top, double right, double bottom ); + + bool isNull() const; + + double left() const; + double top() const; + double right() const; + double bottom() const; + + void setLeft( double left ); + void setTop( double top ); + void setRight( double right ); + void setBottom( double bottom ); + + QgsMargins &operator+=( const QgsMargins &margins ); + QgsMargins &operator-=( const QgsMargins &margins ); + QgsMargins &operator+=( double addend ); + QgsMargins &operator-=( double subtrahend ); + QgsMargins &operator*=( double factor ); + QgsMargins &operator/=( double divisor ); + + QString toString() const; + static QgsMargins fromString( const QString& string ); +}; + +bool operator==( const QgsMargins &lhs, const QgsMargins &rhs ); +bool operator!=( const QgsMargins &lhs, const QgsMargins &rhs ); +QgsMargins operator+( const QgsMargins &m1, const QgsMargins &m2 ); +QgsMargins operator-( const QgsMargins &m1, const QgsMargins &m2 ); +QgsMargins operator+( const QgsMargins &lhs, double rhs ); +QgsMargins operator+( double lhs, const QgsMargins &rhs ); +QgsMargins operator-( const QgsMargins &lhs, double rhs ); +QgsMargins operator*( const QgsMargins &margins, double factor ); +QgsMargins operator*( double factor, const QgsMargins &margins ); +QgsMargins operator/( const QgsMargins &margins, double divisor ); +QgsMargins operator+( const QgsMargins &margins ); +QgsMargins operator-( const QgsMargins &margins ); + diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 2fabefcd7cc8..c0d19cbeea7d 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -170,6 +170,7 @@ SET(QGIS_CORE_SRCS qgsmaptopixel.cpp qgsmaptopixelgeometrysimplifier.cpp qgsmapunitscale.cpp + qgsmargins.cpp qgsmessagelog.cpp qgsmessageoutput.cpp qgsmimedatautils.cpp @@ -714,6 +715,7 @@ SET(QGIS_CORE_HDRS qgsmaptopixel.h qgsmaptopixelgeometrysimplifier.h qgsmapunitscale.h + qgsmargins.h qgsmimedatautils.h qgsmultirenderchecker.h qgsobjectcustomproperties.h diff --git a/src/core/qgsmargins.cpp b/src/core/qgsmargins.cpp new file mode 100644 index 000000000000..5f7b4e26bdf7 --- /dev/null +++ b/src/core/qgsmargins.cpp @@ -0,0 +1,37 @@ +/*************************************************************************** + qgsmargins.cpp + -------------- + Date : January 2017 + Copyright : (C) 2017 by Nyall Dawson + Email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * 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 "qgsmargins.h" + +QString QgsMargins::toString() const +{ + if ( isNull() ) + return QString(); + else + return QStringLiteral( "%1,%2,%3,%4" ).arg( qgsDoubleToString( mLeft ), qgsDoubleToString( mTop ), + qgsDoubleToString( mRight ), qgsDoubleToString( mBottom ) ); +} + +QgsMargins QgsMargins::fromString( const QString& string ) +{ + QStringList margins = string.split( ',' ); + if ( margins.count() != 4 ) + return QgsMargins(); + + return QgsMargins( margins.at( 0 ).toDouble(), + margins.at( 1 ).toDouble(), + margins.at( 2 ).toDouble(), + margins.at( 3 ).toDouble() ); +} diff --git a/src/core/qgsmargins.h b/src/core/qgsmargins.h new file mode 100644 index 000000000000..3b7e7b492bb7 --- /dev/null +++ b/src/core/qgsmargins.h @@ -0,0 +1,323 @@ +/*************************************************************************** + qgsmargins.h + ------------ + Date : January 2017 + Copyright : (C) 2017 by Nyall Dawson + Email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef QGSMARGINS_H +#define QGSMARGINS_H + +#include "qgis_core.h" +#include "qgis.h" +#include + +/** + * \ingroup core + * \class QgsMargins + * \brief The QgsMargins class defines the four margins of a rectangle. + * + * QgsMargins defines a set of four margins; left, top, right and bottom, that describe the size of the borders surrounding a rectangle. + * + * The isNull() function returns true only if all margins are set to zero. + * \note Added in QGIS 3.0 + */ + +//This class was originally based off Qt's QgsMarginsF class +//It was forked in order to always use double values, rather than qreal values. + +class CORE_EXPORT QgsMargins +{ + public: + + /** + * Constructs a margins object with all margins set to 0. + */ + QgsMargins() = default; + + /** + * Constructs margins with the given \a left, \a top, \a right, \a bottom + * @see setLeft() + * @see setRight() + * @see setTop() + * @see setBottom() + */ + QgsMargins( double left, double top, double right, double bottom ) + : mLeft( left ) + , mTop( top ) + , mRight( right ) + , mBottom( bottom ) + {} + + /** + * Returns \c true if all margins are is 0; otherwise returns false. + */ + bool isNull() const + { + return qgsDoubleNear( mLeft, 0.0 ) && qgsDoubleNear( mTop, 0.0 ) && qgsDoubleNear( mRight, 0.0 ) && qgsDoubleNear( mBottom, 0.0 ); + } + + /** + * Returns the left margin. + * @see setLeft() + */ + double left() const { return mLeft; } + + /** + * Returns the top margin. + * @see setTop() + */ + double top() const { return mTop; } + + /** + * Returns the right margin. + * @see setRight() + */ + double right() const { return mRight; } + + /** + * Returns the bottom margin. + * @see setBottom() + */ + double bottom() const { return mBottom; } + + /** + * Sets the left margin to \a left. + * @see left() + */ + void setLeft( double left ) { mLeft = left; } + + /** + * Sets the top margin to \a top. + * @see top() + */ + void setTop( double top ) { mTop = top; } + + /** + * Sets the right margin to \a right. + * @see right() + */ + void setRight( double right ) { mRight = right; } + + /** + * Sets the bottom margin to \a bottom. + * @see bottom() + */ + void setBottom( double bottom ) { mBottom = bottom; } + + /** + * Add each component of \a margins to the respective component of this object + * and returns a reference to it. + */ + inline QgsMargins &operator+=( const QgsMargins &margins ); + + /** + * Subtract each component of \a margins from the respective component of this object + * and returns a reference to it. + */ + inline QgsMargins &operator-=( const QgsMargins &margins ); + + /** + * Adds the \a addend to each component of this object and returns a reference to it. + */ + inline QgsMargins &operator+=( double addend ); + + /** + * Subtracts the \a subtrahend from each component of this object + * and returns a reference to it. + */ + inline QgsMargins &operator-=( double subtrahend ); + + /** + * Multiplies each component of this object by \a factor + * and returns a reference to it. + */ + inline QgsMargins &operator*=( double factor ); + + /** + * Multiplies each component of this object by \a factor + * and returns a reference to it. + */ + inline QgsMargins &operator/=( double divisor ); + + /** + * Returns the margins encoded to a string. + * @see fromString() + */ + QString toString() const; + + /** + * Returns a QgsMargins object decoded from a string, or a null QgsMargins + * if the string could not be interpreted as margins. + * @see toString() + */ + static QgsMargins fromString( const QString& string ); + + private: + double mLeft = 0.0; + double mTop = 0.0; + double mRight = 0.0; + double mBottom = 0.0; +}; + +/** + * Returns \c true if \a lhs and \a rhs are equal; otherwise returns \c false. + */ +inline bool operator==( const QgsMargins &lhs, const QgsMargins &rhs ) +{ + return qgsDoubleNear( lhs.left(), rhs.left() ) + && qgsDoubleNear( lhs.top(), rhs.top() ) + && qgsDoubleNear( lhs.right(), rhs.right() ) + && qgsDoubleNear( lhs.bottom(), rhs.bottom() ); +} + +/** + * Returns \c true if \a lhs and \a rhs are different; otherwise returns \c false. + */ +inline bool operator!=( const QgsMargins &lhs, const QgsMargins &rhs ) +{ + return !operator==( lhs, rhs ); +} + +/** + * Returns a QgsMargins object that is the sum of the given margins, \a m1 + * and \a m2; each component is added separately. + */ +inline QgsMargins operator+( const QgsMargins &m1, const QgsMargins &m2 ) +{ + return QgsMargins( m1.left() + m2.left(), m1.top() + m2.top(), + m1.right() + m2.right(), m1.bottom() + m2.bottom() ); +} + +/** + * Returns a QgsMargins object that is formed by subtracting \a m2 from + * \a m1; each component is subtracted separately. + */ +inline QgsMargins operator-( const QgsMargins &m1, const QgsMargins &m2 ) +{ + return QgsMargins( m1.left() - m2.left(), m1.top() - m2.top(), + m1.right() - m2.right(), m1.bottom() - m2.bottom() ); +} + +/** + * Returns a QgsMargins object that is formed by adding \a rhs to \a lhs. + */ +inline QgsMargins operator+( const QgsMargins &lhs, double rhs ) +{ + return QgsMargins( lhs.left() + rhs, lhs.top() + rhs, + lhs.right() + rhs, lhs.bottom() + rhs ); +} + +/** + * Returns a QgsMargins object that is formed by adding \a lhs to \a rhs. + */ +inline QgsMargins operator+( double lhs, const QgsMargins &rhs ) +{ + return QgsMargins( rhs.left() + lhs, rhs.top() + lhs, + rhs.right() + lhs, rhs.bottom() + lhs ); +} + +/** + * Returns a QgsMargins object that is formed by subtracting \a rhs from \a lhs. + */ +inline QgsMargins operator-( const QgsMargins &lhs, double rhs ) +{ + return QgsMargins( lhs.left() - rhs, lhs.top() - rhs, + lhs.right() - rhs, lhs.bottom() - rhs ); +} + +/** + * Returns a QgsMargins object that is formed by multiplying each component + * of the given \a margins by \a factor. + */ +inline QgsMargins operator*( const QgsMargins &margins, double factor ) +{ + return QgsMargins( margins.left() * factor, margins.top() * factor, + margins.right() * factor, margins.bottom() * factor ); +} + +/** + * Returns a QgsMargins object that is formed by multiplying each component + * of the given \a margins by \a factor. + */ +inline QgsMargins operator*( double factor, const QgsMargins &margins ) +{ + return QgsMargins( margins.left() * factor, margins.top() * factor, + margins.right() * factor, margins.bottom() * factor ); +} + +/** + * Returns a QgsMargins object that is formed by dividing the components of + * the given \a margins by the given \a divisor. + */ +inline QgsMargins operator/( const QgsMargins &margins, double divisor ) +{ + return QgsMargins( margins.left() / divisor, margins.top() / divisor, + margins.right() / divisor, margins.bottom() / divisor ); +} + +inline QgsMargins& QgsMargins::operator+=( const QgsMargins & margins ) +{ + return *this = *this + margins; +} + +inline QgsMargins& QgsMargins::operator-=( const QgsMargins & margins ) +{ + return *this = *this - margins; +} + +inline QgsMargins& QgsMargins::operator+=( double addend ) +{ + mLeft += addend; + mTop += addend; + mRight += addend; + mBottom += addend; + return *this; +} + +inline QgsMargins& QgsMargins::operator-=( double subtrahend ) +{ + mLeft -= subtrahend; + mTop -= subtrahend; + mRight -= subtrahend; + mBottom -= subtrahend; + return *this; +} + +inline QgsMargins& QgsMargins::operator*=( double factor ) +{ + return *this = *this * factor; +} + +inline QgsMargins& QgsMargins::operator/=( double divisor ) +{ + return *this = *this / divisor; +} + +/** + * Returns a QgsMargins object that is formed from all components of \a margins. + */ +inline QgsMargins operator+( const QgsMargins &margins ) +{ + return margins; +} + +/** + * Returns a QgsMargins object that is formed by negating all components of \a margins. + */ +inline QgsMargins operator-( const QgsMargins &margins ) +{ + return QgsMargins( -margins.left(), -margins.top(), -margins.right(), -margins.bottom() ); +} + +Q_DECLARE_TYPEINFO( QgsMargins, Q_MOVABLE_TYPE ); + +#endif // QGSMARGINS_H diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index 9dd9354ea77f..3987a472df2d 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -64,6 +64,7 @@ ADD_PYTHON_TEST(PyQgsJSONUtils test_qgsjsonutils.py) ADD_PYTHON_TEST(PyQgsMapCanvasAnnotationItem test_qgsmapcanvasannotationitem.py) ADD_PYTHON_TEST(PyQgsMapLayerModel test_qgsmaplayermodel.py) ADD_PYTHON_TEST(PyQgsMapUnitScale test_qgsmapunitscale.py) +ADD_PYTHON_TEST(PyQgsMargins test_qgsmargins.py) ADD_PYTHON_TEST(PyQgsMemoryProvider test_provider_memory.py) ADD_PYTHON_TEST(PyQgsMultiEditToolButton test_qgsmultiedittoolbutton.py) ADD_PYTHON_TEST(PyQgsNetworkContentFetcher test_qgsnetworkcontentfetcher.py) diff --git a/tests/src/python/test_qgsmargins.py b/tests/src/python/test_qgsmargins.py new file mode 100644 index 000000000000..78838ac36e7a --- /dev/null +++ b/tests/src/python/test_qgsmargins.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsMargins. + +.. note:: 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. +""" +__author__ = 'Nyall Dawson' +__date__ = '2017-01' +__copyright__ = 'Copyright 2017, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + + +import qgis # NOQA + +from qgis.testing import unittest +from qgis.core import QgsMargins + + +class TestQgsOptional(unittest.TestCase): + + def testGetSet(self): + margins = QgsMargins() + margins.setLeft(1.1) + self.assertEqual(margins.left(), 1.1) + margins.setTop(2.2) + self.assertEqual(margins.top(), 2.2) + margins.setBottom(3.3) + self.assertEqual(margins.bottom(), 3.3) + margins.setRight(4.4) + self.assertEqual(margins.right(), 4.4) + + margins = QgsMargins() + self.assertTrue(margins.isNull()) + margins.setLeft(5.5) + margins.setRight(5.5) + self.assertFalse(margins.isNull()) + self.assertEqual(margins, QgsMargins(5.5, 0.0, 5.5, 0.0)) + + def testOperators(self): + m1 = QgsMargins(12.1, 14.1, 16.1, 18.1) + m2 = QgsMargins(2.1, 3.1, 4.1, 5.1) + + added = m1 + m2 + self.assertAlmostEqual(added.left(), 14.2) + self.assertAlmostEqual(added.top(), 17.2) + self.assertAlmostEqual(added.right(), 20.2) + self.assertAlmostEqual(added.bottom(), 23.2) + a = QgsMargins(m1) + a += m2 + self.assertEqual(a, added) + + subtracted = m1 - m2 + self.assertAlmostEqual(subtracted.left(), 10.0) + self.assertAlmostEqual(subtracted.top(), 11.0) + self.assertAlmostEqual(subtracted.right(), 12.0) + self.assertAlmostEqual(subtracted.bottom(), 13.0) + a = QgsMargins(m1) + a -= m2 + self.assertEqual(a, subtracted) + + h = QgsMargins(m1) + h += 2.1 + self.assertAlmostEqual(h.left(), 14.2) + self.assertAlmostEqual(h.top(), 16.2) + self.assertAlmostEqual(h.right(), 18.2) + self.assertAlmostEqual(h.bottom(), 20.2) + h -= 2.1 + self.assertEqual(h, m1) + + doubled = m1 * 2.0 + self.assertEqual(doubled, QgsMargins(24.2, 28.2, 32.2, 36.2)) + self.assertEqual(2.0 * m1, doubled) + self.assertEqual(m1 * 2.0, doubled) + + a = QgsMargins(m1) + a *= 2.0 + self.assertEqual(a, doubled) + + halved = m1 / 2.0 + self.assertAlmostEqual(halved.left(), 6.05) + self.assertAlmostEqual(halved.top(), 7.05) + self.assertAlmostEqual(halved.right(), 8.05) + self.assertAlmostEqual(halved.bottom(), 9.05) + + a = QgsMargins(m1) + a /= 2.0 + self.assertEqual(a, halved) + + self.assertEqual(m1 + (-m1), QgsMargins()) + + m3 = QgsMargins(10.3, 11.4, 12.5, 13.6) + self.assertEqual(m3 + 1.1, QgsMargins(11.4, 12.5, 13.6, 14.7)) + self.assertEqual(1.1 + m3, QgsMargins(11.4, 12.5, 13.6, 14.7)) + m4 = m3 - 1.1 + self.assertAlmostEqual(m4.left(), 9.2) + self.assertAlmostEqual(m4.top(), 10.3) + self.assertAlmostEqual(m4.right(), 11.4) + self.assertAlmostEqual(m4.bottom(), 12.5) + self.assertEqual(+m3, QgsMargins(10.3, 11.4, 12.5, 13.6)) + self.assertEqual(-m3, QgsMargins(-10.3, -11.4, -12.5, -13.6)) + + def testToString(self): + # null margin + self.assertFalse(QgsMargins().toString()) + + self.assertEqual(QgsMargins(1, 2, 3, 4).toString(), '1,2,3,4') + self.assertEqual(QgsMargins(1, -2, 3, -4).toString(), '1,-2,3,-4') + + def testFromString(self): + + self.assertTrue(QgsMargins.fromString('').isNull()) + self.assertTrue(QgsMargins.fromString('not good').isNull()) + self.assertTrue(QgsMargins.fromString('1,2,3').isNull()) + self.assertTrue(QgsMargins.fromString('1,2,3,4,5').isNull()) + + self.assertEqual(QgsMargins.fromString('1,2,3,4'), QgsMargins(1, 2, 3, 4)) + self.assertEqual(QgsMargins.fromString('1,-2,3,-4'), QgsMargins(1, -2, 3, -4)) + +if __name__ == '__main__': + unittest.main()