QGIS API Documentation 3.99.0-Master (26c88405ac0)
Loading...
Searching...
No Matches
qgsbookmarkmanager.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsbookmarkmanager.cpp
3 --------------------
4 Date : September 2019
5 Copyright : (C) 2019 Nyall Dawson
6 Email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsbookmarkmanager.h"
17
18#include <sqlite3.h>
19
20#include "qgsapplication.h"
21#include "qgsproject.h"
22#include "qgssettings.h"
23#include "qgssqliteutils.h"
24
25#include <QTextStream>
26#include <QUuid>
27
28#include "moc_qgsbookmarkmanager.cpp"
29
30//
31// QgsBookMark
32//
33
34QString QgsBookmark::id() const
35{
36 return mId;
37}
38
39void QgsBookmark::setId( const QString &id )
40{
41 mId = id;
42}
43
44QgsBookmark QgsBookmark::fromXml( const QDomElement &element, const QDomDocument & )
45{
47 b.setId( element.attribute( QStringLiteral( "id" ) ) );
48 b.setName( element.attribute( QStringLiteral( "name" ) ) );
49 b.setGroup( element.attribute( QStringLiteral( "group" ) ) );
50 const QgsRectangle e = QgsRectangle::fromWkt( element.attribute( QStringLiteral( "extent" ) ) );
51 b.setRotation( element.attribute( QStringLiteral( "rotation" ) ).toDouble() );
53 crs.readXml( element );
54 b.setExtent( QgsReferencedRectangle( e, crs ) );
55 return b;
56}
57
58QDomElement QgsBookmark::writeXml( QDomDocument &doc ) const
59{
60 QDomElement bookmarkElem = doc.createElement( QStringLiteral( "Bookmark" ) );
61 bookmarkElem.setAttribute( QStringLiteral( "id" ), mId );
62 bookmarkElem.setAttribute( QStringLiteral( "name" ), mName );
63 bookmarkElem.setAttribute( QStringLiteral( "group" ), mGroup );
64 bookmarkElem.setAttribute( QStringLiteral( "extent" ), mExtent.asWktPolygon() );
65 bookmarkElem.setAttribute( QStringLiteral( "rotation" ), mRotation );
66 mExtent.crs().writeXml( bookmarkElem, doc );
67 return bookmarkElem;
68}
69
70bool QgsBookmark::operator==( const QgsBookmark &other ) const
71{
72 return mId == other.mId
73 && mName == other.mName
74 && mExtent == other.mExtent
75 && mGroup == other.mGroup
76 && qgsDoubleNear( mRotation, other.mRotation );
77}
78
79bool QgsBookmark::operator!=( const QgsBookmark &other ) const
80{
81 return !( *this == other );
82}
83
84QString QgsBookmark::name() const
85{
86 return mName;
87}
88
89void QgsBookmark::setName( const QString &name )
90{
91 mName = name;
92}
93
94QString QgsBookmark::group() const
95{
96 return mGroup;
97}
98
99void QgsBookmark::setGroup( const QString &group )
100{
101 mGroup = group;
102}
103
105{
106 return mExtent;
107}
108
110{
111 mExtent = extent;
112}
113
115{
116 return mRotation;
117}
118
120{
121 mRotation = rotation;
122}
123
124
125//
126// QgsBookmarkManager
127//
128
130{
131 QgsBookmarkManager *res = new QgsBookmarkManager( project );
132 res->mProject = project;
133 return res;
134}
135
137 : QObject( parent )
138{
139 // we defer actually loading bookmarks until initialize() is called..
140}
141
146
147QString QgsBookmarkManager::addBookmark( const QgsBookmark &b, bool *ok )
148{
149 if ( ok )
150 *ok = false;
151
152 QgsBookmark bookmark = b;
153 if ( bookmark.id().isEmpty() )
154 bookmark.setId( QUuid::createUuid().toString() );
155 else
156 {
157 // check for duplicate ID
158 for ( const QgsBookmark &b : std::as_const( mBookmarks ) )
159 {
160 if ( b.id() == bookmark.id() )
161 {
162 return QString();
163 }
164 }
165 }
166
167 if ( ok )
168 *ok = true;
169
170 emit bookmarkAboutToBeAdded( bookmark.id() );
171 mBookmarks << bookmark;
172 if ( !mGroups.contains( bookmark.group() ) )
173 mGroups << bookmark.group();
174 emit bookmarkAdded( bookmark.id() );
175 if ( mProject )
176 {
177 mProject->setDirty( true );
178 }
179
180 return bookmark.id();
181}
182
183bool QgsBookmarkManager::removeBookmark( const QString &id )
184{
185 if ( id.isEmpty() )
186 return false;
187
188 QString group;
189 int pos = -1;
190 int i = 0;
191 for ( const QgsBookmark &b : std::as_const( mBookmarks ) )
192 {
193 if ( b.id() == id )
194 {
195 group = b.group();
196 pos = i;
197 break;
198 }
199 i++;
200 }
201
202 if ( pos < 0 )
203 return false;
204
205 emit bookmarkAboutToBeRemoved( id );
206 mBookmarks.removeAt( pos );
207 if ( bookmarksByGroup( group ).isEmpty() )
208 mGroups.removeOne( group );
209 emit bookmarkRemoved( id );
210 if ( mProject )
211 {
212 mProject->setDirty( true );
213 }
214
215 return true;
216}
217
219{
220 // check for duplicate ID
221 int i = 0;
222 for ( const QgsBookmark &b : std::as_const( mBookmarks ) )
223 {
224 if ( b.id() == bookmark.id() )
225 {
226 if ( mBookmarks[i].group() != bookmark.group() )
227 {
228 if ( bookmarksByGroup( mBookmarks[i].group() ).count() == 1 )
229 mGroups.removeOne( mBookmarks[i].group() );
230 if ( !mGroups.contains( bookmark.group() ) )
231 mGroups << bookmark.group();
232 }
233 mBookmarks[i] = bookmark;
234 emit bookmarkChanged( bookmark.id() );
235 if ( mProject )
236 {
237 mProject->setDirty( true );
238 }
239
240 return true;
241 }
242 i++;
243 }
244 return false;
245}
246
248{
249 const QList< QgsBookmark > bookmarks = mBookmarks;
250 for ( const QgsBookmark &b : bookmarks )
251 {
252 removeBookmark( b.id() );
253 }
254}
255
256QStringList QgsBookmarkManager::groups() const
257{
258 return mGroups;
259}
260
261void QgsBookmarkManager::renameGroup( const QString &oldName, const QString &newName )
262{
263 for ( int i = 0; i < mBookmarks.count(); ++i )
264 {
265 if ( mBookmarks.at( i ).group() == oldName )
266 {
267 mBookmarks[ i ].setGroup( newName );
268 emit bookmarkChanged( mBookmarks.at( i ).id() );
269 }
270 }
271}
272
273QList<QgsBookmark> QgsBookmarkManager::bookmarks() const
274{
275 return mBookmarks;
276}
277
279{
280 for ( const QgsBookmark &b : mBookmarks )
281 {
282 if ( b.id() == id )
283 return b;
284 }
285 return QgsBookmark();
286}
287
288QList<QgsBookmark> QgsBookmarkManager::bookmarksByGroup( const QString &group )
289{
290 QList<QgsBookmark> bookmarks;
291 for ( const QgsBookmark &b : mBookmarks )
292 {
293 if ( b.group() == group )
294 bookmarks << b;
295 }
296 return bookmarks;
297}
298
299bool QgsBookmarkManager::readXml( const QDomElement &element, const QDomDocument &doc )
300{
301 clear();
302
303 QDomElement bookmarksElem = element;
304 if ( element.tagName() != QLatin1String( "Bookmarks" ) )
305 {
306 bookmarksElem = element.firstChildElement( QStringLiteral( "Bookmarks" ) );
307 }
308 bool result = true;
309 if ( mProject && bookmarksElem.isNull() )
310 {
311 // handle legacy projects
312 const int count = mProject->readNumEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/count" ) );
313 for ( int i = 0; i < count; ++i )
314 {
315 const double minX = mProject->readDoubleEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/MinX" ).arg( i ) );
316 const double minY = mProject->readDoubleEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/MinY" ).arg( i ) );
317 const double maxX = mProject->readDoubleEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/MaxX" ).arg( i ) );
318 const double maxY = mProject->readDoubleEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/MaxY" ).arg( i ) );
319 const long srid = mProject->readNumEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/SRID" ).arg( i ) );
320 QgsBookmark b;
321 b.setId( QStringLiteral( "bookmark_%1" ).arg( i ) );
322 b.setName( mProject->readEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/Name" ).arg( i ) ) );
324
325 bool added = false;
326 addBookmark( b, &added );
327 result = added && result;
328 }
329 return result;
330 }
331
332 //restore each
333 QDomNodeList bookmarkNodes = element.elementsByTagName( QStringLiteral( "Bookmark" ) );
334 for ( int i = 0; i < bookmarkNodes.size(); ++i )
335 {
336 QgsBookmark b = QgsBookmark::fromXml( bookmarkNodes.at( i ).toElement(), doc );
337 bool added = false;
338 addBookmark( b, &added );
339 result = added && result;
340 }
341
342 return result;
343}
344
345QDomElement QgsBookmarkManager::writeXml( QDomDocument &doc ) const
346{
347 QDomElement bookmarksElem = doc.createElement( QStringLiteral( "Bookmarks" ) );
348
349 for ( const QgsBookmark &b : mBookmarks )
350 {
351 QDomElement bookmarkElem = b.writeXml( doc );
352 bookmarksElem.appendChild( bookmarkElem );
353 }
354 return bookmarksElem;
355}
356
357bool QgsBookmarkManager::moveBookmark( const QString &id, QgsBookmarkManager *destination )
358{
359 QgsBookmark b = bookmarkById( id );
360 if ( b.id().isEmpty() )
361 return false;
362
363 removeBookmark( id );
364 bool ok = false;
365 destination->addBookmark( b, &ok );
366 return ok;
367}
368
369bool QgsBookmarkManager::exportToFile( const QString &path, const QList<const QgsBookmarkManager *> &managers, const QString &group )
370{
371 // note - we don't use the other writeXml implementation, to maintain older format compatibility
372 QDomDocument doc( QStringLiteral( "qgis_bookmarks" ) );
373 QDomElement root = doc.createElement( QStringLiteral( "qgis_bookmarks" ) );
374 doc.appendChild( root );
375
376 QList<QString> headerList;
377 headerList
378 << QStringLiteral( "project" )
379 << QStringLiteral( "xmin" )
380 << QStringLiteral( "ymin" )
381 << QStringLiteral( "xmax" )
382 << QStringLiteral( "ymax" )
383 << QStringLiteral( "rotation" )
384 << QStringLiteral( "sr_id" );
385
386 for ( const QgsBookmarkManager *manager : managers )
387 {
388 const QList< QgsBookmark > bookmarks = manager->bookmarks();
389 for ( const QgsBookmark &b : bookmarks )
390 {
391 if ( !group.isEmpty() && b.group() != group )
392 continue;
393
394 QDomElement bookmark = doc.createElement( QStringLiteral( "bookmark" ) );
395 root.appendChild( bookmark );
396
397 QDomElement id = doc.createElement( QStringLiteral( "id" ) );
398 id.appendChild( doc.createTextNode( b.id() ) );
399 bookmark.appendChild( id );
400
401 QDomElement name = doc.createElement( QStringLiteral( "name" ) );
402 name.appendChild( doc.createTextNode( b.name() ) );
403 bookmark.appendChild( name );
404
405 QDomElement group = doc.createElement( QStringLiteral( "project" ) );
406 group.appendChild( doc.createTextNode( b.group() ) );
407 bookmark.appendChild( group );
408
409 QDomElement xMin = doc.createElement( QStringLiteral( "xmin" ) );
410 xMin.appendChild( doc.createTextNode( qgsDoubleToString( b.extent().xMinimum() ) ) );
411 bookmark.appendChild( xMin );
412 QDomElement yMin = doc.createElement( QStringLiteral( "ymin" ) );
413 yMin.appendChild( doc.createTextNode( qgsDoubleToString( b.extent().yMinimum() ) ) );
414 bookmark.appendChild( yMin );
415 QDomElement xMax = doc.createElement( QStringLiteral( "xmax" ) );
416 xMax.appendChild( doc.createTextNode( qgsDoubleToString( b.extent().xMaximum() ) ) );
417 bookmark.appendChild( xMax );
418 QDomElement yMax = doc.createElement( QStringLiteral( "ymax" ) );
419 yMax.appendChild( doc.createTextNode( qgsDoubleToString( b.extent().yMaximum() ) ) );
420 bookmark.appendChild( yMax );
421
422 QDomElement rotation = doc.createElement( QStringLiteral( "rotation" ) );
423 rotation.appendChild( doc.createTextNode( qgsDoubleToString( b.rotation() ) ) );
424 bookmark.appendChild( rotation );
425
426 QDomElement crs = doc.createElement( QStringLiteral( "sr_id" ) );
427 crs.appendChild( doc.createTextNode( QString::number( b.extent().crs().srsid() ) ) );
428 bookmark.appendChild( crs );
429 }
430 }
431
432 QFile f( path );
433 if ( !f.open( QFile::WriteOnly | QIODevice::Truncate ) )
434 {
435 f.close();
436 return false;
437 }
438
439 QTextStream out( &f );
440#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
441 out.setCodec( "UTF-8" );
442#endif
443 doc.save( out, 2 );
444 f.close();
445
446 return true;
447}
448
449bool QgsBookmarkManager::importFromFile( const QString &path )
450{
451 if ( path.isEmpty() )
452 {
453 return false;
454 }
455
456 QFile f( path );
457 if ( !f.open( QIODevice::ReadOnly | QIODevice::Text ) )
458 {
459 return false;
460 }
461
462 QDomDocument doc;
463 if ( !doc.setContent( &f ) )
464 {
465 return false;
466 }
467 f.close();
468
469 QDomElement docElem = doc.documentElement();
470 QDomNodeList nodeList = docElem.elementsByTagName( QStringLiteral( "bookmark" ) );
471
472 bool res = true;
473 for ( int i = 0; i < nodeList.count(); i++ )
474 {
475 QDomNode bookmark = nodeList.at( i );
476 QDomElement name = bookmark.firstChildElement( QStringLiteral( "name" ) );
477 QDomElement prjname = bookmark.firstChildElement( QStringLiteral( "project" ) );
478 QDomElement xmin = bookmark.firstChildElement( QStringLiteral( "xmin" ) );
479 QDomElement ymin = bookmark.firstChildElement( QStringLiteral( "ymin" ) );
480 QDomElement xmax = bookmark.firstChildElement( QStringLiteral( "xmax" ) );
481 QDomElement ymax = bookmark.firstChildElement( QStringLiteral( "ymax" ) );
482 QDomElement rotation = bookmark.firstChildElement( QStringLiteral( "rotation" ) );
483 QDomElement srid = bookmark.firstChildElement( QStringLiteral( "sr_id" ) );
484
485 bool ok = false;
486 QgsBookmark b;
487 b.setName( name.text() );
488 b.setGroup( prjname.text() );
490 crs.createFromSrsId( srid.text().toLongLong() );
491 b.setExtent( QgsReferencedRectangle( QgsRectangle( xmin.text().toDouble(),
492 ymin.text().toDouble(),
493 xmax.text().toDouble(),
494 ymax.text().toDouble() ), crs ) );
495 b.setRotation( rotation.text().toDouble() );
496 addBookmark( b, &ok );
497 res = res && ok;
498 }
499
500 return res;
501}
502
503void QgsBookmarkManager::store()
504{
505 if ( !mFilePath.isEmpty() )
506 {
507 QFile f( mFilePath );
508 if ( !f.open( QFile::WriteOnly | QIODevice::Truncate ) )
509 {
510 f.close();
511 return;
512 }
513
514 QDomDocument doc;
515 QDomElement elem = writeXml( doc );
516 doc.appendChild( elem );
517
518 QTextStream out( &f );
519#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
520 out.setCodec( "UTF-8" );
521#endif
522 doc.save( out, 2 );
523 f.close();
524 }
525}
526
527void QgsBookmarkManager::initialize( const QString &filePath )
528{
529 if ( mInitialized )
530 return;
531
532 mFilePath = filePath;
533
534 mInitialized = true;
535
536 // restore state
537 if ( !QFileInfo::exists( mFilePath ) )
538 {
539 //convert old bookmarks from db
541 int result = database.open( QgsApplication::qgisUserDatabaseFilePath() );
542 if ( result != SQLITE_OK )
543 {
544 return;
545 }
546
547 sqlite3_statement_unique_ptr preparedStatement = database.prepare( QStringLiteral( "SELECT name,xmin,ymin,xmax,ymax,projection_srid FROM tbl_bookmarks" ), result );
548 if ( result == SQLITE_OK )
549 {
550 while ( preparedStatement.step() == SQLITE_ROW )
551 {
552 const QString name = preparedStatement.columnAsText( 0 );
553 const double xMin = preparedStatement.columnAsDouble( 1 );
554 const double yMin = preparedStatement.columnAsDouble( 2 );
555 const double xMax = preparedStatement.columnAsDouble( 3 );
556 const double yMax = preparedStatement.columnAsDouble( 4 );
557 const long long srid = preparedStatement.columnAsInt64( 5 );
558
559 QgsBookmark b;
560 b.setName( name );
561 const QgsRectangle extent( xMin, yMin, xMax, yMax );
563 addBookmark( b );
564 }
565 }
566 store();
567 }
568 else
569 {
570 QFile f( mFilePath );
571 if ( !f.open( QIODevice::ReadOnly | QIODevice::Text ) )
572 {
573 return;
574 }
575
576 QDomDocument doc;
577 if ( !doc.setContent( &f ) )
578 {
579 return;
580 }
581 f.close();
582
583 QDomElement elem = doc.documentElement();
584 readXml( elem, doc );
585 }
586}
static QString qgisUserDatabaseFilePath()
Returns the path to the user qgis.db file.
void renameGroup(const QString &oldName, const QString &newName)
Renames an existing group from oldName to newName.
bool removeBookmark(const QString &id)
Removes the bookmark with matching id from the manager.
QList< QgsBookmark > bookmarksByGroup(const QString &group)
Returns a list of bookmark with a matching group, or an empty list if no matching bookmarks were foun...
void initialize(const QString &filePath)
Initializes the bookmark manager.
void bookmarkAboutToBeRemoved(const QString &id)
Emitted when a bookmark is about to be removed from the manager.
void bookmarkChanged(const QString &id)
Emitted when a bookmark is changed.
static QgsBookmarkManager * createProjectBasedManager(QgsProject *project)
Returns a newly created QgsBookmarkManager using a project-based bookmark store, linked to the specif...
bool readXml(const QDomElement &element, const QDomDocument &doc)
Reads the manager's state from a DOM element, restoring all bookmarks present in the XML document.
void clear()
Removes and deletes all bookmarks from the manager.
void bookmarkAdded(const QString &id)
Emitted when a bookmark has been added to the manager.
bool updateBookmark(const QgsBookmark &bookmark)
Updates the definition of a bookmark in the manager.
void bookmarkAboutToBeAdded(const QString &id)
Emitted when a bookmark is about to be added to the manager.
bool moveBookmark(const QString &id, QgsBookmarkManager *destination)
Moves the bookmark with matching id from this manager to a destination manager.
static bool exportToFile(const QString &path, const QList< const QgsBookmarkManager * > &managers, const QString &group=QString())
Exports all bookmarks from a list of managers to an xml file at the specified path.
QgsBookmark bookmarkById(const QString &id) const
Returns the bookmark with a matching id, or an empty bookmark if no matching bookmarks were found.
QStringList groups() const
Returns a list of all bookmark groups contained in the manager.
QString addBookmark(const QgsBookmark &bookmark, bool *ok=nullptr)
Adds a bookmark to the manager.
QDomElement writeXml(QDomDocument &doc) const
Returns a DOM element representing the state of the manager.
QgsBookmarkManager(QObject *parent=nullptr)
Constructor for QgsBookmarkManager, with the specified parent object.
QList< QgsBookmark > bookmarks() const
Returns a list of all bookmarks contained in the manager.
void bookmarkRemoved(const QString &id)
Emitted when a bookmark was removed from the manager.
bool importFromFile(const QString &path)
Imports the bookmarks from an xml file at the specified path.
Represents a spatial bookmark, with a name, CRS and extent.
static QgsBookmark fromXml(const QDomElement &element, const QDomDocument &doc)
Creates a bookmark using the properties from a DOM element.
void setGroup(const QString &group)
Sets the bookmark's group, which is a user-visible string identifying the bookmark's category.
bool operator!=(const QgsBookmark &other) const
void setRotation(double rotation)
Sets the bookmark's spatial map rotation.
QgsBookmark()=default
Default constructor, creates an empty bookmark.
QString id() const
Returns the bookmark's unique ID.
QgsReferencedRectangle extent() const
Returns the bookmark's spatial extent.
void setExtent(const QgsReferencedRectangle &extent)
Sets the bookmark's spatial extent.
double rotation() const
Returns the bookmark's map rotation.
void setId(const QString &id)
Sets the bookmark's unique id.
QDomElement writeXml(QDomDocument &doc) const
Returns a DOM element representing the bookmark's properties.
bool operator==(const QgsBookmark &other) const
QString group() const
Returns the bookmark's group, which is a user-visible string identifying the bookmark's category.
void setName(const QString &name)
Sets the bookmark's name, which is a user-visible string identifying the bookmark.
QString name() const
Returns the bookmark's name, which is a user-visible string identifying the bookmark.
Represents a coordinate reference system (CRS).
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
bool createFromSrsId(long srsId)
Sets this CRS by lookup of internal QGIS CRS ID in the CRS database.
static QgsCoordinateReferenceSystem fromSrsId(long srsId)
Creates a CRS from a specified QGIS SRS ID.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:109
A rectangle specified with double values.
static QgsRectangle fromWkt(const QString &wkt)
Creates a new rectangle from a wkt string.
A QgsRectangle with associated coordinate reference system.
Unique pointer for sqlite3 databases, which automatically closes the database when the pointer goes o...
sqlite3_statement_unique_ptr prepare(const QString &sql, int &resultCode) const
Prepares a sql statement, returning the result.
int open(const QString &path)
Opens the database at the specified file path.
Unique pointer for sqlite3 prepared statements, which automatically finalizes the statement when the ...
QString columnAsText(int column) const
Returns the column value from the current statement row as a string.
double columnAsDouble(int column) const
Gets column value from the current statement row as a double.
int step()
Steps to the next record in the statement, returning the sqlite3 result code.
qlonglong columnAsInt64(int column) const
Gets column value from the current statement row as a long long integer (64 bits).
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:6524
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6607