QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 #include "qgsproject.h"
18 #include "qgssettings.h"
19 #include "qgssqliteutils.h"
20 #include "qgsapplication.h"
21 #include <QUuid>
22 #include <QTextStream>
23 #include <sqlite3.h>
24 
25 //
26 // QgsBookMark
27 //
28 
29 QString QgsBookmark::id() const
30 {
31  return mId;
32 }
33 
34 void QgsBookmark::setId( const QString &id )
35 {
36  mId = id;
37 }
38 
39 QgsBookmark QgsBookmark::fromXml( const QDomElement &element, const QDomDocument & )
40 {
41  QgsBookmark b;
42  b.setId( element.attribute( QStringLiteral( "id" ) ) );
43  b.setName( element.attribute( QStringLiteral( "name" ) ) );
44  b.setGroup( element.attribute( QStringLiteral( "group" ) ) );
45  const QgsRectangle e = QgsRectangle::fromWkt( element.attribute( QStringLiteral( "extent" ) ) );
47  crs.readXml( element );
49  return b;
50 }
51 
52 QDomElement QgsBookmark::writeXml( QDomDocument &doc ) const
53 {
54  QDomElement bookmarkElem = doc.createElement( QStringLiteral( "Bookmark" ) );
55  bookmarkElem.setAttribute( QStringLiteral( "id" ), mId );
56  bookmarkElem.setAttribute( QStringLiteral( "name" ), mName );
57  bookmarkElem.setAttribute( QStringLiteral( "group" ), mGroup );
58  bookmarkElem.setAttribute( QStringLiteral( "extent" ), mExtent.asWktPolygon() );
59  mExtent.crs().writeXml( bookmarkElem, doc );
60  return bookmarkElem;
61 }
62 
64 {
65  return mId == other.mId && mName == other.mName && mExtent == other.mExtent && mGroup == other.mGroup;
66 }
67 
69 {
70  return !( *this == other );
71 }
72 
73 QString QgsBookmark::name() const
74 {
75  return mName;
76 }
77 
78 void QgsBookmark::setName( const QString &name )
79 {
80  mName = name;
81 }
82 
83 QString QgsBookmark::group() const
84 {
85  return mGroup;
86 }
87 
88 void QgsBookmark::setGroup( const QString &group )
89 {
90  mGroup = group;
91 }
92 
94 {
95  return mExtent;
96 }
97 
99 {
100  mExtent = extent;
101 }
102 
103 
104 //
105 // QgsBookmarkManager
106 //
107 
109 {
110  QgsBookmarkManager *res = new QgsBookmarkManager( project );
111  res->mProject = project;
112  return res;
113 }
114 
116  : QObject( parent )
117 {
118  // we defer actually loading bookmarks until initialize() is called..
119 }
120 
122 {
123  store();
124 }
125 
126 QString QgsBookmarkManager::addBookmark( const QgsBookmark &b, bool *ok )
127 {
128  if ( ok )
129  *ok = false;
130 
131  QgsBookmark bookmark = b;
132  if ( bookmark.id().isEmpty() )
133  bookmark.setId( QUuid::createUuid().toString() );
134  else
135  {
136  // check for duplicate ID
137  for ( const QgsBookmark &b : qgis::as_const( mBookmarks ) )
138  {
139  if ( b.id() == bookmark.id() )
140  {
141  return QString();
142  }
143  }
144  }
145 
146  if ( ok )
147  *ok = true;
148 
149  emit bookmarkAboutToBeAdded( bookmark.id() );
150  mBookmarks << bookmark;
151  if ( !mGroups.contains( bookmark.group() ) )
152  mGroups << bookmark.group();
153  emit bookmarkAdded( bookmark.id() );
154  if ( mProject )
155  {
156  mProject->setDirty( true );
157  }
158 
159  return bookmark.id();
160 }
161 
162 bool QgsBookmarkManager::removeBookmark( const QString &id )
163 {
164  if ( id.isEmpty() )
165  return false;
166 
167  QString group;
168  int pos = -1;
169  int i = 0;
170  for ( const QgsBookmark &b : qgis::as_const( mBookmarks ) )
171  {
172  if ( b.id() == id )
173  {
174  group = b.group();
175  pos = i;
176  break;
177  }
178  i++;
179  }
180 
181  if ( pos < 0 )
182  return false;
183 
184  emit bookmarkAboutToBeRemoved( id );
185  mBookmarks.removeAt( pos );
186  if ( bookmarksByGroup( group ).isEmpty() )
187  mGroups.removeOne( group );
188  emit bookmarkRemoved( id );
189  if ( mProject )
190  {
191  mProject->setDirty( true );
192  }
193 
194  return true;
195 }
196 
198 {
199  // check for duplicate ID
200  int i = 0;
201  for ( const QgsBookmark &b : qgis::as_const( mBookmarks ) )
202  {
203  if ( b.id() == bookmark.id() )
204  {
205  if ( mBookmarks[i].group() != bookmark.group() )
206  {
207  if ( bookmarksByGroup( mBookmarks[i].group() ).count() == 1 )
208  mGroups.removeOne( mBookmarks[i].group() );
209  if ( !mGroups.contains( bookmark.group() ) )
210  mGroups << bookmark.group();
211  }
212  mBookmarks[i] = bookmark;
213  emit bookmarkChanged( bookmark.id() );
214  if ( mProject )
215  {
216  mProject->setDirty( true );
217  }
218 
219  return true;
220  }
221  i++;
222  }
223  return false;
224 }
225 
227 {
228  const QList< QgsBookmark > bookmarks = mBookmarks;
229  for ( const QgsBookmark &b : bookmarks )
230  {
231  removeBookmark( b.id() );
232  }
233 }
234 
235 QStringList QgsBookmarkManager::groups() const
236 {
237  return mGroups;
238 }
239 
240 void QgsBookmarkManager::renameGroup( const QString &oldName, const QString &newName )
241 {
242  for ( int i = 0; i < mBookmarks.count(); ++i )
243  {
244  if ( mBookmarks.at( i ).group() == oldName )
245  {
246  mBookmarks[ i ].setGroup( newName );
247  emit bookmarkChanged( mBookmarks.at( i ).id() );
248  }
249  }
250 }
251 
252 QList<QgsBookmark> QgsBookmarkManager::bookmarks() const
253 {
254  return mBookmarks;
255 }
256 
258 {
259  for ( const QgsBookmark &b : mBookmarks )
260  {
261  if ( b.id() == id )
262  return b;
263  }
264  return QgsBookmark();
265 }
266 
267 QList<QgsBookmark> QgsBookmarkManager::bookmarksByGroup( const QString &group )
268 {
269  QList<QgsBookmark> bookmarks;
270  for ( const QgsBookmark &b : mBookmarks )
271  {
272  if ( b.group() == group )
273  bookmarks << b;
274  }
275  return bookmarks;
276 }
277 
278 bool QgsBookmarkManager::readXml( const QDomElement &element, const QDomDocument &doc )
279 {
280  clear();
281 
282  QDomElement bookmarksElem = element;
283  if ( element.tagName() != QLatin1String( "Bookmarks" ) )
284  {
285  bookmarksElem = element.firstChildElement( QStringLiteral( "Bookmarks" ) );
286  }
287  bool result = true;
288  if ( mProject && bookmarksElem.isNull() )
289  {
290  // handle legacy projects
291  const int count = mProject->readNumEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/count" ) );
292  for ( int i = 0; i < count; ++i )
293  {
294  const double minX = mProject->readDoubleEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/MinX" ).arg( i ) );
295  const double minY = mProject->readDoubleEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/MinY" ).arg( i ) );
296  const double maxX = mProject->readDoubleEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/MaxX" ).arg( i ) );
297  const double maxY = mProject->readDoubleEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/MaxY" ).arg( i ) );
298  const long srid = mProject->readNumEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/SRID" ).arg( i ) );
299  QgsBookmark b;
300  b.setId( QStringLiteral( "bookmark_%1" ).arg( i ) );
301  b.setName( mProject->readEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/Name" ).arg( i ) ) );
303 
304  bool added = false;
305  addBookmark( b, &added );
306  result = added && result;
307  }
308  return result;
309  }
310 
311  //restore each
312  QDomNodeList bookmarkNodes = element.elementsByTagName( QStringLiteral( "Bookmark" ) );
313  for ( int i = 0; i < bookmarkNodes.size(); ++i )
314  {
315  QgsBookmark b = QgsBookmark::fromXml( bookmarkNodes.at( i ).toElement(), doc );
316  bool added = false;
317  addBookmark( b, &added );
318  result = added && result;
319  }
320 
321  return result;
322 }
323 
324 QDomElement QgsBookmarkManager::writeXml( QDomDocument &doc ) const
325 {
326  QDomElement bookmarksElem = doc.createElement( QStringLiteral( "Bookmarks" ) );
327 
328  for ( const QgsBookmark &b : mBookmarks )
329  {
330  QDomElement bookmarkElem = b.writeXml( doc );
331  bookmarksElem.appendChild( bookmarkElem );
332  }
333  return bookmarksElem;
334 }
335 
336 bool QgsBookmarkManager::moveBookmark( const QString &id, QgsBookmarkManager *destination )
337 {
338  QgsBookmark b = bookmarkById( id );
339  if ( b.id().isEmpty() )
340  return false;
341 
342  removeBookmark( id );
343  bool ok = false;
344  destination->addBookmark( b, &ok );
345  return ok;
346 }
347 
348 bool QgsBookmarkManager::exportToFile( const QString &path, const QList<const QgsBookmarkManager *> &managers, const QString &group )
349 {
350  // note - we don't use the other writeXml implementation, to maintain older format compatibility
351  QDomDocument doc( QStringLiteral( "qgis_bookmarks" ) );
352  QDomElement root = doc.createElement( QStringLiteral( "qgis_bookmarks" ) );
353  doc.appendChild( root );
354 
355  QList<QString> headerList;
356  headerList
357  << QStringLiteral( "project" )
358  << QStringLiteral( "xmin" )
359  << QStringLiteral( "ymin" )
360  << QStringLiteral( "xmax" )
361  << QStringLiteral( "ymax" )
362  << QStringLiteral( "sr_id" );
363 
364  for ( const QgsBookmarkManager *manager : managers )
365  {
366  const QList< QgsBookmark > bookmarks = manager->bookmarks();
367  for ( const QgsBookmark &b : bookmarks )
368  {
369  if ( !group.isEmpty() && b.group() != group )
370  continue;
371 
372  QDomElement bookmark = doc.createElement( QStringLiteral( "bookmark" ) );
373  root.appendChild( bookmark );
374 
375  QDomElement id = doc.createElement( QStringLiteral( "id" ) );
376  id.appendChild( doc.createTextNode( b.id() ) );
377  bookmark.appendChild( id );
378 
379  QDomElement name = doc.createElement( QStringLiteral( "name" ) );
380  name.appendChild( doc.createTextNode( b.name() ) );
381  bookmark.appendChild( name );
382 
383  QDomElement group = doc.createElement( QStringLiteral( "project" ) );
384  group.appendChild( doc.createTextNode( b.group() ) );
385  bookmark.appendChild( group );
386 
387  QDomElement xMin = doc.createElement( QStringLiteral( "xmin" ) );
388  xMin.appendChild( doc.createTextNode( qgsDoubleToString( b.extent().xMinimum() ) ) );
389  bookmark.appendChild( xMin );
390  QDomElement yMin = doc.createElement( QStringLiteral( "ymin" ) );
391  yMin.appendChild( doc.createTextNode( qgsDoubleToString( b.extent().yMinimum() ) ) );
392  bookmark.appendChild( yMin );
393  QDomElement xMax = doc.createElement( QStringLiteral( "xmax" ) );
394  xMax.appendChild( doc.createTextNode( qgsDoubleToString( b.extent().xMaximum() ) ) );
395  bookmark.appendChild( xMax );
396  QDomElement yMax = doc.createElement( QStringLiteral( "ymax" ) );
397  yMax.appendChild( doc.createTextNode( qgsDoubleToString( b.extent().yMaximum() ) ) );
398  bookmark.appendChild( yMax );
399 
400  QDomElement crs = doc.createElement( QStringLiteral( "sr_id" ) );
401  crs.appendChild( doc.createTextNode( QString::number( b.extent().crs().srsid() ) ) );
402  bookmark.appendChild( crs );
403  }
404  }
405 
406  QFile f( path );
407  if ( !f.open( QFile::WriteOnly | QIODevice::Truncate ) )
408  {
409  f.close();
410  return false;
411  }
412 
413  QTextStream out( &f );
414  out.setCodec( "UTF-8" );
415  doc.save( out, 2 );
416  f.close();
417 
418  return true;
419 }
420 
421 bool QgsBookmarkManager::importFromFile( const QString &path )
422 {
423  if ( path.isEmpty() )
424  {
425  return false;
426  }
427 
428  QFile f( path );
429  if ( !f.open( QIODevice::ReadOnly | QIODevice::Text ) )
430  {
431  return false;
432  }
433 
434  QDomDocument doc;
435  if ( !doc.setContent( &f ) )
436  {
437  return false;
438  }
439  f.close();
440 
441  QDomElement docElem = doc.documentElement();
442  QDomNodeList nodeList = docElem.elementsByTagName( QStringLiteral( "bookmark" ) );
443 
444  bool res = true;
445  for ( int i = 0; i < nodeList.count(); i++ )
446  {
447  QDomNode bookmark = nodeList.at( i );
448  QDomElement name = bookmark.firstChildElement( QStringLiteral( "name" ) );
449  QDomElement prjname = bookmark.firstChildElement( QStringLiteral( "project" ) );
450  QDomElement xmin = bookmark.firstChildElement( QStringLiteral( "xmin" ) );
451  QDomElement ymin = bookmark.firstChildElement( QStringLiteral( "ymin" ) );
452  QDomElement xmax = bookmark.firstChildElement( QStringLiteral( "xmax" ) );
453  QDomElement ymax = bookmark.firstChildElement( QStringLiteral( "ymax" ) );
454  QDomElement srid = bookmark.firstChildElement( QStringLiteral( "sr_id" ) );
455 
456  bool ok = false;
457  QgsBookmark b;
458  b.setName( name.text() );
459  b.setGroup( prjname.text() );
461  crs.createFromSrsId( srid.text().toLongLong() );
462  b.setExtent( QgsReferencedRectangle( QgsRectangle( xmin.text().toDouble(),
463  ymin.text().toDouble(),
464  xmax.text().toDouble(),
465  ymax.text().toDouble() ), crs ) );
466  addBookmark( b, &ok );
467  res = res && ok;
468  }
469 
470  return res;
471 }
472 
473 void QgsBookmarkManager::store()
474 {
475  if ( !mFilePath.isEmpty() )
476  {
477  QFile f( mFilePath );
478  if ( !f.open( QFile::WriteOnly | QIODevice::Truncate ) )
479  {
480  f.close();
481  return;
482  }
483 
484  QDomDocument doc;
485  QDomElement elem = writeXml( doc );
486  doc.appendChild( elem );
487 
488  QTextStream out( &f );
489  out.setCodec( "UTF-8" );
490  doc.save( out, 2 );
491  f.close();
492  }
493 }
494 
495 void QgsBookmarkManager::initialize( const QString &filePath )
496 {
497  if ( mInitialized )
498  return;
499 
500  mFilePath = filePath;
501 
502  mInitialized = true;
503 
504  // restore state
505  if ( !QFileInfo::exists( mFilePath ) )
506  {
507  //convert old bookmarks from db
509  int result = database.open( QgsApplication::qgisUserDatabaseFilePath() );
510  if ( result != SQLITE_OK )
511  {
512  return;
513  }
514 
515  sqlite3_statement_unique_ptr preparedStatement = database.prepare( QStringLiteral( "SELECT name,xmin,ymin,xmax,ymax,projection_srid FROM tbl_bookmarks" ), result );
516  if ( result == SQLITE_OK )
517  {
518  while ( preparedStatement.step() == SQLITE_ROW )
519  {
520  const QString name = preparedStatement.columnAsText( 0 );
521  const double xMin = preparedStatement.columnAsDouble( 1 );
522  const double yMin = preparedStatement.columnAsDouble( 2 );
523  const double xMax = preparedStatement.columnAsDouble( 3 );
524  const double yMax = preparedStatement.columnAsDouble( 4 );
525  const long long srid = preparedStatement.columnAsInt64( 5 );
526 
527  QgsBookmark b;
528  b.setName( name );
529  const QgsRectangle extent( xMin, yMin, xMax, yMax );
531  addBookmark( b );
532  }
533  }
534  store();
535  }
536  else
537  {
538  QFile f( mFilePath );
539  if ( !f.open( QIODevice::ReadOnly | QIODevice::Text ) )
540  {
541  return;
542  }
543 
544  QDomDocument doc;
545  if ( !doc.setContent( &f ) )
546  {
547  return;
548  }
549  f.close();
550 
551  QDomElement elem = doc.documentElement();
552  readXml( elem, doc );
553  }
554 }
static QString qgisUserDatabaseFilePath()
Returns the path to the user qgis.db file.
Manages storage of a set of bookmarks.
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)
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.
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.
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.
bool operator!=(const QgsBookmark &other)
QString name() const
Returns the bookmark's name, which is a user-visible string identifying the bookmark.
This class 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.
bool writeXml(QDomNode &node, QDomDocument &doc) const
Stores state to the given Dom node in the given document.
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:99
int readNumEntry(const QString &scope, const QString &key, int def=0, bool *ok=nullptr) const
Reads an integer from the specified scope and key.
QString readEntry(const QString &scope, const QString &key, const QString &def=QString(), bool *ok=nullptr) const
Reads a string from the specified scope and key.
double readDoubleEntry(const QString &scope, const QString &key, double def=0, bool *ok=nullptr) const
Reads a double from the specified scope and key.
void setDirty(bool b=true)
Flag the project as dirty (modified).
Definition: qgsproject.cpp:552
A rectangle specified with double values.
Definition: qgsrectangle.h:42
static QgsRectangle fromWkt(const QString &wkt)
Creates a new rectangle from a wkt string.
QString asWktPolygon() const
Returns a string representation of the rectangle as a WKT Polygon.
QgsCoordinateReferenceSystem crs() const
Returns the associated coordinate reference system, or an invalid CRS if no reference system is set.
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:276
const QgsCoordinateReferenceSystem & crs