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