QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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 : std::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 : std::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 : std::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 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
415  out.setCodec( "UTF-8" );
416 #endif
417  doc.save( out, 2 );
418  f.close();
419 
420  return true;
421 }
422 
423 bool QgsBookmarkManager::importFromFile( const QString &path )
424 {
425  if ( path.isEmpty() )
426  {
427  return false;
428  }
429 
430  QFile f( path );
431  if ( !f.open( QIODevice::ReadOnly | QIODevice::Text ) )
432  {
433  return false;
434  }
435 
436  QDomDocument doc;
437  if ( !doc.setContent( &f ) )
438  {
439  return false;
440  }
441  f.close();
442 
443  QDomElement docElem = doc.documentElement();
444  QDomNodeList nodeList = docElem.elementsByTagName( QStringLiteral( "bookmark" ) );
445 
446  bool res = true;
447  for ( int i = 0; i < nodeList.count(); i++ )
448  {
449  QDomNode bookmark = nodeList.at( i );
450  QDomElement name = bookmark.firstChildElement( QStringLiteral( "name" ) );
451  QDomElement prjname = bookmark.firstChildElement( QStringLiteral( "project" ) );
452  QDomElement xmin = bookmark.firstChildElement( QStringLiteral( "xmin" ) );
453  QDomElement ymin = bookmark.firstChildElement( QStringLiteral( "ymin" ) );
454  QDomElement xmax = bookmark.firstChildElement( QStringLiteral( "xmax" ) );
455  QDomElement ymax = bookmark.firstChildElement( QStringLiteral( "ymax" ) );
456  QDomElement srid = bookmark.firstChildElement( QStringLiteral( "sr_id" ) );
457 
458  bool ok = false;
459  QgsBookmark b;
460  b.setName( name.text() );
461  b.setGroup( prjname.text() );
463  crs.createFromSrsId( srid.text().toLongLong() );
464  b.setExtent( QgsReferencedRectangle( QgsRectangle( xmin.text().toDouble(),
465  ymin.text().toDouble(),
466  xmax.text().toDouble(),
467  ymax.text().toDouble() ), crs ) );
468  addBookmark( b, &ok );
469  res = res && ok;
470  }
471 
472  return res;
473 }
474 
475 void QgsBookmarkManager::store()
476 {
477  if ( !mFilePath.isEmpty() )
478  {
479  QFile f( mFilePath );
480  if ( !f.open( QFile::WriteOnly | QIODevice::Truncate ) )
481  {
482  f.close();
483  return;
484  }
485 
486  QDomDocument doc;
487  QDomElement elem = writeXml( doc );
488  doc.appendChild( elem );
489 
490  QTextStream out( &f );
491 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
492  out.setCodec( "UTF-8" );
493 #endif
494  doc.save( out, 2 );
495  f.close();
496  }
497 }
498 
499 void QgsBookmarkManager::initialize( const QString &filePath )
500 {
501  if ( mInitialized )
502  return;
503 
504  mFilePath = filePath;
505 
506  mInitialized = true;
507 
508  // restore state
509  if ( !QFileInfo::exists( mFilePath ) )
510  {
511  //convert old bookmarks from db
513  int result = database.open( QgsApplication::qgisUserDatabaseFilePath() );
514  if ( result != SQLITE_OK )
515  {
516  return;
517  }
518 
519  sqlite3_statement_unique_ptr preparedStatement = database.prepare( QStringLiteral( "SELECT name,xmin,ymin,xmax,ymax,projection_srid FROM tbl_bookmarks" ), result );
520  if ( result == SQLITE_OK )
521  {
522  while ( preparedStatement.step() == SQLITE_ROW )
523  {
524  const QString name = preparedStatement.columnAsText( 0 );
525  const double xMin = preparedStatement.columnAsDouble( 1 );
526  const double yMin = preparedStatement.columnAsDouble( 2 );
527  const double xMax = preparedStatement.columnAsDouble( 3 );
528  const double yMax = preparedStatement.columnAsDouble( 4 );
529  const long long srid = preparedStatement.columnAsInt64( 5 );
530 
531  QgsBookmark b;
532  b.setName( name );
533  const QgsRectangle extent( xMin, yMin, xMax, yMax );
535  addBookmark( b );
536  }
537  }
538  store();
539  }
540  else
541  {
542  QFile f( mFilePath );
543  if ( !f.open( QIODevice::ReadOnly | QIODevice::Text ) )
544  {
545  return;
546  }
547 
548  QDomDocument doc;
549  if ( !doc.setContent( &f ) )
550  {
551  return;
552  }
553  f.close();
554 
555  QDomElement elem = doc.documentElement();
556  readXml( elem, doc );
557  }
558 }
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:101
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:518
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:1198
const QgsCoordinateReferenceSystem & crs