[FEATURE][API] New class QgsBookmarkManager

Attached to QgsProject, this provides a stable, supported method
of managing project bookmarks (vs the old undocumented, not stable
approach of directly manipulating project keys)
This commit is contained in:
Nyall Dawson 2019-09-02 10:11:59 +10:00
parent 25ffc9f1c7
commit d8300987e8
9 changed files with 725 additions and 1 deletions

View File

@ -0,0 +1,224 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsbookmarkmanager.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsBookmark
{
%Docstring
Represents a spatial bookmark, with a name, CRS and extent.
QgsBookmark objects are typically used alongside the QgsBookmarkManager class,
which handles storage of a set of bookmarks.
.. versionadded:: 3.10
%End
%TypeHeaderCode
#include "qgsbookmarkmanager.h"
%End
public:
QgsBookmark();
%Docstring
Default constructor, creates an empty bookmark.
%End
QString id() const;
%Docstring
Returns the bookmark's unique ID.
.. seealso:: :py:func:`setId`
%End
void setId( const QString &id );
%Docstring
Sets the bookmark's unique ``id``.
.. seealso:: :py:func:`id`
%End
QString name() const;
%Docstring
Returns the bookmark's name, which is a user-visible string identifying
the bookmark.
.. seealso:: :py:func:`setName`
%End
void setName( const QString &name );
%Docstring
Sets the bookmark's ``name``, which is a user-visible string identifying
the bookmark.
.. seealso:: :py:func:`name`
%End
QgsReferencedRectangle extent() const;
%Docstring
Returns the bookmark's spatial extent.
.. seealso:: :py:func:`setExtent`
%End
void setExtent( const QgsReferencedRectangle &extent );
%Docstring
Sets the bookmark's spatial ``extent``.
.. seealso:: :py:func:`extent`
%End
static QgsBookmark fromXml( const QDomElement &element, const QDomDocument &doc );
%Docstring
Creates a bookmark using the properties from a DOM ``element``.
.. seealso:: :py:func:`writeXml`
%End
QDomElement writeXml( QDomDocument &doc ) const;
%Docstring
Returns a DOM element representing the bookmark's properties.
.. seealso:: :py:func:`fromXml`
%End
SIP_PYOBJECT __repr__();
%MethodCode
QString str = QStringLiteral( "<QgsBookmark: '%1' (%2 - %3)>" ).arg( sipCpp->name(), sipCpp->extent().asWktCoordinates(), sipCpp->extent().crs().authid() );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
%End
};
class QgsBookmarkManager : QObject
{
%Docstring
Manages storage of a set of bookmarks.
QgsBookmarkManager handles the storage, serializing and deserializing
of geographic bookmarks. Usually this class is not constructed directly, but
rather accessed through a QgsProject via :py:func:`QgsProject.bookmarkManager()`
.. versionadded:: 3.10
%End
%TypeHeaderCode
#include "qgsbookmarkmanager.h"
%End
public:
explicit QgsBookmarkManager( QgsProject *project /TransferThis/ = 0 );
%Docstring
Constructor for QgsBookmarkManager, with the specified ``parent`` project.
%End
~QgsBookmarkManager();
QString addBookmark( const QgsBookmark &bookmark, bool *ok /Out/ = 0 );
%Docstring
Adds a ``bookmark`` to the manager.
:param bookmark: the bookmark to add
:return: - The bookmark's ID (or newly generated ID, if no ID was originally set and one was automatically generated)
- ok: will be set to ``True`` if the bookmark was successfully added, or ``False`` if the bookmark could not be added (eg as a result of a duplicate bookmark ID).
.. seealso:: :py:func:`removeBookmark`
.. seealso:: :py:func:`bookmarkAdded`
%End
bool removeBookmark( const QString &id );
%Docstring
Removes the bookmark with matching ``id`` from the manager.
Returns ``True`` if the removal was successful, or ``False`` if the removal failed (eg as a result
of removing a bookmark which is not contained in the manager).
.. seealso:: :py:func:`addBookmark`
.. seealso:: :py:func:`bookmarkRemoved`
.. seealso:: :py:func:`bookmarkAboutToBeRemoved`
.. seealso:: :py:func:`clear`
%End
void clear();
%Docstring
Removes and deletes all bookmarks from the manager.
.. seealso:: :py:func:`removeBookmark`
%End
QList< QgsBookmark > bookmarks() const;
%Docstring
Returns a list of all bookmarks contained in the manager.
%End
QgsBookmark bookmarkById( const QString &id ) const;
%Docstring
Returns the bookmark with a matching ``id``, or an empty bookmark if no matching bookmarks
were found.
%End
bool readXml( const QDomElement &element, const QDomDocument &doc );
%Docstring
Reads the manager's state from a DOM element, restoring all bookmarks
present in the XML document.
.. seealso:: :py:func:`writeXml`
%End
QDomElement writeXml( QDomDocument &doc ) const;
%Docstring
Returns a DOM element representing the state of the manager.
.. seealso:: :py:func:`readXml`
%End
signals:
void bookmarkAboutToBeAdded( const QString &id );
%Docstring
Emitted when a bookmark is about to be added to the manager
%End
void bookmarkAdded( const QString &id );
%Docstring
Emitted when a bookmark has been added to the manager
%End
void bookmarkRemoved( const QString &id );
%Docstring
Emitted when a bookmark was removed from the manager
%End
void bookmarkAboutToBeRemoved( const QString &id );
%Docstring
Emitted when a bookmark is about to be removed from the manager
%End
void bookmarkRenamed( const QgsBookmark &bookmark, const QString &newName );
%Docstring
Emitted when a bookmark is renamed
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsbookmarkmanager.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -555,6 +555,15 @@ the project.
.. versionadded:: 3.0
%End
QgsBookmarkManager *bookmarkManager();
%Docstring
Returns the project's bookmark manager, which manages bookmarks within
the project.
.. versionadded:: 3.10
%End
QgsLayerTree *layerTreeRoot() const;
%Docstring
Returns pointer to the root (invisible) node of the project's layer tree

View File

@ -341,6 +341,7 @@
%Include auto_generated/qgsanimatedicon.sip
%Include auto_generated/qgsauxiliarystorage.sip
%Include auto_generated/qgsblockingnetworkrequest.sip
%Include auto_generated/qgsbookmarkmanager.sip
%Include auto_generated/qgsbrowsermodel.sip
%Include auto_generated/qgsbrowserproxymodel.sip
%Include auto_generated/qgscoordinatereferencesystem.sip

View File

@ -191,6 +191,7 @@ SET(QGIS_CORE_SRCS
qgsauxiliarystorage.cpp
qgsbearingutils.cpp
qgsblockingnetworkrequest.cpp
qgsbookmarkmanager.cpp
qgsbrowsermodel.cpp
qgsbrowserproxymodel.cpp
qgscachedfeatureiterator.cpp
@ -651,6 +652,7 @@ SET(QGIS_CORE_MOC_HDRS
qgsanimatedicon.h
qgsauxiliarystorage.h
qgsblockingnetworkrequest.h
qgsbookmarkmanager.h
qgsbrowsermodel.h
qgsbrowserproxymodel.h
qgscoordinatereferencesystem.h

View File

@ -0,0 +1,226 @@
/***************************************************************************
qgsbookmarkmanager.cpp
--------------------
Date : September 2019
Copyright : (C) 2019 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 "qgsbookmarkmanager.h"
#include "qgsproject.h"
#include <QUuid>
//
// QgsBookMark
//
QString QgsBookmark::id() const
{
return mId;
}
void QgsBookmark::setId( const QString &id )
{
mId = id;
}
QgsBookmark QgsBookmark::fromXml( const QDomElement &element, const QDomDocument & )
{
QgsBookmark b;
b.setId( element.attribute( QStringLiteral( "id" ) ) );
b.setName( element.attribute( QStringLiteral( "name" ) ) );
const QgsRectangle e = QgsRectangle::fromWkt( element.attribute( QStringLiteral( "extent" ) ) );
QgsCoordinateReferenceSystem crs;
crs.readXml( element );
b.setExtent( QgsReferencedRectangle( e, crs ) );
return b;
}
QDomElement QgsBookmark::writeXml( QDomDocument &doc ) const
{
QDomElement bookmarkElem = doc.createElement( QStringLiteral( "Bookmark" ) );
bookmarkElem.setAttribute( QStringLiteral( "id" ), mId );
bookmarkElem.setAttribute( QStringLiteral( "name" ), mName );
bookmarkElem.setAttribute( QStringLiteral( "extent" ), mExtent.asWktPolygon() );
mExtent.crs().writeXml( bookmarkElem, doc );
return bookmarkElem;
}
QString QgsBookmark::name() const
{
return mName;
}
void QgsBookmark::setName( const QString &name )
{
mName = name;
}
QgsReferencedRectangle QgsBookmark::extent() const
{
return mExtent;
}
void QgsBookmark::setExtent( const QgsReferencedRectangle &extent )
{
mExtent = extent;
}
QgsBookmarkManager::QgsBookmarkManager( QgsProject *project )
: QObject( project )
, mProject( project )
{
}
QgsBookmarkManager::~QgsBookmarkManager()
{
clear();
}
QString QgsBookmarkManager::addBookmark( const QgsBookmark &b, bool *ok )
{
if ( ok )
*ok = false;
QgsBookmark bookmark = b;
if ( bookmark.id().isEmpty() )
bookmark.setId( QUuid::createUuid().toString() );
else
{
// check for duplicate ID
for ( const QgsBookmark &b : qgis::as_const( mBookmarks ) )
{
if ( b.id() == bookmark.id() )
{
return QString();
}
}
}
if ( ok )
*ok = true;
emit bookmarkAboutToBeAdded( bookmark.id() );
mBookmarks << bookmark;
emit bookmarkAdded( bookmark.id() );
mProject->setDirty( true );
return bookmark.id();
}
bool QgsBookmarkManager::removeBookmark( const QString &id )
{
if ( id.isEmpty() )
return false;
int pos = -1;
int i = 0;
for ( const QgsBookmark &b : qgis::as_const( mBookmarks ) )
{
if ( b.id() == id )
{
pos = i;
break;
}
i++;
}
if ( pos < 0 )
return false;
emit bookmarkAboutToBeRemoved( id );
mBookmarks.removeAt( pos );
emit bookmarkRemoved( id );
mProject->setDirty( true );
return true;
}
void QgsBookmarkManager::clear()
{
const QList< QgsBookmark > bookmarks = mBookmarks;
for ( const QgsBookmark &b : bookmarks )
{
removeBookmark( b.id() );
}
}
QList<QgsBookmark> QgsBookmarkManager::bookmarks() const
{
return mBookmarks;
}
QgsBookmark QgsBookmarkManager::bookmarkById( const QString &id ) const
{
for ( const QgsBookmark &b : mBookmarks )
{
if ( b.id() == id )
return b;
}
return QgsBookmark();
}
bool QgsBookmarkManager::readXml( const QDomElement &element, const QDomDocument &doc )
{
clear();
QDomElement bookmarksElem = element;
if ( element.tagName() != QStringLiteral( "Bookmarks" ) )
{
bookmarksElem = element.firstChildElement( QStringLiteral( "Bookmarks" ) );
}
bool result = true;
if ( bookmarksElem.isNull() )
{
// handle legacy projects
const int count = QgsProject::instance()->readNumEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/count" ) );
for ( int i = 0; i < count; ++i )
{
const double minX = QgsProject::instance()->readDoubleEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/MinX" ).arg( i ) );
const double minY = QgsProject::instance()->readDoubleEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/MinY" ).arg( i ) );
const double maxX = QgsProject::instance()->readDoubleEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/MaxX" ).arg( i ) );
const double maxY = QgsProject::instance()->readDoubleEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/MaxY" ).arg( i ) );
const long srid = QgsProject::instance()->readNumEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/SRID" ).arg( i ) );
QgsBookmark b;
b.setId( QStringLiteral( "bookmark_%1" ).arg( i ) );
b.setName( QgsProject::instance()->readEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/Name" ).arg( i ) ) );
b.setExtent( QgsReferencedRectangle( QgsRectangle( minX, minY, maxX, maxY ), QgsCoordinateReferenceSystem::fromSrsId( srid ) ) );
bool added = false;
addBookmark( b, &added );
result = added && result;
}
return result;
}
//restore each
QDomNodeList bookmarkNodes = element.elementsByTagName( QStringLiteral( "Bookmark" ) );
for ( int i = 0; i < bookmarkNodes.size(); ++i )
{
QgsBookmark b = QgsBookmark::fromXml( bookmarkNodes.at( i ).toElement(), doc );
bool added = false;
addBookmark( b, &added );
result = added && result;
}
return result;
}
QDomElement QgsBookmarkManager::writeXml( QDomDocument &doc ) const
{
QDomElement bookmarksElem = doc.createElement( QStringLiteral( "Bookmarks" ) );
for ( const QgsBookmark &b : mBookmarks )
{
QDomElement bookmarkElem = b.writeXml( doc );
bookmarksElem.appendChild( bookmarkElem );
}
return bookmarksElem;
}

View File

@ -0,0 +1,219 @@
/***************************************************************************
qgsbookmarkmanager.h
------------------
Date : Septemeber 2019
Copyright : (C) 2019 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 QGSBOOKMARKMANAGER_H
#define QGSBOOKMARKMANAGER_H
#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgsreferencedgeometry.h"
#include <QObject>
class QgsProject;
/**
* \ingroup core
* \class QgsBookmark
*
* \brief Represents a spatial bookmark, with a name, CRS and extent.
*
* QgsBookmark objects are typically used alongside the QgsBookmarkManager class,
* which handles storage of a set of bookmarks.
*
* \since QGIS 3.10
*/
class CORE_EXPORT QgsBookmark
{
public:
/**
* Default constructor, creates an empty bookmark.
*/
QgsBookmark() = default;
/**
* Returns the bookmark's unique ID.
* \see setId()
*/
QString id() const;
/**
* Sets the bookmark's unique \a id.
* \see id()
*/
void setId( const QString &id );
/**
* Returns the bookmark's name, which is a user-visible string identifying
* the bookmark.
* \see setName()
*/
QString name() const;
/**
* Sets the bookmark's \a name, which is a user-visible string identifying
* the bookmark.
* \see name()
*/
void setName( const QString &name );
/**
* Returns the bookmark's spatial extent.
* \see setExtent()
*/
QgsReferencedRectangle extent() const;
/**
* Sets the bookmark's spatial \a extent.
* \see extent()
*/
void setExtent( const QgsReferencedRectangle &extent );
/**
* Creates a bookmark using the properties from a DOM \a element.
* \see writeXml()
*/
static QgsBookmark fromXml( const QDomElement &element, const QDomDocument &doc );
/**
* Returns a DOM element representing the bookmark's properties.
* \see fromXml()
*/
QDomElement writeXml( QDomDocument &doc ) const;
#ifdef SIP_RUN
SIP_PYOBJECT __repr__();
% MethodCode
QString str = QStringLiteral( "<QgsBookmark: '%1' (%2 - %3)>" ).arg( sipCpp->name(), sipCpp->extent().asWktCoordinates(), sipCpp->extent().crs().authid() );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
% End
#endif
private:
QString mId;
QString mName;
QgsReferencedRectangle mExtent;
};
/**
* \ingroup core
* \class QgsBookmarkManager
*
* \brief Manages storage of a set of bookmarks.
*
* QgsBookmarkManager handles the storage, serializing and deserializing
* of geographic bookmarks. Usually this class is not constructed directly, but
* rather accessed through a QgsProject via QgsProject::bookmarkManager().
*
* \since QGIS 3.10
*/
class CORE_EXPORT QgsBookmarkManager : public QObject
{
Q_OBJECT
public:
/**
* Constructor for QgsBookmarkManager, with the specified \a parent project.
*/
explicit QgsBookmarkManager( QgsProject *project SIP_TRANSFERTHIS = nullptr );
~QgsBookmarkManager() override;
/**
* Adds a \a bookmark to the manager.
*
* \param bookmark the bookmark to add
* \param ok if specified, will be set to TRUE if the bookmark was successfully added, or
* FALSE if the bookmark could not be added (eg as a result of a duplicate bookmark ID).
*
* \returns The bookmark's ID (or newly generated ID, if no ID was originally set and one was automatically generated)
*
* \see removeBookmark()
* \see bookmarkAdded()
*/
QString addBookmark( const QgsBookmark &bookmark, bool *ok SIP_OUT = nullptr );
/**
* Removes the bookmark with matching \a id from the manager.
*
* Returns TRUE if the removal was successful, or FALSE if the removal failed (eg as a result
* of removing a bookmark which is not contained in the manager).
*
* \see addBookmark()
* \see bookmarkRemoved()
* \see bookmarkAboutToBeRemoved()
* \see clear()
*/
bool removeBookmark( const QString &id );
/**
* Removes and deletes all bookmarks from the manager.
* \see removeBookmark()
*/
void clear();
/**
* Returns a list of all bookmarks contained in the manager.
*/
QList< QgsBookmark > bookmarks() const;
/**
* Returns the bookmark with a matching \a id, or an empty bookmark if no matching bookmarks
* were found.
*/
QgsBookmark bookmarkById( const QString &id ) const;
/**
* Reads the manager's state from a DOM element, restoring all bookmarks
* present in the XML document.
* \see writeXml()
*/
bool readXml( const QDomElement &element, const QDomDocument &doc );
/**
* Returns a DOM element representing the state of the manager.
* \see readXml()
*/
QDomElement writeXml( QDomDocument &doc ) const;
signals:
//! Emitted when a bookmark is about to be added to the manager
void bookmarkAboutToBeAdded( const QString &id );
//! Emitted when a bookmark has been added to the manager
void bookmarkAdded( const QString &id );
//! Emitted when a bookmark was removed from the manager
void bookmarkRemoved( const QString &id );
//! Emitted when a bookmark is about to be removed from the manager
void bookmarkAboutToBeRemoved( const QString &id );
//! Emitted when a bookmark is renamed
void bookmarkRenamed( const QgsBookmark &bookmark, const QString &newName );
private:
QgsProject *mProject = nullptr;
QList< QgsBookmark > mBookmarks;
};
#endif // QGSBOOKMARKMANAGER_H

View File

@ -49,6 +49,7 @@
#include "qgsmaplayerlistutils.h"
#include "qgsmeshlayer.h"
#include "qgslayoutmanager.h"
#include "qgsbookmarkmanager.h"
#include "qgsmaplayerstore.h"
#include "qgsziputils.h"
#include "qgsauxiliarystorage.h"
@ -358,6 +359,7 @@ QgsProject::QgsProject( QObject *parent )
, mRelationManager( new QgsRelationManager( this ) )
, mAnnotationManager( new QgsAnnotationManager( this ) )
, mLayoutManager( new QgsLayoutManager( this ) )
, mBookmarkManager( new QgsBookmarkManager( this ) )
, mRootGroup( new QgsLayerTree )
, mLabelingEngineSettings( new QgsLabelingEngineSettings )
, mArchive( new QgsProjectArchive() )
@ -724,6 +726,7 @@ void QgsProject::clear()
mRelationManager->clear();
mAnnotationManager->clear();
mLayoutManager->clear();
mBookmarkManager->clear();
mSnappingConfig.reset();
emit snappingConfigChanged( mSnappingConfig );
emit topologicalEditingChanged();
@ -1393,6 +1396,7 @@ bool QgsProject::readProjectFile( const QString &filename, QgsProject::ReadFlags
mAnnotationManager->readXml( doc->documentElement(), context );
mLayoutManager->readXml( doc->documentElement(), *doc );
mBookmarkManager->readXml( doc->documentElement(), *doc );
// reassign change dependencies now that all layers are loaded
QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
@ -1938,6 +1942,9 @@ bool QgsProject::writeProjectFile( const QString &filename )
QDomElement layoutElem = mLayoutManager->writeXml( *doc );
qgisNode.appendChild( layoutElem );
QDomElement bookmarkElem = mBookmarkManager->writeXml( *doc );
qgisNode.appendChild( bookmarkElem );
// now wrap it up and ship it to the project file
doc->normalize(); // XXX I'm not entirely sure what this does
@ -2597,6 +2604,16 @@ QgsLayoutManager *QgsProject::layoutManager()
return mLayoutManager.get();
}
const QgsBookmarkManager *QgsProject::bookmarkManager() const
{
return mBookmarkManager;
}
QgsBookmarkManager *QgsProject::bookmarkManager()
{
return mBookmarkManager;
}
QgsLayerTree *QgsProject::layerTreeRoot() const
{
return mRootGroup;

View File

@ -70,6 +70,7 @@ class QgsLayerTree;
class QgsLabelingEngineSettings;
class QgsAuxiliaryStorage;
class QgsMapLayer;
class QgsBookmarkManager;
/**
* \ingroup core
@ -545,6 +546,21 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
*/
QgsLayoutManager *layoutManager();
/**
* Returns the project's bookmark manager, which manages bookmarks within
* the project.
* \note not available in Python bindings
* \since QGIS 3.10
*/
const QgsBookmarkManager *bookmarkManager() const SIP_SKIP;
/**
* Returns the project's bookmark manager, which manages bookmarks within
* the project.
* \since QGIS 3.10
*/
QgsBookmarkManager *bookmarkManager();
/**
* Returns pointer to the root (invisible) node of the project's layer tree
* \since QGIS 2.4
@ -1600,6 +1616,8 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
std::unique_ptr<QgsAnnotationManager> mAnnotationManager;
std::unique_ptr<QgsLayoutManager> mLayoutManager;
QgsBookmarkManager *mBookmarkManager = nullptr;
QgsLayerTree *mRootGroup = nullptr;
QgsLayerTreeRegistryBridge *mLayerTreeRegistryBridge = nullptr;

View File

@ -17,7 +17,7 @@ from qgis.testing import unittest, start_app
from qgis.core import QgsGeometry, QgsPoint, QgsPointXY, QgsCircle, QgsCircularString, QgsCompoundCurve,\
QgsCurvePolygon, QgsEllipse, QgsLineString, QgsMultiCurve, QgsRectangle, QgsExpression, QgsField, QgsError,\
QgsMimeDataUtils, QgsVector, QgsVector3D, QgsVectorLayer, QgsReferencedPointXY, QgsReferencedRectangle,\
QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsProject, QgsClassificationRange
QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsProject, QgsClassificationRange, QgsBookmark
start_app()
@ -192,6 +192,14 @@ class TestPython__repr__(unittest.TestCase):
QgsProject.instance().setFileName('/home/test/my_project.qgs')
self.assertEqual(QgsProject.instance().__repr__(), "<QgsProject: '/home/test/my_project.qgs' (singleton instance)>")
def testQgsBookmark(self):
b = QgsBookmark()
self.assertEqual(b.__repr__(), "<QgsBookmark: '' (0 0, 0 0 - )>")
b.setName('test bookmark')
self.assertEqual(b.__repr__(), "<QgsBookmark: 'test bookmark' (0 0, 0 0 - )>")
b.setExtent(QgsReferencedRectangle(QgsRectangle(1, 2, 3, 4), QgsCoordinateReferenceSystem('EPSG:3111')))
self.assertEqual(b.__repr__(), "<QgsBookmark: 'test bookmark' (1 2, 3 4 - EPSG:3111)>")
if __name__ == "__main__":
unittest.main()