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